Refactoring of the screen magnification feature.

1. The screen magnification feature was implemented entirely as a part of the accessibility
   manager. To achieve that the window manager had to implement a bunch of hooks for an
   external client to observe its internal state. This was problematic since it dilutes
   the window manager interface and allows code that is deeply coupled with the window
   manager to reside outside of it. Also the observer callbacks were IPCs which cannot
   be called with the window manager's lock held. To avoid that the window manager had
   to post messages requesting notification of interested parties which makes the code
   consuming the callbacks to run asynchronously of the window manager. This causes timing
   issues and adds unnecessary complexity.

   Now the magnification logic is split in two halves. The first half that is responsible
   to track the magnified portion of the screen and serve as a policy which windows can be
   magnified and it is a part of the window manager. This part exposes higher level APIs
   allowing interested parties with the right permissions to control the magnification
   of a given display. The APIs also allow a client to be registered for callbacks on
   interesting changes such as resize of the magnified region, etc. This part servers
   as a mediator between magnification controllers and the window manager.

   The second half is a controller that is responsible to drive the magnification
   state based on touch interactions. It also presents a highlight when magnified to
   suggest the magnified potion of the screen. The controller is responsible for auto
   zooming out in case the user context changes - rotation, new actitivity. The controller
   also auto pans if a dialog appears and it does not interesect the magnified frame.

bug:7410464

2. By design screen magnification and touch exploration work separately and together. If
   magnification is enabled the user sees a larger version of the widgets and a sub section
   of the screen content. Accessibility services use the introspection APIs to "see" what
   is on the screen so they can speak it, navigate to the next item in response to a
   gesture, etc. Hence, the information returned to accessibility services has to reflect
   what a sighted user would see on the screen. Therefore, if the screen is magnified
   we need to adjust the bounds and position of the infos describing views in a magnified
   window such that the info bounds are equivalent to what the user sees.

   To improve performance we keep accessibility node info caches in the client process.
   However, when magnification state changes we have to clear these caches since the
   bounds of the cached infos no longer reflect the screen content which just got smaller
   or larger.

   This patch propagates not only the window scale as before but also the X/Y pan and the
   bounds of the magnified portion of the screen to the introspected app. This information
   is used to adjust the bounds of the node infos coming from this window such that the
   reported bounds are the same as the user sees not as the app thinks they are. Note that
   if magnification is enabled we zoom the content and pan it along the X and Y axis. Also
   recomputed is the isVisibleToUser property of the reported info since in a magnified
   state the user sees a subset of the window content and the views not in the magnified
   viewport should be reported as not visible to the user.

bug:7344059

Change-Id: I6f7832c7a6a65c5368b390eb1f1518d0c7afd7d2
diff --git a/Android.mk b/Android.mk
index f338fa1e..dc45b1c 100644
--- a/Android.mk
+++ b/Android.mk
@@ -154,7 +154,8 @@
 	core/java/android/view/accessibility/IAccessibilityManager.aidl \
 	core/java/android/view/accessibility/IAccessibilityManagerClient.aidl \
 	core/java/android/view/IApplicationToken.aidl \
-	core/java/android/view/IDisplayContentChangeListener.aidl \
+	core/java/android/view/IDisplayMagnificationMediator.aidl \
+	core/java/android/view/IDisplayMagnificationController.aidl \
 	core/java/android/view/IInputFilter.aidl \
 	core/java/android/view/IInputFilterHost.aidl \
 	core/java/android/view/IOnKeyguardExitResult.aidl \
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 01d09f6..425794a 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -141,6 +141,11 @@
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/telephony/java/com/android/internal/telephony/IExtendedNetworkService.java)
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/telephony/java/com/android/internal/telephony/IExtendedNetworkService.P)
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/ImageProcessing_intermediates)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/view/IDisplayContentChangeListener.java)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/view/IDisplayContentChangeListener.P)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/view/IWindowManager.java)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/view/IWindowManager.P)
+
 # ************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
 # ************************************************
diff --git a/api/current.txt b/api/current.txt
index 454e5da..2be13a0 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -24746,6 +24746,7 @@
     method public android.graphics.Canvas lockCanvas(android.graphics.Rect) throws java.lang.IllegalArgumentException, android.view.Surface.OutOfResourcesException;
     method public void readFromParcel(android.os.Parcel);
     method public void release();
+    method public static java.lang.String rotationToString(int);
     method public deprecated void unlockCanvas(android.graphics.Canvas);
     method public void unlockCanvasAndPost(android.graphics.Canvas);
     method public void writeToParcel(android.os.Parcel, int);
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index b0bad07..ecf3b19 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -548,6 +548,7 @@
         private static final int DO_ON_INTERRUPT = 20;
         private static final int DO_ON_ACCESSIBILITY_EVENT = 30;
         private static final int DO_ON_GESTURE = 40;
+        private static final int DO_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE = 50;
 
         private final HandlerCaller mCaller;
 
@@ -580,6 +581,11 @@
             mCaller.sendMessage(message);
         }
 
+        public void clearAccessibilityNodeInfoCache() {
+            Message message = mCaller.obtainMessage(DO_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE);
+            mCaller.sendMessage(message);
+        }
+
         public void executeMessage(Message message) {
             switch (message.what) {
                 case DO_ON_ACCESSIBILITY_EVENT :
@@ -611,6 +617,9 @@
                     final int gestureId = message.arg1;
                     mCallback.onGesture(gestureId);
                     return;
+                case DO_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE:
+                    AccessibilityInteractionClient.getInstance().clearCache();
+                    return;
                 default :
                     Log.w(LOG_TAG, "Unknown message type " + message.what);
             }
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
index d459fd5..5d684e3 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
@@ -33,4 +33,6 @@
     void onInterrupt();
 
     void onGesture(int gesture);
+
+    void clearAccessibilityNodeInfoCache();
 }
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index dd50f3c..f33f503 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -18,6 +18,7 @@
 
 import android.os.Bundle;
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.view.MagnificationSpec;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
 
@@ -44,9 +45,9 @@
      * @param callback Callback which to receive the result.
      * @param flags Additional flags.
      * @param threadId The id of the calling thread.
-     * @return The current window scale, where zero means a failure.
+     * @return Whether the call succeeded.
      */
-    float findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
+    boolean findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
         long accessibilityNodeId, int interactionId,
         IAccessibilityInteractionConnectionCallback callback, int flags, long threadId);
 
@@ -66,9 +67,9 @@
      * @param interactionId The id of the interaction for matching with the callback result.
      * @param callback Callback which to receive the result.
      * @param threadId The id of the calling thread.
-     * @return The current window scale, where zero means a failure.
+     * @return Whether the call succeeded.
      */
-    float findAccessibilityNodeInfosByText(int accessibilityWindowId, long accessibilityNodeId,
+    boolean findAccessibilityNodeInfosByText(int accessibilityWindowId, long accessibilityNodeId,
         String text, int interactionId, IAccessibilityInteractionConnectionCallback callback,
         long threadId);
 
@@ -88,9 +89,9 @@
      * @param interactionId The id of the interaction for matching with the callback result.
      * @param callback Callback which to receive the result.
      * @param threadId The id of the calling thread.
-     * @return The current window scale, where zero means a failure.
+     * @return Whether the call succeeded.
      */
-    float findAccessibilityNodeInfoByViewId(int accessibilityWindowId, long accessibilityNodeId,
+    boolean findAccessibilityNodeInfoByViewId(int accessibilityWindowId, long accessibilityNodeId,
         int viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback,
         long threadId);
 
@@ -110,9 +111,9 @@
      * @param interactionId The id of the interaction for matching with the callback result.
      * @param callback Callback which to receive the result.
      * @param threadId The id of the calling thread.
-     * @return The current window scale, where zero means a failure.
+     * @return Whether the call succeeded.
      */
-    float findFocus(int accessibilityWindowId, long accessibilityNodeId, int focusType,
+    boolean findFocus(int accessibilityWindowId, long accessibilityNodeId, int focusType,
         int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId);
 
     /**
@@ -131,9 +132,9 @@
      * @param interactionId The id of the interaction for matching with the callback result.
      * @param callback Callback which to receive the result.
      * @param threadId The id of the calling thread.
-     * @return The current window scale, where zero means a failure.
+     * @return Whether the call succeeded.
      */
-    float focusSearch(int accessibilityWindowId, long accessibilityNodeId, int direction,
+    boolean focusSearch(int accessibilityWindowId, long accessibilityNodeId, int direction,
         int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId);
 
     /**
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index 9bee4bf..ba82d79 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -18,6 +18,7 @@
 
 import static android.view.accessibility.AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS;
 
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.Handler;
@@ -26,6 +27,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.util.SparseLongArray;
+import android.view.View.AttachInfo;
 import android.view.accessibility.AccessibilityInteractionClient;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeProvider;
@@ -62,7 +64,10 @@
 
     private final ArrayList<View> mTempArrayList = new ArrayList<View>();
 
+    private final Point mTempPoint = new Point();
     private final Rect mTempRect = new Rect();
+    private final Rect mTempRect1 = new Rect();
+    private final Rect mTempRect2 = new Rect();
 
     public AccessibilityInteractionController(ViewRootImpl viewRootImpl) {
         Looper looper =  viewRootImpl.mHandler.getLooper();
@@ -86,7 +91,7 @@
     public void findAccessibilityNodeInfoByAccessibilityIdClientThread(
             long accessibilityNodeId, int interactionId,
             IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
-            long interrogatingTid) {
+            long interrogatingTid, MagnificationSpec spec) {
         Message message = mHandler.obtainMessage();
         message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID;
         message.arg1 = flags;
@@ -96,6 +101,7 @@
         args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
         args.argi3 = interactionId;
         args.arg1 = callback;
+        args.arg2 = spec;
         message.obj = args;
 
         // If the interrogation is performed by the same thread as the main UI
@@ -119,6 +125,7 @@
         final int interactionId = args.argi3;
         final IAccessibilityInteractionConnectionCallback callback =
             (IAccessibilityInteractionConnectionCallback) args.arg1;
+        final MagnificationSpec spec = (MagnificationSpec) args.arg2;
 
         args.recycle();
 
@@ -142,7 +149,10 @@
         } finally {
             try {
                 mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
-                applyApplicationScaleIfNeeded(infos);
+                applyAppScaleAndMagnificationSpecIfNeeded(infos, spec);
+                if (spec != null) {
+                    spec.recycle();
+                }
                 callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
                 infos.clear();
             } catch (RemoteException re) {
@@ -153,7 +163,7 @@
 
     public void findAccessibilityNodeInfoByViewIdClientThread(long accessibilityNodeId,
             int viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback,
-            int flags, int interrogatingPid, long interrogatingTid) {
+            int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
         Message message = mHandler.obtainMessage();
         message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID;
         message.arg1 = flags;
@@ -163,6 +173,7 @@
         args.argi1 = viewId;
         args.argi2 = interactionId;
         args.arg1 = callback;
+        args.arg2 = spec;
 
         message.obj = args;
 
@@ -187,6 +198,7 @@
         final int interactionId = args.argi2;
         final IAccessibilityInteractionConnectionCallback callback =
             (IAccessibilityInteractionConnectionCallback) args.arg1;
+        final MagnificationSpec spec = (MagnificationSpec) args.arg2;
 
         args.recycle();
 
@@ -212,7 +224,10 @@
         } finally {
             try {
                 mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
-                applyApplicationScaleIfNeeded(info);
+                applyAppScaleAndMagnificationSpecIfNeeded(info, spec);
+                if (spec != null) {
+                    spec.recycle();
+                }
                 callback.setFindAccessibilityNodeInfoResult(info, interactionId);
             } catch (RemoteException re) {
                 /* ignore - the other side will time out */
@@ -222,7 +237,7 @@
 
     public void findAccessibilityNodeInfosByTextClientThread(long accessibilityNodeId,
             String text, int interactionId, IAccessibilityInteractionConnectionCallback callback,
-            int flags, int interrogatingPid, long interrogatingTid) {
+            int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
         Message message = mHandler.obtainMessage();
         message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT;
         message.arg1 = flags;
@@ -230,10 +245,10 @@
         SomeArgs args = SomeArgs.obtain();
         args.arg1 = text;
         args.arg2 = callback;
+        args.arg3 = spec;
         args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
         args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
         args.argi3 = interactionId;
-
         message.obj = args;
 
         // If the interrogation is performed by the same thread as the main UI
@@ -255,6 +270,7 @@
         final String text = (String) args.arg1;
         final IAccessibilityInteractionConnectionCallback callback =
             (IAccessibilityInteractionConnectionCallback) args.arg2;
+        final MagnificationSpec spec = (MagnificationSpec) args.arg3;
         final int accessibilityViewId = args.argi1;
         final int virtualDescendantId = args.argi2;
         final int interactionId = args.argi3;
@@ -310,7 +326,10 @@
         } finally {
             try {
                 mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
-                applyApplicationScaleIfNeeded(infos);
+                applyAppScaleAndMagnificationSpecIfNeeded(infos, spec);
+                if (spec != null) {
+                    spec.recycle();
+                }
                 callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
             } catch (RemoteException re) {
                 /* ignore - the other side will time out */
@@ -320,7 +339,7 @@
 
     public void findFocusClientThread(long accessibilityNodeId, int focusType, int interactionId,
             IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid,
-            long interrogatingTid) {
+            long interrogatingTid, MagnificationSpec spec) {
         Message message = mHandler.obtainMessage();
         message.what = PrivateHandler.MSG_FIND_FOCUS;
         message.arg1 = flags;
@@ -331,6 +350,7 @@
         args.argi2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
         args.argi3 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
         args.arg1 = callback;
+        args.arg2 = spec;
 
         message.obj = args;
 
@@ -356,7 +376,7 @@
         final int virtualDescendantId = args.argi3;
         final IAccessibilityInteractionConnectionCallback callback =
             (IAccessibilityInteractionConnectionCallback) args.arg1;
-
+        final MagnificationSpec spec = (MagnificationSpec) args.arg2;
         args.recycle();
 
         AccessibilityNodeInfo focused = null;
@@ -407,7 +427,10 @@
         } finally {
             try {
                 mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
-                applyApplicationScaleIfNeeded(focused);
+                applyAppScaleAndMagnificationSpecIfNeeded(focused, spec);
+                if (spec != null) {
+                    spec.recycle();
+                }
                 callback.setFindAccessibilityNodeInfoResult(focused, interactionId);
             } catch (RemoteException re) {
                 /* ignore - the other side will time out */
@@ -417,7 +440,7 @@
 
     public void focusSearchClientThread(long accessibilityNodeId, int direction, int interactionId,
             IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid,
-            long interrogatingTid) {
+            long interrogatingTid, MagnificationSpec spec) {
         Message message = mHandler.obtainMessage();
         message.what = PrivateHandler.MSG_FOCUS_SEARCH;
         message.arg1 = flags;
@@ -427,6 +450,7 @@
         args.argi2 = direction;
         args.argi3 = interactionId;
         args.arg1 = callback;
+        args.arg2 = spec;
 
         message.obj = args;
 
@@ -451,6 +475,7 @@
         final int interactionId = args.argi3;
         final IAccessibilityInteractionConnectionCallback callback =
             (IAccessibilityInteractionConnectionCallback) args.arg1;
+        final MagnificationSpec spec = (MagnificationSpec) args.arg2;
 
         args.recycle();
 
@@ -476,7 +501,10 @@
         } finally {
             try {
                 mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
-                applyApplicationScaleIfNeeded(next);
+                applyAppScaleAndMagnificationSpecIfNeeded(next, spec);
+                if (spec != null) {
+                    spec.recycle();
+                }
                 callback.setFindAccessibilityNodeInfoResult(next, interactionId);
             } catch (RemoteException re) {
                 /* ignore - the other side will time out */
@@ -572,38 +600,84 @@
         return foundView;
     }
 
-    private void applyApplicationScaleIfNeeded(List<AccessibilityNodeInfo> infos) {
+    private void applyAppScaleAndMagnificationSpecIfNeeded(List<AccessibilityNodeInfo> infos,
+            MagnificationSpec spec) {
         if (infos == null) {
             return;
         }
         final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
-        if (applicationScale != 1.0f) {
+        if (shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) {
             final int infoCount = infos.size();
             for (int i = 0; i < infoCount; i++) {
                 AccessibilityNodeInfo info = infos.get(i);
-                applyApplicationScaleIfNeeded(info);
+                applyAppScaleAndMagnificationSpecIfNeeded(info, spec);
             }
         }
     }
 
-    private void applyApplicationScaleIfNeeded(AccessibilityNodeInfo info) {
+    private void applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityNodeInfo info,
+            MagnificationSpec spec) {
         if (info == null) {
             return;
         }
+
         final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
+        if (!shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) {
+            return;
+        }
+
+        Rect boundsInParent = mTempRect;
+        Rect boundsInScreen = mTempRect1;
+
+        info.getBoundsInParent(boundsInParent);
+        info.getBoundsInScreen(boundsInScreen);
         if (applicationScale != 1.0f) {
-            Rect bounds = mTempRect;
+            boundsInParent.scale(applicationScale);
+            boundsInScreen.scale(applicationScale);
+        }
+        if (spec != null) {
+            boundsInParent.scale(spec.scale);
+            // boundsInParent must not be offset.
+            boundsInScreen.scale(spec.scale);
+            boundsInScreen.offset((int) spec.offsetX, (int) spec.offsetY);
+        }
+        info.setBoundsInParent(boundsInParent);
+        info.setBoundsInScreen(boundsInScreen);
 
-            info.getBoundsInParent(bounds);
-            bounds.scale(applicationScale);
-            info.setBoundsInParent(bounds);
+        if (spec != null) {
+            AttachInfo attachInfo = mViewRootImpl.mAttachInfo;
+            if (attachInfo.mDisplay == null) {
+                return;
+            }
 
-            info.getBoundsInScreen(bounds);
-            bounds.scale(applicationScale);
-            info.setBoundsInScreen(bounds);
+            final float scale = attachInfo.mApplicationScale * spec.scale;
+
+            Rect visibleWinFrame = mTempRect1;
+            visibleWinFrame.left = (int) (attachInfo.mWindowLeft * scale + spec.offsetX);
+            visibleWinFrame.top = (int) (attachInfo.mWindowTop * scale + spec.offsetY);
+            visibleWinFrame.right = (int) (visibleWinFrame.left + mViewRootImpl.mWidth * scale);
+            visibleWinFrame.bottom = (int) (visibleWinFrame.top + mViewRootImpl.mHeight * scale);
+
+            attachInfo.mDisplay.getRealSize(mTempPoint);
+            final int displayWidth = mTempPoint.x;
+            final int displayHeight = mTempPoint.y;
+
+            Rect visibleDisplayFrame = mTempRect2;
+            visibleDisplayFrame.set(0, 0, displayWidth, displayHeight);
+
+            visibleWinFrame.intersect(visibleDisplayFrame);
+
+            if (!visibleWinFrame.intersects(boundsInScreen.left, boundsInScreen.top,
+                    boundsInScreen.right, boundsInScreen.bottom)) {
+                info.setVisibleToUser(false);
+            }
         }
     }
 
+    private boolean shouldApplyAppScaleAndMagnificationSpec(float appScale,
+            MagnificationSpec spec) {
+        return (appScale != 1.0f || (spec != null && !spec.isNop()));
+    }
 
     /**
      * This class encapsulates a prefetching strategy for the accessibility APIs for
diff --git a/core/java/android/view/IDisplayContentChangeListener.aidl b/core/java/android/view/IDisplayContentChangeListener.aidl
deleted file mode 100644
index ef7edea..0000000
--- a/core/java/android/view/IDisplayContentChangeListener.aidl
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License")
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
-
-package android.view;
-
-import android.os.IBinder;
-import android.view.WindowInfo;
-import android.graphics.Rect;
-
-/**
- * Interface for observing content changes on a display.
- *
- * {@hide}
- */
-oneway interface IDisplayContentChangeListener {
-    void onWindowTransition(int displayId, int transition, in WindowInfo info);
-    void onRectangleOnScreenRequested(int displayId, in Rect rectangle, boolean immediate);
-    void onWindowLayersChanged(int displayId);
-    void onRotationChanged(int rotation);
-}
diff --git a/core/java/android/view/WindowInfo.aidl b/core/java/android/view/IDisplayMagnificationController.aidl
similarity index 67%
copy from core/java/android/view/WindowInfo.aidl
copy to core/java/android/view/IDisplayMagnificationController.aidl
index 23e927a..efe2775 100644
--- a/core/java/android/view/WindowInfo.aidl
+++ b/core/java/android/view/IDisplayMagnificationController.aidl
@@ -1,8 +1,7 @@
 /*
-**
 ** Copyright 2012, The Android Open Source Project
 **
-** Licensed under the Apache License, Version 2.0 (the "License")
+** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
 ** You may obtain a copy of the License at
 **
@@ -17,4 +16,12 @@
 
 package android.view;
 
-parcelable WindowInfo;
+/**
+ * {@hide}
+ */
+oneway interface IDisplayMagnificationController {
+    void onMagnifedFrameChanged(int left, int top, int right, int bottom);
+    void onRectangleOnScreenRequested(int left, int top, int right, int bottom);
+    void onRotationChanged(int rotation);
+    void onUserContextChanged();
+}
diff --git a/core/java/android/view/IDisplayMagnificationMediator.aidl b/core/java/android/view/IDisplayMagnificationMediator.aidl
new file mode 100644
index 0000000..aa25dac
--- /dev/null
+++ b/core/java/android/view/IDisplayMagnificationMediator.aidl
@@ -0,0 +1,31 @@
+/*
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.view;
+
+import android.view.IDisplayMagnificationController;
+import android.view.MagnificationSpec;
+
+/**
+ * {@hide}
+ */
+interface IDisplayMagnificationMediator {
+    void addController(int displayId, in IDisplayMagnificationController controller);
+    void removeController(in IDisplayMagnificationController controller);
+    void setMagnificationSpec(in IDisplayMagnificationController controller,
+            in MagnificationSpec spec);
+    MagnificationSpec getCompatibleMagnificationSpec(in IBinder windowToken);
+}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 0fe2a8e..17f04e9 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -27,17 +27,17 @@
 import android.os.Bundle;
 import android.os.IRemoteCallback;
 import android.view.IApplicationToken;
-import android.view.IDisplayContentChangeListener;
+import android.view.IDisplayMagnificationMediator;
 import android.view.IOnKeyguardExitResult;
 import android.view.IRotationWatcher;
 import android.view.IWindowSession;
 import android.view.KeyEvent;
 import android.view.InputEvent;
+import android.view.MagnificationSpec;
 import android.view.MotionEvent;
 import android.view.InputChannel;
 import android.view.InputDevice;
 import android.view.IInputFilter;
-import android.view.WindowInfo;
 
 /**
  * System private interface to the window manager.
@@ -221,39 +221,19 @@
     IBinder getFocusedWindowToken();
 
     /**
-     * Gets the compatibility scale of e window given its token.
-     */
-    float getWindowCompatibilityScale(IBinder windowToken);
-
-    /**
      * Sets an input filter for manipulating the input event stream.
      */
     void setInputFilter(in IInputFilter filter);
 
     /**
-     * Sets the scale and offset for implementing accessibility magnification.
+     * Gets the display magnification mediator.
      */
-    void magnifyDisplay(int dipslayId, float scale, float offsetX, float offsetY);
+    IDisplayMagnificationMediator getDisplayMagnificationMediator();
 
     /**
-     * Adds a listener for display content changes.
+     * Gets the frame of a window given its token.
      */
-    void addDisplayContentChangeListener(int displayId, IDisplayContentChangeListener listener);
-
-    /**
-     * Removes a listener for display content changes.
-     */
-    void removeDisplayContentChangeListener(int displayId, IDisplayContentChangeListener listener);
-
-    /**
-     * Gets the info for a window given its token.
-     */
-    WindowInfo getWindowInfo(IBinder token);
-
-    /**
-     * Gets the infos for all visible windows.
-     */
-    void getVisibleWindowsForDisplay(int displayId, out List<WindowInfo> outInfos);
+    void getWindowFrame(IBinder token, out Rect outFrame);
 
     /**
      * Device is in safe mode.
diff --git a/core/java/android/view/WindowInfo.aidl b/core/java/android/view/MagnificationSpec.aidl
similarity index 95%
rename from core/java/android/view/WindowInfo.aidl
rename to core/java/android/view/MagnificationSpec.aidl
index 23e927a..d5fbdef 100644
--- a/core/java/android/view/WindowInfo.aidl
+++ b/core/java/android/view/MagnificationSpec.aidl
@@ -17,4 +17,4 @@
 
 package android.view;
 
-parcelable WindowInfo;
+parcelable MagnificationSpec;
diff --git a/core/java/android/view/MagnificationSpec.java b/core/java/android/view/MagnificationSpec.java
new file mode 100644
index 0000000..7fb5615
--- /dev/null
+++ b/core/java/android/view/MagnificationSpec.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Pools.SynchronizedPool;
+
+/**
+ * This class represents spec for performing screen magnification.
+ *
+ * @hide
+ */
+public class MagnificationSpec implements Parcelable {
+    private static final int MAX_POOL_SIZE = 20;
+    private static final SynchronizedPool<MagnificationSpec> sPool =
+            new SynchronizedPool<MagnificationSpec>(MAX_POOL_SIZE);
+
+    public float scale = 1.0f;
+    public float offsetX;
+    public float offsetY;
+
+    private MagnificationSpec() {
+        /* do nothing - reducing visibility */
+    }
+
+    public void initialize(float scale, float offsetX, float offsetY) {
+        this.scale = scale;
+        this.offsetX = offsetX;
+        this.offsetY = offsetY;
+    }
+
+    public boolean isNop() {
+        return scale == 1.0f && offsetX == 0 && offsetY == 0;
+    }
+
+    public static MagnificationSpec obtain(MagnificationSpec other) {
+        MagnificationSpec info = obtain();
+        info.scale = other.scale;
+        info.offsetX = other.offsetX;
+        info.offsetY = other.offsetY;
+        return info;
+    }
+
+    public static MagnificationSpec obtain() {
+        MagnificationSpec spec = sPool.acquire();
+        return (spec != null) ? spec : new MagnificationSpec();
+    }
+
+    public void recycle() {
+        clear();
+        sPool.release(this);
+    }
+
+    public void clear() {
+       scale = 1.0f;
+       offsetX = 0.0f;
+       offsetY = 0.0f;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeFloat(scale);
+        parcel.writeFloat(offsetX);
+        parcel.writeFloat(offsetY);
+        recycle();
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("<scale:");
+        builder.append(scale);
+        builder.append(",offsetX:");
+        builder.append(offsetX);
+        builder.append(",offsetY:");
+        builder.append(offsetY);
+        builder.append(">");
+        return builder.toString();
+    }
+
+    private void initFromParcel(Parcel parcel) {
+        scale = parcel.readFloat();
+        offsetX = parcel.readFloat();
+        offsetY = parcel.readFloat();
+    }
+
+    public static final Creator<MagnificationSpec> CREATOR = new Creator<MagnificationSpec>() {
+        @Override
+        public MagnificationSpec[] newArray(int size) {
+            return new MagnificationSpec[size];
+        }
+
+        @Override
+        public MagnificationSpec createFromParcel(Parcel parcel) {
+            MagnificationSpec spec = MagnificationSpec.obtain();
+            spec.initFromParcel(parcel);
+            return spec;
+        }
+    };
+}
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 550a740..f2c5eac 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -808,6 +808,32 @@
     }
 
     /**
+     * Returns a human readable representation of a rotation.
+     *
+     * @param rotation The rotation.
+     * @return The rotation symbolic name.
+     */
+    public static String rotationToString(int rotation) {
+        switch (rotation) {
+            case Surface.ROTATION_0: {
+                return "ROTATION_0";
+            }
+            case Surface.ROTATION_90: {
+                return "ROATATION_90";
+            }
+            case Surface.ROTATION_180: {
+                return "ROATATION_180";
+            }
+            case Surface.ROTATION_270: {
+                return "ROATATION_270";
+            }
+            default: {
+                throw new IllegalArgumentException("Invalid rotation: " + rotation);
+            }
+        }
+    }
+
+    /**
      * A Canvas class that can handle the compatibility mode.
      * This does two things differently.
      * <ul>
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e66e30f..5ad7bb3 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -5416,12 +5416,13 @@
         @Override
         public void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId,
                 int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
-                int interrogatingPid, long interrogatingTid) {
+                int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
             ViewRootImpl viewRootImpl = mViewRootImpl.get();
             if (viewRootImpl != null && viewRootImpl.mView != null) {
                 viewRootImpl.getAccessibilityInteractionController()
                     .findAccessibilityNodeInfoByAccessibilityIdClientThread(accessibilityNodeId,
-                            interactionId, callback, flags, interrogatingPid, interrogatingTid);
+                            interactionId, callback, flags, interrogatingPid, interrogatingTid,
+                            spec);
             } else {
                 // We cannot make the call and notify the caller so it does not wait.
                 try {
@@ -5455,12 +5456,13 @@
         @Override
         public void findAccessibilityNodeInfoByViewId(long accessibilityNodeId, int viewId,
                 int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
-                int interrogatingPid, long interrogatingTid) {
+                int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
             ViewRootImpl viewRootImpl = mViewRootImpl.get();
             if (viewRootImpl != null && viewRootImpl.mView != null) {
                 viewRootImpl.getAccessibilityInteractionController()
                     .findAccessibilityNodeInfoByViewIdClientThread(accessibilityNodeId, viewId,
-                            interactionId, callback, flags, interrogatingPid, interrogatingTid);
+                            interactionId, callback, flags, interrogatingPid, interrogatingTid,
+                            spec);
             } else {
                 // We cannot make the call and notify the caller so it does not wait.
                 try {
@@ -5474,12 +5476,13 @@
         @Override
         public void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text,
                 int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
-                int interrogatingPid, long interrogatingTid) {
+                int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
             ViewRootImpl viewRootImpl = mViewRootImpl.get();
             if (viewRootImpl != null && viewRootImpl.mView != null) {
                 viewRootImpl.getAccessibilityInteractionController()
                     .findAccessibilityNodeInfosByTextClientThread(accessibilityNodeId, text,
-                            interactionId, callback, flags, interrogatingPid, interrogatingTid);
+                            interactionId, callback, flags, interrogatingPid, interrogatingTid,
+                            spec);
             } else {
                 // We cannot make the call and notify the caller so it does not wait.
                 try {
@@ -5493,12 +5496,12 @@
         @Override
         public void findFocus(long accessibilityNodeId, int focusType, int interactionId,
                 IAccessibilityInteractionConnectionCallback callback, int flags,
-                int interrogatingPid, long interrogatingTid) {
+                int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
             ViewRootImpl viewRootImpl = mViewRootImpl.get();
             if (viewRootImpl != null && viewRootImpl.mView != null) {
                 viewRootImpl.getAccessibilityInteractionController()
                     .findFocusClientThread(accessibilityNodeId, focusType, interactionId, callback,
-                            flags, interrogatingPid, interrogatingTid);
+                            flags, interrogatingPid, interrogatingTid, spec);
             } else {
                 // We cannot make the call and notify the caller so it does not wait.
                 try {
@@ -5512,12 +5515,12 @@
         @Override
         public void focusSearch(long accessibilityNodeId, int direction, int interactionId,
                 IAccessibilityInteractionConnectionCallback callback, int flags,
-                int interrogatingPid, long interrogatingTid) {
+                int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
             ViewRootImpl viewRootImpl = mViewRootImpl.get();
             if (viewRootImpl != null && viewRootImpl.mView != null) {
                 viewRootImpl.getAccessibilityInteractionController()
                     .focusSearchClientThread(accessibilityNodeId, direction, interactionId,
-                            callback, flags, interrogatingPid, interrogatingTid);
+                            callback, flags, interrogatingPid, interrogatingTid, spec);
             } else {
                 // We cannot make the call and notify the caller so it does not wait.
                 try {
diff --git a/core/java/android/view/WindowInfo.java b/core/java/android/view/WindowInfo.java
deleted file mode 100644
index 7d16e14..0000000
--- a/core/java/android/view/WindowInfo.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import android.graphics.Rect;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * Information the state of a window.
- *
- * @hide
- */
-public class WindowInfo implements Parcelable {
-
-    private static final int MAX_POOL_SIZE = 20;
-
-    private static int UNDEFINED = -1;
-
-    private static Object sPoolLock = new Object();
-    private static WindowInfo sPool;
-    private static int sPoolSize;
-
-    private WindowInfo mNext;
-    private boolean mInPool;
-
-    public IBinder token;
-
-    public final Rect frame = new Rect();
-
-    public final Rect touchableRegion = new Rect();
-
-    public int type = UNDEFINED;
-
-    public float compatibilityScale = UNDEFINED;
-
-    public boolean visible;
-
-    public int displayId = UNDEFINED;
-
-    public int layer = UNDEFINED;
-
-    private WindowInfo() {
-        /* do nothing - reduce visibility */
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel parcel, int flags) {
-        parcel.writeStrongBinder(token);
-        parcel.writeParcelable(frame, 0);
-        parcel.writeParcelable(touchableRegion, 0);
-        parcel.writeInt(type);
-        parcel.writeFloat(compatibilityScale);
-        parcel.writeInt(visible ? 1 : 0);
-        parcel.writeInt(displayId);
-        parcel.writeInt(layer);
-        recycle();
-    }
-
-    private void initFromParcel(Parcel parcel) {
-        token = parcel.readStrongBinder();
-        frame.set((Rect) parcel.readParcelable(null));
-        touchableRegion.set((Rect) parcel.readParcelable(null));
-        type = parcel.readInt();
-        compatibilityScale = parcel.readFloat();
-        visible = (parcel.readInt() == 1);
-        displayId = parcel.readInt();
-        layer = parcel.readInt();
-    }
-
-    public static WindowInfo obtain(WindowInfo other) {
-        WindowInfo info = obtain();
-        info.token = other.token;
-        info.frame.set(other.frame);
-        info.touchableRegion.set(other.touchableRegion);
-        info.type = other.type;
-        info.compatibilityScale = other.compatibilityScale;
-        info.visible = other.visible;
-        info.displayId = other.displayId;
-        info.layer = other.layer;
-        return info;
-    }
-
-    public static WindowInfo obtain() {
-        synchronized (sPoolLock) {
-            if (sPoolSize > 0) {
-                WindowInfo info = sPool;
-                sPool = info.mNext;
-                info.mNext = null;
-                info.mInPool = false;
-                sPoolSize--;
-                return info;
-            } else {
-                return new WindowInfo();
-            }
-        }
-    }
-
-    public void recycle() {
-        if (mInPool) {
-            throw new IllegalStateException("Already recycled.");
-        }
-        clear();
-        synchronized (sPoolLock) {
-            if (sPoolSize < MAX_POOL_SIZE) {
-                mNext = sPool;
-                sPool = this;
-                mInPool = true;
-                sPoolSize++;
-            }
-        }
-    }
-
-    private void clear() {
-        token = null;
-        frame.setEmpty();
-        touchableRegion.setEmpty();
-        type = UNDEFINED;
-        compatibilityScale = UNDEFINED;
-        visible = false;
-        displayId = UNDEFINED;
-        layer = UNDEFINED;
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder builder = new StringBuilder();
-        builder.append("Window [token:").append((token != null) ? token.hashCode() : null);
-        builder.append(", displayId:").append(displayId);
-        builder.append(", type:").append(type);
-        builder.append(", visible:").append(visible);
-        builder.append(", layer:").append(layer);
-        builder.append(", compatibilityScale:").append(compatibilityScale);
-        builder.append(", frame:").append(frame);
-        builder.append(", touchableRegion:").append(touchableRegion);
-        builder.append("]");
-        return builder.toString();
-    }
-
-    /**
-     * @see Parcelable.Creator
-     */
-    public static final Parcelable.Creator<WindowInfo> CREATOR =
-            new Parcelable.Creator<WindowInfo>() {
-        public WindowInfo createFromParcel(Parcel parcel) {
-            WindowInfo info = WindowInfo.obtain();
-            info.initFromParcel(parcel);
-            return info;
-        }
-
-        public WindowInfo[] newArray(int size) {
-            return new WindowInfo[size];
-        }
-    };
-}
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 26739b3..7bdb44c 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -1112,14 +1112,6 @@
     public void setLastInputMethodWindowLw(WindowState ime, WindowState target);
 
     /**
-     * Returns whether magnification can be applied to the given window type.
-     *
-     * @param attrs The window's LayoutParams.
-     * @return Whether magnification can be applied.
-     */
-    public boolean canMagnifyWindowLw(WindowManager.LayoutParams attrs);
-
-    /**
      * Called when the current user changes. Guaranteed to be called before the broadcast
      * of the new user id is made to all listeners.
      *
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 20b5f17..67df684 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -17,7 +17,6 @@
 package android.view.accessibility;
 
 import android.accessibilityservice.IAccessibilityServiceConnection;
-import android.graphics.Rect;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -102,8 +101,6 @@
 
     private Message mSameThreadMessage;
 
-    private final Rect mTempBounds = new Rect();
-
     // The connection cache is shared between all interrogating threads.
     private static final SparseArray<IAccessibilityServiceConnection> sConnectionCache =
         new SparseArray<IAccessibilityServiceConnection>();
@@ -194,14 +191,14 @@
                     return cachedInfo;
                 }
                 final int interactionId = mInteractionIdCounter.getAndIncrement();
-                final float windowScale = connection.findAccessibilityNodeInfoByAccessibilityId(
+                final boolean success = connection.findAccessibilityNodeInfoByAccessibilityId(
                         accessibilityWindowId, accessibilityNodeId, interactionId, this,
                         prefetchFlags, Thread.currentThread().getId());
                 // If the scale is zero the call has failed.
-                if (windowScale > 0) {
+                if (success) {
                     List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
                             interactionId);
-                    finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, windowScale);
+                    finalizeAndCacheAccessibilityNodeInfos(infos, connectionId);
                     if (infos != null && !infos.isEmpty()) {
                         return infos.get(0);
                     }
@@ -242,15 +239,13 @@
             IAccessibilityServiceConnection connection = getConnection(connectionId);
             if (connection != null) {
                 final int interactionId = mInteractionIdCounter.getAndIncrement();
-                final float windowScale =
-                    connection.findAccessibilityNodeInfoByViewId(accessibilityWindowId,
-                            accessibilityNodeId, viewId, interactionId, this,
-                            Thread.currentThread().getId());
-                // If the scale is zero the call has failed.
-                if (windowScale > 0) {
+                final boolean success =connection.findAccessibilityNodeInfoByViewId(
+                        accessibilityWindowId, accessibilityNodeId, viewId, interactionId, this,
+                        Thread.currentThread().getId());
+                if (success) {
                     AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
                             interactionId);
-                    finalizeAndCacheAccessibilityNodeInfo(info, connectionId, windowScale);
+                    finalizeAndCacheAccessibilityNodeInfo(info, connectionId);
                     return info;
                 }
             } else {
@@ -290,14 +285,13 @@
             IAccessibilityServiceConnection connection = getConnection(connectionId);
             if (connection != null) {
                 final int interactionId = mInteractionIdCounter.getAndIncrement();
-                final float windowScale = connection.findAccessibilityNodeInfosByText(
+                final boolean success = connection.findAccessibilityNodeInfosByText(
                         accessibilityWindowId, accessibilityNodeId, text, interactionId, this,
                         Thread.currentThread().getId());
-                // If the scale is zero the call has failed.
-                if (windowScale > 0) {
+                if (success) {
                     List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
                             interactionId);
-                    finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, windowScale);
+                    finalizeAndCacheAccessibilityNodeInfos(infos, connectionId);
                     return infos;
                 }
             } else {
@@ -336,14 +330,13 @@
             IAccessibilityServiceConnection connection = getConnection(connectionId);
             if (connection != null) {
                 final int interactionId = mInteractionIdCounter.getAndIncrement();
-                final float windowScale = connection.findFocus(accessibilityWindowId,
+                final boolean success = connection.findFocus(accessibilityWindowId,
                         accessibilityNodeId, focusType, interactionId, this,
                         Thread.currentThread().getId());
-                // If the scale is zero the call has failed.
-                if (windowScale > 0) {
+                if (success) {
                     AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
                             interactionId);
-                    finalizeAndCacheAccessibilityNodeInfo(info, connectionId, windowScale);
+                    finalizeAndCacheAccessibilityNodeInfo(info, connectionId);
                     return info;
                 }
             } else {
@@ -381,14 +374,13 @@
             IAccessibilityServiceConnection connection = getConnection(connectionId);
             if (connection != null) {
                 final int interactionId = mInteractionIdCounter.getAndIncrement();
-                final float windowScale = connection.focusSearch(accessibilityWindowId,
+                final boolean success = connection.focusSearch(accessibilityWindowId,
                         accessibilityNodeId, direction, interactionId, this,
                         Thread.currentThread().getId());
-                // If the scale is zero the call has failed.
-                if (windowScale > 0) {
+                if (success) {
                     AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
                             interactionId);
-                    finalizeAndCacheAccessibilityNodeInfo(info, connectionId, windowScale);
+                    finalizeAndCacheAccessibilityNodeInfo(info, connectionId);
                     return info;
                 }
             } else {
@@ -604,36 +596,14 @@
     }
 
     /**
-     * Applies compatibility scale to the info bounds if it is not equal to one.
-     *
-     * @param info The info whose bounds to scale.
-     * @param scale The scale to apply.
-     */
-    private void applyCompatibilityScaleIfNeeded(AccessibilityNodeInfo info, float scale) {
-        if (scale == 1.0f) {
-            return;
-        }
-        Rect bounds = mTempBounds;
-        info.getBoundsInParent(bounds);
-        bounds.scale(scale);
-        info.setBoundsInParent(bounds);
-
-        info.getBoundsInScreen(bounds);
-        bounds.scale(scale);
-        info.setBoundsInScreen(bounds);
-    }
-
-    /**
      * Finalize an {@link AccessibilityNodeInfo} before passing it to the client.
      *
      * @param info The info.
      * @param connectionId The id of the connection to the system.
-     * @param windowScale The source window compatibility scale.
      */
-    private void finalizeAndCacheAccessibilityNodeInfo(AccessibilityNodeInfo info, int connectionId,
-            float windowScale) {
+    private void finalizeAndCacheAccessibilityNodeInfo(AccessibilityNodeInfo info,
+            int connectionId) {
         if (info != null) {
-            applyCompatibilityScaleIfNeeded(info, windowScale);
             info.setConnectionId(connectionId);
             info.setSealed(true);
             sAccessibilityNodeInfoCache.add(info);
@@ -645,15 +615,14 @@
      *
      * @param infos The {@link AccessibilityNodeInfo}s.
      * @param connectionId The id of the connection to the system.
-     * @param windowScale The source window compatibility scale.
      */
     private void finalizeAndCacheAccessibilityNodeInfos(List<AccessibilityNodeInfo> infos,
-            int connectionId, float windowScale) {
+            int connectionId) {
         if (infos != null) {
             final int infosCount = infos.size();
             for (int i = 0; i < infosCount; i++) {
                 AccessibilityNodeInfo info = infos.get(i);
-                finalizeAndCacheAccessibilityNodeInfo(info, connectionId, windowScale);
+                finalizeAndCacheAccessibilityNodeInfo(info, connectionId);
             }
         }
     }
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
index 9b39300..c313b07 100644
--- a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
@@ -17,6 +17,7 @@
 package android.view.accessibility;
 
 import android.os.Bundle;
+import android.view.MagnificationSpec;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
 
@@ -30,23 +31,23 @@
 
     void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId, int interactionId,
         IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
-        long interrogatingTid);
+        long interrogatingTid, in MagnificationSpec spec);
 
     void findAccessibilityNodeInfoByViewId(long accessibilityNodeId, int viewId, int interactionId,
         IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
-        long interrogatingTid);
+        long interrogatingTid, in MagnificationSpec spec);
 
     void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text, int interactionId,
         IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
-        long interrogatingTid);
+        long interrogatingTid, in MagnificationSpec spec);
 
     void findFocus(long accessibilityNodeId, int focusType, int interactionId,
         IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
-        long interrogatingTid);
+        long interrogatingTid, in MagnificationSpec spec);
 
     void focusSearch(long accessibilityNodeId, int direction, int interactionId,
         IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
-        long interrogatingTid);
+        long interrogatingTid, in MagnificationSpec spec);
 
     void performAccessibilityAction(long accessibilityNodeId, int action, in Bundle arguments,
         int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
diff --git a/core/java/com/android/internal/policy/PolicyManager.java b/core/java/com/android/internal/policy/PolicyManager.java
index 5274e54..462b3a9 100644
--- a/core/java/com/android/internal/policy/PolicyManager.java
+++ b/core/java/com/android/internal/policy/PolicyManager.java
@@ -22,8 +22,6 @@
 import android.view.Window;
 import android.view.WindowManagerPolicy;
 
-import com.android.internal.policy.IPolicy;
-
 /**
  * {@hide}
  */
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index e978a8f..6262e4b 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -4520,19 +4520,6 @@
     }
 
     @Override
-    public boolean canMagnifyWindowLw(WindowManager.LayoutParams attrs) {
-        switch (attrs.type) {
-            case WindowManager.LayoutParams.TYPE_INPUT_METHOD:
-            case WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG:
-            case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR:
-            case WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY: {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    @Override
     public void setCurrentUserLw(int newUserId) {
         if (mKeyguardMediator != null) {
             mKeyguardMediator.setCurrentUser(newUserId);
@@ -4552,6 +4539,71 @@
         mKeyguardMediator.showAssistant();
     }
 
+    /**
+     * Returns the human readable name of a window transition.
+     *
+     * @param transition The window transition.
+     * @return The transition symbolic name.
+     */
+    public static String windowTransitionToString(int transition) {
+        switch (transition) {
+            case WindowManagerPolicy.TRANSIT_UNSET: {
+                return "TRANSIT_UNSET";
+            }
+            case WindowManagerPolicy.TRANSIT_NONE: {
+                return "TRANSIT_NONE";
+            }
+            case WindowManagerPolicy.TRANSIT_ENTER: {
+                return "TRANSIT_ENTER";
+            }
+            case WindowManagerPolicy.TRANSIT_EXIT: {
+                return "TRANSIT_EXIT";
+            }
+            case WindowManagerPolicy.TRANSIT_SHOW: {
+                return "TRANSIT_SHOW";
+            }
+            case WindowManagerPolicy.TRANSIT_EXIT_MASK: {
+                return "TRANSIT_EXIT_MASK";
+            }
+            case WindowManagerPolicy.TRANSIT_PREVIEW_DONE: {
+                return "TRANSIT_PREVIEW_DONE";
+            }
+            case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN: {
+                return "TRANSIT_ACTIVITY_OPEN";
+            }
+            case WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE: {
+                return "TRANSIT_ACTIVITY_CLOSE";
+            }
+            case WindowManagerPolicy.TRANSIT_TASK_OPEN: {
+                return "TRANSIT_TASK_OPEN";
+            }
+            case WindowManagerPolicy.TRANSIT_TASK_CLOSE: {
+                return "TRANSIT_TASK_CLOSE";
+            }
+            case WindowManagerPolicy.TRANSIT_TASK_TO_FRONT: {
+                return "TRANSIT_TASK_TO_FRONT";
+            }
+            case WindowManagerPolicy.TRANSIT_TASK_TO_BACK: {
+                return "TRANSIT_TASK_TO_BACK";
+            }
+            case WindowManagerPolicy.TRANSIT_WALLPAPER_CLOSE: {
+                return "TRANSIT_WALLPAPER_CLOSE";
+            }
+            case WindowManagerPolicy.TRANSIT_WALLPAPER_OPEN: {
+                return "TRANSIT_WALLPAPER_OPEN";
+            }
+            case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN: {
+                return "TRANSIT_WALLPAPER_INTRA_OPEN";
+            }
+            case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_CLOSE: {
+                return "TRANSIT_WALLPAPER_INTRA_CLOSE";
+            }
+            default: {
+                return "<UNKNOWN>";
+            }
+        }
+    }
+
     @Override
     public void dump(String prefix, PrintWriter pw, String[] args) {
         pw.print(prefix); pw.print("mSafeMode="); pw.print(mSafeMode);
diff --git a/policy/src/com/android/internal/policy/impl/Policy.java b/policy/src/com/android/internal/policy/impl/Policy.java
index 153ef0f..42bfc5f 100644
--- a/policy/src/com/android/internal/policy/impl/Policy.java
+++ b/policy/src/com/android/internal/policy/impl/Policy.java
@@ -24,9 +24,6 @@
 import android.view.WindowManagerPolicy;
 
 import com.android.internal.policy.IPolicy;
-import com.android.internal.policy.impl.PhoneLayoutInflater;
-import com.android.internal.policy.impl.PhoneWindow;
-import com.android.internal.policy.impl.PhoneWindowManager;
 
 /**
  * {@hide}
diff --git a/services/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java
index eb414fa..45da1e1 100644
--- a/services/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.os.PowerManager;
 import android.util.Slog;
+import android.view.Display;
 import android.view.InputDevice;
 import android.view.InputEvent;
 import android.view.InputFilter;
@@ -169,7 +170,8 @@
 
     private void enableFeatures() {
         if ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0) {
-            mEventHandler = mScreenMagnifier = new ScreenMagnifier(mContext);
+            mEventHandler = mScreenMagnifier = new ScreenMagnifier(mContext,
+                    Display.DEFAULT_DISPLAY, mAms);
             mEventHandler.setNext(this);
         }
         if ((mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) {
diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
index 65bfa7e..3e2540d 100644
--- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -65,12 +65,13 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.Display;
+import android.view.IDisplayMagnificationMediator;
 import android.view.IWindow;
 import android.view.IWindowManager;
 import android.view.InputDevice;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
-import android.view.WindowInfo;
+import android.view.MagnificationSpec;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityInteractionClient;
@@ -158,6 +159,8 @@
 
     private final MainHandler mMainHandler;
 
+    private IDisplayMagnificationMediator mMagnificationMediator;
+
     private Service mUiAutomationService;
 
     private Service mQueryBridge;
@@ -621,6 +624,13 @@
                 return false;
             }
             focus.getBoundsInScreen(outBounds);
+
+            MagnificationSpec spec = service.getCompatibleMagnificationSpec(focus.getWindowId());
+            if (spec != null && !spec.isNop()) {
+                outBounds.offset((int) -spec.offsetX, (int) -spec.offsetY);
+                outBounds.scale(1 / spec.scale);
+            }
+
             // Clip to the window rectangle.
             Rect windowBounds = mTempRect;
             getActiveWindowBounds(windowBounds);
@@ -628,6 +638,7 @@
             // Clip to the screen rectangle.
             mDefaultDisplay.getRealSize(mTempPoint);
             outBounds.intersect(0,  0,  mTempPoint.x, mTempPoint.y);
+
             return true;
         } finally {
             client.removeConnection(connectionId);
@@ -648,19 +659,13 @@
                 token = getCurrentUserStateLocked().mWindowTokens.get(windowId);
             }
         }
-        WindowInfo info = null;
         try {
-            info = mWindowManagerService.getWindowInfo(token);
-            if (info != null) {
-                outBounds.set(info.frame);
+            mWindowManagerService.getWindowFrame(token, outBounds);
+            if (!outBounds.isEmpty()) {
                 return true;
             }
         } catch (RemoteException re) {
             /* ignore */
-        } finally {
-            if (info != null) {
-                info.recycle();
-            }
         }
         return false;
     }
@@ -677,6 +682,10 @@
         mSecurityPolicy.onTouchInteractionEnd();
     }
 
+    void onMagnificationStateChanged() {
+        notifyClearAccessibilityNodeInfoCacheLocked();
+    }
+
     private void switchUser(int userId) {
         synchronized (mLock) {
             // The user switched so we do not need to restore the current user
@@ -762,6 +771,14 @@
         return false;
     }
 
+    private void notifyClearAccessibilityNodeInfoCacheLocked() {
+        UserState state = getCurrentUserStateLocked();
+        for (int i = state.mServices.size() - 1; i >= 0; i--) {
+            Service service = state.mServices.get(i);
+            service.notifyClearAccessibilityNodeInfoCache();
+        }
+    }
+
     /**
      * Removes an AccessibilityInteractionConnection.
      *
@@ -1438,9 +1455,10 @@
     class Service extends IAccessibilityServiceConnection.Stub
             implements ServiceConnection, DeathRecipient {
 
-        // We pick the MSB to avoid collision since accessibility event types are
+        // We pick the MSBs to avoid collision since accessibility event types are
         // used as message types allowing us to remove messages per event type. 
         private static final int MSG_ON_GESTURE = 0x80000000;
+        private static final int MSG_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE = 0x40000000;
 
         final int mUserId;
 
@@ -1494,6 +1512,9 @@
                         final int gestureId = message.arg1;
                         notifyGestureInternal(gestureId);
                     } break;
+                    case MSG_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE: {
+                        notifyClearAccessibilityNodeInfoCacheInternal();
+                    } break;
                     default: {
                         final int eventType = type;
                         notifyAccessibilityEventInternal(eventType);
@@ -1636,7 +1657,7 @@
         }
 
         @Override
-        public float findAccessibilityNodeInfoByViewId(int accessibilityWindowId,
+        public boolean findAccessibilityNodeInfoByViewId(int accessibilityWindowId,
                 long accessibilityNodeId, int viewId, int interactionId,
                 IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
                 throws RemoteException {
@@ -1647,17 +1668,17 @@
                         .resolveCallingUserIdEnforcingPermissionsLocked(
                                 UserHandle.getCallingUserId());
                 if (resolvedUserId != mCurrentUserId) {
-                    return -1;
+                    return false;
                 }
                 mSecurityPolicy.enforceCanRetrieveWindowContent(this);
                 final boolean permissionGranted = mSecurityPolicy.canRetrieveWindowContent(this);
                 if (!permissionGranted) {
-                    return 0;
+                    return false;
                 } else {
                     resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
                     connection = getConnectionLocked(resolvedWindowId);
                     if (connection == null) {
-                        return 0;
+                        return false;
                     }
                 }
             }
@@ -1665,10 +1686,11 @@
                     AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0;
             final int interrogatingPid = Binder.getCallingPid();
             final long identityToken = Binder.clearCallingIdentity();
+            MagnificationSpec spec = getCompatibleMagnificationSpec(resolvedWindowId);
             try {
                 connection.findAccessibilityNodeInfoByViewId(accessibilityNodeId, viewId,
-                        interactionId, callback, flags, interrogatingPid, interrogatingTid);
-                return getCompatibilityScale(resolvedWindowId);
+                        interactionId, callback, flags, interrogatingPid, interrogatingTid, spec);
+                return true;
             } catch (RemoteException re) {
                 if (DEBUG) {
                     Slog.e(LOG_TAG, "Error findAccessibilityNodeInfoByViewId().");
@@ -1676,11 +1698,11 @@
             } finally {
                 Binder.restoreCallingIdentity(identityToken);
             }
-            return 0;
+            return false;
         }
 
         @Override
-        public float findAccessibilityNodeInfosByText(int accessibilityWindowId,
+        public boolean findAccessibilityNodeInfosByText(int accessibilityWindowId,
                 long accessibilityNodeId, String text, int interactionId,
                 IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
                 throws RemoteException {
@@ -1691,18 +1713,18 @@
                         .resolveCallingUserIdEnforcingPermissionsLocked(
                         UserHandle.getCallingUserId());
                 if (resolvedUserId != mCurrentUserId) {
-                    return -1;
+                    return false;
                 }
                 mSecurityPolicy.enforceCanRetrieveWindowContent(this);
                 resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
                 final boolean permissionGranted =
                     mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId);
                 if (!permissionGranted) {
-                    return 0;
+                    return false;
                 } else {
                     connection = getConnectionLocked(resolvedWindowId);
                     if (connection == null) {
-                        return 0;
+                        return false;
                     }
                 }
             }
@@ -1710,11 +1732,11 @@
                     AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0;
             final int interrogatingPid = Binder.getCallingPid();
             final long identityToken = Binder.clearCallingIdentity();
+            MagnificationSpec spec = getCompatibleMagnificationSpec(resolvedWindowId);
             try {
                 connection.findAccessibilityNodeInfosByText(accessibilityNodeId, text,
-                        interactionId, callback, flags, interrogatingPid,
-                        interrogatingTid);
-                return getCompatibilityScale(resolvedWindowId);
+                        interactionId, callback, flags, interrogatingPid, interrogatingTid, spec);
+                return true;
             } catch (RemoteException re) {
                 if (DEBUG) {
                     Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfosByText()");
@@ -1722,12 +1744,12 @@
             } finally {
                 Binder.restoreCallingIdentity(identityToken);
             }
-            return 0;
+            return false;
         }
 
         @Override
-        public float findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
-                long accessibilityNodeId, int interactionId,
+        public boolean findAccessibilityNodeInfoByAccessibilityId(
+                int accessibilityWindowId, long accessibilityNodeId, int interactionId,
                 IAccessibilityInteractionConnectionCallback callback, int flags,
                 long interrogatingTid) throws RemoteException {
             final int resolvedWindowId;
@@ -1737,18 +1759,18 @@
                         .resolveCallingUserIdEnforcingPermissionsLocked(
                         UserHandle.getCallingUserId());
                 if (resolvedUserId != mCurrentUserId) {
-                    return -1;
+                    return false;
                 }
                 mSecurityPolicy.enforceCanRetrieveWindowContent(this);
                 resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
                 final boolean permissionGranted =
                     mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId);
                 if (!permissionGranted) {
-                    return 0;
+                    return false;
                 } else {
                     connection = getConnectionLocked(resolvedWindowId);
                     if (connection == null) {
-                        return 0;
+                        return false;
                     }
                 }
             }
@@ -1756,10 +1778,12 @@
                     AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0);
             final int interrogatingPid = Binder.getCallingPid();
             final long identityToken = Binder.clearCallingIdentity();
+            MagnificationSpec spec = getCompatibleMagnificationSpec(resolvedWindowId);
             try {
                 connection.findAccessibilityNodeInfoByAccessibilityId(accessibilityNodeId,
-                        interactionId, callback, allFlags, interrogatingPid, interrogatingTid);
-                return getCompatibilityScale(resolvedWindowId);
+                        interactionId, callback, allFlags, interrogatingPid, interrogatingTid,
+                        spec);
+                return true;
             } catch (RemoteException re) {
                 if (DEBUG) {
                     Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfoByAccessibilityId()");
@@ -1767,11 +1791,11 @@
             } finally {
                 Binder.restoreCallingIdentity(identityToken);
             }
-            return 0;
+            return false;
         }
 
         @Override
-        public float findFocus(int accessibilityWindowId, long accessibilityNodeId,
+        public boolean findFocus(int accessibilityWindowId, long accessibilityNodeId,
                 int focusType, int interactionId,
                 IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
                 throws RemoteException {
@@ -1782,18 +1806,18 @@
                         .resolveCallingUserIdEnforcingPermissionsLocked(
                         UserHandle.getCallingUserId());
                 if (resolvedUserId != mCurrentUserId) {
-                    return -1;
+                    return false;
                 }
                 mSecurityPolicy.enforceCanRetrieveWindowContent(this);
                 resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
                 final boolean permissionGranted =
                     mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId);
                 if (!permissionGranted) {
-                    return 0;
+                    return false;
                 } else {
                     connection = getConnectionLocked(resolvedWindowId);
                     if (connection == null) {
-                        return 0;
+                        return false;
                     }
                 }
             }
@@ -1801,10 +1825,11 @@
                     AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0;
             final int interrogatingPid = Binder.getCallingPid();
             final long identityToken = Binder.clearCallingIdentity();
+            MagnificationSpec spec = getCompatibleMagnificationSpec(resolvedWindowId);
             try {
                 connection.findFocus(accessibilityNodeId, focusType, interactionId, callback,
-                        flags, interrogatingPid, interrogatingTid);
-                return getCompatibilityScale(resolvedWindowId);
+                        flags, interrogatingPid, interrogatingTid, spec);
+                return true;
             } catch (RemoteException re) {
                 if (DEBUG) {
                     Slog.e(LOG_TAG, "Error calling findAccessibilityFocus()");
@@ -1812,11 +1837,11 @@
             } finally {
                 Binder.restoreCallingIdentity(identityToken);
             }
-            return 0;
+            return false;
         }
 
         @Override
-        public float focusSearch(int accessibilityWindowId, long accessibilityNodeId,
+        public boolean focusSearch(int accessibilityWindowId, long accessibilityNodeId,
                 int direction, int interactionId,
                 IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
                 throws RemoteException {
@@ -1827,18 +1852,18 @@
                         .resolveCallingUserIdEnforcingPermissionsLocked(
                         UserHandle.getCallingUserId());
                 if (resolvedUserId != mCurrentUserId) {
-                    return -1;
+                    return false;
                 }
                 mSecurityPolicy.enforceCanRetrieveWindowContent(this);
                 resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
                 final boolean permissionGranted =
                     mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId);
                 if (!permissionGranted) {
-                    return 0;
+                    return false;
                 } else {
                     connection = getConnectionLocked(resolvedWindowId);
                     if (connection == null) {
-                        return 0;
+                        return false;
                     }
                 }
             }
@@ -1846,10 +1871,11 @@
                     AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0;
             final int interrogatingPid = Binder.getCallingPid();
             final long identityToken = Binder.clearCallingIdentity();
+            MagnificationSpec spec = getCompatibleMagnificationSpec(resolvedWindowId);
             try {
                 connection.focusSearch(accessibilityNodeId, direction, interactionId, callback,
-                        flags, interrogatingPid, interrogatingTid);
-                return getCompatibilityScale(resolvedWindowId);
+                        flags, interrogatingPid, interrogatingTid, spec);
+                return true;
             } catch (RemoteException re) {
                 if (DEBUG) {
                     Slog.e(LOG_TAG, "Error calling accessibilityFocusSearch()");
@@ -1857,7 +1883,7 @@
             } finally {
                 Binder.restoreCallingIdentity(identityToken);
             }
-            return 0;
+            return false;
         }
 
         @Override
@@ -2082,6 +2108,10 @@
             mHandler.obtainMessage(MSG_ON_GESTURE, gestureId, 0).sendToTarget();
         }
 
+        public void notifyClearAccessibilityNodeInfoCache() {
+            mHandler.sendEmptyMessage(MSG_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE);
+        }
+
         private void notifyGestureInternal(int gestureId) {
             IAccessibilityServiceClient listener = mServiceInterface;
             if (listener != null) {
@@ -2094,6 +2124,18 @@
             }
         }
 
+        private void notifyClearAccessibilityNodeInfoCacheInternal() {
+            IAccessibilityServiceClient listener = mServiceInterface;
+            if (listener != null) {
+                try {
+                    listener.clearAccessibilityNodeInfoCache();
+                } catch (RemoteException re) {
+                    Slog.e(LOG_TAG, "Error during requesting accessibility info cache"
+                            + " to be cleared.", re);
+                }
+            }
+        }
+
         private void sendDownAndUpKeyEvents(int keyCode) {
             final long token = Binder.clearCallingIdentity();
 
@@ -2176,20 +2218,23 @@
             return accessibilityWindowId;
         }
 
-        private float getCompatibilityScale(int windowId) {
+        private MagnificationSpec getCompatibleMagnificationSpec(int windowId) {
             try {
-                IBinder windowToken = mGlobalWindowTokens.get(windowId);
-                if (windowToken != null) {
-                    return mWindowManagerService.getWindowCompatibilityScale(windowToken);
+                if (mMagnificationMediator == null) {
+                    mMagnificationMediator = mWindowManagerService
+                            .getDisplayMagnificationMediator();
                 }
-                windowToken = getCurrentUserStateLocked().mWindowTokens.get(windowId);
+                IBinder windowToken = mGlobalWindowTokens.get(windowId);
+                if (windowToken == null) {
+                    windowToken = getCurrentUserStateLocked().mWindowTokens.get(windowId);
+                }                    
                 if (windowToken != null) {
-                    return mWindowManagerService.getWindowCompatibilityScale(windowToken);
+                    return mMagnificationMediator.getCompatibleMagnificationSpec(windowToken);
                 }
             } catch (RemoteException re) {
                 /* ignore */
             }
-            return 1.0f;
+            return null;
         }
     }
 
diff --git a/services/java/com/android/server/accessibility/ScreenMagnifier.java b/services/java/com/android/server/accessibility/ScreenMagnifier.java
index 482bff5..e5f0924 100644
--- a/services/java/com/android/server/accessibility/ScreenMagnifier.java
+++ b/services/java/com/android/server/accessibility/ScreenMagnifier.java
@@ -21,6 +21,7 @@
 import android.animation.ObjectAnimator;
 import android.animation.TypeEvaluator;
 import android.animation.ValueAnimator;
+import android.app.Service;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -48,20 +49,19 @@
 import android.view.GestureDetector;
 import android.view.GestureDetector.SimpleOnGestureListener;
 import android.view.Gravity;
-import android.view.IDisplayContentChangeListener;
+import android.view.IDisplayMagnificationController;
+import android.view.IDisplayMagnificationMediator;
 import android.view.IWindowManager;
+import android.view.MagnificationSpec;
 import android.view.MotionEvent;
 import android.view.MotionEvent.PointerCoords;
 import android.view.MotionEvent.PointerProperties;
 import android.view.ScaleGestureDetector;
 import android.view.ScaleGestureDetector.OnScaleGestureListener;
-import android.view.Surface;
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
-import android.view.WindowInfo;
 import android.view.WindowManager;
-import android.view.WindowManagerPolicy;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
@@ -69,9 +69,6 @@
 import com.android.internal.R;
 import com.android.internal.os.SomeArgs;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
 import java.util.Locale;
 
 /**
@@ -91,7 +88,7 @@
  *    moving finger will be magnified to fit the screen. For example, if the
  *    screen was not magnified and the user triple taps and holds the screen
  *    would magnify and the viewport will follow the user's finger. When the
- *    finger goes up the screen will clear zoom out. If the same user interaction
+ *    finger goes up the screen will zoom out. If the same user interaction
  *    is performed when the screen is magnified, the viewport movement will
  *    be the same but when the finger goes up the screen will stay magnified.
  *    In other words, the initial magnified state is sticky.
@@ -106,26 +103,25 @@
  *    to pan the viewport. Note that in this mode the content is panned as
  *    opposed to the viewport dragging mode in which the viewport is moved.
  *
- * 5. When in a permanent magnified state the user can use three or more
+ * 5. When in a permanent magnified state the user can use two or more
  *    fingers to change the magnification scale which will become the current
  *    default magnification scale. The next time the user magnifies the same
  *    magnification scale would be used.
  *
  * 6. The magnification scale will be persisted in settings and in the cloud.
  */
-public final class ScreenMagnifier implements EventStreamTransformation {
+public final class ScreenMagnifier extends IDisplayMagnificationController.Stub
+        implements EventStreamTransformation {
+
+    private static final String LOG_TAG = ScreenMagnifier.class.getSimpleName();
 
     private static final boolean DEBUG_STATE_TRANSITIONS = false;
     private static final boolean DEBUG_DETECTING = false;
-    private static final boolean DEBUG_TRANSFORMATION = false;
+    private static final boolean DEBUG_SET_MAGNIFICATION_SPEC = false;
     private static final boolean DEBUG_PANNING = false;
     private static final boolean DEBUG_SCALING = false;
-    private static final boolean DEBUG_VIEWPORT_WINDOW = false;
-    private static final boolean DEBUG_WINDOW_TRANSITIONS = false;
-    private static final boolean DEBUG_ROTATION = false;
     private static final boolean DEBUG_MAGNIFICATION_CONTROLLER = false;
-
-    private static final String LOG_TAG = ScreenMagnifier.class.getSimpleName();
+    private static final boolean DEBUG_VIEWPORT_WINDOW = false;
 
     private static final int STATE_DELEGATING = 1;
     private static final int STATE_DETECTING = 2;
@@ -133,27 +129,30 @@
     private static final int STATE_MAGNIFIED_INTERACTION = 4;
 
     private static final float DEFAULT_MAGNIFICATION_SCALE = 2.0f;
+    private static final int MULTI_TAP_TIME_SLOP_ADJUSTMENT = 50;
+
+    private static final int MESSAGE_ON_MAGNIFIED_FRAME_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;
+    private static final int MESSAGE_SHOW_VIEWPORT_FRAME = 5;
+
     private static final int DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE = 1;
     private static final float DEFAULT_WINDOW_ANIMATION_SCALE = 1.0f;
 
-    private static final int MULTI_TAP_TIME_SLOP_ADJUSTMENT = 50;
+    private final Rect mTempRect = new Rect();
+    private final Rect mTempRect1 = new Rect();
 
-    private final IWindowManager mWindowManagerService = IWindowManager.Stub.asInterface(
-            ServiceManager.getService("window"));
-    private final WindowManager mWindowManager;
-    private final DisplayProvider mDisplayProvider;
-
-    private final DetectingStateHandler mDetectingStateHandler = new DetectingStateHandler();
-    private final MagnifiedContentInteractonStateHandler mMagnifiedContentInteractonStateHandler;
-    private final StateViewportDraggingHandler mStateViewportDraggingHandler =
-            new StateViewportDraggingHandler();
-
-    private final Interpolator mInterpolator = new DecelerateInterpolator(2.5f);
-
+    private final Context mContext;
     private final MagnificationController mMagnificationController;
-    private final DisplayContentObserver mDisplayContentObserver;
     private final ScreenStateObserver mScreenStateObserver;
-    private final Viewport mViewport;
+    private final ViewportWindow mViewportWindow;
+
+    private final DetectingStateHandler mDetectingStateHandler;
+    private final MagnifiedContentInteractonStateHandler mMagnifiedContentInteractonStateHandler;
+    private final StateViewportDraggingHandler mStateViewportDraggingHandler;
+
+    private final AccessibilityManagerService mAms;
 
     private final int mTapTimeSlop = ViewConfiguration.getTapTimeout();
     private final int mMultiTapTimeSlop =
@@ -161,11 +160,12 @@
     private final int mTapDistanceSlop;
     private final int mMultiTapDistanceSlop;
 
-    private final int mShortAnimationDuration;
-    private final int mLongAnimationDuration;
+    private final long mLongAnimationDuration;
     private final float mWindowAnimationScale;
 
-    private final Context mContext;
+    private final Rect mMagnifiedFrame = new Rect();
+
+    private IDisplayMagnificationMediator mDisplayMagnifier;
 
     private EventStreamTransformation mNext;
 
@@ -178,12 +178,46 @@
 
     private long mDelegatingStateDownTime;
 
-    public ScreenMagnifier(Context context) {
-        mContext = context;
-        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message message) {
+            switch (message.what) {
+                case MESSAGE_ON_MAGNIFIED_FRAME_CHANGED: {
+                    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;
+                    handleOnMagnifiedFrameChanged(left, top, right, bottom);
+                    args.recycle();
+                } break;
+                case MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED: {
+                    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: {
+                    final int rotation = message.arg1;
+                    handleOnRotationChanged(rotation);
+                } break;
+                case MESSAGE_SHOW_VIEWPORT_FRAME: {
+                    mViewportWindow.setShown(true, true);
+                } break;
+            }
+        }
+    };
 
-        mShortAnimationDuration = context.getResources().getInteger(
-                com.android.internal.R.integer.config_shortAnimTime);
+    public ScreenMagnifier(Context context, int displayId, AccessibilityManagerService service) {
+        mContext = context;
+        mAms = service;
+
         mLongAnimationDuration = context.getResources().getInteger(
                 com.android.internal.R.integer.config_longAnimTime);
         mTapDistanceSlop = ViewConfiguration.get(context).getScaledTouchSlop();
@@ -191,23 +225,132 @@
         mWindowAnimationScale = Settings.Global.getFloat(context.getContentResolver(),
                 Settings.Global.WINDOW_ANIMATION_SCALE, DEFAULT_WINDOW_ANIMATION_SCALE);
 
-        mMagnificationController = new MagnificationController(mShortAnimationDuration);
-        mDisplayProvider = new DisplayProvider(context, mWindowManager);
-        mViewport = new Viewport(mContext, mWindowManager, mWindowManagerService,
-                mDisplayProvider, mInterpolator, mShortAnimationDuration);
-        mDisplayContentObserver = new DisplayContentObserver(mContext, mViewport,
-                mMagnificationController, mWindowManagerService, mDisplayProvider,
-                mLongAnimationDuration, mWindowAnimationScale);
-        mScreenStateObserver = new ScreenStateObserver(mContext, mViewport,
-                mMagnificationController);
-
+        mDetectingStateHandler = new DetectingStateHandler();
+        mStateViewportDraggingHandler = new StateViewportDraggingHandler();
         mMagnifiedContentInteractonStateHandler = new MagnifiedContentInteractonStateHandler(
                 context);
 
+        mMagnificationController = new MagnificationController(mLongAnimationDuration);
+        mViewportWindow = new ViewportWindow(context, displayId);
+        mScreenStateObserver = new ScreenStateObserver(context, mMagnificationController,
+                mViewportWindow);
+
+        try {
+            IWindowManager windowManagerService = IWindowManager.Stub.asInterface(
+                    ServiceManager.getService("window"));
+            mDisplayMagnifier = windowManagerService.getDisplayMagnificationMediator();
+            mDisplayMagnifier.addController(Display.DEFAULT_DISPLAY, this);
+        } catch (RemoteException re) {
+            /* ignore */
+        }
+
         transitionToState(STATE_DETECTING);
     }
 
     @Override
+    public void onMagnifedFrameChanged(int left, int top, int right, int bottom) {
+        SomeArgs args = SomeArgs.obtain();
+        args.argi1 = left;
+        args.argi2 = top;
+        args.argi3 = right;
+        args.argi4 = bottom;
+        mHandler.obtainMessage(MESSAGE_ON_MAGNIFIED_FRAME_CHANGED, args).sendToTarget();
+    }
+
+    private void handleOnMagnifiedFrameChanged(int left, int top, int right, int bottom) {
+        mMagnifiedFrame.set(left, top, right, bottom);
+        mViewportWindow.setBounds(mMagnifiedFrame, mMagnificationController.isMagnifying());
+        mAms.onMagnificationStateChanged();
+    }
+
+    @Override
+    public void onRectangleOnScreenRequested(int left, int top, int right, int bottom) {
+        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) {
+        Rect rectangle = mTempRect;
+        rectangle.set(left, top, right, bottom);
+        if (!Rect.intersects(rectangle, mMagnifiedFrame)) {
+            return;
+        }
+        Rect magnifFrameInScreenCoords = mTempRect1;
+        getMagnifiedFrameInContentCoords(magnifFrameInScreenCoords);
+        final float scrollX;
+        final float scrollY;
+        if (rectangle.width() > magnifFrameInScreenCoords.width()) {
+            final int direction = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault());
+            if (direction == View.LAYOUT_DIRECTION_LTR) {
+                scrollX = rectangle.left - magnifFrameInScreenCoords.left;
+            } else {
+                scrollX = rectangle.right - magnifFrameInScreenCoords.right;
+            }
+        } else if (rectangle.left < magnifFrameInScreenCoords.left) {
+            scrollX = rectangle.left - magnifFrameInScreenCoords.left;
+        } else if (rectangle.right > magnifFrameInScreenCoords.right) {
+            scrollX = rectangle.right - magnifFrameInScreenCoords.right;
+        } else {
+            scrollX = 0;
+        }
+        if (rectangle.height() > magnifFrameInScreenCoords.height()) {
+            scrollY = rectangle.top - magnifFrameInScreenCoords.top;
+        } else if (rectangle.top < magnifFrameInScreenCoords.top) {
+            scrollY = rectangle.top - magnifFrameInScreenCoords.top;
+        } else if (rectangle.bottom > magnifFrameInScreenCoords.bottom) {
+            scrollY = rectangle.bottom - magnifFrameInScreenCoords.bottom;
+        } else {
+            scrollY = 0;
+        }
+        final float scale = mMagnificationController.getScale();
+        mMagnificationController.offsetMagnifiedRegionCenter(scrollX * scale, scrollY * scale);
+    }
+
+    private void getMagnifiedFrameInContentCoords(Rect rect) {
+        MagnificationSpec spec = mMagnificationController.getMagnificationSpec();
+        rect.set(mMagnifiedFrame);
+        rect.offset((int) -spec.offsetX, (int) -spec.offsetY);
+        rect.scale(1.0f / spec.scale);
+    }
+
+    @Override
+    public void onRotationChanged(int rotation) {
+        mHandler.obtainMessage(MESSAGE_ON_ROTATION_CHANGED, rotation, 0).sendToTarget();
+    }
+
+    private void handleOnRotationChanged(int rotation) {
+        resetMagnificationIfNeeded();
+        mViewportWindow.setShown(false, false);
+        mViewportWindow.rotationChanged();
+        if (mMagnificationController.isMagnifying()) {
+            final long delay = (long) (2 * mLongAnimationDuration * mWindowAnimationScale);
+            Message message = mHandler.obtainMessage(MESSAGE_SHOW_VIEWPORT_FRAME);
+            mHandler.sendMessageDelayed(message, delay);
+        }
+    }
+
+    @Override
+    public void onUserContextChanged() {
+        mHandler.sendEmptyMessage(MESSAGE_ON_USER_CONTEXT_CHANGED);
+    }
+
+    private void handleOnUserContextChanged() {
+        resetMagnificationIfNeeded();
+    }
+
+    private void resetMagnificationIfNeeded() {
+        if (mMagnificationController.isMagnifying()
+                && isScreenMagnificationAutoUpdateEnabled(mContext)) {
+            mMagnificationController.reset(true);
+            mViewportWindow.setShown(false, true);
+        }
+    }
+
+    @Override
     public void onMotionEvent(MotionEvent event, MotionEvent rawEvent,
             int policyFlags) {
         mMagnifiedContentInteractonStateHandler.onMotionEvent(event);
@@ -257,12 +400,8 @@
 
     @Override
     public void onDestroy() {
-        mMagnificationController.setScaleAndMagnifiedRegionCenter(1.0f,
-                0, 0, true);
-        mViewport.setFrameShown(false, true);
-        mDisplayProvider.destroy();
-        mDisplayContentObserver.destroy();
         mScreenStateObserver.destroy();
+        mViewportWindow.destroy();
     }
 
     private void handleMotionEventStateDelegating(MotionEvent event,
@@ -284,10 +423,10 @@
             final float eventX = event.getX();
             final float eventY = event.getY();
             if (mMagnificationController.isMagnifying()
-                    && mViewport.getBounds().contains((int) eventX, (int) eventY)) {
+                    && mMagnifiedFrame.contains((int) eventX, (int) eventY)) {
                 final float scale = mMagnificationController.getScale();
-                final float scaledOffsetX = mMagnificationController.getScaledOffsetX();
-                final float scaledOffsetY = mMagnificationController.getScaledOffsetY();
+                final float scaledOffsetX = mMagnificationController.getOffsetX();
+                final float scaledOffsetY = mMagnificationController.getOffsetY();
                 final int pointerCount = event.getPointerCount();
                 PointerCoords[] coords = getTempPointerCoordsWithMinSize(pointerCount);
                 PointerProperties[] properties = getTempPointerPropertiesWithMinSize(pointerCount);
@@ -412,16 +551,11 @@
             if (mCurrentState != STATE_MAGNIFIED_INTERACTION) {
                 return true;
             }
-            final float scale = mMagnificationController.getScale();
-            final float scrollX = distanceX / scale;
-            final float scrollY = distanceY / scale;
-            final float centerX = mMagnificationController.getMagnifiedRegionCenterX() + scrollX;
-            final float centerY = mMagnificationController.getMagnifiedRegionCenterY() + scrollY;
             if (DEBUG_PANNING) {
-                Slog.i(LOG_TAG, "Panned content by scrollX: " + scrollX
-                        + " scrollY: " + scrollY);
+                Slog.i(LOG_TAG, "Panned content by scrollX: " + distanceX
+                        + " scrollY: " + distanceY);
             }
-            mMagnificationController.setMagnifiedRegionCenter(centerX, centerY, false);
+            mMagnificationController.offsetMagnifiedRegionCenter(distanceX, distanceY);
             return true;
         }
 
@@ -485,7 +619,7 @@
                     }
                     final float eventX = event.getX();
                     final float eventY = event.getY();
-                    if (mViewport.getBounds().contains((int) eventX, (int) eventY)) {
+                    if (mMagnifiedFrame.contains((int) eventX, (int) eventY)) {
                         if (mLastMoveOutsideMagnifiedRegion) {
                             mLastMoveOutsideMagnifiedRegion = false;
                             mMagnificationController.setMagnifiedRegionCenter(eventX,
@@ -501,7 +635,7 @@
                 case MotionEvent.ACTION_UP: {
                     if (!mTranslationEnabledBeforePan) {
                         mMagnificationController.reset(true);
-                        mViewport.setFrameShown(false, true);
+                        mViewportWindow.setShown(false, true);
                     }
                     clear();
                     transitionToState(STATE_DETECTING);
@@ -559,7 +693,7 @@
             switch (action) {
                 case MotionEvent.ACTION_DOWN: {
                     mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
-                    if (!mViewport.getBounds().contains((int) event.getX(),
+                    if (!mMagnifiedFrame.contains((int) event.getX(),
                             (int) event.getY())) {
                         transitionToDelegatingStateAndClear();
                         return;
@@ -601,7 +735,7 @@
                         return;
                     }
                     mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD);
-                    if (!mViewport.getBounds().contains((int) event.getX(), (int) event.getY())) {
+                    if (!mMagnifiedFrame.contains((int) event.getX(), (int) event.getY())) {
                          transitionToDelegatingStateAndClear();
                          return;
                     }
@@ -727,10 +861,10 @@
             if (!mMagnificationController.isMagnifying()) {
                 mMagnificationController.setScaleAndMagnifiedRegionCenter(getPersistedScale(),
                         up.getX(), up.getY(), true);
-                mViewport.setFrameShown(true, true);
+                mViewportWindow.setShown(true, true);
             } else {
                 mMagnificationController.reset(true);
-                mViewport.setFrameShown(false, true);
+                mViewportWindow.setShown(false, true);
             }
         }
 
@@ -742,7 +876,7 @@
             mTranslationEnabledBeforePan = mMagnificationController.isMagnifying();
             mMagnificationController.setScaleAndMagnifiedRegionCenter(getPersistedScale(),
                     down.getX(), down.getY(), true);
-            mViewport.setFrameShown(true, true);
+            mViewportWindow.setShown(true, true);
             transitionToState(STATE_VIEWPORT_DRAGGING);
         }
     }
@@ -837,14 +971,192 @@
         }
     }
 
-    private static final class ScreenStateObserver extends BroadcastReceiver {
+    private final class MagnificationController {
 
+        private static final String PROPERTY_NAME_MAGNIFICATION_SPEC =
+                "magnificationSpec";
+
+        private final MagnificationSpec mSentMagnificationSpec = MagnificationSpec.obtain();
+
+        private final MagnificationSpec mCurrentMagnificationSpec = MagnificationSpec.obtain();
+
+        private final Rect mTempRect = new Rect();
+
+        private final ValueAnimator mTransformationAnimator;
+
+        public MagnificationController(long animationDuration) {
+            Property<MagnificationController, MagnificationSpec> property =
+                    Property.of(MagnificationController.class, MagnificationSpec.class,
+                    PROPERTY_NAME_MAGNIFICATION_SPEC);
+            TypeEvaluator<MagnificationSpec> evaluator = new TypeEvaluator<MagnificationSpec>() {
+                private final MagnificationSpec mTempTransformationSpec =
+                        MagnificationSpec.obtain();
+                @Override
+                public MagnificationSpec evaluate(float fraction, MagnificationSpec fromSpec,
+                        MagnificationSpec toSpec) {
+                    MagnificationSpec result = mTempTransformationSpec;
+                    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;
+                }
+            };
+            mTransformationAnimator = ObjectAnimator.ofObject(this, property,
+                    evaluator, mSentMagnificationSpec, mCurrentMagnificationSpec);
+            mTransformationAnimator.setDuration((long) (animationDuration));
+            mTransformationAnimator.setInterpolator(new DecelerateInterpolator(2.5f));
+        }
+
+        public boolean isMagnifying() {
+            return mCurrentMagnificationSpec.scale > 1.0f;
+        }
+
+        public void reset(boolean animate) {
+            if (mTransformationAnimator.isRunning()) {
+                mTransformationAnimator.cancel();
+            }
+            mCurrentMagnificationSpec.clear();
+            if (animate) {
+                animateMangificationSpec(mSentMagnificationSpec,
+                        mCurrentMagnificationSpec);
+            } else {
+                setMagnificationSpec(mCurrentMagnificationSpec);
+            }
+            Rect bounds = mTempRect;
+            bounds.setEmpty();
+            mAms.onMagnificationStateChanged();
+        }
+
+        public float getScale() {
+            return mCurrentMagnificationSpec.scale;
+        }
+
+        public float getOffsetX() {
+            return mCurrentMagnificationSpec.offsetX;
+        }
+
+        public float getOffsetY() {
+            return mCurrentMagnificationSpec.offsetY;
+        }
+
+        public void setScale(float scale, float pivotX, float pivotY, boolean animate) {
+            MagnificationSpec spec = mCurrentMagnificationSpec;
+            final float oldScale = spec.scale;
+            final float oldCenterX = (-spec.offsetX + mMagnifiedFrame.width() / 2) / oldScale;
+            final float oldCenterY = (-spec.offsetY + mMagnifiedFrame.height() / 2) / oldScale;
+            final float normPivotX = (-spec.offsetX + pivotX) / oldScale;
+            final float normPivotY = (-spec.offsetY + pivotY) / oldScale;
+            final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale);
+            final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale);
+            final float centerX = normPivotX + offsetX;
+            final float centerY = normPivotY + offsetY;
+            setScaleAndMagnifiedRegionCenter(scale, centerX, centerY, animate);
+        }
+
+        public void setMagnifiedRegionCenter(float centerX, float centerY, boolean animate) {
+            setScaleAndMagnifiedRegionCenter(mCurrentMagnificationSpec.scale, centerX, centerY,
+                    animate);
+        }
+
+        public void offsetMagnifiedRegionCenter(float offsetX, float offsetY) {
+            final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX;
+            mCurrentMagnificationSpec.offsetX = Math.min(Math.max(nonNormOffsetX,
+                    getMinOffsetX()), 0);
+            final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY;
+            mCurrentMagnificationSpec.offsetY = Math.min(Math.max(nonNormOffsetY,
+                    getMinOffsetY()), 0);
+            setMagnificationSpec(mCurrentMagnificationSpec);
+        }
+
+        public void setScaleAndMagnifiedRegionCenter(float scale, float centerX, float centerY,
+                boolean animate) {
+            if (Float.compare(mCurrentMagnificationSpec.scale, scale) == 0
+                    && Float.compare(mCurrentMagnificationSpec.offsetX,
+                            centerX) == 0
+                    && Float.compare(mCurrentMagnificationSpec.offsetY,
+                            centerY) == 0) {
+                return;
+            }
+            if (mTransformationAnimator.isRunning()) {
+                mTransformationAnimator.cancel();
+            }
+            if (DEBUG_MAGNIFICATION_CONTROLLER) {
+                Slog.i(LOG_TAG, "scale: " + scale + " offsetX: " + centerX
+                        + " offsetY: " + centerY);
+            }
+            updateMagnificationSpec(scale, centerX, centerY);
+            if (animate) {
+                animateMangificationSpec(mSentMagnificationSpec,
+                        mCurrentMagnificationSpec);
+            } else {
+                setMagnificationSpec(mCurrentMagnificationSpec);
+            }
+            mAms.onMagnificationStateChanged();
+        }
+
+        public void updateMagnificationSpec(float scale, float magnifiedCenterX,
+                float magnifiedCenterY) {
+            mCurrentMagnificationSpec.scale = scale;
+            final int viewportWidth = mMagnifiedFrame.width();
+            final float nonNormOffsetX = viewportWidth / 2 - magnifiedCenterX * scale;
+            mCurrentMagnificationSpec.offsetX = Math.min(Math.max(nonNormOffsetX,
+                    getMinOffsetX()), 0);
+            final int viewportHeight = mMagnifiedFrame.height();
+            final float nonNormOffsetY = viewportHeight / 2 - magnifiedCenterY * scale;
+            mCurrentMagnificationSpec.offsetY = Math.min(Math.max(nonNormOffsetY,
+                    getMinOffsetY()), 0);
+        }
+
+        private float getMinOffsetX() {
+            final float viewportWidth = mMagnifiedFrame.width();
+            return viewportWidth - viewportWidth * mCurrentMagnificationSpec.scale;
+        }
+
+        private float getMinOffsetY() {
+            final float viewportHeight = mMagnifiedFrame.height();
+            return viewportHeight - viewportHeight * mCurrentMagnificationSpec.scale;
+        }
+
+        private void animateMangificationSpec(MagnificationSpec fromSpec,
+                MagnificationSpec toSpec) {
+            mTransformationAnimator.setObjectValues(fromSpec, toSpec);
+            mTransformationAnimator.start();
+        }
+
+        public MagnificationSpec getMagnificationSpec() {
+            return mSentMagnificationSpec;
+        }
+
+        public void setMagnificationSpec(MagnificationSpec spec) {
+            if (DEBUG_SET_MAGNIFICATION_SPEC) {
+                Slog.i(LOG_TAG, "Sending: " + spec);
+            }
+            try {
+                mSentMagnificationSpec.scale = spec.scale;
+                mSentMagnificationSpec.offsetX = spec.offsetX;
+                mSentMagnificationSpec.offsetY = spec.offsetY;
+                mDisplayMagnifier.setMagnificationSpec(ScreenMagnifier.this,
+                        MagnificationSpec.obtain(spec));
+            } catch (RemoteException re) {
+                /* ignore */
+            }
+        }
+    }
+
+    private static final class ScreenStateObserver extends BroadcastReceiver {
         private static final int MESSAGE_ON_SCREEN_STATE_CHANGE = 1;
 
+        private final Context mContext;
+        private final MagnificationController mMagnificationController;
+        private final ViewportWindow mViewportWindow;
+
         private final Handler mHandler = new Handler() {
             @Override
             public void handleMessage(Message message) {
-                switch (message.what) {
+                 switch (message.what) {
                     case MESSAGE_ON_SCREEN_STATE_CHANGE: {
                         String action = (String) message.obj;
                         handleOnScreenStateChange(action);
@@ -853,15 +1165,11 @@
             }
         };
 
-        private final Context mContext;
-        private final Viewport mViewport;
-        private final MagnificationController mMagnificationController;
-
-        public ScreenStateObserver(Context context, Viewport viewport,
-                MagnificationController magnificationController) {
+        public ScreenStateObserver(Context context,
+                MagnificationController magnificationController, ViewportWindow viewportWindow) {
             mContext = context;
-            mViewport = viewport;
             mMagnificationController = magnificationController;
+            mViewportWindow = viewportWindow;
             mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_SCREEN_OFF));
         }
 
@@ -873,637 +1181,95 @@
         public void onReceive(Context context, Intent intent) {
             mHandler.obtainMessage(MESSAGE_ON_SCREEN_STATE_CHANGE,
                     intent.getAction()).sendToTarget();
-        }
+            }
 
         private void handleOnScreenStateChange(String action) {
-            if (action.equals(Intent.ACTION_SCREEN_OFF)
-                    && mMagnificationController.isMagnifying()
-                    && isScreenMagnificationAutoUpdateEnabled(mContext)) {
-                mMagnificationController.reset(false);
-                mViewport.setFrameShown(false, false);
-            }
-        }
-    }
-
-    private static final class DisplayContentObserver {
-
-        private static final int MESSAGE_SHOW_VIEWPORT_FRAME = 1;
-        private static final int MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED = 3;
-        private static final int MESSAGE_ON_WINDOW_TRANSITION = 4;
-        private static final int MESSAGE_ON_ROTATION_CHANGED = 5;
-        private static final int MESSAGE_ON_WINDOW_LAYERS_CHANGED = 6;
-
-        private final Handler mHandler = new MyHandler();
-
-        private final Rect mTempRect = new Rect();
-
-        private final IDisplayContentChangeListener mDisplayContentChangeListener;
-
-        private final Context mContext;
-        private final Viewport mViewport;
-        private final MagnificationController mMagnificationController;
-        private final IWindowManager mWindowManagerService;
-        private final DisplayProvider mDisplayProvider;
-        private final long mLongAnimationDuration;
-        private final float mWindowAnimationScale;
-
-        public DisplayContentObserver(Context context, Viewport viewport,
-                MagnificationController magnificationController,
-                IWindowManager windowManagerService, DisplayProvider displayProvider,
-                long longAnimationDuration, float windowAnimationScale) {
-            mContext = context;
-            mViewport = viewport;
-            mMagnificationController = magnificationController;
-            mWindowManagerService = windowManagerService;
-            mDisplayProvider = displayProvider;
-            mLongAnimationDuration = longAnimationDuration;
-            mWindowAnimationScale = windowAnimationScale;
-
-            mDisplayContentChangeListener = new IDisplayContentChangeListener.Stub() {
-                @Override
-                public void onWindowTransition(int displayId, int transition, WindowInfo info) {
-                    mHandler.obtainMessage(MESSAGE_ON_WINDOW_TRANSITION,
-                            transition, 0, WindowInfo.obtain(info)).sendToTarget();
-                }
-
-                @Override
-                public void onRectangleOnScreenRequested(int dsiplayId, Rect rectangle,
-                        boolean immediate) {
-                    SomeArgs args = SomeArgs.obtain();
-                    args.argi1 = rectangle.left;
-                    args.argi2 = rectangle.top;
-                    args.argi3 = rectangle.right;
-                    args.argi4 = rectangle.bottom;
-                    mHandler.obtainMessage(MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED, 0,
-                            immediate ? 1 : 0, args).sendToTarget();
-                }
-
-                @Override
-                public void onRotationChanged(int rotation) throws RemoteException {
-                    mHandler.obtainMessage(MESSAGE_ON_ROTATION_CHANGED, rotation, 0)
-                            .sendToTarget();
-                }
-
-                @Override
-                public void onWindowLayersChanged(int displayId) throws RemoteException {
-                    mHandler.sendEmptyMessage(MESSAGE_ON_WINDOW_LAYERS_CHANGED);
-                }
-            };
-
-            try {
-                mWindowManagerService.addDisplayContentChangeListener(
-                        mDisplayProvider.getDisplay().getDisplayId(),
-                        mDisplayContentChangeListener);
-            } catch (RemoteException re) {
-                /* ignore */
-            }
-        }
-
-        public void destroy() {
-            try {
-                mWindowManagerService.removeDisplayContentChangeListener(
-                        mDisplayProvider.getDisplay().getDisplayId(),
-                        mDisplayContentChangeListener);
-            } catch (RemoteException re) {
-                /* ignore*/
-            }
-        }
-
-        private void handleOnRotationChanged(int rotation) {
-            if (DEBUG_ROTATION) {
-                Slog.i(LOG_TAG, "Rotation: " + rotationToString(rotation));
-            }
-            resetMagnificationIfNeeded();
-            mViewport.setFrameShown(false, false);
-            mViewport.rotationChanged();
-            mViewport.recomputeBounds(false);
-            if (mMagnificationController.isMagnifying()) {
-                final long delay = (long) (2 * mLongAnimationDuration * mWindowAnimationScale);
-                Message message = mHandler.obtainMessage(MESSAGE_SHOW_VIEWPORT_FRAME);
-                mHandler.sendMessageDelayed(message, delay);
-            }
-        }
-
-        private void handleOnWindowTransition(int transition, WindowInfo info) {
-            if (DEBUG_WINDOW_TRANSITIONS) {
-                Slog.i(LOG_TAG, "Window transitioning: "
-                        + windowTransitionToString(transition));
-            }
-            try {
-                final boolean magnifying = mMagnificationController.isMagnifying();
-                if (magnifying) {
-                    switch (transition) {
-                        case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN:
-                        case WindowManagerPolicy.TRANSIT_TASK_OPEN:
-                        case WindowManagerPolicy.TRANSIT_TASK_TO_FRONT:
-                        case WindowManagerPolicy.TRANSIT_WALLPAPER_OPEN:
-                        case WindowManagerPolicy.TRANSIT_WALLPAPER_CLOSE:
-                        case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN: {
-                            resetMagnificationIfNeeded();
-                        }
-                    }
-                }
-                if (info.type == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
-                        || info.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD
-                        || info.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG
-                        || info.type == WindowManager.LayoutParams.TYPE_KEYGUARD
-                        || info.type == WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG) {
-                    switch (transition) {
-                        case WindowManagerPolicy.TRANSIT_ENTER:
-                        case WindowManagerPolicy.TRANSIT_SHOW:
-                        case WindowManagerPolicy.TRANSIT_EXIT:
-                        case WindowManagerPolicy.TRANSIT_HIDE: {
-                            mViewport.recomputeBounds(mMagnificationController.isMagnifying());
-                        } break;
-                    }
-                }
-                switch (transition) {
-                    case WindowManagerPolicy.TRANSIT_ENTER:
-                    case WindowManagerPolicy.TRANSIT_SHOW: {
-                        if (!magnifying || !isScreenMagnificationAutoUpdateEnabled(mContext)) {
-                            break;
-                        }
-                        final int type = info.type;
-                        switch (type) {
-                            // TODO: Are these all the windows we want to make
-                            //       visible when they appear on the screen?
-                            //       Do we need to take some of them out?
-                            case WindowManager.LayoutParams.TYPE_APPLICATION:
-                            case WindowManager.LayoutParams.TYPE_APPLICATION_PANEL:
-                            case WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA:
-                            case WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL:
-                            case WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG:
-                            case WindowManager.LayoutParams.TYPE_SEARCH_BAR:
-                            case WindowManager.LayoutParams.TYPE_PHONE:
-                            case WindowManager.LayoutParams.TYPE_SYSTEM_ALERT:
-                            case WindowManager.LayoutParams.TYPE_TOAST:
-                            case WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY:
-                            case WindowManager.LayoutParams.TYPE_PRIORITY_PHONE:
-                            case WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG:
-                            case WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG:
-                            case WindowManager.LayoutParams.TYPE_SYSTEM_ERROR:
-                            case WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY:
-                            case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL:
-                            case WindowManager.LayoutParams.TYPE_RECENTS_OVERLAY: {
-                                Rect magnifiedRegionBounds = mMagnificationController
-                                        .getMagnifiedRegionBounds();
-                                Rect touchableRegion = info.touchableRegion;
-                                if (!magnifiedRegionBounds.intersect(touchableRegion)) {
-                                    ensureRectangleInMagnifiedRegionBounds(
-                                            magnifiedRegionBounds, touchableRegion);
-                                }
-                            } break;
-                        } break;
-                    }
-                }
-            } finally {
-                if (info != null) {
-                    info.recycle();
-                }
-            }
-        }
-
-        private void handleOnRectangleOnScreenRequested(Rect rectangle, boolean immediate) {
-            if (!mMagnificationController.isMagnifying()) {
-                return;
-            }
-            Rect magnifiedRegionBounds = mMagnificationController.getMagnifiedRegionBounds();
-            if (magnifiedRegionBounds.contains(rectangle)) {
-                return;
-            }
-            ensureRectangleInMagnifiedRegionBounds(magnifiedRegionBounds, rectangle);
-        }
-
-        private void ensureRectangleInMagnifiedRegionBounds(Rect magnifiedRegionBounds,
-                Rect rectangle) {
-            if (!Rect.intersects(rectangle, mViewport.getBounds())) {
-                return;
-            }
-            final float scrollX;
-            final float scrollY;
-            if (rectangle.width() > magnifiedRegionBounds.width()) {
-                final int direction = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault());
-                if (direction == View.LAYOUT_DIRECTION_LTR) {
-                    scrollX = rectangle.left - magnifiedRegionBounds.left;
-                } else {
-                    scrollX = rectangle.right - magnifiedRegionBounds.right;
-                }
-            } else if (rectangle.left < magnifiedRegionBounds.left) {
-                scrollX = rectangle.left - magnifiedRegionBounds.left;
-            } else if (rectangle.right > magnifiedRegionBounds.right) {
-                scrollX = rectangle.right - magnifiedRegionBounds.right;
-            } else {
-                scrollX = 0;
-            }
-            if (rectangle.height() > magnifiedRegionBounds.height()) {
-                scrollY = rectangle.top - magnifiedRegionBounds.top;
-            } else if (rectangle.top < magnifiedRegionBounds.top) {
-                scrollY = rectangle.top - magnifiedRegionBounds.top;
-            } else if (rectangle.bottom > magnifiedRegionBounds.bottom) {
-                scrollY = rectangle.bottom - magnifiedRegionBounds.bottom;
-            } else {
-                scrollY = 0;
-            }
-            final float viewportCenterX = mMagnificationController.getMagnifiedRegionCenterX()
-                    + scrollX;
-            final float viewportCenterY = mMagnificationController.getMagnifiedRegionCenterY()
-                    + scrollY;
-            mMagnificationController.setMagnifiedRegionCenter(viewportCenterX, viewportCenterY,
-                    true);
-        }
-
-        private void resetMagnificationIfNeeded() {
             if (mMagnificationController.isMagnifying()
                     && isScreenMagnificationAutoUpdateEnabled(mContext)) {
-                mMagnificationController.reset(true);
-                mViewport.setFrameShown(false, true);
-            }
-        }
-
-        private String windowTransitionToString(int transition) {
-            switch (transition) {
-                case WindowManagerPolicy.TRANSIT_UNSET: {
-                    return "TRANSIT_UNSET";
-                }
-                case WindowManagerPolicy.TRANSIT_NONE: {
-                    return "TRANSIT_NONE";
-                }
-                case WindowManagerPolicy.TRANSIT_ENTER: {
-                    return "TRANSIT_ENTER";
-                }
-                case WindowManagerPolicy.TRANSIT_EXIT: {
-                    return "TRANSIT_EXIT";
-                }
-                case WindowManagerPolicy.TRANSIT_SHOW: {
-                    return "TRANSIT_SHOW";
-                }
-                case WindowManagerPolicy.TRANSIT_EXIT_MASK: {
-                    return "TRANSIT_EXIT_MASK";
-                }
-                case WindowManagerPolicy.TRANSIT_PREVIEW_DONE: {
-                    return "TRANSIT_PREVIEW_DONE";
-                }
-                case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN: {
-                    return "TRANSIT_ACTIVITY_OPEN";
-                }
-                case WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE: {
-                    return "TRANSIT_ACTIVITY_CLOSE";
-                }
-                case WindowManagerPolicy.TRANSIT_TASK_OPEN: {
-                    return "TRANSIT_TASK_OPEN";
-                }
-                case WindowManagerPolicy.TRANSIT_TASK_CLOSE: {
-                    return "TRANSIT_TASK_CLOSE";
-                }
-                case WindowManagerPolicy.TRANSIT_TASK_TO_FRONT: {
-                    return "TRANSIT_TASK_TO_FRONT";
-                }
-                case WindowManagerPolicy.TRANSIT_TASK_TO_BACK: {
-                    return "TRANSIT_TASK_TO_BACK";
-                }
-                case WindowManagerPolicy.TRANSIT_WALLPAPER_CLOSE: {
-                    return "TRANSIT_WALLPAPER_CLOSE";
-                }
-                case WindowManagerPolicy.TRANSIT_WALLPAPER_OPEN: {
-                    return "TRANSIT_WALLPAPER_OPEN";
-                }
-                case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN: {
-                    return "TRANSIT_WALLPAPER_INTRA_OPEN";
-                }
-                case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_CLOSE: {
-                    return "TRANSIT_WALLPAPER_INTRA_CLOSE";
-                }
-                default: {
-                    return "<UNKNOWN>";
-                }
-            }
-        }
-
-        private String rotationToString(int rotation) {
-            switch (rotation) {
-                case Surface.ROTATION_0: {
-                    return "ROTATION_0";
-                }
-                case Surface.ROTATION_90: {
-                    return "ROATATION_90";
-                }
-                case Surface.ROTATION_180: {
-                    return "ROATATION_180";
-                }
-                case Surface.ROTATION_270: {
-                    return "ROATATION_270";
-                }
-                default: {
-                    throw new IllegalArgumentException("Invalid rotation: "
-                        + rotation);
-                }
-            }
-        }
-
-        private final class MyHandler extends Handler {
-            @Override
-            public void handleMessage(Message message) {
-                final int action = message.what;
-                switch (action) {
-                    case MESSAGE_SHOW_VIEWPORT_FRAME: {
-                        mViewport.setFrameShown(true, true);
-                    } break;
-                    case MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED: {
-                        SomeArgs args = (SomeArgs) message.obj;
-                        try {
-                            mTempRect.set(args.argi1, args.argi2, args.argi3, args.argi4);
-                            final boolean immediate = (message.arg1 == 1);
-                            handleOnRectangleOnScreenRequested(mTempRect, immediate);
-                        } finally {
-                            args.recycle();
-                        }
-                    } break;
-                    case MESSAGE_ON_WINDOW_TRANSITION: {
-                        final int transition = message.arg1;
-                        WindowInfo info = (WindowInfo) message.obj;
-                        handleOnWindowTransition(transition, info);
-                    } break;
-                    case MESSAGE_ON_ROTATION_CHANGED: {
-                        final int rotation = message.arg1;
-                        handleOnRotationChanged(rotation);
-                    } break;
-                    case MESSAGE_ON_WINDOW_LAYERS_CHANGED: {
-                        mViewport.recomputeBounds(mMagnificationController.isMagnifying());
-                    } break;
-                    default: {
-                        throw new IllegalArgumentException("Unknown message: " + action);
-                    }
-                }
+                mMagnificationController.reset(false);
+                mViewportWindow.setShown(false, false);
             }
         }
     }
 
-    private final class MagnificationController {
-
-        private static final String PROPERTY_NAME_ACCESSIBILITY_TRANSFORMATION =
-                "accessibilityTransformation";
-
-        private final MagnificationSpec mSentMagnificationSpec = new MagnificationSpec();
-
-        private final MagnificationSpec mCurrentMagnificationSpec = new MagnificationSpec();
-
-        private final Rect mTempRect = new Rect();
-
-        private final ValueAnimator mTransformationAnimator;
-
-        public MagnificationController(int animationDuration) {
-            Property<MagnificationController, MagnificationSpec> property =
-                    Property.of(MagnificationController.class, MagnificationSpec.class,
-                    PROPERTY_NAME_ACCESSIBILITY_TRANSFORMATION);
-            TypeEvaluator<MagnificationSpec> evaluator = new TypeEvaluator<MagnificationSpec>() {
-                private final MagnificationSpec mTempTransformationSpec = new MagnificationSpec();
-                @Override
-                public MagnificationSpec evaluate(float fraction, MagnificationSpec fromSpec,
-                        MagnificationSpec toSpec) {
-                    MagnificationSpec result = mTempTransformationSpec;
-                    result.mScale = fromSpec.mScale
-                            + (toSpec.mScale - fromSpec.mScale) * fraction;
-                    result.mMagnifiedRegionCenterX = fromSpec.mMagnifiedRegionCenterX
-                            + (toSpec.mMagnifiedRegionCenterX - fromSpec.mMagnifiedRegionCenterX)
-                            * fraction;
-                    result.mMagnifiedRegionCenterY = fromSpec.mMagnifiedRegionCenterY
-                            + (toSpec.mMagnifiedRegionCenterY - fromSpec.mMagnifiedRegionCenterY)
-                            * fraction;
-                    result.mScaledOffsetX = fromSpec.mScaledOffsetX
-                            + (toSpec.mScaledOffsetX - fromSpec.mScaledOffsetX)
-                            * fraction;
-                    result.mScaledOffsetY = fromSpec.mScaledOffsetY
-                            + (toSpec.mScaledOffsetY - fromSpec.mScaledOffsetY)
-                            * fraction;
-                    return result;
-                }
-            };
-            mTransformationAnimator = ObjectAnimator.ofObject(this, property,
-                    evaluator, mSentMagnificationSpec, mCurrentMagnificationSpec);
-            mTransformationAnimator.setDuration((long) (animationDuration));
-            mTransformationAnimator.setInterpolator(mInterpolator);
-        }
-
-        public boolean isMagnifying() {
-            return mCurrentMagnificationSpec.mScale > 1.0f;
-        }
-
-        public void reset(boolean animate) {
-            if (mTransformationAnimator.isRunning()) {
-                mTransformationAnimator.cancel();
-            }
-            mCurrentMagnificationSpec.reset();
-            if (animate) {
-                animateAccessibilityTranformation(mSentMagnificationSpec,
-                        mCurrentMagnificationSpec);
-            } else {
-                setAccessibilityTransformation(mCurrentMagnificationSpec);
-            }
-        }
-
-        public Rect getMagnifiedRegionBounds() {
-            mTempRect.set(mViewport.getBounds());
-            mTempRect.offset((int) -mCurrentMagnificationSpec.mScaledOffsetX,
-                    (int) -mCurrentMagnificationSpec.mScaledOffsetY);
-            mTempRect.scale(1.0f / mCurrentMagnificationSpec.mScale);
-            return mTempRect;
-        }
-
-        public float getScale() {
-            return mCurrentMagnificationSpec.mScale;
-        }
-
-        public float getMagnifiedRegionCenterX() {
-            return mCurrentMagnificationSpec.mMagnifiedRegionCenterX;
-        }
-
-        public float getMagnifiedRegionCenterY() {
-            return mCurrentMagnificationSpec.mMagnifiedRegionCenterY;
-        }
-
-        public float getScaledOffsetX() {
-            return mCurrentMagnificationSpec.mScaledOffsetX;
-        }
-
-        public float getScaledOffsetY() {
-            return mCurrentMagnificationSpec.mScaledOffsetY;
-        }
-
-        public void setScale(float scale, float pivotX, float pivotY, boolean animate) {
-            MagnificationSpec spec = mCurrentMagnificationSpec;
-            final float oldScale = spec.mScale;
-            final float oldCenterX = spec.mMagnifiedRegionCenterX;
-            final float oldCenterY = spec.mMagnifiedRegionCenterY;
-            final float normPivotX = (-spec.mScaledOffsetX + pivotX) / oldScale;
-            final float normPivotY = (-spec.mScaledOffsetY + pivotY) / oldScale;
-            final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale);
-            final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale);
-            final float centerX = normPivotX + offsetX;
-            final float centerY = normPivotY + offsetY;
-            setScaleAndMagnifiedRegionCenter(scale, centerX, centerY, animate);
-        }
-
-        public void setMagnifiedRegionCenter(float centerX, float centerY, boolean animate) {
-            setScaleAndMagnifiedRegionCenter(mCurrentMagnificationSpec.mScale, centerX, centerY,
-                    animate);
-        }
-
-        public void setScaleAndMagnifiedRegionCenter(float scale, float centerX, float centerY,
-                boolean animate) {
-            if (Float.compare(mCurrentMagnificationSpec.mScale, scale) == 0
-                    && Float.compare(mCurrentMagnificationSpec.mMagnifiedRegionCenterX,
-                            centerX) == 0
-                    && Float.compare(mCurrentMagnificationSpec.mMagnifiedRegionCenterY,
-                            centerY) == 0) {
-                return;
-            }
-            if (mTransformationAnimator.isRunning()) {
-                mTransformationAnimator.cancel();
-            }
-            if (DEBUG_MAGNIFICATION_CONTROLLER) {
-                Slog.i(LOG_TAG, "scale: " + scale + " centerX: " + centerX
-                        + " centerY: " + centerY);
-            }
-            mCurrentMagnificationSpec.initialize(scale, centerX, centerY);
-            if (animate) {
-                animateAccessibilityTranformation(mSentMagnificationSpec,
-                        mCurrentMagnificationSpec);
-            } else {
-                setAccessibilityTransformation(mCurrentMagnificationSpec);
-            }
-        }
-
-        private void animateAccessibilityTranformation(MagnificationSpec fromSpec,
-                MagnificationSpec toSpec) {
-            mTransformationAnimator.setObjectValues(fromSpec, toSpec);
-            mTransformationAnimator.start();
-        }
-
-        @SuppressWarnings("unused")
-        // Called from an animator.
-        public MagnificationSpec getAccessibilityTransformation() {
-            return mSentMagnificationSpec;
-        }
-
-        public void setAccessibilityTransformation(MagnificationSpec transformation) {
-            if (DEBUG_TRANSFORMATION) {
-                Slog.i(LOG_TAG, "Transformation scale: " + transformation.mScale
-                        + " offsetX: " + transformation.mScaledOffsetX
-                        + " offsetY: " + transformation.mScaledOffsetY);
-            }
-            try {
-                mSentMagnificationSpec.updateFrom(transformation);
-                mWindowManagerService.magnifyDisplay(mDisplayProvider.getDisplay().getDisplayId(),
-                        transformation.mScale, transformation.mScaledOffsetX,
-                        transformation.mScaledOffsetY);
-            } catch (RemoteException re) {
-                /* ignore */
-            }
-        }
-
-        private class MagnificationSpec {
-
-            private static final float DEFAULT_SCALE = 1.0f;
-
-            public float mScale = DEFAULT_SCALE;
-
-            public float mMagnifiedRegionCenterX;
-
-            public float mMagnifiedRegionCenterY;
-
-            public float mScaledOffsetX;
-
-            public float mScaledOffsetY;
-
-            public void initialize(float scale, float magnifiedRegionCenterX,
-                    float magnifiedRegionCenterY) {
-                mScale = scale;
-
-                final int viewportWidth = mViewport.getBounds().width();
-                final int viewportHeight = mViewport.getBounds().height();
-                final float minMagnifiedRegionCenterX = (viewportWidth / 2) / scale;
-                final float minMagnifiedRegionCenterY = (viewportHeight / 2) / scale;
-                final float maxMagnifiedRegionCenterX = viewportWidth - minMagnifiedRegionCenterX;
-                final float maxMagnifiedRegionCenterY = viewportHeight - minMagnifiedRegionCenterY;
-
-                mMagnifiedRegionCenterX = Math.min(Math.max(magnifiedRegionCenterX,
-                        minMagnifiedRegionCenterX), maxMagnifiedRegionCenterX);
-                mMagnifiedRegionCenterY = Math.min(Math.max(magnifiedRegionCenterY,
-                        minMagnifiedRegionCenterY), maxMagnifiedRegionCenterY);
-
-                mScaledOffsetX = -(mMagnifiedRegionCenterX * scale - viewportWidth / 2);
-                mScaledOffsetY = -(mMagnifiedRegionCenterY * scale - viewportHeight / 2);
-            }
-
-            public void updateFrom(MagnificationSpec other) {
-                mScale = other.mScale;
-                mMagnifiedRegionCenterX = other.mMagnifiedRegionCenterX;
-                mMagnifiedRegionCenterY = other.mMagnifiedRegionCenterY;
-                mScaledOffsetX = other.mScaledOffsetX;
-                mScaledOffsetY = other.mScaledOffsetY;
-            }
-
-            public void reset() {
-                mScale = DEFAULT_SCALE;
-                mMagnifiedRegionCenterX = 0;
-                mMagnifiedRegionCenterY = 0;
-                mScaledOffsetX = 0;
-                mScaledOffsetY = 0;
-            }
-        }
-    }
-
-    private static final class Viewport {
+    private static final class ViewportWindow implements DisplayListener {
+        private static final String WINDOW_TITLE = "Magnification Overlay";
 
         private static final String PROPERTY_NAME_ALPHA = "alpha";
-
         private static final String PROPERTY_NAME_BOUNDS = "bounds";
 
         private static final int MIN_ALPHA = 0;
-
         private static final int MAX_ALPHA = 255;
 
-        private final ArrayList<WindowInfo> mTempWindowInfoList = new ArrayList<WindowInfo>();
-
-        private final Rect mTempRect1 = new Rect();
-        private final Rect mTempRect2 = new Rect();
-        private final Rect mTempRect3 = new Rect();
-
-        private final IWindowManager mWindowManagerService;
-        private final DisplayProvider mDisplayProvider;
-
-        private final ViewportWindow mViewportFrame;
+        private final Rect mBounds = new Rect();
 
         private final ValueAnimator mResizeFrameAnimator;
-
         private final ValueAnimator mShowHideFrameAnimator;
 
-        public Viewport(Context context, WindowManager windowManager,
-                IWindowManager windowManagerService, DisplayProvider displayInfoProvider,
-                Interpolator animationInterpolator, long animationDuration) {
-            mWindowManagerService = windowManagerService;
-            mDisplayProvider = displayInfoProvider;
-            mViewportFrame = new ViewportWindow(context, windowManager, displayInfoProvider);
+        private final WindowManager mWindowManager;
+        private final DisplayManager mDisplayManager;
+        private final Display mDisplay;
+        private final DisplayInfo mDisplayInfo = new DisplayInfo();
+        private final int mDisplayId;
 
-            mShowHideFrameAnimator = ObjectAnimator.ofInt(mViewportFrame, PROPERTY_NAME_ALPHA,
-                  MIN_ALPHA, MAX_ALPHA);
-            mShowHideFrameAnimator.setInterpolator(animationInterpolator);
-            mShowHideFrameAnimator.setDuration(animationDuration);
+        private final ContentView mWindowContent;
+        private final WindowManager.LayoutParams mWindowParams;
+
+        private boolean mShown;
+        private int mAlpha;
+
+        public ViewportWindow(Context context, int displayId) {
+            mWindowManager = (WindowManager) context.getSystemService(Service.WINDOW_SERVICE);
+
+            mDisplayId = displayId;
+            mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
+            mDisplay = mDisplayManager.getDisplay(displayId);
+            mDisplayManager.registerDisplayListener(this, null);
+            updateDisplayInfo();
+
+            ViewGroup.LayoutParams contentParams = new ViewGroup.LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+            mWindowContent = new ContentView(context);
+            mWindowContent.setLayoutParams(contentParams);
+            mWindowContent.setBackgroundColor(R.color.transparent);
+
+            mWindowParams = new WindowManager.LayoutParams(
+                    WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY);
+            mWindowParams.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+            mWindowParams.setTitle(WINDOW_TITLE);
+            mWindowParams.gravity = Gravity.CENTER;
+            mWindowParams.width = mDisplayInfo.logicalWidth;
+            mWindowParams.height = mDisplayInfo.logicalHeight;
+            mWindowParams.format = PixelFormat.TRANSLUCENT;
+
+            Interpolator interpolator = new DecelerateInterpolator(2.5f);
+            final long longAnimationDuration = context.getResources().getInteger(
+                    com.android.internal.R.integer.config_longAnimTime);
+
+            mShowHideFrameAnimator = ObjectAnimator.ofInt(this, PROPERTY_NAME_ALPHA,
+                    MIN_ALPHA, MAX_ALPHA);
+            mShowHideFrameAnimator.setInterpolator(interpolator);
+            mShowHideFrameAnimator.setDuration(longAnimationDuration);
             mShowHideFrameAnimator.addListener(new AnimatorListener() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
                     if (mShowHideFrameAnimator.getAnimatedValue().equals(MIN_ALPHA)) {
-                        mViewportFrame.hide();
+                        hide();
                     }
                 }
+
                 @Override
                 public void onAnimationStart(Animator animation) {
                     /* do nothing - stub */
                 }
+
                 @Override
                 public void onAnimationCancel(Animator animation) {
                     /* do nothing - stub */
                 }
+
                 @Override
                 public void onAnimationRepeat(Animator animation) {
                     /* do nothing - stub */
@@ -1514,6 +1280,7 @@
                     Rect.class, PROPERTY_NAME_BOUNDS);
             TypeEvaluator<Rect> evaluator = new TypeEvaluator<Rect>() {
                 private final Rect mReusableResultRect = new Rect();
+
                 @Override
                 public Rect evaluate(float fraction, Rect fromFrame, Rect toFrame) {
                     Rect result = mReusableResultRect;
@@ -1528,103 +1295,18 @@
                     return result;
                 }
             };
-            mResizeFrameAnimator = ObjectAnimator.ofObject(mViewportFrame, property,
-                    evaluator, mViewportFrame.mBounds, mViewportFrame.mBounds);
-            mResizeFrameAnimator.setDuration((long) (animationDuration));
-            mResizeFrameAnimator.setInterpolator(animationInterpolator);
-
-            recomputeBounds(false);
+            mResizeFrameAnimator = ObjectAnimator.ofObject(this, property,
+                    evaluator, mBounds, mBounds);
+            mResizeFrameAnimator.setDuration(longAnimationDuration);
+            mResizeFrameAnimator.setInterpolator(interpolator);
         }
 
-        private final Comparator<WindowInfo> mWindowInfoInverseComparator =
-                new Comparator<WindowInfo>() {
-            @Override
-            public int compare(WindowInfo lhs, WindowInfo rhs) {
-                if (lhs.layer != rhs.layer) {
-                    return rhs.layer - lhs.layer;
-                }
-                if (lhs.touchableRegion.top != rhs.touchableRegion.top) {
-                    return rhs.touchableRegion.top - lhs.touchableRegion.top;
-                }
-                if (lhs.touchableRegion.left != rhs.touchableRegion.left) {
-                    return rhs.touchableRegion.left - lhs.touchableRegion.left;
-                }
-                if (lhs.touchableRegion.right != rhs.touchableRegion.right) {
-                    return rhs.touchableRegion.right - lhs.touchableRegion.right;
-                }
-                if (lhs.touchableRegion.bottom != rhs.touchableRegion.bottom) {
-                    return rhs.touchableRegion.bottom - lhs.touchableRegion.bottom;
-                }
-                return 0;
-            }
-        };
-
-        public void recomputeBounds(boolean animate) {
-            Rect magnifiedFrame = mTempRect1;
-            magnifiedFrame.set(0, 0, 0, 0);
-
-            DisplayInfo displayInfo = mDisplayProvider.getDisplayInfo();
-
-            Rect availableFrame = mTempRect2;
-            availableFrame.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight);
-
-            ArrayList<WindowInfo> infos = mTempWindowInfoList;
-            infos.clear();
-            int windowCount = 0;
-            try {
-                mWindowManagerService.getVisibleWindowsForDisplay(
-                        mDisplayProvider.getDisplay().getDisplayId(), infos);
-                Collections.sort(infos, mWindowInfoInverseComparator);
-                windowCount = infos.size();
-                for (int i = 0; i < windowCount; i++) {
-                    WindowInfo info = infos.get(i);
-                    if (info.type == WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY) {
-                        continue;
-                    }
-                    Rect windowFrame = mTempRect3;
-                    windowFrame.set(info.touchableRegion);
-                    if (isWindowMagnified(info.type)) {
-                        magnifiedFrame.union(windowFrame);
-                        magnifiedFrame.intersect(availableFrame);
-                    } else {
-                        subtract(windowFrame, magnifiedFrame);
-                        subtract(availableFrame, windowFrame);
-                    }
-                    if (availableFrame.equals(magnifiedFrame)) {
-                        break;
-                    }
-                }
-            } catch (RemoteException re) {
-                /* ignore */
-            } finally {
-                for (int i = windowCount - 1; i >= 0; i--) {
-                    infos.remove(i).recycle();
-                }
-            }
-
-            final int displayWidth = mDisplayProvider.getDisplayInfo().logicalWidth;
-            final int displayHeight = mDisplayProvider.getDisplayInfo().logicalHeight;
-            magnifiedFrame.intersect(0, 0, displayWidth, displayHeight);
-
-            resize(magnifiedFrame, animate);
+        public boolean isShown() {
+            return mShown;
         }
 
-        private boolean isWindowMagnified(int type) {
-            return (type != WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
-                    && type != WindowManager.LayoutParams.TYPE_INPUT_METHOD
-                    && type != WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG);
-        }
-
-        public void rotationChanged() {
-            mViewportFrame.rotationChanged();
-        }
-
-        public Rect getBounds() {
-            return mViewportFrame.getBounds();
-        }
-
-        public void setFrameShown(boolean shown, boolean animate) {
-            if (mViewportFrame.isShown() == shown) {
+        public void setShown(boolean shown, boolean animate) {
+            if (isShown() == shown) {
                 return;
             }
             if (animate) {
@@ -1632,7 +1314,7 @@
                     mShowHideFrameAnimator.reverse();
                 } else {
                     if (shown) {
-                        mViewportFrame.show();
+                        show();
                         mShowHideFrameAnimator.start();
                     } else {
                         mShowHideFrameAnimator.reverse();
@@ -1641,221 +1323,140 @@
             } else {
                 mShowHideFrameAnimator.cancel();
                 if (shown) {
-                    mViewportFrame.show();
+                    show();
                 } else {
-                    mViewportFrame.hide();
+                    hide();
                 }
             }
         }
 
-        private void resize(Rect bounds, boolean animate) {
-            if (mViewportFrame.getBounds().equals(bounds)) {
+        private void show() {
+            if (mShown) {
+                return;
+            }
+            mShown = true;
+            mWindowManager.addView(mWindowContent, mWindowParams);
+            if (DEBUG_VIEWPORT_WINDOW) {
+                Slog.i(LOG_TAG, "ViewportWindow shown.");
+            }
+        }
+
+        public void setBounds(Rect bounds, boolean animate) {
+            if (getBounds().equals(bounds)) {
                 return;
             }
             if (animate) {
                 if (mResizeFrameAnimator.isRunning()) {
                     mResizeFrameAnimator.cancel();
                 }
-                mResizeFrameAnimator.setObjectValues(mViewportFrame.mBounds, bounds);
+                mResizeFrameAnimator.setObjectValues(getBounds(), bounds);
                 mResizeFrameAnimator.start();
             } else {
-                mViewportFrame.setBounds(bounds);
+                setBounds(bounds);
             }
         }
 
-        private boolean subtract(Rect lhs, Rect rhs) {
-            if (lhs.right < rhs.left || lhs.left  > rhs.right
-                    || lhs.bottom < rhs.top || lhs.top > rhs.bottom) {
-                return false;
+        private void hide() {
+            if (!mShown) {
+                return;
             }
-            if (lhs.left < rhs.left) {
-                lhs.right = rhs.left;
-            }
-            if (lhs.top < rhs.top) {
-                lhs.bottom = rhs.top;
-            }
-            if (lhs.right > rhs.right) {
-                lhs.left = rhs.right;
-            }
-            if (lhs.bottom > rhs.bottom) {
-                lhs.top = rhs.bottom;
-            }
-            return true;
-        }
-
-        private static final class ViewportWindow {
-            private static final String WINDOW_TITLE = "Magnification Overlay";
-
-            private final WindowManager mWindowManager;
-            private final DisplayProvider mDisplayProvider;
-
-            private final ContentView mWindowContent;
-            private final WindowManager.LayoutParams mWindowParams;
-
-            private final Rect mBounds = new Rect();
-            private boolean mShown;
-            private int mAlpha;
-
-            public ViewportWindow(Context context, WindowManager windowManager,
-                    DisplayProvider displayProvider) {
-                mWindowManager = windowManager;
-                mDisplayProvider = displayProvider;
-
-                ViewGroup.LayoutParams contentParams = new ViewGroup.LayoutParams(
-                        ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
-                mWindowContent = new ContentView(context);
-                mWindowContent.setLayoutParams(contentParams);
-                mWindowContent.setBackgroundColor(R.color.transparent);
-
-                mWindowParams = new WindowManager.LayoutParams(
-                        WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY);
-                mWindowParams.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
-                mWindowParams.setTitle(WINDOW_TITLE);
-                mWindowParams.gravity = Gravity.CENTER;
-                mWindowParams.width = displayProvider.getDisplayInfo().logicalWidth;
-                mWindowParams.height = displayProvider.getDisplayInfo().logicalHeight;
-                mWindowParams.format = PixelFormat.TRANSLUCENT;
-            }
-
-            public boolean isShown() {
-                return mShown;
-            }
-
-            public void show() {
-                if (mShown) {
-                    return;
-                }
-                mShown = true;
-                mWindowManager.addView(mWindowContent, mWindowParams);
-                if (DEBUG_VIEWPORT_WINDOW) {
-                    Slog.i(LOG_TAG, "ViewportWindow shown.");
-                }
-            }
-
-            public void hide() {
-                if (!mShown) {
-                    return;
-                }
-                mShown = false;
-                mWindowManager.removeView(mWindowContent);
-                if (DEBUG_VIEWPORT_WINDOW) {
-                    Slog.i(LOG_TAG, "ViewportWindow hidden.");
-                }
-            }
-
-            @SuppressWarnings("unused")
-            // Called reflectively from an animator.
-            public int getAlpha() {
-                return mAlpha;
-            }
-
-            @SuppressWarnings("unused")
-            // Called reflectively from an animator.
-            public void setAlpha(int alpha) {
-                if (mAlpha == alpha) {
-                    return;
-                }
-                mAlpha = alpha;
-                if (mShown) {
-                    mWindowContent.invalidate();
-                }
-                if (DEBUG_VIEWPORT_WINDOW) {
-                    Slog.i(LOG_TAG, "ViewportFrame set alpha: " + alpha);
-                }
-            }
-
-            public Rect getBounds() {
-                return mBounds;
-            }
-
-            public void rotationChanged() {
-                mWindowParams.width = mDisplayProvider.getDisplayInfo().logicalWidth;
-                mWindowParams.height = mDisplayProvider.getDisplayInfo().logicalHeight;
-                if (mShown) {
-                    mWindowManager.updateViewLayout(mWindowContent, mWindowParams);
-                }
-            }
-
-            public void setBounds(Rect bounds) {
-                if (mBounds.equals(bounds)) {
-                    return;
-                }
-                mBounds.set(bounds);
-                if (mShown) {
-                    mWindowContent.invalidate();
-                }
-                if (DEBUG_VIEWPORT_WINDOW) {
-                    Slog.i(LOG_TAG, "ViewportFrame set bounds: " + bounds);
-                }
-            }
-
-            private final class ContentView extends View {
-                private final Drawable mHighlightFrame;
-
-                public ContentView(Context context) {
-                    super(context);
-                    mHighlightFrame = context.getResources().getDrawable(
-                            R.drawable.magnified_region_frame);
-                }
-
-                @Override
-                public void onDraw(Canvas canvas) {
-                    canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
-                    mHighlightFrame.setBounds(mBounds);
-                    mHighlightFrame.setAlpha(mAlpha);
-                    mHighlightFrame.draw(canvas);
-                }
+            mShown = false;
+            mWindowManager.removeView(mWindowContent);
+            if (DEBUG_VIEWPORT_WINDOW) {
+                Slog.i(LOG_TAG, "ViewportWindow hidden.");
             }
         }
-    }
 
-    private static class DisplayProvider implements DisplayListener {
-        private final WindowManager mWindowManager;
-        private final DisplayManager mDisplayManager;
-        private final Display mDefaultDisplay;
-        private final DisplayInfo mDefaultDisplayInfo = new DisplayInfo();
-
-        public DisplayProvider(Context context, WindowManager windowManager) {
-            mWindowManager = windowManager;
-            mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
-            mDefaultDisplay = mWindowManager.getDefaultDisplay();
-            mDisplayManager.registerDisplayListener(this, null);
-            updateDisplayInfo();
+        @SuppressWarnings("unused")
+        // Called reflectively from an animator.
+        public int getAlpha() {
+            return mAlpha;
         }
 
-        public DisplayInfo getDisplayInfo() {
-            return mDefaultDisplayInfo;
+        @SuppressWarnings("unused")
+        // Called reflectively from an animator.
+        public void setAlpha(int alpha) {
+            if (mAlpha == alpha) {
+                return;
+            }
+            mAlpha = alpha;
+            if (mShown) {
+                mWindowContent.invalidate();
+            }
+            if (DEBUG_VIEWPORT_WINDOW) {
+                Slog.i(LOG_TAG, "ViewportFrame set alpha: " + alpha);
+            }
         }
 
-        public Display getDisplay() {
-            return mDefaultDisplay;
+        public Rect getBounds() {
+            return mBounds;
+        }
+
+        public void setBounds(Rect bounds) {
+            if (mBounds.equals(bounds)) {
+                return;
+            }
+            mBounds.set(bounds);
+            if (mShown) {
+                mWindowContent.invalidate();
+            }
+            if (DEBUG_VIEWPORT_WINDOW) {
+                Slog.i(LOG_TAG, "ViewportFrame set bounds: " + bounds);
+            }
+        }
+
+        public void rotationChanged() {
+            mWindowParams.width = mDisplayInfo.logicalWidth;
+            mWindowParams.height = mDisplayInfo.logicalHeight;
+            if (mShown) {
+                mWindowManager.updateViewLayout(mWindowContent, mWindowParams);
+            }
+        }
+
+        private final class ContentView extends View {
+            private final Drawable mHighlightFrame;
+
+            public ContentView(Context context) {
+                super(context);
+                mHighlightFrame = context.getResources().getDrawable(
+                        R.drawable.magnified_region_frame);
+            }
+
+            @Override
+            public void onDraw(Canvas canvas) {
+                canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
+                mHighlightFrame.setBounds(mBounds);
+                mHighlightFrame.setAlpha(mAlpha);
+                mHighlightFrame.draw(canvas);
+            }
         }
 
         private void updateDisplayInfo() {
-            if (!mDefaultDisplay.getDisplayInfo(mDefaultDisplayInfo)) {
-                Slog.e(LOG_TAG, "Default display is not valid.");
+            if (!mDisplay.getDisplayInfo(mDisplayInfo)) {
+                Slog.e(LOG_TAG, "Display is not valid.");
             }
         }
 
         public void destroy() {
+            setShown(false, true);
             mDisplayManager.unregisterDisplayListener(this);
         }
 
         @Override
+        public void onDisplayChanged(int displayId) {
+            if (mDisplayId == displayId) {
+                updateDisplayInfo();
+            }
+        }
+
+        @Override
         public void onDisplayAdded(int displayId) {
-            /* do noting */
+            /* do nothing */
         }
 
         @Override
         public void onDisplayRemoved(int displayId) {
-            // Having no default display
-        }
-
-        @Override
-        public void onDisplayChanged(int displayId) {
-            updateDisplayInfo();
+            /* do nothing */
         }
     }
 }
diff --git a/services/java/com/android/server/wm/DisplayContent.java b/services/java/com/android/server/wm/DisplayContent.java
index 68cdbfc..59e4b0e 100644
--- a/services/java/com/android/server/wm/DisplayContent.java
+++ b/services/java/com/android/server/wm/DisplayContent.java
@@ -16,10 +16,8 @@
 
 package com.android.server.wm;
 
-import android.os.RemoteCallbackList;
 import android.view.Display;
 import android.view.DisplayInfo;
-import android.view.IDisplayContentChangeListener;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -43,12 +41,6 @@
      * from mDisplayWindows; */
     private WindowList mWindows = new WindowList();
 
-    // Specification for magnifying the display content.
-    MagnificationSpec mMagnificationSpec;
-
-    // Callback for observing content changes on a display.
-    RemoteCallbackList<IDisplayContentChangeListener> mDisplayContentChangeListeners;
-
     // This protects the following display size properties, so that
     // getDisplaySize() doesn't need to acquire the global lock.  This is
     // needed because the window manager sometimes needs to use ActivityThread
@@ -128,9 +120,6 @@
             pw.print("-"); pw.print(mDisplayInfo.largestNominalAppWidth);
             pw.print("x"); pw.println(mDisplayInfo.largestNominalAppHeight);
         pw.print(subPrefix); pw.print("layoutNeeded="); pw.print(layoutNeeded);
-        if (mMagnificationSpec != null) {
-            pw.print(" mMagnificationSpec="); pw.print(mMagnificationSpec);
-        }
         pw.println();
     }
 }
diff --git a/services/java/com/android/server/wm/DisplayMagnificationMediator.java b/services/java/com/android/server/wm/DisplayMagnificationMediator.java
new file mode 100644
index 0000000..8621c5c
--- /dev/null
+++ b/services/java/com/android/server/wm/DisplayMagnificationMediator.java
@@ -0,0 +1,616 @@
+package com.android.server.wm;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.Pools.SynchronizedPool;
+import android.view.DisplayInfo;
+import android.view.IDisplayMagnificationController;
+import android.view.IDisplayMagnificationMediator;
+import android.view.MagnificationSpec;
+import android.view.Surface;
+import android.view.WindowManager;
+import android.view.WindowManagerPolicy;
+
+import com.android.internal.os.SomeArgs;
+import com.android.internal.policy.impl.PhoneWindowManager;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+
+final class DisplayMagnificationMediator extends IDisplayMagnificationMediator.Stub
+        implements DisplayListener {
+    private static final String LOG_TAG = DisplayMagnificationMediator.class.getSimpleName();
+
+    private static final boolean DEBUG_WINDOW_TRANSITIONS = false;
+    private static final boolean DEBUG_ROTATION = false;
+    private static final boolean DEBUG_LAYERS = false;
+    private static final boolean DEBUG_RECTANGLE_REQUESTED = false;
+
+    private static final int MESSAGE_NOTIFY_MAGNIFIED_FRAME_CHANGED = 1;
+    private static final int MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED = 2;
+    private static final int MESSAGE_NOTIFY_USER_CONTEXT_CHANGED = 3;
+    private static final int MESSAGE_NOTIFY_ROTATION_CHANGED = 4;
+
+    private static final String METHOD_SIGNATURE_ADD_CONTROLLER =
+            "addController(int, IDisplayMagnificationController)";
+    private static final String METHOD_SIGNATURE_REMOVE_CONTROLLER =
+            "removeController(IDisplayMagnificationController, int)";
+    private static final String METHOD_SIGNATURE_SET_MAGNIFICATION_SPEC =
+            "setMagnificationSpec(IDisplayMagnificationController, MagnificationSpec)";
+
+    private final Rect mTempRect = new Rect();
+    private final Rect mTempRect1 = new Rect();
+    private final Region mTempRegion = new Region();
+
+    private final SparseArray<DisplayState> mDisplayStates =
+            new SparseArray<DisplayMagnificationMediator.DisplayState>();
+
+    private final Context mContext;
+    private final WindowManagerService mWindowManagerService;
+
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message message) {
+            switch (message.what) {
+                case MESSAGE_NOTIFY_MAGNIFIED_FRAME_CHANGED: {
+                    SomeArgs args = (SomeArgs) message.obj;
+                    IDisplayMagnificationController client =
+                            (IDisplayMagnificationController) args.arg1;
+                    final int left = args.argi1;
+                    final int top = args.argi2;
+                    final int right = args.argi3;
+                    final int bottom = args.argi4;
+                    try {
+                        client.onMagnifedFrameChanged(left, top, right, bottom);
+                    } catch (RemoteException re) {
+                        /* ignore */
+                    } finally {
+                        args.recycle();
+                    }
+                } break;
+                case MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED: {
+                    SomeArgs args = (SomeArgs) message.obj;
+                    IDisplayMagnificationController client =
+                            (IDisplayMagnificationController) args.arg1;
+                    final int left = args.argi1;
+                    final int top = args.argi2;
+                    final int right = args.argi3;
+                    final int bottom = args.argi4;
+                    try {
+                        client.onRectangleOnScreenRequested(left, top, right, bottom);
+                    } catch (RemoteException re) {
+                        /* ignore */
+                    } finally {
+                        args.recycle();
+                    }
+                } break;
+                case MESSAGE_NOTIFY_USER_CONTEXT_CHANGED: {
+                    IDisplayMagnificationController client =
+                            (IDisplayMagnificationController) message.obj;
+                    try {
+                        client.onUserContextChanged();
+                    } catch (RemoteException re) {
+                        /* ignore */
+                    }
+                } break;
+                case MESSAGE_NOTIFY_ROTATION_CHANGED: {
+                    IDisplayMagnificationController client =
+                            (IDisplayMagnificationController) message.obj;
+                    final int rotation = message.arg1;
+                    try {
+                        client.onRotationChanged(rotation);
+                    } catch (RemoteException re) {
+                        /* ignore */
+                    }
+                } break;
+            }
+        }
+    };
+
+    public DisplayMagnificationMediator(WindowManagerService windowManagerService) {
+        mContext = windowManagerService.mContext;
+        mWindowManagerService = windowManagerService;
+        DisplayManager displayManager = (DisplayManager)
+                mContext.getSystemService(Context.DISPLAY_SERVICE);
+        displayManager.registerDisplayListener(this, mHandler);
+    }
+
+    @Override
+    public void addController(int displayId, IDisplayMagnificationController controller) {
+        enforceCallingPermission(Manifest.permission.MAGNIFY_DISPLAY,
+                METHOD_SIGNATURE_ADD_CONTROLLER);
+        synchronized (mWindowManagerService.mWindowMap) {
+            DisplayState displayState = mDisplayStates.get(displayId);
+            if (displayState != null) {
+                displayState.clearLw();
+            }
+            mDisplayStates.remove(displayId);
+            mDisplayStates.put(displayId, new DisplayState(displayId, controller));
+        }
+    }
+
+    @Override
+    public void removeController(IDisplayMagnificationController controller) {
+        enforceCallingPermission(Manifest.permission.MAGNIFY_DISPLAY,
+                METHOD_SIGNATURE_REMOVE_CONTROLLER);
+        synchronized (mWindowManagerService.mWindowMap) {
+            final int displayStateCount = mDisplayStates.size();
+            for (int i = 0; i < displayStateCount; i++) {
+                DisplayState displayState = mDisplayStates.valueAt(i);
+                if (displayState.mClient.asBinder() == controller.asBinder()) {
+                    displayState.clearLw();
+                    mDisplayStates.removeAt(i);
+                    return;
+                }
+            }
+        }
+    }
+
+    @Override
+    public void setMagnificationSpec(IDisplayMagnificationController controller,
+            MagnificationSpec spec) {
+        enforceCallingPermission(Manifest.permission.MAGNIFY_DISPLAY,
+                METHOD_SIGNATURE_SET_MAGNIFICATION_SPEC);
+        synchronized (mWindowManagerService.mWindowMap) {
+            DisplayState displayState = null;
+            final int displayStateCount = mDisplayStates.size();
+            for (int i = 0; i < displayStateCount; i++) {
+                DisplayState candidate = mDisplayStates.valueAt(i);
+                if (candidate.mClient.asBinder() == controller.asBinder()) {
+                    displayState = candidate;
+                    break;
+                }
+            }
+            if (displayState == null) {
+                Slog.e(LOG_TAG, "Setting magnification spec for unregistered controller "
+                        + controller);
+                return;
+            }
+            displayState.mMagnificationSpec.initialize(spec.scale, spec.offsetX,
+                    spec.offsetY);
+            spec.recycle();
+        }
+        synchronized (mWindowManagerService.mLayoutToAnim) {
+            mWindowManagerService.scheduleAnimationLocked();
+        }
+    }
+
+    @Override
+    public MagnificationSpec getCompatibleMagnificationSpec(IBinder windowToken) {
+        synchronized (mWindowManagerService.mWindowMap) {
+            WindowState windowState = mWindowManagerService.mWindowMap.get(windowToken);
+            if (windowState == null) {
+                return null;
+            }
+            MagnificationSpec spec = getMagnificationSpecLw(windowState);
+            if ((spec == null || spec.isNop()) && windowState.mGlobalScale == 1.0f) {
+                return null;
+            }
+            if (spec == null) {
+                spec = MagnificationSpec.obtain();
+            } else {
+                spec = MagnificationSpec.obtain(spec);
+            }
+            spec.scale *= windowState.mGlobalScale;
+            return spec;
+        }
+    }
+
+    @Override
+    public void onDisplayAdded(int displayId) {
+        /* do nothing */
+    }
+
+    @Override
+    public void onDisplayRemoved(int displayId) {
+        synchronized (mWindowManagerService.mWindowMap) {
+            DisplayState displayState = mDisplayStates.get(displayId);
+            if (displayState != null) {
+                displayState.clearLw();
+                mDisplayStates.remove(displayId);
+            }
+        }
+    }
+
+    @Override
+    public void onDisplayChanged(int displayId) {
+        /* do nothing */
+    }
+
+    public void onRectangleOnScreenRequestedLw(WindowState windowState, Rect rectangle,
+            boolean immediate) {
+        DisplayState displayState = mDisplayStates.get(windowState.getDisplayId());
+        if (displayState == null) {
+            return;
+        }
+        if (DEBUG_RECTANGLE_REQUESTED) {
+            Slog.i(LOG_TAG, "Rectangle on screen requested: " + rectangle
+                    + " displayId: " + windowState.getDisplayId());
+        }
+        if (!displayState.isMagnifyingLw()) {
+            return;
+        }
+        Rect magnifiedRegionBounds = mTempRect1;
+        displayState.getMagnifiedFrameInContentCoordsLw(magnifiedRegionBounds);
+        if (magnifiedRegionBounds.contains(rectangle)) {
+            return;
+        }
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = displayState.mClient;
+        args.argi1 = rectangle.left;
+        args.argi2 = rectangle.top;
+        args.argi3 = rectangle.right;
+        args.argi4 = rectangle.bottom;
+        mHandler.obtainMessage(MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED,
+                args).sendToTarget();
+    }
+
+    public void onWindowLayersChangedLw(int displayId) {
+        DisplayState displayState = mDisplayStates.get(displayId);
+        if (displayState == null) {
+            return;
+        }
+        if (DEBUG_LAYERS) {
+            Slog.i(LOG_TAG, "Layers changed displayId: " + displayId);
+        }
+        displayState.mViewport.recomputeBoundsLw();
+    }
+
+    public void onRotationChangedLw(int displayId, int rotation) {
+        DisplayState displayState = mDisplayStates.get(displayId);
+        if (displayState == null) {
+            return;
+        }
+        if (DEBUG_ROTATION) {
+            Slog.i(LOG_TAG, "Rotaton: " + Surface.rotationToString(rotation)
+                    + " displayId: " + displayId);
+        }
+        displayState.mViewport.recomputeBoundsLw();
+        mHandler.obtainMessage(MESSAGE_NOTIFY_ROTATION_CHANGED, rotation, 0,
+                displayState.mClient).sendToTarget();
+    }
+
+    public void onWindowTransitionLw(WindowState windowState, int transition) {
+        DisplayState displayState = mDisplayStates.get(windowState.getDisplayId());
+        if (displayState == null) {
+            return;
+        }
+        if (DEBUG_WINDOW_TRANSITIONS) {
+            Slog.i(LOG_TAG, "Window transition: "
+                    + PhoneWindowManager.windowTransitionToString(transition)
+                    + " displayId: " + windowState.getDisplayId());
+        }
+        final boolean magnifying = displayState.isMagnifyingLw();
+        if (magnifying) {
+            switch (transition) {
+                case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN:
+                case WindowManagerPolicy.TRANSIT_TASK_OPEN:
+                case WindowManagerPolicy.TRANSIT_TASK_TO_FRONT:
+                case WindowManagerPolicy.TRANSIT_WALLPAPER_OPEN:
+                case WindowManagerPolicy.TRANSIT_WALLPAPER_CLOSE:
+                case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN: {
+                    mHandler.obtainMessage(MESSAGE_NOTIFY_USER_CONTEXT_CHANGED,
+                            displayState.mClient).sendToTarget();
+                }
+            }
+        }
+        final int type = windowState.mAttrs.type;
+        if (type == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
+                || type == WindowManager.LayoutParams.TYPE_INPUT_METHOD
+                || type == WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG
+                || type == WindowManager.LayoutParams.TYPE_KEYGUARD
+                || type == WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG) {
+            switch (transition) {
+                case WindowManagerPolicy.TRANSIT_ENTER:
+                case WindowManagerPolicy.TRANSIT_SHOW:
+                case WindowManagerPolicy.TRANSIT_EXIT:
+                case WindowManagerPolicy.TRANSIT_HIDE: {
+                    displayState.mViewport.recomputeBoundsLw();
+                } break;
+            }
+        }
+        switch (transition) {
+            case WindowManagerPolicy.TRANSIT_ENTER:
+            case WindowManagerPolicy.TRANSIT_SHOW: {
+                if (!magnifying) {
+                    break;
+                }
+                switch (type) {
+                    case WindowManager.LayoutParams.TYPE_APPLICATION:
+                    case WindowManager.LayoutParams.TYPE_APPLICATION_PANEL:
+                    case WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA:
+                    case WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL:
+                    case WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG:
+                    case WindowManager.LayoutParams.TYPE_SEARCH_BAR:
+                    case WindowManager.LayoutParams.TYPE_PHONE:
+                    case WindowManager.LayoutParams.TYPE_SYSTEM_ALERT:
+                    case WindowManager.LayoutParams.TYPE_TOAST:
+                    case WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY:
+                    case WindowManager.LayoutParams.TYPE_PRIORITY_PHONE:
+                    case WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG:
+                    case WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG:
+                    case WindowManager.LayoutParams.TYPE_SYSTEM_ERROR:
+                    case WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY:
+                    case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL:
+                    case WindowManager.LayoutParams.TYPE_RECENTS_OVERLAY: {
+                        Rect magnifiedRegionBounds = mTempRect1;
+                        displayState.getMagnifiedFrameInContentCoordsLw(magnifiedRegionBounds);
+                        Rect touchableRegionBounds = mTempRect;
+                        windowState.getTouchableRegion(mTempRegion);
+                        mTempRegion.getBounds(touchableRegionBounds);
+                        if (!magnifiedRegionBounds.intersect(touchableRegionBounds)) {
+                            SomeArgs args = SomeArgs.obtain();
+                            args.arg1 = displayState.mClient;
+                            args.argi1 = touchableRegionBounds.left;
+                            args.argi2 = touchableRegionBounds.top;
+                            args.argi3 = touchableRegionBounds.right;
+                            args.argi4 = touchableRegionBounds.bottom;
+                            mHandler.obtainMessage(MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED,
+                                    args).sendToTarget();
+                        }
+                    } break;
+                } break;
+            }
+        }
+    }
+
+    public MagnificationSpec getMagnificationSpecLw(WindowState windowState) {
+        DisplayState displayState = mDisplayStates.get(windowState.getDisplayId());
+        if (displayState == null) {
+            return null;
+        }
+        MagnificationSpec spec = displayState.mMagnificationSpec;
+        if (spec != null && !spec.isNop()) {
+            if (windowState.mAttachedWindow != null) {
+                if (!canMagnifyWindow(windowState.mAttachedWindow.mAttrs.type)) {
+                    return null;
+                }
+            }
+            if (!canMagnifyWindow(windowState.mAttrs.type)) {
+                return null;
+            }
+        }
+        return spec;
+    }
+
+    private void enforceCallingPermission(String permission, String function) {
+        if (Process.myPid() == Binder.getCallingPid()) {
+            return;
+        }
+        if (mContext.checkCallingPermission(permission) != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("The caller does not have " + permission
+                    + " required to call " + function);
+        }
+    }
+
+    private static boolean canMagnifyWindow(int type) {
+        switch (type) {
+            case WindowManager.LayoutParams.TYPE_INPUT_METHOD:
+            case WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG:
+            case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR:
+            case WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY: {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static final class Viewport {
+
+        private final ArrayList<WindowStateInfo> mTempWindowStateInfoList =
+                new ArrayList<WindowStateInfo>();
+
+        private final Rect mTempRect1 = new Rect();
+        private final Rect mTempRect2 = new Rect();
+        private final Rect mTempRect3 = new Rect();
+
+        private final Rect mBounds = new Rect();
+        private final Handler mHandler;
+        private final IDisplayMagnificationController mClient;
+        private final WindowManagerService mWindowManagerService;
+        private final DisplayInfo mDisplayInfo;
+
+        private final int mDisplayId;
+
+        public Viewport(Context context, int displayId, Handler handler,
+                IDisplayMagnificationController client, WindowManagerService windowManagerService) {
+            mDisplayId = displayId;
+            mHandler = handler;
+            mWindowManagerService = windowManagerService;
+            mDisplayInfo = mWindowManagerService.getDisplayContentLocked(displayId)
+                    .getDisplayInfo();
+            mClient = client;
+            recomputeBoundsLw();
+        }
+
+        private final Comparator<WindowStateInfo> mWindowInfoInverseComparator =
+                new Comparator<WindowStateInfo>() {
+            @Override
+            public int compare(WindowStateInfo lhs, WindowStateInfo rhs) {
+                if (lhs.mWindowState.mLayer != rhs.mWindowState.mLayer) {
+                    return rhs.mWindowState.mLayer - lhs.mWindowState.mLayer;
+                }
+                if (lhs.mTouchableRegion.top != rhs.mTouchableRegion.top) {
+                    return rhs.mTouchableRegion.top - lhs.mTouchableRegion.top;
+                }
+                if (lhs.mTouchableRegion.left != rhs.mTouchableRegion.left) {
+                    return rhs.mTouchableRegion.left - lhs.mTouchableRegion.left;
+                }
+                if (lhs.mTouchableRegion.right != rhs.mTouchableRegion.right) {
+                    return rhs.mTouchableRegion.right - lhs.mTouchableRegion.right;
+                }
+                if (lhs.mTouchableRegion.bottom != rhs.mTouchableRegion.bottom) {
+                    return rhs.mTouchableRegion.bottom - lhs.mTouchableRegion.bottom;
+                }
+                return 0;
+            }
+        };
+
+        public void recomputeBoundsLw() {
+            Rect magnifiedFrame = mBounds;
+            magnifiedFrame.set(0, 0, 0, 0);
+
+            Rect oldmagnifiedFrame = mTempRect3;
+            oldmagnifiedFrame.set(magnifiedFrame);
+
+            Rect availableFrame = mTempRect1;
+            availableFrame.set(0, 0, mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
+
+            ArrayList<WindowStateInfo> visibleWindows = mTempWindowStateInfoList;
+            visibleWindows.clear();
+            getVisibleWindowsLw(visibleWindows);
+
+            Collections.sort(visibleWindows, mWindowInfoInverseComparator);
+
+            final int visibleWindowCount = visibleWindows.size();
+            for (int i = 0; i < visibleWindowCount; i++) {
+                WindowStateInfo info = visibleWindows.get(i);
+                if (info.mWindowState.mAttrs.type == WindowManager
+                        .LayoutParams.TYPE_MAGNIFICATION_OVERLAY) {
+                    continue;
+                }
+                Rect windowFrame = mTempRect2;
+                windowFrame.set(info.mTouchableRegion);
+                if (canMagnifyWindow(info.mWindowState.mAttrs.type)) {
+                    magnifiedFrame.union(windowFrame);
+                    magnifiedFrame.intersect(availableFrame);
+                } else {
+                    subtract(windowFrame, magnifiedFrame);
+                    subtract(availableFrame, windowFrame);
+                }
+                if (availableFrame.equals(magnifiedFrame)) {
+                    break;
+                }
+            }
+            for (int i = visibleWindowCount - 1; i >= 0; i--) {
+                visibleWindows.remove(i).recycle();
+            }
+
+            final int displayWidth = mDisplayInfo.logicalWidth;
+            final int displayHeight = mDisplayInfo.logicalHeight;
+            magnifiedFrame.intersect(0, 0, displayWidth, displayHeight);
+
+            if (!oldmagnifiedFrame.equals(magnifiedFrame)) {
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = mClient;
+                args.argi1 = magnifiedFrame.left;
+                args.argi2 = magnifiedFrame.top;
+                args.argi3 = magnifiedFrame.right;
+                args.argi4 = magnifiedFrame.bottom;
+                mHandler.obtainMessage(MESSAGE_NOTIFY_MAGNIFIED_FRAME_CHANGED, args)
+                        .sendToTarget();
+            }
+        }
+
+        private void getVisibleWindowsLw(ArrayList<WindowStateInfo> outWindowStates) {
+            DisplayContent displayContent = mWindowManagerService.getDisplayContentLocked(
+                    mDisplayId);
+            WindowList windowList = displayContent.getWindowList();
+            final int windowCount = windowList.size();
+            for (int i = 0; i < windowCount; i++) {
+                WindowState windowState = windowList.get(i);
+                if (windowState.isVisibleLw() || windowState.mAttrs.type == WindowManager
+                        .LayoutParams.TYPE_UNIVERSE_BACKGROUND) {
+                    outWindowStates.add(WindowStateInfo.obtain(windowState));
+                }
+            }
+        }
+
+        public Rect getBoundsLw() {
+            return mBounds;
+        }
+
+        private static boolean subtract(Rect lhs, Rect rhs) {
+            if (lhs.right < rhs.left || lhs.left  > rhs.right
+                    || lhs.bottom < rhs.top || lhs.top > rhs.bottom) {
+                return false;
+            }
+            if (lhs.left < rhs.left) {
+                lhs.right = rhs.left;
+            }
+            if (lhs.top < rhs.top) {
+                lhs.bottom = rhs.top;
+            }
+            if (lhs.right > rhs.right) {
+                lhs.left = rhs.right;
+            }
+            if (lhs.bottom > rhs.bottom) {
+                lhs.top = rhs.bottom;
+            }
+            return true;
+        }
+
+        private static final class WindowStateInfo {
+            private static final int MAX_POOL_SIZE = 30;
+
+            private static final SynchronizedPool<WindowStateInfo> sPool =
+                    new SynchronizedPool<WindowStateInfo>(MAX_POOL_SIZE);
+
+            private static final Region mTempRegion = new Region();
+
+            public WindowState mWindowState;
+            public final Rect mTouchableRegion = new Rect();
+
+            public static WindowStateInfo obtain(WindowState windowState) {
+                WindowStateInfo info = sPool.acquire();
+                if (info == null) {
+                    info = new WindowStateInfo();
+                }
+                info.mWindowState = windowState;
+                windowState.getTouchableRegion(mTempRegion);
+                mTempRegion.getBounds(info.mTouchableRegion);
+                return info;
+            }
+
+            public void recycle() {
+                mWindowState = null;
+                mTouchableRegion.setEmpty();
+                sPool.release(this);
+            }
+        }
+    }
+
+    private final class DisplayState {
+        final int mDisplayId;
+        final MagnificationSpec mMagnificationSpec;
+        final Viewport mViewport;
+        final IDisplayMagnificationController mClient;
+
+        DisplayState(int displayId, IDisplayMagnificationController client) {
+            mDisplayId = displayId;
+            mClient = client;
+            mMagnificationSpec = MagnificationSpec.obtain();
+            mViewport = new Viewport(mContext, mDisplayId, mHandler,
+                    mClient, mWindowManagerService);
+        }
+
+        public boolean isMagnifyingLw() {
+            return mMagnificationSpec.scale > 1.0f;
+        }
+
+        private void getMagnifiedFrameInContentCoordsLw(Rect rect) {
+            MagnificationSpec spec = mMagnificationSpec;
+            rect.set(mViewport.getBoundsLw());
+            rect.offset((int) -spec.offsetX, (int) -spec.offsetY);
+            rect.scale(1.0f / spec.scale);
+        }
+
+        public void clearLw() {
+            mMagnificationSpec.recycle();
+        }
+    }
+}
diff --git a/services/java/com/android/server/wm/MagnificationSpec.java b/services/java/com/android/server/wm/MagnificationSpec.java
deleted file mode 100644
index 31aae66..0000000
--- a/services/java/com/android/server/wm/MagnificationSpec.java
+++ /dev/null
@@ -1,46 +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.server.wm;
-
-public class MagnificationSpec {
-    public float mScale = 1.0f;
-    public float mOffsetX;
-    public float mOffsetY;
-
-    public void initialize(float scale, float offsetX, float offsetY) {
-        mScale = scale;
-        mOffsetX = offsetX;
-        mOffsetY = offsetY;
-    }
-
-    public boolean isNop() {
-        return mScale == 1.0f && mOffsetX == 0 && mOffsetY == 0;
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder builder = new StringBuilder();
-        builder.append("<scale:");
-        builder.append(mScale);
-        builder.append(",offsetX:");
-        builder.append(mOffsetX);
-        builder.append(",offsetY:");
-        builder.append(mOffsetY);
-        builder.append(">");
-        return builder.toString();
-    }
-}
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 69964e47..dc1121b 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -91,7 +91,6 @@
 import android.os.ParcelFileDescriptor;
 import android.os.PowerManager;
 import android.os.Process;
-import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.StrictMode;
@@ -114,7 +113,7 @@
 import android.view.DisplayInfo;
 import android.view.Gravity;
 import android.view.IApplicationToken;
-import android.view.IDisplayContentChangeListener;
+import android.view.IDisplayMagnificationMediator;
 import android.view.IInputFilter;
 import android.view.IOnKeyguardExitResult;
 import android.view.IRotationWatcher;
@@ -126,12 +125,12 @@
 import android.view.InputEvent;
 import android.view.InputEventReceiver;
 import android.view.KeyEvent;
+import android.view.MagnificationSpec;
 import android.view.MotionEvent;
 import android.view.Surface;
 import android.view.SurfaceSession;
 import android.view.View;
 import android.view.ViewTreeObserver;
-import android.view.WindowInfo;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 import android.view.WindowManagerPolicy;
@@ -424,6 +423,8 @@
 
     IInputMethodManager mInputMethodManager;
 
+    DisplayMagnificationMediator mMagnificationMediator;
+
     final SurfaceSession mFxSession;
     Watermark mWatermark;
     StrictModeFlash mStrictModeFlash;
@@ -2374,7 +2375,9 @@
                 if (win.mWinAnimator.applyAnimationLocked(transit, false)) {
                     win.mExiting = true;
                 }
-                scheduleNotifyWindowTranstionIfNeededLocked(win, transit);
+                if (mMagnificationMediator != null) {
+                    mMagnificationMediator.onWindowTransitionLw(win, transit);
+                }
             }
             if (win.mExiting || win.mWinAnimator.isAnimating()) {
                 // The exit animation is running... wait for it!
@@ -2666,49 +2669,13 @@
 
     public void onRectangleOnScreenRequested(IBinder token, Rect rectangle, boolean immediate) {
         synchronized (mWindowMap) {
-            WindowState window = mWindowMap.get(token);
-            if (window != null) {
-                scheduleNotifyRectangleOnScreenRequestedIfNeededLocked(window, rectangle,
-                        immediate);
-            }
-        }
-    }
-
-    private void scheduleNotifyRectangleOnScreenRequestedIfNeededLocked(WindowState window,
-            Rect rectangle, boolean immediate) {
-        DisplayContent displayContent = window.mDisplayContent;
-        if (displayContent.mDisplayContentChangeListeners != null
-                && displayContent.mDisplayContentChangeListeners.getRegisteredCallbackCount() > 0) {
-            mH.obtainMessage(H.NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED, displayContent.getDisplayId(),
-                    immediate? 1 : 0, new Rect(rectangle)).sendToTarget();
-        }
-    }
-
-    private void handleNotifyRectangleOnScreenRequested(int displayId, Rect rectangle,
-            boolean immediate) {
-        RemoteCallbackList<IDisplayContentChangeListener> callbacks = null;
-        synchronized (mWindowMap) {
-            DisplayContent displayContent = getDisplayContentLocked(displayId);
-            if (displayContent == null) {
-                return;
-            }
-            callbacks = displayContent.mDisplayContentChangeListeners;
-            if (callbacks == null) {
-                return;
-            }
-        }
-        final int callbackCount = callbacks.beginBroadcast();
-        try {
-            for (int i = 0; i < callbackCount; i++) {
-                try {
-                    callbacks.getBroadcastItem(i).onRectangleOnScreenRequested(displayId,
-                            rectangle, immediate);
-                } catch (RemoteException re) {
-                    /* ignore */
+            if (mMagnificationMediator != null) {
+                WindowState window = mWindowMap.get(token);
+                if (window != null) {
+                    mMagnificationMediator.onRectangleOnScreenRequestedLw(window, rectangle,
+                            immediate);
                 }
             }
-        } finally {
-            callbacks.finishBroadcast();
         }
     }
 
@@ -2933,7 +2900,9 @@
                             }
                             winAnimator.destroySurfaceLocked(false);
                         }
-                        scheduleNotifyWindowTranstionIfNeededLocked(win, transit);
+                        if (mMagnificationMediator != null) {
+                            mMagnificationMediator.onWindowTransitionLw(win, transit);
+                        }
                     }
                 }
 
@@ -3072,106 +3041,31 @@
     }
 
     @Override
-    public float getWindowCompatibilityScale(IBinder windowToken) {
-        if (!checkCallingPermission(android.Manifest.permission.RETRIEVE_WINDOW_INFO,
-                "getWindowCompatibilityScale()")) {
-            throw new SecurityException("Requires RETRIEVE_WINDOW_INFO permission.");
-        }
-        synchronized (mWindowMap) {
-            WindowState windowState = mWindowMap.get(windowToken);
-            return (windowState != null) ? windowState.mGlobalScale : 1.0f;
-        }
-    }
-
-    @Override
-    public WindowInfo getWindowInfo(IBinder token) {
+    public void getWindowFrame(IBinder token, Rect outBounds) {
         if (!checkCallingPermission(android.Manifest.permission.RETRIEVE_WINDOW_INFO,
                 "getWindowInfo()")) {
             throw new SecurityException("Requires RETRIEVE_WINDOW_INFO permission.");
         }
         synchronized (mWindowMap) {
-            WindowState window = mWindowMap.get(token);
-            if (window != null) {
-                return getWindowInfoForWindowStateLocked(window);
+            WindowState windowState = mWindowMap.get(token);
+            if (windowState != null) {
+                outBounds.set(windowState.mFrame);
+            } else {
+                outBounds.setEmpty();
             }
-            return null;
         }
     }
 
     @Override
-    public void getVisibleWindowsForDisplay(int displayId, List<WindowInfo> outInfos) {
-        if (!checkCallingPermission(android.Manifest.permission.RETRIEVE_WINDOW_INFO,
-                "getWindowInfos()")) {
+    public IDisplayMagnificationMediator getDisplayMagnificationMediator() {
+        if (!checkCallingPermission(android.Manifest.permission.MAGNIFY_DISPLAY,
+                "getDisplayMagnificationMediator()")) {
             throw new SecurityException("Requires RETRIEVE_WINDOW_INFO permission.");
         }
-        synchronized (mWindowMap) {
-            DisplayContent displayContent = getDisplayContentLocked(displayId);
-            if (displayContent == null) {
-                return;
-            }
-            WindowList windows = displayContent.getWindowList();
-            final int windowCount = windows.size();
-            for (int i = 0; i < windowCount; i++) {
-                WindowState window = windows.get(i);
-                if (window.isVisibleLw() || window.mAttrs.type == TYPE_UNIVERSE_BACKGROUND) {
-                    WindowInfo info = getWindowInfoForWindowStateLocked(window);
-                    outInfos.add(info);
-                }
-            }
+        if (mMagnificationMediator == null) {
+            mMagnificationMediator = new DisplayMagnificationMediator(this);
         }
-    }
-
-    @Override
-    public void magnifyDisplay(int displayId, float scale, float offsetX, float offsetY) {
-        if (!checkCallingPermission(
-                android.Manifest.permission.MAGNIFY_DISPLAY, "magnifyDisplay()")) {
-            throw new SecurityException("Requires MAGNIFY_DISPLAY permission");
-        }
-        synchronized (mWindowMap) {
-            MagnificationSpec spec = getDisplayMagnificationSpecLocked(displayId);
-            if (spec != null) {
-                final boolean scaleChanged = spec.mScale != scale;
-                final boolean offsetChanged = spec.mOffsetX != offsetX || spec.mOffsetY != offsetY;
-                if (!scaleChanged && !offsetChanged) {
-                    return;
-                }
-                spec.initialize(scale, offsetX, offsetY);
-                // If the offset has changed we need to re-add the input windows
-                // since the offsets have to be propagated to the input system.
-                if (offsetChanged) {
-                    // TODO(multidisplay): Input only occurs on the default display.
-                    if (displayId == Display.DEFAULT_DISPLAY) {
-                        mInputMonitor.updateInputWindowsLw(true);
-                    }
-                }
-                scheduleAnimationLocked();
-            }
-        }
-    }
-
-    MagnificationSpec getDisplayMagnificationSpecLocked(int displayId) {
-        DisplayContent displayContent = getDisplayContentLocked(displayId);
-        if (displayContent != null) {
-            if (displayContent.mMagnificationSpec == null) {
-                displayContent.mMagnificationSpec = new MagnificationSpec();
-            }
-            return displayContent.mMagnificationSpec;
-        }
-        return null;
-    }
-
-    private WindowInfo getWindowInfoForWindowStateLocked(WindowState window) {
-        WindowInfo info = WindowInfo.obtain();
-        info.token = window.mToken.token;
-        info.frame.set(window.mFrame);
-        info.type = window.mAttrs.type;
-        info.displayId = window.getDisplayId();
-        info.compatibilityScale = window.mGlobalScale;
-        info.visible = window.isVisibleLw() || info.type == TYPE_UNIVERSE_BACKGROUND;
-        info.layer = window.mLayer;
-        window.getTouchableRegion(mTempRegion);
-        mTempRegion.getBounds(info.touchableRegion);
-        return info;
+        return mMagnificationMediator;
     }
 
     private boolean applyAnimationLocked(AppWindowToken atoken,
@@ -3315,8 +3209,10 @@
                         if (win.isVisibleNow()) {
                             win.mWinAnimator.applyAnimationLocked(WindowManagerPolicy.TRANSIT_EXIT,
                                     false);
-                            scheduleNotifyWindowTranstionIfNeededLocked(win,
-                                    WindowManagerPolicy.TRANSIT_EXIT);
+                            if (mMagnificationMediator != null) {
+                                mMagnificationMediator.onWindowTransitionLw(win,
+                                        WindowManagerPolicy.TRANSIT_EXIT);
+                            }
                             changed = true;
                             win.mDisplayContent.layoutNeeded = true;
                         }
@@ -4071,8 +3967,8 @@
                     delayed = runningAppAnimation = true;
                 }
                 WindowState window = wtoken.findMainWindow();
-                if (window != null) {
-                    scheduleNotifyWindowTranstionIfNeededLocked(window, transit);
+                if (window != null && mMagnificationMediator != null) {
+                    mMagnificationMediator.onWindowTransitionLw(window, transit);
                 }
                 changed = true;
             }
@@ -4091,8 +3987,10 @@
                         if (!runningAppAnimation) {
                             win.mWinAnimator.applyAnimationLocked(
                                     WindowManagerPolicy.TRANSIT_ENTER, true);
-                            scheduleNotifyWindowTranstionIfNeededLocked(win,
-                                    WindowManagerPolicy.TRANSIT_ENTER);
+                            if (mMagnificationMediator != null) {
+                                mMagnificationMediator.onWindowTransitionLw(win,
+                                        WindowManagerPolicy.TRANSIT_ENTER);
+                            }
                         }
                         changed = true;
                         win.mDisplayContent.layoutNeeded = true;
@@ -4101,8 +3999,10 @@
                     if (!runningAppAnimation) {
                         win.mWinAnimator.applyAnimationLocked(
                                 WindowManagerPolicy.TRANSIT_EXIT, false);
-                        scheduleNotifyWindowTranstionIfNeededLocked(win,
-                                WindowManagerPolicy.TRANSIT_EXIT);
+                        if (mMagnificationMediator != null) {
+                            mMagnificationMediator.onWindowTransitionLw(win,
+                                    WindowManagerPolicy.TRANSIT_EXIT);
+                        }
                     }
                     changed = true;
                     win.mDisplayContent.layoutNeeded = true;
@@ -5727,7 +5627,9 @@
             }
         }
 
-        scheduleNotifyRotationChangedIfNeededLocked(displayContent, rotation);
+        if (mMagnificationMediator != null) {
+            mMagnificationMediator.onRotationChangedLw(Display.DEFAULT_DISPLAY, rotation);
+        }
 
         return true;
     }
@@ -6109,146 +6011,6 @@
         return success;
     }
 
-    @Override
-    public void addDisplayContentChangeListener(int displayId,
-            IDisplayContentChangeListener listener) {
-        if (!checkCallingPermission(android.Manifest.permission.RETRIEVE_WINDOW_INFO,
-                "addDisplayContentChangeListener()")) {
-            throw new SecurityException("Requires RETRIEVE_WINDOW_INFO permission");
-        }
-        synchronized(mWindowMap) {
-            DisplayContent displayContent = getDisplayContentLocked(displayId);
-            if (displayContent != null) {
-                if (displayContent.mDisplayContentChangeListeners == null) {
-                    displayContent.mDisplayContentChangeListeners =
-                            new RemoteCallbackList<IDisplayContentChangeListener>();
-                    displayContent.mDisplayContentChangeListeners.register(listener);
-                }
-            }
-        }
-    }
-
-    @Override
-    public void removeDisplayContentChangeListener(int displayId,
-            IDisplayContentChangeListener listener) {
-        if (!checkCallingPermission(android.Manifest.permission.RETRIEVE_WINDOW_INFO,
-                "removeDisplayContentChangeListener()")) {
-            throw new SecurityException("Requires RETRIEVE_WINDOW_INFO permission");
-        }
-        synchronized(mWindowMap) {
-            DisplayContent displayContent = getDisplayContentLocked(displayId);
-            if (displayContent != null) {
-                if (displayContent.mDisplayContentChangeListeners != null) {
-                    displayContent.mDisplayContentChangeListeners.unregister(listener);
-                    if (displayContent.mDisplayContentChangeListeners
-                            .getRegisteredCallbackCount() == 0) {
-                        displayContent.mDisplayContentChangeListeners = null;
-                    }
-                }
-            }
-        }
-    }
-
-    void scheduleNotifyWindowTranstionIfNeededLocked(WindowState window, int transition) {
-        DisplayContent displayContent = window.mDisplayContent;
-        if (displayContent.mDisplayContentChangeListeners != null) {
-            WindowInfo info = getWindowInfoForWindowStateLocked(window);
-            mH.obtainMessage(H.NOTIFY_WINDOW_TRANSITION, transition, 0, info).sendToTarget();
-        }
-    }
-
-    private void handleNotifyWindowTranstion(int transition, WindowInfo info) {
-        RemoteCallbackList<IDisplayContentChangeListener> callbacks = null;
-        synchronized (mWindowMap) {
-            DisplayContent displayContent = getDisplayContentLocked(info.displayId);
-            if (displayContent == null) {
-                return;
-            }
-            callbacks = displayContent.mDisplayContentChangeListeners;
-            if (callbacks == null) {
-                return;
-            }
-        }
-        final int callbackCount = callbacks.beginBroadcast();
-        try {
-            for (int i = 0; i < callbackCount; i++) {
-                try {
-                    callbacks.getBroadcastItem(i).onWindowTransition(info.displayId,
-                            transition, info);
-                } catch (RemoteException re) {
-                    /* ignore */
-                }
-            }
-        } finally {
-            callbacks.finishBroadcast();
-        }
-    }
-
-    private void scheduleNotifyRotationChangedIfNeededLocked(DisplayContent displayContent,
-            int rotation) {
-        if (displayContent.mDisplayContentChangeListeners != null
-                && displayContent.mDisplayContentChangeListeners.getRegisteredCallbackCount() > 0) {
-            mH.obtainMessage(H.NOTIFY_ROTATION_CHANGED, displayContent.getDisplayId(),
-                    rotation).sendToTarget();
-        }
-    }
-
-    private void handleNotifyRotationChanged(int displayId, int rotation) {
-        RemoteCallbackList<IDisplayContentChangeListener> callbacks = null;
-        synchronized (mWindowMap) {
-            DisplayContent displayContent = getDisplayContentLocked(displayId);
-            if (displayContent == null) {
-                return;
-            }
-            callbacks = displayContent.mDisplayContentChangeListeners;
-            if (callbacks == null) {
-                return;
-            }
-        }
-        try {
-            final int watcherCount = callbacks.beginBroadcast();
-            for (int i = 0; i < watcherCount; i++) {
-                try {
-                    callbacks.getBroadcastItem(i).onRotationChanged(rotation);
-                } catch (RemoteException re) {
-                    /* ignore */
-                }
-            }
-        } finally {
-            callbacks.finishBroadcast();
-        }
-    }
-
-    private void scheduleNotifyWindowLayersChangedIfNeededLocked(DisplayContent displayContent) {
-        if (displayContent.mDisplayContentChangeListeners != null
-                && displayContent.mDisplayContentChangeListeners.getRegisteredCallbackCount() > 0) {
-            mH.obtainMessage(H.NOTIFY_WINDOW_LAYERS_CHANGED, displayContent) .sendToTarget();
-        }
-    }
-
-    private void handleNotifyWindowLayersChanged(DisplayContent displayContent) {
-        RemoteCallbackList<IDisplayContentChangeListener> callbacks = null;
-        synchronized (mWindowMap) {
-            callbacks = displayContent.mDisplayContentChangeListeners;
-            if (callbacks == null) {
-                return;
-            }
-        }
-        try {
-            final int watcherCount = callbacks.beginBroadcast();
-            for (int i = 0; i < watcherCount; i++) {
-                try {
-                    callbacks.getBroadcastItem(i).onWindowLayersChanged(
-                            displayContent.getDisplayId());
-                } catch (RemoteException re) {
-                    /* ignore */
-                }
-            }
-        } finally {
-            callbacks.finishBroadcast();
-        }
-    }
-
     public void addWindowChangeListener(WindowChangeListener listener) {
         synchronized(mWindowMap) {
             mWindowChangeListeners.add(listener);
@@ -6892,16 +6654,12 @@
         public static final int UPDATE_ANIM_PARAMETERS = 25;
         public static final int SHOW_STRICT_MODE_VIOLATION = 26;
         public static final int DO_ANIMATION_CALLBACK = 27;
-        public static final int NOTIFY_ROTATION_CHANGED = 28;
-        public static final int NOTIFY_WINDOW_TRANSITION = 29;
-        public static final int NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED = 30;
-        public static final int NOTIFY_WINDOW_LAYERS_CHANGED = 31;
 
-        public static final int DO_DISPLAY_ADDED = 32;
-        public static final int DO_DISPLAY_REMOVED = 33;
-        public static final int DO_DISPLAY_CHANGED = 34;
+        public static final int DO_DISPLAY_ADDED = 28;
+        public static final int DO_DISPLAY_REMOVED = 29;
+        public static final int DO_DISPLAY_CHANGED = 30;
 
-        public static final int CLIENT_FREEZE_TIMEOUT = 35;
+        public static final int CLIENT_FREEZE_TIMEOUT = 31;
 
         public static final int ANIMATOR_WHAT_OFFSET = 100000;
         public static final int SET_TRANSPARENT_REGION = ANIMATOR_WHAT_OFFSET + 1;
@@ -7351,34 +7109,6 @@
                     break;
                 }
 
-                case NOTIFY_ROTATION_CHANGED: {
-                    final int displayId = msg.arg1;
-                    final int rotation = msg.arg2;
-                    handleNotifyRotationChanged(displayId, rotation);
-                    break;
-                }
-
-                case NOTIFY_WINDOW_TRANSITION: {
-                    final int transition = msg.arg1;
-                    WindowInfo info = (WindowInfo) msg.obj;
-                    handleNotifyWindowTranstion(transition, info);
-                    break;
-                }
-
-                case NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED: {
-                    final int displayId = msg.arg1;
-                    final boolean immediate = (msg.arg2 == 1);
-                    Rect rectangle = (Rect) msg.obj;
-                    handleNotifyRectangleOnScreenRequested(displayId, rectangle, immediate);
-                    break;
-                }
-
-                case NOTIFY_WINDOW_LAYERS_CHANGED: {
-                    DisplayContent displayContent = (DisplayContent) msg.obj;
-                    handleNotifyWindowLayersChanged(displayContent);
-                    break;
-                }
-
                 case DO_DISPLAY_ADDED:
                     synchronized (mWindowMap) {
                         handleDisplayAddedLocked(msg.arg1);
@@ -7823,8 +7553,9 @@
             //    "Assigned layer " + curLayer + " to " + w.mClient.asBinder());
         }
 
-        if (anyLayerChanged) {
-            scheduleNotifyWindowLayersChangedIfNeededLocked(getDefaultDisplayContentLocked());
+        if (mMagnificationMediator != null && anyLayerChanged) {
+            mMagnificationMediator.onWindowLayersChangedLw(
+                    windows.get(windows.size() - 1).getDisplayId());
         }
     }
 
diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java
index 3b5b1eb..541e859 100644
--- a/services/java/com/android/server/wm/WindowState.java
+++ b/services/java/com/android/server/wm/WindowState.java
@@ -517,21 +517,6 @@
         }
     }
 
-    MagnificationSpec getWindowMagnificationSpecLocked() {
-        MagnificationSpec spec = mDisplayContent.mMagnificationSpec;
-        if (spec != null && !spec.isNop()) {
-            if (mAttachedWindow != null) {
-                if (!mPolicy.canMagnifyWindowLw(mAttachedWindow.mAttrs)) {
-                    return null;
-                }
-            }
-            if (!mPolicy.canMagnifyWindowLw(mAttrs)) {
-                return null;
-            }
-        }
-        return spec;
-    }
-
     @Override
     public Rect getFrameLw() {
         return mFrame;
diff --git a/services/java/com/android/server/wm/WindowStateAnimator.java b/services/java/com/android/server/wm/WindowStateAnimator.java
index 945c5e3..3c9424a 100644
--- a/services/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/java/com/android/server/wm/WindowStateAnimator.java
@@ -16,6 +16,7 @@
 import android.os.Debug;
 import android.util.Slog;
 import android.view.DisplayInfo;
+import android.view.MagnificationSpec;
 import android.view.Surface;
 import android.view.SurfaceSession;
 import android.view.WindowManager;
@@ -918,10 +919,13 @@
             if (screenAnimation) {
                 tmpMatrix.postConcat(screenRotationAnimation.getEnterTransformation().getMatrix());
             }
-            MagnificationSpec spec = mWin.getWindowMagnificationSpecLocked();
-            if (spec != null && !spec.isNop()) {
-                tmpMatrix.postScale(spec.mScale, spec.mScale);
-                tmpMatrix.postTranslate(spec.mOffsetX, spec.mOffsetY);
+            if (mService.mMagnificationMediator != null) {
+                MagnificationSpec spec = mService.mMagnificationMediator
+                        .getMagnificationSpecLw(mWin);
+                if (spec != null && !spec.isNop()) {
+                    tmpMatrix.postScale(spec.scale, spec.scale);
+                    tmpMatrix.postTranslate(spec.offsetX, spec.offsetY);
+                }
             }
 
             // "convert" it into SurfaceFlinger's format
@@ -994,7 +998,8 @@
         final boolean applyUniverseTransformation = (mAnimator.mUniverseBackground != null
                 && mWin.mAttrs.type != WindowManager.LayoutParams.TYPE_UNIVERSE_BACKGROUND
                 && mWin.mBaseLayer < mAnimator.mAboveUniverseLayer);
-        MagnificationSpec spec = mWin.getWindowMagnificationSpecLocked();
+        MagnificationSpec spec = (mService.mMagnificationMediator != null)
+                ? mService.mMagnificationMediator.getMagnificationSpecLw(mWin) : null;
         if (applyUniverseTransformation || spec != null) {
             final Rect frame = mWin.mFrame;
             final float tmpFloats[] = mService.mTmpFloats;
@@ -1008,8 +1013,8 @@
             }
 
             if (spec != null && !spec.isNop()) {
-                tmpMatrix.postScale(spec.mScale, spec.mScale);
-                tmpMatrix.postTranslate(spec.mOffsetX, spec.mOffsetY);
+                tmpMatrix.postScale(spec.scale, spec.scale);
+                tmpMatrix.postTranslate(spec.offsetX, spec.offsetY);
             }
 
             tmpMatrix.getValues(tmpFloats);
@@ -1494,7 +1499,9 @@
             transit = WindowManagerPolicy.TRANSIT_SHOW;
         }
         applyAnimationLocked(transit, true);
-        mService.scheduleNotifyWindowTranstionIfNeededLocked(mWin, transit);
+        if (mService.mMagnificationMediator != null) {
+            mService.mMagnificationMediator.onWindowTransitionLw(mWin, transit);
+        }
     }
 
     // TODO(cmautner): Move back to WindowState?
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 3e625f9..141b323 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -22,6 +22,7 @@
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
+import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.IRemoteCallback;
@@ -30,13 +31,11 @@
 import android.view.Display;
 import android.view.Gravity;
 import android.view.IApplicationToken;
-import android.view.IDisplayContentChangeListener;
 import android.view.IInputFilter;
 import android.view.IOnKeyguardExitResult;
 import android.view.IRotationWatcher;
 import android.view.IWindowManager;
 import android.view.IWindowSession;
-import android.view.WindowInfo;
 
 import java.util.List;
 
@@ -446,7 +445,7 @@
     public void lockNow(Bundle options) {
         // TODO Auto-generated method stub
     }
-    
+
     @Override
     public boolean isSafeModeEnabled() {
         return false;
@@ -464,43 +463,18 @@
     }
 
     @Override
-    public float getWindowCompatibilityScale(IBinder windowToken) throws RemoteException {
-        // TODO Auto-generated method stub
-        return 0;
-    }
-
-    @Override
     public void setInputFilter(IInputFilter filter) throws RemoteException {
         // TODO Auto-generated method stub
     }
 
     @Override
-    public void magnifyDisplay(int dipslayId, float scale, float offsetX, float offsetY)
-            throws RemoteException {
-        // TODO Auto-generated method stub
-    }
-
-    @Override
-    public void addDisplayContentChangeListener(int displayId,
-            IDisplayContentChangeListener listener) throws RemoteException {
-        // TODO Auto-generated method stub
-    }
-
-    @Override
-    public void removeDisplayContentChangeListener(int displayId,
-            IDisplayContentChangeListener listener) throws RemoteException {
-        // TODO Auto-generated method stub
-    }
-
-    @Override
-    public WindowInfo getWindowInfo(IBinder token) throws RemoteException {
+    public IDisplayMagnificationMediator getDisplayMagnificationMediator() {
         // TODO Auto-generated method stub
         return null;
     }
 
     @Override
-    public void getVisibleWindowsForDisplay(int displayId, List<WindowInfo> outInfos)
-            throws RemoteException {
+    public void getWindowFrame(IBinder token, Rect outFrame) {
         // TODO Auto-generated method stub
     }
 }