diff options
| author | Svetoslav Ganov <svetoslavganov@google.com> | 2011-06-10 20:51:30 -0700 |
|---|---|---|
| committer | Svetoslav Ganov <svetoslavganov@google.com> | 2011-06-10 21:10:46 -0700 |
| commit | eeee4d2c01d3c4ed99e4891dbc75c7de69a803fa (patch) | |
| tree | 316a5bbeb6b5d98029a0996772dca1857fcb9059 /core/java/android | |
| parent | eaf7ce6067707fcebd58067135376af51858d2e5 (diff) | |
Final polish of the interrogation feature.
1. Added a new event type for notifying client accessibilitiy
services for changes in the layout. The event is fired at
most once for a given time frame and is delivered to clients
only if it originates from the window that can be interrogated.
2. Exposed the findByText functionality in AccessibilityNodeInfo.
This is very useful for an accessibility service since it allows
searching for something the user knows is on the screen thus
avoiding touch exploring the content. Touch exploring is
excellent for learning the apps but knowing them search is
much faster.
3. Fixed a bug causing an accessibiliby service not to receive
the event source in case of more than one service is registered
and one of them does not have paermission to interrogate the window.
The same event was dispatched to multiple services but if one
of them does not have interrogation permission the event is
modified to remove the source causing subsequent serivices not
to get the later.
4. Moved the getSource setSource methods to AccessibilityRecord
instead in AccessibilityEvent.
5. Hiden some protected members in AccessibilityRecod which should
not be made public since getters exist.
6. Added the View absolute coordinates in the screen to AccessibilityNodeInfo.
This is needed for fast computation of relative positions of
views from accessibility - common use case for the later.
7. Fixed a couple of marshalling bugs.
8. Added a test for the object contract of AccessibilityNodeInfo.
Change-Id: Id9dc50c33aff441e4c93d25ea316c9bbc4bd7a35
Diffstat (limited to 'core/java/android')
10 files changed, 371 insertions, 162 deletions
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 8bb305d4d55e..164acbcdf71f 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 19f0bf033a78..8b4e7aee7398 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 441cdc14db8a..ee187222e622 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 17d745485130..914973ed41b1 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 06e4827ae371..5ef7763bff73 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 5fa65b450e42..18ef38af7476 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 4bf03a7bed8e..9c495e2126cb 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 77dcd0747ed8..d35186b823ce 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 82dd5dbba4b2..3fe81498aad9 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 c4d05e91662b..755d4e09c039 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; |
