Merge "Adding FEEDBACK_BRAILLE to AccessibilityServiceInfo." into jb-mr1-dev
diff --git a/Android.mk b/Android.mk
index 07500b1..cb1b90b 100644
--- a/Android.mk
+++ b/Android.mk
@@ -153,6 +153,7 @@
 	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/IInputFilter.aidl \
 	core/java/android/view/IInputFilterHost.aidl \
 	core/java/android/view/IOnKeyguardExitResult.aidl \
diff --git a/api/current.txt b/api/current.txt
index c3c540f..7ad9cad 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -16419,6 +16419,7 @@
     method public void finishBroadcast();
     method public java.lang.Object getBroadcastCookie(int);
     method public E getBroadcastItem(int);
+    method public int getRegisteredCallbackCount();
     method public void kill();
     method public void onCallbackDied(E);
     method public void onCallbackDied(E, java.lang.Object);
diff --git a/core/java/android/os/RemoteCallbackList.java b/core/java/android/os/RemoteCallbackList.java
index b74af16..d02a320 100644
--- a/core/java/android/os/RemoteCallbackList.java
+++ b/core/java/android/os/RemoteCallbackList.java
@@ -304,4 +304,25 @@
         
         mBroadcastCount = -1;
     }
+
+    /**
+     * Returns the number of registered callbacks. Note that the number of registered
+     * callbacks may differ from the value returned by {@link #beginBroadcast()} since
+     * the former returns the number of callbacks registered at the time of the call
+     * and the second the number of callback to which the broadcast will be delivered.
+     * <p>
+     * This function is useful to decide whether to schedule a broadcast if this
+     * requires doing some work which otherwise would not be performed.
+     * </p>
+     *
+     * @return The size.
+     */
+    public int getRegisteredCallbackCount() {
+        synchronized (mCallbacks) {
+            if (mKilled) {
+                return 0;
+            }
+            return mCallbacks.size();
+        }
+    }
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 6b4bc76..d7ae441 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3400,6 +3400,46 @@
             "accessibility_web_content_key_bindings";
 
         /**
+         * Setting that specifies whether the display magnification is enabled.
+         * Display magnifications allows the user to zoom in the display content
+         * and is targeted to low vision users. The current magnification scale
+         * is controlled by {@link #ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE}.
+         *
+         * @hide
+         */
+        public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED =
+                "accessibility_display_magnification_enabled";
+
+        /**
+         * Setting that specifies what the display magnification scale is.
+         * Display magnifications allows the user to zoom in the display
+         * content and is targeted to low vision users. Whether a display
+         * magnification is performed is controlled by
+         * {@link #ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED}
+         *
+         * @hide
+         */
+        public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE =
+                "accessibility_display_magnification_scale";
+
+        /**
+         * Setting that specifies whether the display magnification should be
+         * automatically updated. If this fearture is enabled the system will
+         * exit magnification mode or pan the viewport when a context change
+         * occurs. For example, on staring a new activity or rotating the screen,
+         * the system may zoom out so the user can see the new context he is in.
+         * Another example is on showing a window that is not visible in the
+         * magnified viewport the system may pan the viewport to make the window
+         * the has popped up so the user knows that the context has changed.
+         * Whether a screen magnification is performed is controlled by
+         * {@link #ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED}
+         *
+         * @hide
+         */
+        public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE =
+                "accessibility_display_magnification_auto_update";
+
+        /**
          * The timout for considering a press to be a long press in milliseconds.
          * @hide
          */
@@ -4806,6 +4846,9 @@
             PARENTAL_CONTROL_ENABLED,
             PARENTAL_CONTROL_REDIRECT_URL,
             USB_MASS_STORAGE_ENABLED,
+            ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
+            ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
+            ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE,
             ACCESSIBILITY_SCRIPT_INJECTION,
             BACKUP_AUTO_RESTORE,
             ENABLED_ACCESSIBILITY_SERVICES,
diff --git a/core/java/android/view/IDisplayContentChangeListener.aidl b/core/java/android/view/IDisplayContentChangeListener.aidl
new file mode 100644
index 0000000..8f23ff6
--- /dev/null
+++ b/core/java/android/view/IDisplayContentChangeListener.aidl
@@ -0,0 +1,32 @@
+/*
+** 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 onRotationChanged(int rotation);
+}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 7f2de50..b9f3942 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -26,6 +26,7 @@
 import android.graphics.Rect;
 import android.os.IRemoteCallback;
 import android.view.IApplicationToken;
+import android.view.IDisplayContentChangeListener;
 import android.view.IOnKeyguardExitResult;
 import android.view.IRotationWatcher;
 import android.view.IWindowSession;
@@ -34,7 +35,8 @@
 import android.view.MotionEvent;
 import android.view.InputChannel;
 import android.view.InputDevice;
-import  android.view.IInputFilter;
+import android.view.IInputFilter;
+import android.view.WindowInfo;
 
 /**
  * System private interface to the window manager.
@@ -214,11 +216,6 @@
     IBinder getFocusedWindowToken();
 
     /**
-     * Gets the frame on the screen of the window given its token.
-     */
-    boolean getWindowFrame(IBinder token, out Rect outBounds);
-
-    /**
      * Gets the compatibility scale of e window given its token.
      */
     float getWindowCompatibilityScale(IBinder windowToken);
@@ -227,4 +224,29 @@
      * Sets an input filter for manipulating the input event stream.
      */
     void setInputFilter(in IInputFilter filter);
+
+    /**
+     * Sets the scale and offset for implementing accessibility magnification.
+     */
+    void magnifyDisplay(int dipslayId, float scale, float offsetX, float offsetY);
+
+    /**
+     * Adds a listener for display content changes.
+     */
+    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);
 }
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index c5d9255..ff9dcce 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -180,4 +180,9 @@
 
     void setUniverseTransform(IBinder window, float alpha, float offx, float offy,
             float dsdx, float dtdx, float dsdy, float dtdy);
+
+    /**
+     * Notifies that a rectangle on the screen has been requested.
+     */
+    void onRectangleOnScreenRequested(IBinder token, in Rect rectangle, boolean immediate);
 }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index fc5629e..db6a77e 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -6259,6 +6259,11 @@
             if (viewRootImpl != null) {
                 viewRootImpl.setAccessibilityFocus(this, null);
             }
+            if (mAttachInfo != null) {
+                Rect rectangle = mAttachInfo.mTmpInvalRect;
+                getDrawingRect(rectangle);
+                requestRectangleOnScreen(rectangle);
+            }
             invalidate();
             sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
             notifyAccessibilityStateChanged();
@@ -6805,7 +6810,7 @@
      * @hide
      */
     public CharSequence getIterableTextForAccessibility() {
-        return mContentDescription;
+        return getContentDescription();
     }
 
     /**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index ffd495e..a4c0235 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -4649,9 +4649,19 @@
         // ViewAncestor never intercepts touch event, so this can be a no-op
     }
 
-    public boolean requestChildRectangleOnScreen(View child, Rect rectangle,
-            boolean immediate) {
-        return scrollToRectOrFocus(rectangle, immediate);
+    public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
+        final boolean scrolled = scrollToRectOrFocus(rectangle, immediate);
+        if (rectangle != null) {
+            mTempRect.set(rectangle);
+            mTempRect.offset(0, -mCurScrollY);
+            mTempRect.offset(mAttachInfo.mWindowLeft, mAttachInfo.mWindowTop);
+            try {
+                mWindowSession.onRectangleOnScreenRequested(mWindow, mTempRect, immediate);
+            } catch (RemoteException re) {
+                /* ignore */
+            }
+        }
+        return scrolled;
     }
 
     public void childHasTransientStateChanged(View child, boolean hasTransientState) {
diff --git a/core/java/android/view/WindowInfo.aidl b/core/java/android/view/WindowInfo.aidl
new file mode 100644
index 0000000..23e927a
--- /dev/null
+++ b/core/java/android/view/WindowInfo.aidl
@@ -0,0 +1,20 @@
+/*
+**
+** 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;
+
+parcelable WindowInfo;
diff --git a/core/java/android/view/WindowInfo.java b/core/java/android/view/WindowInfo.java
new file mode 100644
index 0000000..00875ae
--- /dev/null
+++ b/core/java/android/view/WindowInfo.java
@@ -0,0 +1,153 @@
+/*
+ * 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;
+
+    public float compatibilityScale;
+
+    public boolean visible;
+
+    public int displayId;
+
+    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);
+        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();
+    }
+
+    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.displayId = other.displayId;
+        info.compatibilityScale = other.compatibilityScale;
+        info.visible = other.visible;
+        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;
+        displayId = UNDEFINED;
+        visible = false;
+    }
+
+    /**
+     * @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/WindowManager.java b/core/java/android/view/WindowManager.java
index 7412f39..fa2d4e8 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -205,7 +205,8 @@
             @ViewDebug.IntToString(from = TYPE_HIDDEN_NAV_CONSUMER, to = "TYPE_HIDDEN_NAV_CONSUMER"),
             @ViewDebug.IntToString(from = TYPE_DREAM, to = "TYPE_DREAM"),
             @ViewDebug.IntToString(from = TYPE_NAVIGATION_BAR_PANEL, to = "TYPE_NAVIGATION_BAR_PANEL"),
-            @ViewDebug.IntToString(from = TYPE_DISPLAY_OVERLAY, to = "TYPE_DISPLAY_OVERLAY")
+            @ViewDebug.IntToString(from = TYPE_DISPLAY_OVERLAY, to = "TYPE_DISPLAY_OVERLAY"),
+            @ViewDebug.IntToString(from = TYPE_MAGNIFICATION_OVERLAY, to = "TYPE_MAGNIFICATION_OVERLAY")
         })
         public int type;
     
@@ -467,6 +468,13 @@
         public static final int TYPE_DISPLAY_OVERLAY = FIRST_SYSTEM_WINDOW+26;
 
         /**
+         * Window type: Magnification overlay window. Used to highlight the magnified
+         * portion of a display when accessibility magnification is enabled.
+         * @hide
+         */
+        public static final int TYPE_MAGNIFICATION_OVERLAY = FIRST_SYSTEM_WINDOW+27;
+
+        /**
          * End of types of system windows.
          */
         public static final int LAST_SYSTEM_WINDOW      = 2999;
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index b059afc..75554da 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -1106,6 +1106,14 @@
     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 canMagnifyWindow(WindowManager.LayoutParams attrs);
+
+    /**
      * Print the WindowManagerPolicy's state into the given stream.
      *
      * @param prefix Text to print at the front of each line.
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index c9688c8..74aea76 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -8355,7 +8355,7 @@
      */
     @Override
     public CharSequence getIterableTextForAccessibility() {
-        if (getContentDescription() == null) {
+        if (!TextUtils.isEmpty(mText)) {
             if (!(mText instanceof Spannable)) {
                 setText(mText, BufferType.SPANNABLE);
             }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f5f1109..23a412f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1285,6 +1285,12 @@
         android:description="@string/permdesc_retrieve_window_info"
         android:protectionLevel="signature" />
 
+    <!-- @hide Allows an application to magnify the content of a display. -->
+    <permission android:name="android.permission.MAGNIFY_DISPLAY"
+        android:label="@string/permlab_magnify_display"
+        android:description="@string/permdesc_magnify_display"
+        android:protectionLevel="signature" />
+
     <!-- Allows an application to watch and control how activities are
          started globally in the system.  Only for is in debugging
          (usually the monkey command). -->
diff --git a/core/res/res/drawable-nodpi/magnified_region_frame.9.png b/core/res/res/drawable-nodpi/magnified_region_frame.9.png
new file mode 100644
index 0000000..4cadefb
--- /dev/null
+++ b/core/res/res/drawable-nodpi/magnified_region_frame.9.png
Binary files differ
diff --git a/core/res/res/layout-sw600dp-port/keyguard_host_view.xml b/core/res/res/layout-sw600dp-port/keyguard_host_view.xml
index 50636f1..082f481 100644
--- a/core/res/res/layout-sw600dp-port/keyguard_host_view.xml
+++ b/core/res/res/layout-sw600dp-port/keyguard_host_view.xml
@@ -32,7 +32,7 @@
         android:id="@+id/app_widget_container"
         android:layout_width="match_parent"
         android:layout_height="0dip"
-        android:layout_weight="1"
+        android:layout_weight="0.4"
         android:visibility="gone">
 
         <!-- TODO: Remove this once supported as a widget -->
@@ -44,7 +44,7 @@
         android:id="@+id/view_flipper"
         android:layout_width="@dimen/kg_security_view_width"
         android:layout_height="0dip"
-        android:layout_weight="1"
+        android:layout_weight="0.6"
         android:layout_gravity="center">
 
         <include layout="@layout/keyguard_selector_view"/>
diff --git a/core/res/res/values-sw600dp/dimens.xml b/core/res/res/values-sw600dp/dimens.xml
index 8937c2a..4e202ac 100644
--- a/core/res/res/values-sw600dp/dimens.xml
+++ b/core/res/res/values-sw600dp/dimens.xml
@@ -45,6 +45,9 @@
     <!-- Size of lockscreen outerring on unsecure unlock LockScreen -->
     <dimen name="keyguard_lockscreen_outerring_diameter">364dp</dimen>
 
+    <!-- Height of FaceUnlock view in keyguard -->
+    <dimen name="face_unlock_height">430dip</dimen>
+
     <!-- target placement radius for GlowPadView. Should be 1/2 of outerring diameter. -->
     <dimen name="glowpadview_target_placement_radius">182dip</dimen>
 
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 48c52cc..3be3b0b 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -193,7 +193,7 @@
     <!-- Padding on left margin of PIN text entry field to center it when del button is showing -->
     <dimen name="keyguard_lockscreen_pin_margin_left">40dip</dimen>
 
-    <!-- Height of FaceUnlock widget in keyguard -->
+    <!-- Height of FaceUnlock view in keyguard -->
     <dimen name="face_unlock_height">330dip</dimen>
 
     <!-- Minimum popup width for selecting an activity in ActivityChooserDialog/ActivityChooserView. -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 2e639a1..4db8cd1 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1261,6 +1261,7 @@
   <java-symbol type="drawable" name="jog_tab_right_sound_on" />
   <java-symbol type="drawable" name="jog_tab_target_green" />
   <java-symbol type="drawable" name="jog_tab_target_yellow" />
+  <java-symbol type="drawable" name="magnified_region_frame" />
   <java-symbol type="drawable" name="menu_background" />
   <java-symbol type="drawable" name="stat_sys_secure" />
   <java-symbol type="drawable" name="kg_widget_overscroll_layer_left" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d2951bf..9f254b6 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -669,8 +669,15 @@
     <string name="permlab_filter_events">filter events</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_filter_events">Allows an application to register an input filter
-        which filters the stream of all user events before they are dispatched. Malicious app
-        may control the system UI whtout user intervention.</string>
+            which filters the stream of all user events before they are dispatched. Malicious app
+            may control the system UI whtout user intervention.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_magnify_display">magnify display</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_magnify_display">Allows an application to magnify the content of a
+        display. Malicious apps may transform the display content in a way that renders the
+        device unusable.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_shutdown">partial shutdown</string>
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 565d2f86..92261da 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -129,6 +129,15 @@
     <!-- Default for Settings.Secure.TOUCH_EXPLORATION_ENABLED -->
     <bool name="def_touch_exploration_enabled">false</bool>
 
+    <!-- Default value for Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE -->
+    <fraction name="def_accessibility_display_magnification_scale">200%</fraction>
+
+    <!-- Default value for Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED -->
+    <bool name="def_accessibility_display_magnification_enabled">false</bool>
+
+    <!-- Default value for Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE -->
+    <bool name="def_accessibility_display_magnification_auto_update">true</bool>
+
     <!-- Default for Settings.System.USER_ROTATION -->
     <integer name="def_user_rotation">0</integer>
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index 1be363f..0917b24 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -67,7 +67,7 @@
     // database gets upgraded properly. At a minimum, please confirm that 'upgradeVersion'
     // is properly propagated through your change.  Not doing so will result in a loss of user
     // settings.
-    private static final int DATABASE_VERSION = 83;
+    private static final int DATABASE_VERSION = 84;
 
     private Context mContext;
     private int mUserHandle;
@@ -1175,14 +1175,6 @@
                 loadStringSetting(stmt, Settings.Secure.SCREENSAVER_COMPONENTS,
                         R.string.def_screensaver_component);
 
-                // Migrate now-global settings. Note that this happens before
-                // new users can be created.
-                createGlobalTable(db);
-                String[] settingsToMove = (String[]) SettingsProvider.sSystemGlobalKeys.toArray();
-                moveSettingsToNewTable(db, TABLE_SYSTEM, TABLE_GLOBAL, settingsToMove);
-                settingsToMove = (String[]) SettingsProvider.sSecureGlobalKeys.toArray();
-                moveSettingsToNewTable(db, TABLE_SECURE, TABLE_GLOBAL, settingsToMove);
-
                 db.setTransactionSuccessful();
             } finally {
                 db.endTransaction();
@@ -1216,9 +1208,9 @@
                 // Migrate now-global settings. Note that this happens before
                 // new users can be created.
                 createGlobalTable(db);
-                String[] settingsToMove = (String[]) SettingsProvider.sSystemGlobalKeys.toArray();
+                String[] settingsToMove = hashsetToStringArray(SettingsProvider.sSystemGlobalKeys);
                 moveSettingsToNewTable(db, TABLE_SYSTEM, TABLE_GLOBAL, settingsToMove);
-                settingsToMove = (String[]) SettingsProvider.sSecureGlobalKeys.toArray();
+                settingsToMove = hashsetToStringArray(SettingsProvider.sSecureGlobalKeys);
                 moveSettingsToNewTable(db, TABLE_SECURE, TABLE_GLOBAL, settingsToMove);
 
                 db.setTransactionSuccessful();
@@ -1229,6 +1221,33 @@
             upgradeVersion = 83;
         }
 
+        if (upgradeVersion == 83) {
+            // 1. Setting whether screen magnification is enabled.
+            // 2. Setting for screen magnification scale.
+            // 3. Setting for screen magnification auto update.
+            db.beginTransaction();
+            SQLiteStatement stmt = null;
+            try {
+                stmt = db.compileStatement("INSERT INTO secure(name,value) VALUES(?,?);");
+                loadBooleanSetting(stmt,
+                        Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
+                        R.bool.def_accessibility_display_magnification_enabled);
+                stmt.close();
+                stmt = db.compileStatement("INSERT INTO secure(name,value) VALUES(?,?);");
+                loadFractionSetting(stmt, Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
+                        R.fraction.def_accessibility_display_magnification_scale, 1);
+                stmt.close();
+                stmt = db.compileStatement("INSERT INTO secure(name,value) VALUES(?,?);");
+                loadBooleanSetting(stmt,
+                        Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE,
+                        R.bool.def_accessibility_display_magnification_auto_update);
+            } finally {
+                db.endTransaction();
+                if (stmt != null) stmt.close();
+            }
+            upgradeVersion = 84;
+        }
+
         // *** Remember to update DATABASE_VERSION above!
 
         if (upgradeVersion != currentVersion) {
@@ -1256,6 +1275,11 @@
         }
     }
 
+    private String[] hashsetToStringArray(HashSet<String> set) {
+        String[] array = new String[set.size()];
+        return set.toArray(array);
+    }
+
     private void moveSettingsToNewTable(SQLiteDatabase db,
             String sourceTable, String destTable,
             String[] settingsToMove) {
@@ -1776,6 +1800,16 @@
                     R.string.def_screensaver_component);
             loadStringSetting(stmt, Settings.Secure.SCREENSAVER_DEFAULT_COMPONENT,
                     R.string.def_screensaver_component);
+
+            loadBooleanSetting(stmt, Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
+                    R.bool.def_accessibility_display_magnification_enabled);
+
+            loadFractionSetting(stmt, Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
+                    R.fraction.def_accessibility_display_magnification_scale, 1);
+
+            loadBooleanSetting(stmt,
+                    Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE,
+                    R.bool.def_accessibility_display_magnification_auto_update);
         } finally {
             if (stmt != null) stmt.close();
         }
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 116492d..54cf73a 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -71,6 +71,7 @@
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseIntArray;
 import android.view.Display;
 import android.view.Gravity;
 import android.view.HapticFeedbackConstants;
@@ -114,6 +115,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_HIDDEN_NAV_CONSUMER;
 import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_PHONE;
 import static android.view.WindowManager.LayoutParams.TYPE_PRIORITY_PHONE;
 import static android.view.WindowManager.LayoutParams.TYPE_SEARCH_BAR;
@@ -219,16 +221,18 @@
     static final int NAVIGATION_BAR_PANEL_LAYER = 20;
     // system-level error dialogs
     static final int SYSTEM_ERROR_LAYER = 21;
+    // used to highlight the magnified portion of a display
+    static final int MAGNIFICATION_OVERLAY_LAYER = 22;
     // used to simulate secondary display devices
-    static final int DISPLAY_OVERLAY_LAYER = 22;
+    static final int DISPLAY_OVERLAY_LAYER = 23;
     // the drag layer: input for drag-and-drop is associated with this window,
     // which sits above all other focusable windows
-    static final int DRAG_LAYER = 23;
-    static final int SECURE_SYSTEM_OVERLAY_LAYER = 24;
-    static final int BOOT_PROGRESS_LAYER = 25;
+    static final int DRAG_LAYER = 24;
+    static final int SECURE_SYSTEM_OVERLAY_LAYER = 25;
+    static final int BOOT_PROGRESS_LAYER = 26;
     // the (mouse) pointer layer
-    static final int POINTER_LAYER = 26;
-    static final int HIDDEN_NAV_CONSUMER_LAYER = 27;
+    static final int POINTER_LAYER = 27;
+    static final int HIDDEN_NAV_CONSUMER_LAYER = 28;
 
     static final int APPLICATION_MEDIA_SUBLAYER = -2;
     static final int APPLICATION_MEDIA_OVERLAY_SUBLAYER = -1;
@@ -1332,6 +1336,8 @@
             return UNIVERSE_BACKGROUND_LAYER;
         case TYPE_DISPLAY_OVERLAY:
             return DISPLAY_OVERLAY_LAYER;
+        case TYPE_MAGNIFICATION_OVERLAY:
+            return MAGNIFICATION_OVERLAY_LAYER;
         }
         Log.e(TAG, "Unknown window type: " + type);
         return APPLICATION_LAYER;
@@ -4310,6 +4316,18 @@
         mLastInputMethodTargetWindow = target;
     }
 
+    public boolean canMagnifyWindow(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;
+    }
+
     public void dump(String prefix, PrintWriter pw, String[] args) {
         pw.print(prefix); pw.print("mSafeMode="); pw.print(mSafeMode);
                 pw.print(" mSystemReady="); pw.print(mSystemReady);
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
index 7e148b3..00bc9be 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
@@ -61,12 +61,6 @@
     static final int APPWIDGET_HOST_ID = 0x4B455947;
     private static final String KEYGUARD_WIDGET_PREFS = "keyguard_widget_prefs";
 
-    // time after launching EmergencyDialer before the screen goes blank.
-    private static final int EMERGENCY_CALL_TIMEOUT = 10000;
-
-    // intent action for launching emergency dialer activity.
-    static final String ACTION_EMERGENCY_DIAL = "com.android.phone.EmergencyDialer.DIAL";
-
     private static final String TAG = "KeyguardViewHost";
 
     private static final int SECURITY_SELECTOR_ID = R.id.keyguard_selector_view;
@@ -80,7 +74,6 @@
     private AppWidgetHost mAppWidgetHost;
     private KeyguardWidgetPager mAppWidgetContainer;
     private ViewFlipper mViewFlipper;
-    private Button mEmergencyDialerButton;
     private boolean mEnableMenuKey;
     private boolean mIsVerifyUnlockOnly;
     private boolean mEnableFallback; // TODO: This should get the value from KeyguardPatternView
@@ -146,14 +139,6 @@
                 Log.v("*********", "Can't find view id " + mViewIds[i]);
             }
         }
-
-        // Enable emergency dialer button
-        mEmergencyDialerButton = (Button) findViewById(R.id.emergency_call_button);
-        mEmergencyDialerButton.setOnClickListener(new OnClickListener() {
-            public void onClick(View v) {
-                takeEmergencyCallAction();
-            }
-        });
     }
 
     void setLockPatternUtils(LockPatternUtils utils) {
@@ -229,22 +214,6 @@
 
     };
 
-    /**
-     * Shows the emergency dialer or returns the user to the existing call.
-     */
-    public void takeEmergencyCallAction() {
-        mCallback.userActivity(EMERGENCY_CALL_TIMEOUT);
-        if (TelephonyManager.getDefault().getCallState()
-                == TelephonyManager.CALL_STATE_OFFHOOK) {
-            mLockPatternUtils.resumeCall();
-        } else {
-            Intent intent = new Intent(ACTION_EMERGENCY_DIAL);
-            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                    | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-            getContext().startActivity(intent);
-        }
-    }
-
     private void showDialog(String title, String message) {
         final AlertDialog dialog = new AlertDialog.Builder(mContext)
             .setTitle(title)
@@ -530,12 +499,14 @@
     public void onScreenTurnedOn() {
         if (DEBUG) Log.d(TAG, "screen on");
         showSecurityScreen(mCurrentSecurityId);
+        getSecurityView(mCurrentSecurityId).onResume();
     }
 
     @Override
     public void onScreenTurnedOff() {
         if (DEBUG) Log.d(TAG, "screen off");
         showSecurityScreen(SECURITY_SELECTOR_ID);
+        getSecurityView(mCurrentSecurityId).onPause();
     }
 
     @Override
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityModel.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityModel.java
index a19b35d..75c4a7c 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityModel.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityModel.java
@@ -69,7 +69,8 @@
         SecurityMode mode = SecurityMode.None;
         if (simState == IccCardConstants.State.PIN_REQUIRED) {
             mode = SecurityMode.SimPin;
-        } else if (simState == IccCardConstants.State.PUK_REQUIRED) {
+        } else if (simState == IccCardConstants.State.PUK_REQUIRED
+                && mLockPatternUtils.isPukUnlockScreenEnable()) {
             mode = SecurityMode.SimPuk;
         } else {
             final int security = mLockPatternUtils.getKeyguardStoredPasswordQuality();
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSelectorView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSelectorView.java
index 28f5e8c..95772af 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSelectorView.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSelectorView.java
@@ -25,6 +25,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.MediaStore;
+import android.telephony.TelephonyManager;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Slog;
@@ -33,6 +34,7 @@
 import android.widget.LinearLayout;
 
 import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.telephony.IccCardConstants.State;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.multiwaveview.GlowPadView;
 import com.android.internal.widget.multiwaveview.GlowPadView.OnTriggerListener;
@@ -43,6 +45,9 @@
     private static final String TAG = "SecuritySelectorView";
     private static final String ASSIST_ICON_METADATA_NAME =
         "com.android.systemui.action_assist_icon";
+    private static final int EMERGENCY_CALL_TIMEOUT = 10000; // screen timeout after starting e.d.
+    static final String ACTION_EMERGENCY_DIAL = "com.android.phone.EmergencyDialer.DIAL";
+
     private KeyguardSecurityCallback mCallback;
     private GlowPadView mGlowPadView;
     private Button mEmergencyCallButton;
@@ -98,9 +103,19 @@
 
     };
 
-    KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
+    private void updateEmergencyCallButton(State simState, int phoneState) {
+        if (mEmergencyCallButton != null) {
+            boolean en = mLockPatternUtils.isEmergencyCallCapable()
+                || (phoneState == TelephonyManager.CALL_STATE_OFFHOOK); // voice call in progress
+            if (en && KeyguardUpdateMonitor.isSimLocked(simState)) {
+                // Some countries can't handle emergency calls while SIM is locked.
+                en = mLockPatternUtils.isEmergencyCallEnabledWhileSimLocked();
+            }
+            mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton, phoneState, en);
+        }
+    }
 
-        private boolean mEmergencyDialerDisableBecauseSimLocked;
+    KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
 
         @Override
         public void onDevicePolicyManagerStateChanged() {
@@ -108,19 +123,15 @@
         }
 
         @Override
-        public void onSimStateChanged(IccCardConstants.State simState) {
-            // Some carriers aren't capable of handling emergency calls while the SIM is locked
-            mEmergencyDialerDisableBecauseSimLocked = KeyguardUpdateMonitor.isSimLocked(simState)
-                    && !mLockPatternUtils.isEmergencyCallEnabledWhileSimLocked();
+        public void onSimStateChanged(State simState) {
+            int phoneState = KeyguardUpdateMonitor.getInstance(mContext).getPhoneState();
+            updateEmergencyCallButton(simState, phoneState);
             updateTargets();
         }
 
         void onPhoneStateChanged(int phoneState) {
-            if (mEmergencyCallButton != null) {
-                mLockPatternUtils.isEmergencyCallEnabledWhileSimLocked();
-                mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton,
-                        phoneState, !mEmergencyDialerDisableBecauseSimLocked);
-            }
+            State simState = KeyguardUpdateMonitor.getInstance(mContext).getSimState();
+            updateEmergencyCallButton(simState, phoneState);
         };
     };
 
@@ -149,9 +160,30 @@
         mGlowPadView = (GlowPadView) findViewById(R.id.glow_pad_view);
         mGlowPadView.setOnTriggerListener(mOnTriggerListener);
         mEmergencyCallButton = (Button) findViewById(R.id.emergency_call_button);
+        mEmergencyCallButton.setOnClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                takeEmergencyCallAction();
+            }
+        });
         updateTargets();
     }
 
+    /**
+     * Shows the emergency dialer or returns the user to the existing call.
+     */
+    public void takeEmergencyCallAction() {
+        mCallback.userActivity(EMERGENCY_CALL_TIMEOUT);
+        if (TelephonyManager.getDefault().getCallState()
+                == TelephonyManager.CALL_STATE_OFFHOOK) {
+            mLockPatternUtils.resumeCall();
+        } else {
+            Intent intent = new Intent(ACTION_EMERGENCY_DIAL);
+            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                    | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+            getContext().startActivity(intent);
+        }
+    }
+
     public boolean isTargetPresent(int resId) {
         return mGlowPadView.getTargetPosition(resId) != -1;
     }
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusView.java
index 481e9ad..5704425 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusView.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusView.java
@@ -21,6 +21,7 @@
 import android.widget.GridLayout;
 
 public class KeyguardStatusView extends GridLayout {
+    @SuppressWarnings("unused")
     private KeyguardStatusViewManager mStatusViewManager;
 
     public KeyguardStatusView(Context context) {
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusViewManager.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusViewManager.java
index 20fad0b..e325f94 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusViewManager.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusViewManager.java
@@ -32,11 +32,10 @@
 import android.text.format.DateFormat;
 import android.util.Log;
 import android.view.View;
-import android.widget.Button;
 import android.widget.TextView;
 
 /***
- * Manages a number of views inside of LockScreen layouts. See below for a list of widgets
+ * Manages a number of views inside of the given layout. See below for a list of widgets.
  */
 class KeyguardStatusViewManager {
     private static final boolean DEBUG = false;
@@ -92,21 +91,12 @@
     private boolean mShowingStatus;
     private CharSequence mPlmn;
     private CharSequence mSpn;
-    protected int mPhoneState;
     private DigitalClock mDigitalClock;
     protected boolean mBatteryCharged;
     protected boolean mBatteryIsLow;
-    private boolean mEmergencyButtonEnabledBecauseSimLocked;
-    private Button mEmergencyCallButton;
-    private boolean mEmergencyCallButtonEnabledInScreen;
 
     /**
-     *
      * @param view the containing view of all widgets
-     * @param updateMonitor the update monitor to use
-     * @param lockPatternUtils lock pattern util object
-     * @param callback used to invoke emergency dialer
-     * @param emergencyButtonEnabledInScreen whether emergency button is enabled by default
      */
     public KeyguardStatusViewManager(View view) {
         if (DEBUG) Log.v(TAG, "KeyguardStatusViewManager()");
@@ -364,7 +354,6 @@
 
         CharSequence carrierText = null;
         int carrierHelpTextId = 0;
-        mEmergencyButtonEnabledBecauseSimLocked = false;
         mStatus = getStatusForIccState(simState);
         mSimState = simState;
         switch (mStatus) {
@@ -394,7 +383,6 @@
                 carrierText = getContext().getText(
                         R.string.lockscreen_permanent_disabled_sim_message_short);
                 carrierHelpTextId = R.string.lockscreen_permanent_disabled_sim_instructions;
-                mEmergencyButtonEnabledBecauseSimLocked = true;
                 break;
 
             case SimMissingLocked:
@@ -402,33 +390,25 @@
                         getContext().getText(R.string.lockscreen_missing_sim_message_short),
                         mPlmn);
                 carrierHelpTextId = R.string.lockscreen_missing_sim_instructions;
-                mEmergencyButtonEnabledBecauseSimLocked = true;
                 break;
 
             case SimLocked:
                 carrierText = makeCarrierStringOnEmergencyCapable(
                         getContext().getText(R.string.lockscreen_sim_locked_message),
                         mPlmn);
-                mEmergencyButtonEnabledBecauseSimLocked = true;
                 break;
 
             case SimPukLocked:
                 carrierText = makeCarrierStringOnEmergencyCapable(
                         getContext().getText(R.string.lockscreen_sim_puk_locked_message),
                         mPlmn);
-                if (!mLockPatternUtils.isPukUnlockScreenEnable()) {
-                    // This means we're showing the PUK unlock screen
-                    mEmergencyButtonEnabledBecauseSimLocked = true;
-                }
                 break;
         }
 
         setCarrierText(carrierText);
         setCarrierHelpText(carrierHelpTextId);
-        updateEmergencyCallButtonState(mPhoneState);
     }
 
-
     /*
      * Add emergencyCallMessage to carrier string only if phone supports emergency calls.
      */
@@ -500,17 +480,6 @@
         }
     }
 
-    private void updateEmergencyCallButtonState(int phoneState) {
-        if (mEmergencyCallButton != null) {
-            boolean enabledBecauseSimLocked =
-                    mLockPatternUtils.isEmergencyCallEnabledWhileSimLocked()
-                    && mEmergencyButtonEnabledBecauseSimLocked;
-            boolean shown = mEmergencyCallButtonEnabledInScreen || enabledBecauseSimLocked;
-            mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton,
-                    phoneState, shown);
-        }
-    }
-
     private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
 
         @Override
@@ -537,12 +506,6 @@
         }
 
         @Override
-        public void onPhoneStateChanged(int phoneState) {
-            mPhoneState = phoneState;
-            updateEmergencyCallButtonState(phoneState);
-        }
-
-        @Override
         public void onSimStateChanged(IccCardConstants.State simState) {
             updateCarrierStateWithSimStatus(simState);
         }
diff --git a/services/java/com/android/server/EventLogTags.logtags b/services/java/com/android/server/EventLogTags.logtags
index dd50beb..39355d5 100644
--- a/services/java/com/android/server/EventLogTags.logtags
+++ b/services/java/com/android/server/EventLogTags.logtags
@@ -148,3 +148,11 @@
 # ---------------------------
 51100 netstats_mobile_sample (dev_rx_bytes|2|2),(dev_tx_bytes|2|2),(dev_rx_pkts|2|1),(dev_tx_pkts|2|1),(xt_rx_bytes|2|2),(xt_tx_bytes|2|2),(xt_rx_pkts|2|1),(xt_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1),(trusted_time|2|3)
 51101 netstats_wifi_sample (dev_rx_bytes|2|2),(dev_tx_bytes|2|2),(dev_rx_pkts|2|1),(dev_tx_pkts|2|1),(xt_rx_bytes|2|2),(xt_tx_bytes|2|2),(xt_rx_pkts|2|1),(xt_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1),(trusted_time|2|3)
+
+
+# ---------------------------
+# LockdownVpnTracker.java
+# ---------------------------
+51200 lockdown_vpn_connecting (egress_net|1)
+51201 lockdown_vpn_connected (egress_net|1)
+51202 lockdown_vpn_error (egress_net|1)
diff --git a/services/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java
index fc774d4..f1a03de 100644
--- a/services/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -26,57 +26,49 @@
 import android.view.WindowManagerPolicy;
 import android.view.accessibility.AccessibilityEvent;
 
-/**
- * Input filter for accessibility.
- *
- * Currently just a stub but will eventually implement touch exploration, etc.
- */
-public class AccessibilityInputFilter extends InputFilter {
-    private static final String TAG = "AccessibilityInputFilter";
+class AccessibilityInputFilter extends InputFilter implements EventStreamTransformation {
+
+    private static final String TAG = AccessibilityInputFilter.class.getSimpleName();
+
     private static final boolean DEBUG = false;
 
+    private static final int UNDEFINED_DEVICE_ID = -1;
+
+    /**
+     * Flag for enabling the screen magnification feature.
+     *
+     * @see #setEnabledFeatures(int)
+     */
+    static final int FLAG_FEATURE_SCREEN_MAGNIFIER = 0x00000001;
+
+    /**
+     * Flag for enabling the touch exploration feature.
+     *
+     * @see #setEnabledFeatures(int)
+     */
+    static final int FLAG_FEATURE_TOUCH_EXPLORATION = 0x00000002;
+
     private final Context mContext;
 
     private final PowerManager mPm;
 
     private final AccessibilityManagerService mAms;
 
-    /**
-     * This is an interface for explorers that take a {@link MotionEvent}
-     * stream and perform touch exploration of the screen content.
-     */
-    public interface Explorer {
-        /**
-         * Handles a {@link MotionEvent}.
-         *
-         * @param event The event to handle.
-         * @param policyFlags The policy flags associated with the event.
-         */
-        public void onMotionEvent(MotionEvent event, int policyFlags);
+    private int mCurrentDeviceId;
 
-        /**
-         * Requests that the explorer clears its internal state.
-         *
-         * @param event The last received event.
-         * @param policyFlags The policy flags associated with the event.
-         */
-        public void clear(MotionEvent event, int policyFlags);
+    private boolean mInstalled;
 
-        /**
-         * Requests that the explorer clears its internal state.
-         */
-        public void clear();
-    }
+    private int mEnabledFeatures;
 
     private TouchExplorer mTouchExplorer;
+    private ScreenMagnifier mScreenMagnifier;
+    private EventStreamTransformation mEventHandler;
 
-    private int mTouchscreenSourceDeviceId;
-
-    public AccessibilityInputFilter(Context context, AccessibilityManagerService service) {
+    AccessibilityInputFilter(Context context, AccessibilityManagerService service) {
         super(context.getMainLooper());
         mContext = context;
         mAms = service;
-        mPm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+        mPm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
     }
 
     @Override
@@ -84,7 +76,9 @@
         if (DEBUG) {
             Slog.d(TAG, "Accessibility input filter installed.");
         }
-        mTouchExplorer = new TouchExplorer(this, mContext, mAms);
+        mInstalled = true;
+        disableFeatures();
+        enableFeatures();
         super.onInstalled();
     }
 
@@ -93,7 +87,8 @@
         if (DEBUG) {
             Slog.d(TAG, "Accessibility input filter uninstalled.");
         }
-        mTouchExplorer.clear();
+        mInstalled = false;
+        disableFeatures();
         super.onUninstalled();
     }
 
@@ -103,27 +98,104 @@
             Slog.d(TAG, "Received event: " + event + ", policyFlags=0x" 
                     + Integer.toHexString(policyFlags));
         }
-        if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
-            MotionEvent motionEvent = (MotionEvent) event;
-            int deviceId = event.getDeviceId();
-            if (mTouchscreenSourceDeviceId != deviceId) {
-                mTouchscreenSourceDeviceId = deviceId;
-                mTouchExplorer.clear(motionEvent, policyFlags);
-            }
-            if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) != 0) {
-                mPm.userActivity(event.getEventTime(), false);
-                mTouchExplorer.onMotionEvent(motionEvent, policyFlags);
-            } else {
-                mTouchExplorer.clear(motionEvent, policyFlags);
-            }
-        } else {
+        if (mEventHandler == null) {
             super.onInputEvent(event, policyFlags);
+            return;
+        }
+        if (event.getSource() != InputDevice.SOURCE_TOUCHSCREEN) {
+            super.onInputEvent(event, policyFlags);
+            return;
+        }
+        if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0) {
+            mEventHandler.clear();
+            super.onInputEvent(event, policyFlags);
+            return;
+        }
+        final int deviceId = event.getDeviceId();
+        if (mCurrentDeviceId != deviceId) {
+            if (mCurrentDeviceId != UNDEFINED_DEVICE_ID) {
+                mEventHandler.clear();
+            }
+            mCurrentDeviceId = deviceId;
+        }
+        mPm.userActivity(event.getEventTime(), false);
+        MotionEvent motionEvent = (MotionEvent) event;
+        mEventHandler.onMotionEvent(motionEvent, policyFlags);
+    }
+
+    @Override
+    public void onMotionEvent(MotionEvent event, int policyFlags) {
+        sendInputEvent(event, policyFlags);
+    }
+
+    @Override
+    public void onAccessibilityEvent(AccessibilityEvent event) {
+        // TODO Implement this to inject the accessibility event
+        //      into the accessibility manager service similarly
+        //      to how this is done for input events.
+    }
+
+    @Override
+    public void setNext(EventStreamTransformation sink) {
+        /* do nothing */
+    }
+
+    @Override
+    public void clear() {
+        /* do nothing */
+    }
+
+    void setEnabledFeatures(int enabledFeatures) {
+        if (mEnabledFeatures == enabledFeatures) {
+            return;
+        }
+        if (mInstalled) {
+            disableFeatures();
+        }
+        mEnabledFeatures = enabledFeatures;
+        if (mInstalled) {
+            enableFeatures();
         }
     }
 
-    public void onAccessibilityEvent(AccessibilityEvent event) {
-        if (mTouchExplorer != null) {
-            mTouchExplorer.onAccessibilityEvent(event);
+    void notifyAccessibilityEvent(AccessibilityEvent event) {
+        if (mEventHandler != null) {
+            mEventHandler.onAccessibilityEvent(event);
         }
     }
+
+    private void enableFeatures() {
+        if ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0) {
+            mEventHandler = mScreenMagnifier = new ScreenMagnifier(mContext);
+            mEventHandler.setNext(this);
+        }
+        if ((mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) {
+            mTouchExplorer = new TouchExplorer(mContext, mAms);
+            mTouchExplorer.setNext(this);
+            if (mEventHandler != null) {
+                mEventHandler.setNext(mTouchExplorer);
+            } else {
+                mEventHandler = mTouchExplorer;
+            }
+        }
+    }
+
+    private void disableFeatures() {
+        if (mTouchExplorer != null) {
+            mTouchExplorer.clear();
+            mTouchExplorer.onDestroy();
+            mTouchExplorer = null;
+        }
+        if (mScreenMagnifier != null) {
+            mScreenMagnifier.clear();
+            mScreenMagnifier.onDestroy();
+            mScreenMagnifier = null;
+        }
+        mEventHandler = null;
+    }
+
+    @Override
+    public void onDestroy() {
+        /* ignore */
+    }
 }
diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
index 857334e..f6354bb 100644
--- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -48,6 +48,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -62,6 +63,7 @@
 import android.view.InputDevice;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
+import android.view.WindowInfo;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityInteractionClient;
@@ -115,6 +117,8 @@
 
     private static final int MSG_SEND_ACCESSIBILITY_EVENT_TO_INPUT_FILTER = 3;
 
+    private static final int MSG_SEND_UPDATE_INPUT_FILTER = 4;
+
     private static int sIdCounter = 0;
 
     private static int sNextWindowId;
@@ -157,11 +161,13 @@
 
     private boolean mIsTouchExplorationEnabled;
 
+    private boolean mIsScreenMagnificationEnabled;
+
     private final IWindowManager mWindowManager;
 
     private final SecurityPolicy mSecurityPolicy;
 
-    private final MainHanler mMainHandler;
+    private final MainHandler mMainHandler;
 
     private Service mUiAutomationService;
 
@@ -183,7 +189,7 @@
         mPackageManager = mContext.getPackageManager();
         mWindowManager = (IWindowManager) ServiceManager.getService(Context.WINDOW_SERVICE);
         mSecurityPolicy = new SecurityPolicy();
-        mMainHandler = new MainHanler();
+        mMainHandler = new MainHandler(mContext.getMainLooper());
         registerPackageChangeAndBootCompletedBroadcastReceiver();
         registerSettingsContentObservers();
     }
@@ -201,7 +207,7 @@
                 synchronized (mLock) {
                     // We will update when the automation service dies.
                     if (mUiAutomationService == null) {
-                        populateAccessibilityServiceListLocked();
+                        populateInstalledAccessibilityServiceLocked();
                         manageServicesLocked();
                     }
                 }
@@ -262,18 +268,11 @@
                     synchronized (mLock) {
                         // We will update when the automation service dies.
                         if (mUiAutomationService == null) {
-                            populateAccessibilityServiceListLocked();
-                            populateEnabledAccessibilityServicesLocked();
-                            populateTouchExplorationGrantedAccessibilityServicesLocked();
-                            handleAccessibilityEnabledSettingChangedLocked();
-                            handleTouchExplorationEnabledSettingChangedLocked();
-                            updateInputFilterLocked();
-                            sendStateToClientsLocked();
+                            updateInternalStateLocked();
                         }
                     }
                     return;
                 }
-
                 super.onReceive(context, intent);
             }
         };
@@ -329,6 +328,24 @@
                     }
                 });
 
+        Uri accessibilityScreenMagnificationEnabledUri = Settings.Secure.getUriFor(
+                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED);
+        contentResolver.registerContentObserver(accessibilityScreenMagnificationEnabledUri, false,
+            new ContentObserver(new Handler()) {
+                @Override
+                public void onChange(boolean selfChange) {
+                    super.onChange(selfChange);
+                    synchronized (mLock) {
+                        // We will update when the automation service dies.
+                        if (mUiAutomationService == null) {
+                            handleScreenMagnificationEnabledSettingChangedLocked();
+                            updateInputFilterLocked();
+                            sendStateToClientsLocked();
+                        }
+                    }
+                }
+            });
+
         Uri accessibilityServicesUri =
             Settings.Secure.getUriFor(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
         contentResolver.registerContentObserver(accessibilityServicesUri, false,
@@ -587,8 +604,11 @@
             final int windowId = mSecurityPolicy.mActiveWindowId;
             IBinder token = mWindowIdToWindowTokenMap.get(windowId);
             try {
-                mWindowManager.getWindowFrame(token, outBounds);
-                return true;
+                WindowInfo info = mWindowManager.getWindowInfo(token);
+                if (info != null) {
+                    outBounds.set(info.frame);
+                    return true;
+                }
             } catch (RemoteException re) {
                 /* ignore */
             }
@@ -652,7 +672,7 @@
     /**
      * Populates the cached list of installed {@link AccessibilityService}s.
      */
-    private void populateAccessibilityServiceListLocked() {
+    private void populateInstalledAccessibilityServiceLocked() {
         mInstalledServices.clear();
 
         List<ResolveInfo> installedServices = mPackageManager.queryIntentServices(
@@ -962,31 +982,26 @@
     }
 
     /**
-     * Updates the touch exploration state.
+     * Updates the state of the input filter.
      */
     private void updateInputFilterLocked() {
-        if (mIsAccessibilityEnabled && mIsTouchExplorationEnabled) {
-            if (!mHasInputFilter) {
-                mHasInputFilter = true;
-                if (mInputFilter == null) {
-                    mInputFilter = new AccessibilityInputFilter(mContext, this);
-                }
-                try {
-                    mWindowManager.setInputFilter(mInputFilter);
-                } catch (RemoteException re) {
-                    /* ignore */
-                }
-            }
-            return;
-        }
-        if (mHasInputFilter) {
-            mHasInputFilter = false;
-            try {
-                mWindowManager.setInputFilter(null);
-            } catch (RemoteException re) {
-                /* ignore */
-            }
-        }
+         mMainHandler.obtainMessage(MSG_SEND_UPDATE_INPUT_FILTER).sendToTarget();
+    }
+
+    /**
+     * Updated the internal state of this service to match the current settings.
+     */
+    private void updateInternalStateLocked() {
+        populateInstalledAccessibilityServiceLocked();
+        populateEnabledAccessibilityServicesLocked();
+        populateTouchExplorationGrantedAccessibilityServicesLocked();
+
+        handleTouchExplorationEnabledSettingChangedLocked();
+        handleScreenMagnificationEnabledSettingChangedLocked();
+        handleAccessibilityEnabledSettingChangedLocked();
+
+        updateInputFilterLocked();
+        sendStateToClientsLocked();
     }
 
     /**
@@ -1012,6 +1027,15 @@
                 Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0) == 1;
     }
 
+    /**
+     * Updates the state based on the screen magnification enabled setting.
+     */
+    private void handleScreenMagnificationEnabledSettingChangedLocked() {
+        mIsScreenMagnificationEnabled = Settings.Secure.getInt(
+                mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, 0) == 1;
+    }
+
     private void handleTouchExplorationGrantedAccessibilityServicesChangedLocked() {
         final int serviceCount = mServices.size();
         for (int i = 0; i < serviceCount; i++) {
@@ -1083,7 +1107,12 @@
         }
     }
 
-    private class MainHanler extends Handler {
+    private class MainHandler extends Handler {
+
+        public MainHandler(Looper looper) {
+            super(looper);
+        }
+
         @Override
         public void handleMessage(Message msg) {
             final int type = msg.what;
@@ -1140,10 +1169,50 @@
                 case MSG_SEND_ACCESSIBILITY_EVENT_TO_INPUT_FILTER: {
                     AccessibilityEvent event = (AccessibilityEvent) msg.obj;
                     if (mHasInputFilter && mInputFilter != null) {
-                        mInputFilter.onAccessibilityEvent(event);
+                        mInputFilter.notifyAccessibilityEvent(event);
                     }
                     event.recycle();
                 } break;
+                case MSG_SEND_UPDATE_INPUT_FILTER: {
+                    boolean setInputFilter = false;
+                    AccessibilityInputFilter inputFilter = null;
+                    synchronized (mLock) {
+                        if ((mIsAccessibilityEnabled && mIsTouchExplorationEnabled)
+                                || mIsScreenMagnificationEnabled) {
+                            if (!mHasInputFilter) {
+                                mHasInputFilter = true;
+                                if (mInputFilter == null) {
+                                    mInputFilter = new AccessibilityInputFilter(mContext,
+                                            AccessibilityManagerService.this);
+                                }
+                                inputFilter = mInputFilter;
+                                setInputFilter = true;
+                            }
+                            int flags = 0;
+                            if (mIsScreenMagnificationEnabled) {
+                                flags |= AccessibilityInputFilter.FLAG_FEATURE_SCREEN_MAGNIFIER;
+                            }
+                            if (mIsTouchExplorationEnabled) {
+                                flags |= AccessibilityInputFilter.FLAG_FEATURE_TOUCH_EXPLORATION;
+                            }
+                            mInputFilter.setEnabledFeatures(flags);
+                        } else {
+                            if (mHasInputFilter) {
+                                mHasInputFilter = false;
+                                mInputFilter.setEnabledFeatures(0);
+                                inputFilter = null;
+                                setInputFilter = true;
+                            }
+                        }
+                    }
+                    if (setInputFilter) {
+                        try {
+                            mWindowManager.setInputFilter(inputFilter);
+                        } catch (RemoteException re) {
+                            /* ignore */
+                        }
+                    }
+                } break;
             }
         }
     }
@@ -1629,18 +1698,7 @@
                 // the state based on values in the settings database.
                 if (mIsAutomation) {
                     mUiAutomationService = null;
-
-                    populateEnabledAccessibilityServicesLocked();
-                    populateTouchExplorationGrantedAccessibilityServicesLocked();
-
-                    handleAccessibilityEnabledSettingChangedLocked();
-                    sendStateToClientsLocked();
-
-                    handleTouchExplorationEnabledSettingChangedLocked();
-                    updateInputFilterLocked();
-
-                    populateAccessibilityServiceListLocked();
-                    manageServicesLocked();
+                    updateInternalStateLocked();
                 }
             }
         }
diff --git a/services/java/com/android/server/accessibility/EventStreamTransformation.java b/services/java/com/android/server/accessibility/EventStreamTransformation.java
new file mode 100644
index 0000000..b715570
--- /dev/null
+++ b/services/java/com/android/server/accessibility/EventStreamTransformation.java
@@ -0,0 +1,90 @@
+/*
+ ** 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 com.android.server.accessibility;
+
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.accessibility.AccessibilityEvent;
+
+/**
+ * Interface for classes that can handle and potentially transform a stream of
+ * motion and accessibility events. Instances implementing this interface are
+ * ordered in a sequence to implement a transformation chain. An instance may
+ * consume, modify, and generate events. It is responsible to deliver the
+ * output events to the next transformation in the sequence set via
+ * {@link #setNext(EventStreamTransformation)}.
+ *
+ * Note that since instances implementing this interface are transformations
+ * of the event stream, an instance should work against the event stream
+ * potentially modified by previous ones. Hence, the order of transformations
+ * is important.
+ *
+ * It is a responsibility of each handler that decides to react to an event
+ * sequence and prevent any subsequent ones from performing an action to send
+ * the appropriate cancel event given it has delegated a part of the events
+ * that belong to the current gesture. This will ensure that subsequent
+ * transformations will not be left in an inconsistent state and the applications
+ * see a consistent event stream.
+ *
+ * For example, to cancel a {@link KeyEvent} the handler has to emit an event
+ * with action {@link KeyEvent#ACTION_UP} with the additional flag
+ * {@link KeyEvent#FLAG_CANCELED}. To cancel a {@link MotionEvent} the handler
+ * has to send an event with action {@link MotionEvent#ACTION_CANCEL}.
+ *
+ * It is a responsibility of each handler that received a cancel event to clear its
+ * internal state and to propagate the event to the next one to enable subsequent
+ * transformations to clear their internal state.
+ *
+ * It is a responsibility for each transformation to start handling events only
+ * after an event that designates the start of a well-formed event sequence.
+ * For example, if it received a down motion event followed by a cancel motion
+ * event, it should not handle subsequent move and up events until it gets a down.
+ */
+interface EventStreamTransformation {
+
+    /**
+     * Receives a motion event.
+     *
+     * @param event The motion event.
+     * @param policyFlags Policy flags for the event.
+     */
+    public void onMotionEvent(MotionEvent event, int policyFlags);
+
+    /**
+     * Receives an accessibility event.
+     *
+     * @param event The accessibility event.
+     */
+    public void onAccessibilityEvent(AccessibilityEvent event);
+
+    /**
+     * Sets the next transformation.
+     *
+     * @param next The next transformation.
+     */
+    public void setNext(EventStreamTransformation next);
+
+    /**
+     * Clears the internal state of this transformation.
+     */
+    public void clear();
+
+    /**
+     * Destroys this transformation.
+     */
+    public void onDestroy();
+}
diff --git a/services/java/com/android/server/accessibility/GestureUtils.java b/services/java/com/android/server/accessibility/GestureUtils.java
new file mode 100644
index 0000000..b68b09f
--- /dev/null
+++ b/services/java/com/android/server/accessibility/GestureUtils.java
@@ -0,0 +1,102 @@
+package com.android.server.accessibility;
+
+import android.util.MathUtils;
+import android.view.MotionEvent;
+
+/**
+ * Some helper functions for gesture detection.
+ */
+final class GestureUtils {
+
+    private GestureUtils() {
+        /* cannot be instantiated */
+    }
+
+    public static boolean isTap(MotionEvent down, MotionEvent up, int tapTimeSlop,
+            int tapDistanceSlop, int actionIndex) {
+        return eventsWithinTimeAndDistanceSlop(down, up, tapTimeSlop, tapDistanceSlop, actionIndex);
+    }
+
+    public static boolean isMultiTap(MotionEvent firstUp, MotionEvent secondUp,
+            int multiTapTimeSlop, int multiTapDistanceSlop, int actionIndex) {
+        return eventsWithinTimeAndDistanceSlop(firstUp, secondUp, multiTapTimeSlop,
+                multiTapDistanceSlop, actionIndex);
+    }
+
+    private static boolean eventsWithinTimeAndDistanceSlop(MotionEvent first, MotionEvent second,
+            int timeout, int distance, int actionIndex) {
+        if (isTimedOut(first, second, timeout)) {
+            return false;
+        }
+        final double deltaMove = computeDistance(first, second, actionIndex);
+        if (deltaMove >= distance) {
+            return false;
+        }
+        return true;
+    }
+
+    public static double computeDistance(MotionEvent first, MotionEvent second, int pointerIndex) {
+         return MathUtils.dist(first.getX(pointerIndex), first.getY(pointerIndex),
+                 second.getX(pointerIndex), second.getY(pointerIndex));
+    }
+
+    public static boolean isTimedOut(MotionEvent firstUp, MotionEvent secondUp, int timeout) {
+        final long deltaTime = secondUp.getEventTime() - firstUp.getEventTime();
+        return (deltaTime >= timeout);
+    }
+
+    public static boolean isSamePointerContext(MotionEvent first, MotionEvent second) {
+        return (first.getPointerIdBits() == second.getPointerIdBits()
+                && first.getPointerId(first.getActionIndex())
+                        == second.getPointerId(second.getActionIndex()));
+    }
+
+    /**
+     * Determines whether a two pointer gesture is a dragging one.
+     *
+     * @param event The event with the pointer data.
+     * @return True if the gesture is a dragging one.
+     */
+    public static boolean isDraggingGesture(float firstPtrDownX, float firstPtrDownY,
+            float secondPtrDownX, float secondPtrDownY, float firstPtrX, float firstPtrY,
+            float secondPtrX, float secondPtrY, float maxDraggingAngleCos) {
+
+        // Check if the pointers are moving in the same direction.
+        final float firstDeltaX = firstPtrX - firstPtrDownX;
+        final float firstDeltaY = firstPtrY - firstPtrDownY;
+
+        if (firstDeltaX == 0 && firstDeltaY == 0) {
+            return true;
+        }
+
+        final float firstMagnitude =
+            (float) Math.sqrt(firstDeltaX * firstDeltaX + firstDeltaY * firstDeltaY);
+        final float firstXNormalized =
+            (firstMagnitude > 0) ? firstDeltaX / firstMagnitude : firstDeltaX;
+        final float firstYNormalized =
+            (firstMagnitude > 0) ? firstDeltaY / firstMagnitude : firstDeltaY;
+
+        final float secondDeltaX = secondPtrX - secondPtrDownX;
+        final float secondDeltaY = secondPtrY - secondPtrDownY;
+
+        if (secondDeltaX == 0 && secondDeltaY == 0) {
+            return true;
+        }
+
+        final float secondMagnitude =
+            (float) Math.sqrt(secondDeltaX * secondDeltaX + secondDeltaY * secondDeltaY);
+        final float secondXNormalized =
+            (secondMagnitude > 0) ? secondDeltaX / secondMagnitude : secondDeltaX;
+        final float secondYNormalized =
+            (secondMagnitude > 0) ? secondDeltaY / secondMagnitude : secondDeltaY;
+
+        final float angleCos =
+            firstXNormalized * secondXNormalized + firstYNormalized * secondYNormalized;
+
+        if (angleCos < maxDraggingAngleCos) {
+            return false;
+        }
+
+        return true;
+    }
+}
diff --git a/services/java/com/android/server/accessibility/ScreenMagnifier.java b/services/java/com/android/server/accessibility/ScreenMagnifier.java
new file mode 100644
index 0000000..bd7f276
--- /dev/null
+++ b/services/java/com/android/server/accessibility/ScreenMagnifier.java
@@ -0,0 +1,1754 @@
+/*
+ * 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.accessibility;
+
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.ObjectAnimator;
+import android.animation.TypeEvaluator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.PointF;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.provider.Settings;
+import android.util.MathUtils;
+import android.util.Property;
+import android.util.Slog;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.Gravity;
+import android.view.IDisplayContentChangeListener;
+import android.view.IWindowManager;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.MotionEvent.PointerCoords;
+import android.view.MotionEvent.PointerProperties;
+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;
+
+import com.android.internal.R;
+import com.android.internal.os.SomeArgs;
+
+import java.util.ArrayList;
+
+/**
+ * This class handles the screen magnification when accessibility is enabled.
+ * The behavior is as follows:
+ *
+ * 1. Triple tap toggles permanent screen magnification which is magnifying
+ *    the area around the location of the triple tap. One can think of the
+ *    location of the triple tap as the center of the magnified viewport.
+ *    For example, a triple tap when not magnified would magnify the screen
+ *    and leave it in a magnified state. A triple tapping when magnified would
+ *    clear magnification and leave the screen in a not magnified state.
+ *
+ * 2. Triple tap and hold would magnify the screen if not magnified and enable
+ *    viewport dragging mode until the finger goes up. One can think of this
+ *    mode as a way to move the magnified viewport since the area around the
+ *    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
+ *    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.
+ *
+ * 3. Pinching with any number of additional fingers when viewport dragging
+ *    is enabled, i.e. the user triple tapped and holds, would adjust the
+ *    magnification scale which will become the current default magnification
+ *    scale. The next time the user magnifies the same magnification scale
+ *    would be used.
+ *
+ * 4. When in a permanent magnified state the user can use two or more fingers
+ *    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
+ *    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 {
+
+    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_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_GESTURE_DETECTOR = false;
+    private static final boolean DEBUG_MAGNIFICATION_CONTROLLER = false;
+
+    private static final String LOG_TAG = ScreenMagnifier.class.getSimpleName();
+
+    private static final int STATE_DELEGATING = 1;
+    private static final int STATE_DETECTING = 2;
+    private static final int STATE_SCALING = 3;
+    private static final int STATE_VIEWPORT_DRAGGING = 4;
+    private static final int STATE_PANNING = 5;
+    private static final int STATE_DECIDE_PAN_OR_SCALE = 6;
+
+    private static final float DEFAULT_MAGNIFICATION_SCALE = 2.0f;
+    private static final int DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE = 1;
+    private static final float DEFAULT_WINDOW_ANIMATION_SCALE = 1.0f;
+
+    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 GestureDetector mGestureDetector;
+    private final StateViewportDraggingHandler mStateViewportDraggingHandler =
+            new StateViewportDraggingHandler();
+
+    private final Interpolator mInterpolator = new DecelerateInterpolator(2.5f);
+
+    private final MagnificationController mMagnificationController;
+    private final DisplayContentObserver mDisplayContentObserver;
+    private final Viewport mViewport;
+
+    private final int mTapTimeSlop = ViewConfiguration.getTapTimeout();
+    private final int mMultiTapTimeSlop = ViewConfiguration.getDoubleTapTimeout();
+    private final int mTapDistanceSlop;
+    private final int mMultiTapDistanceSlop;
+
+    private final int mShortAnimationDuration;
+    private final int mLongAnimationDuration;
+    private final float mWindowAnimationScale;
+
+    private final Context mContext;
+
+    private EventStreamTransformation mNext;
+
+    private int mCurrentState;
+    private boolean mTranslationEnabledBeforePan;
+
+    private PointerCoords[] mTempPointerCoords;
+    private PointerProperties[] mTempPointerProperties;
+
+    public ScreenMagnifier(Context context) {
+        mContext = context;
+        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+
+        mShortAnimationDuration = context.getResources().getInteger(
+                com.android.internal.R.integer.config_shortAnimTime);
+        mLongAnimationDuration = context.getResources().getInteger(
+                com.android.internal.R.integer.config_longAnimTime);
+        mTapDistanceSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+        mMultiTapDistanceSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
+        mWindowAnimationScale = Settings.System.getFloat(context.getContentResolver(),
+                Settings.System.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);
+
+        mGestureDetector = new GestureDetector(context);
+
+        transitionToState(STATE_DETECTING);
+    }
+
+    @Override
+    public void onMotionEvent(MotionEvent event, int policyFlags) {
+        switch (mCurrentState) {
+            case STATE_DELEGATING: {
+                handleMotionEventStateDelegating(event, policyFlags);
+            } break;
+            case STATE_DETECTING: {
+                mDetectingStateHandler.onMotionEvent(event, policyFlags);
+            } break;
+            case STATE_VIEWPORT_DRAGGING: {
+                mStateViewportDraggingHandler.onMotionEvent(event, policyFlags);
+            } break;
+            case STATE_SCALING:
+            case STATE_PANNING:
+            case STATE_DECIDE_PAN_OR_SCALE: {
+                // Handled by the gesture detector. Since the detector
+                // needs all touch events to work properly we cannot
+                // call it only for these states.
+            } break;
+            default: {
+                throw new IllegalStateException("Unknown state: " + mCurrentState);
+            }
+        }
+        mGestureDetector.onMotionEvent(event);
+    }
+
+    @Override
+    public void onAccessibilityEvent(AccessibilityEvent event) {
+        if (mNext != null) {
+            mNext.onAccessibilityEvent(event);
+        }
+    }
+
+    @Override
+    public void setNext(EventStreamTransformation next) {
+        mNext = next;
+    }
+
+    @Override
+    public void clear() {
+        mCurrentState = STATE_DETECTING;
+        mDetectingStateHandler.clear();
+        mStateViewportDraggingHandler.clear();
+        mGestureDetector.clear();
+
+        if (mNext != null) {
+            mNext.clear();
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        mDisplayProvider.destroy();
+        mDisplayContentObserver.destroy();
+    }
+
+    private void handleMotionEventStateDelegating(MotionEvent event, int policyFlags) {
+        if (event.getActionMasked() == MotionEvent.ACTION_UP) {
+            if (mDetectingStateHandler.mDelayedEventQueue == null) {
+                transitionToState(STATE_DETECTING);
+            }
+        }
+        if (mNext != null) {
+            // If the event is within the magnified portion of the screen we have
+            // to change its location to be where the user thinks he is poking the
+            // UI which may have been magnified and panned.
+            final float eventX = event.getX();
+            final float eventY = event.getY();
+            if (mMagnificationController.isMagnifying()
+                    && mViewport.getBounds().contains((int) eventX, (int) eventY)) {
+                final float scale = mMagnificationController.getScale();
+                final float scaledOffsetX = mMagnificationController.getScaledOffsetX();
+                final float scaledOffsetY = mMagnificationController.getScaledOffsetY();
+                final int pointerCount = event.getPointerCount();
+                PointerCoords[] coords = getTempPointerCoordsWithMinSize(pointerCount);
+                PointerProperties[] properties = getTempPointerPropertiesWithMinSize(pointerCount);
+                for (int i = 0; i < pointerCount; i++) {
+                    event.getPointerCoords(i, coords[i]);
+                    coords[i].x = (coords[i].x - scaledOffsetX) / scale;
+                    coords[i].y = (coords[i].y - scaledOffsetY) / scale;
+                    event.getPointerProperties(i, properties[i]);
+                }
+                event = MotionEvent.obtain(event.getDownTime(),
+                        event.getEventTime(), event.getAction(), pointerCount, properties,
+                        coords, 0, 0, 1.0f, 1.0f, event.getDeviceId(), 0, event.getSource(),
+                        event.getFlags());
+            }
+            mNext.onMotionEvent(event, policyFlags);
+        }
+    }
+
+    private PointerCoords[] getTempPointerCoordsWithMinSize(int size) {
+        final int oldSize = (mTempPointerCoords != null) ? mTempPointerCoords.length : 0;
+        if (oldSize < size) {
+            mTempPointerCoords = new PointerCoords[size];
+        }
+        for (int i = oldSize; i < size; i++) {
+            mTempPointerCoords[i] = new PointerCoords();
+        }
+        return mTempPointerCoords;
+    }
+
+    private PointerProperties[] getTempPointerPropertiesWithMinSize(int size) {
+        final int oldSize = (mTempPointerProperties != null) ? mTempPointerProperties.length : 0;
+        if (oldSize < size) {
+            mTempPointerProperties = new PointerProperties[size];
+        }
+        for (int i = oldSize; i < size; i++) {
+            mTempPointerProperties[i] = new PointerProperties();
+        }
+        return mTempPointerProperties;
+    }
+    
+    private void transitionToState(int state) {
+        if (DEBUG_STATE_TRANSITIONS) {
+            switch (state) {
+                case STATE_DELEGATING: {
+                    Slog.i(LOG_TAG, "mCurrentState: STATE_DELEGATING");
+                } break;
+                case STATE_DETECTING: {
+                    Slog.i(LOG_TAG, "mCurrentState: STATE_DETECTING");
+                } break;
+                case STATE_VIEWPORT_DRAGGING: {
+                    Slog.i(LOG_TAG, "mCurrentState: STATE_VIEWPORT_DRAGGING");
+                } break;
+                case STATE_SCALING: {
+                    Slog.i(LOG_TAG, "mCurrentState: STATE_SCALING");
+                } break;
+                case STATE_PANNING: {
+                    Slog.i(LOG_TAG, "mCurrentState: STATE_PANNING");
+                } break;
+                case STATE_DECIDE_PAN_OR_SCALE: {
+                    Slog.i(LOG_TAG, "mCurrentState: STATE_DETECTING_PAN_OR_SCALE");
+                } break;
+                default: {
+                    throw new IllegalArgumentException("Unknown state: " + state);
+                }
+            }
+        }
+        mCurrentState = state;
+    }
+
+    private final class GestureDetector implements OnScaleGestureListener {
+        private static final float MIN_SCALE = 1.3f;
+        private static final float MAX_SCALE = 5.0f;
+
+        private static final float DETECT_SCALING_THRESHOLD = 0.25f;
+        private static final int DETECT_PANNING_THRESHOLD_DIP = 30;
+
+        private final float mScaledDetectPanningThreshold;
+
+        private final ScaleGestureDetector mScaleGestureDetector;
+
+        private final PointF mPrevFocus = new PointF(Float.NaN, Float.NaN);
+        private final PointF mInitialFocus = new PointF(Float.NaN, Float.NaN);
+
+        private float mCurrScale = Float.NaN;
+        private float mCurrScaleFactor = 1.0f;
+        private float mPrevScaleFactor = 1.0f;
+        private float mCurrPan;
+        private float mPrevPan;
+
+        private float mScaleFocusX = Float.NaN;
+        private float mScaleFocusY = Float.NaN;
+
+        public GestureDetector(Context context) {
+            final float density = context.getResources().getDisplayMetrics().density;
+            mScaledDetectPanningThreshold = DETECT_PANNING_THRESHOLD_DIP * density;
+            mScaleGestureDetector = new ScaleGestureDetector(context, this);
+        }
+
+        public void onMotionEvent(MotionEvent event) {
+            mScaleGestureDetector.onTouchEvent(event);
+            switch (mCurrentState) {
+                case STATE_DETECTING:
+                case STATE_DELEGATING:
+                case STATE_VIEWPORT_DRAGGING: {
+                    return;
+                }
+            }
+            if (event.getActionMasked() == MotionEvent.ACTION_UP) {
+                clear();
+                if (mCurrentState == STATE_SCALING) {
+                    persistScale(mMagnificationController.getScale());
+                }
+                transitionToState(STATE_DETECTING);
+            }
+        }
+
+        @Override
+        public boolean onScale(ScaleGestureDetector detector) {
+            switch (mCurrentState) {
+                case STATE_DETECTING:
+                case STATE_DELEGATING:
+                case STATE_VIEWPORT_DRAGGING: {
+                    return true;
+                }
+                case STATE_DECIDE_PAN_OR_SCALE: {
+                    mCurrScaleFactor = mScaleGestureDetector.getScaleFactor();
+                    final float scaleDelta = Math.abs(1.0f - mCurrScaleFactor * mPrevScaleFactor);
+                    if (DEBUG_GESTURE_DETECTOR) {
+                        Slog.i(LOG_TAG, "scaleDelta: " + scaleDelta);
+                    }
+                    if (scaleDelta > DETECT_SCALING_THRESHOLD) {
+                        performScale(detector, true);
+                        transitionToState(STATE_SCALING);
+                        return false;
+                    }
+                    mCurrPan = (float) MathUtils.dist(
+                            mScaleGestureDetector.getFocusX(),
+                            mScaleGestureDetector.getFocusY(),
+                            mInitialFocus.x, mInitialFocus.y);
+                    final float panDelta = mCurrPan + mPrevPan;
+                    if (DEBUG_GESTURE_DETECTOR) {
+                        Slog.i(LOG_TAG, "panDelta: " + panDelta);
+                    }
+                    if (panDelta > mScaledDetectPanningThreshold) {
+                        transitionToState(STATE_PANNING);
+                        performPan(detector, true);
+                        return false;
+                    }
+                } break;
+                case STATE_SCALING: {
+                    performScale(detector, false);
+                } break;
+                case STATE_PANNING: {
+                    performPan(detector, false);
+                } break;
+            }
+            return false;
+        }
+
+        @Override
+        public boolean onScaleBegin(ScaleGestureDetector detector) {
+            switch (mCurrentState) {
+                case STATE_DECIDE_PAN_OR_SCALE: {
+                    mPrevScaleFactor *= mCurrScaleFactor;
+                    mPrevPan += mCurrPan;
+                    mPrevFocus.x = mInitialFocus.x = detector.getFocusX();
+                    mPrevFocus.y = mInitialFocus.y = detector.getFocusY();
+                } break;
+                case STATE_SCALING: {
+                    mPrevScaleFactor = 1.0f;
+                    mCurrScale = Float.NaN;
+                } break;
+                case STATE_PANNING: {
+                    mPrevPan += mCurrPan;
+                    mPrevFocus.x = mInitialFocus.x = detector.getFocusX();
+                    mPrevFocus.y = mInitialFocus.y = detector.getFocusY();
+                } break;
+            }
+            return true;
+        }
+
+        @Override
+        public void onScaleEnd(ScaleGestureDetector detector) {
+            /* do nothing */
+        }
+
+        public void clear() {
+            mCurrScaleFactor = 1.0f;
+            mPrevScaleFactor = 1.0f;
+            mPrevPan = 0;
+            mCurrPan = 0;
+            mInitialFocus.set(Float.NaN, Float.NaN);
+            mPrevFocus.set(Float.NaN, Float.NaN);
+            mCurrScale = Float.NaN;
+            mScaleFocusX = Float.NaN;
+            mScaleFocusY = Float.NaN;
+        }
+
+        private void performPan(ScaleGestureDetector detector, boolean animate) {
+            if (Float.compare(mPrevFocus.x, Float.NaN) == 0
+                    && Float.compare(mPrevFocus.y, Float.NaN) == 0) {
+                mPrevFocus.set(detector.getFocusX(), detector.getFocusY());
+                return;
+            }
+            final float scale = mMagnificationController.getScale();
+            final float scrollX = (detector.getFocusX() - mPrevFocus.x) / scale;
+            final float scrollY = (detector.getFocusY() - mPrevFocus.y) / 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);
+            }
+            mMagnificationController.setMagnifiedRegionCenter(centerX, centerY, animate);
+            mPrevFocus.set(detector.getFocusX(), detector.getFocusY());
+        }
+
+        private void performScale(ScaleGestureDetector detector, boolean animate) {
+            if (Float.compare(mCurrScale, Float.NaN) == 0) {
+                mCurrScale = mMagnificationController.getScale();
+                return;
+            }
+            final float totalScaleFactor = mPrevScaleFactor * detector.getScaleFactor();
+            final float newScale = mCurrScale * totalScaleFactor;
+            final float normalizedNewScale = Math.min(Math.max(newScale, MIN_SCALE),
+                    MAX_SCALE);
+            if (DEBUG_SCALING) {
+                Slog.i(LOG_TAG, "normalizedNewScale: " + normalizedNewScale);
+            }
+            if (Float.compare(mScaleFocusX, Float.NaN) == 0
+                    && Float.compare(mScaleFocusY, Float.NaN) == 0) {
+                mScaleFocusX = detector.getFocusX();
+                mScaleFocusY = detector.getFocusY();
+            }
+            mMagnificationController.setScale(normalizedNewScale, mScaleFocusX,
+                    mScaleFocusY, animate);
+        }
+    }
+
+    private final class StateViewportDraggingHandler {
+        private boolean mLastMoveOutsideMagnifiedRegion;
+
+        private void onMotionEvent(MotionEvent event, int policyFlags) {
+            final int action = event.getActionMasked();
+            switch (action) {
+                case MotionEvent.ACTION_DOWN: {
+                    throw new IllegalArgumentException("Unexpected event type: ACTION_DOWN");
+                }
+                case MotionEvent.ACTION_POINTER_DOWN: {
+                    clear();
+                    transitionToState(STATE_SCALING);
+                } break;
+                case MotionEvent.ACTION_MOVE: {
+                    if (event.getPointerCount() != 1) {
+                        throw new IllegalStateException("Should have one pointer down.");
+                    }
+                    final float eventX = event.getX();
+                    final float eventY = event.getY();
+                    if (mViewport.getBounds().contains((int) eventX, (int) eventY)) {
+                        if (mLastMoveOutsideMagnifiedRegion) {
+                            mLastMoveOutsideMagnifiedRegion = false;
+                            mMagnificationController.setMagnifiedRegionCenter(eventX,
+                                    eventY, true);
+                        } else {
+                            mMagnificationController.setMagnifiedRegionCenter(eventX,
+                                    eventY, false);
+                        }
+                    } else {
+                        mLastMoveOutsideMagnifiedRegion = true;
+                    }
+                } break;
+                case MotionEvent.ACTION_UP: {
+                    if (!mTranslationEnabledBeforePan) {
+                        mMagnificationController.reset(true);
+                        mViewport.setFrameShown(false, true);
+                    }
+                    clear();
+                    transitionToState(STATE_DETECTING);
+                } break;
+                case MotionEvent.ACTION_POINTER_UP: {
+                    throw new IllegalArgumentException("Unexpected event type: ACTION_POINTER_UP");
+                }
+            }
+        }
+
+        public void clear() {
+            mLastMoveOutsideMagnifiedRegion = false;
+        }
+    }
+
+    private final class DetectingStateHandler {
+
+        private static final int MESSAGE_ON_ACTION_TAP_AND_HOLD = 1;
+
+        private static final int MESSAGE_TRANSITION_TO_DELEGATING_STATE = 2;
+
+        private static final int ACTION_TAP_COUNT = 3;
+
+        private MotionEventInfo mDelayedEventQueue;
+
+        private MotionEvent mLastDownEvent;
+        private MotionEvent mLastTapUpEvent;
+        private int mTapCount;
+
+        private final Handler mHandler = new Handler() {
+            @Override
+            public void handleMessage(Message message) {
+                final int type = message.what;
+                switch (type) {
+                    case MESSAGE_ON_ACTION_TAP_AND_HOLD: {
+                        MotionEvent event = (MotionEvent) message.obj;
+                        final int policyFlags = message.arg1;
+                        onActionTapAndHold(event, policyFlags);
+                    } break;
+                    case MESSAGE_TRANSITION_TO_DELEGATING_STATE: {
+                        transitionToState(STATE_DELEGATING);
+                        sendDelayedMotionEvents();
+                        clear();
+                    } break;
+                    default: {
+                        throw new IllegalArgumentException("Unknown message type: " + type);
+                    }
+                }
+            }
+        };
+
+        public void onMotionEvent(MotionEvent event, int policyFlags) {
+            cacheDelayedMotionEvent(event, policyFlags);
+            final int action = event.getActionMasked();
+            switch (action) {
+                case MotionEvent.ACTION_DOWN: {
+                    mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
+                    if (!mViewport.getBounds().contains((int) event.getX(),
+                            (int) event.getY())) {
+                        transitionToDelegatingStateAndClear();
+                        return;
+                    }
+                    if (mTapCount == ACTION_TAP_COUNT - 1 && mLastDownEvent != null
+                            && GestureUtils.isMultiTap(mLastDownEvent, event,
+                                    mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) {
+                        Message message = mHandler.obtainMessage(MESSAGE_ON_ACTION_TAP_AND_HOLD,
+                                policyFlags, 0, event);
+                        mHandler.sendMessageDelayed(message,
+                                ViewConfiguration.getLongPressTimeout());
+                    } else if (mTapCount < ACTION_TAP_COUNT) {
+                        Message message = mHandler.obtainMessage(
+                                MESSAGE_TRANSITION_TO_DELEGATING_STATE);
+                        mHandler.sendMessageDelayed(message, mTapTimeSlop + mMultiTapDistanceSlop);
+                    }
+                    clearLastDownEvent();
+                    mLastDownEvent = MotionEvent.obtain(event);
+                } break;
+                case MotionEvent.ACTION_POINTER_DOWN: {
+                    if (mMagnificationController.isMagnifying()) {
+                        transitionToState(STATE_DECIDE_PAN_OR_SCALE);
+                        clear();
+                    } else {
+                        transitionToDelegatingStateAndClear();
+                    }
+                } break;
+                case MotionEvent.ACTION_MOVE: {
+                    if (mLastDownEvent != null && mTapCount < ACTION_TAP_COUNT - 1) {
+                        final double distance = GestureUtils.computeDistance(mLastDownEvent,
+                                event, 0);
+                        if (Math.abs(distance) > mTapDistanceSlop) {
+                            transitionToDelegatingStateAndClear();
+                        }
+                    }
+                } break;
+                case MotionEvent.ACTION_UP: {
+                    if (mLastDownEvent == null) {
+                        return;
+                    }
+                    mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD);
+                    if (!mViewport.getBounds().contains((int) event.getX(), (int) event.getY())) {
+                         transitionToDelegatingStateAndClear();
+                         return;
+                    }
+                    if (!GestureUtils.isTap(mLastDownEvent, event, mTapTimeSlop,
+                            mTapDistanceSlop, 0)) {
+                        transitionToDelegatingStateAndClear();
+                        return;
+                    }
+                    if (mLastTapUpEvent != null && !GestureUtils.isMultiTap(mLastTapUpEvent,
+                            event, mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) {
+                        transitionToDelegatingStateAndClear();
+                        return;
+                    }
+                    mTapCount++;
+                    if (DEBUG_DETECTING) {
+                        Slog.i(LOG_TAG, "Tap count:" + mTapCount);
+                    }
+                    if (mTapCount == ACTION_TAP_COUNT) {
+                        clear();
+                        onActionTap(event, policyFlags);
+                        return;
+                    }
+                    clearLastTapUpEvent();
+                    mLastTapUpEvent = MotionEvent.obtain(event);
+                } break;
+                case MotionEvent.ACTION_POINTER_UP: {
+                    /* do nothing */
+                } break;
+            }
+        }
+
+        public void clear() {
+            mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD);
+            mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
+            clearTapDetectionState();
+            clearDelayedMotionEvents();
+        }
+
+        private void clearTapDetectionState() {
+            mTapCount = 0;
+            clearLastTapUpEvent();
+            clearLastDownEvent();
+        }
+
+        private void clearLastTapUpEvent() {
+            if (mLastTapUpEvent != null) {
+                mLastTapUpEvent.recycle();
+                mLastTapUpEvent = null;
+            }
+        }
+
+        private void clearLastDownEvent() {
+            if (mLastDownEvent != null) {
+                mLastDownEvent.recycle();
+                mLastDownEvent = null;
+            }
+        }
+
+        private void cacheDelayedMotionEvent(MotionEvent event, int policyFlags) {
+            MotionEventInfo info = MotionEventInfo.obtain(event, policyFlags);
+            if (mDelayedEventQueue == null) {
+                mDelayedEventQueue = info;
+            } else {
+                MotionEventInfo tail = mDelayedEventQueue;
+                while (tail.mNext != null) {
+                    tail = tail.mNext;
+                }
+                tail.mNext = info;
+            }
+        }
+
+        private void sendDelayedMotionEvents() {
+            while (mDelayedEventQueue != null) {
+                MotionEventInfo info = mDelayedEventQueue;
+                mDelayedEventQueue = info.mNext;
+                ScreenMagnifier.this.onMotionEvent(info.mEvent, info.mPolicyFlags);
+                info.recycle();
+            }
+        }
+
+        private void clearDelayedMotionEvents() {
+            while (mDelayedEventQueue != null) {
+                MotionEventInfo info = mDelayedEventQueue;
+                mDelayedEventQueue = info.mNext;
+                info.recycle();
+            }
+        }
+
+        private void transitionToDelegatingStateAndClear() {
+            transitionToState(STATE_DELEGATING);
+            sendDelayedMotionEvents();
+            clear();
+        }
+
+        private void onActionTap(MotionEvent up, int policyFlags) {
+            if (DEBUG_DETECTING) {
+                Slog.i(LOG_TAG, "onActionTap()");
+            }
+            if (!mMagnificationController.isMagnifying()) {
+                mMagnificationController.setScaleAndMagnifiedRegionCenter(getPersistedScale(),
+                        up.getX(), up.getY(), true);
+                mViewport.setFrameShown(true, true);
+            } else {
+                mMagnificationController.reset(true);
+                mViewport.setFrameShown(false, true);
+            }
+        }
+
+        private void onActionTapAndHold(MotionEvent down, int policyFlags) {
+            if (DEBUG_DETECTING) {
+                Slog.i(LOG_TAG, "onActionTapAndHold()");
+            }
+            clear();
+            mTranslationEnabledBeforePan = mMagnificationController.isMagnifying();
+            mMagnificationController.setScaleAndMagnifiedRegionCenter(getPersistedScale(),
+                    down.getX(), down.getY(), true);
+            mViewport.setFrameShown(true, true);
+            transitionToState(STATE_VIEWPORT_DRAGGING);
+        }
+    }
+
+    private void persistScale(final float scale) {
+        new AsyncTask<Void, Void, Void>() {
+            @Override
+            protected Void doInBackground(Void... params) {
+                Settings.Secure.putFloat(mContext.getContentResolver(),
+                        Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale);
+                return null;
+            }
+        }.execute();
+    }
+
+    private float getPersistedScale() {
+        return Settings.Secure.getFloat(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
+                DEFAULT_MAGNIFICATION_SCALE);
+    }
+
+    private static final class MotionEventInfo {
+
+        private static final int MAX_POOL_SIZE = 10;
+
+        private static final Object sLock = new Object();
+        private static MotionEventInfo sPool;
+        private static int sPoolSize;
+
+        private MotionEventInfo mNext;
+        private boolean mInPool;
+
+        public MotionEvent mEvent;
+        public int mPolicyFlags;
+
+        public static MotionEventInfo obtain(MotionEvent event, int policyFlags) {
+            synchronized (sLock) {
+                MotionEventInfo info;
+                if (sPoolSize > 0) {
+                    sPoolSize--;
+                    info = sPool;
+                    sPool = info.mNext;
+                    info.mNext = null;
+                    info.mInPool = false;
+                } else {
+                    info = new MotionEventInfo();
+                }
+                info.initialize(event, policyFlags);
+                return info;
+            }
+        }
+
+        private void initialize(MotionEvent event, int policyFlags) {
+            mEvent = MotionEvent.obtain(event);
+            mPolicyFlags = policyFlags;
+        }
+
+        public void recycle() {
+            synchronized (sLock) {
+                if (mInPool) {
+                    throw new IllegalStateException("Already recycled.");
+                }
+                clear();
+                if (sPoolSize < MAX_POOL_SIZE) {
+                    sPoolSize++;
+                    mNext = sPool;
+                    sPool = this;
+                    mInPool = true;
+                }
+            }
+        }
+
+        private void clear() {
+            mEvent.recycle();
+            mEvent = null;
+            mPolicyFlags = 0;
+        }
+    }
+
+    private static final class DisplayContentObserver {
+
+        private static final int MESSAGE_SHOW_VIEWPORT_FRAME = 1;
+        private static final int MESSAGE_RECOMPUTE_VIEWPORT_BOUNDS = 2;
+        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 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();
+                }
+            };
+
+            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) {
+                    switch (transition) {
+                        case WindowManagerPolicy.TRANSIT_ENTER:
+                        case WindowManagerPolicy.TRANSIT_SHOW:
+                        case WindowManagerPolicy.TRANSIT_EXIT:
+                        case WindowManagerPolicy.TRANSIT_HIDE: {
+                            mViewport.recomputeBounds(mMagnificationController.isMagnifying());
+                        } break;
+                    }
+                } else {
+                    switch (transition) {
+                        case WindowManagerPolicy.TRANSIT_ENTER:
+                        case WindowManagerPolicy.TRANSIT_SHOW: {
+                            if (!magnifying || !screenMagnificationAutoUpdateEnabled(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_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: {
+                                    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()) {
+                scrollX = rectangle.left - magnifiedRegionBounds.left;
+            } 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()
+                    && screenMagnificationAutoUpdateEnabled(mContext)) {
+                mMagnificationController.reset(true);
+                mViewport.setFrameShown(false, true);
+            }
+        }
+
+        private boolean screenMagnificationAutoUpdateEnabled(Context context) {
+            return (Settings.Secure.getInt(context.getContentResolver(),
+                    Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE,
+                    DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE) == 1);
+        }
+
+        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_RECOMPUTE_VIEWPORT_BOUNDS: {
+                        final boolean animate = message.arg1 == 1;
+                        mViewport.recomputeBounds(animate);
+                    } 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;
+                    default: {
+                        throw new IllegalArgumentException("Unknown message: " + action);
+                    }
+                }
+            }
+        }
+    }
+
+    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 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 mTempRect = new Rect();
+
+        private final IWindowManager mWindowManagerService;
+        private final DisplayProvider mDisplayProvider;
+
+        private final ViewportWindow mViewportFrame;
+
+        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);
+
+            mShowHideFrameAnimator = ObjectAnimator.ofInt(mViewportFrame, PROPERTY_NAME_ALPHA,
+                  MIN_ALPHA, MAX_ALPHA);
+            mShowHideFrameAnimator.setInterpolator(animationInterpolator);
+            mShowHideFrameAnimator.setDuration(animationDuration);
+            mShowHideFrameAnimator.addListener(new AnimatorListener() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    if (mShowHideFrameAnimator.getAnimatedValue().equals(MIN_ALPHA)) {
+                        mViewportFrame.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 */
+                }
+            });
+
+            Property<ViewportWindow, Rect> property = Property.of(ViewportWindow.class,
+                    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;
+                    result.left = (int) (fromFrame.left
+                            + (toFrame.left - fromFrame.left) * fraction);
+                    result.top = (int) (fromFrame.top
+                            + (toFrame.top - fromFrame.top) * fraction);
+                    result.right = (int) (fromFrame.right
+                            + (toFrame.right - fromFrame.right) * fraction);
+                    result.bottom = (int) (fromFrame.bottom
+                            + (toFrame.bottom - fromFrame.bottom) * fraction);
+                    return result;
+                }
+            };
+            mResizeFrameAnimator = ObjectAnimator.ofObject(mViewportFrame, property,
+                    evaluator, mViewportFrame.mBounds, mViewportFrame.mBounds);
+            mResizeFrameAnimator.setDuration((long) (animationDuration));
+            mResizeFrameAnimator.setInterpolator(animationInterpolator);
+
+            recomputeBounds(false);
+        }
+
+        public void recomputeBounds(boolean animate) {
+            Rect frame = mTempRect;
+            frame.set(0, 0, mDisplayProvider.getDisplayInfo().logicalWidth,
+                    mDisplayProvider.getDisplayInfo().logicalHeight);
+            ArrayList<WindowInfo> infos = mTempWindowInfoList;
+            infos.clear();
+            try {
+                mWindowManagerService.getVisibleWindowsForDisplay(
+                        mDisplayProvider.getDisplay().getDisplayId(), infos);
+                final int windowCount = infos.size();
+                for (int i = 0; i < windowCount; i++) {
+                    WindowInfo info = infos.get(i);
+                    if (info.type == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
+                            || info.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD
+                            || info.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG) {
+                        subtract(frame, info.touchableRegion);
+                    }
+                    info.recycle();
+                }
+            } catch (RemoteException re) {
+                /* ignore */
+            } finally {
+                infos.clear();
+            }
+            resize(frame, animate);
+        }
+
+        public void rotationChanged() {
+            mViewportFrame.rotationChanged();
+        }
+
+        public Rect getBounds() {
+            return mViewportFrame.getBounds();
+        }
+
+        public void setFrameShown(boolean shown, boolean animate) {
+            if (mViewportFrame.isShown() == shown) {
+                return;
+            }
+            if (animate) {
+                if (mShowHideFrameAnimator.isRunning()) {
+                    mShowHideFrameAnimator.reverse();
+                } else {
+                    if (shown) {
+                        mViewportFrame.show();
+                        mShowHideFrameAnimator.start();
+                    } else {
+                        mShowHideFrameAnimator.reverse();
+                    }
+                }
+            } else {
+                mShowHideFrameAnimator.cancel();
+                if (shown) {
+                    mViewportFrame.show();
+                } else {
+                    mViewportFrame.hide();
+                }
+            }
+        }
+
+        private void resize(Rect bounds, boolean animate) {
+            if (mViewportFrame.getBounds().equals(bounds)) {
+                return;
+            }
+            if (animate) {
+                if (mResizeFrameAnimator.isRunning()) {
+                    mResizeFrameAnimator.cancel();
+                }
+                mResizeFrameAnimator.setObjectValues(mViewportFrame.mBounds, bounds);
+                mResizeFrameAnimator.start();
+            } else {
+                mViewportFrame.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;
+            }
+            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);
+                }
+            }
+        }
+    }
+
+    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();
+        }
+
+        public DisplayInfo getDisplayInfo() {
+            return mDefaultDisplayInfo;
+        }
+
+        public Display getDisplay() {
+            return mDefaultDisplay;
+        }
+
+        private void updateDisplayInfo() {
+            if (!mDefaultDisplay.getDisplayInfo(mDefaultDisplayInfo)) {
+                Slog.e(LOG_TAG, "Default display is not valid.");
+            }
+        }
+
+        public void destroy() {
+            mDisplayManager.unregisterDisplayListener(this);
+        }
+
+        @Override
+        public void onDisplayAdded(int displayId) {
+            /* do noting */
+        }
+
+        @Override
+        public void onDisplayRemoved(int displayId) {
+            // Having no default display
+        }
+
+        @Override
+        public void onDisplayChanged(int displayId) {
+            updateDisplayInfo();
+        }
+    }
+}
diff --git a/services/java/com/android/server/accessibility/TouchExplorer.java b/services/java/com/android/server/accessibility/TouchExplorer.java
index ba9f2cd..9e4f33e 100644
--- a/services/java/com/android/server/accessibility/TouchExplorer.java
+++ b/services/java/com/android/server/accessibility/TouchExplorer.java
@@ -28,7 +28,6 @@
 import android.os.Handler;
 import android.os.SystemClock;
 import android.util.Slog;
-import android.view.InputFilter;
 import android.view.MotionEvent;
 import android.view.MotionEvent.PointerCoords;
 import android.view.MotionEvent.PointerProperties;
@@ -64,7 +63,7 @@
  *
  * @hide
  */
-public class TouchExplorer {
+class TouchExplorer implements EventStreamTransformation {
 
     private static final boolean DEBUG = false;
 
@@ -120,10 +119,6 @@
     // Slop between the first and second tap to be a double tap.
     private final int mDoubleTapSlop;
 
-    // The InputFilter this tracker is associated with i.e. the filter
-    // which delegates event processing to this touch explorer.
-    private final InputFilter mInputFilter;
-
     // The current state of the touch explorer.
     private int mCurrentState = STATE_TOUCH_EXPLORING;
 
@@ -155,6 +150,9 @@
     // The scaled velocity above which we detect gestures.
     private final int mScaledGestureDetectionVelocity;
 
+    // The handler to which to delegate events.
+    private EventStreamTransformation mNext;
+
     // Helper to track gesture velocity.
     private VelocityTracker mVelocityTracker;
 
@@ -206,12 +204,10 @@
      * @param inputFilter The input filter associated with this explorer.
      * @param context A context handle for accessing resources.
      */
-    public TouchExplorer(InputFilter inputFilter, Context context,
-            AccessibilityManagerService service) {
+    public TouchExplorer(Context context, AccessibilityManagerService service) {
         mAms = service;
         mReceivedPointerTracker = new ReceivedPointerTracker(context);
         mInjectedPointerTracker = new InjectedPointerTracker();
-        mInputFilter = inputFilter;
         mTapTimeout = ViewConfiguration.getTapTimeout();
         mDetermineUserIntentTimeout = (int) (mTapTimeout * 1.5f);
         mDoubleTapTimeout = ViewConfiguration.getDoubleTapTimeout();
@@ -242,7 +238,11 @@
         }
     }
 
-    public void clear(MotionEvent event, int policyFlags) {
+    public void onDestroy() {
+        // TODO: Implement
+    }
+
+    private void clear(MotionEvent event, int policyFlags) {
         switch (mCurrentState) {
             case STATE_TOUCH_EXPLORING: {
                 // If a touch exploration gesture is in progress send events for its end.
@@ -278,8 +278,17 @@
         mLongPressingPointerDeltaX = 0;
         mLongPressingPointerDeltaY = 0;
         mCurrentState = STATE_TOUCH_EXPLORING;
+        if (mNext != null) {
+            mNext.clear();
+        }
     }
 
+    @Override
+    public void setNext(EventStreamTransformation next) {
+        mNext = next;
+    }
+
+    @Override
     public void onMotionEvent(MotionEvent event, int policyFlags) {
         if (DEBUG) {
             Slog.d(LOG_TAG, "Received event: " + event + ", policyFlags=0x"
@@ -325,6 +334,9 @@
                 mLastTouchedWindowId = event.getWindowId();
             } break;
         }
+        if (mNext != null) {
+            mNext.onAccessibilityEvent(event);
+        }
     }
 
     /**
@@ -958,7 +970,9 @@
 
         // Make sure that the user will see the event.
         policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER;
-        mInputFilter.sendInputEvent(event, policyFlags);
+        if (mNext != null) {
+            mNext.onMotionEvent(event, policyFlags);
+        }
 
         mInjectedPointerTracker.onMotionEvent(event);
 
@@ -1008,11 +1022,13 @@
         private MotionEvent mFirstTapEvent;
 
         public void onMotionEvent(MotionEvent event, int policyFlags) {
+            final int actionIndex = event.getActionIndex();
             final int action = event.getActionMasked();
             switch (action) {
                 case MotionEvent.ACTION_DOWN:
                 case MotionEvent.ACTION_POINTER_DOWN: {
-                    if (mFirstTapEvent != null && !isSamePointerContext(mFirstTapEvent, event)) {
+                    if (mFirstTapEvent != null
+                            && !GestureUtils.isSamePointerContext(mFirstTapEvent, event)) {
                         clear();
                     }
                     mDownEvent = MotionEvent.obtain(event);
@@ -1022,19 +1038,21 @@
                     if (mDownEvent == null) {
                         return;
                     }
-                    if (!isSamePointerContext(mDownEvent, event)) {
+                    if (!GestureUtils.isSamePointerContext(mDownEvent, event)) {
                         clear();
                         return;
                     }
-                    if (isTap(mDownEvent, event)) {
-                        if (mFirstTapEvent == null || isTimedOut(mFirstTapEvent, event,
-                                mDoubleTapTimeout)) {
+                    if (GestureUtils.isTap(mDownEvent, event, mTapTimeout, mTouchSlop,
+                            actionIndex)) {
+                        if (mFirstTapEvent == null || GestureUtils.isTimedOut(mFirstTapEvent,
+                                event, mDoubleTapTimeout)) {
                             mFirstTapEvent = MotionEvent.obtain(event);
                             mDownEvent.recycle();
                             mDownEvent = null;
                             return;
                         }
-                        if (isDoubleTap(mFirstTapEvent, event)) {
+                        if (GestureUtils.isMultiTap(mFirstTapEvent, event, mDoubleTapTimeout,
+                                mDoubleTapSlop, actionIndex)) {
                             onDoubleTap(event, policyFlags);
                             mFirstTapEvent.recycle();
                             mFirstTapEvent = null;
@@ -1140,42 +1158,6 @@
             }
         }
 
-        public boolean isTap(MotionEvent down, MotionEvent up) {
-            return eventsWithinTimeoutAndDistance(down, up, mTapTimeout, mTouchSlop);
-        }
-
-        private boolean isDoubleTap(MotionEvent firstUp, MotionEvent secondUp) {
-            return eventsWithinTimeoutAndDistance(firstUp, secondUp, mDoubleTapTimeout,
-                    mDoubleTapSlop);
-        }
-
-        private boolean eventsWithinTimeoutAndDistance(MotionEvent first, MotionEvent second,
-                int timeout, int distance) {
-            if (isTimedOut(first, second, timeout)) {
-                return false;
-            }
-            final int downPtrIndex = first.getActionIndex();
-            final int upPtrIndex = second.getActionIndex();
-            final float deltaX = second.getX(upPtrIndex) - first.getX(downPtrIndex);
-            final float deltaY = second.getY(upPtrIndex) - first.getY(downPtrIndex);
-            final double deltaMove = Math.hypot(deltaX, deltaY);
-            if (deltaMove >= distance) {
-                return false;
-            }
-            return true;
-        }
-
-        private boolean isTimedOut(MotionEvent firstUp, MotionEvent secondUp, int timeout) {
-            final long deltaTime = secondUp.getEventTime() - firstUp.getEventTime();
-            return (deltaTime >= timeout);
-        }
-
-        private boolean isSamePointerContext(MotionEvent first, MotionEvent second) {
-            return (first.getPointerIdBits() == second.getPointerIdBits()
-                    && first.getPointerId(first.getActionIndex())
-                            == second.getPointerId(second.getActionIndex()));
-        }
-
         public boolean firstTapDetected() {
             return mFirstTapEvent != null
                 && SystemClock.uptimeMillis() - mFirstTapEvent.getEventTime() < mDoubleTapTimeout;
@@ -1201,47 +1183,14 @@
         final float secondPtrX = event.getX(secondPtrIndex);
         final float secondPtrY = event.getY(secondPtrIndex);
 
-        // Check if the pointers are moving in the same direction.
-        final float firstDeltaX =
-            firstPtrX - receivedTracker.getReceivedPointerDownX(firstPtrIndex);
-        final float firstDeltaY =
-            firstPtrY - receivedTracker.getReceivedPointerDownY(firstPtrIndex);
+        final float firstPtrDownX = receivedTracker.getReceivedPointerDownX(firstPtrIndex);
+        final float firstPtrDownY = receivedTracker.getReceivedPointerDownY(firstPtrIndex);
+        final float secondPtrDownX = receivedTracker.getReceivedPointerDownX(secondPtrIndex);
+        final float secondPtrDownY = receivedTracker.getReceivedPointerDownY(secondPtrIndex);
 
-        if (firstDeltaX == 0 && firstDeltaY == 0) {
-            return true;
-        }
-
-        final float firstMagnitude =
-            (float) Math.sqrt(firstDeltaX * firstDeltaX + firstDeltaY * firstDeltaY);
-        final float firstXNormalized =
-            (firstMagnitude > 0) ? firstDeltaX / firstMagnitude : firstDeltaX;
-        final float firstYNormalized =
-            (firstMagnitude > 0) ? firstDeltaY / firstMagnitude : firstDeltaY;
-
-        final float secondDeltaX =
-            secondPtrX - receivedTracker.getReceivedPointerDownX(secondPtrIndex);
-        final float secondDeltaY =
-            secondPtrY - receivedTracker.getReceivedPointerDownY(secondPtrIndex);
-
-        if (secondDeltaX == 0 && secondDeltaY == 0) {
-            return true;
-        }
-
-        final float secondMagnitude =
-            (float) Math.sqrt(secondDeltaX * secondDeltaX + secondDeltaY * secondDeltaY);
-        final float secondXNormalized =
-            (secondMagnitude > 0) ? secondDeltaX / secondMagnitude : secondDeltaX;
-        final float secondYNormalized =
-            (secondMagnitude > 0) ? secondDeltaY / secondMagnitude : secondDeltaY;
-
-        final float angleCos =
-            firstXNormalized * secondXNormalized + firstYNormalized * secondYNormalized;
-
-        if (angleCos < MAX_DRAGGING_ANGLE_COS) {
-            return false;
-        }
-
-        return true;
+        return GestureUtils.isDraggingGesture(firstPtrDownX, firstPtrDownY, secondPtrDownX,
+                secondPtrDownY, firstPtrX, firstPtrY, secondPtrX, secondPtrY,
+                MAX_DRAGGING_ANGLE_COS);
     }
 
     /**
diff --git a/services/java/com/android/server/net/LockdownVpnTracker.java b/services/java/com/android/server/net/LockdownVpnTracker.java
index dabcf2f..f2d6745 100644
--- a/services/java/com/android/server/net/LockdownVpnTracker.java
+++ b/services/java/com/android/server/net/LockdownVpnTracker.java
@@ -41,6 +41,7 @@
 import com.android.internal.net.VpnProfile;
 import com.android.internal.util.Preconditions;
 import com.android.server.ConnectivityService;
+import com.android.server.EventLogTags;
 import com.android.server.connectivity.Vpn;
 
 /**
@@ -122,12 +123,18 @@
         }
         if (egressDisconnected) return;
 
+        final int egressType = egressInfo.getType();
+        if (vpnInfo.getDetailedState() == DetailedState.FAILED) {
+            EventLogTags.writeLockdownVpnError(egressType);
+        }
+
         if (mErrorCount > MAX_ERROR_COUNT) {
             showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
 
         } else if (egressInfo.isConnected() && !vpnInfo.isConnectedOrConnecting()) {
             if (mProfile.isValidLockdownProfile()) {
                 Slog.d(TAG, "Active network connected; starting VPN");
+                EventLogTags.writeLockdownVpnConnecting(egressType);
                 showNotification(R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected);
 
                 mAcceptedEgressIface = egressProp.getInterfaceName();
@@ -148,6 +155,7 @@
             }
 
             Slog.d(TAG, "VPN connected using iface=" + iface + ", sourceAddr=" + sourceAddr);
+            EventLogTags.writeLockdownVpnConnected(egressType);
             showNotification(R.string.vpn_lockdown_connected, R.drawable.vpn_connected);
 
             try {
diff --git a/services/java/com/android/server/wm/DisplayContent.java b/services/java/com/android/server/wm/DisplayContent.java
index 452ca4f..0ea051f 100644
--- a/services/java/com/android/server/wm/DisplayContent.java
+++ b/services/java/com/android/server/wm/DisplayContent.java
@@ -16,8 +16,10 @@
 
 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;
@@ -41,6 +43,11 @@
      * 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
@@ -114,6 +121,7 @@
         pw.print("-"); pw.print(mDisplayInfo.largestNominalAppWidth);
         pw.print("x"); pw.println(mDisplayInfo.largestNominalAppHeight);
         pw.print("  layoutNeeded="); pw.println(layoutNeeded);
+        pw.print("magnificationSpec="); pw.println(mMagnificationSpec.toString());
         pw.println();
     }
 }
diff --git a/services/java/com/android/server/wm/MagnificationSpec.java b/services/java/com/android/server/wm/MagnificationSpec.java
new file mode 100644
index 0000000..31aae66
--- /dev/null
+++ b/services/java/com/android/server/wm/MagnificationSpec.java
@@ -0,0 +1,46 @@
+/*
+ * 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/Session.java b/services/java/com/android/server/wm/Session.java
index 4038b70..16beeab 100644
--- a/services/java/com/android/server/wm/Session.java
+++ b/services/java/com/android/server/wm/Session.java
@@ -422,6 +422,17 @@
         }
     }
 
+    public void onRectangleOnScreenRequested(IBinder token, Rect rectangle, boolean immediate) {
+        synchronized(mService.mWindowMap) {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                mService.onRectangleOnScreenRequested(token, rectangle, immediate);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+
     void windowAddedLocked() {
         if (mSurfaceSession == null) {
             if (WindowManagerService.localLOGV) Slog.v(
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index bfd949b..9a0d280 100755
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -86,6 +86,7 @@
 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;
@@ -108,6 +109,7 @@
 import android.view.DisplayInfo;
 import android.view.Gravity;
 import android.view.IApplicationToken;
+import android.view.IDisplayContentChangeListener;
 import android.view.IInputFilter;
 import android.view.IOnKeyguardExitResult;
 import android.view.IRotationWatcher;
@@ -124,6 +126,7 @@
 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;
@@ -716,6 +719,9 @@
      */
     boolean mInTouchMode = true;
 
+    // Temp regions for intermediary calculations.
+    private final Region mTempRegion = new Region();
+
     private ViewServer mViewServer;
     private ArrayList<WindowChangeListener> mWindowChangeListeners =
         new ArrayList<WindowChangeListener>();
@@ -2325,6 +2331,7 @@
                 if (win.mWinAnimator.applyAnimationLocked(transit, false)) {
                     win.mExiting = true;
                 }
+                scheduleNotifyWindowTranstionIfNeededLocked(win, transit);
             }
             if (win.mExiting || win.mWinAnimator.isAnimating()) {
                 // The exit animation is running... wait for it!
@@ -2613,6 +2620,54 @@
         performLayoutAndPlaceSurfacesLocked();
     }
 
+    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 = getDisplayContent(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 */
+                }
+            }
+        } finally {
+            callbacks.finishBroadcast();
+        }
+    }
+
     public int relayoutWindow(Session session, IWindow client, int seq,
             WindowManager.LayoutParams attrs, int requestedWidth,
             int requestedHeight, int viewVisibility, int flags,
@@ -2842,6 +2897,7 @@
                             }
                             winAnimator.destroySurfaceLocked();
                         }
+                        scheduleNotifyWindowTranstionIfNeededLocked(win, transit);
                     }
                 }
 
@@ -2987,6 +3043,97 @@
         }
     }
 
+    @Override
+    public WindowInfo getWindowInfo(IBinder token) {
+        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);
+            }
+            return null;
+        }
+    }
+
+    @Override
+    public void getVisibleWindowsForDisplay(int displayId, List<WindowInfo> outInfos) {
+        if (!checkCallingPermission(android.Manifest.permission.RETRIEVE_WINDOW_INFO,
+                "getWindowInfos()")) {
+            throw new SecurityException("Requires RETRIEVE_WINDOW_INFO permission.");
+        }
+        synchronized (mWindowMap) {
+            DisplayContent displayContent = getDisplayContent(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 == WindowManager.LayoutParams.TYPE_UNIVERSE_BACKGROUND) {
+                    WindowInfo info = getWindowInfoForWindowStateLocked(window);
+                    outInfos.add(info);
+                }
+            }
+        }
+    }
+
+    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 = getDisplayContent(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 == WindowManager.LayoutParams.TYPE_UNIVERSE_BACKGROUND;
+        window.getTouchableRegion(mTempRegion);
+        mTempRegion.getBounds(info.touchableRegion);
+        return info;
+    }
+
     private AttributeCache.Entry getCachedAnimations(WindowManager.LayoutParams lp) {
         if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: layout params pkg="
                 + (lp != null ? lp.packageName : null)
@@ -3481,6 +3628,8 @@
                         if (win.isVisibleNow()) {
                             win.mWinAnimator.applyAnimationLocked(WindowManagerPolicy.TRANSIT_EXIT,
                                     false);
+                            scheduleNotifyWindowTranstionIfNeededLocked(win,
+                                    WindowManagerPolicy.TRANSIT_EXIT);
                             changed = true;
                             win.mDisplayContent.layoutNeeded = true;
                         }
@@ -4254,6 +4403,10 @@
                 if (applyAnimationLocked(wtoken, lp, transit, visible)) {
                     delayed = runningAppAnimation = true;
                 }
+                WindowState window = wtoken.findMainWindow();
+                if (window != null) {
+                    scheduleNotifyWindowTranstionIfNeededLocked(window, transit);
+                }
                 changed = true;
             }
 
@@ -4271,6 +4424,8 @@
                         if (!runningAppAnimation) {
                             win.mWinAnimator.applyAnimationLocked(
                                     WindowManagerPolicy.TRANSIT_ENTER, true);
+                            scheduleNotifyWindowTranstionIfNeededLocked(win,
+                                    WindowManagerPolicy.TRANSIT_ENTER);
                         }
                         changed = true;
                         win.mDisplayContent.layoutNeeded = true;
@@ -4279,6 +4434,8 @@
                     if (!runningAppAnimation) {
                         win.mWinAnimator.applyAnimationLocked(
                                 WindowManagerPolicy.TRANSIT_EXIT, false);
+                        scheduleNotifyWindowTranstionIfNeededLocked(win,
+                                WindowManagerPolicy.TRANSIT_EXIT);
                     }
                     changed = true;
                     win.mDisplayContent.layoutNeeded = true;
@@ -5818,12 +5975,16 @@
                 mInnerFields.mOrientationChangeComplete = false;
             }
         }
+
         for (int i=mRotationWatchers.size()-1; i>=0; i--) {
             try {
                 mRotationWatchers.get(i).onRotationChanged(rotation);
             } catch (RemoteException e) {
             }
         }
+
+        scheduleNotifyRotationChangedIfNeededLocked(displayContent, rotation);
+
         return true;
     }
 
@@ -6204,6 +6365,110 @@
         return success;
     }
 
+    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 = getDisplayContent(displayId);
+            if (displayContent.mDisplayContentChangeListeners == null) {
+                displayContent.mDisplayContentChangeListeners =
+                        new RemoteCallbackList<IDisplayContentChangeListener>();
+            displayContent.mDisplayContentChangeListeners.register(listener);
+            }
+        }
+    }
+
+    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 = getDisplayContent(displayId);
+            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 = getDisplayContent(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 = getDisplayContent(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();
+        }
+    }
+
     public void addWindowChangeListener(WindowChangeListener listener) {
         synchronized(mWindowMap) {
             mWindowChangeListeners.add(listener);
@@ -6767,21 +7032,6 @@
         }
     }
 
-    public boolean getWindowFrame(IBinder token, Rect outBounds) {
-        if (!checkCallingPermission(android.Manifest.permission.RETRIEVE_WINDOW_INFO,
-                "getWindowFrame()")) {
-            throw new SecurityException("Requires RETRIEVE_WINDOW_INFO permission.");
-        }
-        synchronized (mWindowMap) {
-            WindowState windowState = mWindowMap.get(token);
-            if (windowState != null) {
-                outBounds.set(windowState.getFrameLw());
-                return true;
-            }
-        }
-        return false;
-    }
-
     private WindowState getFocusedWindow() {
         synchronized (mWindowMap) {
             return getFocusedWindowLocked();
@@ -6929,6 +7179,9 @@
         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 ANIMATOR_WHAT_OFFSET = 100000;
         public static final int SET_TRANSPARENT_REGION = ANIMATOR_WHAT_OFFSET + 1;
@@ -7367,6 +7620,25 @@
                     }
                     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;
+                }
             }
             if (DEBUG_WINDOW_TRACE) {
                 Slog.v(TAG, "handleMessage: exit");
diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java
index bb8d85a..478475d 100644
--- a/services/java/com/android/server/wm/WindowState.java
+++ b/services/java/com/android/server/wm/WindowState.java
@@ -514,6 +514,21 @@
         }
     }
 
+    MagnificationSpec getWindowMagnificationSpecLocked() {
+        MagnificationSpec spec = mDisplayContent.mMagnificationSpec;
+        if (spec != null && !spec.isNop()) {
+            if (mAttachedWindow != null) {
+                if (!mPolicy.canMagnifyWindow(mAttachedWindow.mAttrs)) {
+                    return null;
+                }
+            }
+            if (!mPolicy.canMagnifyWindow(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 2b3c87d..8912c73 100644
--- a/services/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/java/com/android/server/wm/WindowStateAnimator.java
@@ -3,7 +3,6 @@
 package com.android.server.wm;
 
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
-
 import static com.android.server.wm.WindowManagerService.LayoutFields.SET_ORIENTATION_CHANGE_COMPLETE;
 import static com.android.server.wm.WindowManagerService.LayoutFields.SET_TURN_ON_SCREEN;
 
@@ -887,6 +886,11 @@
                 tmpMatrix.postConcat(
                         mService.mAnimator.mScreenRotationAnimation.getEnterTransformation().getMatrix());
             }
+            MagnificationSpec spec = mWin.getWindowMagnificationSpecLocked();
+            if (spec != null && !spec.isNop()) {
+                tmpMatrix.postScale(spec.mScale, spec.mScale);
+                tmpMatrix.postTranslate(spec.mOffsetX, spec.mOffsetY);
+            }
 
             // "convert" it into SurfaceFlinger's format
             // (a 2x2 matrix + an offset)
@@ -954,16 +958,30 @@
         if (WindowManagerService.localLOGV) Slog.v(
                 TAG, "computeShownFrameLocked: " + this +
                 " not attached, mAlpha=" + mAlpha);
-        if (mAnimator.mUniverseBackground != null &&
-                mWin.mAttrs.type != WindowManager.LayoutParams.TYPE_UNIVERSE_BACKGROUND
-                && mWin.mBaseLayer < mAnimator.mAboveUniverseLayer) {
+
+        final boolean applyUniverseTransformation = (mAnimator.mUniverseBackground != null
+                && mWin.mAttrs.type != WindowManager.LayoutParams.TYPE_UNIVERSE_BACKGROUND
+                && mWin.mBaseLayer < mAnimator.mAboveUniverseLayer);
+        MagnificationSpec spec = mWin.getWindowMagnificationSpecLocked();
+        if (applyUniverseTransformation || spec != null) {
             final Rect frame = mWin.mFrame;
             final float tmpFloats[] = mService.mTmpFloats;
             final Matrix tmpMatrix = mWin.mTmpMatrix;
+
             tmpMatrix.setScale(mWin.mGlobalScale, mWin.mGlobalScale);
             tmpMatrix.postTranslate(frame.left + mWin.mXOffset, frame.top + mWin.mYOffset);
-            tmpMatrix.postConcat(mAnimator.mUniverseBackground.mUniverseTransform.getMatrix());
+
+            if (applyUniverseTransformation) {
+                tmpMatrix.postConcat(mAnimator.mUniverseBackground.mUniverseTransform.getMatrix());
+            }
+
+            if (spec != null && !spec.isNop()) {
+                tmpMatrix.postScale(spec.mScale, spec.mScale);
+                tmpMatrix.postTranslate(spec.mOffsetX, spec.mOffsetY);
+            }
+
             tmpMatrix.getValues(tmpFloats);
+
             mHaveMatrix = true;
             mDsDx = tmpFloats[Matrix.MSCALE_X];
             mDtDx = tmpFloats[Matrix.MSKEW_Y];
@@ -973,8 +991,12 @@
             float y = tmpFloats[Matrix.MTRANS_Y];
             int w = frame.width();
             int h = frame.height();
-            mWin.mShownFrame.set(x, y, x+w, y+h);
-            mShownAlpha = mAlpha * mAnimator.mUniverseBackground.mUniverseTransform.getAlpha();
+            mWin.mShownFrame.set(x, y, x + w, y + h);
+
+            mShownAlpha = mAlpha;
+            if (applyUniverseTransformation) {
+                mShownAlpha *= mAnimator.mUniverseBackground.mUniverseTransform.getAlpha();
+            }
         } else {
             mWin.mShownFrame.set(mWin.mFrame);
             if (mWin.mXOffset != 0 || mWin.mYOffset != 0) {
@@ -1435,8 +1457,8 @@
         } else {
             transit = WindowManagerPolicy.TRANSIT_SHOW;
         }
-
         applyAnimationLocked(transit, true);
+        mService.scheduleNotifyWindowTranstionIfNeededLocked(mWin, transit);
     }
 
     // TODO(cmautner): Move back to WindowState?
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowManager.java
index 5516339..5d64e8a 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowManager.java
@@ -22,22 +22,21 @@
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
-import android.graphics.Point;
-import android.graphics.Rect;
 import android.os.IBinder;
 import android.os.IRemoteCallback;
 import android.os.RemoteException;
 import android.util.DisplayMetrics;
 import android.view.Display;
-import android.view.DisplayInfo;
 import android.view.Display_Delegate;
 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;
 
@@ -293,7 +292,6 @@
     @Override
     public void setAppOrientation(IApplicationToken arg0, int arg1) throws RemoteException {
         // TODO Auto-generated method stub
-
     }
 
     @Override
@@ -301,7 +299,6 @@
             CharSequence arg4, int arg5, int arg6, int arg7, IBinder arg8, boolean arg9)
             throws RemoteException {
         // TODO Auto-generated method stub
-
     }
 
     @Override
@@ -313,19 +310,16 @@
     @Override
     public void setAppWillBeHidden(IBinder arg0) throws RemoteException {
         // TODO Auto-generated method stub
-
     }
 
     @Override
     public void setEventDispatching(boolean arg0) throws RemoteException {
         // TODO Auto-generated method stub
-
     }
 
     @Override
     public void setFocusedApp(IBinder arg0, boolean arg1) throws RemoteException {
         // TODO Auto-generated method stub
-
     }
 
     @Override
@@ -341,13 +335,11 @@
     @Override
     public void setInTouchMode(boolean arg0) throws RemoteException {
         // TODO Auto-generated method stub
-
     }
 
     @Override
     public void setNewConfiguration(Configuration arg0) throws RemoteException {
         // TODO Auto-generated method stub
-
     }
 
     @Override
@@ -358,19 +350,16 @@
     @Override
     public void setStrictModeVisualIndicatorPreference(String arg0) throws RemoteException {
         // TODO Auto-generated method stub
-
     }
 
     @Override
     public void showStrictModeViolation(boolean arg0) throws RemoteException {
         // TODO Auto-generated method stub
-
     }
 
     @Override
     public void startAppFreezingScreen(IBinder arg0, int arg1) throws RemoteException {
         // TODO Auto-generated method stub
-
     }
 
     @Override
@@ -382,13 +371,11 @@
     @Override
     public void statusBarVisibilityChanged(int arg0) throws RemoteException {
         // TODO Auto-generated method stub
-
     }
 
     @Override
     public void stopAppFreezingScreen(IBinder arg0, boolean arg1) throws RemoteException {
         // TODO Auto-generated method stub
-
     }
 
     @Override
@@ -400,7 +387,6 @@
     @Override
     public void thawRotation() throws RemoteException {
         // TODO Auto-generated method stub
-
     }
 
     @Override
@@ -453,12 +439,6 @@
     }
 
     @Override
-    public boolean getWindowFrame(IBinder token, Rect outBounds) {
-        // TODO Auto-generated method stub
-        return false;
-    }
-
-    @Override
     public float getWindowCompatibilityScale(IBinder windowToken) throws RemoteException {
         // TODO Auto-generated method stub
         return 0;
@@ -468,4 +448,34 @@
     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 {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public void getVisibleWindowsForDisplay(int displayId, List<WindowInfo> outInfos)
+            throws RemoteException {
+        // TODO Auto-generated method stub
+    }
 }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
index 691eca7..67b0a9c 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
@@ -194,4 +194,9 @@
         // pass for now.
         return null;
     }
+
+    @Override
+    public void onRectangleOnScreenRequested(IBinder window, Rect rectangle, boolean immediate) {
+        // pass for now.
+    }
 }