summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSvetoslav Ganov <svetoslavganov@google.com>2011-06-10 20:51:30 -0700
committerSvetoslav Ganov <svetoslavganov@google.com>2011-06-10 21:10:46 -0700
commiteeee4d2c01d3c4ed99e4891dbc75c7de69a803fa (patch)
tree316a5bbeb6b5d98029a0996772dca1857fcb9059
parenteaf7ce6067707fcebd58067135376af51858d2e5 (diff)
downloadframeworks_base-eeee4d2c01d3c4ed99e4891dbc75c7de69a803fa.zip
frameworks_base-eeee4d2c01d3c4ed99e4891dbc75c7de69a803fa.tar.gz
frameworks_base-eeee4d2c01d3c4ed99e4891dbc75c7de69a803fa.tar.bz2
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
-rw-r--r--api/14.txt12
-rw-r--r--api/current.txt28
-rw-r--r--core/java/android/accessibilityservice/AccessibilityService.java4
-rw-r--r--core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl33
-rw-r--r--core/java/android/view/View.java10
-rw-r--r--core/java/android/view/ViewAncestor.java75
-rw-r--r--core/java/android/view/accessibility/AccessibilityEvent.java159
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java126
-rw-r--r--core/java/android/view/accessibility/AccessibilityRecord.java119
-rw-r--r--core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl4
-rw-r--r--core/java/android/widget/AbsListView.java1
-rw-r--r--core/java/android/widget/AdapterView.java2
-rw-r--r--core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java136
-rw-r--r--services/java/com/android/server/accessibility/AccessibilityManagerService.java91
14 files changed, 532 insertions, 268 deletions
diff --git a/api/14.txt b/api/14.txt
index 5c4732e..050540b 100644
--- a/api/14.txt
+++ b/api/14.txt
@@ -22239,7 +22239,6 @@ package android.view.accessibility {
}
public class AccessibilityRecord {
- ctor protected AccessibilityRecord();
method public int getAddedCount();
method public java.lang.CharSequence getBeforeText();
method public java.lang.CharSequence getClassName();
@@ -22269,17 +22268,6 @@ package android.view.accessibility {
method public void setParcelableData(android.os.Parcelable);
method public void setPassword(boolean);
method public void setRemovedCount(int);
- field protected int mAddedCount;
- field protected java.lang.CharSequence mBeforeText;
- field protected int mBooleanProperties;
- field protected java.lang.CharSequence mClassName;
- field protected java.lang.CharSequence mContentDescription;
- field protected int mCurrentItemIndex;
- field protected int mFromIndex;
- field protected int mItemCount;
- field protected android.os.Parcelable mParcelableData;
- field protected int mRemovedCount;
- field protected final java.util.List mText;
}
}
diff --git a/api/current.txt b/api/current.txt
index f06df4b..82706f4 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -22719,13 +22719,11 @@ package android.view.accessibility {
method public void appendRecord(android.view.accessibility.AccessibilityRecord);
method public int describeContents();
method public static java.lang.String eventTypeToString(int);
- method public int getAccessibilityWindowId();
method public long getEventTime();
method public int getEventType();
method public java.lang.CharSequence getPackageName();
method public android.view.accessibility.AccessibilityRecord getRecord(int);
method public int getRecordCount();
- method public android.view.accessibility.AccessibilityNodeInfo getSource();
method public void initFromParcel(android.os.Parcel);
method public static android.view.accessibility.AccessibilityEvent obtain(int);
method public static android.view.accessibility.AccessibilityEvent obtain(android.view.accessibility.AccessibilityEvent);
@@ -22733,7 +22731,6 @@ package android.view.accessibility {
method public void setEventTime(long);
method public void setEventType(int);
method public void setPackageName(java.lang.CharSequence);
- method public void setSource(android.view.View);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator CREATOR;
field public static final int INVALID_POSITION = -1; // 0xffffffff
@@ -22749,6 +22746,7 @@ package android.view.accessibility {
field public static final int TYPE_VIEW_LONG_CLICKED = 2; // 0x2
field public static final int TYPE_VIEW_SELECTED = 4; // 0x4
field public static final int TYPE_VIEW_TEXT_CHANGED = 16; // 0x10
+ field public static final int TYPE_WINDOW_CONTENT_CHANGED = 2048; // 0x800
field public static final int TYPE_WINDOW_STATE_CHANGED = 32; // 0x20
}
@@ -22776,9 +22774,10 @@ package android.view.accessibility {
method public void addAction(int);
method public void addChild(android.view.View);
method public int describeContents();
- method public int getAccessibilityWindowId();
+ method public java.util.List<android.view.accessibility.AccessibilityNodeInfo> findAccessibilityNodeInfosByText(java.lang.String);
method public int getActions();
- method public void getBounds(android.graphics.Rect);
+ method public void getBoundsInParent(android.graphics.Rect);
+ method public void getBoundsInScreen(android.graphics.Rect);
method public android.view.accessibility.AccessibilityNodeInfo getChild(int);
method public int getChildCount();
method public java.lang.CharSequence getClassName();
@@ -22786,6 +22785,7 @@ package android.view.accessibility {
method public java.lang.CharSequence getPackageName();
method public android.view.accessibility.AccessibilityNodeInfo getParent();
method public java.lang.CharSequence getText();
+ method public int getWindowId();
method public boolean isCheckable();
method public boolean isChecked();
method public boolean isClickable();
@@ -22799,7 +22799,8 @@ package android.view.accessibility {
method public static android.view.accessibility.AccessibilityNodeInfo obtain();
method public boolean performAction(int);
method public void recycle();
- method public void setBounds(android.graphics.Rect);
+ method public void setBoundsInParent(android.graphics.Rect);
+ method public void setBoundsInScreen(android.graphics.Rect);
method public void setCheckable(boolean);
method public void setChecked(boolean);
method public void setClassName(java.lang.CharSequence);
@@ -22824,7 +22825,6 @@ package android.view.accessibility {
}
public class AccessibilityRecord {
- ctor protected AccessibilityRecord();
method public int getAddedCount();
method public java.lang.CharSequence getBeforeText();
method public java.lang.CharSequence getClassName();
@@ -22834,7 +22834,9 @@ package android.view.accessibility {
method public int getItemCount();
method public android.os.Parcelable getParcelableData();
method public int getRemovedCount();
+ method public android.view.accessibility.AccessibilityNodeInfo getSource();
method public java.util.List<java.lang.CharSequence> getText();
+ method public int getWindowId();
method public boolean isChecked();
method public boolean isEnabled();
method public boolean isFullScreen();
@@ -22855,17 +22857,7 @@ package android.view.accessibility {
method public void setParcelableData(android.os.Parcelable);
method public void setPassword(boolean);
method public void setRemovedCount(int);
- field protected int mAddedCount;
- field protected java.lang.CharSequence mBeforeText;
- field protected int mBooleanProperties;
- field protected java.lang.CharSequence mClassName;
- field protected java.lang.CharSequence mContentDescription;
- field protected int mCurrentItemIndex;
- field protected int mFromIndex;
- field protected int mItemCount;
- field protected android.os.Parcelable mParcelableData;
- field protected int mRemovedCount;
- field protected final java.util.List mText;
+ method public void setSource(android.view.View);
}
}
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();
}
diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
index 86671d6..ec59da6 100644
--- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -46,6 +46,7 @@ import android.text.TextUtils.SimpleStringSplitter;
import android.util.Slog;
import android.util.SparseArray;
import android.view.IWindow;
+import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.IAccessibilityInteractionConnection;
@@ -149,9 +150,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
synchronized (mLock) {
notifyEventListenerLocked(service, eventType);
- AccessibilityEvent oldEvent = service.mPendingEvents.get(eventType);
- service.mPendingEvents.remove(eventType);
- tryRecycleLocked(oldEvent);
}
}
};
@@ -319,17 +317,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
public boolean sendAccessibilityEvent(AccessibilityEvent event) {
synchronized (mLock) {
- mSecurityPolicy.updateRetrievalAllowingWindowAndEventSourceLocked(event);
- notifyAccessibilityServicesDelayedLocked(event, false);
- notifyAccessibilityServicesDelayedLocked(event, true);
- }
- // event not scheduled for dispatch => recycle
- if (mHandledFeedbackTypes == 0) {
- event.recycle();
- } else {
- mHandledFeedbackTypes = 0;
+ if (mSecurityPolicy.canDispatchAccessibilityEvent(event)) {
+ mSecurityPolicy.updateRetrievalAllowingWindowAndEventSourceLocked(event);
+ notifyAccessibilityServicesDelayedLocked(event, false);
+ notifyAccessibilityServicesDelayedLocked(event, true);
+ }
}
-
+ event.recycle();
+ mHandledFeedbackTypes = 0;
return (OWN_PROCESS_ID != Binder.getCallingPid());
}
@@ -517,46 +512,27 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private void notifyAccessibilityServiceDelayedLocked(Service service,
AccessibilityEvent event) {
synchronized (mLock) {
- int eventType = event.getEventType();
+ final int eventType = event.getEventType();
+ // Make a copy since during dispatch it is possible the event to
+ // be modified to remove its source if the receiving service does
+ // not have permission to access the window content.
+ AccessibilityEvent newEvent = AccessibilityEvent.obtain(event);
AccessibilityEvent oldEvent = service.mPendingEvents.get(eventType);
- service.mPendingEvents.put(eventType, event);
+ service.mPendingEvents.put(eventType, newEvent);
- int what = eventType | (service.mId << 16);
+ final int what = eventType | (service.mId << 16);
if (oldEvent != null) {
mHandler.removeMessages(what);
- tryRecycleLocked(oldEvent);
+ oldEvent.recycle();
}
Message message = mHandler.obtainMessage(what, service);
- message.arg1 = event.getEventType();
+ message.arg1 = eventType;
mHandler.sendMessageDelayed(message, service.mNotificationTimeout);
}
}
/**
- * Recycles an event if it can be safely recycled. The condition is that no
- * not notified service is interested in the event.
- *
- * @param event The event.
- */
- private void tryRecycleLocked(AccessibilityEvent event) {
- if (event == null) {
- return;
- }
- int eventType = event.getEventType();
- List<Service> services = mServices;
-
- // linear in the number of service which is not large
- for (int i = 0, count = services.size(); i < count; i++) {
- Service service = services.get(i);
- if (service.mPendingEvents.get(eventType) == event) {
- return;
- }
- }
- event.recycle();
- }
-
- /**
* Notifies a service for a scheduled event given the event type.
*
* @param service The service.
@@ -565,7 +541,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private void notifyEventListenerLocked(Service service, int eventType) {
IEventListener listener = service.mServiceInterface;
AccessibilityEvent event = service.mPendingEvents.get(eventType);
-
+ service.mPendingEvents.remove(eventType);
try {
if (mSecurityPolicy.canRetrieveWindowContent(service)) {
event.setConnection(service);
@@ -574,6 +550,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
event.setSealed(true);
listener.onAccessibilityEvent(event);
+ event.recycle();
if (DEBUG) {
Slog.i(LOG_TAG, "Event " + event + " sent to " + listener);
}
@@ -926,7 +903,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
}
- public AccessibilityNodeInfo findAccessibilityNodeInfoByViewId(int viewId) {
+ public AccessibilityNodeInfo findAccessibilityNodeInfoByViewIdInActiveWindow(int viewId) {
IAccessibilityInteractionConnection connection = null;
synchronized (mLock) {
final boolean permissionGranted = mSecurityPolicy.canRetrieveWindowContent(this);
@@ -961,10 +938,18 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
return null;
}
- public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewText(String text) {
+ public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewTextInActiveWindow(
+ String text) {
+ return findAccessibilityNodeInfosByViewText(text,
+ mSecurityPolicy.mRetrievalAlowingWindowId, View.NO_ID);
+ }
+
+ public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewText(String text,
+ int accessibilityWindowId, int accessibilityViewId) {
IAccessibilityInteractionConnection connection = null;
synchronized (mLock) {
- final boolean permissionGranted = mSecurityPolicy.canRetrieveWindowContent(this);
+ final boolean permissionGranted =
+ mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, accessibilityWindowId);
if (permissionGranted) {
connection = getConnectionToRetrievalAllowingWindowLocked();
}
@@ -978,7 +963,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
final long identityToken = Binder.clearCallingIdentity();
try {
final int interactionId = mInteractionIdCounter.getAndIncrement();
- connection.findAccessibilityNodeInfosByViewText(text, interactionId, mCallback);
+ connection.findAccessibilityNodeInfosByViewText(text, accessibilityViewId,
+ interactionId, mCallback);
List<AccessibilityNodeInfo> infos =
mCallback.getFindAccessibilityNodeInfosResultAndClear(interactionId);
if (infos != null) {
@@ -1112,16 +1098,23 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
| AccessibilityEvent.TYPE_VIEW_CLICKED | AccessibilityEvent.TYPE_VIEW_FOCUSED
| AccessibilityEvent.TYPE_VIEW_HOVER_ENTER | AccessibilityEvent.TYPE_VIEW_HOVER_EXIT
| AccessibilityEvent.TYPE_VIEW_LONG_CLICKED | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED
- | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
+ | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED | AccessibilityEvent.TYPE_VIEW_SELECTED
+ | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
private int mRetrievalAlowingWindowId;
+ private boolean canDispatchAccessibilityEvent(AccessibilityEvent event) {
+ // Send window changed event only for the retrieval allowing window.
+ return (event.getEventType() != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
+ || event.getWindowId() == mRetrievalAlowingWindowId);
+ }
+
public void updateRetrievalAllowingWindowAndEventSourceLocked(AccessibilityEvent event) {
- final int windowId = event.getSourceAccessibilityWindowId();
+ final int windowId = event.getWindowId();
final int eventType = event.getEventType();
if ((eventType & RETRIEVAL_ALLOWING_EVENT_TYPES) != 0) {
mRetrievalAlowingWindowId = windowId;
- } else {
+ } else {
event.setSource(null);
}
}