summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSvetoslav Ganov <svetoslavganov@google.com>2012-09-06 18:57:43 -0700
committerAndroid (Google) Code Review <android-gerrit@google.com>2012-09-06 18:57:43 -0700
commit6da5827193408ec6d2a77b64448b2f1c40b9a482 (patch)
tree840718c7f3d92ffe7239d18de3467bfb76899108
parentc71d4060609ee64939ad5369d33b86d44baa17a9 (diff)
parent1cf70bbf96930662cab0e699d70b62865766ff52 (diff)
downloadframeworks_base-6da5827193408ec6d2a77b64448b2f1c40b9a482.zip
frameworks_base-6da5827193408ec6d2a77b64448b2f1c40b9a482.tar.gz
frameworks_base-6da5827193408ec6d2a77b64448b2f1c40b9a482.tar.bz2
Merge "Screen magnification - feature - framework." into jb-mr1-dev
-rw-r--r--Android.mk1
-rw-r--r--api/current.txt1
-rw-r--r--core/java/android/os/RemoteCallbackList.java21
-rw-r--r--core/java/android/provider/Settings.java43
-rw-r--r--core/java/android/view/IDisplayContentChangeListener.aidl32
-rw-r--r--core/java/android/view/IWindowManager.aidl34
-rw-r--r--core/java/android/view/IWindowSession.aidl5
-rw-r--r--core/java/android/view/View.java5
-rw-r--r--core/java/android/view/ViewRootImpl.java16
-rw-r--r--core/java/android/view/WindowInfo.aidl20
-rw-r--r--core/java/android/view/WindowInfo.java153
-rw-r--r--core/java/android/view/WindowManager.java10
-rw-r--r--core/java/android/view/WindowManagerPolicy.java8
-rw-r--r--core/res/AndroidManifest.xml6
-rw-r--r--core/res/res/drawable-nodpi/magnified_region_frame.9.pngbin0 -> 741 bytes
-rw-r--r--core/res/res/values/public.xml1
-rwxr-xr-xcore/res/res/values/strings.xml11
-rw-r--r--packages/SettingsProvider/res/values/defaults.xml9
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java39
-rwxr-xr-xpolicy/src/com/android/internal/policy/impl/PhoneWindowManager.java30
-rw-r--r--services/java/com/android/server/accessibility/AccessibilityInputFilter.java178
-rw-r--r--services/java/com/android/server/accessibility/AccessibilityManagerService.java160
-rw-r--r--services/java/com/android/server/accessibility/EventStreamTransformation.java90
-rw-r--r--services/java/com/android/server/accessibility/GestureUtils.java102
-rw-r--r--services/java/com/android/server/accessibility/ScreenMagnifier.java1754
-rw-r--r--services/java/com/android/server/accessibility/TouchExplorer.java135
-rw-r--r--services/java/com/android/server/wm/DisplayContent.java8
-rw-r--r--services/java/com/android/server/wm/MagnificationSpec.java46
-rw-r--r--services/java/com/android/server/wm/Session.java11
-rwxr-xr-xservices/java/com/android/server/wm/WindowManagerService.java302
-rw-r--r--services/java/com/android/server/wm/WindowState.java15
-rw-r--r--services/java/com/android/server/wm/WindowStateAnimator.java38
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowManager.java57
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java5
34 files changed, 3086 insertions, 260 deletions
diff --git a/Android.mk b/Android.mk
index 07500b1..cb1b90b 100644
--- a/Android.mk
+++ b/Android.mk
@@ -153,6 +153,7 @@ LOCAL_SRC_FILES += \
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 0f5fd35..4fab38b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -16418,6 +16418,7 @@ package android.os {
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 @@ public class RemoteCallbackList<E extends IInterface> {
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 @@ public final class Settings {
"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 @@ public final class Settings {
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.Point;
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.InputEvent;
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 @@ interface IWindowManager
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 @@ interface IWindowManager
* 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 @@ interface IWindowSession {
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..e5d72ab 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -6259,6 +6259,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
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();
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 @@ public final class ViewRootImpl implements ViewParent,
// 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 @@ public interface WindowManager extends ViewManager {
@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 interface WindowManager extends ViewManager {
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 interface WindowManagerPolicy {
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/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/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 565d2f8..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..41bbe6ef 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 @@ public class DatabaseHelper extends SQLiteOpenHelper {
// 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;
@@ -1229,6 +1229,33 @@ public class DatabaseHelper extends SQLiteOpenHelper {
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) {
@@ -1776,6 +1803,16 @@ public class DatabaseHelper extends SQLiteOpenHelper {
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.EventLog;
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_DREAM;
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 @@ public class PhoneWindowManager implements WindowManagerPolicy {
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 @@ public class PhoneWindowManager implements WindowManagerPolicy {
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 @@ public class PhoneWindowManager implements WindowManagerPolicy {
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/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.MotionEvent;
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);
-
- /**
- * 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);
-
- /**
- * Requests that the explorer clears its internal state.
- */
- public void clear();
- }
+ private int mCurrentDeviceId;
- private TouchExplorer mTouchExplorer;
+ private boolean mInstalled;
+
+ private int mEnabledFeatures;
- private int mTouchscreenSourceDeviceId;
+ private TouchExplorer mTouchExplorer;
+ private ScreenMagnifier mScreenMagnifier;
+ private EventStreamTransformation mEventHandler;
- 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 @@ public class AccessibilityInputFilter extends InputFilter {
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 @@ public class AccessibilityInputFilter extends InputFilter {
if (DEBUG) {
Slog.d(TAG, "Accessibility input filter uninstalled.");
}
- mTouchExplorer.clear();
+ mInstalled = false;
+ disableFeatures();
super.onUninstalled();
}
@@ -103,27 +98,104 @@ public class AccessibilityInputFilter extends InputFilter {
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 (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();
}
- if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) != 0) {
- mPm.userActivity(event.getEventTime(), false);
- mTouchExplorer.onMotionEvent(motionEvent, policyFlags);
+ 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();
+ }
+ }
+
+ 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 {
- mTouchExplorer.clear(motionEvent, policyFlags);
+ mEventHandler = mTouchExplorer;
}
- } else {
- super.onInputEvent(event, policyFlags);
}
}
- public void onAccessibilityEvent(AccessibilityEvent event) {
+ private void disableFeatures() {
if (mTouchExplorer != null) {
- mTouchExplorer.onAccessibilityEvent(event);
+ 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.Build;
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.IWindowManager;
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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
synchronized (mLock) {
// We will update when the automation service dies.
if (mUiAutomationService == null) {
- populateAccessibilityServiceListLocked();
+ populateInstalledAccessibilityServiceLocked();
manageServicesLocked();
}
}
@@ -262,18 +268,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
});
+ 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
/**
* 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
/**
- * 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
}
- 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
// 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.graphics.Rect;
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 @@ import java.util.Arrays;
*
* @hide
*/
-public class TouchExplorer {
+class TouchExplorer implements EventStreamTransformation {
private static final boolean DEBUG = false;
@@ -120,10 +119,6 @@ public class TouchExplorer {
// 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 @@ public class TouchExplorer {
// 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 @@ public class TouchExplorer {
* @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 class TouchExplorer {
}
}
- 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 @@ public class TouchExplorer {
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 @@ public class TouchExplorer {
mLastTouchedWindowId = event.getWindowId();
} break;
}
+ if (mNext != null) {
+ mNext.onAccessibilityEvent(event);
+ }
}
/**
@@ -958,7 +970,9 @@ public class TouchExplorer {
// 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 @@ public class TouchExplorer {
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 @@ public class TouchExplorer {
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 class TouchExplorer {
}
}
- 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 @@ public class TouchExplorer {
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);
-
- 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;
- }
+ final float firstPtrDownX = receivedTracker.getReceivedPointerDownX(firstPtrIndex);
+ final float firstPtrDownY = receivedTracker.getReceivedPointerDownY(firstPtrIndex);
+ final float secondPtrDownX = receivedTracker.getReceivedPointerDownX(secondPtrIndex);
+ final float secondPtrDownY = receivedTracker.getReceivedPointerDownY(secondPtrIndex);
- 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/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 @@ class DisplayContent {
* 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 @@ class DisplayContent {
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 @@ final class Session extends IWindowSession.Stub
}
}
+ 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.Parcel;
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.Display;
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.Surface;
import android.view.SurfaceSession;
import android.view.View;
import android.view.ViewTreeObserver;
+import android.view.WindowInfo;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.WindowManagerPolicy;
@@ -716,6 +719,9 @@ public class WindowManagerService extends IWindowManager.Stub
*/
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 @@ public class WindowManagerService extends IWindowManager.Stub
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 @@ public class WindowManagerService extends IWindowManager.Stub
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 @@ public class WindowManagerService extends IWindowManager.Stub
}
winAnimator.destroySurfaceLocked();
}
+ scheduleNotifyWindowTranstionIfNeededLocked(win, transit);
}
}
@@ -2987,6 +3043,97 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ @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 @@ public class WindowManagerService extends IWindowManager.Stub
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 @@ public class WindowManagerService extends IWindowManager.Stub
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 @@ public class WindowManagerService extends IWindowManager.Stub
if (!runningAppAnimation) {
win.mWinAnimator.applyAnimationLocked(
WindowManagerPolicy.TRANSIT_ENTER, true);
+ scheduleNotifyWindowTranstionIfNeededLocked(win,
+ WindowManagerPolicy.TRANSIT_ENTER);
}
changed = true;
win.mDisplayContent.layoutNeeded = true;
@@ -4279,6 +4434,8 @@ public class WindowManagerService extends IWindowManager.Stub
if (!runningAppAnimation) {
win.mWinAnimator.applyAnimationLocked(
WindowManagerPolicy.TRANSIT_EXIT, false);
+ scheduleNotifyWindowTranstionIfNeededLocked(win,
+ WindowManagerPolicy.TRANSIT_EXIT);
}
changed = true;
win.mDisplayContent.layoutNeeded = true;
@@ -5818,12 +5975,16 @@ public class WindowManagerService extends IWindowManager.Stub
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 @@ public class WindowManagerService extends IWindowManager.Stub
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 class WindowManagerService extends IWindowManager.Stub
}
}
- 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 class WindowManagerService extends IWindowManager.Stub
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 @@ public class WindowManagerService extends IWindowManager.Stub
}
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 @@ final class WindowState implements WindowManagerPolicy.WindowState {
}
}
+ 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 @@ class WindowStateAnimator {
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 @@ class WindowStateAnimator {
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 @@ class WindowStateAnimator {
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 @@ class WindowStateAnimator {
} 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..41fbc7c 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 com.android.internal.view.IInputMethodClient;
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 @@ public class BridgeWindowManager implements IWindowManager {
@Override
public void setAppOrientation(IApplicationToken arg0, int arg1) throws RemoteException {
// TODO Auto-generated method stub
-
}
@Override
@@ -301,7 +299,6 @@ public class BridgeWindowManager implements IWindowManager {
CharSequence arg4, int arg5, int arg6, int arg7, IBinder arg8, boolean arg9)
throws RemoteException {
// TODO Auto-generated method stub
-
}
@Override
@@ -313,19 +310,16 @@ public class BridgeWindowManager implements IWindowManager {
@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 @@ public class BridgeWindowManager implements IWindowManager {
@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 @@ public class BridgeWindowManager implements IWindowManager {
@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 @@ public class BridgeWindowManager implements IWindowManager {
@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 @@ public class BridgeWindowManager implements IWindowManager {
@Override
public void thawRotation() throws RemoteException {
// TODO Auto-generated method stub
-
}
@Override
@@ -453,19 +439,48 @@ public class BridgeWindowManager implements IWindowManager {
}
@Override
- public boolean getWindowFrame(IBinder token, Rect outBounds) {
+ public float getWindowCompatibilityScale(IBinder windowToken) throws RemoteException {
// TODO Auto-generated method stub
- return false;
+ return 0;
}
@Override
- public float getWindowCompatibilityScale(IBinder windowToken) throws RemoteException {
+ public void setInputFilter(IInputFilter filter) throws RemoteException {
// TODO Auto-generated method stub
- return 0;
}
@Override
- public void setInputFilter(IInputFilter filter) throws RemoteException {
+ public void stopWatchRotation(IRotationWatcher watcher) 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
+ }
+
+ @Override
+ public void magnifyDisplay(int dipslayId, float scale, float offsetX, float offsetY)
+ 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 @@ public final class BridgeWindowSession implements IWindowSession {
// pass for now.
return null;
}
+
+ @Override
+ public void onRectangleOnScreenRequested(IBinder window, Rect rectangle, boolean immediate) {
+ // pass for now.
+ }
}