diff options
Diffstat (limited to 'core/java/android')
26 files changed, 2120 insertions, 670 deletions
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]; + } + }; +} |