diff options
author | Svetoslav Ganov <svetoslavganov@google.com> | 2012-09-06 18:57:43 -0700 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2012-09-06 18:57:43 -0700 |
commit | 6da5827193408ec6d2a77b64448b2f1c40b9a482 (patch) | |
tree | 840718c7f3d92ffe7239d18de3467bfb76899108 | |
parent | c71d4060609ee64939ad5369d33b86d44baa17a9 (diff) | |
parent | 1cf70bbf96930662cab0e699d70b62865766ff52 (diff) | |
download | frameworks_base-6da5827193408ec6d2a77b64448b2f1c40b9a482.zip frameworks_base-6da5827193408ec6d2a77b64448b2f1c40b9a482.tar.gz frameworks_base-6da5827193408ec6d2a77b64448b2f1c40b9a482.tar.bz2 |
Merge "Screen magnification - feature - framework." into jb-mr1-dev
34 files changed, 3086 insertions, 260 deletions
@@ -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 Binary files differnew file mode 100644 index 0000000..4cadefb --- /dev/null +++ b/core/res/res/drawable-nodpi/magnified_region_frame.9.png 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. + } } |