diff options
40 files changed, 4017 insertions, 1757 deletions
@@ -196,7 +196,6 @@ LOCAL_SRC_FILES += \ core/java/android/view/accessibility/IAccessibilityManagerClient.aidl \ core/java/android/view/IApplicationToken.aidl \ core/java/android/view/IAssetAtlas.aidl \ - core/java/android/view/IMagnificationCallbacks.aidl \ core/java/android/view/IInputFilter.aidl \ core/java/android/view/IInputFilterHost.aidl \ core/java/android/view/IOnKeyguardExitResult.aidl \ @@ -399,6 +398,7 @@ aidl_files := \ frameworks/base/core/java/android/view/accessibility/AccessibilityEvent.aidl \ frameworks/base/core/java/android/view/accessibility/AccessibilityNodeInfo.aidl \ frameworks/base/core/java/android/view/accessibility/AccessibilityRecord.aidl \ + frameworks/base/core/java/android/view/accessibility/AccessibilityWindowInfo.aidl \ frameworks/base/core/java/android/view/KeyEvent.aidl \ frameworks/base/core/java/android/view/MotionEvent.aidl \ frameworks/base/core/java/android/view/Surface.aidl \ diff --git a/CleanSpec.mk b/CleanSpec.mk index 448b03d..4f0e603 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -187,6 +187,8 @@ $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/framework-res_in $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/src/core/java/android/print/IPrintClient.*) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/services_intermediates) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/src/media/java/android/media/IMedia*) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/src/core/java/android/view/IMagnificationCallbacks*) + # ************************************************ # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST # ************************************************ diff --git a/api/current.txt b/api/current.txt index 139e927..e41b3fe 100644 --- a/api/current.txt +++ b/api/current.txt @@ -2328,6 +2328,7 @@ package android.accessibilityservice { ctor public AccessibilityService(); method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow(); method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo(); + method public java.util.List<android.view.accessibility.AccessibilityWindowInfo> getWindows(); method public abstract void onAccessibilityEvent(android.view.accessibility.AccessibilityEvent); method public final android.os.IBinder onBind(android.content.Intent); method protected boolean onGesture(int); @@ -2393,6 +2394,7 @@ package android.accessibilityservice { field public static final int FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 8; // 0x8 field public static final int FLAG_REQUEST_FILTER_KEY_EVENTS = 32; // 0x20 field public static final int FLAG_REQUEST_TOUCH_EXPLORATION_MODE = 4; // 0x4 + field public static final int FLAG_RETRIEVE_INTERACTIVE_WINDOWS = 64; // 0x40 field public int eventTypes; field public int feedbackType; field public int flags; @@ -4600,6 +4602,7 @@ package android.app { method public android.view.accessibility.AccessibilityEvent executeAndWaitForEvent(java.lang.Runnable, android.app.UiAutomation.AccessibilityEventFilter, long) throws java.util.concurrent.TimeoutException; method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow(); method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo(); + method public java.util.List<android.view.accessibility.AccessibilityWindowInfo> getWindows(); method public boolean injectInputEvent(android.view.InputEvent, boolean); method public final boolean performGlobalAction(int); method public void setOnAccessibilityEventListener(android.app.UiAutomation.OnAccessibilityEventListener); @@ -30400,6 +30403,7 @@ package android.view.accessibility { field public static final int TYPE_VIEW_TEXT_CHANGED = 16; // 0x10 field public static final int TYPE_VIEW_TEXT_SELECTION_CHANGED = 8192; // 0x2000 field public static final int TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY = 131072; // 0x20000 + field public static final int TYPE_WINDOWS_CHANGED = 4194304; // 0x400000 field public static final int TYPE_WINDOW_CONTENT_CHANGED = 2048; // 0x800 field public static final int TYPE_WINDOW_STATE_CHANGED = 32; // 0x20 } @@ -30463,6 +30467,7 @@ package android.view.accessibility { method public int getTextSelectionEnd(); method public int getTextSelectionStart(); method public java.lang.String getViewIdResourceName(); + method public android.view.accessibility.AccessibilityWindowInfo getWindow(); method public int getWindowId(); method public boolean isAccessibilityFocused(); method public boolean isCheckable(); @@ -30609,6 +30614,7 @@ package android.view.accessibility { method public java.util.List<android.view.accessibility.AccessibilityNodeInfo> findAccessibilityNodeInfosByText(java.lang.String, int); method public android.view.accessibility.AccessibilityNodeInfo findFocus(int); method public boolean performAction(int, int, android.os.Bundle); + field public static final int HOST_VIEW_ID = -1; // 0xffffffff } public class AccessibilityRecord { @@ -30660,6 +30666,28 @@ package android.view.accessibility { method public void setToIndex(int); } + public final class AccessibilityWindowInfo implements android.os.Parcelable { + method public int describeContents(); + method public void getBoundsInScreen(android.graphics.Rect); + method public android.view.accessibility.AccessibilityWindowInfo getChild(int); + method public int getChildCount(); + method public int getId(); + method public int getLayer(); + method public android.view.accessibility.AccessibilityWindowInfo getParent(); + method public android.view.accessibility.AccessibilityNodeInfo getRoot(); + method public int getType(); + method public boolean isActive(); + method public boolean isFocused(); + method public static android.view.accessibility.AccessibilityWindowInfo obtain(); + method public static android.view.accessibility.AccessibilityWindowInfo obtain(android.view.accessibility.AccessibilityWindowInfo); + method public void recycle(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + field public static final int TYPE_APPLICATION = 1; // 0x1 + field public static final int TYPE_INPUT_METHOD = 2; // 0x2 + field public static final int TYPE_SYSTEM = 3; // 0x3 + } + public class CaptioningManager { method public void addCaptioningChangeListener(android.view.accessibility.CaptioningManager.CaptioningChangeListener); method public final float getFontScale(); diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 1e3d5be..b01d92c 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -23,14 +23,18 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RemoteException; +import android.os.SystemClock; import android.util.Log; import android.view.KeyEvent; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityInteractionClient; import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityWindowInfo; import com.android.internal.os.HandlerCaller; +import java.util.List; + /** * An accessibility service runs in the background and receives callbacks by the system * when {@link AccessibilityEvent}s are fired. Such events denote some state transition @@ -180,28 +184,37 @@ import com.android.internal.os.HandlerCaller; * event generation has settled down.</p> * <h3>Event types</h3> * <ul> - * <li>{@link AccessibilityEvent#TYPE_VIEW_CLICKED} - * <li>{@link AccessibilityEvent#TYPE_VIEW_LONG_CLICKED} - * <li>{@link AccessibilityEvent#TYPE_VIEW_FOCUSED} - * <li>{@link AccessibilityEvent#TYPE_VIEW_SELECTED} - * <li>{@link AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED} - * <li>{@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED} - * <li>{@link AccessibilityEvent#TYPE_NOTIFICATION_STATE_CHANGED} - * <li>{@link AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_START} - * <li>{@link AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_END} - * <li>{@link AccessibilityEvent#TYPE_VIEW_HOVER_ENTER} - * <li>{@link AccessibilityEvent#TYPE_VIEW_HOVER_EXIT} - * <li>{@link AccessibilityEvent#TYPE_VIEW_SCROLLED} - * <li>{@link AccessibilityEvent#TYPE_VIEW_TEXT_SELECTION_CHANGED} - * <li>{@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} + * <li>{@link AccessibilityEvent#TYPE_VIEW_CLICKED}</li> + * <li>{@link AccessibilityEvent#TYPE_VIEW_LONG_CLICKED}</li> + * <li>{@link AccessibilityEvent#TYPE_VIEW_FOCUSED}</li> + * <li>{@link AccessibilityEvent#TYPE_VIEW_SELECTED}</li> + * <li>{@link AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED}</li> + * <li>{@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED}</li> + * <li>{@link AccessibilityEvent#TYPE_NOTIFICATION_STATE_CHANGED}</li> + * <li>{@link AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_START}</li> + * <li>{@link AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_END}</li> + * <li>{@link AccessibilityEvent#TYPE_VIEW_HOVER_ENTER}</li> + * <li>{@link AccessibilityEvent#TYPE_VIEW_HOVER_EXIT}</li> + * <li>{@link AccessibilityEvent#TYPE_VIEW_SCROLLED}</li> + * <li>{@link AccessibilityEvent#TYPE_VIEW_TEXT_SELECTION_CHANGED}</li> + * <li>{@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED}</li> + * <li>{@link AccessibilityEvent#TYPE_ANNOUNCEMENT}</li> + * <li>{@link AccessibilityEvent#TYPE_GESTURE_DETECTION_START}</li> + * <li>{@link AccessibilityEvent#TYPE_GESTURE_DETECTION_END}</li> + * <li>{@link AccessibilityEvent#TYPE_TOUCH_INTERACTION_START}</li> + * <li>{@link AccessibilityEvent#TYPE_TOUCH_INTERACTION_END}</li> + * <li>{@link AccessibilityEvent#TYPE_VIEW_ACCESSIBILITY_FOCUSED}</li> + * <li>{@link AccessibilityEvent#TYPE_WINDOWS_CHANGED}</li> + * <li>{@link AccessibilityEvent#TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED}</li> * </ul> * <h3>Feedback types</h3> * <ul> - * <li>{@link AccessibilityServiceInfo#FEEDBACK_AUDIBLE} - * <li>{@link AccessibilityServiceInfo#FEEDBACK_HAPTIC} - * <li>{@link AccessibilityServiceInfo#FEEDBACK_AUDIBLE} - * <li>{@link AccessibilityServiceInfo#FEEDBACK_VISUAL} - * <li>{@link AccessibilityServiceInfo#FEEDBACK_GENERIC} + * <li>{@link AccessibilityServiceInfo#FEEDBACK_AUDIBLE}</li> + * <li>{@link AccessibilityServiceInfo#FEEDBACK_HAPTIC}</li> + * <li>{@link AccessibilityServiceInfo#FEEDBACK_AUDIBLE}</li> + * <li>{@link AccessibilityServiceInfo#FEEDBACK_VISUAL}</li> + * <li>{@link AccessibilityServiceInfo#FEEDBACK_GENERIC}</li> + * <li>{@link AccessibilityServiceInfo#FEEDBACK_BRAILLE}</li> * </ul> * @see AccessibilityEvent * @see AccessibilityServiceInfo @@ -443,8 +456,41 @@ public abstract class AccessibilityService extends Service { } /** + * Gets the windows on the screen. This method returns only the windows + * that a sighted user can interact with, as opposed to all windows. + * For example, if there is a modal dialog shown and the user cannot touch + * anything behind it, then only the modal window will be reported + * (assuming it is the top one). For convenience the returned windows + * are ordered in a descending layer order, which is the windows that + * are higher in the Z-order are reported first. + * <p> + * <strong>Note:</strong> In order to access the windows your service has + * to declare the capability to retrieve window content by setting the + * {@link android.R.styleable#AccessibilityService_canRetrieveWindowContent} + * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}. + * Also the service has to opt-in to retrieve the interactive windows by + * setting the {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS} + * flag. + * </p> + * + * @return The windows if there are windows and the service is can retrieve + * them, otherwise an empty list. + */ + public List<AccessibilityWindowInfo> getWindows() { + return AccessibilityInteractionClient.getInstance().getWindows(mConnectionId); + } + + /** * Gets the root node in the currently active window if this service - * can retrieve window content. + * can retrieve window content. The active window is the one that the user + * is currently touching or the window with input focus, if the user is not + * touching any window. + * <p> + * <strong>Note:</strong> In order to access the root node your service has + * to declare the capability to retrieve window content by setting the + * {@link android.R.styleable#AccessibilityService_canRetrieveWindowContent} + * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}. + * </p> * * @return The root node if this service can retrieve window content. */ @@ -584,12 +630,13 @@ public abstract class AccessibilityService extends Service { static final int NO_ID = -1; - private static final int DO_SET_SET_CONNECTION = 10; - private static final int DO_ON_INTERRUPT = 20; - private static final int DO_ON_ACCESSIBILITY_EVENT = 30; - private static final int DO_ON_GESTURE = 40; - private static final int DO_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE = 50; - private static final int DO_ON_KEY_EVENT = 60; + private static final int DO_SET_SET_CONNECTION = 1; + private static final int DO_ON_INTERRUPT = 2; + private static final int DO_ON_ACCESSIBILITY_EVENT = 3; + private static final int DO_ON_GESTURE = 4; + private static final int DO_CLEAR_ACCESSIBILITY_CACHE = 5; + private static final int DO_ON_KEY_EVENT = 6; + private static final int DO_ON_WINDOWS_CHANGED = 7; private final HandlerCaller mCaller; @@ -624,8 +671,8 @@ public abstract class AccessibilityService extends Service { mCaller.sendMessage(message); } - public void clearAccessibilityNodeInfoCache() { - Message message = mCaller.obtainMessage(DO_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE); + public void clearAccessibilityCache() { + Message message = mCaller.obtainMessage(DO_CLEAR_ACCESSIBILITY_CACHE); mCaller.sendMessage(message); } @@ -635,6 +682,13 @@ public abstract class AccessibilityService extends Service { mCaller.sendMessage(message); } + @Override + public void onWindowsChanged(int[] windowIds) { + Message message = mCaller.obtainMessageO(DO_ON_WINDOWS_CHANGED, windowIds); + mCaller.sendMessage(message); + } + + @Override public void executeMessage(Message message) { switch (message.what) { case DO_ON_ACCESSIBILITY_EVENT: { @@ -642,12 +696,19 @@ public abstract class AccessibilityService extends Service { if (event != null) { AccessibilityInteractionClient.getInstance().onAccessibilityEvent(event); mCallback.onAccessibilityEvent(event); - event.recycle(); + // Make sure the event is recycled. + try { + event.recycle(); + } catch (IllegalStateException ise) { + /* ignore - best effort */ + } } } return; + case DO_ON_INTERRUPT: { mCallback.onInterrupt(); } return; + case DO_SET_SET_CONNECTION: { mConnectionId = message.arg1; IAccessibilityServiceConnection connection = @@ -658,18 +719,22 @@ public abstract class AccessibilityService extends Service { mCallback.onSetConnectionId(mConnectionId); mCallback.onServiceConnected(); } else { - AccessibilityInteractionClient.getInstance().removeConnection(mConnectionId); + AccessibilityInteractionClient.getInstance().removeConnection( + mConnectionId); AccessibilityInteractionClient.getInstance().clearCache(); mCallback.onSetConnectionId(AccessibilityInteractionClient.NO_ID); } } return; + case DO_ON_GESTURE: { final int gestureId = message.arg1; mCallback.onGesture(gestureId); } return; - case DO_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE: { + + case DO_CLEAR_ACCESSIBILITY_CACHE: { AccessibilityInteractionClient.getInstance().clearCache(); } return; + case DO_ON_KEY_EVENT: { KeyEvent event = (KeyEvent) message.obj; try { @@ -685,9 +750,40 @@ public abstract class AccessibilityService extends Service { } } } finally { - event.recycle(); + // Make sure the event is recycled. + try { + event.recycle(); + } catch (IllegalStateException ise) { + /* ignore - best effort */ + } } } return; + + case DO_ON_WINDOWS_CHANGED: { + final int[] windowIds = (int[]) message.obj; + + // Update the cached windows first. + // NOTE: The cache will hold on to the windows so do not recycle. + if (windowIds != null) { + AccessibilityInteractionClient.getInstance().removeWindows(windowIds); + } + + // Let the client know the windows changed. + AccessibilityEvent event = AccessibilityEvent.obtain( + AccessibilityEvent.TYPE_WINDOWS_CHANGED); + event.setEventTime(SystemClock.uptimeMillis()); + event.setSealed(true); + + mCallback.onAccessibilityEvent(event); + + // Make sure the event is recycled. + try { + event.recycle(); + } catch (IllegalStateException ise) { + /* ignore - best effort */ + } + } break; + default : Log.w(LOG_TAG, "Unknown message type " + message.what); } diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java index bdc4fdd..4f9ba59 100644 --- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java @@ -284,6 +284,27 @@ public class AccessibilityServiceInfo implements Parcelable { public static final int FLAG_REQUEST_FILTER_KEY_EVENTS = 0x00000020; /** + * This flag indicates to the system that the accessibility service wants + * to access content of all interactive windows. An interactive window is a + * window that can be touched by a sighted user when explore by touch is not + * enabled. If this flag is not set your service will not receive + * {@link android.view.accessibility.AccessibilityEvent#TYPE_WINDOWS_CHANGED} + * events, calling AccessibilityService{@link AccessibilityService#getWindows() + * AccessibilityService.getWindows()} will return an empty list, and {@link + * AccessibilityNodeInfo#getWindow() AccessibilityNodeInfo.getWindow()} will + * return null. + * <p> + * Services that want to set this flag have to declare the capability + * to retrieve window content in their meta-data by setting the attribute + * {@link android.R.attr#canRetrieveWindowContent canRetrieveWindowContent} to + * true, otherwise this flag will be ignored. For how to declare the meta-data + * of a service refer to {@value AccessibilityService#SERVICE_META_DATA}. + * </p> + * @see android.R.styleable#AccessibilityService_canRetrieveWindowContent + */ + public static final int FLAG_RETRIEVE_INTERACTIVE_WINDOWS = 0x00000040; + + /** * The event types an {@link AccessibilityService} is interested in. * <p> * <strong>Can be dynamically set at runtime.</strong> @@ -302,6 +323,15 @@ public class AccessibilityServiceInfo implements Parcelable { * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_SCROLLED * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_TEXT_SELECTION_CHANGED * @see android.view.accessibility.AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED + * @see android.view.accessibility.AccessibilityEvent#TYPE_TOUCH_INTERACTION_START + * @see android.view.accessibility.AccessibilityEvent#TYPE_TOUCH_INTERACTION_END + * @see android.view.accessibility.AccessibilityEvent#TYPE_ANNOUNCEMENT + * @see android.view.accessibility.AccessibilityEvent#TYPE_GESTURE_DETECTION_START + * @see android.view.accessibility.AccessibilityEvent#TYPE_GESTURE_DETECTION_END + * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_ACCESSIBILITY_FOCUSED + * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED + * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY + * @see android.view.accessibility.AccessibilityEvent#TYPE_WINDOWS_CHANGED */ public int eventTypes; @@ -354,6 +384,7 @@ public class AccessibilityServiceInfo implements Parcelable { * @see #FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY * @see #FLAG_REQUEST_FILTER_KEY_EVENTS * @see #FLAG_REPORT_VIEW_IDS + * @see #FLAG_RETRIEVE_INTERACTIVE_WINDOWS */ public int flags; @@ -449,7 +480,7 @@ public class AccessibilityServiceInfo implements Parcelable { com.android.internal.R.styleable.AccessibilityService_accessibilityFeedbackType, 0); notificationTimeout = asAttributes.getInt( - com.android.internal.R.styleable.AccessibilityService_notificationTimeout, + com.android.internal.R.styleable.AccessibilityService_notificationTimeout, 0); flags = asAttributes.getInt( com.android.internal.R.styleable.AccessibilityService_accessibilityFlags, 0); @@ -861,6 +892,8 @@ public class AccessibilityServiceInfo implements Parcelable { return "FLAG_REPORT_VIEW_IDS"; case FLAG_REQUEST_FILTER_KEY_EVENTS: return "FLAG_REQUEST_FILTER_KEY_EVENTS"; + case FLAG_RETRIEVE_INTERACTIVE_WINDOWS: + return "FLAG_RETRIEVE_INTERACTIVE_WINDOWS"; default: return null; } diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl index c5e3d43a..edd8727 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl @@ -18,6 +18,7 @@ package android.accessibilityservice; import android.accessibilityservice.IAccessibilityServiceConnection; import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityWindowInfo; import android.view.KeyEvent; /** @@ -35,7 +36,9 @@ import android.view.KeyEvent; void onGesture(int gesture); - void clearAccessibilityNodeInfoCache(); + void clearAccessibilityCache(); void onKeyEvent(in KeyEvent event, int sequence); + + void onWindowsChanged(in int[] changedWindowIds); } diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl index 3df06b5..5f7a17d 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -21,6 +21,7 @@ import android.accessibilityservice.AccessibilityServiceInfo; import android.view.MagnificationSpec; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; +import android.view.accessibility.AccessibilityWindowInfo; /** * Interface given to an AccessibilitySerivce to talk to the AccessibilityManagerService. @@ -53,6 +54,10 @@ interface IAccessibilityServiceConnection { int action, in Bundle arguments, int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId); + AccessibilityWindowInfo getWindow(int windowId); + + List<AccessibilityWindowInfo> getWindows(); + AccessibilityServiceInfo getServiceInfo(); boolean performGlobalAction(int action); diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index 498fa42..354a19f 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -36,9 +36,11 @@ import android.view.Surface; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityInteractionClient; import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityWindowInfo; import android.view.accessibility.IAccessibilityInteractionConnection; import java.util.ArrayList; +import java.util.List; import java.util.concurrent.TimeoutException; /** @@ -269,10 +271,10 @@ public final class UiAutomation { * @param action The action to perform. * @return Whether the action was successfully performed. * - * @see AccessibilityService#GLOBAL_ACTION_BACK - * @see AccessibilityService#GLOBAL_ACTION_HOME - * @see AccessibilityService#GLOBAL_ACTION_NOTIFICATIONS - * @see AccessibilityService#GLOBAL_ACTION_RECENTS + * @see android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_BACK + * @see android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_HOME + * @see android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_NOTIFICATIONS + * @see android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_RECENTS */ public final boolean performGlobalAction(int action) { final IAccessibilityServiceConnection connection; @@ -346,6 +348,33 @@ public final class UiAutomation { } /** + * Gets the windows on the screen. This method returns only the windows + * that a sighted user can interact with, as opposed to all windows. + * For example, if there is a modal dialog shown and the user cannot touch + * anything behind it, then only the modal window will be reported + * (assuming it is the top one). For convenience the returned windows + * are ordered in a descending layer order, which is the windows that + * are higher in the Z-order are reported first. + * <p> + * <strong>Note:</strong> In order to access the windows you have to opt-in + * to retrieve the interactive windows by setting the + * {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS} flag. + * </p> + * + * @return The windows if there are windows such, otherwise an empty list. + */ + public List<AccessibilityWindowInfo> getWindows() { + final int connectionId; + synchronized (mLock) { + throwIfNotConnectedLocked(); + connectionId = mConnectionId; + } + // Calling out without a lock held. + return AccessibilityInteractionClient.getInstance() + .getWindows(connectionId); + } + + /** * Gets the root {@link AccessibilityNodeInfo} in the active window. * * @return The root info. @@ -632,7 +661,7 @@ public final class UiAutomation { * potentially undesirable actions such as calling 911 or posting on public forums etc. * * @param enable whether to run in a "monkey" mode or not. Default is not. - * @see {@link ActivityManager#isUserAMonkey()} + * @see {@link android.app.ActivityManager#isUserAMonkey()} */ public void setRunAsMonkey(boolean enable) { synchronized (mLock) { diff --git a/core/java/android/util/LongArray.java b/core/java/android/util/LongArray.java index 290a89b..d5f15f0 100644 --- a/core/java/android/util/LongArray.java +++ b/core/java/android/util/LongArray.java @@ -111,7 +111,6 @@ public class LongArray implements Cloneable { } @Override - @SuppressWarnings("unchecked") public LongArray clone() { LongArray clone = null; try { @@ -154,7 +153,8 @@ public class LongArray implements Cloneable { if (index >= mSize) { throw new ArrayIndexOutOfBoundsException(mSize, index); } - System.arraycopy(mValues, index, mValues, index + 1, mSize - index); + System.arraycopy(mValues, index + 1, mValues, index, mSize - index - 1); + mSize--; } /** diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java index 3859ad4..abae068 100644 --- a/core/java/android/view/AccessibilityInteractionController.java +++ b/core/java/android/view/AccessibilityInteractionController.java @@ -18,12 +18,14 @@ package android.view; import android.graphics.Point; import android.graphics.Rect; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.Process; import android.os.RemoteException; +import android.util.LongSparseArray; import android.view.View.AttachInfo; import android.view.accessibility.AccessibilityInteractionClient; import android.view.accessibility.AccessibilityNodeInfo; @@ -35,8 +37,11 @@ import com.android.internal.util.Predicate; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Queue; /** * Class for managing accessibility interactions initiated from the system @@ -47,6 +52,8 @@ import java.util.Map; */ final class AccessibilityInteractionController { + private static final boolean ENFORCE_NODE_TREE_CONSISTENT = Build.IS_DEBUGGABLE; + private final ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList = new ArrayList<AccessibilityNodeInfo>(); @@ -137,7 +144,7 @@ final class AccessibilityInteractionController { } mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; View root = null; - if (accessibilityViewId == AccessibilityNodeInfo.UNDEFINED) { + if (accessibilityViewId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { root = mViewRootImpl.mView; } else { root = findViewByAccessibilityId(accessibilityViewId); @@ -209,7 +216,7 @@ final class AccessibilityInteractionController { } mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; View root = null; - if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) { + if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { root = findViewByAccessibilityId(accessibilityViewId); } else { root = mViewRootImpl.mView; @@ -289,7 +296,7 @@ final class AccessibilityInteractionController { } mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; View root = null; - if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) { + if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { root = findViewByAccessibilityId(accessibilityViewId); } else { root = mViewRootImpl.mView; @@ -297,9 +304,14 @@ final class AccessibilityInteractionController { if (root != null && isShown(root)) { AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider(); if (provider != null) { - infos = provider.findAccessibilityNodeInfosByText(text, - virtualDescendantId); - } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED) { + if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { + infos = provider.findAccessibilityNodeInfosByText(text, + virtualDescendantId); + } else { + infos = provider.findAccessibilityNodeInfosByText(text, + AccessibilityNodeProvider.HOST_VIEW_ID); + } + } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { ArrayList<View> foundViews = mTempArrayList; foundViews.clear(); root.findViewsWithText(foundViews, text, View.FIND_VIEWS_WITH_TEXT @@ -316,7 +328,7 @@ final class AccessibilityInteractionController { if (provider != null) { List<AccessibilityNodeInfo> infosFromProvider = provider.findAccessibilityNodeInfosByText(text, - AccessibilityNodeInfo.UNDEFINED); + AccessibilityNodeProvider.HOST_VIEW_ID); if (infosFromProvider != null) { infos.addAll(infosFromProvider); } @@ -391,7 +403,7 @@ final class AccessibilityInteractionController { } mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; View root = null; - if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) { + if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { root = findViewByAccessibilityId(accessibilityViewId); } else { root = mViewRootImpl.mView; @@ -417,7 +429,7 @@ final class AccessibilityInteractionController { focused = AccessibilityNodeInfo.obtain( mViewRootImpl.mAccessibilityFocusedVirtualView); } - } else if (virtualDescendantId == View.NO_ID) { + } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { focused = host.createAccessibilityNodeInfo(); } } break; @@ -500,7 +512,7 @@ final class AccessibilityInteractionController { } mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; View root = null; - if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) { + if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { root = findViewByAccessibilityId(accessibilityViewId); } else { root = mViewRootImpl.mView; @@ -576,7 +588,7 @@ final class AccessibilityInteractionController { } mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; View target = null; - if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) { + if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { target = findViewByAccessibilityId(accessibilityViewId); } else { target = mViewRootImpl.mView; @@ -584,9 +596,14 @@ final class AccessibilityInteractionController { if (target != null && isShown(target)) { AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider(); if (provider != null) { - succeeded = provider.performAction(virtualDescendantId, action, - arguments); - } else if (virtualDescendantId == View.NO_ID) { + if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { + succeeded = provider.performAction(virtualDescendantId, action, + arguments); + } else { + succeeded = provider.performAction(AccessibilityNodeProvider.HOST_VIEW_ID, + action, arguments); + } + } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { succeeded = target.performAccessibilityAction(action, arguments); } } @@ -734,6 +751,85 @@ final class AccessibilityInteractionController { } } } + if (ENFORCE_NODE_TREE_CONSISTENT) { + enforceNodeTreeConsistent(outInfos); + } + } + + private void enforceNodeTreeConsistent(List<AccessibilityNodeInfo> nodes) { + LongSparseArray<AccessibilityNodeInfo> nodeMap = + new LongSparseArray<AccessibilityNodeInfo>(); + final int nodeCount = nodes.size(); + for (int i = 0; i < nodeCount; i++) { + AccessibilityNodeInfo node = nodes.get(i); + nodeMap.put(node.getSourceNodeId(), node); + } + + // If the nodes are a tree it does not matter from + // which node we start to search for the root. + AccessibilityNodeInfo root = nodeMap.valueAt(0); + AccessibilityNodeInfo parent = root; + while (parent != null) { + root = parent; + parent = nodeMap.get(parent.getParentNodeId()); + } + + // Traverse the tree and do some checks. + AccessibilityNodeInfo accessFocus = null; + AccessibilityNodeInfo inputFocus = null; + HashSet<AccessibilityNodeInfo> seen = new HashSet<AccessibilityNodeInfo>(); + Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>(); + fringe.add(root); + + while (!fringe.isEmpty()) { + AccessibilityNodeInfo current = fringe.poll(); + + // Check for duplicates + if (!seen.add(current)) { + throw new IllegalStateException("Duplicate node: " + + current + " in window:" + + mViewRootImpl.mAttachInfo.mAccessibilityWindowId); + } + + // Check for one accessibility focus. + if (current.isAccessibilityFocused()) { + if (accessFocus != null) { + throw new IllegalStateException("Duplicate accessibility focus:" + + current + + " in window:" + mViewRootImpl.mAttachInfo.mAccessibilityWindowId); + } else { + accessFocus = current; + } + } + + // Check for one input focus. + if (current.isFocused()) { + if (inputFocus != null) { + throw new IllegalStateException("Duplicate input focus: " + + current + " in window:" + + mViewRootImpl.mAttachInfo.mAccessibilityWindowId); + } else { + inputFocus = current; + } + } + + final int childCount = current.getChildCount(); + for (int j = 0; j < childCount; j++) { + final long childId = current.getChildId(j); + final AccessibilityNodeInfo child = nodeMap.get(childId); + if (child != null) { + fringe.add(child); + } + } + } + + // Check for disconnected nodes. + for (int j = nodeMap.size() - 1; j >= 0; j--) { + AccessibilityNodeInfo info = nodeMap.valueAt(j); + if (!seen.contains(info)) { + throw new IllegalStateException("Disconnected node: " + info); + } + } } private void prefetchPredecessorsOfRealNode(View view, @@ -774,7 +870,7 @@ final class AccessibilityInteractionController { info = child.createAccessibilityNodeInfo(); } else { info = provider.createAccessibilityNodeInfo( - AccessibilityNodeInfo.UNDEFINED); + AccessibilityNodeProvider.HOST_VIEW_ID); } if (info != null) { outInfos.add(info); @@ -814,7 +910,7 @@ final class AccessibilityInteractionController { } } else { AccessibilityNodeInfo info = provider.createAccessibilityNodeInfo( - AccessibilityNodeInfo.UNDEFINED); + AccessibilityNodeProvider.HOST_VIEW_ID); if (info != null) { outInfos.add(info); addedChildren.put(child, info); @@ -845,16 +941,22 @@ final class AccessibilityInteractionController { List<AccessibilityNodeInfo> outInfos) { long parentNodeId = root.getParentNodeId(); int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId); - while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) { + while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { return; } final int virtualDescendantId = AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId); - if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED + if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID || accessibilityViewId == providerHost.getAccessibilityViewId()) { - AccessibilityNodeInfo parent = provider.createAccessibilityNodeInfo( - virtualDescendantId); + final AccessibilityNodeInfo parent; + if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { + parent = provider.createAccessibilityNodeInfo( + virtualDescendantId); + } else { + parent= provider.createAccessibilityNodeInfo( + AccessibilityNodeProvider.HOST_VIEW_ID); + } if (parent != null) { outInfos.add(parent); } @@ -875,10 +977,15 @@ final class AccessibilityInteractionController { AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId); final int parentVirtualDescendantId = AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId); - if (parentVirtualDescendantId != AccessibilityNodeInfo.UNDEFINED + if (parentVirtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID || parentAccessibilityViewId == providerHost.getAccessibilityViewId()) { - AccessibilityNodeInfo parent = - provider.createAccessibilityNodeInfo(parentVirtualDescendantId); + final AccessibilityNodeInfo parent; + if (parentAccessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { + parent = provider.createAccessibilityNodeInfo(parentVirtualDescendantId); + } else { + parent = provider.createAccessibilityNodeInfo( + AccessibilityNodeProvider.HOST_VIEW_ID); + } if (parent != null) { final int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) { diff --git a/core/java/android/view/IMagnificationCallbacks.aidl b/core/java/android/view/IMagnificationCallbacks.aidl deleted file mode 100644 index 032d073..0000000 --- a/core/java/android/view/IMagnificationCallbacks.aidl +++ /dev/null @@ -1,29 +0,0 @@ -/* -** Copyright 2012, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ - -package android.view; - -import android.graphics.Region; - -/** - * {@hide} - */ -oneway interface IMagnificationCallbacks { - void onMagnifedBoundsChanged(in Region bounds); - void onRectangleOnScreenRequested(int left, int top, int right, int bottom); - void onRotationChanged(int rotation); - void onUserContextChanged(); -} diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index c92a104..8f542bb 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -27,7 +27,6 @@ import android.graphics.Rect; import android.os.Bundle; import android.os.IRemoteCallback; import android.view.IApplicationToken; -import android.view.IMagnificationCallbacks; import android.view.IOnKeyguardExitResult; import android.view.IRotationWatcher; import android.view.IWindowSession; @@ -197,7 +196,7 @@ interface IWindowManager void thawRotation(); /** - * Gets whether the rotation is frozen. + * Gets whether the rotation is frozen. * * @return Whether the rotation is frozen. */ @@ -231,55 +230,7 @@ interface IWindowManager void lockNow(in Bundle options); /** - * Gets the token for the focused window. - */ - IBinder getFocusedWindowToken(); - - /** - * Sets an input filter for manipulating the input event stream. - */ - void setInputFilter(in IInputFilter filter); - - /** - * Gets the frame of a window given its token. - */ - void getWindowFrame(IBinder token, out Rect outFrame); - - /** * Device is in safe mode. */ boolean isSafeModeEnabled(); - - /** - * Sets the display magnification callbacks. These callbacks notify - * the client for contextual changes related to display magnification. - * - * @param callbacks The magnification callbacks. - */ - void setMagnificationCallbacks(IMagnificationCallbacks callbacks); - - /** - * Sets the magnification spec to be applied to all windows that can be - * magnified. - * - * @param spec The current magnification spec. - */ - void setMagnificationSpec(in MagnificationSpec spec); - - /** - * Gets the magnification spec for a window given its token. If the - * window has a compatibility scale it is also folded in the returned - * magnification spec. - * - * @param windowToken The unique window token. - * @return The magnification spec if such or null. - */ - MagnificationSpec getCompatibleMagnificationSpecForWindow(in IBinder windowToken); - - /** - * Sets the current touch exploration state. - * - * @param enabled Whether touch exploration is enabled. - */ - void setTouchExplorationEnabled(boolean enabled); } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 179c805..a8ccd49 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -5101,10 +5101,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see AccessibilityDelegate */ public void sendAccessibilityEvent(int eventType) { - // Excluded views do not send accessibility events. - if (!includeForAccessibility()) { - return; - } if (mAccessibilityDelegate != null) { mAccessibilityDelegate.sendAccessibilityEvent(this, eventType); } else { @@ -9386,6 +9382,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mParent.invalidateChild(this, null); } dispatchVisibilityChanged(this, newVisibility); + + notifySubtreeAccessibilityStateChangedIfNeeded(); } if ((changed & WILL_NOT_CACHE_DRAWING) != 0) { diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index f9b9401..22fbbd6 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -3711,7 +3711,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager childHasTransientStateChanged(child, true); } - if (child.isImportantForAccessibility() && child.getVisibility() != View.GONE) { + if (child.getVisibility() != View.GONE) { notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -3954,7 +3954,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager onViewRemoved(view); - if (view.isImportantForAccessibility() && view.getVisibility() != View.GONE) { + if (view.getVisibility() != View.GONE) { notifySubtreeAccessibilityStateChangedIfNeeded(); } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 185cb65..1d6e998 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -6355,7 +6355,7 @@ public final class ViewRootImpl implements ViewParent, public void ensureConnection() { if (mAttachInfo != null) { final boolean registered = - mAttachInfo.mAccessibilityWindowId != AccessibilityNodeInfo.UNDEFINED; + mAttachInfo.mAccessibilityWindowId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID; if (!registered) { mAttachInfo.mAccessibilityWindowId = mAccessibilityManager.addAccessibilityInteractionConnection(mWindow, @@ -6366,9 +6366,9 @@ public final class ViewRootImpl implements ViewParent, public void ensureNoConnection() { final boolean registered = - mAttachInfo.mAccessibilityWindowId != AccessibilityNodeInfo.UNDEFINED; + mAttachInfo.mAccessibilityWindowId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID; if (registered) { - mAttachInfo.mAccessibilityWindowId = AccessibilityNodeInfo.UNDEFINED; + mAttachInfo.mAccessibilityWindowId = AccessibilityNodeInfo.UNDEFINED_ITEM_ID; mAccessibilityManager.removeAccessibilityInteractionConnection(mWindow); } } diff --git a/core/java/android/view/WindowInfo.aidl b/core/java/android/view/WindowInfo.aidl new file mode 100644 index 0000000..75b8fd2 --- /dev/null +++ b/core/java/android/view/WindowInfo.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2014, 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..7f89044 --- /dev/null +++ b/core/java/android/view/WindowInfo.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2014 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; +import android.util.Pools; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class represents information about a window from the + * window manager to another part of the system. + * + * @hide + */ +public class WindowInfo implements Parcelable { + private static final int MAX_POOL_SIZE = 10; + + private static final Pools.SynchronizedPool<WindowInfo> sPool = + new Pools.SynchronizedPool<WindowInfo>(MAX_POOL_SIZE); + + public int type; + public int layer; + public IBinder token; + public IBinder parentToken; + public boolean focused; + public final Rect boundsInScreen = new Rect(); + public List<IBinder> childTokens; + + private WindowInfo() { + /* do nothing - hide constructor */ + } + + public static WindowInfo obtain() { + WindowInfo window = sPool.acquire(); + if (window == null) { + window = new WindowInfo(); + } + return window; + } + + public static WindowInfo obtain(WindowInfo other) { + WindowInfo window = obtain(); + window.type = other.type; + window.layer = other.layer; + window.token = other.token; + window.parentToken = other.parentToken; + window.focused = other.focused; + window.boundsInScreen.set(other.boundsInScreen); + + if (other.childTokens != null && !other.childTokens.isEmpty()) { + if (window.childTokens == null) { + window.childTokens = new ArrayList<IBinder>(other.childTokens); + } else { + window.childTokens.addAll(other.childTokens); + } + } + + return window; + } + + public void recycle() { + clear(); + sPool.release(this); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(type); + parcel.writeInt(layer); + parcel.writeStrongBinder(token); + parcel.writeStrongBinder(parentToken); + parcel.writeInt(focused ? 1 : 0); + boundsInScreen.writeToParcel(parcel, flags); + + if (childTokens != null && !childTokens.isEmpty()) { + parcel.writeInt(1); + parcel.writeBinderList(childTokens); + } else { + parcel.writeInt(0); + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("WindowInfo["); + builder.append("type=").append(type); + builder.append(", layer=").append(layer); + builder.append(", token=").append(token); + builder.append(", parent=").append(parentToken); + builder.append(", focused=").append(focused); + builder.append(", children=").append(childTokens); + builder.append(']'); + return builder.toString(); + } + + private void initFromParcel(Parcel parcel) { + type = parcel.readInt(); + layer = parcel.readInt(); + token = parcel.readStrongBinder(); + parentToken = parcel.readStrongBinder(); + focused = (parcel.readInt() == 1); + boundsInScreen.readFromParcel(parcel); + + final boolean hasChildren = (parcel.readInt() == 1); + if (hasChildren) { + if (childTokens == null) { + childTokens = new ArrayList<IBinder>(); + } + parcel.readBinderList(childTokens); + } + } + + private void clear() { + type = 0; + layer = 0; + token = null; + parentToken = null; + focused = false; + boundsInScreen.setEmpty(); + if (childTokens != null) { + childTokens.clear(); + } + } + + public static final Parcelable.Creator<WindowInfo> CREATOR = + new Creator<WindowInfo>() { + @Override + public WindowInfo createFromParcel(Parcel parcel) { + WindowInfo window = obtain(); + window.initFromParcel(parcel); + return window; + } + + @Override + public WindowInfo[] newArray(int size) { + return new WindowInfo[size]; + } + }; +} diff --git a/core/java/android/view/WindowManagerInternal.java b/core/java/android/view/WindowManagerInternal.java index a1bd4bd..14dc356 100644 --- a/core/java/android/view/WindowManagerInternal.java +++ b/core/java/android/view/WindowManagerInternal.java @@ -16,7 +16,12 @@ package android.view; +import android.graphics.Rect; +import android.graphics.Region; import android.hardware.display.DisplayManagerInternal; +import android.os.IBinder; + +import java.util.List; /** * Window manager local system service interface. @@ -24,10 +29,136 @@ import android.hardware.display.DisplayManagerInternal; * @hide Only for use within the system server. */ public abstract class WindowManagerInternal { + + /** + * Interface to receive a callback when the windows reported for + * accessibility changed. + */ + public interface WindowsForAccessibilityCallback { + + /** + * Called when the windows for accessibility changed. + * + * @param windows The windows for accessibility. + */ + public void onWindowsForAccessibilityChanged(List<WindowInfo> windows); + } + + /** + * Callbacks for contextual changes that affect the screen magnification + * feature. + */ + public interface MagnificationCallbacks { + + /** + * Called when the bounds of the screen content that is magnified changed. + * Note that not the entire screen is magnified. + * + * @param bounds The bounds. + */ + public void onMagnifedBoundsChanged(Region bounds); + + /** + * Called when an application requests a rectangle on the screen to allow + * the client to apply the appropriate pan and scale. + * + * @param left The rectangle left. + * @param top The rectangle top. + * @param right The rectangle right. + * @param bottom The rectangle bottom. + */ + public void onRectangleOnScreenRequested(int left, int top, int right, int bottom); + + /** + * Notifies that the rotation changed. + * + * @param rotation The current rotation. + */ + public void onRotationChanged(int rotation); + + /** + * Notifies that the context of the user changed. For example, an application + * was started. + */ + public void onUserContextChanged(); + } + /** * Request that the window manager call * {@link DisplayManagerInternal#performTraversalInTransactionFromWindowManager} * within a surface transaction at a later time. */ public abstract void requestTraversalFromDisplayManager(); -}
\ No newline at end of file + + /** + * Set by the accessibility layer to observe changes in the magnified region, + * rotation, and other window transformations related to display magnification + * as the window manager is responsible for doing the actual magnification + * and has access to the raw window data while the accessibility layer serves + * as a controller. + * + * @param callbacks The callbacks to invoke. + */ + public abstract void setMagnificationCallbacks(MagnificationCallbacks callbacks); + + /** + * Set by the accessibility layer to specify the magnification and panning to + * be applied to all windows that should be magnified. + * + * @param callbacks The callbacks to invoke. + * + * @see #setMagnificationCallbacks(MagnificationCallbacks) + */ + public abstract void setMagnificationSpec(MagnificationSpec spec); + + /** + * Gets the magnification and translation applied to a window given its token. + * Not all windows are magnified and the window manager policy determines which + * windows are magnified. The returned result also takes into account the compat + * scale if necessary. + * + * @param windowToken The window's token. + * + * @return The magnification spec for the window. + * + * @see #setMagnificationCallbacks(MagnificationCallbacks) + */ + public abstract MagnificationSpec getCompatibleMagnificationSpecForWindow( + IBinder windowToken); + + /** + * Sets a callback for observing which windows are touchable for the purposes + * of accessibility. + * + * @param callback The callback. + */ + public abstract void setWindowsForAccessibilityCallback( + WindowsForAccessibilityCallback callback); + + /** + * Sets a filter for manipulating the input event stream. + * + * @param filter The filter implementation. + */ + public abstract void setInputFilter(IInputFilter filter); + + /** + * Gets the token of the window that has input focus. + * + * @return The token. + */ + public abstract IBinder getFocusedWindowToken(); + + /** + * @return Whether the keyguard is engaged. + */ + public abstract boolean isKeyguardLocked(); + + /** + * Gets the frame of a window given its token. + * + * @param token The token. + * @param outBounds The frame to populate. + */ + public abstract void getWindowFrame(IBinder token, Rect outBounds); +} diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index 75656545..bd203c8 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -1187,11 +1187,4 @@ public interface WindowManagerPolicy { * @return True if the window is a top level one. */ public boolean isTopLevelWindow(int windowType); - - /** - * Sets the current touch exploration state. - * - * @param enabled Whether touch exploration is enabled. - */ - public void setTouchExplorationEnabled(boolean enabled); } diff --git a/core/java/android/view/accessibility/AccessibilityCache.java b/core/java/android/view/accessibility/AccessibilityCache.java new file mode 100644 index 0000000..77d48e2 --- /dev/null +++ b/core/java/android/view/accessibility/AccessibilityCache.java @@ -0,0 +1,467 @@ +/* + * 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.accessibility; + +import android.os.Build; +import android.util.ArraySet; +import android.util.Log; +import android.util.LongArray; +import android.util.LongSparseArray; +import android.util.SparseArray; + +import java.util.ArrayList; +import java.util.List; + +/** + * Cache for AccessibilityWindowInfos and AccessibilityNodeInfos. + * It is updated when windows change or nodes change. + */ +final class AccessibilityCache { + + private static final String LOG_TAG = "AccessibilityCache"; + + private static final boolean DEBUG = false; + + private static final boolean CHECK_INTEGRITY = Build.IS_DEBUGGABLE; + + private final Object mLock = new Object(); + + private final LongArray mTempLongArray = new LongArray(); + + private final SparseArray<AccessibilityWindowInfo> mWindowCache = + new SparseArray<AccessibilityWindowInfo>(); + + private final SparseArray<LongSparseArray<AccessibilityNodeInfo>> mNodeCache = + new SparseArray<LongSparseArray<AccessibilityNodeInfo>>(); + + private final SparseArray<AccessibilityWindowInfo> mTempWindowArray = + new SparseArray<AccessibilityWindowInfo>(); + + public void addWindow(AccessibilityWindowInfo window) { + synchronized (mLock) { + if (DEBUG) { + Log.i(LOG_TAG, "Caching window: " + window.getId()); + } + mWindowCache.put(window.getId(), window); + } + } + + public void removeWindows(int[] windowIds) { + synchronized (mLock) { + final int windowCount = windowIds.length; + for (int i = 0; i < windowCount; i++) { + final int windowId = windowIds[i]; + AccessibilityWindowInfo window = mWindowCache.get(windowId); + if (window != null) { + if (DEBUG) { + Log.i(LOG_TAG, "Removing window: " + windowId); + } + window.recycle(); + mWindowCache.remove(windowId); + } + clearNodesForWindowLocked(windowIds[i]); + } + } + } + + /** + * Notifies the cache that the something in the UI changed. As a result + * the cache will either refresh some nodes or evict some nodes. + * + * @param event An event. + */ + public void onAccessibilityEvent(AccessibilityEvent event) { + synchronized (mLock) { + final int eventType = event.getEventType(); + switch (eventType) { + case AccessibilityEvent.TYPE_VIEW_FOCUSED: + case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: + case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: + case AccessibilityEvent.TYPE_VIEW_SELECTED: + case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED: + case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED: { + refreshCachedNodeLocked(event.getWindowId(), event.getSourceNodeId()); + } break; + + case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: { + synchronized (mLock) { + final int windowId = event.getWindowId(); + final long sourceId = event.getSourceNodeId(); + if ((event.getContentChangeTypes() + & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE) != 0) { + clearSubTreeLocked(windowId, sourceId); + } else { + refreshCachedNodeLocked(windowId, sourceId); + } + } + } break; + + case AccessibilityEvent.TYPE_VIEW_SCROLLED: { + clearSubTreeLocked(event.getWindowId(), event.getSourceNodeId()); + } break; + } + } + + if (CHECK_INTEGRITY) { + checkIntegrity(); + } + } + + private void refreshCachedNodeLocked(int windowId, long sourceId) { + if (DEBUG) { + Log.i(LOG_TAG, "Refreshing cached node."); + } + + LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId); + if (nodes == null) { + return; + } + AccessibilityNodeInfo cachedInfo = nodes.get(sourceId); + // If the source is not in the cache - nothing to do. + if (cachedInfo == null) { + return; + } + // The node changed so we will just refresh it right now. + if (cachedInfo.refresh(true)) { + return; + } + // Weird, we could not refresh. Just evict the entire sub-tree. + clearSubTreeLocked(windowId, sourceId); + } + + /** + * Gets a cached {@link AccessibilityNodeInfo} given the id of the hosting + * window and the accessibility id of the node. + * + * @param windowId The id of the window hosting the node. + * @param accessibilityNodeId The info accessibility node id. + * @return The cached {@link AccessibilityNodeInfo} or null if such not found. + */ + public AccessibilityNodeInfo getNode(int windowId, long accessibilityNodeId) { + synchronized(mLock) { + LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId); + if (nodes == null) { + return null; + } + AccessibilityNodeInfo info = nodes.get(accessibilityNodeId); + if (info != null) { + // Return a copy since the client calls to AccessibilityNodeInfo#recycle() + // will wipe the data of the cached info. + info = AccessibilityNodeInfo.obtain(info); + } + if (DEBUG) { + Log.i(LOG_TAG, "get(" + accessibilityNodeId + ") = " + info); + } + return info; + } + } + + public List<AccessibilityWindowInfo> getWindows() { + synchronized (mLock) { + final int windowCount = mWindowCache.size(); + if (windowCount > 0) { + // Careful to return the windows in a decreasing layer order. + SparseArray<AccessibilityWindowInfo> sortedWindows = mTempWindowArray; + sortedWindows.clear(); + + for (int i = 0; i < windowCount; i++) { + AccessibilityWindowInfo window = mWindowCache.valueAt(i); + sortedWindows.put(window.getLayer(), window); + } + + List<AccessibilityWindowInfo> windows = new ArrayList<AccessibilityWindowInfo>(); + for (int i = windowCount - 1; i >= 0; i--) { + AccessibilityWindowInfo window = sortedWindows.valueAt(i); + windows.add(AccessibilityWindowInfo.obtain(window)); + } + + sortedWindows.clear(); + + return windows; + } + return null; + } + } + + public AccessibilityWindowInfo getWindow(int windowId) { + synchronized (mLock) { + AccessibilityWindowInfo window = mWindowCache.get(windowId); + if (window != null) { + return AccessibilityWindowInfo.obtain(window); + } + return null; + } + } + + /** + * Caches an {@link AccessibilityNodeInfo}. + * + * @param info The node to cache. + */ + public void add(AccessibilityNodeInfo info) { + synchronized(mLock) { + if (DEBUG) { + Log.i(LOG_TAG, "add(" + info + ")"); + } + + final int windowId = info.getWindowId(); + LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId); + if (nodes == null) { + nodes = new LongSparseArray<AccessibilityNodeInfo>(); + mNodeCache.put(windowId, nodes); + } + + final long sourceId = info.getSourceNodeId(); + AccessibilityNodeInfo oldInfo = nodes.get(sourceId); + if (oldInfo != null) { + // If the added node is in the cache we have to be careful if + // the new one represents a source state where some of the + // children have been removed to remove the descendants that + // are no longer present. + final LongArray newChildrenIds = info.getChildNodeIds(); + if (newChildrenIds != null) { + // Cache the new ids as we will do some lookups. + LongArray newChildNodeIds = mTempLongArray; + final int newChildCount = newChildNodeIds.size(); + for (int i = 0; i < newChildCount; i++) { + newChildNodeIds.add(newChildrenIds.get(i)); + } + + final int oldChildCount = oldInfo.getChildCount(); + for (int i = 0; i < oldChildCount; i++) { + final long oldChildId = oldInfo.getChildId(i); + if (newChildNodeIds.indexOf(oldChildId) < 0) { + clearSubTreeLocked(windowId, oldChildId); + } + } + + newChildNodeIds.clear(); + } + + // Also be careful if the parent has changed since the new + // parent may be a predecessor of the old parent which will + // add cyclse to the cache. + final long oldParentId = oldInfo.getParentNodeId(); + if (info.getParentNodeId() != oldParentId) { + clearSubTreeLocked(windowId, oldParentId); + } + } + + // Cache a copy since the client calls to AccessibilityNodeInfo#recycle() + // will wipe the data of the cached info. + AccessibilityNodeInfo clone = AccessibilityNodeInfo.obtain(info); + nodes.put(sourceId, clone); + } + } + + /** + * Clears the cache. + */ + public void clear() { + synchronized(mLock) { + if (DEBUG) { + Log.i(LOG_TAG, "clear()"); + } + final int windowCount = mWindowCache.size(); + for (int i = windowCount - 1; i >= 0; i--) { + AccessibilityWindowInfo window = mWindowCache.valueAt(i); + window.recycle(); + mWindowCache.removeAt(i); + } + final int nodesForWindowCount = mNodeCache.size(); + for (int i = 0; i < nodesForWindowCount; i++) { + final int windowId = mNodeCache.keyAt(i); + clearNodesForWindowLocked(windowId); + } + } + } + + private void clearNodesForWindowLocked(int windowId) { + if (DEBUG) { + Log.i(LOG_TAG, "clearWindowLocked(" + windowId + ")"); + } + LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId); + if (nodes == null) { + return; + } + // Recycle the nodes before clearing the cache. + final int nodeCount = nodes.size(); + for (int i = nodeCount - 1; i >= 0; i--) { + AccessibilityNodeInfo info = nodes.valueAt(i); + nodes.removeAt(i); + info.recycle(); + } + mNodeCache.remove(windowId); + } + + /** + * Clears a subtree rooted at the node with the given id that is + * hosted in a given window. + * + * @param windowId The id of the hosting window. + * @param rootNodeId The root id. + */ + private void clearSubTreeLocked(int windowId, long rootNodeId) { + if (DEBUG) { + Log.i(LOG_TAG, "Clearing cached subtree."); + } + LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId); + if (nodes != null) { + clearSubTreeRecursiveLocked(nodes, rootNodeId); + } + } + + /** + * Clears a subtree given a pointer to the root id and the nodes + * in the hosting window. + * + * @param nodes The nodes in the hosting window. + * @param rootNodeId The id of the root to evict. + */ + private void clearSubTreeRecursiveLocked(LongSparseArray<AccessibilityNodeInfo> nodes, + long rootNodeId) { + AccessibilityNodeInfo current = nodes.get(rootNodeId); + if (current == null) { + return; + } + nodes.remove(rootNodeId); + final int childCount = current.getChildCount(); + for (int i = 0; i < childCount; i++) { + final long childNodeId = current.getChildId(i); + clearSubTreeRecursiveLocked(nodes, childNodeId); + } + } + + /** + * Check the integrity of the cache which is nodes from different windows + * are not mixed, there is a single active window, there is a single focused + * window, for every window there are no duplicates nodes, all nodes for a + * window are connected, for every window there is a single input focused + * node, and for every window there is a single accessibility focused node. + */ + public void checkIntegrity() { + synchronized (mLock) { + // Get the root. + if (mWindowCache.size() <= 0 && mNodeCache.size() == 0) { + return; + } + + AccessibilityWindowInfo focusedWindow = null; + AccessibilityWindowInfo activeWindow = null; + + final int windowCount = mWindowCache.size(); + for (int i = 0; i < windowCount; i++) { + AccessibilityWindowInfo window = mWindowCache.valueAt(i); + + // Check for one active window. + if (window.isActive()) { + if (activeWindow != null) { + Log.e(LOG_TAG, "Duplicate active window:" + window); + } else { + activeWindow = window; + } + } + + // Check for one focused window. + if (window.isFocused()) { + if (focusedWindow != null) { + Log.e(LOG_TAG, "Duplicate focused window:" + window); + } else { + focusedWindow = window; + } + } + } + + // Traverse the tree and do some checks. + AccessibilityNodeInfo accessFocus = null; + AccessibilityNodeInfo inputFocus = null; + + final int nodesForWindowCount = mNodeCache.size(); + for (int i = 0; i < nodesForWindowCount; i++) { + LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.valueAt(i); + if (nodes.size() <= 0) { + continue; + } + + ArraySet<AccessibilityNodeInfo> seen = new ArraySet<AccessibilityNodeInfo>(); + final int windowId = mNodeCache.keyAt(i); + + final int nodeCount = nodes.size(); + for (int j = 0; j < nodeCount; j++) { + AccessibilityNodeInfo node = nodes.valueAt(j); + + // Check for duplicates + if (!seen.add(node)) { + Log.e(LOG_TAG, "Duplicate node: " + node + + " in window:" + windowId); + } + + // Check for one accessibility focus. + if (node.isAccessibilityFocused()) { + if (accessFocus != null) { + Log.e(LOG_TAG, "Duplicate accessibility focus:" + node + + " in window:" + windowId); + } else { + accessFocus = node; + } + } + + // Check for one input focus. + if (node.isFocused()) { + if (inputFocus != null) { + Log.e(LOG_TAG, "Duplicate input focus: " + node + + " in window:" + windowId); + } else { + inputFocus = node; + } + } + + // The node should be a child of its parent if we have the parent. + AccessibilityNodeInfo nodeParent = nodes.get(node.getParentNodeId()); + if (nodeParent != null) { + boolean childOfItsParent = false; + final int childCount = nodeParent.getChildCount(); + for (int k = 0; k < childCount; k++) { + AccessibilityNodeInfo child = nodes.get(nodeParent.getChildId(k)); + if (child == node) { + childOfItsParent = true; + break; + } + } + if (!childOfItsParent) { + Log.e(LOG_TAG, "Invalid parent-child ralation between parent: " + + nodeParent + " and child: " + node); + } + } + + // The node should be the parent of its child if we have the child. + final int childCount = node.getChildCount(); + for (int k = 0; k < childCount; k++) { + AccessibilityNodeInfo child = nodes.get(node.getChildId(k)); + if (child != null) { + AccessibilityNodeInfo parent = nodes.get(child.getParentNodeId()); + if (parent != node) { + Log.e(LOG_TAG, "Invalid child-parent ralation between child: " + + node + " and parent: " + nodeParent); + } + } + } + } + } + } + } +} diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java index 8b91155..417e22c 100644 --- a/core/java/android/view/accessibility/AccessibilityEvent.java +++ b/core/java/android/view/accessibility/AccessibilityEvent.java @@ -343,6 +343,23 @@ import java.util.List; * view.</br> * </p> * <p> + * <b>Windows changed</b> - represents the event of changes in the windows shown on + * the screen such as a window appeared, a window disappeared, a window size changed, + * a window layer changed, etc.</br> + * <em>Type:</em> {@link #TYPE_WINDOWS_CHANGED}</br> + * <em>Properties:</em></br> + * <ul> + * <li>{@link #getEventType()} - The type of the event.</li> + * <li>{@link #getEventTime()} - The event time.</li> + * </ul> + * <em>Note:</em> You can retrieve the {@link AccessibilityWindowInfo} for the window + * source of the event via {@link AccessibilityEvent#getSource()} to get the source + * node on which then call {@link AccessibilityNodeInfo#getWindow() + * AccessibilityNodeInfo.getWindow()} to get the window. Also all windows on the screen can + * be retrieved by a call to {@link android.accessibilityservice.AccessibilityService#getWindows() + * android.accessibilityservice.AccessibilityService.getWindows()}. + * </p> + * <p> * <b>NOTIFICATION TYPES</b></br> * </p> * <p> @@ -662,6 +679,11 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par public static final int TYPE_TOUCH_INTERACTION_END = 0x00200000; /** + * Represents the event change in the windows shown on the screen. + */ + public static final int TYPE_WINDOWS_CHANGED = 0x00400000; + + /** * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event: * The type of change is not defined. */ @@ -708,6 +730,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par * @see #TYPE_GESTURE_DETECTION_END * @see #TYPE_TOUCH_INTERACTION_START * @see #TYPE_TOUCH_INTERACTION_END + * @see #TYPE_WINDOWS_CHANGED */ public static final int TYPES_ALL_MASK = 0xFFFFFFFF; @@ -1366,6 +1389,13 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par builder.append("TYPE_TOUCH_INTERACTION_END"); eventTypeCount++; } break; + case TYPE_WINDOWS_CHANGED: { + if (eventTypeCount > 0) { + builder.append(", "); + } + builder.append("TYPE_WINDOWS_CHANGED"); + eventTypeCount++; + } break; } } if (eventTypeCount > 1) { diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index 5a55e34..4dd8dcb 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -100,14 +100,11 @@ public final class AccessibilityInteractionClient private Message mSameThreadMessage; - // The connection cache is shared between all interrogating threads. private static final SparseArray<IAccessibilityServiceConnection> sConnectionCache = new SparseArray<IAccessibilityServiceConnection>(); - // The connection cache is shared between all interrogating threads since - // at any given time there is only one window allowing querying. - private static final AccessibilityNodeInfoCache sAccessibilityNodeInfoCache = - new AccessibilityNodeInfoCache(); + private static final AccessibilityCache sAccessibilityCache = + new AccessibilityCache(); /** * @return The client for the current thread. @@ -166,6 +163,86 @@ public final class AccessibilityInteractionClient } /** + * Gets the info for a window. + * + * @param connectionId The id of a connection for interacting with the system. + * @param accessibilityWindowId A unique window id. Use + * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID} + * to query the currently active window. + * @return The {@link AccessibilityWindowInfo}. + */ + public AccessibilityWindowInfo getWindow(int connectionId, int accessibilityWindowId) { + try { + IAccessibilityServiceConnection connection = getConnection(connectionId); + if (connection != null) { + AccessibilityWindowInfo window = sAccessibilityCache.getWindow( + accessibilityWindowId); + if (window != null) { + if (DEBUG) { + Log.i(LOG_TAG, "Window cache hit"); + } + return window; + } + if (DEBUG) { + Log.i(LOG_TAG, "Window cache miss"); + } + window = connection.getWindow(accessibilityWindowId); + if (window != null) { + sAccessibilityCache.addWindow(window); + return window; + } + } else { + if (DEBUG) { + Log.w(LOG_TAG, "No connection for connection id: " + connectionId); + } + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error while calling remote getWindow", re); + } + return null; + } + + /** + * Gets the info for all windows. + * + * @param connectionId The id of a connection for interacting with the system. + * @return The {@link AccessibilityWindowInfo} list. + */ + public List<AccessibilityWindowInfo> getWindows(int connectionId) { + try { + IAccessibilityServiceConnection connection = getConnection(connectionId); + if (connection != null) { + List<AccessibilityWindowInfo> windows = sAccessibilityCache.getWindows(); + if (windows != null) { + if (DEBUG) { + Log.i(LOG_TAG, "Window cache hit"); + } + return windows; + } + if (DEBUG) { + Log.i(LOG_TAG, "Window cache miss"); + } + windows = connection.getWindows(); + if (windows != null) { + final int windowCount = windows.size(); + for (int i = 0; i < windowCount; i++) { + AccessibilityWindowInfo window = windows.get(i); + sAccessibilityCache.addWindow(window); + } + return windows; + } + } else { + if (DEBUG) { + Log.w(LOG_TAG, "No connection for connection id: " + connectionId); + } + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error while calling remote getWindows", re); + } + return Collections.emptyList(); + } + + /** * Finds an {@link AccessibilityNodeInfo} by accessibility id. * * @param connectionId The id of a connection for interacting with the system. @@ -183,15 +260,26 @@ public final class AccessibilityInteractionClient public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(int connectionId, int accessibilityWindowId, long accessibilityNodeId, boolean bypassCache, int prefetchFlags) { + if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0 + && (prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) == 0) { + throw new IllegalArgumentException("FLAG_PREFETCH_SIBLINGS" + + " requires FLAG_PREFETCH_PREDECESSORS"); + } try { IAccessibilityServiceConnection connection = getConnection(connectionId); if (connection != null) { if (!bypassCache) { - AccessibilityNodeInfo cachedInfo = sAccessibilityNodeInfoCache.get( - accessibilityNodeId); + AccessibilityNodeInfo cachedInfo = sAccessibilityCache.getNode( + accessibilityWindowId, accessibilityNodeId); if (cachedInfo != null) { + if (DEBUG) { + Log.i(LOG_TAG, "Node cache hit"); + } return cachedInfo; } + if (DEBUG) { + Log.i(LOG_TAG, "Node cache miss"); + } } final int interactionId = mInteractionIdCounter.getAndIncrement(); final boolean success = connection.findAccessibilityNodeInfoByAccessibilityId( @@ -212,10 +300,8 @@ public final class AccessibilityInteractionClient } } } catch (RemoteException re) { - if (DEBUG) { - Log.w(LOG_TAG, "Error while calling remote" - + " findAccessibilityNodeInfoByAccessibilityId", re); - } + Log.e(LOG_TAG, "Error while calling remote" + + " findAccessibilityNodeInfoByAccessibilityId", re); } return null; } @@ -259,10 +345,8 @@ public final class AccessibilityInteractionClient } } } catch (RemoteException re) { - if (DEBUG) { - Log.w(LOG_TAG, "Error while calling remote" - + " findAccessibilityNodeInfoByViewIdInActiveWindow", re); - } + Log.w(LOG_TAG, "Error while calling remote" + + " findAccessibilityNodeInfoByViewIdInActiveWindow", re); } return Collections.emptyList(); } @@ -307,10 +391,8 @@ public final class AccessibilityInteractionClient } } } catch (RemoteException re) { - if (DEBUG) { - Log.w(LOG_TAG, "Error while calling remote" - + " findAccessibilityNodeInfosByViewText", re); - } + Log.w(LOG_TAG, "Error while calling remote" + + " findAccessibilityNodeInfosByViewText", re); } return Collections.emptyList(); } @@ -352,9 +434,7 @@ public final class AccessibilityInteractionClient } } } catch (RemoteException re) { - if (DEBUG) { - Log.w(LOG_TAG, "Error while calling remote findFocus", re); - } + Log.w(LOG_TAG, "Error while calling remote findFocus", re); } return null; } @@ -396,9 +476,7 @@ public final class AccessibilityInteractionClient } } } catch (RemoteException re) { - if (DEBUG) { - Log.w(LOG_TAG, "Error while calling remote accessibilityFocusSearch", re); - } + Log.w(LOG_TAG, "Error while calling remote accessibilityFocusSearch", re); } return null; } @@ -436,19 +514,21 @@ public final class AccessibilityInteractionClient } } } catch (RemoteException re) { - if (DEBUG) { - Log.w(LOG_TAG, "Error while calling remote performAccessibilityAction", re); - } + Log.w(LOG_TAG, "Error while calling remote performAccessibilityAction", re); } return false; } public void clearCache() { - sAccessibilityNodeInfoCache.clear(); + sAccessibilityCache.clear(); } public void onAccessibilityEvent(AccessibilityEvent event) { - sAccessibilityNodeInfoCache.onAccessibilityEvent(event); + sAccessibilityCache.onAccessibilityEvent(event); + } + + public void removeWindows(int[] windowIds) { + sAccessibilityCache.removeWindows(windowIds); } /** @@ -613,7 +693,7 @@ public final class AccessibilityInteractionClient if (info != null) { info.setConnectionId(connectionId); info.setSealed(true); - sAccessibilityNodeInfoCache.add(info); + sAccessibilityCache.add(info); } } diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index a711f48..116c173 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -115,9 +115,9 @@ public final class AccessibilityManager { private static AccessibilityManager sInstance; - private static final int DO_SET_STATE = 10; + private final Object mLock = new Object(); - final IAccessibilityManager mService; + private IAccessibilityManager mService; final int mUserId; @@ -166,29 +166,14 @@ public final class AccessibilityManager { public void onTouchExplorationStateChanged(boolean enabled); } - final IAccessibilityManagerClient.Stub mClient = new IAccessibilityManagerClient.Stub() { + private final IAccessibilityManagerClient.Stub mClient = + new IAccessibilityManagerClient.Stub() { public void setState(int state) { - mHandler.obtainMessage(DO_SET_STATE, state, 0).sendToTarget(); - } - }; - - class MyHandler extends Handler { - - MyHandler(Looper mainLooper) { - super(mainLooper); - } - - @Override - public void handleMessage(Message message) { - switch (message.what) { - case DO_SET_STATE : - setState(message.arg1); - return; - default : - Log.w(LOG_TAG, "Unknown message type: " + message.what); + synchronized (mLock) { + setStateLocked(state); } } - } + }; /** * Get an AccessibilityManager instance (create one if necessary). @@ -234,16 +219,8 @@ public final class AccessibilityManager { mHandler = new MyHandler(context.getMainLooper()); mService = service; mUserId = userId; - if (mService == null) { - mIsEnabled = false; - } - try { - if (mService != null) { - final int stateFlags = mService.addClient(mClient, userId); - setState(stateFlags); - } - } catch (RemoteException re) { - Log.e(LOG_TAG, "AccessibilityManagerService is dead", re); + synchronized (mLock) { + tryConnectToServiceLocked(); } } @@ -253,7 +230,11 @@ public final class AccessibilityManager { * @return True if accessibility is enabled, false otherwise. */ public boolean isEnabled() { - synchronized (mHandler) { + synchronized (mLock) { + IAccessibilityManager service = getServiceLocked(); + if (service == null) { + return false; + } return mIsEnabled; } } @@ -264,24 +245,16 @@ public final class AccessibilityManager { * @return True if touch exploration is enabled, false otherwise. */ public boolean isTouchExplorationEnabled() { - synchronized (mHandler) { + synchronized (mLock) { + IAccessibilityManager service = getServiceLocked(); + if (service == null) { + return false; + } return mIsTouchExplorationEnabled; } } /** - * Returns the client interface this instance registers in - * the centralized accessibility manager service. - * - * @return The client. - * - * @hide - */ - public IAccessibilityManagerClient getClient() { - return (IAccessibilityManagerClient) mClient.asBinder(); - } - - /** * Sends an {@link AccessibilityEvent}. * * @param event The event to send. @@ -295,8 +268,17 @@ public final class AccessibilityManager { * their descendants. */ public void sendAccessibilityEvent(AccessibilityEvent event) { - if (!mIsEnabled) { - throw new IllegalStateException("Accessibility off. Did you forget to check that?"); + final IAccessibilityManager service; + final int userId; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + if (!mIsEnabled) { + throw new IllegalStateException("Accessibility off. Did you forget to check that?"); + } + userId = mUserId; } boolean doRecycle = false; try { @@ -305,7 +287,7 @@ public final class AccessibilityManager { // client using it is called through Binder from another process. Example: MMS // app adds a SMS notification and the NotificationManagerService calls this method long identityToken = Binder.clearCallingIdentity(); - doRecycle = mService.sendAccessibilityEvent(event, mUserId); + doRecycle = service.sendAccessibilityEvent(event, userId); Binder.restoreCallingIdentity(identityToken); if (DEBUG) { Log.i(LOG_TAG, event + " sent"); @@ -323,11 +305,20 @@ public final class AccessibilityManager { * Requests feedback interruption from all accessibility services. */ public void interrupt() { - if (!mIsEnabled) { - throw new IllegalStateException("Accessibility off. Did you forget to check that?"); + final IAccessibilityManager service; + final int userId; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + if (!mIsEnabled) { + throw new IllegalStateException("Accessibility off. Did you forget to check that?"); + } + userId = mUserId; } try { - mService.interrupt(mUserId); + service.interrupt(userId); if (DEBUG) { Log.i(LOG_TAG, "Requested interrupt from all services"); } @@ -361,18 +352,30 @@ public final class AccessibilityManager { * @return An unmodifiable list with {@link AccessibilityServiceInfo}s. */ public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() { + final IAccessibilityManager service; + final int userId; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return Collections.emptyList(); + } + userId = mUserId; + } + List<AccessibilityServiceInfo> services = null; try { - if (mService != null) { - services = mService.getInstalledAccessibilityServiceList(mUserId); - if (DEBUG) { - Log.i(LOG_TAG, "Installed AccessibilityServices " + services); - } + services = service.getInstalledAccessibilityServiceList(userId); + if (DEBUG) { + Log.i(LOG_TAG, "Installed AccessibilityServices " + services); } } catch (RemoteException re) { Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re); } - return services != null ? Collections.unmodifiableList(services) : Collections.EMPTY_LIST; + if (services != null) { + return Collections.unmodifiableList(services); + } else { + return Collections.emptyList(); + } } /** @@ -390,18 +393,30 @@ public final class AccessibilityManager { */ public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList( int feedbackTypeFlags) { + final IAccessibilityManager service; + final int userId; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return Collections.emptyList(); + } + userId = mUserId; + } + List<AccessibilityServiceInfo> services = null; try { - if (mService != null) { - services = mService.getEnabledAccessibilityServiceList(feedbackTypeFlags, mUserId); - if (DEBUG) { - Log.i(LOG_TAG, "Installed AccessibilityServices " + services); - } + services = service.getEnabledAccessibilityServiceList(feedbackTypeFlags, userId); + if (DEBUG) { + Log.i(LOG_TAG, "Installed AccessibilityServices " + services); } } catch (RemoteException re) { Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re); } - return services != null ? Collections.unmodifiableList(services) : Collections.EMPTY_LIST; + if (services != null) { + return Collections.unmodifiableList(services); + } else { + return Collections.emptyList(); + } } /** @@ -413,6 +428,7 @@ public final class AccessibilityManager { */ public boolean addAccessibilityStateChangeListener( AccessibilityStateChangeListener listener) { + // Final CopyOnArrayList - no lock needed. return mAccessibilityStateChangeListeners.add(listener); } @@ -424,6 +440,7 @@ public final class AccessibilityManager { */ public boolean removeAccessibilityStateChangeListener( AccessibilityStateChangeListener listener) { + // Final CopyOnArrayList - no lock needed. return mAccessibilityStateChangeListeners.remove(listener); } @@ -436,6 +453,7 @@ public final class AccessibilityManager { */ public boolean addTouchExplorationStateChangeListener( TouchExplorationStateChangeListener listener) { + // Final CopyOnArrayList - no lock needed. return mTouchExplorationStateChangeListeners.add(listener); } @@ -447,6 +465,7 @@ public final class AccessibilityManager { */ public boolean removeTouchExplorationStateChangeListener( TouchExplorationStateChangeListener listener) { + // Final CopyOnArrayList - no lock needed. return mTouchExplorationStateChangeListeners.remove(listener); } @@ -455,50 +474,24 @@ public final class AccessibilityManager { * * @param stateFlags The state flags. */ - private void setState(int stateFlags) { + private void setStateLocked(int stateFlags) { final boolean enabled = (stateFlags & STATE_FLAG_ACCESSIBILITY_ENABLED) != 0; final boolean touchExplorationEnabled = (stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0; - synchronized (mHandler) { - final boolean wasEnabled = mIsEnabled; - final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled; - // Ensure listeners get current state from isZzzEnabled() calls. - mIsEnabled = enabled; - mIsTouchExplorationEnabled = touchExplorationEnabled; + final boolean wasEnabled = mIsEnabled; + final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled; - if (wasEnabled != enabled) { - notifyAccessibilityStateChangedLh(); - } + // Ensure listeners get current state from isZzzEnabled() calls. + mIsEnabled = enabled; + mIsTouchExplorationEnabled = touchExplorationEnabled; - if (wasTouchExplorationEnabled != touchExplorationEnabled) { - notifyTouchExplorationStateChangedLh(); - } + if (wasEnabled != enabled) { + mHandler.sendEmptyMessage(MyHandler.MSG_NOTIFY_ACCESSIBILITY_STATE_CHANGED); } - } - - /** - * Notifies the registered {@link AccessibilityStateChangeListener}s. - * <p> - * The caller must be locked on {@link #mHandler}. - */ - private void notifyAccessibilityStateChangedLh() { - final int listenerCount = mAccessibilityStateChangeListeners.size(); - for (int i = 0; i < listenerCount; i++) { - mAccessibilityStateChangeListeners.get(i).onAccessibilityStateChanged(mIsEnabled); - } - } - /** - * Notifies the registered {@link TouchExplorationStateChangeListener}s. - * <p> - * The caller must be locked on {@link #mHandler}. - */ - private void notifyTouchExplorationStateChangedLh() { - final int listenerCount = mTouchExplorationStateChangeListeners.size(); - for (int i = 0; i < listenerCount; i++) { - mTouchExplorationStateChangeListeners.get(i) - .onTouchExplorationStateChanged(mIsTouchExplorationEnabled); + if (wasTouchExplorationEnabled != touchExplorationEnabled) { + mHandler.sendEmptyMessage(MyHandler.MSG_NOTIFY_EXPLORATION_STATE_CHANGED); } } @@ -511,11 +504,17 @@ public final class AccessibilityManager { */ public int addAccessibilityInteractionConnection(IWindow windowToken, IAccessibilityInteractionConnection connection) { - if (mService == null) { - return View.NO_ID; + final IAccessibilityManager service; + final int userId; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return View.NO_ID; + } + userId = mUserId; } try { - return mService.addAccessibilityInteractionConnection(windowToken, connection, mUserId); + return service.addAccessibilityInteractionConnection(windowToken, connection, userId); } catch (RemoteException re) { Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re); } @@ -529,12 +528,90 @@ public final class AccessibilityManager { * @hide */ public void removeAccessibilityInteractionConnection(IWindow windowToken) { - try { - if (mService != null) { - mService.removeAccessibilityInteractionConnection(windowToken); + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; } + } + try { + service.removeAccessibilityInteractionConnection(windowToken); } catch (RemoteException re) { Log.e(LOG_TAG, "Error while removing an accessibility interaction connection. ", re); } } + + private IAccessibilityManager getServiceLocked() { + if (mService == null) { + tryConnectToServiceLocked(); + } + return mService; + } + + private void tryConnectToServiceLocked() { + IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE); + if (iBinder == null) { + return; + } + IAccessibilityManager service = IAccessibilityManager.Stub.asInterface(iBinder); + try { + final int stateFlags = service.addClient(mClient, mUserId); + setStateLocked(stateFlags); + mService = service; + } catch (RemoteException re) { + Log.e(LOG_TAG, "AccessibilityManagerService is dead", re); + } + } + + /** + * Notifies the registered {@link AccessibilityStateChangeListener}s. + */ + private void handleNotifyAccessibilityStateChanged() { + final boolean isEnabled; + synchronized (mLock) { + isEnabled = mIsEnabled; + } + final int listenerCount = mAccessibilityStateChangeListeners.size(); + for (int i = 0; i < listenerCount; i++) { + mAccessibilityStateChangeListeners.get(i).onAccessibilityStateChanged(isEnabled); + } + } + + /** + * Notifies the registered {@link TouchExplorationStateChangeListener}s. + */ + private void handleNotifyTouchExplorationStateChanged() { + final boolean isTouchExplorationEnabled; + synchronized (mLock) { + isTouchExplorationEnabled = mIsTouchExplorationEnabled; + } + final int listenerCount = mTouchExplorationStateChangeListeners.size(); + for (int i = 0; i < listenerCount; i++) { + mTouchExplorationStateChangeListeners.get(i) + .onTouchExplorationStateChanged(isTouchExplorationEnabled); + } + } + + private final class MyHandler extends Handler { + public static final int MSG_NOTIFY_ACCESSIBILITY_STATE_CHANGED = 1; + public static final int MSG_NOTIFY_EXPLORATION_STATE_CHANGED = 2; + + public MyHandler(Looper looper) { + super(looper, null, false); + } + + @Override + public void handleMessage(Message message) { + switch (message.what) { + case MSG_NOTIFY_ACCESSIBILITY_STATE_CHANGED: { + handleNotifyAccessibilityStateChanged(); + } break; + + case MSG_NOTIFY_EXPLORATION_STATE_CHANGED: { + handleNotifyTouchExplorationStateChanged(); + } + } + } + } } diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 560d0c9..a6904f7 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -62,13 +62,19 @@ public class AccessibilityNodeInfo implements Parcelable { private static final boolean DEBUG = false; /** @hide */ - public static final int UNDEFINED = -1; + public static final int UNDEFINED_CONNECTION_ID = -1; /** @hide */ - public static final long ROOT_NODE_ID = makeNodeId(UNDEFINED, UNDEFINED); + public static final int UNDEFINED_SELECTION_INDEX = -1; /** @hide */ - public static final int ACTIVE_WINDOW_ID = UNDEFINED; + public static final int UNDEFINED_ITEM_ID = Integer.MAX_VALUE; + + /** @hide */ + public static final long ROOT_NODE_ID = makeNodeId(UNDEFINED_ITEM_ID, UNDEFINED_ITEM_ID); + + /** @hide */ + public static final int ACTIVE_WINDOW_ID = UNDEFINED_ITEM_ID; /** @hide */ public static final int FLAG_PREFETCH_PREDECESSORS = 0x00000001; @@ -504,6 +510,13 @@ public class AccessibilityNodeInfo implements Parcelable { * @hide */ public static long makeNodeId(int accessibilityViewId, int virtualDescendantId) { + // We changed the value for undefined node to positive due to wrong + // global id composition (two 32-bin ints into one 64-bit long) but + // the value used for the host node provider view has id -1 so we + // remap it here. + if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) { + virtualDescendantId = UNDEFINED_ITEM_ID; + } return (((long) virtualDescendantId) << VIRTUAL_DESCENDANT_ID_SHIFT) | accessibilityViewId; } @@ -515,7 +528,7 @@ public class AccessibilityNodeInfo implements Parcelable { private boolean mSealed; // Data. - private int mWindowId = UNDEFINED; + private int mWindowId = UNDEFINED_ITEM_ID; private long mSourceNodeId = ROOT_NODE_ID; private long mParentNodeId = ROOT_NODE_ID; private long mLabelForId = ROOT_NODE_ID; @@ -536,14 +549,14 @@ public class AccessibilityNodeInfo implements Parcelable { private int mMovementGranularities; - private int mTextSelectionStart = UNDEFINED; - private int mTextSelectionEnd = UNDEFINED; + private int mTextSelectionStart = UNDEFINED_SELECTION_INDEX; + private int mTextSelectionEnd = UNDEFINED_SELECTION_INDEX; private int mInputType = InputType.TYPE_NULL; private int mLiveRegion = View.ACCESSIBILITY_LIVE_REGION_NONE; private Bundle mExtras; - private int mConnectionId = UNDEFINED; + private int mConnectionId = UNDEFINED_CONNECTION_ID; private RangeInfo mRangeInfo; private CollectionInfo mCollectionInfo; @@ -567,7 +580,7 @@ public class AccessibilityNodeInfo implements Parcelable { * @param source The info source. */ public void setSource(View source) { - setSource(source, UNDEFINED); + setSource(source, UNDEFINED_ITEM_ID); } /** @@ -591,9 +604,9 @@ public class AccessibilityNodeInfo implements Parcelable { */ public void setSource(View root, int virtualDescendantId) { enforceNotSealed(); - mWindowId = (root != null) ? root.getAccessibilityWindowId() : UNDEFINED; + mWindowId = (root != null) ? root.getAccessibilityWindowId() : UNDEFINED_ITEM_ID; final int rootAccessibilityViewId = - (root != null) ? root.getAccessibilityViewId() : UNDEFINED; + (root != null) ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID; mSourceNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId); } @@ -766,7 +779,7 @@ public class AccessibilityNodeInfo implements Parcelable { * @throws IllegalStateException If called from an AccessibilityService. */ public void addChild(View child) { - addChildInternal(child, UNDEFINED, true); + addChildInternal(child, UNDEFINED_ITEM_ID, true); } /** @@ -776,7 +789,7 @@ public class AccessibilityNodeInfo implements Parcelable { * @hide */ public void addChildUnchecked(View child) { - addChildInternal(child, UNDEFINED, false); + addChildInternal(child, UNDEFINED_ITEM_ID, false); } /** @@ -794,7 +807,7 @@ public class AccessibilityNodeInfo implements Parcelable { * @throws IllegalStateException If called from an AccessibilityService. */ public boolean removeChild(View child) { - return removeChild(child, UNDEFINED); + return removeChild(child, UNDEFINED_ITEM_ID); } /** @@ -821,7 +834,7 @@ public class AccessibilityNodeInfo implements Parcelable { mChildNodeIds = new LongArray(); } final int rootAccessibilityViewId = - (root != null) ? root.getAccessibilityViewId() : UNDEFINED; + (root != null) ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID; final long childNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId); // If we're checking uniqueness and the ID already exists, abort. if (checked && mChildNodeIds.indexOf(childNodeId) >= 0) { @@ -847,7 +860,7 @@ public class AccessibilityNodeInfo implements Parcelable { return false; } final int rootAccessibilityViewId = - (root != null) ? root.getAccessibilityViewId() : UNDEFINED; + (root != null) ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID; final long childNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId); final int index = childIds.indexOf(childNodeId); if (index < 0) { @@ -1043,6 +1056,22 @@ public class AccessibilityNodeInfo implements Parcelable { } /** + * Gets the window to which this node belongs. + * + * @return The window. + * + * @see android.accessibilityservice.AccessibilityService#getWindows() + */ + public AccessibilityWindowInfo getWindow() { + enforceSealed(); + if (!canPerformRequestOverConnection(mSourceNodeId)) { + return null; + } + AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); + return client.getWindow(mConnectionId, mWindowId); + } + + /** * Gets the parent. * <p> * <strong>Note:</strong> It is a client responsibility to recycle the @@ -1059,7 +1088,8 @@ public class AccessibilityNodeInfo implements Parcelable { } AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, - mWindowId, mParentNodeId, false, FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS); + mWindowId, mParentNodeId, false, FLAG_PREFETCH_PREDECESSORS + | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS); } /** @@ -1084,7 +1114,7 @@ public class AccessibilityNodeInfo implements Parcelable { * @throws IllegalStateException If called from an AccessibilityService. */ public void setParent(View parent) { - setParent(parent, UNDEFINED); + setParent(parent, UNDEFINED_ITEM_ID); } /** @@ -1109,7 +1139,7 @@ public class AccessibilityNodeInfo implements Parcelable { public void setParent(View root, int virtualDescendantId) { enforceNotSealed(); final int rootAccessibilityViewId = - (root != null) ? root.getAccessibilityViewId() : UNDEFINED; + (root != null) ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID; mParentNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId); } @@ -1811,7 +1841,7 @@ public class AccessibilityNodeInfo implements Parcelable { * @param labeled The view for which this info serves as a label. */ public void setLabelFor(View labeled) { - setLabelFor(labeled, UNDEFINED); + setLabelFor(labeled, UNDEFINED_ITEM_ID); } /** @@ -1836,7 +1866,7 @@ public class AccessibilityNodeInfo implements Parcelable { public void setLabelFor(View root, int virtualDescendantId) { enforceNotSealed(); final int rootAccessibilityViewId = (root != null) - ? root.getAccessibilityViewId() : UNDEFINED; + ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID; mLabelForId = makeNodeId(rootAccessibilityViewId, virtualDescendantId); } @@ -1858,7 +1888,8 @@ public class AccessibilityNodeInfo implements Parcelable { } AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, - mWindowId, mLabelForId, false, FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS); + mWindowId, mLabelForId, false, FLAG_PREFETCH_PREDECESSORS + | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS); } /** @@ -1868,7 +1899,7 @@ public class AccessibilityNodeInfo implements Parcelable { * @param label The view that labels this node's source. */ public void setLabeledBy(View label) { - setLabeledBy(label, UNDEFINED); + setLabeledBy(label, UNDEFINED_ITEM_ID); } /** @@ -1893,7 +1924,7 @@ public class AccessibilityNodeInfo implements Parcelable { public void setLabeledBy(View root, int virtualDescendantId) { enforceNotSealed(); final int rootAccessibilityViewId = (root != null) - ? root.getAccessibilityViewId() : UNDEFINED; + ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID; mLabeledById = makeNodeId(rootAccessibilityViewId, virtualDescendantId); } @@ -1915,7 +1946,8 @@ public class AccessibilityNodeInfo implements Parcelable { } AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, - mWindowId, mLabeledById, false, FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS); + mWindowId, mLabeledById, false, FLAG_PREFETCH_PREDECESSORS + | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS); } /** @@ -2362,6 +2394,7 @@ public class AccessibilityNodeInfo implements Parcelable { if (mChildNodeIds == null) { mChildNodeIds = otherChildNodeIds.clone(); } else { + mChildNodeIds.clear(); mChildNodeIds.addAll(otherChildNodeIds); } } @@ -2474,8 +2507,8 @@ public class AccessibilityNodeInfo implements Parcelable { mParentNodeId = ROOT_NODE_ID; mLabelForId = ROOT_NODE_ID; mLabeledById = ROOT_NODE_ID; - mWindowId = UNDEFINED; - mConnectionId = UNDEFINED; + mWindowId = UNDEFINED_ITEM_ID; + mConnectionId = UNDEFINED_CONNECTION_ID; mMovementGranularities = 0; if (mChildNodeIds != null) { mChildNodeIds.clear(); @@ -2489,8 +2522,8 @@ public class AccessibilityNodeInfo implements Parcelable { mContentDescription = null; mViewIdResourceName = null; mActions = 0; - mTextSelectionStart = UNDEFINED; - mTextSelectionEnd = UNDEFINED; + mTextSelectionStart = UNDEFINED_SELECTION_INDEX; + mTextSelectionEnd = UNDEFINED_SELECTION_INDEX; mInputType = InputType.TYPE_NULL; mLiveRegion = View.ACCESSIBILITY_LIVE_REGION_NONE; if (mExtras != null) { @@ -2583,9 +2616,9 @@ public class AccessibilityNodeInfo implements Parcelable { } private boolean canPerformRequestOverConnection(long accessibilityNodeId) { - return (mWindowId != UNDEFINED - && getAccessibilityViewId(accessibilityNodeId) != UNDEFINED - && mConnectionId != UNDEFINED); + return (mWindowId != UNDEFINED_ITEM_ID + && getAccessibilityViewId(accessibilityNodeId) != UNDEFINED_ITEM_ID + && mConnectionId != UNDEFINED_CONNECTION_ID); } @Override @@ -2625,6 +2658,7 @@ public class AccessibilityNodeInfo implements Parcelable { builder.append(super.toString()); if (DEBUG) { + builder.append("; sourceNodeId: " + mSourceNodeId); builder.append("; accessibilityViewId: " + getAccessibilityViewId(mSourceNodeId)); builder.append("; virtualDescendantId: " + getVirtualDescendantId(mSourceNodeId)); builder.append("; mParentNodeId: " + mParentNodeId); diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java b/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java deleted file mode 100644 index b4944be..0000000 --- a/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java +++ /dev/null @@ -1,336 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view.accessibility; - -import android.os.Build; -import android.util.Log; -import android.util.LongArray; -import android.util.LongSparseArray; - -import java.util.HashSet; -import java.util.LinkedList; -import java.util.Queue; - -/** - * Simple cache for AccessibilityNodeInfos. The cache is mapping an - * accessibility id to an info. The cache allows storing of - * <code>null</code> values. It also tracks accessibility events - * and invalidates accordingly. - * - * @hide - */ -public class AccessibilityNodeInfoCache { - - private static final String LOG_TAG = AccessibilityNodeInfoCache.class.getSimpleName(); - - private static final boolean ENABLED = true; - - private static final boolean DEBUG = false; - - private static final boolean CHECK_INTEGRITY_IF_DEBUGGABLE_BUILD = true; - - private final Object mLock = new Object(); - - private final LongSparseArray<AccessibilityNodeInfo> mCacheImpl; - - private int mWindowId; - - public AccessibilityNodeInfoCache() { - if (ENABLED) { - mCacheImpl = new LongSparseArray<AccessibilityNodeInfo>(); - } else { - mCacheImpl = null; - } - } - - /** - * The cache keeps track of {@link AccessibilityEvent}s and invalidates - * cached nodes as appropriate. - * - * @param event An event. - */ - public void onAccessibilityEvent(AccessibilityEvent event) { - if (ENABLED) { - final int eventType = event.getEventType(); - switch (eventType) { - case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END: - case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: - case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER: - case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT: { - // If the active window changes, clear the cache. - final int windowId = event.getWindowId(); - if (mWindowId != windowId) { - mWindowId = windowId; - clear(); - } - } break; - case AccessibilityEvent.TYPE_VIEW_FOCUSED: - case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: - case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: - case AccessibilityEvent.TYPE_VIEW_SELECTED: - case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED: - case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED: { - refreshCachedNode(event.getSourceNodeId()); - } break; - case AccessibilityEvent.TYPE_VIEW_SCROLLED: { - synchronized (mLock) { - clearSubTreeLocked(event.getSourceNodeId()); - } - } break; - case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: { - synchronized (mLock) { - final long sourceId = event.getSourceNodeId(); - if ((event.getContentChangeTypes() - & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE) != 0) { - clearSubTreeLocked(sourceId); - } else { - refreshCachedNode(sourceId); - } - } - } break; - } - if (CHECK_INTEGRITY_IF_DEBUGGABLE_BUILD && Build.IS_DEBUGGABLE) { - checkIntegrity(); - } - } - } - - private void refreshCachedNode(long sourceId) { - if (DEBUG) { - Log.i(LOG_TAG, "Refreshing cached node."); - } - synchronized (mLock) { - AccessibilityNodeInfo cachedInfo = mCacheImpl.get(sourceId); - // If the source is not in the cache - nothing to do. - if (cachedInfo == null) { - return; - } - // The node changed so we will just refresh it right now. - if (cachedInfo.refresh(true)) { - return; - } - // Weird, we could not refresh. Just evict the entire sub-tree. - clearSubTreeLocked(sourceId); - } - } - - /** - * Gets a cached {@link AccessibilityNodeInfo} given its accessibility node id. - * - * @param accessibilityNodeId The info accessibility node id. - * @return The cached {@link AccessibilityNodeInfo} or null if such not found. - */ - public AccessibilityNodeInfo get(long accessibilityNodeId) { - if (ENABLED) { - synchronized(mLock) { - AccessibilityNodeInfo info = mCacheImpl.get(accessibilityNodeId); - if (info != null) { - // Return a copy since the client calls to AccessibilityNodeInfo#recycle() - // will wipe the data of the cached info. - info = AccessibilityNodeInfo.obtain(info); - } - if (DEBUG) { - Log.i(LOG_TAG, "get(" + accessibilityNodeId + ") = " + info); - } - return info; - } - } else { - return null; - } - } - - /** - * Caches an {@link AccessibilityNodeInfo} given its accessibility node id. - * - * @param info The {@link AccessibilityNodeInfo} to cache. - */ - public void add(AccessibilityNodeInfo info) { - if (ENABLED) { - synchronized(mLock) { - if (DEBUG) { - Log.i(LOG_TAG, "add(" + info + ")"); - } - - final long sourceId = info.getSourceNodeId(); - AccessibilityNodeInfo oldInfo = mCacheImpl.get(sourceId); - if (oldInfo != null) { - // If the added node is in the cache we have to be careful if - // the new one represents a source state where some of the - // children have been removed to avoid having disconnected - // subtrees in the cache. - // TODO: Runs in O(n^2), could optimize to O(n + n log n) - final LongArray newChildrenIds = info.getChildNodeIds(); - if (newChildrenIds != null) { - final int oldChildCount = oldInfo.getChildCount(); - for (int i = 0; i < oldChildCount; i++) { - final long oldChildId = oldInfo.getChildId(i); - if (newChildrenIds.indexOf(oldChildId) < 0) { - clearSubTreeLocked(oldChildId); - } - } - } - - // Also be careful if the parent has changed since the new - // parent may be a predecessor of the old parent which will - // make the cached tree cyclic. - final long oldParentId = oldInfo.getParentNodeId(); - if (info.getParentNodeId() != oldParentId) { - clearSubTreeLocked(oldParentId); - } - } - - // Cache a copy since the client calls to AccessibilityNodeInfo#recycle() - // will wipe the data of the cached info. - AccessibilityNodeInfo clone = AccessibilityNodeInfo.obtain(info); - mCacheImpl.put(sourceId, clone); - } - } - } - - /** - * Clears the cache. - */ - public void clear() { - if (ENABLED) { - synchronized(mLock) { - if (DEBUG) { - Log.i(LOG_TAG, "clear()"); - } - // Recycle the nodes before clearing the cache. - final int nodeCount = mCacheImpl.size(); - for (int i = 0; i < nodeCount; i++) { - AccessibilityNodeInfo info = mCacheImpl.valueAt(i); - info.recycle(); - } - mCacheImpl.clear(); - } - } - } - - /** - * Clears a subtree rooted at the node with the given id. - * - * @param rootNodeId The root id. - */ - private void clearSubTreeLocked(long rootNodeId) { - if (DEBUG) { - Log.i(LOG_TAG, "Clearing cached subtree."); - } - clearSubTreeRecursiveLocked(rootNodeId); - } - - private void clearSubTreeRecursiveLocked(long rootNodeId) { - AccessibilityNodeInfo current = mCacheImpl.get(rootNodeId); - if (current == null) { - return; - } - mCacheImpl.remove(rootNodeId); - final int childCount = current.getChildCount(); - for (int i = 0; i < childCount; i++) { - final long childNodeId = current.getChildId(i); - clearSubTreeRecursiveLocked(childNodeId); - } - } - - /** - * Check the integrity of the cache which is it does not have nodes - * from more than one window, there are no duplicates, all nodes are - * connected, there is a single input focused node, and there is a - * single accessibility focused node. - */ - private void checkIntegrity() { - synchronized (mLock) { - // Get the root. - if (mCacheImpl.size() <= 0) { - return; - } - - // If the cache is a tree it does not matter from - // which node we start to search for the root. - AccessibilityNodeInfo root = mCacheImpl.valueAt(0); - AccessibilityNodeInfo parent = root; - while (parent != null) { - root = parent; - parent = mCacheImpl.get(parent.getParentNodeId()); - } - - // Traverse the tree and do some checks. - final int windowId = root.getWindowId(); - AccessibilityNodeInfo accessFocus = null; - AccessibilityNodeInfo inputFocus = null; - HashSet<AccessibilityNodeInfo> seen = new HashSet<AccessibilityNodeInfo>(); - Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>(); - fringe.add(root); - - while (!fringe.isEmpty()) { - AccessibilityNodeInfo current = fringe.poll(); - // Check for duplicates - if (!seen.add(current)) { - Log.e(LOG_TAG, "Duplicate node: " + current); - return; - } - - // Check for one accessibility focus. - if (current.isAccessibilityFocused()) { - if (accessFocus != null) { - Log.e(LOG_TAG, "Duplicate accessibility focus:" + current); - } else { - accessFocus = current; - } - } - - // Check for one input focus. - if (current.isFocused()) { - if (inputFocus != null) { - Log.e(LOG_TAG, "Duplicate input focus: " + current); - } else { - inputFocus = current; - } - } - - final int childCount = current.getChildCount(); - for (int i = 0; i < childCount; i++) { - final long childId = current.getChildId(i); - final AccessibilityNodeInfo child = mCacheImpl.get(childId); - if (child != null) { - fringe.add(child); - } - } - } - - int disconnectedNodeCount = 0; - // Check for disconnected nodes or ones from another window. - for (int i = 0; i < mCacheImpl.size(); i++) { - AccessibilityNodeInfo info = mCacheImpl.valueAt(i); - if (!seen.contains(info)) { - if (info.getWindowId() == windowId) { - if (DEBUG) { - Log.e(LOG_TAG, "Disconnected node: " + info); - } - disconnectedNodeCount++; - } else { - Log.e(LOG_TAG, "Node from: " + info.getWindowId() + " not from:" - + windowId + " " + info); - } - } - } - if (disconnectedNodeCount > 0) { - Log.e(LOG_TAG, String.format("Found %d disconnected nodes", disconnectedNodeCount)); - } - } - } -} diff --git a/core/java/android/view/accessibility/AccessibilityNodeProvider.java b/core/java/android/view/accessibility/AccessibilityNodeProvider.java index 718c32f..abcbb70 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeProvider.java +++ b/core/java/android/view/accessibility/AccessibilityNodeProvider.java @@ -70,9 +70,14 @@ import java.util.List; public abstract class AccessibilityNodeProvider { /** + * The virtual id for the hosting View. + */ + public static final int HOST_VIEW_ID = -1; + + /** * Returns an {@link AccessibilityNodeInfo} representing a virtual view, * i.e. a descendant of the host View, with the given <code>virtualViewId</code> - * or the host View itself if <code>virtualViewId</code> equals to {@link View#NO_ID}. + * or the host View itself if <code>virtualViewId</code> equals to {@link #HOST_VIEW_ID}. * <p> * A virtual descendant is an imaginary View that is reported as a part of the view * hierarchy for accessibility purposes. This enables custom views that draw complex @@ -99,7 +104,7 @@ public abstract class AccessibilityNodeProvider { /** * Performs an accessibility action on a virtual view, i.e. a descendant of the * host View, with the given <code>virtualViewId</code> or the host View itself - * if <code>virtualViewId</code> equals to {@link View#NO_ID}. + * if <code>virtualViewId</code> equals to {@link #HOST_VIEW_ID}. * * @param virtualViewId A client defined virtual view id. * @param action The action to perform. @@ -117,8 +122,8 @@ public abstract class AccessibilityNodeProvider { /** * Finds {@link AccessibilityNodeInfo}s by text. The match is case insensitive * containment. The search is relative to the virtual view, i.e. a descendant of the - * host View, with the given <code>virtualViewId</code> or the host View itself - * <code>virtualViewId</code> equals to {@link View#NO_ID}. + * host View, with the given <code>virtualViewId</code> or the host View itself + * <code>virtualViewId</code> equals to {@link #HOST_VIEW_ID}. * * @param virtualViewId A client defined virtual view id which defined * the root of the tree in which to perform the search. diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java index 3fcd218..cc6a71d 100644 --- a/core/java/android/view/accessibility/AccessibilityRecord.java +++ b/core/java/android/view/accessibility/AccessibilityRecord.java @@ -78,7 +78,7 @@ public class AccessibilityRecord { private boolean mIsInPool; boolean mSealed; - int mBooleanProperties = PROPERTY_IMPORTANT_FOR_ACCESSIBILITY; + int mBooleanProperties = 0; int mCurrentItemIndex = UNDEFINED; int mItemCount = UNDEFINED; int mFromIndex = UNDEFINED; @@ -791,7 +791,7 @@ public class AccessibilityRecord { */ void clear() { mSealed = false; - mBooleanProperties = PROPERTY_IMPORTANT_FOR_ACCESSIBILITY; + mBooleanProperties = 0; mCurrentItemIndex = UNDEFINED; mItemCount = UNDEFINED; mFromIndex = UNDEFINED; diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.aidl b/core/java/android/view/accessibility/AccessibilityWindowInfo.aidl new file mode 100644 index 0000000..fdb25fb --- /dev/null +++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2014, 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.accessibility; + +parcelable AccessibilityWindowInfo; diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java new file mode 100644 index 0000000..80b5c50 --- /dev/null +++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java @@ -0,0 +1,574 @@ +/* + * Copyright (C) 2014 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.accessibility; + +import android.graphics.Rect; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.LongArray; +import android.util.Pools.SynchronizedPool; + +/** + * This class represents a state snapshot of a window for accessibility + * purposes. The screen content contains one or more windows where some + * windows can be descendants of other windows, which is the windows are + * hierarchically ordered. Note that there is no root window. Hence, the + * screen content can be seen as a collection of window trees. + */ +public final class AccessibilityWindowInfo implements Parcelable { + + private static final boolean DEBUG = false; + + /** + * Window type: This is an application window. Such a window shows UI for + * interacting with an application. + */ + public static final int TYPE_APPLICATION = 1; + + /** + * Window type: This is an input method window. Such a window shows UI for + * inputting text such as keyboard, suggestions, etc. + */ + public static final int TYPE_INPUT_METHOD = 2; + + /** + * Window type: This is an system window. Such a window shows UI for + * interacting with the system. + */ + public static final int TYPE_SYSTEM = 3; + + private static final int UNDEFINED = -1; + + private static final int BOOLEAN_PROPERTY_ACTIVE = 1 << 0; + private static final int BOOLEAN_PROPERTY_FOCUSED = 1 << 1; + + // Housekeeping. + private static final int MAX_POOL_SIZE = 10; + private static final SynchronizedPool<AccessibilityWindowInfo> sPool = + new SynchronizedPool<AccessibilityWindowInfo>(MAX_POOL_SIZE); + + // Data. + private int mType = UNDEFINED; + private int mLayer = UNDEFINED; + private int mBooleanProperties; + private int mId = UNDEFINED; + private int mParentId = UNDEFINED; + private final Rect mBoundsInScreen = new Rect(); + private LongArray mChildIds; + + private int mConnectionId = UNDEFINED; + + private AccessibilityWindowInfo() { + /* do nothing - hide constructor */ + } + + /** + * Gets the type of the window. + * + * @return The type. + * + * @see #TYPE_APPLICATION + * @see #TYPE_INPUT_METHOD + * @see #TYPE_SYSTEM + */ + public int getType() { + return mType; + } + + /** + * Sets the type of the window. + * + * @param The type + * + * @hide + */ + public void setType(int type) { + mType = type; + } + + /** + * Gets the layer which determines the Z-order of the window. Windows + * with greater layer appear on top of windows with lesser layer. + * + * @return The window layer. + */ + public int getLayer() { + return mLayer; + } + + /** + * Sets the layer which determines the Z-order of the window. Windows + * with greater layer appear on top of windows with lesser layer. + * + * @param The window layer. + * + * @hide + */ + public void setLayer(int layer) { + mLayer = layer; + } + + /** + * Gets the root node in the window's hierarchy. + * + * @return The root node. + */ + public AccessibilityNodeInfo getRoot() { + if (mConnectionId == UNDEFINED) { + return null; + } + AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); + return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, + mId, AccessibilityNodeInfo.ROOT_NODE_ID, + true, AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS); + } + + /** + * Gets the parent window if such. + * + * @return The parent window. + */ + public AccessibilityWindowInfo getParent() { + if (mConnectionId == UNDEFINED || mParentId == UNDEFINED) { + return null; + } + AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); + return client.getWindow(mConnectionId, mParentId); + } + + /** + * Sets the parent window id. + * + * @param parentId The parent id. + * + * @hide + */ + public void setParentId(int parentId) { + mParentId = parentId; + } + + /** + * Gets the unique window id. + * + * @return windowId The window id. + */ + public int getId() { + return mId; + } + + /** + * Sets the unique window id. + * + * @param windowId The window id. + * + * @hide + */ + public void setId(int id) { + mId = id; + } + + /** + * Sets the unique id of the IAccessibilityServiceConnection over which + * this instance can send requests to the system. + * + * @param connectionId The connection id. + * + * @hide + */ + public void setConnectionId(int connectionId) { + mConnectionId = connectionId; + } + + /** + * Gets the bounds of this window in the screen. + * + * @param outBounds The out window bounds. + */ + public void getBoundsInScreen(Rect outBounds) { + outBounds.set(mBoundsInScreen); + } + + /** + * Sets the bounds of this window in the screen. + * + * @param bounds The out window bounds. + * + * @hide + */ + public void setBoundsInScreen(Rect bounds) { + mBoundsInScreen.set(bounds); + } + + /** + * Gets if this window is active. An active window is the one + * the user is currently touching or the window has input focus + * and the user is not touching any window. + * + * @return Whether this is the active window. + */ + public boolean isActive() { + return getBooleanProperty(BOOLEAN_PROPERTY_ACTIVE); + } + + /** + * Sets if this window is active, which is this is the window + * the user is currently touching or the window has input focus + * and the user is not touching any window. + * + * @param Whether this is the active window. + * + * @hide + */ + public void setActive(boolean active) { + setBooleanProperty(BOOLEAN_PROPERTY_ACTIVE, active); + } + + /** + * Gets if this window has input focus. + * + * @return Whether has input focus. + */ + public boolean isFocused() { + return getBooleanProperty(BOOLEAN_PROPERTY_FOCUSED); + } + + /** + * Sets if this window has input focus. + * + * @param Whether has input focus. + * + * @hide + */ + public void setFocused(boolean focused) { + setBooleanProperty(BOOLEAN_PROPERTY_FOCUSED, focused); + } + + /** + * Gets the number of child windows. + * + * @return The child count. + */ + public int getChildCount() { + return (mChildIds != null) ? mChildIds.size() : 0; + } + + /** + * Gets the child window at a given index. + * + * @param index The index. + * @return The child. + */ + public AccessibilityWindowInfo getChild(int index) { + if (mChildIds == null) { + throw new IndexOutOfBoundsException(); + } + if (mConnectionId == UNDEFINED) { + return null; + } + final int childId = (int) mChildIds.get(index); + AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); + return client.getWindow(mConnectionId, childId); + } + + /** + * Adds a child window. + * + * @param childId The child window id. + * + * @hide + */ + public void addChild(int childId) { + if (mChildIds == null) { + mChildIds = new LongArray(); + } + mChildIds.add(childId); + } + + /** + * Returns a cached instance if such is available or a new one is + * created. + * + * @return An instance. + */ + public static AccessibilityWindowInfo obtain() { + AccessibilityWindowInfo info = sPool.acquire(); + if (info == null) { + info = new AccessibilityWindowInfo(); + } + return info; + } + + /** + * Returns a cached instance if such is available or a new one is + * created. The returned instance is initialized from the given + * <code>info</code>. + * + * @param info The other info. + * @return An instance. + */ + public static AccessibilityWindowInfo obtain(AccessibilityWindowInfo info) { + AccessibilityWindowInfo infoClone = obtain(); + + infoClone.mType = info.mType; + infoClone.mLayer = info.mLayer; + infoClone.mBooleanProperties = info.mBooleanProperties; + infoClone.mId = info.mId; + infoClone.mParentId = info.mParentId; + infoClone.mBoundsInScreen.set(info.mBoundsInScreen); + + if (info.mChildIds != null && info.mChildIds.size() > 0) { + if (infoClone.mChildIds == null) { + infoClone.mChildIds = info.mChildIds.clone(); + } else { + infoClone.mChildIds.addAll(info.mChildIds); + } + } + + infoClone.mConnectionId = info.mConnectionId; + + return infoClone; + } + + /** + * Return an instance back to be reused. + * <p> + * <strong>Note:</strong> You must not touch the object after calling this function. + * </p> + * + * @throws IllegalStateException If the info is already recycled. + */ + public void recycle() { + clear(); + sPool.release(this); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(mType); + parcel.writeInt(mLayer); + parcel.writeInt(mBooleanProperties); + parcel.writeInt(mId); + parcel.writeInt(mParentId); + mBoundsInScreen.writeToParcel(parcel, flags); + + final LongArray childIds = mChildIds; + if (childIds == null) { + parcel.writeInt(0); + } else { + final int childCount = childIds.size(); + parcel.writeInt(childCount); + for (int i = 0; i < childCount; i++) { + parcel.writeInt((int) childIds.get(i)); + } + } + + parcel.writeInt(mConnectionId); + } + + private void initFromParcel(Parcel parcel) { + mType = parcel.readInt(); + mLayer = parcel.readInt(); + mBooleanProperties = parcel.readInt(); + mId = parcel.readInt(); + mParentId = parcel.readInt(); + mBoundsInScreen.readFromParcel(parcel); + + final int childCount = parcel.readInt(); + if (childCount > 0) { + if (mChildIds == null) { + mChildIds = new LongArray(childCount); + } + for (int i = 0; i < childCount; i++) { + final int childId = parcel.readInt(); + mChildIds.add(childId); + } + } + + mConnectionId = parcel.readInt(); + } + + @Override + public int hashCode() { + return mId; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + AccessibilityWindowInfo other = (AccessibilityWindowInfo) obj; + return (mId == other.mId); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("AccessibilityWindowInfo["); + builder.append("id=").append(mId); + builder.append(", type=").append(typeToString(mType)); + builder.append(", layer=").append(mLayer); + builder.append(", bounds=").append(mBoundsInScreen); + builder.append(", focused=").append(isFocused()); + builder.append(", active=").append(isActive()); + if (DEBUG) { + builder.append(", parent=").append(mParentId); + builder.append(", children=["); + if (mChildIds != null) { + final int childCount = mChildIds.size(); + for (int i = 0; i < childCount; i++) { + builder.append(mChildIds.get(i)); + if (i < childCount - 1) { + builder.append(','); + } + } + } else { + builder.append("null"); + } + builder.append(']'); + } else { + builder.append(", hasParent=").append(mParentId != UNDEFINED); + builder.append(", hasChildren=").append(mChildIds != null + && mChildIds.size() > 0); + } + builder.append(']'); + return builder.toString(); + } + + /** + * Clears the internal state. + */ + private void clear() { + mType = UNDEFINED; + mLayer = UNDEFINED; + mBooleanProperties = 0; + mId = UNDEFINED; + mParentId = UNDEFINED; + mBoundsInScreen.setEmpty(); + if (mChildIds != null) { + mChildIds.clear(); + } + mConnectionId = UNDEFINED; + } + + /** + * Gets the value of a boolean property. + * + * @param property The property. + * @return The value. + */ + private boolean getBooleanProperty(int property) { + return (mBooleanProperties & property) != 0; + } + + /** + * Sets a boolean property. + * + * @param property The property. + * @param value The value. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + private void setBooleanProperty(int property, boolean value) { + if (value) { + mBooleanProperties |= property; + } else { + mBooleanProperties &= ~property; + } + } + + private static String typeToString(int type) { + switch (type) { + case TYPE_APPLICATION: { + return "TYPE_APPLICATION"; + } + case TYPE_INPUT_METHOD: { + return "TYPE_INPUT_METHOD"; + } + case TYPE_SYSTEM: { + return "TYPE_SYSTEM"; + } + default: + return "<UNKNOWN>"; + } + } + + /** + * Checks whether this window changed. The argument should be + * another state of the same window, which is have the same id + * and type as they never change. + * + * @param other The new state. + * @return Whether something changed. + * + * @hide + */ + public boolean changed(AccessibilityWindowInfo other) { + if (other.mId != mId) { + throw new IllegalArgumentException("Not same window."); + } + if (other.mType != mType) { + throw new IllegalArgumentException("Not same type."); + } + if (!mBoundsInScreen.equals(mBoundsInScreen)) { + return true; + } + if (mLayer != other.mLayer) { + return true; + } + if (mBooleanProperties != other.mBooleanProperties) { + return true; + } + if (mParentId != other.mParentId) { + return true; + } + if (mChildIds == null) { + if (other.mChildIds != null) { + return true; + } + } else if (!mChildIds.equals(other.mChildIds)) { + return true; + } + return false; + } + + public static final Parcelable.Creator<AccessibilityWindowInfo> CREATOR = + new Creator<AccessibilityWindowInfo>() { + @Override + public AccessibilityWindowInfo createFromParcel(Parcel parcel) { + AccessibilityWindowInfo info = obtain(); + info.initFromParcel(parcel); + return info; + } + + @Override + public AccessibilityWindowInfo[] newArray(int size) { + return new AccessibilityWindowInfo[size]; + } + }; +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index a1a6bba..37ef539 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1913,24 +1913,12 @@ android:description="@string/permdesc_filter_events" android:protectionLevel="signature" /> - <!-- @hide Allows an application to retrieve info for a window from the window manager. --> - <permission android:name="android.permission.RETRIEVE_WINDOW_INFO" - android:label="@string/permlab_retrieve_window_info" - android:description="@string/permdesc_retrieve_window_info" - android:protectionLevel="signature" /> - <!-- @hide Allows an application to temporary enable accessibility on the device. --> <permission android:name="android.permission.TEMPORARY_ENABLE_ACCESSIBILITY" android:label="@string/permlab_temporary_enable_accessibility" android:description="@string/permdesc_temporary_enable_accessibility" 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/values/attrs.xml b/core/res/res/values/attrs.xml index 8482fdb..582ed1b 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -2641,6 +2641,8 @@ <flag name="flagReportViewIds" value="0x00000010" /> <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_REQUEST_FILTER_KEY_EVENTS} --> <flag name="flagRequestFilterKeyEvents" value="0x00000020" /> + <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS} --> + <flag name="flagRetrieveInteractiveWindows" value="0x00000040" /> </attr> <!-- Component name of an activity that allows the user to modify the settings for this service. This setting cannot be changed at runtime. --> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index f1bcf65..0699e8b 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -830,13 +830,6 @@ user consent.</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_retrieve_window_info">retrieve window info</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_retrieve_window_info">Allows an application to retrieve - information about the the windows from the window manager. Malicious apps may - retrieve information that is intended for internal system usage.</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_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 @@ -844,13 +837,6 @@ 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> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_shutdown">Puts the activity manager into a shutdown diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index afa68da..3c23c6e 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -91,6 +91,7 @@ import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.WindowManagerPolicy; import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; import android.view.animation.Animation; import android.view.animation.AnimationUtils; @@ -212,6 +213,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { final Object mServiceAquireLock = new Object(); Vibrator mVibrator; // Vibrator for giving feedback of orientation changes SearchManager mSearchManager; + AccessibilityManager mAccessibilityManager; // Vibrator pattern for haptic feedback of a long press. long[] mLongPressVibePattern; @@ -299,7 +301,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { boolean mOrientationSensorEnabled = false; int mCurrentAppOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; boolean mHasSoftInput = false; - boolean mTouchExplorationEnabled = false; boolean mTranslucentDecorEnabled = true; int mPointerLocationMode = 0; // guarded by mLock @@ -905,6 +906,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { com.android.internal.R.bool.config_enableTranslucentDecor); readConfigurationDependentBehaviors(); + mAccessibilityManager = (AccessibilityManager) context.getSystemService( + Context.ACCESSIBILITY_SERVICE); + // register for dock events IntentFilter filter = new IntentFilter(); filter.addAction(UiModeManager.ACTION_ENTER_CAR_MODE); @@ -1105,7 +1109,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { * navigation bar and touch exploration is not enabled */ private boolean canHideNavigationBar() { - return mHasNavigationBar && !mTouchExplorationEnabled; + return mHasNavigationBar + && !mAccessibilityManager.isTouchExplorationEnabled(); } @Override @@ -5246,7 +5251,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { * R.boolean.config_enableTranslucentDecor is false. */ private boolean areTranslucentBarsAllowed() { - return mTranslucentDecorEnabled && !mTouchExplorationEnabled; + return mTranslucentDecorEnabled + && !mAccessibilityManager.isTouchExplorationEnabled(); } // Use this instead of checking config_showNavigationBar so that it can be consistently @@ -5297,11 +5303,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { } @Override - public void setTouchExplorationEnabled(boolean enabled) { - mTouchExplorationEnabled = enabled; - } - - @Override public boolean isTopLevelWindow(int windowType) { if (windowType >= WindowManager.LayoutParams.FIRST_SUB_WINDOW && windowType <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 959d4a9..0edce11 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -44,7 +44,6 @@ import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.hardware.input.InputManager; import android.net.Uri; -import android.opengl.Matrix; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -52,7 +51,6 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; -import android.os.Parcel; import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; @@ -63,24 +61,27 @@ import android.os.UserManager; import android.provider.Settings; import android.text.TextUtils; import android.text.TextUtils.SimpleStringSplitter; +import android.util.LongArray; import android.util.Pools.Pool; import android.util.Pools.SimplePool; import android.util.Slog; import android.util.SparseArray; import android.view.Display; import android.view.IWindow; -import android.view.IWindowManager; import android.view.InputDevice; import android.view.InputEventConsistencyVerifier; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MagnificationSpec; +import android.view.WindowInfo; import android.view.WindowManager; +import android.view.WindowManagerInternal; import android.view.WindowManagerPolicy; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityInteractionClient; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityWindowInfo; import android.view.accessibility.IAccessibilityInteractionConnection; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; import android.view.accessibility.IAccessibilityManager; @@ -89,6 +90,7 @@ import android.view.accessibility.IAccessibilityManagerClient; import com.android.internal.R; import com.android.internal.content.PackageMonitor; import com.android.internal.statusbar.IStatusBarService; +import com.android.server.LocalServices; import org.xmlpull.v1.XmlPullParserException; @@ -166,7 +168,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { private final PackageManager mPackageManager; - private final IWindowManager mWindowManagerService; + private final WindowManagerInternal mWindowManagerService; private final SecurityPolicy mSecurityPolicy; @@ -197,9 +199,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { private int mCurrentUserId = UserHandle.USER_OWNER; + private final LongArray mTempLongArray = new LongArray(); + //TODO: Remove this hack private boolean mInitialized; + private WindowsForAccessibilityCallback mWindowsForAccessibilityCallback; + private UserState getCurrentUserStateLocked() { return getUserStateLocked(mCurrentUserId); } @@ -221,7 +227,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { public AccessibilityManagerService(Context context) { mContext = context; mPackageManager = mContext.getPackageManager(); - mWindowManagerService = (IWindowManager) ServiceManager.getService(Context.WINDOW_SERVICE); + mWindowManagerService = LocalServices.getService(WindowManagerInternal.class); mSecurityPolicy = new SecurityPolicy(); mMainHandler = new MainHandler(mContext.getMainLooper()); //TODO: (multi-display) We need to support multiple displays. @@ -390,7 +396,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { if (resolvedUserId != mCurrentUserId) { return true; // yes, recycle the event } - if (mSecurityPolicy.canDispatchAccessibilityEvent(event)) { + if (mSecurityPolicy.canDispatchAccessibilityEventLocked(event)) { mSecurityPolicy.updateEventSourceLocked(event); mMainHandler.obtainMessage(MainHandler.MSG_UPDATE_ACTIVE_WINDOW, event.getWindowId(), event.getEventType()).sendToTarget(); @@ -632,11 +638,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { mSecurityPolicy.enforceCallingPermission( Manifest.permission.TEMPORARY_ENABLE_ACCESSIBILITY, TEMPORARY_ENABLE_ACCESSIBILITY_UNTIL_KEYGUARD_REMOVED); - try { - if (!mWindowManagerService.isKeyguardLocked()) { - return; - } - } catch (RemoteException re) { + if (!mWindowManagerService.isKeyguardLocked()) { return; } synchronized (mLock) { @@ -739,6 +741,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { * @param outBounds The output to which to write the bounds. */ boolean getActiveWindowBounds(Rect outBounds) { + // TODO: This should be refactored to work with accessibility + // focus in multiple windows. IBinder token; synchronized (mLock) { final int windowId = mSecurityPolicy.mActiveWindowId; @@ -747,13 +751,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { token = getCurrentUserStateLocked().mWindowTokens.get(windowId); } } - try { - mWindowManagerService.getWindowFrame(token, outBounds); - if (!outBounds.isEmpty()) { - return true; - } - } catch (RemoteException re) { - /* ignore */ + mWindowManagerService.getWindowFrame(token, outBounds); + if (!outBounds.isEmpty()) { + return true; } return false; } @@ -771,7 +771,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } void onMagnificationStateChanged() { - notifyClearAccessibilityNodeInfoCacheLocked(); + notifyClearAccessibilityCacheLocked(); } private void switchUser(int userId) { @@ -879,7 +879,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { return false; } - private void notifyClearAccessibilityNodeInfoCacheLocked() { + private void notifyClearAccessibilityCacheLocked() { UserState state = getCurrentUserStateLocked(); for (int i = state.mBoundServices.size() - 1; i >= 0; i--) { Service service = state.mBoundServices.get(i); @@ -887,6 +887,16 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } + private void notifyWindowsChangedLocked(List<AccessibilityWindowInfo> windows) { + UserState state = getCurrentUserStateLocked(); + for (int i = state.mBoundServices.size() - 1; i >= 0; i--) { + Service service = state.mBoundServices.get(i); + if (mSecurityPolicy.canRetrieveWindowsLocked(service)) { + service.notifyWindowsChangedLocked(windows); + } + } + } + /** * Removes an AccessibilityInteractionConnection. * @@ -994,7 +1004,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { Service service = state.mBoundServices.get(i); if (service.mIsDefault == isDefault) { - if (canDispathEventLocked(service, event, state.mHandledFeedbackTypes)) { + if (canDispatchEventToServiceLocked(service, event, + state.mHandledFeedbackTypes)) { state.mHandledFeedbackTypes |= service.mFeedbackType; service.notifyAccessibilityEvent(event); } @@ -1043,7 +1054,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { * @param handledFeedbackTypes The feedback types for which services have been notified. * @return True if the listener should be notified, false otherwise. */ - private boolean canDispathEventLocked(Service service, AccessibilityEvent event, + private boolean canDispatchEventToServiceLocked(Service service, AccessibilityEvent event, int handledFeedbackTypes) { if (!service.canReceiveEventsLocked()) { @@ -1232,11 +1243,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } if (setInputFilter) { - try { - mWindowManagerService.setInputFilter(inputFilter); - } catch (RemoteException re) { - /* ignore */ - } + mWindowManagerService.setInputFilter(inputFilter); } } @@ -1296,6 +1303,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { mInitialized = true; updateLegacyCapabilities(userState); updateServicesLocked(userState); + updateWindowsForAccessibilityCallback(userState); updateFilterKeyEventsLocked(userState); updateTouchExplorationLocked(userState); updateEnhancedWebAccessibilityLocked(userState); @@ -1304,6 +1312,43 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { scheduleUpdateClientsIfNeededLocked(userState); } + private void updateWindowsForAccessibilityCallback(UserState userState) { + if (userState.mIsAccessibilityEnabled) { + // We observe windows for accessibility only if there is at least + // one bound service that can retrieve window content that specified + // it is interested in accessing such windows. For services that are + // binding we do an update pass after each bind event, so we run this + // code and register the callback if needed. + boolean boundServiceCanRetrieveInteractiveWindows = false; + + List<Service> boundServices = userState.mBoundServices; + final int boundServiceCount = boundServices.size(); + for (int i = 0; i < boundServiceCount; i++) { + Service boundService = boundServices.get(i); + if (mSecurityPolicy.canRetrieveWindowContentLocked(boundService) + && boundService.mRetrieveInteractiveWindows) { + boundServiceCanRetrieveInteractiveWindows = true; + break; + } + } + + if (boundServiceCanRetrieveInteractiveWindows) { + if (mWindowsForAccessibilityCallback == null) { + mWindowsForAccessibilityCallback = new WindowsForAccessibilityCallback(); + mWindowManagerService.setWindowsForAccessibilityCallback( + mWindowsForAccessibilityCallback); + } + return; + } + } + + if (mWindowsForAccessibilityCallback != null) { + mWindowsForAccessibilityCallback = null; + mWindowManagerService.setWindowsForAccessibilityCallback( + mWindowsForAccessibilityCallback); + } + } + private void updateLegacyCapabilities(UserState userState) { // Up to JB-MR1 we had a white list with services that can enable touch // exploration. When a service is first started we show a dialog to the @@ -1435,11 +1480,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { Settings.Secure.TOUCH_EXPLORATION_ENABLED, enabled ? 1 : 0, userState.mUserId); } - try { - mWindowManagerService.setTouchExplorationEnabled(enabled); - } catch (RemoteException e) { - e.printStackTrace(); - } } private boolean canRequestAndRequestsTouchExplorationLocked(Service service) { @@ -1605,6 +1645,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } event.recycle(); } break; + case MSG_SEND_KEY_EVENT_TO_INPUT_FILTER: { KeyEvent event = (KeyEvent) msg.obj; final int policyFlags = msg.arg1; @@ -1615,28 +1656,34 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } event.recycle(); } break; + case MSG_SEND_STATE_TO_CLIENTS: { final int clientState = msg.arg1; final int userId = msg.arg2; sendStateToClients(clientState, mGlobalClients); sendStateToClientsForUser(clientState, userId); } break; + case MSG_SEND_CLEARED_STATE_TO_CLIENTS_FOR_USER: { final int userId = msg.arg1; sendStateToClientsForUser(0, userId); } break; + case MSG_UPDATE_ACTIVE_WINDOW: { final int windowId = msg.arg1; final int eventType = msg.arg2; mSecurityPolicy.updateActiveWindow(windowId, eventType); } break; + case MSG_ANNOUNCE_NEW_USER_IF_NEEDED: { announceNewUserIfNeeded(); } break; + case MSG_UPDATE_INPUT_FILTER: { UserState userState = (UserState) msg.obj; updateInputFilter(userState); } break; + case MSG_SHOW_ENABLED_TOUCH_EXPLORATION_DIALOG: { Service service = (Service) msg.obj; showEnableTouchExplorationDialog(service); @@ -1655,7 +1702,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { AccessibilityEvent event = AccessibilityEvent.obtain( AccessibilityEvent.TYPE_ANNOUNCEMENT); event.getText().add(message); - event.setWindowId(mSecurityPolicy.getRetrievalAllowingWindowLocked()); sendAccessibilityEvent(event, mCurrentUserId); } } @@ -1703,6 +1749,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { mPendingEventPool.release(pendingEvent); } + private int findWindowIdLocked(IBinder token) { + final int globalIndex = mGlobalWindowTokens.indexOfValue(token); + if (globalIndex >= 0) { + return mGlobalWindowTokens.keyAt(globalIndex); + } + UserState userState = getCurrentUserStateLocked(); + final int userIndex = userState.mWindowTokens.indexOfValue(token); + if (userIndex >= 0) { + return userState.mWindowTokens.keyAt(userIndex); + } + return -1; + } + /** * This class represents an accessibility service. It stores all per service * data required for the service management, provides API for starting/stopping the @@ -1738,6 +1797,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { boolean mRequestFilterKeyEvents; + boolean mRetrieveInteractiveWindows; + int mFetchFlags; long mNotificationTimeout; @@ -1748,7 +1809,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { boolean mIsAutomation; - final Rect mTempBounds = new Rect(); + final Rect mTempBounds1 = new Rect(); + + final Rect mTempBounds2 = new Rect(); final ResolveInfo mResolveInfo; @@ -1758,6 +1821,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { final KeyEventDispatcher mKeyEventDispatcher = new KeyEventDispatcher(); + final SparseArray<AccessibilityWindowInfo> mIntrospectedWindows = + new SparseArray<AccessibilityWindowInfo>(); + boolean mWasConnectedAndDied; // Handler only for dispatching accessibility events since we use event @@ -1822,7 +1888,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { mRequestEnhancedWebAccessibility = (info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY) != 0; mRequestFilterKeyEvents = (info.flags - & AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS) != 0; + & AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS) != 0; + mRetrieveInteractiveWindows = (info.flags + & AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS) != 0; + + if (!mRetrieveInteractiveWindows) { + clearIntrospectedWindows(); + } } /** @@ -1932,6 +2004,59 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } @Override + public List<AccessibilityWindowInfo> getWindows() { + synchronized (mLock) { + final int resolvedUserId = mSecurityPolicy + .resolveCallingUserIdEnforcingPermissionsLocked( + UserHandle.getCallingUserId()); + if (resolvedUserId != mCurrentUserId) { + return null; + } + final boolean permissionGranted = + mSecurityPolicy.canRetrieveWindowsLocked(this); + if (!permissionGranted) { + return null; + } + List<AccessibilityWindowInfo> windows = new ArrayList<AccessibilityWindowInfo>(); + final int windowCount = mSecurityPolicy.mWindows.size(); + for (int i = 0; i < windowCount; i++) { + AccessibilityWindowInfo window = mSecurityPolicy.mWindows.get(i); + AccessibilityWindowInfo windowClone = + AccessibilityWindowInfo.obtain(window); + windowClone.setConnectionId(mId); + mIntrospectedWindows.put(window.getId(), windowClone); + windows.add(windowClone); + } + return windows; + } + } + + @Override + public AccessibilityWindowInfo getWindow(int windowId) { + synchronized (mLock) { + final int resolvedUserId = mSecurityPolicy + .resolveCallingUserIdEnforcingPermissionsLocked( + UserHandle.getCallingUserId()); + if (resolvedUserId != mCurrentUserId) { + return null; + } + final boolean permissionGranted = + mSecurityPolicy.canRetrieveWindowsLocked(this); + if (!permissionGranted) { + return null; + } + AccessibilityWindowInfo window = mSecurityPolicy.findWindowById(windowId); + if (window != null) { + AccessibilityWindowInfo windowClone = AccessibilityWindowInfo.obtain(window); + windowClone.setConnectionId(mId); + mIntrospectedWindows.put(windowId, windowClone); + return windowClone; + } + return null; + } + } + + @Override public boolean findAccessibilityNodeInfosByViewId(int accessibilityWindowId, long accessibilityNodeId, String viewIdResName, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) @@ -1945,12 +2070,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { if (resolvedUserId != mCurrentUserId) { return false; } - mSecurityPolicy.enforceCanRetrieveWindowContent(this); - final boolean permissionGranted = mSecurityPolicy.canRetrieveWindowContent(this); + resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); + final boolean permissionGranted = + mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); if (!permissionGranted) { return false; } else { - resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); connection = getConnectionLocked(resolvedWindowId); if (connection == null) { return false; @@ -1989,7 +2114,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { if (resolvedUserId != mCurrentUserId) { return false; } - mSecurityPolicy.enforceCanRetrieveWindowContent(this); resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); final boolean permissionGranted = mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); @@ -2034,7 +2158,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { if (resolvedUserId != mCurrentUserId) { return false; } - mSecurityPolicy.enforceCanRetrieveWindowContent(this); resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); final boolean permissionGranted = mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); @@ -2079,7 +2202,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { if (resolvedUserId != mCurrentUserId) { return false; } - mSecurityPolicy.enforceCanRetrieveWindowContent(this); resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); final boolean permissionGranted = mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); @@ -2123,7 +2245,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { if (resolvedUserId != mCurrentUserId) { return false; } - mSecurityPolicy.enforceCanRetrieveWindowContent(this); resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); final boolean permissionGranted = mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); @@ -2167,7 +2288,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { if (resolvedUserId != mCurrentUserId) { return false; } - mSecurityPolicy.enforceCanRetrieveWindowContent(this); resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); final boolean permissionGranted = mSecurityPolicy.canPerformActionLocked(this, resolvedWindowId, action, arguments); @@ -2365,7 +2485,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } mPendingEvents.remove(eventType); - if (mSecurityPolicy.canRetrieveWindowContent(this)) { + if (mSecurityPolicy.canRetrieveWindowContentLocked(this)) { event.setConnectionId(mId); } else { event.setSource(null); @@ -2396,12 +2516,69 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } public void notifyClearAccessibilityNodeInfoCache() { + clearIntrospectedWindows(); mInvocationHandler.sendEmptyMessage( - InvocationHandler.MSG_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE); + InvocationHandler.MSG_CLEAR_ACCESSIBILITY_CACHE); + } + + private void clearIntrospectedWindows() { + final int windowCount = mIntrospectedWindows.size(); + for (int i = windowCount - 1; i >= 0; i--) { + mIntrospectedWindows.valueAt(i).recycle(); + mIntrospectedWindows.removeAt(i); + } + } + + public void notifyWindowsChangedLocked(List<AccessibilityWindowInfo> windows) { + LongArray changedWindows = mTempLongArray; + changedWindows.clear(); + + // Figure out which windows the service cares about changed. + final int windowCount = windows.size(); + for (int i = 0; i < windowCount; i++) { + AccessibilityWindowInfo newState = windows.get(i); + final int windowId = newState.getId(); + AccessibilityWindowInfo oldState = mIntrospectedWindows.get(windowId); + if (oldState != null && oldState.changed(newState)) { + oldState.recycle(); + mIntrospectedWindows.put(newState.getId(), + AccessibilityWindowInfo.obtain(newState)); + changedWindows.add(windowId); + } + } + + // Figure out which windows the service cares about went away. + final int introspectedWindowCount = mIntrospectedWindows.size(); + for (int i = introspectedWindowCount - 1; i >= 0; i--) { + AccessibilityWindowInfo window = mIntrospectedWindows.valueAt(i); + if (changedWindows.indexOf(window.getId()) < 0 && !windows.contains(window)) { + changedWindows.add(window.getId()); + mIntrospectedWindows.removeAt(i); + window.recycle(); + } + } + + int[] windowIds = null; + + final int changedWindowCount = changedWindows.size(); + if (changedWindowCount > 0) { + windowIds = new int[changedWindowCount]; + for (int i = 0; i < changedWindowCount; i++) { + // Cast is fine as we use long array to cache ints. + windowIds[i] = (int) changedWindows.get(i); + } + changedWindows.clear(); + } + + mInvocationHandler.obtainMessage(InvocationHandler.MSG_ON_WINDOWS_CHANGED, + windowIds).sendToTarget(); } private void notifyGestureInternal(int gestureId) { - IAccessibilityServiceClient listener = mServiceInterface; + final IAccessibilityServiceClient listener; + synchronized (mLock) { + listener = mServiceInterface; + } if (listener != null) { try { listener.onGesture(gestureId); @@ -2416,11 +2593,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { mKeyEventDispatcher.notifyKeyEvent(event, policyFlags); } - private void notifyClearAccessibilityNodeInfoCacheInternal() { - IAccessibilityServiceClient listener = mServiceInterface; + private void notifyClearAccessibilityCacheInternal() { + final IAccessibilityServiceClient listener; + synchronized (mLock) { + listener = mServiceInterface; + } if (listener != null) { try { - listener.clearAccessibilityNodeInfoCache(); + listener.clearAccessibilityCache(); } catch (RemoteException re) { Slog.e(LOG_TAG, "Error during requesting accessibility info cache" + " to be cleared.", re); @@ -2428,6 +2608,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } + private void notifyWindowsChangedInternal(int[] windowIds) { + final IAccessibilityServiceClient listener; + synchronized (mLock) { + listener = mServiceInterface; + } + if (listener != null) { + try { + listener.onWindowsChanged(windowIds); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error during sending windows to: " + mService, re); + } + } + } + private void sendDownAndUpKeyEvents(int keyCode) { final long token = Binder.clearCallingIdentity(); @@ -2511,27 +2705,23 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } private MagnificationSpec getCompatibleMagnificationSpec(int windowId) { - try { - IBinder windowToken = mGlobalWindowTokens.get(windowId); - if (windowToken == null) { - windowToken = getCurrentUserStateLocked().mWindowTokens.get(windowId); - } - if (windowToken != null) { - return mWindowManagerService.getCompatibleMagnificationSpecForWindow( - windowToken); - } - } catch (RemoteException re) { - /* ignore */ + IBinder windowToken = mGlobalWindowTokens.get(windowId); + if (windowToken == null) { + windowToken = getCurrentUserStateLocked().mWindowTokens.get(windowId); + } + if (windowToken != null) { + return mWindowManagerService.getCompatibleMagnificationSpecForWindow( + windowToken); } return null; } private final class InvocationHandler extends Handler { - public static final int MSG_ON_GESTURE = 1; public static final int MSG_ON_KEY_EVENT = 2; - public static final int MSG_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE = 3; + public static final int MSG_CLEAR_ACCESSIBILITY_CACHE = 3; public static final int MSG_ON_KEY_EVENT_TIMEOUT = 4; + public static final int MSG_ON_WINDOWS_CHANGED = 5; public InvocationHandler(Looper looper) { super(looper, null, true); @@ -2545,18 +2735,27 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { final int gestureId = message.arg1; notifyGestureInternal(gestureId); } break; + case MSG_ON_KEY_EVENT: { KeyEvent event = (KeyEvent) message.obj; final int policyFlags = message.arg1; notifyKeyEventInternal(event, policyFlags); } break; - case MSG_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE: { - notifyClearAccessibilityNodeInfoCacheInternal(); + + case MSG_CLEAR_ACCESSIBILITY_CACHE: { + notifyClearAccessibilityCacheInternal(); } break; + case MSG_ON_KEY_EVENT_TIMEOUT: { PendingEvent eventState = (PendingEvent) message.obj; setOnKeyEventResult(false, eventState.sequence); } break; + + case MSG_ON_WINDOWS_CHANGED: { + final int[] windowIds = (int[]) message.obj; + notifyWindowsChangedInternal(windowIds); + } break; + default: { throw new IllegalArgumentException("Unknown message: " + type); } @@ -2700,6 +2899,113 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } + final class WindowsForAccessibilityCallback implements + WindowManagerInternal.WindowsForAccessibilityCallback { + + @Override + public void onWindowsForAccessibilityChanged(List<WindowInfo> windows) { + synchronized (mLock) { + List<WindowInfo> receivedWindows = (List<WindowInfo>) windows; + + // Populate the windows to report. + List<AccessibilityWindowInfo> reportedWindows = + new ArrayList<AccessibilityWindowInfo>(); + final int receivedWindowCount = receivedWindows.size(); + for (int i = 0; i < receivedWindowCount; i++) { + WindowInfo receivedWindow = receivedWindows.get(i); + AccessibilityWindowInfo reportedWindow = populateReportedWindow( + receivedWindow); + if (reportedWindow != null) { + reportedWindows.add(reportedWindow); + } + } + + if (DEBUG) { + Slog.i(LOG_TAG, "Windows changed: " + reportedWindows); + } + + // Let the policy update the focused and active windows. + mSecurityPolicy.updateWindowsLocked(reportedWindows); + } + } + + private AccessibilityWindowInfo populateReportedWindow(WindowInfo window) { + final int windowId = findWindowIdLocked(window.token); + if (windowId < 0) { + return null; + } + + AccessibilityWindowInfo reportedWindow = AccessibilityWindowInfo.obtain(); + + reportedWindow.setId(windowId); + reportedWindow.setType(getTypeForWindowManagerWindowType(window.type)); + reportedWindow.setLayer(window.layer); + reportedWindow.setFocused(window.focused); + reportedWindow.setBoundsInScreen(window.boundsInScreen); + + final int parentId = findWindowIdLocked(window.parentToken); + if (parentId >= 0) { + reportedWindow.setParentId(parentId); + } + + if (window.childTokens != null) { + final int childCount = window.childTokens.size(); + for (int i = 0; i < childCount; i++) { + IBinder childToken = window.childTokens.get(i); + final int childId = findWindowIdLocked(childToken); + if (childId >= 0) { + reportedWindow.addChild(childId); + } + } + } + + return reportedWindow; + } + + private int getTypeForWindowManagerWindowType(int windowType) { + switch (windowType) { + case WindowManager.LayoutParams.TYPE_APPLICATION: + case WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA: + case WindowManager.LayoutParams.TYPE_APPLICATION_PANEL: + case WindowManager.LayoutParams.TYPE_APPLICATION_STARTING: + case WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL: + case WindowManager.LayoutParams.TYPE_BASE_APPLICATION: + case WindowManager.LayoutParams.TYPE_PHONE: + case WindowManager.LayoutParams.TYPE_PRIORITY_PHONE: + case WindowManager.LayoutParams.TYPE_TOAST: + case WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG: { + return AccessibilityWindowInfo.TYPE_APPLICATION; + } + + case WindowManager.LayoutParams.TYPE_INPUT_METHOD: + case WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG: { + return AccessibilityWindowInfo.TYPE_INPUT_METHOD; + } + + case WindowManager.LayoutParams.TYPE_KEYGUARD: + case WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG: + case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR: + case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL: + case WindowManager.LayoutParams.TYPE_SEARCH_BAR: + case WindowManager.LayoutParams.TYPE_STATUS_BAR: + case WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL: + case WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL: + case WindowManager.LayoutParams.TYPE_RECENTS_OVERLAY: + case WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY: + case WindowManager.LayoutParams.TYPE_SYSTEM_ALERT: + case WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG: + case WindowManager.LayoutParams.TYPE_SYSTEM_ERROR: + case WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY: { + return AccessibilityWindowInfo.TYPE_SYSTEM; + } + + default: { + return -1; + } + } + } + } + final class SecurityPolicy { private static final int VALID_ACTIONS = AccessibilityNodeInfo.ACTION_CLICK @@ -2738,17 +3044,37 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { | AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED | AccessibilityEvent.TYPE_VIEW_SCROLLED | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED - | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED; + | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED + | AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY; + + public final List<AccessibilityWindowInfo> mWindows = + new ArrayList<AccessibilityWindowInfo>(); + + public int mActiveWindowId; + public int mFocusedWindowId; + public AccessibilityEvent mShowingFocusedWindowEvent; - private int mActiveWindowId; private boolean mTouchInteractionInProgress; - private boolean canDispatchAccessibilityEvent(AccessibilityEvent event) { + private boolean canDispatchAccessibilityEventLocked(AccessibilityEvent event) { final int eventType = event.getEventType(); switch (eventType) { // All events that are for changes in a global window // state should *always* be dispatched. case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: + if (mWindowsForAccessibilityCallback != null) { + // OK, this is fun. Sometimes the focused window is notified + // it has focus before being shown. Historically this event + // means that the window is focused and can be introspected. + // But we still have not gotten the window state from the + // window manager, so delay the notification until then. + AccessibilityWindowInfo window = findWindowById(event.getWindowId()); + if (window == null || !window.isFocused()) { + mShowingFocusedWindowEvent = AccessibilityEvent.obtain(event); + return false; + } + } + // $fall-through$ case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED: // All events generated by the user touching the // screen should *always* be dispatched. @@ -2758,15 +3084,81 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { case AccessibilityEvent.TYPE_GESTURE_DETECTION_END: case AccessibilityEvent.TYPE_TOUCH_INTERACTION_START: case AccessibilityEvent.TYPE_TOUCH_INTERACTION_END: - // These will change the active window, so dispatch. case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER: case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT: { return true; } // All events for changes in window content should be - // dispatched *only* if this window is the active one. - default: - return event.getWindowId() == mActiveWindowId; + // dispatched *only* if this window is one of the windows + // the accessibility layer reports which are windows + // that a sighted user can touch. + default: { + return isRetrievalAllowingWindow(event.getWindowId()); + } + } + } + + public void updateWindowsLocked(List<AccessibilityWindowInfo> windows) { + final int oldWindowCount = mWindows.size(); + for (int i = oldWindowCount - 1; i >= 0; i--) { + mWindows.remove(i).recycle(); + } + + mFocusedWindowId = -1; + if (!mTouchInteractionInProgress) { + mActiveWindowId = -1; + } + + // If the active window goes away while the user is touch exploring we + // reset the active window id and wait for the next hover event from + // under the user's finger to determine which one is the new one. It + // is possible that the finger is not moving and the input system + // filters out such events. + boolean activeWindowGone = true; + + final int windowCount = windows.size(); + if (windowCount > 0) { + for (int i = 0; i < windowCount; i++) { + AccessibilityWindowInfo window = windows.get(i); + final int windowId = window.getId(); + if (window.isFocused()) { + mFocusedWindowId = windowId; + if (!mTouchInteractionInProgress) { + mActiveWindowId = windowId; + window.setActive(true); + } else if (windowId == mActiveWindowId) { + activeWindowGone = false; + } + } + mWindows.add(window); + } + + if (mTouchInteractionInProgress && activeWindowGone) { + mActiveWindowId = -1; + } + + // Focused window may change the active one, so set the + // active window once we decided which it is. + for (int i = 0; i < windowCount; i++) { + AccessibilityWindowInfo window = mWindows.get(i); + if (window.getId() == mActiveWindowId) { + window.setActive(true); + } + } + } + + notifyWindowsChangedLocked(mWindows); + + // If we are delaying a window state change event as the window + // source was showing when it was fired, now is the time to send. + if (mShowingFocusedWindowEvent != null) { + final int windowId = mShowingFocusedWindowEvent.getWindowId(); + AccessibilityWindowInfo window = findWindowById(windowId); + if (window != null && window.isFocused()) { + // Sending does the recycle. + sendAccessibilityEvent(mShowingFocusedWindowEvent, mCurrentUserId); + } + mShowingFocusedWindowEvent = null; } } @@ -2781,67 +3173,96 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { // the window that the user is currently touching. If the user is // touching a window that does not have input focus as soon as the // the user stops touching that window the focused window becomes - // the active one. + // the active one. Here we detect the touched window and make it + // active. In updateWindowsLocked() we update the focused window + // and if the user is not touching the screen, we make the focused + // window the active one. switch (eventType) { case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: { - if (getFocusedWindowId() == windowId) { - mActiveWindowId = windowId; + // If no service has the capability to introspect screen, + // we do not register callback in the window manager for + // window changes, so we have to ask the window manager + // what the focused window is to update the active one. + // The active window also determined events from which + // windows are delivered. + boolean focusedWindowActive = false; + synchronized (mLock) { + if (mWindowsForAccessibilityCallback == null) { + focusedWindowActive = true; + } + } + if (focusedWindowActive) { + if (windowId == getFocusedWindowId()) { + synchronized (mLock) { + mActiveWindowId = windowId; + } + } } } break; + case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER: { // Do not allow delayed hover events to confuse us // which the active window is. - if (mTouchInteractionInProgress) { - mActiveWindowId = windowId; + synchronized (mLock) { + if (mTouchInteractionInProgress && mActiveWindowId != windowId) { + setActiveWindowLocked(windowId); + } } } break; } } public void onTouchInteractionStart() { - mTouchInteractionInProgress = true; + synchronized (mLock) { + mTouchInteractionInProgress = true; + } } public void onTouchInteractionEnd() { - mTouchInteractionInProgress = false; - // We want to set the active window to be current immediately - // after the user has stopped touching the screen since if the - // user types with the IME he should get a feedback for the - // letter typed in the text view which is in the input focused - // window. Note that we always deliver hover accessibility events - // (they are a result of user touching the screen) so change of - // the active window before all hover accessibility events from - // the touched window are delivered is fine. - mActiveWindowId = getFocusedWindowId(); + synchronized (mLock) { + mTouchInteractionInProgress = false; + // We want to set the active window to be current immediately + // after the user has stopped touching the screen since if the + // user types with the IME he should get a feedback for the + // letter typed in the text view which is in the input focused + // window. Note that we always deliver hover accessibility events + // (they are a result of user touching the screen) so change of + // the active window before all hover accessibility events from + // the touched window are delivered is fine. + setActiveWindowLocked(mFocusedWindowId); + } } - public int getRetrievalAllowingWindowLocked() { - return mActiveWindowId; + private void setActiveWindowLocked(int windowId) { + if (mActiveWindowId != windowId) { + mActiveWindowId = windowId; + final int windowCount = mWindows.size(); + for (int i = 0; i < windowCount; i++) { + AccessibilityWindowInfo window = mWindows.get(i); + window.setActive(window.getId() == windowId); + } + notifyWindowsChangedLocked(mWindows); + } } public boolean canGetAccessibilityNodeInfoLocked(Service service, int windowId) { - return canRetrieveWindowContent(service) && isRetrievalAllowingWindow(windowId); + return canRetrieveWindowContentLocked(service) && isRetrievalAllowingWindow(windowId); } public boolean canPerformActionLocked(Service service, int windowId, int action, Bundle arguments) { - return canRetrieveWindowContent(service) + return canRetrieveWindowContentLocked(service) && isRetrievalAllowingWindow(windowId) && isActionPermitted(action); } - public boolean canRetrieveWindowContent(Service service) { - return (service.mAccessibilityServiceInfo.getCapabilities() - & AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT) != 0; + public boolean canRetrieveWindowsLocked(Service service) { + return canRetrieveWindowContentLocked(service) && service.mRetrieveInteractiveWindows; } - public void enforceCanRetrieveWindowContent(Service service) throws RemoteException { - // This happens due to incorrect registration so make it apparent. - if (!canRetrieveWindowContent(service)) { - Slog.e(LOG_TAG, "Accessibility serivce " + service.mComponentName + " does not " + - "declare android:canRetrieveWindowContent."); - throw new RemoteException(); - } + public boolean canRetrieveWindowContentLocked(Service service) { + return (service.mAccessibilityServiceInfo.getCapabilities() + & AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT) != 0; } public int resolveCallingUserIdEnforcingPermissionsLocked(int userId) { @@ -2878,7 +3299,21 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } private boolean isRetrievalAllowingWindow(int windowId) { - return (mActiveWindowId == windowId); + if (windowId == mActiveWindowId) { + return true; + } + return findWindowById(windowId) != null; + } + + private AccessibilityWindowInfo findWindowById(int windowId) { + final int windowCount = mWindows.size(); + for (int i = 0; i < windowCount; i++) { + AccessibilityWindowInfo window = mWindows.get(i); + if (window.getId() == windowId) { + return window; + } + } + return null; } private boolean isActionPermitted(int action) { @@ -2901,35 +3336,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } private int getFocusedWindowId() { - try { - // We call this only on window focus change or after touch - // exploration gesture end and the shown windows are not that - // many, so the linear look up is just fine. - IBinder token = mWindowManagerService.getFocusedWindowToken(); - if (token != null) { - synchronized (mLock) { - int windowId = getFocusedWindowIdLocked(token, mGlobalWindowTokens); - if (windowId < 0) { - windowId = getFocusedWindowIdLocked(token, - getCurrentUserStateLocked().mWindowTokens); - } - return windowId; - } - } - } catch (RemoteException re) { - /* ignore */ - } - return -1; - } - - private int getFocusedWindowIdLocked(IBinder token, SparseArray<IBinder> windows) { - final int windowCount = windows.size(); - for (int i = 0; i < windowCount; i++) { - if (windows.valueAt(i) == token) { - return windows.keyAt(i); - } - } - return -1; + IBinder token = mWindowManagerService.getFocusedWindowToken(); + return findWindowIdLocked(token); } } diff --git a/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java b/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java index 5f12cf4..c8b080e 100644 --- a/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java +++ b/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java @@ -29,8 +29,6 @@ import android.os.AsyncTask; import android.os.Binder; import android.os.Handler; import android.os.Message; -import android.os.RemoteException; -import android.os.ServiceManager; import android.os.SystemClock; import android.provider.Settings; import android.text.TextUtils; @@ -38,8 +36,6 @@ import android.util.Property; import android.util.Slog; import android.view.GestureDetector; import android.view.GestureDetector.SimpleOnGestureListener; -import android.view.IMagnificationCallbacks; -import android.view.IWindowManager; import android.view.MagnificationSpec; import android.view.MotionEvent; import android.view.MotionEvent.PointerCoords; @@ -48,10 +44,12 @@ import android.view.ScaleGestureDetector; import android.view.ScaleGestureDetector.OnScaleGestureListener; import android.view.View; import android.view.ViewConfiguration; +import android.view.WindowManagerInternal; import android.view.accessibility.AccessibilityEvent; import android.view.animation.DecelerateInterpolator; import com.android.internal.os.SomeArgs; +import com.android.server.LocalServices; import java.util.Locale; @@ -94,8 +92,8 @@ import java.util.Locale; * * 6. The magnification scale will be persisted in settings and in the cloud. */ -public final class ScreenMagnifier extends IMagnificationCallbacks.Stub - implements EventStreamTransformation { +public final class ScreenMagnifier implements WindowManagerInternal.MagnificationCallbacks, + EventStreamTransformation { private static final String LOG_TAG = ScreenMagnifier.class.getSimpleName(); @@ -127,7 +125,7 @@ public final class ScreenMagnifier extends IMagnificationCallbacks.Stub private final Rect mTempRect1 = new Rect(); private final Context mContext; - private final IWindowManager mWindowManager; + private final WindowManagerInternal mWindowManager; private final MagnificationController mMagnificationController; private final ScreenStateObserver mScreenStateObserver; @@ -191,8 +189,7 @@ public final class ScreenMagnifier extends IMagnificationCallbacks.Stub public ScreenMagnifier(Context context, int displayId, AccessibilityManagerService service) { mContext = context; - mWindowManager = IWindowManager.Stub.asInterface( - ServiceManager.getService("window")); + mWindowManager = LocalServices.getService(WindowManagerInternal.class); mAms = service; mLongAnimationDuration = context.getResources().getInteger( @@ -208,11 +205,7 @@ public final class ScreenMagnifier extends IMagnificationCallbacks.Stub mMagnificationController = new MagnificationController(mLongAnimationDuration); mScreenStateObserver = new ScreenStateObserver(context, mMagnificationController); - try { - mWindowManager.setMagnificationCallbacks(this); - } catch (RemoteException re) { - /* ignore */ - } + mWindowManager.setMagnificationCallbacks(this); transitionToState(STATE_DETECTING); } @@ -378,11 +371,7 @@ public final class ScreenMagnifier extends IMagnificationCallbacks.Stub @Override public void onDestroy() { mScreenStateObserver.destroy(); - try { - mWindowManager.setMagnificationCallbacks(null); - } catch (RemoteException re) { - /* ignore */ - } + mWindowManager.setMagnificationCallbacks(null); } private void handleMotionEventStateDelegating(MotionEvent event, @@ -462,7 +451,7 @@ public final class ScreenMagnifier extends IMagnificationCallbacks.Stub } return mTempPointerProperties; } - + private void transitionToState(int state) { if (DEBUG_STATE_TRANSITIONS) { switch (state) { @@ -1120,15 +1109,10 @@ public final class ScreenMagnifier extends IMagnificationCallbacks.Stub if (DEBUG_SET_MAGNIFICATION_SPEC) { Slog.i(LOG_TAG, "Sending: " + spec); } - try { - mSentMagnificationSpec.scale = spec.scale; - mSentMagnificationSpec.offsetX = spec.offsetX; - mSentMagnificationSpec.offsetY = spec.offsetY; - mWindowManager.setMagnificationSpec( - MagnificationSpec.obtain(spec)); - } catch (RemoteException re) { - /* ignore */ - } + mSentMagnificationSpec.scale = spec.scale; + mSentMagnificationSpec.offsetX = spec.offsetX; + mSentMagnificationSpec.offsetY = spec.offsetY; + mWindowManager.setMagnificationSpec(MagnificationSpec.obtain(spec)); } } diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java new file mode 100644 index 0000000..35b7f99 --- /dev/null +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -0,0 +1,1173 @@ +/* + * Copyright (C) 2014 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; + +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.app.Service; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PixelFormat; +import android.graphics.Point; +import android.graphics.PorterDuff.Mode; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Region; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.util.ArraySet; +import android.util.Log; +import android.util.Slog; +import android.util.SparseArray; +import android.util.TypedValue; +import android.view.MagnificationSpec; +import android.view.Surface; +import android.view.Surface.OutOfResourcesException; +import android.view.SurfaceControl; +import android.view.WindowInfo; +import android.view.WindowManager; +import android.view.WindowManagerInternal.MagnificationCallbacks; +import android.view.WindowManagerInternal.WindowsForAccessibilityCallback; +import android.view.WindowManagerPolicy; +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; +import java.util.List; +import java.util.Set; + +/** + * This class contains the accessibility related logic of the window manger. + */ +final class AccessibilityController { + + private final WindowManagerService mWindowManagerService; + + private static final float[] sTempFloats = new float[9]; + + public AccessibilityController(WindowManagerService service) { + mWindowManagerService = service; + } + + private DisplayMagnifier mDisplayMagnifier; + + private WindowsForAccessibilityObserver mWindowsForAccessibilityObserver; + + public void setMagnificationCallbacksLocked(MagnificationCallbacks callbacks) { + if (callbacks != null) { + if (mDisplayMagnifier != null) { + throw new IllegalStateException("Magnification callbacks already set!"); + } + mDisplayMagnifier = new DisplayMagnifier(mWindowManagerService, callbacks); + } else { + if (mDisplayMagnifier == null) { + throw new IllegalStateException("Magnification callbacks already cleared!"); + } + mDisplayMagnifier.destroyLocked(); + mDisplayMagnifier = null; + } + } + + public void setWindowsForAccessibilityCallback(WindowsForAccessibilityCallback callback) { + if (callback != null) { + if (mWindowsForAccessibilityObserver != null) { + throw new IllegalStateException( + "Windows for accessibility callback already set!"); + } + mWindowsForAccessibilityObserver = new WindowsForAccessibilityObserver( + mWindowManagerService, callback); + } else { + if (mWindowsForAccessibilityObserver == null) { + throw new IllegalStateException( + "Windows for accessibility callback already cleared!"); + } + mWindowsForAccessibilityObserver = null; + } + } + + public void setMagnificationSpecLocked(MagnificationSpec spec) { + if (mDisplayMagnifier != null) { + mDisplayMagnifier.setMagnificationSpecLocked(spec); + } + if (mWindowsForAccessibilityObserver != null) { + mWindowsForAccessibilityObserver.computeChangedWindows(); + } + } + + public void onRectangleOnScreenRequestedLocked(Rect rectangle, boolean immediate) { + if (mDisplayMagnifier != null) { + mDisplayMagnifier.onRectangleOnScreenRequestedLocked(rectangle, immediate); + } + // Not relevant for the window observer. + } + + public void onWindowLayersChangedLocked() { + if (mDisplayMagnifier != null) { + mDisplayMagnifier.onWindowLayersChangedLocked(); + } + if (mWindowsForAccessibilityObserver != null) { + mWindowsForAccessibilityObserver.computeChangedWindows(); + } + } + + public void onRotationChangedLocked(DisplayContent displayContent, int rotation) { + if (mDisplayMagnifier != null) { + mDisplayMagnifier.onRotationChangedLocked(displayContent, rotation); + } + if (mWindowsForAccessibilityObserver != null) { + mWindowsForAccessibilityObserver.computeChangedWindows(); + } + } + + public void onAppWindowTransitionLocked(WindowState windowState, int transition) { + if (mDisplayMagnifier != null) { + mDisplayMagnifier.onAppWindowTransitionLocked(windowState, transition); + } + // Not relevant for the window observer. + } + + public void onWindowTransitionLocked(WindowState windowState, int transition) { + if (mDisplayMagnifier != null) { + mDisplayMagnifier.onWindowTransitionLocked(windowState, transition); + } + if (mWindowsForAccessibilityObserver != null) { + mWindowsForAccessibilityObserver.computeChangedWindows(); + } + } + + public void onWindowFocusChangedLocked() { + // Not relevant for the display magnifier. + + if (mWindowsForAccessibilityObserver != null) { + mWindowsForAccessibilityObserver.computeChangedWindows(); + } + } + + /** NOTE: This has to be called within a surface transaction. */ + public void drawMagnifiedRegionBorderIfNeededLocked() { + if (mDisplayMagnifier != null) { + mDisplayMagnifier.drawMagnifiedRegionBorderIfNeededLocked(); + } + // Not relevant for the window observer. + } + + public MagnificationSpec getMagnificationSpecForWindowLocked(WindowState windowState) { + if (mDisplayMagnifier != null) { + return mDisplayMagnifier.getMagnificationSpecForWindowLocked(windowState); + } + return null; + } + + public boolean hasCallbacksLocked() { + return (mDisplayMagnifier != null + || mWindowsForAccessibilityObserver != null); + } + + private static void populateTransformationMatrixLocked(WindowState windowState, + Matrix outMatrix) { + sTempFloats[Matrix.MSCALE_X] = windowState.mWinAnimator.mDsDx; + sTempFloats[Matrix.MSKEW_Y] = windowState.mWinAnimator.mDtDx; + sTempFloats[Matrix.MSKEW_X] = windowState.mWinAnimator.mDsDy; + sTempFloats[Matrix.MSCALE_Y] = windowState.mWinAnimator.mDtDy; + sTempFloats[Matrix.MTRANS_X] = windowState.mShownFrame.left; + sTempFloats[Matrix.MTRANS_Y] = windowState.mShownFrame.top; + sTempFloats[Matrix.MPERSP_0] = 0; + sTempFloats[Matrix.MPERSP_1] = 0; + sTempFloats[Matrix.MPERSP_2] = 1; + outMatrix.setValues(sTempFloats); + } + + /** + * This class encapsulates the functionality related to display magnification. + */ + private static final class DisplayMagnifier { + + private static final String LOG_TAG = "DisplayMagnifier"; + + private static final boolean DEBUG_WINDOW_TRANSITIONS = false; + private static final boolean DEBUG_ROTATION = false; + private static final boolean DEBUG_LAYERS = false; + private static final boolean DEBUG_RECTANGLE_REQUESTED = false; + private static final boolean DEBUG_VIEWPORT_WINDOW = false; + + private final Rect mTempRect1 = new Rect(); + private final Rect mTempRect2 = new Rect(); + + private final Region mTempRegion1 = new Region(); + private final Region mTempRegion2 = new Region(); + private final Region mTempRegion3 = new Region(); + private final Region mTempRegion4 = new Region(); + + private final Context mContext; + private final WindowManagerService mWindowManagerService; + private final MagnifiedViewport mMagnifedViewport; + private final Handler mHandler; + + private final MagnificationCallbacks mCallbacks; + + private final long mLongAnimationDuration; + + public DisplayMagnifier(WindowManagerService windowManagerService, + MagnificationCallbacks callbacks) { + mContext = windowManagerService.mContext; + mWindowManagerService = windowManagerService; + mCallbacks = callbacks; + mHandler = new MyHandler(mWindowManagerService.mH.getLooper()); + mMagnifedViewport = new MagnifiedViewport(); + mLongAnimationDuration = mContext.getResources().getInteger( + com.android.internal.R.integer.config_longAnimTime); + } + + public void setMagnificationSpecLocked(MagnificationSpec spec) { + mMagnifedViewport.updateMagnificationSpecLocked(spec); + mMagnifedViewport.recomputeBoundsLocked(); + mWindowManagerService.scheduleAnimationLocked(); + } + + public void onRectangleOnScreenRequestedLocked(Rect rectangle, boolean immediate) { + if (DEBUG_RECTANGLE_REQUESTED) { + Slog.i(LOG_TAG, "Rectangle on screen requested: " + rectangle); + } + if (!mMagnifedViewport.isMagnifyingLocked()) { + return; + } + Rect magnifiedRegionBounds = mTempRect2; + mMagnifedViewport.getMagnifiedFrameInContentCoordsLocked(magnifiedRegionBounds); + if (magnifiedRegionBounds.contains(rectangle)) { + return; + } + SomeArgs args = SomeArgs.obtain(); + args.argi1 = rectangle.left; + args.argi2 = rectangle.top; + args.argi3 = rectangle.right; + args.argi4 = rectangle.bottom; + mHandler.obtainMessage(MyHandler.MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED, + args).sendToTarget(); + } + + public void onWindowLayersChangedLocked() { + if (DEBUG_LAYERS) { + Slog.i(LOG_TAG, "Layers changed."); + } + mMagnifedViewport.recomputeBoundsLocked(); + mWindowManagerService.scheduleAnimationLocked(); + } + + public void onRotationChangedLocked(DisplayContent displayContent, int rotation) { + if (DEBUG_ROTATION) { + Slog.i(LOG_TAG, "Rotaton: " + Surface.rotationToString(rotation) + + " displayId: " + displayContent.getDisplayId()); + } + mMagnifedViewport.onRotationChangedLocked(); + mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_ROTATION_CHANGED); + } + + public void onAppWindowTransitionLocked(WindowState windowState, int transition) { + if (DEBUG_WINDOW_TRANSITIONS) { + Slog.i(LOG_TAG, "Window transition: " + + AppTransition.appTransitionToString(transition) + + " displayId: " + windowState.getDisplayId()); + } + final boolean magnifying = mMagnifedViewport.isMagnifyingLocked(); + if (magnifying) { + switch (transition) { + case AppTransition.TRANSIT_ACTIVITY_OPEN: + case AppTransition.TRANSIT_TASK_OPEN: + case AppTransition.TRANSIT_TASK_TO_FRONT: + case AppTransition.TRANSIT_WALLPAPER_OPEN: + case AppTransition.TRANSIT_WALLPAPER_CLOSE: + case AppTransition.TRANSIT_WALLPAPER_INTRA_OPEN: { + mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_USER_CONTEXT_CHANGED); + } + } + } + } + + public void onWindowTransitionLocked(WindowState windowState, int transition) { + if (DEBUG_WINDOW_TRANSITIONS) { + Slog.i(LOG_TAG, "Window transition: " + + AppTransition.appTransitionToString(transition) + + " displayId: " + windowState.getDisplayId()); + } + final boolean magnifying = mMagnifedViewport.isMagnifyingLocked(); + final int type = windowState.mAttrs.type; + switch (transition) { + case WindowManagerPolicy.TRANSIT_ENTER: + case WindowManagerPolicy.TRANSIT_SHOW: { + if (!magnifying) { + break; + } + switch (type) { + case WindowManager.LayoutParams.TYPE_APPLICATION: + case WindowManager.LayoutParams.TYPE_APPLICATION_PANEL: + case WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA: + case WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL: + case WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG: + case WindowManager.LayoutParams.TYPE_SEARCH_BAR: + case WindowManager.LayoutParams.TYPE_PHONE: + case WindowManager.LayoutParams.TYPE_SYSTEM_ALERT: + case WindowManager.LayoutParams.TYPE_TOAST: + case WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY: + case WindowManager.LayoutParams.TYPE_PRIORITY_PHONE: + case WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG: + case WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG: + case WindowManager.LayoutParams.TYPE_SYSTEM_ERROR: + case WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY: + case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL: + case WindowManager.LayoutParams.TYPE_RECENTS_OVERLAY: { + Rect magnifiedRegionBounds = mTempRect2; + mMagnifedViewport.getMagnifiedFrameInContentCoordsLocked( + magnifiedRegionBounds); + Rect touchableRegionBounds = mTempRect1; + windowState.getTouchableRegion(mTempRegion1); + mTempRegion1.getBounds(touchableRegionBounds); + if (!magnifiedRegionBounds.intersect(touchableRegionBounds)) { + mCallbacks.onRectangleOnScreenRequested( + touchableRegionBounds.left, + touchableRegionBounds.top, + touchableRegionBounds.right, + touchableRegionBounds.bottom); + } + } break; + } break; + } + } + } + + public MagnificationSpec getMagnificationSpecForWindowLocked(WindowState windowState) { + MagnificationSpec spec = mMagnifedViewport.getMagnificationSpecLocked(); + if (spec != null && !spec.isNop()) { + WindowManagerPolicy policy = mWindowManagerService.mPolicy; + final int windowType = windowState.mAttrs.type; + if (!policy.isTopLevelWindow(windowType) && windowState.mAttachedWindow != null + && !policy.canMagnifyWindow(windowType)) { + return null; + } + if (!policy.canMagnifyWindow(windowState.mAttrs.type)) { + return null; + } + } + return spec; + } + + public void destroyLocked() { + mMagnifedViewport.destroyWindow(); + } + + /** NOTE: This has to be called within a surface transaction. */ + public void drawMagnifiedRegionBorderIfNeededLocked() { + mMagnifedViewport.drawWindowIfNeededLocked(); + } + + private final class MagnifiedViewport { + + private static final int DEFAUTLT_BORDER_WIDTH_DIP = 5; + + private final SparseArray<WindowState> mTempWindowStates = + new SparseArray<WindowState>(); + + private final RectF mTempRectF = new RectF(); + + private final Point mTempPoint = new Point(); + + private final Matrix mTempMatrix = new Matrix(); + + private final Region mMagnifiedBounds = new Region(); + private final Region mOldMagnifiedBounds = new Region(); + + private final MagnificationSpec mMagnificationSpec = MagnificationSpec.obtain(); + + private final WindowManager mWindowManager; + + private final int mBorderWidth; + private final int mHalfBorderWidth; + + private final ViewportWindow mWindow; + + private boolean mFullRedrawNeeded; + + public MagnifiedViewport() { + mWindowManager = (WindowManager) mContext.getSystemService(Service.WINDOW_SERVICE); + mBorderWidth = (int) TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, DEFAUTLT_BORDER_WIDTH_DIP, + mContext.getResources().getDisplayMetrics()); + mHalfBorderWidth = (int) (mBorderWidth + 0.5) / 2; + mWindow = new ViewportWindow(mContext); + recomputeBoundsLocked(); + } + + public void updateMagnificationSpecLocked(MagnificationSpec spec) { + if (spec != null) { + mMagnificationSpec.initialize(spec.scale, spec.offsetX, spec.offsetY); + } else { + mMagnificationSpec.clear(); + } + // If this message is pending we are in a rotation animation and do not want + // to show the border. We will do so when the pending message is handled. + if (!mHandler.hasMessages(MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)) { + setMagnifiedRegionBorderShownLocked(isMagnifyingLocked(), true); + } + } + + public void recomputeBoundsLocked() { + mWindowManager.getDefaultDisplay().getRealSize(mTempPoint); + final int screenWidth = mTempPoint.x; + final int screenHeight = mTempPoint.y; + + Region magnifiedBounds = mMagnifiedBounds; + magnifiedBounds.set(0, 0, 0, 0); + + Region availableBounds = mTempRegion1; + availableBounds.set(0, 0, screenWidth, screenHeight); + + Region nonMagnifiedBounds = mTempRegion4; + nonMagnifiedBounds.set(0, 0, 0, 0); + + SparseArray<WindowState> visibleWindows = mTempWindowStates; + visibleWindows.clear(); + populateWindowsOnScreenLocked(visibleWindows); + + final int visibleWindowCount = visibleWindows.size(); + for (int i = visibleWindowCount - 1; i >= 0; i--) { + WindowState windowState = visibleWindows.valueAt(i); + if (windowState.mAttrs.type == WindowManager + .LayoutParams.TYPE_MAGNIFICATION_OVERLAY) { + continue; + } + + Region windowBounds = mTempRegion2; + Matrix matrix = mTempMatrix; + populateTransformationMatrixLocked(windowState, matrix); + RectF windowFrame = mTempRectF; + + if (mWindowManagerService.mPolicy.canMagnifyWindow(windowState.mAttrs.type)) { + windowFrame.set(windowState.mFrame); + windowFrame.offset(-windowFrame.left, -windowFrame.top); + matrix.mapRect(windowFrame); + windowBounds.set((int) windowFrame.left, (int) windowFrame.top, + (int) windowFrame.right, (int) windowFrame.bottom); + magnifiedBounds.op(windowBounds, Region.Op.UNION); + magnifiedBounds.op(availableBounds, Region.Op.INTERSECT); + } else { + Region touchableRegion = mTempRegion3; + windowState.getTouchableRegion(touchableRegion); + Rect touchableFrame = mTempRect1; + touchableRegion.getBounds(touchableFrame); + windowFrame.set(touchableFrame); + windowFrame.offset(-windowState.mFrame.left, -windowState.mFrame.top); + matrix.mapRect(windowFrame); + windowBounds.set((int) windowFrame.left, (int) windowFrame.top, + (int) windowFrame.right, (int) windowFrame.bottom); + nonMagnifiedBounds.op(windowBounds, Region.Op.UNION); + windowBounds.op(magnifiedBounds, Region.Op.DIFFERENCE); + availableBounds.op(windowBounds, Region.Op.DIFFERENCE); + } + + Region accountedBounds = mTempRegion2; + accountedBounds.set(magnifiedBounds); + accountedBounds.op(nonMagnifiedBounds, Region.Op.UNION); + accountedBounds.op(0, 0, screenWidth, screenHeight, Region.Op.INTERSECT); + + if (accountedBounds.isRect()) { + Rect accountedFrame = mTempRect1; + accountedBounds.getBounds(accountedFrame); + if (accountedFrame.width() == screenWidth + && accountedFrame.height() == screenHeight) { + break; + } + } + } + + visibleWindows.clear(); + + magnifiedBounds.op(mHalfBorderWidth, mHalfBorderWidth, + screenWidth - mHalfBorderWidth, screenHeight - mHalfBorderWidth, + Region.Op.INTERSECT); + + if (!mOldMagnifiedBounds.equals(magnifiedBounds)) { + Region bounds = Region.obtain(); + bounds.set(magnifiedBounds); + mHandler.obtainMessage(MyHandler.MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED, + bounds).sendToTarget(); + + mWindow.setBounds(magnifiedBounds); + Rect dirtyRect = mTempRect1; + if (mFullRedrawNeeded) { + mFullRedrawNeeded = false; + dirtyRect.set(mHalfBorderWidth, mHalfBorderWidth, + screenWidth - mHalfBorderWidth, screenHeight - mHalfBorderWidth); + mWindow.invalidate(dirtyRect); + } else { + Region dirtyRegion = mTempRegion3; + dirtyRegion.set(magnifiedBounds); + dirtyRegion.op(mOldMagnifiedBounds, Region.Op.UNION); + dirtyRegion.op(nonMagnifiedBounds, Region.Op.INTERSECT); + dirtyRegion.getBounds(dirtyRect); + mWindow.invalidate(dirtyRect); + } + + mOldMagnifiedBounds.set(magnifiedBounds); + } + } + + public void onRotationChangedLocked() { + // If we are magnifying, hide the magnified border window immediately so + // the user does not see strange artifacts during rotation. The screenshot + // used for rotation has already the border. After the rotation is complete + // we will show the border. + if (isMagnifyingLocked()) { + setMagnifiedRegionBorderShownLocked(false, false); + final long delay = (long) (mLongAnimationDuration + * mWindowManagerService.mWindowAnimationScale); + Message message = mHandler.obtainMessage( + MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED); + mHandler.sendMessageDelayed(message, delay); + } + recomputeBoundsLocked(); + mWindow.updateSize(); + } + + public void setMagnifiedRegionBorderShownLocked(boolean shown, boolean animate) { + if (shown) { + mFullRedrawNeeded = true; + mOldMagnifiedBounds.set(0, 0, 0, 0); + } + mWindow.setShown(shown, animate); + } + + public void getMagnifiedFrameInContentCoordsLocked(Rect rect) { + MagnificationSpec spec = mMagnificationSpec; + mMagnifiedBounds.getBounds(rect); + rect.offset((int) -spec.offsetX, (int) -spec.offsetY); + rect.scale(1.0f / spec.scale); + } + + public boolean isMagnifyingLocked() { + return mMagnificationSpec.scale > 1.0f; + } + + public MagnificationSpec getMagnificationSpecLocked() { + return mMagnificationSpec; + } + + /** NOTE: This has to be called within a surface transaction. */ + public void drawWindowIfNeededLocked() { + recomputeBoundsLocked(); + mWindow.drawIfNeeded(); + } + + public void destroyWindow() { + mWindow.releaseSurface(); + } + + private void populateWindowsOnScreenLocked(SparseArray<WindowState> outWindows) { + DisplayContent displayContent = mWindowManagerService + .getDefaultDisplayContentLocked(); + WindowList windowList = displayContent.getWindowList(); + final int windowCount = windowList.size(); + for (int i = 0; i < windowCount; i++) { + WindowState windowState = windowList.get(i); + if ((windowState.isOnScreen() || windowState.mAttrs.type == WindowManager + .LayoutParams.TYPE_UNIVERSE_BACKGROUND) + && !windowState.mWinAnimator.mEnterAnimationPending) { + outWindows.put(windowState.mLayer, windowState); + } + } + } + + private final class ViewportWindow { + private static final String SURFACE_TITLE = "Magnification Overlay"; + + private static final String PROPERTY_NAME_ALPHA = "alpha"; + + private static final int MIN_ALPHA = 0; + private static final int MAX_ALPHA = 255; + + private final Region mBounds = new Region(); + private final Rect mDirtyRect = new Rect(); + private final Paint mPaint = new Paint(); + + private final ValueAnimator mShowHideFrameAnimator; + private final SurfaceControl mSurfaceControl; + private final Surface mSurface = new Surface(); + + private boolean mShown; + private int mAlpha; + + private boolean mInvalidated; + + public ViewportWindow(Context context) { + SurfaceControl surfaceControl = null; + try { + mWindowManager.getDefaultDisplay().getRealSize(mTempPoint); + surfaceControl = new SurfaceControl(mWindowManagerService.mFxSession, + SURFACE_TITLE, mTempPoint.x, mTempPoint.y, PixelFormat.TRANSLUCENT, + SurfaceControl.HIDDEN); + } catch (OutOfResourcesException oore) { + /* ignore */ + } + mSurfaceControl = surfaceControl; + mSurfaceControl.setLayerStack(mWindowManager.getDefaultDisplay() + .getLayerStack()); + mSurfaceControl.setLayer(mWindowManagerService.mPolicy.windowTypeToLayerLw( + WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY) + * WindowManagerService.TYPE_LAYER_MULTIPLIER); + mSurfaceControl.setPosition(0, 0); + mSurface.copyFrom(mSurfaceControl); + + TypedValue typedValue = new TypedValue(); + context.getTheme().resolveAttribute(R.attr.colorActivatedHighlight, + typedValue, true); + final int borderColor = context.getResources().getColor(typedValue.resourceId); + + mPaint.setStyle(Paint.Style.STROKE); + mPaint.setStrokeWidth(mBorderWidth); + mPaint.setColor(borderColor); + + Interpolator interpolator = new DecelerateInterpolator(2.5f); + final long longAnimationDuration = context.getResources().getInteger( + com.android.internal.R.integer.config_longAnimTime); + + mShowHideFrameAnimator = ObjectAnimator.ofInt(this, PROPERTY_NAME_ALPHA, + MIN_ALPHA, MAX_ALPHA); + mShowHideFrameAnimator.setInterpolator(interpolator); + mShowHideFrameAnimator.setDuration(longAnimationDuration); + mInvalidated = true; + } + + public void setShown(boolean shown, boolean animate) { + synchronized (mWindowManagerService.mWindowMap) { + if (mShown == shown) { + return; + } + mShown = shown; + if (animate) { + if (mShowHideFrameAnimator.isRunning()) { + mShowHideFrameAnimator.reverse(); + } else { + if (shown) { + mShowHideFrameAnimator.start(); + } else { + mShowHideFrameAnimator.reverse(); + } + } + } else { + mShowHideFrameAnimator.cancel(); + if (shown) { + setAlpha(MAX_ALPHA); + } else { + setAlpha(MIN_ALPHA); + } + } + if (DEBUG_VIEWPORT_WINDOW) { + Slog.i(LOG_TAG, "ViewportWindow shown: " + mShown); + } + } + } + + @SuppressWarnings("unused") + // Called reflectively from an animator. + public int getAlpha() { + synchronized (mWindowManagerService.mWindowMap) { + return mAlpha; + } + } + + public void setAlpha(int alpha) { + synchronized (mWindowManagerService.mWindowMap) { + if (mAlpha == alpha) { + return; + } + mAlpha = alpha; + invalidate(null); + if (DEBUG_VIEWPORT_WINDOW) { + Slog.i(LOG_TAG, "ViewportWindow set alpha: " + alpha); + } + } + } + + public void setBounds(Region bounds) { + synchronized (mWindowManagerService.mWindowMap) { + if (mBounds.equals(bounds)) { + return; + } + mBounds.set(bounds); + invalidate(mDirtyRect); + if (DEBUG_VIEWPORT_WINDOW) { + Slog.i(LOG_TAG, "ViewportWindow set bounds: " + bounds); + } + } + } + + public void updateSize() { + synchronized (mWindowManagerService.mWindowMap) { + mWindowManager.getDefaultDisplay().getRealSize(mTempPoint); + mSurfaceControl.setSize(mTempPoint.x, mTempPoint.y); + invalidate(mDirtyRect); + } + } + + public void invalidate(Rect dirtyRect) { + if (dirtyRect != null) { + mDirtyRect.set(dirtyRect); + } else { + mDirtyRect.setEmpty(); + } + mInvalidated = true; + mWindowManagerService.scheduleAnimationLocked(); + } + + /** NOTE: This has to be called within a surface transaction. */ + public void drawIfNeeded() { + synchronized (mWindowManagerService.mWindowMap) { + if (!mInvalidated) { + return; + } + mInvalidated = false; + Canvas canvas = null; + try { + // Empty dirty rectangle means unspecified. + if (mDirtyRect.isEmpty()) { + mBounds.getBounds(mDirtyRect); + } + mDirtyRect.inset(- mHalfBorderWidth, - mHalfBorderWidth); + canvas = mSurface.lockCanvas(mDirtyRect); + if (DEBUG_VIEWPORT_WINDOW) { + Slog.i(LOG_TAG, "Dirty rect: " + mDirtyRect); + } + } catch (IllegalArgumentException iae) { + /* ignore */ + } catch (Surface.OutOfResourcesException oore) { + /* ignore */ + } + if (canvas == null) { + return; + } + if (DEBUG_VIEWPORT_WINDOW) { + Slog.i(LOG_TAG, "Bounds: " + mBounds); + } + canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR); + mPaint.setAlpha(mAlpha); + Path path = mBounds.getBoundaryPath(); + canvas.drawPath(path, mPaint); + + mSurface.unlockCanvasAndPost(canvas); + + if (mAlpha > 0) { + mSurfaceControl.show(); + } else { + mSurfaceControl.hide(); + } + } + } + + public void releaseSurface() { + mSurfaceControl.release(); + mSurface.release(); + } + } + } + + private class MyHandler extends Handler { + public static final int MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED = 1; + public static final int MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED = 2; + public static final int MESSAGE_NOTIFY_USER_CONTEXT_CHANGED = 3; + public static final int MESSAGE_NOTIFY_ROTATION_CHANGED = 4; + public static final int MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED = 5; + + public MyHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message message) { + switch (message.what) { + case MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED: { + Region bounds = (Region) message.obj; + mCallbacks.onMagnifedBoundsChanged(bounds); + bounds.recycle(); + } break; + + case MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED: { + SomeArgs args = (SomeArgs) message.obj; + final int left = args.argi1; + final int top = args.argi2; + final int right = args.argi3; + final int bottom = args.argi4; + mCallbacks.onRectangleOnScreenRequested(left, top, right, bottom); + args.recycle(); + } break; + + case MESSAGE_NOTIFY_USER_CONTEXT_CHANGED: { + mCallbacks.onUserContextChanged(); + } break; + + case MESSAGE_NOTIFY_ROTATION_CHANGED: { + final int rotation = message.arg1; + mCallbacks.onRotationChanged(rotation); + } break; + + case MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED : { + synchronized (mWindowManagerService.mWindowMap) { + if (mMagnifedViewport.isMagnifyingLocked()) { + mMagnifedViewport.setMagnifiedRegionBorderShownLocked(true, true); + mWindowManagerService.scheduleAnimationLocked(); + } + } + } break; + } + } + } + } + + /** + * This class encapsulates the functionality related to computing the windows + * reported for accessibility purposes. These windows are all windows a sighted + * user can see on the screen. + */ + private static final class WindowsForAccessibilityObserver { + private static final String LOG_TAG = "WindowsForAccessibilityObserver"; + + private static final boolean DEBUG = false; + + private final SparseArray<WindowState> mTempWindowStates = + new SparseArray<WindowState>(); + + private final List<WindowInfo> mOldWindows = new ArrayList<WindowInfo>(); + + private final Set<IBinder> mTempBinderSet = new ArraySet<IBinder>(); + + private final RectF mTempRectF = new RectF(); + + private final Matrix mTempMatrix = new Matrix(); + + private final Point mTempPoint = new Point(); + + private final Rect mTempRect = new Rect(); + + private final Region mTempRegion = new Region(); + + private final Region mTempRegion1 = new Region(); + + private final Context mContext; + + private final WindowManagerService mWindowManagerService; + + private final Handler mHandler; + + private final WindowsForAccessibilityCallback mCallback; + + public WindowsForAccessibilityObserver(WindowManagerService windowManagerService, + WindowsForAccessibilityCallback callback) { + mContext = windowManagerService.mContext; + mWindowManagerService = windowManagerService; + mCallback = callback; + mHandler = new MyHandler(mWindowManagerService.mH.getLooper()); + computeChangedWindows(); + } + + public void computeChangedWindows() { + if (DEBUG) { + Slog.i(LOG_TAG, "computeChangedWindows()"); + } + + synchronized (mWindowManagerService.mWindowMap) { + WindowManager windowManager = (WindowManager) + mContext.getSystemService(Context.WINDOW_SERVICE); + windowManager.getDefaultDisplay().getRealSize(mTempPoint); + final int screenWidth = mTempPoint.x; + final int screenHeight = mTempPoint.y; + + Region unaccountedSpace = mTempRegion; + unaccountedSpace.set(0, 0, screenWidth, screenHeight); + + SparseArray<WindowState> visibleWindows = mTempWindowStates; + populateVisibleWindowsOnScreenLocked(visibleWindows); + + List<WindowInfo> windows = new ArrayList<WindowInfo>(); + + Set<IBinder> addedWindows = mTempBinderSet; + addedWindows.clear(); + + final int visibleWindowCount = visibleWindows.size(); + for (int i = visibleWindowCount - 1; i >= 0; i--) { + WindowState windowState = visibleWindows.valueAt(i); + // Compute the window touchable frame as shown on the screen. + + // Get the touchable frame. + Region touchableRegion = mTempRegion1; + windowState.getTouchableRegion(touchableRegion); + Rect touchableFrame = mTempRect; + touchableRegion.getBounds(touchableFrame); + + // Move to origin as all transforms are captured by the matrix. + RectF windowFrame = mTempRectF; + windowFrame.set(touchableFrame); + windowFrame.offset(-windowState.mFrame.left, -windowState.mFrame.top); + + // Map the frame to get what appears on the screen. + Matrix matrix = mTempMatrix; + populateTransformationMatrixLocked(windowState, matrix); + matrix.mapRect(windowFrame); + + // Got the bounds. + Rect boundsInScreen = mTempRect; + boundsInScreen.set((int) windowFrame.left, (int) windowFrame.top, + (int) windowFrame.right, (int) windowFrame.bottom); + + final int flags = windowState.mAttrs.flags; + + // If the window is not touchable, do not report it but take into account + // the space it takes since the content behind it cannot be touched. + if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) == 1) { + unaccountedSpace.op(boundsInScreen, unaccountedSpace, + Region.Op.DIFFERENCE); + continue; + } + + // If the window is completely covered by other windows - ignore. + if (unaccountedSpace.quickReject(boundsInScreen)) { + continue; + } + + // Add windows of certain types not covered by modal windows. + if (isReportedWindowType(windowState.mAttrs.type)) { + // Add the window to the ones to be reported. + WindowInfo window = WindowInfo.obtain(); + window.type = windowState.mAttrs.type; + window.layer = windowState.mLayer; + window.token = windowState.mClient.asBinder(); + + addedWindows.add(window.token); + + WindowState attachedWindow = windowState.mAttachedWindow; + if (attachedWindow != null) { + window.parentToken = attachedWindow.mClient.asBinder(); + } + + window.focused = windowState.isFocused(); + window.boundsInScreen.set(boundsInScreen); + + final int childCount = windowState.mChildWindows.size(); + if (childCount > 0) { + if (window.childTokens == null) { + window.childTokens = new ArrayList<IBinder>(); + } + for (int j = 0; j < childCount; j++) { + WindowState child = windowState.mChildWindows.get(j); + window.childTokens.add(child.mClient.asBinder()); + } + } + + windows.add(window); + } + + // Account for the space this window takes. + unaccountedSpace.op(boundsInScreen, unaccountedSpace, + Region.Op.REVERSE_DIFFERENCE); + + // We figured out what is touchable for the entire screen - done. + if (unaccountedSpace.isEmpty()) { + break; + } + + // If a window is modal, no other below can be touched - done. + if ((flags & (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)) == 0) { + break; + } + } + + // Remove child/parent references to windows that were not added. + final int windowCount = windows.size(); + for (int i = 0; i < windowCount; i++) { + WindowInfo window = windows.get(i); + if (!addedWindows.contains(window.parentToken)) { + window.parentToken = null; + } + if (window.childTokens != null) { + final int childTokenCount = window.childTokens.size(); + for (int j = childTokenCount - 1; j >= 0; j--) { + if (!addedWindows.contains(window.childTokens.get(j))) { + window.childTokens.remove(j); + } + } + // Leave the child token list if empty. + } + } + + visibleWindows.clear(); + addedWindows.clear(); + + // We computed the windows and if they changed notify the client. + boolean windowsChanged = false; + if (mOldWindows.size() != windows.size()) { + // Different size means something changed. + windowsChanged = true; + } else if (!mOldWindows.isEmpty() || !windows.isEmpty()) { + // Since we always traverse windows from high to low layer + // the old and new windows at the same index should be the + // same, otherwise something changed. + for (int i = 0; i < windowCount; i++) { + WindowInfo oldWindow = mOldWindows.get(i); + WindowInfo newWindow = windows.get(i); + // We do not care for layer changes given the window + // order does not change. This brings no new information + // to the clients. + if (windowChangedNoLayer(oldWindow, newWindow)) { + windowsChanged = true; + break; + } + } + } + + if (windowsChanged) { + if (DEBUG) { + Log.i(LOG_TAG, "Windows changed:" + windows); + } + // Remember the old windows to detect changes. + cacheWindows(windows); + // Announce the change. + mHandler.obtainMessage(MyHandler.MESSAGE_NOTIFY_WINDOWS_CHANGED, + windows).sendToTarget(); + } else { + if (DEBUG) { + Log.i(LOG_TAG, "No windows changed."); + } + // Recycle the nodes as we do not need them. + clearAndRecycleWindows(windows); + } + } + } + + private void cacheWindows(List<WindowInfo> windows) { + final int oldWindowCount = mOldWindows.size(); + for (int i = oldWindowCount - 1; i >= 0; i--) { + mOldWindows.remove(i).recycle(); + } + final int newWindowCount = windows.size(); + for (int i = 0; i < newWindowCount; i++) { + WindowInfo newWindow = windows.get(i); + mOldWindows.add(WindowInfo.obtain(newWindow)); + } + } + + private boolean windowChangedNoLayer(WindowInfo oldWindow, WindowInfo newWindow) { + if (oldWindow == newWindow) { + return false; + } + if (oldWindow == null && newWindow != null) { + return true; + } + if (oldWindow != null && newWindow == null) { + return true; + } + if (oldWindow.type != newWindow.type) { + return true; + } + if (oldWindow.focused != newWindow.focused) { + return true; + } + if (oldWindow.token == null) { + if (newWindow.token != null) { + return true; + } + } else if (!oldWindow.token.equals(newWindow.token)) { + return true; + } + if (oldWindow.parentToken == null) { + if (newWindow.parentToken != null) { + return true; + } + } else if (!oldWindow.parentToken.equals(newWindow.parentToken)) { + return true; + } + if (!oldWindow.boundsInScreen.equals(newWindow.boundsInScreen)) { + return true; + } + if (oldWindow.childTokens != null && newWindow.childTokens != null + && !oldWindow.childTokens.equals(newWindow.childTokens)) { + return true; + } + return false; + } + + private void clearAndRecycleWindows(List<WindowInfo> windows) { + final int windowCount = windows.size(); + for (int i = windowCount - 1; i >= 0; i--) { + windows.remove(i).recycle(); + } + } + + private static boolean isReportedWindowType(int windowType) { + return (windowType != WindowManager.LayoutParams.TYPE_KEYGUARD_SCRIM + && windowType != WindowManager.LayoutParams.TYPE_WALLPAPER + && windowType != WindowManager.LayoutParams.TYPE_BOOT_PROGRESS + && windowType != WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY + && windowType != WindowManager.LayoutParams.TYPE_DRAG + && windowType != WindowManager.LayoutParams.TYPE_HIDDEN_NAV_CONSUMER + && windowType != WindowManager.LayoutParams.TYPE_POINTER + && windowType != WindowManager.LayoutParams.TYPE_UNIVERSE_BACKGROUND + && windowType != WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY + && windowType != WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY + && windowType != WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY + && windowType != WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION); + } + + private void populateVisibleWindowsOnScreenLocked(SparseArray<WindowState> outWindows) { + DisplayContent displayContent = mWindowManagerService + .getDefaultDisplayContentLocked(); + WindowList windowList = displayContent.getWindowList(); + final int windowCount = windowList.size(); + for (int i = 0; i < windowCount; i++) { + WindowState windowState = windowList.get(i); + if (windowState.isVisibleLw()) { + outWindows.put(windowState.mLayer, windowState); + } + } + } + + private class MyHandler extends Handler { + public static final int MESSAGE_NOTIFY_WINDOWS_CHANGED = 1; + + public MyHandler(Looper looper) { + super(looper, null, false); + } + + @Override + @SuppressWarnings("unchecked") + public void handleMessage(Message message) { + switch (message.what) { + case MESSAGE_NOTIFY_WINDOWS_CHANGED: { + List<WindowInfo> windows = (List<WindowInfo>) message.obj; + mCallback.onWindowsForAccessibilityChanged(windows); + clearAndRecycleWindows(windows); + } break; + } + } + } + } +} diff --git a/services/core/java/com/android/server/wm/DisplayMagnifier.java b/services/core/java/com/android/server/wm/DisplayMagnifier.java deleted file mode 100644 index 382d7b4..0000000 --- a/services/core/java/com/android/server/wm/DisplayMagnifier.java +++ /dev/null @@ -1,756 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm; - -import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; -import android.app.Service; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Matrix; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.PixelFormat; -import android.graphics.Point; -import android.graphics.PorterDuff.Mode; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.Region; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.os.RemoteException; -import android.util.Pools.SimplePool; -import android.util.Slog; -import android.util.SparseArray; -import android.util.TypedValue; -import android.view.IMagnificationCallbacks; -import android.view.MagnificationSpec; -import android.view.Surface; -import android.view.Surface.OutOfResourcesException; -import android.view.SurfaceControl; -import android.view.WindowManager; -import android.view.WindowManagerPolicy; -import android.view.animation.DecelerateInterpolator; -import android.view.animation.Interpolator; - -import com.android.internal.R; -import com.android.internal.os.SomeArgs; - -/** - * This class is a part of the window manager and encapsulates the - * functionality related to display magnification. - */ -final class DisplayMagnifier { - private static final String LOG_TAG = DisplayMagnifier.class.getSimpleName(); - - private static final boolean DEBUG_WINDOW_TRANSITIONS = false; - private static final boolean DEBUG_ROTATION = false; - private static final boolean DEBUG_LAYERS = false; - private static final boolean DEBUG_RECTANGLE_REQUESTED = false; - private static final boolean DEBUG_VIEWPORT_WINDOW = false; - - private final Rect mTempRect1 = new Rect(); - private final Rect mTempRect2 = new Rect(); - - private final Region mTempRegion1 = new Region(); - private final Region mTempRegion2 = new Region(); - private final Region mTempRegion3 = new Region(); - private final Region mTempRegion4 = new Region(); - - private final Context mContext; - private final WindowManagerService mWindowManagerService; - private final MagnifiedViewport mMagnifedViewport; - private final Handler mHandler; - - private final IMagnificationCallbacks mCallbacks; - - private final long mLongAnimationDuration; - - public DisplayMagnifier(WindowManagerService windowManagerService, - IMagnificationCallbacks callbacks) { - mContext = windowManagerService.mContext; - mWindowManagerService = windowManagerService; - mCallbacks = callbacks; - mHandler = new MyHandler(mWindowManagerService.mH.getLooper()); - mMagnifedViewport = new MagnifiedViewport(); - mLongAnimationDuration = mContext.getResources().getInteger( - com.android.internal.R.integer.config_longAnimTime); - } - - public void setMagnificationSpecLocked(MagnificationSpec spec) { - mMagnifedViewport.updateMagnificationSpecLocked(spec); - mMagnifedViewport.recomputeBoundsLocked(); - mWindowManagerService.scheduleAnimationLocked(); - } - - public void onRectangleOnScreenRequestedLocked(Rect rectangle, boolean immediate) { - if (DEBUG_RECTANGLE_REQUESTED) { - Slog.i(LOG_TAG, "Rectangle on screen requested: " + rectangle); - } - if (!mMagnifedViewport.isMagnifyingLocked()) { - return; - } - Rect magnifiedRegionBounds = mTempRect2; - mMagnifedViewport.getMagnifiedFrameInContentCoordsLocked(magnifiedRegionBounds); - if (magnifiedRegionBounds.contains(rectangle)) { - return; - } - SomeArgs args = SomeArgs.obtain(); - args.argi1 = rectangle.left; - args.argi2 = rectangle.top; - args.argi3 = rectangle.right; - args.argi4 = rectangle.bottom; - mHandler.obtainMessage(MyHandler.MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED, - args).sendToTarget(); - } - - public void onWindowLayersChangedLocked() { - if (DEBUG_LAYERS) { - Slog.i(LOG_TAG, "Layers changed."); - } - mMagnifedViewport.recomputeBoundsLocked(); - mWindowManagerService.scheduleAnimationLocked(); - } - - public void onRotationChangedLocked(DisplayContent displayContent, int rotation) { - if (DEBUG_ROTATION) { - Slog.i(LOG_TAG, "Rotaton: " + Surface.rotationToString(rotation) - + " displayId: " + displayContent.getDisplayId()); - } - mMagnifedViewport.onRotationChangedLocked(); - mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_ROTATION_CHANGED); - } - - public void onAppWindowTransitionLocked(WindowState windowState, int transition) { - if (DEBUG_WINDOW_TRANSITIONS) { - Slog.i(LOG_TAG, "Window transition: " - + AppTransition.appTransitionToString(transition) - + " displayId: " + windowState.getDisplayId()); - } - final boolean magnifying = mMagnifedViewport.isMagnifyingLocked(); - if (magnifying) { - switch (transition) { - case AppTransition.TRANSIT_ACTIVITY_OPEN: - case AppTransition.TRANSIT_TASK_OPEN: - case AppTransition.TRANSIT_TASK_TO_FRONT: - case AppTransition.TRANSIT_WALLPAPER_OPEN: - case AppTransition.TRANSIT_WALLPAPER_CLOSE: - case AppTransition.TRANSIT_WALLPAPER_INTRA_OPEN: { - mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_USER_CONTEXT_CHANGED); - } - } - } - } - - public void onWindowTransitionLocked(WindowState windowState, int transition) { - if (DEBUG_WINDOW_TRANSITIONS) { - Slog.i(LOG_TAG, "Window transition: " - + AppTransition.appTransitionToString(transition) - + " displayId: " + windowState.getDisplayId()); - } - final boolean magnifying = mMagnifedViewport.isMagnifyingLocked(); - final int type = windowState.mAttrs.type; - switch (transition) { - case WindowManagerPolicy.TRANSIT_ENTER: - case WindowManagerPolicy.TRANSIT_SHOW: { - if (!magnifying) { - break; - } - switch (type) { - case WindowManager.LayoutParams.TYPE_APPLICATION: - case WindowManager.LayoutParams.TYPE_APPLICATION_PANEL: - case WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA: - case WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL: - case WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG: - case WindowManager.LayoutParams.TYPE_SEARCH_BAR: - case WindowManager.LayoutParams.TYPE_PHONE: - case WindowManager.LayoutParams.TYPE_SYSTEM_ALERT: - case WindowManager.LayoutParams.TYPE_TOAST: - case WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY: - case WindowManager.LayoutParams.TYPE_PRIORITY_PHONE: - case WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG: - case WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG: - case WindowManager.LayoutParams.TYPE_SYSTEM_ERROR: - case WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY: - case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL: - case WindowManager.LayoutParams.TYPE_RECENTS_OVERLAY: { - Rect magnifiedRegionBounds = mTempRect2; - mMagnifedViewport.getMagnifiedFrameInContentCoordsLocked( - magnifiedRegionBounds); - Rect touchableRegionBounds = mTempRect1; - windowState.getTouchableRegion(mTempRegion1); - mTempRegion1.getBounds(touchableRegionBounds); - if (!magnifiedRegionBounds.intersect(touchableRegionBounds)) { - try { - mCallbacks.onRectangleOnScreenRequested( - touchableRegionBounds.left, - touchableRegionBounds.top, - touchableRegionBounds.right, - touchableRegionBounds.bottom); - } catch (RemoteException re) { - /* ignore */ - } - } - } break; - } break; - } - } - } - - public MagnificationSpec getMagnificationSpecForWindowLocked(WindowState windowState) { - MagnificationSpec spec = mMagnifedViewport.getMagnificationSpecLocked(); - if (spec != null && !spec.isNop()) { - WindowManagerPolicy policy = mWindowManagerService.mPolicy; - final int windowType = windowState.mAttrs.type; - if (!policy.isTopLevelWindow(windowType) && windowState.mAttachedWindow != null - && !policy.canMagnifyWindow(windowType)) { - return null; - } - if (!policy.canMagnifyWindow(windowState.mAttrs.type)) { - return null; - } - } - return spec; - } - - public void destroyLocked() { - mMagnifedViewport.destroyWindow(); - } - - /** NOTE: This has to be called within a surface transaction. */ - public void drawMagnifiedRegionBorderIfNeededLocked() { - mMagnifedViewport.drawWindowIfNeededLocked(); - } - - private final class MagnifiedViewport { - - private static final int DEFAUTLT_BORDER_WIDTH_DIP = 5; - - private final SparseArray<WindowStateInfo> mTempWindowStateInfos = - new SparseArray<WindowStateInfo>(); - - private final float[] mTempFloats = new float[9]; - - private final RectF mTempRectF = new RectF(); - - private final Point mTempPoint = new Point(); - - private final Matrix mTempMatrix = new Matrix(); - - private final Region mMagnifiedBounds = new Region(); - private final Region mOldMagnifiedBounds = new Region(); - - private final MagnificationSpec mMagnificationSpec = MagnificationSpec.obtain(); - - private final WindowManager mWindowManager; - - private final int mBorderWidth; - private final int mHalfBorderWidth; - - private final ViewportWindow mWindow; - - private boolean mFullRedrawNeeded; - - public MagnifiedViewport() { - mWindowManager = (WindowManager) mContext.getSystemService(Service.WINDOW_SERVICE); - mBorderWidth = (int) TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, DEFAUTLT_BORDER_WIDTH_DIP, - mContext.getResources().getDisplayMetrics()); - mHalfBorderWidth = (int) (mBorderWidth + 0.5) / 2; - mWindow = new ViewportWindow(mContext); - recomputeBoundsLocked(); - } - - public void updateMagnificationSpecLocked(MagnificationSpec spec) { - if (spec != null) { - mMagnificationSpec.initialize(spec.scale, spec.offsetX, spec.offsetY); - } else { - mMagnificationSpec.clear(); - } - // If this message is pending we are in a rotation animation and do not want - // to show the border. We will do so when the pending message is handled. - if (!mHandler.hasMessages(MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)) { - setMagnifiedRegionBorderShownLocked(isMagnifyingLocked(), true); - } - } - - public void recomputeBoundsLocked() { - mWindowManager.getDefaultDisplay().getRealSize(mTempPoint); - final int screenWidth = mTempPoint.x; - final int screenHeight = mTempPoint.y; - - Region magnifiedBounds = mMagnifiedBounds; - magnifiedBounds.set(0, 0, 0, 0); - - Region availableBounds = mTempRegion1; - availableBounds.set(0, 0, screenWidth, screenHeight); - - Region nonMagnifiedBounds = mTempRegion4; - nonMagnifiedBounds.set(0, 0, 0, 0); - - SparseArray<WindowStateInfo> visibleWindows = mTempWindowStateInfos; - visibleWindows.clear(); - getWindowsOnScreenLocked(visibleWindows); - - final int visibleWindowCount = visibleWindows.size(); - for (int i = visibleWindowCount - 1; i >= 0; i--) { - WindowStateInfo info = visibleWindows.valueAt(i); - if (info.mWindowState.mAttrs.type == WindowManager - .LayoutParams.TYPE_MAGNIFICATION_OVERLAY) { - continue; - } - - Region windowBounds = mTempRegion2; - Matrix matrix = mTempMatrix; - populateTransformationMatrix(info.mWindowState, matrix); - RectF windowFrame = mTempRectF; - - if (mWindowManagerService.mPolicy.canMagnifyWindow(info.mWindowState.mAttrs.type)) { - windowFrame.set(info.mWindowState.mFrame); - windowFrame.offset(-windowFrame.left, -windowFrame.top); - matrix.mapRect(windowFrame); - windowBounds.set((int) windowFrame.left, (int) windowFrame.top, - (int) windowFrame.right, (int) windowFrame.bottom); - magnifiedBounds.op(windowBounds, Region.Op.UNION); - magnifiedBounds.op(availableBounds, Region.Op.INTERSECT); - } else { - windowFrame.set(info.mTouchableRegion); - windowFrame.offset(-info.mWindowState.mFrame.left, - -info.mWindowState.mFrame.top); - matrix.mapRect(windowFrame); - windowBounds.set((int) windowFrame.left, (int) windowFrame.top, - (int) windowFrame.right, (int) windowFrame.bottom); - nonMagnifiedBounds.op(windowBounds, Region.Op.UNION); - windowBounds.op(magnifiedBounds, Region.Op.DIFFERENCE); - availableBounds.op(windowBounds, Region.Op.DIFFERENCE); - } - - Region accountedBounds = mTempRegion2; - accountedBounds.set(magnifiedBounds); - accountedBounds.op(nonMagnifiedBounds, Region.Op.UNION); - accountedBounds.op(0, 0, screenWidth, screenHeight, Region.Op.INTERSECT); - - if (accountedBounds.isRect()) { - Rect accountedFrame = mTempRect1; - accountedBounds.getBounds(accountedFrame); - if (accountedFrame.width() == screenWidth - && accountedFrame.height() == screenHeight) { - break; - } - } - } - - for (int i = visibleWindowCount - 1; i >= 0; i--) { - WindowStateInfo info = visibleWindows.valueAt(i); - info.recycle(); - visibleWindows.removeAt(i); - } - - magnifiedBounds.op(mHalfBorderWidth, mHalfBorderWidth, - screenWidth - mHalfBorderWidth, screenHeight - mHalfBorderWidth, - Region.Op.INTERSECT); - - if (!mOldMagnifiedBounds.equals(magnifiedBounds)) { - Region bounds = Region.obtain(); - bounds.set(magnifiedBounds); - mHandler.obtainMessage(MyHandler.MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED, - bounds).sendToTarget(); - - mWindow.setBounds(magnifiedBounds); - Rect dirtyRect = mTempRect1; - if (mFullRedrawNeeded) { - mFullRedrawNeeded = false; - dirtyRect.set(mHalfBorderWidth, mHalfBorderWidth, - screenWidth - mHalfBorderWidth, screenHeight - mHalfBorderWidth); - mWindow.invalidate(dirtyRect); - } else { - Region dirtyRegion = mTempRegion3; - dirtyRegion.set(magnifiedBounds); - dirtyRegion.op(mOldMagnifiedBounds, Region.Op.UNION); - dirtyRegion.op(nonMagnifiedBounds, Region.Op.INTERSECT); - dirtyRegion.getBounds(dirtyRect); - mWindow.invalidate(dirtyRect); - } - - mOldMagnifiedBounds.set(magnifiedBounds); - } - } - - private void populateTransformationMatrix(WindowState windowState, Matrix outMatrix) { - mTempFloats[Matrix.MSCALE_X] = windowState.mWinAnimator.mDsDx; - mTempFloats[Matrix.MSKEW_Y] = windowState.mWinAnimator.mDtDx; - mTempFloats[Matrix.MSKEW_X] = windowState.mWinAnimator.mDsDy; - mTempFloats[Matrix.MSCALE_Y] = windowState.mWinAnimator.mDtDy; - mTempFloats[Matrix.MTRANS_X] = windowState.mShownFrame.left; - mTempFloats[Matrix.MTRANS_Y] = windowState.mShownFrame.top; - mTempFloats[Matrix.MPERSP_0] = 0; - mTempFloats[Matrix.MPERSP_1] = 0; - mTempFloats[Matrix.MPERSP_2] = 1; - outMatrix.setValues(mTempFloats); - } - - private void getWindowsOnScreenLocked(SparseArray<WindowStateInfo> outWindowStates) { - DisplayContent displayContent = mWindowManagerService.getDefaultDisplayContentLocked(); - WindowList windowList = displayContent.getWindowList(); - final int windowCount = windowList.size(); - for (int i = 0; i < windowCount; i++) { - WindowState windowState = windowList.get(i); - if ((windowState.isOnScreen() || windowState.mAttrs.type == WindowManager - .LayoutParams.TYPE_UNIVERSE_BACKGROUND) - && !windowState.mWinAnimator.mEnterAnimationPending) { - outWindowStates.put(windowState.mLayer, WindowStateInfo.obtain(windowState)); - } - } - } - - public void onRotationChangedLocked() { - // If we are magnifying, hide the magnified border window immediately so - // the user does not see strange artifacts during rotation. The screenshot - // used for rotation has already the border. After the rotation is complete - // we will show the border. - if (isMagnifyingLocked()) { - setMagnifiedRegionBorderShownLocked(false, false); - final long delay = (long) (mLongAnimationDuration - * mWindowManagerService.mWindowAnimationScale); - Message message = mHandler.obtainMessage( - MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED); - mHandler.sendMessageDelayed(message, delay); - } - recomputeBoundsLocked(); - mWindow.updateSize(); - } - - public void setMagnifiedRegionBorderShownLocked(boolean shown, boolean animate) { - if (shown) { - mFullRedrawNeeded = true; - mOldMagnifiedBounds.set(0, 0, 0, 0); - } - mWindow.setShown(shown, animate); - } - - public void getMagnifiedFrameInContentCoordsLocked(Rect rect) { - MagnificationSpec spec = mMagnificationSpec; - mMagnifiedBounds.getBounds(rect); - rect.offset((int) -spec.offsetX, (int) -spec.offsetY); - rect.scale(1.0f / spec.scale); - } - - public boolean isMagnifyingLocked() { - return mMagnificationSpec.scale > 1.0f; - } - - public MagnificationSpec getMagnificationSpecLocked() { - return mMagnificationSpec; - } - - /** NOTE: This has to be called within a surface transaction. */ - public void drawWindowIfNeededLocked() { - recomputeBoundsLocked(); - mWindow.drawIfNeeded(); - } - - public void destroyWindow() { - mWindow.releaseSurface(); - } - - private final class ViewportWindow { - private static final String SURFACE_TITLE = "Magnification Overlay"; - - private static final String PROPERTY_NAME_ALPHA = "alpha"; - - private static final int MIN_ALPHA = 0; - private static final int MAX_ALPHA = 255; - - private final Region mBounds = new Region(); - private final Rect mDirtyRect = new Rect(); - private final Paint mPaint = new Paint(); - - private final ValueAnimator mShowHideFrameAnimator; - private final SurfaceControl mSurfaceControl; - private final Surface mSurface = new Surface(); - - private boolean mShown; - private int mAlpha; - - private boolean mInvalidated; - - public ViewportWindow(Context context) { - SurfaceControl surfaceControl = null; - try { - mWindowManager.getDefaultDisplay().getRealSize(mTempPoint); - surfaceControl = new SurfaceControl(mWindowManagerService.mFxSession, SURFACE_TITLE, - mTempPoint.x, mTempPoint.y, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN); - } catch (OutOfResourcesException oore) { - /* ignore */ - } - mSurfaceControl = surfaceControl; - mSurfaceControl.setLayerStack(mWindowManager.getDefaultDisplay().getLayerStack()); - mSurfaceControl.setLayer(mWindowManagerService.mPolicy.windowTypeToLayerLw( - WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY) - * WindowManagerService.TYPE_LAYER_MULTIPLIER); - mSurfaceControl.setPosition(0, 0); - mSurface.copyFrom(mSurfaceControl); - - TypedValue typedValue = new TypedValue(); - context.getTheme().resolveAttribute(R.attr.colorActivatedHighlight, - typedValue, true); - final int borderColor = context.getResources().getColor(typedValue.resourceId); - - mPaint.setStyle(Paint.Style.STROKE); - mPaint.setStrokeWidth(mBorderWidth); - mPaint.setColor(borderColor); - - Interpolator interpolator = new DecelerateInterpolator(2.5f); - final long longAnimationDuration = context.getResources().getInteger( - com.android.internal.R.integer.config_longAnimTime); - - mShowHideFrameAnimator = ObjectAnimator.ofInt(this, PROPERTY_NAME_ALPHA, - MIN_ALPHA, MAX_ALPHA); - mShowHideFrameAnimator.setInterpolator(interpolator); - mShowHideFrameAnimator.setDuration(longAnimationDuration); - mInvalidated = true; - } - - public void setShown(boolean shown, boolean animate) { - synchronized (mWindowManagerService.mWindowMap) { - if (mShown == shown) { - return; - } - mShown = shown; - if (animate) { - if (mShowHideFrameAnimator.isRunning()) { - mShowHideFrameAnimator.reverse(); - } else { - if (shown) { - mShowHideFrameAnimator.start(); - } else { - mShowHideFrameAnimator.reverse(); - } - } - } else { - mShowHideFrameAnimator.cancel(); - if (shown) { - setAlpha(MAX_ALPHA); - } else { - setAlpha(MIN_ALPHA); - } - } - if (DEBUG_VIEWPORT_WINDOW) { - Slog.i(LOG_TAG, "ViewportWindow shown: " + mShown); - } - } - } - - @SuppressWarnings("unused") - // Called reflectively from an animator. - public int getAlpha() { - synchronized (mWindowManagerService.mWindowMap) { - return mAlpha; - } - } - - public void setAlpha(int alpha) { - synchronized (mWindowManagerService.mWindowMap) { - if (mAlpha == alpha) { - return; - } - mAlpha = alpha; - invalidate(null); - if (DEBUG_VIEWPORT_WINDOW) { - Slog.i(LOG_TAG, "ViewportWindow set alpha: " + alpha); - } - } - } - - public void setBounds(Region bounds) { - synchronized (mWindowManagerService.mWindowMap) { - if (mBounds.equals(bounds)) { - return; - } - mBounds.set(bounds); - invalidate(mDirtyRect); - if (DEBUG_VIEWPORT_WINDOW) { - Slog.i(LOG_TAG, "ViewportWindow set bounds: " + bounds); - } - } - } - - public void updateSize() { - synchronized (mWindowManagerService.mWindowMap) { - mWindowManager.getDefaultDisplay().getRealSize(mTempPoint); - mSurfaceControl.setSize(mTempPoint.x, mTempPoint.y); - invalidate(mDirtyRect); - } - } - - public void invalidate(Rect dirtyRect) { - if (dirtyRect != null) { - mDirtyRect.set(dirtyRect); - } else { - mDirtyRect.setEmpty(); - } - mInvalidated = true; - mWindowManagerService.scheduleAnimationLocked(); - } - - /** NOTE: This has to be called within a surface transaction. */ - public void drawIfNeeded() { - synchronized (mWindowManagerService.mWindowMap) { - if (!mInvalidated) { - return; - } - mInvalidated = false; - Canvas canvas = null; - try { - // Empty dirty rectangle means unspecified. - if (mDirtyRect.isEmpty()) { - mBounds.getBounds(mDirtyRect); - } - mDirtyRect.inset(- mHalfBorderWidth, - mHalfBorderWidth); - canvas = mSurface.lockCanvas(mDirtyRect); - if (DEBUG_VIEWPORT_WINDOW) { - Slog.i(LOG_TAG, "Dirty rect: " + mDirtyRect); - } - } catch (IllegalArgumentException iae) { - /* ignore */ - } catch (Surface.OutOfResourcesException oore) { - /* ignore */ - } - if (canvas == null) { - return; - } - if (DEBUG_VIEWPORT_WINDOW) { - Slog.i(LOG_TAG, "Bounds: " + mBounds); - } - canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR); - mPaint.setAlpha(mAlpha); - Path path = mBounds.getBoundaryPath(); - canvas.drawPath(path, mPaint); - - mSurface.unlockCanvasAndPost(canvas); - - if (mAlpha > 0) { - mSurfaceControl.show(); - } else { - mSurfaceControl.hide(); - } - } - } - - public void releaseSurface() { - mSurfaceControl.release(); - mSurface.release(); - } - } - } - - private static final class WindowStateInfo { - private static final int MAX_POOL_SIZE = 30; - - private static final SimplePool<WindowStateInfo> sPool = - new SimplePool<WindowStateInfo>(MAX_POOL_SIZE); - - private static final Region mTempRegion = new Region(); - - public WindowState mWindowState; - public final Rect mTouchableRegion = new Rect(); - - public static WindowStateInfo obtain(WindowState windowState) { - WindowStateInfo info = sPool.acquire(); - if (info == null) { - info = new WindowStateInfo(); - } - info.mWindowState = windowState; - windowState.getTouchableRegion(mTempRegion); - mTempRegion.getBounds(info.mTouchableRegion); - return info; - } - - public void recycle() { - mWindowState = null; - mTouchableRegion.setEmpty(); - sPool.release(this); - } - } - - private class MyHandler extends Handler { - public static final int MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED = 1; - public static final int MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED = 2; - public static final int MESSAGE_NOTIFY_USER_CONTEXT_CHANGED = 3; - public static final int MESSAGE_NOTIFY_ROTATION_CHANGED = 4; - public static final int MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED = 5; - - public MyHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message message) { - switch (message.what) { - case MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED: { - Region bounds = (Region) message.obj; - try { - mCallbacks.onMagnifedBoundsChanged(bounds); - } catch (RemoteException re) { - /* ignore */ - } finally { - bounds.recycle(); - } - } break; - case MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED: { - SomeArgs args = (SomeArgs) message.obj; - final int left = args.argi1; - final int top = args.argi2; - final int right = args.argi3; - final int bottom = args.argi4; - try { - mCallbacks.onRectangleOnScreenRequested(left, top, right, bottom); - } catch (RemoteException re) { - /* ignore */ - } finally { - args.recycle(); - } - } break; - case MESSAGE_NOTIFY_USER_CONTEXT_CHANGED: { - try { - mCallbacks.onUserContextChanged(); - } catch (RemoteException re) { - /* ignore */ - } - } break; - case MESSAGE_NOTIFY_ROTATION_CHANGED: { - final int rotation = message.arg1; - try { - mCallbacks.onRotationChanged(rotation); - } catch (RemoteException re) { - /* ignore */ - } - } break; - case MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED : { - synchronized (mWindowManagerService.mWindowMap) { - if (mMagnifedViewport.isMagnifyingLocked()) { - mMagnifedViewport.setMagnifiedRegionBorderShownLocked(true, true); - mWindowManagerService.scheduleAnimationLocked(); - } - } - } break; - } - } - } -} diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index 0c68258..266527d 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -529,8 +529,9 @@ public class WindowAnimator { mAnimating |= mService.getDisplayContentLocked(displayId).animateDimLayers(); //TODO (multidisplay): Magnification is supported only for the default display. - if (mService.mDisplayMagnifier != null && displayId == Display.DEFAULT_DISPLAY) { - mService.mDisplayMagnifier.drawMagnifiedRegionBorderIfNeededLocked(); + if (mService.mAccessibilityController != null + && displayId == Display.DEFAULT_DISPLAY) { + mService.mAccessibilityController.drawMagnifiedRegionBorderIfNeededLocked(); } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index a4f960e..d6a1e5d 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -103,12 +103,12 @@ import android.view.DisplayInfo; import android.view.Gravity; import android.view.IApplicationToken; import android.view.IInputFilter; -import android.view.IMagnificationCallbacks; import android.view.IOnKeyguardExitResult; import android.view.IRotationWatcher; import android.view.IWindow; import android.view.IWindowManager; import android.view.IWindowSession; +import android.view.IWindowsForAccessibilityCallback; import android.view.InputChannel; import android.view.InputDevice; import android.view.InputEvent; @@ -418,7 +418,7 @@ public class WindowManagerService extends IWindowManager.Stub IInputMethodManager mInputMethodManager; - DisplayMagnifier mDisplayMagnifier; + AccessibilityController mAccessibilityController; final SurfaceSession mFxSession; Watermark mWatermark; @@ -2439,9 +2439,9 @@ public class WindowManagerService extends IWindowManager.Stub win.mExiting = true; } //TODO (multidisplay): Magnification is supported only for the default display. - if (mDisplayMagnifier != null + if (mAccessibilityController != null && win.getDisplayId() == Display.DEFAULT_DISPLAY) { - mDisplayMagnifier.onWindowTransitionLocked(win, transit); + mAccessibilityController.onWindowTransitionLocked(win, transit); } } if (win.mExiting || win.mWinAnimator.isAnimating()) { @@ -2759,11 +2759,12 @@ public class WindowManagerService extends IWindowManager.Stub public void onRectangleOnScreenRequested(IBinder token, Rect rectangle, boolean immediate) { synchronized (mWindowMap) { - if (mDisplayMagnifier != null) { + if (mAccessibilityController != null) { WindowState window = mWindowMap.get(token); //TODO (multidisplay): Magnification is supported only for the default display. if (window != null && window.getDisplayId() == Display.DEFAULT_DISPLAY) { - mDisplayMagnifier.onRectangleOnScreenRequestedLocked(rectangle, immediate); + mAccessibilityController.onRectangleOnScreenRequestedLocked(rectangle, + immediate); } } } @@ -2998,9 +2999,9 @@ public class WindowManagerService extends IWindowManager.Stub winAnimator.destroySurfaceLocked(); } //TODO (multidisplay): Magnification is supported only for the default - if (mDisplayMagnifier != null + if (mAccessibilityController != null && win.getDisplayId() == Display.DEFAULT_DISPLAY) { - mDisplayMagnifier.onWindowTransitionLocked(win, transit); + mAccessibilityController.onWindowTransitionLocked(win, transit); } } } @@ -3143,86 +3144,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - @Override - public void getWindowFrame(IBinder token, Rect outBounds) { - if (!checkCallingPermission(android.Manifest.permission.RETRIEVE_WINDOW_INFO, - "getWindowInfo()")) { - throw new SecurityException("Requires RETRIEVE_WINDOW_INFO permission."); - } - synchronized (mWindowMap) { - WindowState windowState = mWindowMap.get(token); - if (windowState != null) { - outBounds.set(windowState.mFrame); - } else { - outBounds.setEmpty(); - } - } - } - - @Override - public void setMagnificationSpec(MagnificationSpec spec) { - if (!checkCallingPermission(android.Manifest.permission.MAGNIFY_DISPLAY, - "setMagnificationSpec()")) { - throw new SecurityException("Requires MAGNIFY_DISPLAY permission."); - } - synchronized (mWindowMap) { - if (mDisplayMagnifier != null) { - mDisplayMagnifier.setMagnificationSpecLocked(spec); - } else { - throw new IllegalStateException("Magnification callbacks not set!"); - } - } - if (Binder.getCallingPid() != android.os.Process.myPid()) { - spec.recycle(); - } - } - - @Override - public MagnificationSpec getCompatibleMagnificationSpecForWindow(IBinder windowToken) { - if (!checkCallingPermission(android.Manifest.permission.MAGNIFY_DISPLAY, - "getCompatibleMagnificationSpecForWindow()")) { - throw new SecurityException("Requires MAGNIFY_DISPLAY permission."); - } - synchronized (mWindowMap) { - WindowState windowState = mWindowMap.get(windowToken); - if (windowState == null) { - return null; - } - MagnificationSpec spec = null; - if (mDisplayMagnifier != null) { - spec = mDisplayMagnifier.getMagnificationSpecForWindowLocked(windowState); - } - if ((spec == null || spec.isNop()) && windowState.mGlobalScale == 1.0f) { - return null; - } - spec = (spec == null) ? MagnificationSpec.obtain() : MagnificationSpec.obtain(spec); - spec.scale *= windowState.mGlobalScale; - return spec; - } - } - - @Override - public void setMagnificationCallbacks(IMagnificationCallbacks callbacks) { - if (!checkCallingPermission(android.Manifest.permission.MAGNIFY_DISPLAY, - "setMagnificationCallbacks()")) { - throw new SecurityException("Requires MAGNIFY_DISPLAY permission."); - } - synchronized (mWindowMap) { - if (mDisplayMagnifier == null) { - mDisplayMagnifier = new DisplayMagnifier(this, callbacks); - } else { - if (callbacks == null) { - if (mDisplayMagnifier != null) { - mDisplayMagnifier.destroyLocked(); - mDisplayMagnifier = null; - } - } else { - throw new IllegalStateException("Magnification callbacks already set!"); - } - } - } - } - private boolean applyAnimationLocked(AppWindowToken atoken, WindowManager.LayoutParams lp, int transit, boolean enter) { // Only apply an animation if the display isn't frozen. If it is @@ -3402,8 +3323,8 @@ public class WindowManagerService extends IWindowManager.Stub win.mWinAnimator.applyAnimationLocked(WindowManagerPolicy.TRANSIT_EXIT, false); //TODO (multidisplay): Magnification is supported only for the default - if (mDisplayMagnifier != null && win.isDefaultDisplay()) { - mDisplayMagnifier.onWindowTransitionLocked(win, + if (mAccessibilityController != null && win.isDefaultDisplay()) { + mAccessibilityController.onWindowTransitionLocked(win, WindowManagerPolicy.TRANSIT_EXIT); } changed = true; @@ -4259,9 +4180,9 @@ public class WindowManagerService extends IWindowManager.Stub } WindowState window = wtoken.findMainWindow(); //TODO (multidisplay): Magnification is supported only for the default display. - if (window != null && mDisplayMagnifier != null + if (window != null && mAccessibilityController != null && window.getDisplayId() == Display.DEFAULT_DISPLAY) { - mDisplayMagnifier.onAppWindowTransitionLocked(window, transit); + mAccessibilityController.onAppWindowTransitionLocked(window, transit); } changed = true; } @@ -4281,9 +4202,9 @@ public class WindowManagerService extends IWindowManager.Stub win.mWinAnimator.applyAnimationLocked( WindowManagerPolicy.TRANSIT_ENTER, true); //TODO (multidisplay): Magnification is supported only for the default - if (mDisplayMagnifier != null + if (mAccessibilityController != null && win.getDisplayId() == Display.DEFAULT_DISPLAY) { - mDisplayMagnifier.onWindowTransitionLocked(win, + mAccessibilityController.onWindowTransitionLocked(win, WindowManagerPolicy.TRANSIT_ENTER); } } @@ -4298,9 +4219,9 @@ public class WindowManagerService extends IWindowManager.Stub win.mWinAnimator.applyAnimationLocked( WindowManagerPolicy.TRANSIT_EXIT, false); //TODO (multidisplay): Magnification is supported only for the default - if (mDisplayMagnifier != null + if (mAccessibilityController != null && win.getDisplayId() == Display.DEFAULT_DISPLAY) { - mDisplayMagnifier.onWindowTransitionLocked(win, + mAccessibilityController.onWindowTransitionLocked(win, WindowManagerPolicy.TRANSIT_EXIT); } } @@ -5278,19 +5199,6 @@ public class WindowManagerService extends IWindowManager.Stub ShutdownThread.rebootSafeMode(mContext, confirm); } - @Override - public void setInputFilter(IInputFilter filter) { - if (!checkCallingPermission(android.Manifest.permission.FILTER_EVENTS, "setInputFilter()")) { - throw new SecurityException("Requires FILTER_EVENTS permission"); - } - mInputManager.setInputFilter(filter); - } - - @Override - public void setTouchExplorationEnabled(boolean enabled) { - mPolicy.setTouchExplorationEnabled(enabled); - } - public void updateRelatedUserIds(final int[] relatedUserIds) { synchronized (mWindowMap) { mRelatedUserIds = relatedUserIds; @@ -6106,9 +6014,9 @@ public class WindowManagerService extends IWindowManager.Stub } //TODO (multidisplay): Magnification is supported only for the default display. - if (mDisplayMagnifier != null + if (mAccessibilityController != null && displayContent.getDisplayId() == Display.DEFAULT_DISPLAY) { - mDisplayMagnifier.onRotationChangedLocked(getDefaultDisplayContentLocked(), rotation); + mAccessibilityController.onRotationChangedLocked(getDefaultDisplayContentLocked(), rotation); } return true; @@ -7015,21 +6923,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - @Override - public IBinder getFocusedWindowToken() { - if (!checkCallingPermission(android.Manifest.permission.RETRIEVE_WINDOW_INFO, - "getFocusedWindowToken()")) { - throw new SecurityException("Requires RETRIEVE_WINDOW_INFO permission."); - } - synchronized (mWindowMap) { - WindowState windowState = getFocusedWindowLocked(); - if (windowState != null) { - return windowState.mClient.asBinder(); - } - return null; - } - } - private WindowState getFocusedWindow() { synchronized (mWindowMap) { return getFocusedWindowLocked(); @@ -8209,9 +8102,9 @@ public class WindowManagerService extends IWindowManager.Stub } //TODO (multidisplay): Magnification is supported only for the default display. - if (mDisplayMagnifier != null && anyLayerChanged + if (mAccessibilityController != null && anyLayerChanged && windows.get(windows.size() - 1).getDisplayId() == Display.DEFAULT_DISPLAY) { - mDisplayMagnifier.onWindowLayersChangedLocked(); + mAccessibilityController.onWindowLayersChangedLocked(); } } @@ -9844,6 +9737,11 @@ public class WindowManagerService extends IWindowManager.Stub final WindowState oldFocus = mCurrentFocus; mCurrentFocus = newFocus; mLosingFocus.remove(newFocus); + + if (mAccessibilityController != null) { + mAccessibilityController.onWindowFocusChangedLocked(); + } + int focusChanged = mPolicy.focusChangedLw(oldFocus, newFocus); if (imWindowChanged && oldFocus != mInputMethodWindow) { @@ -10957,5 +10855,100 @@ public class WindowManagerService extends IWindowManager.Stub public void requestTraversalFromDisplayManager() { requestTraversal(); } + + @Override + public void setMagnificationSpec(MagnificationSpec spec) { + synchronized (mWindowMap) { + if (mAccessibilityController != null) { + mAccessibilityController.setMagnificationSpecLocked(spec); + } else { + throw new IllegalStateException("Magnification callbacks not set!"); + } + } + if (Binder.getCallingPid() != android.os.Process.myPid()) { + spec.recycle(); + } + } + + @Override + public MagnificationSpec getCompatibleMagnificationSpecForWindow(IBinder windowToken) { + synchronized (mWindowMap) { + WindowState windowState = mWindowMap.get(windowToken); + if (windowState == null) { + return null; + } + MagnificationSpec spec = null; + if (mAccessibilityController != null) { + spec = mAccessibilityController.getMagnificationSpecForWindowLocked(windowState); + } + if ((spec == null || spec.isNop()) && windowState.mGlobalScale == 1.0f) { + return null; + } + spec = (spec == null) ? MagnificationSpec.obtain() : MagnificationSpec.obtain(spec); + spec.scale *= windowState.mGlobalScale; + return spec; + } + } + + @Override + public void setMagnificationCallbacks(MagnificationCallbacks callbacks) { + synchronized (mWindowMap) { + if (mAccessibilityController == null) { + mAccessibilityController = new AccessibilityController( + WindowManagerService.this); + } + mAccessibilityController.setMagnificationCallbacksLocked(callbacks); + if (!mAccessibilityController.hasCallbacksLocked()) { + mAccessibilityController = null; + } + } + } + + @Override + public void setWindowsForAccessibilityCallback(WindowsForAccessibilityCallback callback) { + synchronized (mWindowMap) { + if (mAccessibilityController == null) { + mAccessibilityController = new AccessibilityController( + WindowManagerService.this); + } + mAccessibilityController.setWindowsForAccessibilityCallback(callback); + if (!mAccessibilityController.hasCallbacksLocked()) { + mAccessibilityController = null; + } + } + } + + @Override + public void setInputFilter(IInputFilter filter) { + mInputManager.setInputFilter(filter); + } + + @Override + public IBinder getFocusedWindowToken() { + synchronized (mWindowMap) { + WindowState windowState = getFocusedWindowLocked(); + if (windowState != null) { + return windowState.mClient.asBinder(); + } + return null; + } + } + + @Override + public boolean isKeyguardLocked() { + return isKeyguardLocked(); + } + + @Override + public void getWindowFrame(IBinder token, Rect outBounds) { + synchronized (mWindowMap) { + WindowState windowState = mWindowMap.get(token); + if (windowState != null) { + outBounds.set(windowState.mFrame); + } else { + outBounds.setEmpty(); + } + } + } } } diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 93f6d22..2cd6000 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -952,8 +952,8 @@ class WindowStateAnimator { tmpMatrix.postConcat(screenRotationAnimation.getEnterTransformation().getMatrix()); } //TODO (multidisplay): Magnification is supported only for the default display. - if (mService.mDisplayMagnifier != null && displayId == Display.DEFAULT_DISPLAY) { - MagnificationSpec spec = mService.mDisplayMagnifier + if (mService.mAccessibilityController != null && displayId == Display.DEFAULT_DISPLAY) { + MagnificationSpec spec = mService.mAccessibilityController .getMagnificationSpecForWindowLocked(mWin); if (spec != null && !spec.isNop()) { tmpMatrix.postScale(spec.scale, spec.scale); @@ -1032,8 +1032,8 @@ class WindowStateAnimator { && mWin.mBaseLayer < mAnimator.mAboveUniverseLayer); MagnificationSpec spec = null; //TODO (multidisplay): Magnification is supported only for the default display. - if (mService.mDisplayMagnifier != null && displayId == Display.DEFAULT_DISPLAY) { - spec = mService.mDisplayMagnifier.getMagnificationSpecForWindowLocked(mWin); + if (mService.mAccessibilityController != null && displayId == Display.DEFAULT_DISPLAY) { + spec = mService.mAccessibilityController.getMagnificationSpecForWindowLocked(mWin); } if (applyUniverseTransformation || spec != null) { final Rect frame = mWin.mFrame; @@ -1565,9 +1565,9 @@ class WindowStateAnimator { } applyAnimationLocked(transit, true); //TODO (multidisplay): Magnification is supported only for the default display. - if (mService.mDisplayMagnifier != null + if (mService.mAccessibilityController != null && mWin.getDisplayId() == Display.DEFAULT_DISPLAY) { - mService.mDisplayMagnifier.onWindowTransitionLocked(mWin, transit); + mService.mAccessibilityController.onWindowTransitionLocked(mWin, transit); } } |