summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSvetoslav Ganov <svetoslavganov@google.com>2012-08-06 10:53:34 -0700
committerSvetoslav Ganov <svetoslavganov@google.com>2012-09-06 18:56:17 -0700
commit1cf70bbf96930662cab0e699d70b62865766ff52 (patch)
tree2173762d4e4d7be76f5691cebd74e1bd8f2b8543
parentfa8d83d90444354e8eca6ca0e080bc917e5a1f32 (diff)
downloadframeworks_base-1cf70bbf96930662cab0e699d70b62865766ff52.zip
frameworks_base-1cf70bbf96930662cab0e699d70b62865766ff52.tar.gz
frameworks_base-1cf70bbf96930662cab0e699d70b62865766ff52.tar.bz2
Screen magnification - feature - framework.
This change is the initial check in of the screen magnification feature. This feature enables magnification of the screen via global gestures (assuming it has been enabled from settings) to allow a low vision user to efficiently use an Android device. Interaction model: 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. Note: Since two fingers are used to pan the content in a permanently magnified state no other two finger gestures in touch exploration or applications will work unless the uses zooms out to normal state where all gestures works as expected. This is an intentional tradeoff to allow efficient panning since in a permanently magnified state this would be the dominant action to be performed. Design: 1. The window manager exposes APIs for setting accessibility transformation which is a scale and offsets for X and Y axis. The window manager queries the window policy for which windows will not be magnified. For example, the IME windows and the navigation bar are not magnified including windows that are attached to them. 2. The accessibility features such a screen magnification and touch exploration are now impemented as a sequence of transformations on the event stream. The accessibility manager service may request each of these features or both. The behavior of the features is not changed based on the fact that another one is enabled. 3. The screen magnifier keeps a viewport of the content that is magnified which is surrounded by a glow in a magnified state. Interactions outside of the viewport are delegated directly to the application without interpretation. For example, a triple tap on the letter 'a' of the IME would type three letters instead of toggling magnified state. The viewport is updated on screen rotation and on window transitions. For example, when the IME pops up the viewport shrinks. 4. The glow around the viewport is implemented as a special type of window that does not take input focus, cannot be touched, is laid out in the screen coordiates with width and height matching these of the screen. When the magnified region changes the root view of the window draws the hightlight but the size of the window does not change - unless a rotation happens. All changes in the viewport size or showing or hiding it are animated. 5. The viewport is encapsulated in a class that knows how to show, hide, and resize the viewport - potentially animating that. This class uses the new animation framework for animations. 6. The magnification is handled by a magnification controller that keeps track of the current trnasformation to be applied to the screen content and the desired such. If these two are not the same it is responsibility of the magnification controller to reconcile them by potentially animating the transition from one to the other. 7. A dipslay content observer wathces for winodw transitions, screen rotations, and when a rectange on the screen has been reqeusted. This class is responsible for handling interesting state changes such as changing the viewport bounds on IME pop up or screen rotation, panning the content to make a requested rectangle visible on the screen, etc. 8. To implement viewport updates the window manger was updated with APIs to watch for window transitions and when a rectangle has been requested on the screen. These APIs are protected by a signature level permission. Also a parcelable and poolable window info class has been added with APIs for getting the window info given the window token. This enables getting some useful information about a window. There APIs are also signature protected. bug:6795382 Change-Id: Iec93da8bf6376beebbd4f5167ab7723dc7d9bd00
-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.
+ }
}