diff options
Diffstat (limited to 'core')
11 files changed, 480 insertions, 189 deletions
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 8bb305d..164acbc 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -190,9 +190,9 @@ public abstract class AccessibilityService extends Service { * <li> * Register for all event types with no notification timeout and keep track * for the active window by calling - * {@link AccessibilityEvent#getAccessibilityWindowId()} of the last received + * {@link AccessibilityEvent#getWindowId()} of the last received * event and compare this with the - * {@link AccessibilityNodeInfo#getAccessibilityWindowId()} before calling + * {@link AccessibilityNodeInfo#getWindowId()} before calling * retrieval methods on the latter. * </li> * <li> diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl index 19f0bf0..8b4e7aee 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -47,7 +47,9 @@ interface IAccessibilityServiceConnection { /** * Finds {@link AccessibilityNodeInfo}s by View text. The match is case - * insensitive containment. + * insensitive containment. The search is performed in the window whose + * id is specified and starts from the View whose accessibility id is + * specified. * <p> * <strong> * It is a client responsibility to recycle the received infos by @@ -57,12 +59,35 @@ interface IAccessibilityServiceConnection { * </p> * * @param text The searched text. + * @param accessibilityId The id of the view from which to start searching. + * Use {@link android.view.View#NO_ID} to start from the root. * @return A list of node info. */ - List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewText(String text); + List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewText(String text, + int accessibilityWindowId, int accessibilityViewId); /** - * Finds an {@link AccessibilityNodeInfo} by View id. + * Finds {@link AccessibilityNodeInfo}s by View text. The match is case + * insensitive containment. The search is performed in the currently + * active window and start from the root View in the window. + * <p> + * <strong> + * It is a client responsibility to recycle the received infos by + * calling {@link AccessibilityNodeInfo#recycle()} to avoid creating + * of multiple instances. + * </strong> + * </p> + * + * @param text The searched text. + * @param accessibilityId The id of the view from which to start searching. + * Use {@link android.view.View#NO_ID} to start from the root. + * @return A list of node info. + */ + List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewTextInActiveWindow(String text); + + /** + * Finds an {@link AccessibilityNodeInfo} by View id. The search is performed + * in the currently active window and start from the root View in the window. * <p> * <strong> * It is a client responsibility to recycle the received info by @@ -74,7 +99,7 @@ interface IAccessibilityServiceConnection { * @param id The id of the node. * @return The node info. */ - AccessibilityNodeInfo findAccessibilityNodeInfoByViewId(int viewId); + AccessibilityNodeInfo findAccessibilityNodeInfoByViewIdInActiveWindow(int viewId); /** * Performs an accessibility action on an {@link AccessibilityNodeInfo}. diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 441cdc1..ee18722 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -3704,7 +3704,8 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit * The base implementation sets: * <ul> * <li>{@link AccessibilityNodeInfo#setParent(View)},</li> - * <li>{@link AccessibilityNodeInfo#setBounds(Rect)},</li> + * <li>{@link AccessibilityNodeInfo#setBoundsInParent(Rect)},</li> + * <li>{@link AccessibilityNodeInfo#setBoundsInScreen(Rect)},</li> * <li>{@link AccessibilityNodeInfo#setPackageName(CharSequence)},</li> * <li>{@link AccessibilityNodeInfo#setClassName(CharSequence)},</li> * <li>{@link AccessibilityNodeInfo#setContentDescription(CharSequence)},</li> @@ -3724,7 +3725,12 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { Rect bounds = mAttachInfo.mTmpInvalRect; getDrawingRect(bounds); - info.setBounds(bounds); + info.setBoundsInParent(bounds); + + int[] locationOnScreen = mAttachInfo.mInvalidateChildLocation; + getLocationOnScreen(locationOnScreen); + bounds.offset(locationOnScreen[0], locationOnScreen[1]); + info.setBoundsInScreen(bounds); ViewParent parent = getParent(); if (parent instanceof View) { diff --git a/core/java/android/view/ViewAncestor.java b/core/java/android/view/ViewAncestor.java index 17d7454..914973e 100644 --- a/core/java/android/view/ViewAncestor.java +++ b/core/java/android/view/ViewAncestor.java @@ -136,6 +136,13 @@ public final class ViewAncestor extends Handler implements ViewParent, static final ArrayList<ComponentCallbacks> sConfigCallbacks = new ArrayList<ComponentCallbacks>(); + /** + * Delay before dispatching an accessibility event that the window + * content has changed. The window content is considered changed + * after a layout pass. + */ + private static final long SEND_WINDOW_CONTENT_CHANGED_DELAY_MILLIS = 500; + long mLastTrackballTime = 0; final TrackballAxis mTrackballAxisX = new TrackballAxis(); final TrackballAxis mTrackballAxisY = new TrackballAxis(); @@ -277,6 +284,8 @@ public final class ViewAncestor extends Handler implements ViewParent, AccessibilityInteractionConnectionManager mAccessibilityInteractionConnectionManager; + SendWindowContentChanged mSendWindowContentChanged; + private final int mDensity; /** @@ -1427,6 +1436,10 @@ public final class ViewAncestor extends Handler implements ViewParent, if (triggerGlobalLayoutListener) { attachInfo.mRecomputeGlobalAttributes = false; attachInfo.mTreeObserver.dispatchOnGlobalLayout(); + + if (AccessibilityManager.getInstance(host.mContext).isEnabled()) { + postSendWindowContentChangedCallback(); + } } if (computesInternalInsets) { @@ -2086,6 +2099,7 @@ public final class ViewAncestor extends Handler implements ViewParent, mAccessibilityInteractionConnectionManager.ensureNoConnection(); mAccessibilityManager.removeAccessibilityStateChangeListener( mAccessibilityInteractionConnectionManager); + removeSendWindowContentChangedCallback(); mView = null; mAttachInfo.mRootView = null; @@ -3671,6 +3685,29 @@ public final class ViewAncestor extends Handler implements ViewParent, } } + /** + * Post a callback to send a + * {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event. + */ + private void postSendWindowContentChangedCallback() { + if (mSendWindowContentChanged == null) { + mSendWindowContentChanged = new SendWindowContentChanged(); + } else { + removeCallbacks(mSendWindowContentChanged); + } + postDelayed(mSendWindowContentChanged, SEND_WINDOW_CONTENT_CHANGED_DELAY_MILLIS); + } + + /** + * Remove a posted callback to send a + * {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event. + */ + private void removeSendWindowContentChangedCallback() { + if (mSendWindowContentChanged != null) { + removeCallbacks(mSendWindowContentChanged); + } + } + public boolean showContextMenuForChild(View originalView) { return false; } @@ -4268,12 +4305,12 @@ public final class ViewAncestor extends Handler implements ViewParent, } } - public void findAccessibilityNodeInfosByViewText(String text, int interactionId, - IAccessibilityInteractionConnectionCallback callback) { + public void findAccessibilityNodeInfosByViewText(String text, int accessibilityId, + int interactionId, IAccessibilityInteractionConnectionCallback callback) { if (mViewAncestor.get() != null) { getAccessibilityInteractionController() - .findAccessibilityNodeInfosByViewTextClientThread(text, interactionId, - callback); + .findAccessibilityNodeInfosByViewTextClientThread(text, accessibilityId, + interactionId, callback); } } } @@ -4414,13 +4451,15 @@ public final class ViewAncestor extends Handler implements ViewParent, } } - public void findAccessibilityNodeInfosByViewTextClientThread(String text, int interactionId, + public void findAccessibilityNodeInfosByViewTextClientThread(String text, + int accessibilityViewId, int interactionId, IAccessibilityInteractionConnectionCallback callback) { Message message = Message.obtain(); message.what = DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_TEXT; SomeArgs args = mPool.acquire(); args.arg1 = text; - args.argi1 = interactionId; + args.argi1 = accessibilityViewId; + args.argi2 = interactionId; args.arg2 = callback; message.obj = args; sendMessage(message); @@ -4429,18 +4468,28 @@ public final class ViewAncestor extends Handler implements ViewParent, public void findAccessibilityNodeInfosByViewTextUiThread(Message message) { SomeArgs args = (SomeArgs) message.obj; final String text = (String) args.arg1; - final int interactionId = args.argi1; + final int accessibilityViewId = args.argi1; + final int interactionId = args.argi2; final IAccessibilityInteractionConnectionCallback callback = (IAccessibilityInteractionConnectionCallback) args.arg2; mPool.release(args); List<AccessibilityNodeInfo> infos = null; try { - View root = ViewAncestor.this.mView; - ArrayList<View> foundViews = mAttachInfo.mFocusablesTempList; foundViews.clear(); + View root = null; + if (accessibilityViewId != View.NO_ID) { + root = findViewByAccessibilityId(accessibilityViewId); + } else { + root = ViewAncestor.this.mView; + } + + if (root == null) { + return; + } + root.findViewsWithText(foundViews, text); if (foundViews.isEmpty()) { return; @@ -4577,4 +4626,12 @@ public final class ViewAncestor extends Handler implements ViewParent, } } } + + private class SendWindowContentChanged implements Runnable { + public void run() { + if (mView != null) { + mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); + } + } + } } diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java index 06e4827..5ef7763 100644 --- a/core/java/android/view/accessibility/AccessibilityEvent.java +++ b/core/java/android/view/accessibility/AccessibilityEvent.java @@ -19,11 +19,10 @@ package android.view.accessibility; import android.accessibilityservice.IAccessibilityServiceConnection; import android.os.Parcel; import android.os.Parcelable; -import android.os.RemoteException; import android.text.TextUtils; -import android.view.View; import java.util.ArrayList; +import java.util.List; /** * This class represents accessibility events that are sent by the system when @@ -130,7 +129,7 @@ import java.util.ArrayList; * <p> * <b>TRANSITION TYPES</b> <br> * <p> - * <b>Window state changed</b> - represents the event of opening/closing a + * <b>Window state changed</b> - represents the event of opening a * {@link android.widget.PopupWindow}, {@link android.view.Menu}, * {@link android.app.Dialog}, etc. <br> * Type: {@link #TYPE_WINDOW_STATE_CHANGED} <br> @@ -140,6 +139,16 @@ import java.util.ArrayList; * {@link #getEventTime()}, * {@link #getText()} * <p> + * <b>Window content changed</b> - represents the event of change in the + * content of a window. This change can be adding/removing view, changing + * a view size, etc.<br> + * Type: {@link #TYPE_WINDOW_CONTENT_CHANGED} <br> + * Properties: + * {@link #getClassName()}, + * {@link #getPackageName()}, + * {@link #getEventTime()}, + * {@link #getText()} + * <p> * <b>NOTIFICATION TYPES</b> <br> * <p> * <b>Notification state changed</b> - represents the event showing/hiding @@ -244,6 +253,11 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par public static final int TYPE_TOUCH_EXPLORATION_GESTURE_END = 0x00000400; /** + * Represents the event of changing the content of a window. + */ + public static final int TYPE_WINDOW_CONTENT_CHANGED = 0x00000800; + + /** * Mask for {@link AccessibilityEvent} all types. * * @see #TYPE_VIEW_CLICKED @@ -264,15 +278,11 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par private boolean mIsInPool; private int mEventType; - private int mSourceAccessibilityViewId = View.NO_ID; - private int mSourceAccessibilityWindowId = View.NO_ID; private CharSequence mPackageName; private long mEventTime; private final ArrayList<AccessibilityRecord> mRecords = new ArrayList<AccessibilityRecord>(); - private IAccessibilityServiceConnection mConnection; - /* * Hide constructor from clients. */ @@ -288,10 +298,43 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par super.init(event); mEventType = event.mEventType; mEventTime = event.mEventTime; - mSourceAccessibilityWindowId = event.mSourceAccessibilityWindowId; - mSourceAccessibilityViewId = event.mSourceAccessibilityViewId; mPackageName = event.mPackageName; - mConnection = event.mConnection; + } + + /** + * Sets the connection for interacting with the AccessibilityManagerService. + * + * @param connection The connection. + * + * @hide + */ + @Override + public void setConnection(IAccessibilityServiceConnection connection) { + super.setConnection(connection); + List<AccessibilityRecord> records = mRecords; + final int recordCount = records.size(); + for (int i = 0; i < recordCount; i++) { + AccessibilityRecord record = records.get(i); + record.setConnection(connection); + } + } + + /** + * Sets if this instance is sealed. + * + * @param sealed Whether is sealed. + * + * @hide + */ + @Override + public void setSealed(boolean sealed) { + super.setSealed(sealed); + List<AccessibilityRecord> records = mRecords; + final int recordCount = records.size(); + for (int i = 0; i < recordCount; i++) { + AccessibilityRecord record = records.get(i); + record.setSealed(sealed); + } } /** @@ -335,81 +378,6 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par } /** - * Sets the event source. - * - * @param source The source. - * - * @throws IllegalStateException If called from an AccessibilityService. - */ - public void setSource(View source) { - enforceNotSealed(); - if (source != null) { - mSourceAccessibilityWindowId = source.getAccessibilityWindowId(); - mSourceAccessibilityViewId = source.getAccessibilityViewId(); - } else { - mSourceAccessibilityWindowId = View.NO_ID; - mSourceAccessibilityViewId = View.NO_ID; - } - } - - /** - * Gets the {@link AccessibilityNodeInfo} of the event source. - * <p> - * <strong> - * It is a client responsibility to recycle the received info by - * calling {@link AccessibilityNodeInfo#recycle()} to avoid creating - * of multiple instances. - * </strong> - * </p> - * @return The info. - */ - public AccessibilityNodeInfo getSource() { - enforceSealed(); - if (mSourceAccessibilityWindowId == View.NO_ID - || mSourceAccessibilityViewId == View.NO_ID) { - return null; - } - try { - return mConnection.findAccessibilityNodeInfoByAccessibilityId( - mSourceAccessibilityWindowId, mSourceAccessibilityViewId); - } catch (RemoteException e) { - return null; - } - } - - /** - * Gets the id of the window from which the event comes from. - * - * @return The window id. - */ - public int getAccessibilityWindowId() { - return mSourceAccessibilityWindowId; - } - - /** - * Sets the client token for the accessibility service that - * provided this node info. - * - * @param connection The connection. - * - * @hide - */ - public final void setConnection(IAccessibilityServiceConnection connection) { - mConnection = connection; - } - - /** - * Gets the accessibility window id of the source window. - * - * @return The id. - * - * @hide - */ - public int getSourceAccessibilityWindowId() { - return mSourceAccessibilityWindowId; - } - - /** * Sets the event type. * * @param eventType The event type. @@ -548,10 +516,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par @Override protected void clear() { super.clear(); - mConnection = null; mEventType = 0; - mSourceAccessibilityViewId = View.NO_ID; - mSourceAccessibilityWindowId = View.NO_ID; mPackageName = null; mEventTime = 0; while (!mRecords.isEmpty()) { @@ -572,8 +537,6 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par } setSealed(parcel.readInt() == 1); mEventType = parcel.readInt(); - mSourceAccessibilityWindowId = parcel.readInt(); - mSourceAccessibilityViewId = parcel.readInt(); mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); mEventTime = parcel.readLong(); readAccessibilityRecordFromParcel(this, parcel); @@ -583,6 +546,8 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par for (int i = 0; i < recordCount; i++) { AccessibilityRecord record = AccessibilityRecord.obtain(); readAccessibilityRecordFromParcel(record, parcel); + // Do this to write the connection only once. + record.setConnection(mConnection); mRecords.add(record); } } @@ -606,6 +571,9 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par record.mBeforeText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); record.mParcelableData = parcel.readParcelable(null); parcel.readList(record.mText, null); + record.mSourceWindowId = parcel.readInt(); + record.mSourceViewId = parcel.readInt(); + record.mSealed = (parcel.readInt() == 1); } /** @@ -620,8 +588,6 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par } parcel.writeInt(isSealed() ? 1 : 0); parcel.writeInt(mEventType); - parcel.writeInt(mSourceAccessibilityWindowId); - parcel.writeInt(mSourceAccessibilityViewId); TextUtils.writeToParcel(mPackageName, parcel, 0); parcel.writeLong(mEventTime); writeAccessibilityRecordToParcel(this, parcel, flags); @@ -654,6 +620,9 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par TextUtils.writeToParcel(record.mBeforeText, parcel, flags); parcel.writeParcelable(record.mParcelableData, flags); parcel.writeList(record.mText); + parcel.writeInt(record.mSourceWindowId); + parcel.writeInt(record.mSourceViewId); + parcel.writeInt(record.mSealed ? 1 : 0); } /** @@ -672,8 +641,8 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par builder.append(super.toString()); if (DEBUG) { builder.append("\n"); - builder.append("; sourceAccessibilityWindowId: ").append(mSourceAccessibilityWindowId); - builder.append("; sourceAccessibilityViewId: ").append(mSourceAccessibilityViewId); + builder.append("; sourceWindowId: ").append(mSourceWindowId); + builder.append("; sourceViewId: ").append(mSourceViewId); for (int i = 0; i < mRecords.size(); i++) { AccessibilityRecord record = mRecords.get(i); builder.append(" Record "); @@ -733,6 +702,8 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par return "TYPE_TOUCH_EXPLORATION_GESTURE_START"; case TYPE_TOUCH_EXPLORATION_GESTURE_END: return "TYPE_TOUCH_EXPLORATION_GESTURE_END"; + case TYPE_WINDOW_CONTENT_CHANGED: + return "TYPE_WINDOW_CONTENT_CHANGED"; default: return null; } diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 5fa65b4..18ef38a 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -26,6 +26,9 @@ import android.util.SparseArray; import android.util.SparseIntArray; import android.view.View; +import java.util.Collections; +import java.util.List; + /** * This class represents a node of the screen content. From the point of * view of an accessibility service the screen content is presented as tree @@ -97,7 +100,8 @@ public class AccessibilityNodeInfo implements Parcelable { private int mAccessibilityWindowId = View.NO_ID; private int mParentAccessibilityViewId = View.NO_ID; private int mBooleanProperties; - private final Rect mBounds = new Rect(); + private final Rect mBoundsInParent = new Rect(); + private final Rect mBoundsInScreen = new Rect(); private CharSequence mPackageName; private CharSequence mClassName; @@ -132,7 +136,7 @@ public class AccessibilityNodeInfo implements Parcelable { * * @return The window id. */ - public int getAccessibilityWindowId() { + public int getWindowId() { return mAccessibilityWindowId; } @@ -163,12 +167,16 @@ public class AccessibilityNodeInfo implements Parcelable { public AccessibilityNodeInfo getChild(int index) { enforceSealed(); final int childAccessibilityViewId = mChildAccessibilityIds.get(index); + if (!canPerformRequestOverConnection(childAccessibilityViewId)) { + return null; + } try { return mConnection.findAccessibilityNodeInfoByAccessibilityId(mAccessibilityWindowId, childAccessibilityViewId); - } catch (RemoteException e) { - return null; + } catch (RemoteException re) { + /* ignore*/ } + return null; } /** @@ -230,12 +238,37 @@ public class AccessibilityNodeInfo implements Parcelable { */ public boolean performAction(int action) { enforceSealed(); + if (!canPerformRequestOverConnection(mAccessibilityViewId)) { + return false; + } try { return mConnection.performAccessibilityAction(mAccessibilityWindowId, mAccessibilityViewId, action); } catch (RemoteException e) { - return false; + /* ignore */ } + return false; + } + + /** + * Finds {@link AccessibilityNodeInfo}s by text. The match is case + * insensitive containment. + * + * @param text The searched text. + * @return A list of node info. + */ + public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String text) { + enforceSealed(); + if (!canPerformRequestOverConnection(mAccessibilityViewId)) { + return null; + } + try { + return mConnection.findAccessibilityNodeInfosByViewText(text, mAccessibilityWindowId, + mAccessibilityViewId); + } catch (RemoteException e) { + /* ignore */ + } + return Collections.emptyList(); } /** @@ -251,12 +284,16 @@ public class AccessibilityNodeInfo implements Parcelable { */ public AccessibilityNodeInfo getParent() { enforceSealed(); + if (!canPerformRequestOverConnection(mAccessibilityViewId)) { + return null; + } try { - return mConnection.findAccessibilityNodeInfoByAccessibilityId(mAccessibilityWindowId, - mParentAccessibilityViewId); + return mConnection.findAccessibilityNodeInfoByAccessibilityId( + mAccessibilityWindowId, mParentAccessibilityViewId); } catch (RemoteException e) { - return null; + /* ignore */ } + return null; } /** @@ -279,8 +316,9 @@ public class AccessibilityNodeInfo implements Parcelable { * * @param outBounds The output node bounds. */ - public void getBounds(Rect outBounds) { - outBounds.set(mBounds.left, mBounds.top, mBounds.right, mBounds.bottom); + public void getBoundsInParent(Rect outBounds) { + outBounds.set(mBoundsInParent.left, mBoundsInParent.top, + mBoundsInParent.right, mBoundsInParent.bottom); } /** @@ -293,9 +331,34 @@ public class AccessibilityNodeInfo implements Parcelable { * * @throws IllegalStateException If called from an AccessibilityService. */ - public void setBounds(Rect bounds) { + public void setBoundsInParent(Rect bounds) { enforceNotSealed(); - mBounds.set(bounds.left, bounds.top, bounds.right, bounds.bottom); + mBoundsInParent.set(bounds.left, bounds.top, bounds.right, bounds.bottom); + } + + /** + * Gets the node bounds in screen coordinates. + * + * @param outBounds The output node bounds. + */ + public void getBoundsInScreen(Rect outBounds) { + outBounds.set(mBoundsInScreen.left, mBoundsInScreen.top, + mBoundsInScreen.right, mBoundsInScreen.bottom); + } + + /** + * Sets the node bounds in screen coordinates. + * <p> + * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * @param bounds The node bounds. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setBoundsInScreen(Rect bounds) { + enforceNotSealed(); + mBoundsInScreen.set(bounds.left, bounds.top, bounds.right, bounds.bottom); } /** @@ -636,6 +699,7 @@ public class AccessibilityNodeInfo implements Parcelable { * @hide */ public final void setConnection(IAccessibilityServiceConnection connection) { + enforceNotSealed(); mConnection = connection; } @@ -777,10 +841,15 @@ public class AccessibilityNodeInfo implements Parcelable { parcel.writeInt(childIds.valueAt(i)); } - parcel.writeInt(mBounds.top); - parcel.writeInt(mBounds.bottom); - parcel.writeInt(mBounds.left); - parcel.writeInt(mBounds.right); + parcel.writeInt(mBoundsInParent.top); + parcel.writeInt(mBoundsInParent.bottom); + parcel.writeInt(mBoundsInParent.left); + parcel.writeInt(mBoundsInParent.right); + + parcel.writeInt(mBoundsInScreen.top); + parcel.writeInt(mBoundsInScreen.bottom); + parcel.writeInt(mBoundsInScreen.left); + parcel.writeInt(mBoundsInScreen.right); parcel.writeInt(mActions); @@ -818,10 +887,15 @@ public class AccessibilityNodeInfo implements Parcelable { childIds.put(i, childId); } - mBounds.top = parcel.readInt(); - mBounds.bottom = parcel.readInt(); - mBounds.left = parcel.readInt(); - mBounds.right = parcel.readInt(); + mBoundsInParent.top = parcel.readInt(); + mBoundsInParent.bottom = parcel.readInt(); + mBoundsInParent.left = parcel.readInt(); + mBoundsInParent.right = parcel.readInt(); + + mBoundsInScreen.top = parcel.readInt(); + mBoundsInScreen.bottom = parcel.readInt(); + mBoundsInScreen.left = parcel.readInt(); + mBoundsInScreen.right = parcel.readInt(); mActions = parcel.readInt(); @@ -842,7 +916,8 @@ public class AccessibilityNodeInfo implements Parcelable { mAccessibilityViewId = View.NO_ID; mParentAccessibilityViewId = View.NO_ID; mChildAccessibilityIds.clear(); - mBounds.set(0, 0, 0, 0); + mBoundsInParent.set(0, 0, 0, 0); + mBoundsInScreen.set(0, 0, 0, 0); mBooleanProperties = 0; mPackageName = null; mClassName = null; @@ -869,6 +944,12 @@ public class AccessibilityNodeInfo implements Parcelable { return actionSymbolicNames.get(action); } + private boolean canPerformRequestOverConnection(int accessibilityViewId) { + return (mAccessibilityWindowId != View.NO_ID + && accessibilityViewId != View.NO_ID + && mConnection != null); + } + @Override public boolean equals(Object object) { if (this == object) { @@ -918,7 +999,8 @@ public class AccessibilityNodeInfo implements Parcelable { builder.append("]"); } - builder.append("; bounds: " + mBounds); + builder.append("; boundsInParent: " + mBoundsInParent); + builder.append("; boundsInScreen: " + mBoundsInScreen); builder.append("; packageName: ").append(mPackageName); builder.append("; className: ").append(mClassName); diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java index 4bf03a7..9c495e2 100644 --- a/core/java/android/view/accessibility/AccessibilityRecord.java +++ b/core/java/android/view/accessibility/AccessibilityRecord.java @@ -16,7 +16,10 @@ package android.view.accessibility; +import android.accessibilityservice.IAccessibilityServiceConnection; import android.os.Parcelable; +import android.os.RemoteException; +import android.view.View; import java.util.ArrayList; import java.util.List; @@ -45,26 +48,29 @@ public class AccessibilityRecord { private static int sPoolSize; private AccessibilityRecord mNext; private boolean mIsInPool; - private boolean mSealed; - protected int mBooleanProperties; - protected int mCurrentItemIndex; - protected int mItemCount; - protected int mFromIndex; - protected int mAddedCount; - protected int mRemovedCount; + boolean mSealed; + int mBooleanProperties; + int mCurrentItemIndex; + int mItemCount; + int mFromIndex; + int mAddedCount; + int mRemovedCount; + int mSourceViewId = View.NO_ID; + int mSourceWindowId = View.NO_ID; - protected CharSequence mClassName; - protected CharSequence mContentDescription; - protected CharSequence mBeforeText; - protected Parcelable mParcelableData; + CharSequence mClassName; + CharSequence mContentDescription; + CharSequence mBeforeText; + Parcelable mParcelableData; - protected final List<CharSequence> mText = new ArrayList<CharSequence>(); + final List<CharSequence> mText = new ArrayList<CharSequence>(); + IAccessibilityServiceConnection mConnection; /* * Hide constructor. */ - protected AccessibilityRecord() { + AccessibilityRecord() { } @@ -74,7 +80,7 @@ public class AccessibilityRecord { * @param record The to initialize from. */ void init(AccessibilityRecord record) { - mSealed = record.isSealed(); + mSealed = record.mSealed; mBooleanProperties = record.mBooleanProperties; mCurrentItemIndex = record.mCurrentItemIndex; mItemCount = record.mItemCount; @@ -86,6 +92,73 @@ public class AccessibilityRecord { mBeforeText = record.mBeforeText; mParcelableData = record.mParcelableData; mText.addAll(record.mText); + mSourceWindowId = record.mSourceWindowId; + mSourceViewId = record.mSourceViewId; + mConnection = record.mConnection; + } + + /** + * Sets the event source. + * + * @param source The source. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setSource(View source) { + enforceNotSealed(); + if (source != null) { + mSourceWindowId = source.getAccessibilityWindowId(); + mSourceViewId = source.getAccessibilityViewId(); + } else { + mSourceWindowId = View.NO_ID; + mSourceViewId = View.NO_ID; + } + } + + /** + * Gets the {@link AccessibilityNodeInfo} of the event source. + * <p> + * <strong> + * It is a client responsibility to recycle the received info by + * calling {@link AccessibilityNodeInfo#recycle()} to avoid creating + * of multiple instances. + * </strong> + * </p> + * @return The info. + */ + public AccessibilityNodeInfo getSource() { + enforceSealed(); + if (mSourceWindowId == View.NO_ID || mSourceViewId == View.NO_ID || mConnection == null) { + return null; + } + try { + return mConnection.findAccessibilityNodeInfoByAccessibilityId(mSourceWindowId, + mSourceViewId); + } catch (RemoteException e) { + /* ignore */ + } + return null; + } + + /** + * Sets the connection for interacting with the AccessibilityManagerService. + * + * @param connection The connection. + * + * @hide + */ + public void setConnection(IAccessibilityServiceConnection connection) { + enforceNotSealed(); + mConnection = connection; + } + + /** + * Gets the id of the window from which the event comes from. + * + * @return The window id. + */ + public int getWindowId() { + return mSourceWindowId; } /** @@ -386,10 +459,8 @@ public class AccessibilityRecord { * Gets if this instance is sealed. * * @return Whether is sealed. - * - * @hide */ - public boolean isSealed() { + boolean isSealed() { return mSealed; } @@ -397,10 +468,8 @@ public class AccessibilityRecord { * Enforces that this instance is sealed. * * @throws IllegalStateException If this instance is not sealed. - * - * @hide */ - protected void enforceSealed() { + void enforceSealed() { if (!isSealed()) { throw new IllegalStateException("Cannot perform this " + "action on a not sealed instance."); @@ -411,10 +480,8 @@ public class AccessibilityRecord { * Enforces that this instance is not sealed. * * @throws IllegalStateException If this instance is sealed. - * - * @hide */ - protected void enforceNotSealed() { + void enforceNotSealed() { if (isSealed()) { throw new IllegalStateException("Cannot perform this " + "action on an sealed instance."); @@ -502,10 +569,8 @@ public class AccessibilityRecord { /** * Clears the state of this instance. - * - * @hide */ - protected void clear() { + void clear() { mSealed = false; mBooleanProperties = 0; mCurrentItemIndex = INVALID_POSITION; @@ -518,6 +583,8 @@ public class AccessibilityRecord { mBeforeText = null; mParcelableData = null; mText.clear(); + mSourceViewId = View.NO_ID; + mSourceWindowId = View.NO_ID; } @Override diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl index 77dcd07..d35186b 100644 --- a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl +++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl @@ -33,8 +33,8 @@ oneway interface IAccessibilityInteractionConnection { void findAccessibilityNodeInfoByViewId(int id, int interactionId, IAccessibilityInteractionConnectionCallback callback); - void findAccessibilityNodeInfosByViewText(String text, int interactionId, - IAccessibilityInteractionConnectionCallback callback); + void findAccessibilityNodeInfosByViewText(String text, int accessibilityViewId, + int interactionId, IAccessibilityInteractionConnectionCallback callback); void performAccessibilityAction(int accessibilityId, int action, int interactionId, IAccessibilityInteractionConnectionCallback callback); diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 82dd5db..3fe8149 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -55,7 +55,6 @@ import android.view.ViewConfiguration; import android.view.ViewDebug; import android.view.ViewGroup; import android.view.ViewTreeObserver; -import android.view.accessibility.AccessibilityEvent; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java index c4d05e9..755d4e0 100644 --- a/core/java/android/widget/AdapterView.java +++ b/core/java/android/widget/AdapterView.java @@ -904,8 +904,10 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) { // Add a record for ourselves as well. AccessibilityEvent record = AccessibilityEvent.obtain(); + record.setSource(this); // Set the class since it is not populated in #dispatchPopulateAccessibilityEvent record.setClassName(getClass().getName()); + child.onInitializeAccessibilityEvent(record); child.dispatchPopulateAccessibilityEvent(record); event.appendRecord(record); return true; diff --git a/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java b/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java index 3260616..99d534c 100644 --- a/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java +++ b/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java @@ -29,6 +29,7 @@ import android.os.SystemClock; import android.provider.Settings; import android.test.ActivityInstrumentationTestCase2; import android.test.suitebuilder.annotation.LargeTest; +import android.util.Log; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; @@ -49,6 +50,9 @@ import java.util.Queue; */ public class InterrogationActivityTest extends ActivityInstrumentationTestCase2<InterrogationActivity> { + private static final boolean DEBUG = true; + + private static String LOG_TAG = "InterrogationActivityTest"; // Timeout before give up wait for the system to process an accessibility setting change. private static final int TIMEOUT_PROPAGATE_ACCESSIBLITY_SETTING = 2000; @@ -62,7 +66,7 @@ public class InterrogationActivityTest private static IAccessibilityServiceConnection sConnection; // The last received accessibility event - private static volatile AccessibilityEvent sLastAccessibilityEvent; + private static volatile AccessibilityEvent sLastFocusAccessibilityEvent; public InterrogationActivityTest() { super(InterrogationActivity.class); @@ -72,18 +76,19 @@ public class InterrogationActivityTest @LargeTest public void testFindAccessibilityNodeInfoByViewId() throws Exception { beforeClassIfNeeded(); + final long startTimeMillis = SystemClock.uptimeMillis(); try { // bring up the activity getActivity(); - AccessibilityNodeInfo button = getConnection().findAccessibilityNodeInfoByViewId( - R.id.button5); + AccessibilityNodeInfo button = + getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); assertNotNull(button); assertEquals(0, button.getChildCount()); // bounds Rect bounds = new Rect(); - button.getBounds(bounds); + button.getBoundsInParent(bounds); assertEquals(0, bounds.left); assertEquals(0, bounds.top); assertEquals(73, bounds.right); @@ -111,28 +116,40 @@ public class InterrogationActivityTest button.getActions()); } finally { afterClassIfNeeded(); + if (DEBUG) { + final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; + Log.i(LOG_TAG, "testFindAccessibilityNodeInfoByViewId: " + + elapsedTimeMillis + "ms"); + } } } @LargeTest public void testFindAccessibilityNodeInfoByViewText() throws Exception { beforeClassIfNeeded(); + final long startTimeMillis = SystemClock.uptimeMillis(); try { // bring up the activity getActivity(); // find a view by text List<AccessibilityNodeInfo> buttons = - getConnection().findAccessibilityNodeInfosByViewText("butto"); + getConnection().findAccessibilityNodeInfosByViewTextInActiveWindow("butto"); assertEquals(9, buttons.size()); } finally { afterClassIfNeeded(); + if (DEBUG) { + final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; + Log.i(LOG_TAG, "testFindAccessibilityNodeInfoByViewText: " + + elapsedTimeMillis + "ms"); + } } } @LargeTest public void testTraverseAllViews() throws Exception { beforeClassIfNeeded(); + final long startTimeMillis = SystemClock.uptimeMillis(); try { // bring up the activity getActivity(); @@ -153,8 +170,8 @@ public class InterrogationActivityTest classNameAndTextList.add("android.widget.ButtonButton8"); classNameAndTextList.add("android.widget.ButtonButton9"); - AccessibilityNodeInfo root = getConnection().findAccessibilityNodeInfoByViewId( - R.id.root); + AccessibilityNodeInfo root = + getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.root); assertNotNull("We must find the existing root.", root); Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>(); @@ -181,125 +198,152 @@ public class InterrogationActivityTest } } finally { afterClassIfNeeded(); + if (DEBUG) { + final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; + Log.i(LOG_TAG, "testTraverseAllViews: " + elapsedTimeMillis + "ms"); + } } } @LargeTest public void testPerformAccessibilityActionFocus() throws Exception { beforeClassIfNeeded(); + final long startTimeMillis = SystemClock.uptimeMillis(); try { // bring up the activity getActivity(); // find a view and make sure it is not focused - AccessibilityNodeInfo button = getConnection().findAccessibilityNodeInfoByViewId( - R.id.button5); + AccessibilityNodeInfo button = + getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); assertFalse(button.isFocused()); // focus the view assertTrue(button.performAction(ACTION_FOCUS)); // find the view again and make sure it is focused - button = getConnection().findAccessibilityNodeInfoByViewId(R.id.button5); + button = getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); assertTrue(button.isFocused()); } finally { afterClassIfNeeded(); + if (DEBUG) { + final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; + Log.i(LOG_TAG, "testPerformAccessibilityActionFocus: " + elapsedTimeMillis + "ms"); + } } } @LargeTest public void testPerformAccessibilityActionClearFocus() throws Exception { beforeClassIfNeeded(); + final long startTimeMillis = SystemClock.uptimeMillis(); try { // bring up the activity getActivity(); // find a view and make sure it is not focused - AccessibilityNodeInfo button = getConnection().findAccessibilityNodeInfoByViewId( - R.id.button5); + AccessibilityNodeInfo button = + getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); assertFalse(button.isFocused()); // focus the view assertTrue(button.performAction(ACTION_FOCUS)); // find the view again and make sure it is focused - button = getConnection().findAccessibilityNodeInfoByViewId(R.id.button5); + button = getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); assertTrue(button.isFocused()); // unfocus the view assertTrue(button.performAction(ACTION_CLEAR_FOCUS)); // find the view again and make sure it is not focused - button = getConnection().findAccessibilityNodeInfoByViewId(R.id.button5); + button = getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); assertFalse(button.isFocused()); } finally { afterClassIfNeeded(); + if (DEBUG) { + final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; + Log.i(LOG_TAG, "testPerformAccessibilityActionClearFocus: " + + elapsedTimeMillis + "ms"); + } } } @LargeTest public void testPerformAccessibilityActionSelect() throws Exception { beforeClassIfNeeded(); + final long startTimeMillis = SystemClock.uptimeMillis(); try { // bring up the activity getActivity(); // find a view and make sure it is not selected - AccessibilityNodeInfo button = getConnection().findAccessibilityNodeInfoByViewId( - R.id.button5); + AccessibilityNodeInfo button = + getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); assertFalse(button.isSelected()); // select the view assertTrue(button.performAction(ACTION_SELECT)); // find the view again and make sure it is selected - button = getConnection().findAccessibilityNodeInfoByViewId(R.id.button5); + button = getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); assertTrue(button.isSelected()); } finally { afterClassIfNeeded(); + if (DEBUG) { + final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; + Log.i(LOG_TAG, "testPerformAccessibilityActionSelect: " + elapsedTimeMillis + "ms"); + } } } @LargeTest public void testPerformAccessibilityActionClearSelection() throws Exception { beforeClassIfNeeded(); + final long startTimeMillis = SystemClock.uptimeMillis(); try { // bring up the activity getActivity(); // find a view and make sure it is not selected - AccessibilityNodeInfo button = getConnection().findAccessibilityNodeInfoByViewId( - R.id.button5); + AccessibilityNodeInfo button = + getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); assertFalse(button.isSelected()); // select the view assertTrue(button.performAction(ACTION_SELECT)); // find the view again and make sure it is selected - button = getConnection().findAccessibilityNodeInfoByViewId(R.id.button5); + button = getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); assertTrue(button.isSelected()); // unselect the view assertTrue(button.performAction(ACTION_CLEAR_SELECTION)); // find the view again and make sure it is not selected - button = getConnection().findAccessibilityNodeInfoByViewId(R.id.button5); + button = getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); assertFalse(button.isSelected()); } finally { afterClassIfNeeded(); + if (DEBUG) { + final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; + Log.i(LOG_TAG, "testPerformAccessibilityActionClearSelection: " + + elapsedTimeMillis + "ms"); + } } } @LargeTest public void testAccessibilityEventGetSource() throws Exception { beforeClassIfNeeded(); + final long startTimeMillis = SystemClock.uptimeMillis(); try { // bring up the activity getActivity(); // find a view and make sure it is not focused - AccessibilityNodeInfo button = getConnection().findAccessibilityNodeInfoByViewId( - R.id.button5); + AccessibilityNodeInfo button = + getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); assertFalse(button.isSelected()); // focus the view @@ -314,14 +358,14 @@ public class InterrogationActivityTest } // check that last event source - AccessibilityNodeInfo source = sLastAccessibilityEvent.getSource(); + AccessibilityNodeInfo source = sLastFocusAccessibilityEvent.getSource(); assertNotNull(source); // bounds Rect buttonBounds = new Rect(); - button.getBounds(buttonBounds); + button.getBoundsInParent(buttonBounds); Rect sourceBounds = new Rect(); - source.getBounds(sourceBounds); + source.getBoundsInParent(sourceBounds); assertEquals(buttonBounds.left, sourceBounds.left); assertEquals(buttonBounds.right, sourceBounds.right); @@ -346,6 +390,42 @@ public class InterrogationActivityTest assertSame(button.isChecked(), source.isChecked()); } finally { afterClassIfNeeded(); + if (DEBUG) { + final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; + Log.i(LOG_TAG, "testAccessibilityEventGetSource: " + elapsedTimeMillis + "ms"); + } + } + } + + @LargeTest + public void testObjectContract() throws Exception { + beforeClassIfNeeded(); + final long startTimeMillis = SystemClock.uptimeMillis(); + try { + // bring up the activity + getActivity(); + + // find a view and make sure it is not focused + AccessibilityNodeInfo button = + getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); + AccessibilityNodeInfo parent = button.getParent(); + final int childCount = parent.getChildCount(); + for (int i = 0; i < childCount; i++) { + AccessibilityNodeInfo child = parent.getChild(i); + assertNotNull(child); + if (child.equals(button)) { + assertEquals("Equal objects must have same hasCode.", button.hashCode(), + child.hashCode()); + return; + } + } + fail("Parent's children do not have the info whose parent is the parent."); + } finally { + afterClassIfNeeded(); + if (DEBUG) { + final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; + Log.i(LOG_TAG, "testObjectContract: " + elapsedTimeMillis + "ms"); + } } } @@ -442,7 +522,9 @@ public class InterrogationActivityTest public void onInterrupt() {} public void onAccessibilityEvent(AccessibilityEvent event) { - sLastAccessibilityEvent = AccessibilityEvent.obtain(event); + if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED) { + sLastFocusAccessibilityEvent = AccessibilityEvent.obtain(event); + } synchronized (sConnection) { sConnection.notifyAll(); } |