am d8ec8db5
: Merge "Fixing memory leaks in the accessiiblity layer." into ics-mr1
* commit 'd8ec8db5e0f227e4f63e948acb78d829f5ad30c8': Fixing memory leaks in the accessiiblity layer.
This commit is contained in:
@ -16,8 +16,6 @@
|
||||
|
||||
package android.accessibilityservice;
|
||||
|
||||
import com.android.internal.os.HandlerCaller;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
@ -25,8 +23,11 @@ import android.os.Message;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.view.accessibility.AccessibilityInteractionClient;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
|
||||
import com.android.internal.os.HandlerCaller;
|
||||
|
||||
/**
|
||||
* An accessibility service runs in the background and receives callbacks by the system
|
||||
* when {@link AccessibilityEvent}s are fired. Such events denote some state transition
|
||||
@ -219,7 +220,7 @@ public abstract class AccessibilityService extends Service {
|
||||
|
||||
private AccessibilityServiceInfo mInfo;
|
||||
|
||||
IAccessibilityServiceConnection mConnection;
|
||||
private int mConnectionId;
|
||||
|
||||
/**
|
||||
* Callback for {@link android.view.accessibility.AccessibilityEvent}s.
|
||||
@ -264,9 +265,11 @@ public abstract class AccessibilityService extends Service {
|
||||
* AccessibilityManagerService.
|
||||
*/
|
||||
private void sendServiceInfo() {
|
||||
if (mInfo != null && mConnection != null) {
|
||||
IAccessibilityServiceConnection connection =
|
||||
AccessibilityInteractionClient.getInstance().getConnection(mConnectionId);
|
||||
if (mInfo != null && connection != null) {
|
||||
try {
|
||||
mConnection.setServiceInfo(mInfo);
|
||||
connection.setServiceInfo(mInfo);
|
||||
} catch (RemoteException re) {
|
||||
Log.w(LOG_TAG, "Error while setting AccessibilityServiceInfo", re);
|
||||
}
|
||||
@ -302,8 +305,9 @@ public abstract class AccessibilityService extends Service {
|
||||
mCaller = new HandlerCaller(context, this);
|
||||
}
|
||||
|
||||
public void setConnection(IAccessibilityServiceConnection connection) {
|
||||
Message message = mCaller.obtainMessageO(DO_SET_SET_CONNECTION, connection);
|
||||
public void setConnection(IAccessibilityServiceConnection connection, int connectionId) {
|
||||
Message message = mCaller.obtainMessageIO(DO_SET_SET_CONNECTION, connectionId,
|
||||
connection);
|
||||
mCaller.sendMessage(message);
|
||||
}
|
||||
|
||||
@ -330,8 +334,19 @@ public abstract class AccessibilityService extends Service {
|
||||
mTarget.onInterrupt();
|
||||
return;
|
||||
case DO_SET_SET_CONNECTION :
|
||||
mConnection = ((IAccessibilityServiceConnection) message.obj);
|
||||
mTarget.onServiceConnected();
|
||||
final int connectionId = message.arg1;
|
||||
IAccessibilityServiceConnection connection =
|
||||
(IAccessibilityServiceConnection) message.obj;
|
||||
if (connection != null) {
|
||||
AccessibilityInteractionClient.getInstance().addConnection(connectionId,
|
||||
connection);
|
||||
mConnectionId = connectionId;
|
||||
mTarget.onServiceConnected();
|
||||
} else {
|
||||
AccessibilityInteractionClient.getInstance().removeConnection(connectionId);
|
||||
mConnectionId = AccessibilityInteractionClient.NO_ID;
|
||||
// TODO: Do we need a onServiceDisconnected callback?
|
||||
}
|
||||
return;
|
||||
default :
|
||||
Log.w(LOG_TAG, "Unknown message type " + message.what);
|
||||
|
@ -26,7 +26,7 @@ import android.view.accessibility.AccessibilityEvent;
|
||||
*/
|
||||
oneway interface IEventListener {
|
||||
|
||||
void setConnection(in IAccessibilityServiceConnection connection);
|
||||
void setConnection(in IAccessibilityServiceConnection connection, int connectionId);
|
||||
|
||||
void onAccessibilityEvent(in AccessibilityEvent event);
|
||||
|
||||
|
@ -134,6 +134,7 @@ public class SparseArray<E> implements Cloneable {
|
||||
if (i != o) {
|
||||
keys[o] = keys[i];
|
||||
values[o] = val;
|
||||
values[i] = null;
|
||||
}
|
||||
|
||||
o++;
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
package android.view.accessibility;
|
||||
|
||||
import android.accessibilityservice.IAccessibilityServiceConnection;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.text.TextUtils;
|
||||
@ -589,24 +588,6 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
|
||||
mPackageName = event.mPackageName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
@ -821,23 +802,19 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
|
||||
* @param parcel A parcel containing the state of a {@link AccessibilityEvent}.
|
||||
*/
|
||||
public void initFromParcel(Parcel parcel) {
|
||||
if (parcel.readInt() == 1) {
|
||||
mConnection = IAccessibilityServiceConnection.Stub.asInterface(
|
||||
parcel.readStrongBinder());
|
||||
}
|
||||
setSealed(parcel.readInt() == 1);
|
||||
mSealed = (parcel.readInt() == 1);
|
||||
mEventType = parcel.readInt();
|
||||
mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
|
||||
mEventTime = parcel.readLong();
|
||||
mConnectionId = parcel.readInt();
|
||||
readAccessibilityRecordFromParcel(this, parcel);
|
||||
|
||||
// Read the records.
|
||||
final int recordCount = parcel.readInt();
|
||||
for (int i = 0; i < recordCount; i++) {
|
||||
AccessibilityRecord record = AccessibilityRecord.obtain();
|
||||
// Do this to write the connection only once.
|
||||
record.setConnection(mConnection);
|
||||
readAccessibilityRecordFromParcel(record, parcel);
|
||||
record.mConnectionId = mConnectionId;
|
||||
mRecords.add(record);
|
||||
}
|
||||
}
|
||||
@ -875,16 +852,11 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public void writeToParcel(Parcel parcel, int flags) {
|
||||
if (mConnection == null) {
|
||||
parcel.writeInt(0);
|
||||
} else {
|
||||
parcel.writeInt(1);
|
||||
parcel.writeStrongBinder(mConnection.asBinder());
|
||||
}
|
||||
parcel.writeInt(isSealed() ? 1 : 0);
|
||||
parcel.writeInt(mEventType);
|
||||
TextUtils.writeToParcel(mPackageName, parcel, 0);
|
||||
parcel.writeLong(mEventTime);
|
||||
parcel.writeInt(mConnectionId);
|
||||
writeAccessibilityRecordToParcel(this, parcel, flags);
|
||||
|
||||
// Write the records.
|
||||
|
@ -21,6 +21,8 @@ import android.graphics.Rect;
|
||||
import android.os.Message;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@ -61,6 +63,12 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
public final class AccessibilityInteractionClient
|
||||
extends IAccessibilityInteractionConnectionCallback.Stub {
|
||||
|
||||
public static final int NO_ID = -1;
|
||||
|
||||
private static final String LOG_TAG = "AccessibilityInteractionClient";
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private static final long TIMEOUT_INTERACTION_MILLIS = 5000;
|
||||
|
||||
private static final Object sStaticLock = new Object();
|
||||
@ -83,6 +91,9 @@ public final class AccessibilityInteractionClient
|
||||
|
||||
private final Rect mTempBounds = new Rect();
|
||||
|
||||
private final SparseArray<IAccessibilityServiceConnection> mConnectionCache =
|
||||
new SparseArray<IAccessibilityServiceConnection>();
|
||||
|
||||
/**
|
||||
* @return The singleton of this class.
|
||||
*/
|
||||
@ -111,28 +122,37 @@ public final class AccessibilityInteractionClient
|
||||
/**
|
||||
* Finds an {@link AccessibilityNodeInfo} by accessibility id.
|
||||
*
|
||||
* @param connection A connection for interacting with the system.
|
||||
* @param connectionId The id of a connection for interacting with the system.
|
||||
* @param accessibilityWindowId A unique window id.
|
||||
* @param accessibilityViewId A unique View accessibility id.
|
||||
* @return An {@link AccessibilityNodeInfo} if found, null otherwise.
|
||||
*/
|
||||
public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(
|
||||
IAccessibilityServiceConnection connection, int accessibilityWindowId,
|
||||
int accessibilityViewId) {
|
||||
public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(int connectionId,
|
||||
int accessibilityWindowId, int accessibilityViewId) {
|
||||
try {
|
||||
final int interactionId = mInteractionIdCounter.getAndIncrement();
|
||||
final float windowScale = connection.findAccessibilityNodeInfoByAccessibilityId(
|
||||
accessibilityWindowId, accessibilityViewId, interactionId, this,
|
||||
Thread.currentThread().getId());
|
||||
// If the scale is zero the call has failed.
|
||||
if (windowScale > 0) {
|
||||
AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
|
||||
interactionId);
|
||||
finalizeAccessibilityNodeInfo(info, connection, windowScale);
|
||||
return info;
|
||||
IAccessibilityServiceConnection connection = getConnection(connectionId);
|
||||
if (connection != null) {
|
||||
final int interactionId = mInteractionIdCounter.getAndIncrement();
|
||||
final float windowScale = connection.findAccessibilityNodeInfoByAccessibilityId(
|
||||
accessibilityWindowId, accessibilityViewId, interactionId, this,
|
||||
Thread.currentThread().getId());
|
||||
// If the scale is zero the call has failed.
|
||||
if (windowScale > 0) {
|
||||
AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
|
||||
interactionId);
|
||||
finalizeAccessibilityNodeInfo(info, connectionId, windowScale);
|
||||
return info;
|
||||
}
|
||||
} else {
|
||||
if (DEBUG) {
|
||||
Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
|
||||
}
|
||||
}
|
||||
} catch (RemoteException re) {
|
||||
/* ignore */
|
||||
if (DEBUG) {
|
||||
Log.w(LOG_TAG, "Error while calling remote"
|
||||
+ " findAccessibilityNodeInfoByAccessibilityId", re);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -141,25 +161,36 @@ public final class AccessibilityInteractionClient
|
||||
* Finds an {@link AccessibilityNodeInfo} by View id. The search is performed
|
||||
* in the currently active window and starts from the root View in the window.
|
||||
*
|
||||
* @param connection A connection for interacting with the system.
|
||||
* @param connectionId The id of a connection for interacting with the system.
|
||||
* @param viewId The id of the view.
|
||||
* @return An {@link AccessibilityNodeInfo} if found, null otherwise.
|
||||
*/
|
||||
public AccessibilityNodeInfo findAccessibilityNodeInfoByViewIdInActiveWindow(
|
||||
IAccessibilityServiceConnection connection, int viewId) {
|
||||
public AccessibilityNodeInfo findAccessibilityNodeInfoByViewIdInActiveWindow(int connectionId,
|
||||
int viewId) {
|
||||
try {
|
||||
final int interactionId = mInteractionIdCounter.getAndIncrement();
|
||||
final float windowScale = connection.findAccessibilityNodeInfoByViewIdInActiveWindow(
|
||||
viewId, interactionId, this, Thread.currentThread().getId());
|
||||
// If the scale is zero the call has failed.
|
||||
if (windowScale > 0) {
|
||||
AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
|
||||
interactionId);
|
||||
finalizeAccessibilityNodeInfo(info, connection, windowScale);
|
||||
return info;
|
||||
IAccessibilityServiceConnection connection = getConnection(connectionId);
|
||||
if (connection != null) {
|
||||
final int interactionId = mInteractionIdCounter.getAndIncrement();
|
||||
final float windowScale =
|
||||
connection.findAccessibilityNodeInfoByViewIdInActiveWindow(viewId,
|
||||
interactionId, this, Thread.currentThread().getId());
|
||||
// If the scale is zero the call has failed.
|
||||
if (windowScale > 0) {
|
||||
AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
|
||||
interactionId);
|
||||
finalizeAccessibilityNodeInfo(info, connectionId, windowScale);
|
||||
return info;
|
||||
}
|
||||
} else {
|
||||
if (DEBUG) {
|
||||
Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
|
||||
}
|
||||
}
|
||||
} catch (RemoteException re) {
|
||||
/* ignore */
|
||||
if (DEBUG) {
|
||||
Log.w(LOG_TAG, "Error while calling remote"
|
||||
+ " findAccessibilityNodeInfoByViewIdInActiveWindow", re);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -169,25 +200,36 @@ public final class AccessibilityInteractionClient
|
||||
* insensitive containment. The search is performed in the currently
|
||||
* active window and starts from the root View in the window.
|
||||
*
|
||||
* @param connection A connection for interacting with the system.
|
||||
* @param connectionId The id of a connection for interacting with the system.
|
||||
* @param text The searched text.
|
||||
* @return A list of found {@link AccessibilityNodeInfo}s.
|
||||
*/
|
||||
public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewTextInActiveWindow(
|
||||
IAccessibilityServiceConnection connection, String text) {
|
||||
int connectionId, String text) {
|
||||
try {
|
||||
final int interactionId = mInteractionIdCounter.getAndIncrement();
|
||||
final float windowScale = connection.findAccessibilityNodeInfosByViewTextInActiveWindow(
|
||||
text, interactionId, this, Thread.currentThread().getId());
|
||||
// If the scale is zero the call has failed.
|
||||
if (windowScale > 0) {
|
||||
List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
|
||||
interactionId);
|
||||
finalizeAccessibilityNodeInfos(infos, connection, windowScale);
|
||||
return infos;
|
||||
IAccessibilityServiceConnection connection = getConnection(connectionId);
|
||||
if (connection != null) {
|
||||
final int interactionId = mInteractionIdCounter.getAndIncrement();
|
||||
final float windowScale =
|
||||
connection.findAccessibilityNodeInfosByViewTextInActiveWindow(text,
|
||||
interactionId, this, Thread.currentThread().getId());
|
||||
// If the scale is zero the call has failed.
|
||||
if (windowScale > 0) {
|
||||
List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
|
||||
interactionId);
|
||||
finalizeAccessibilityNodeInfos(infos, connectionId, windowScale);
|
||||
return infos;
|
||||
}
|
||||
} else {
|
||||
if (DEBUG) {
|
||||
Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
|
||||
}
|
||||
}
|
||||
} catch (RemoteException re) {
|
||||
/* ignore */
|
||||
if (DEBUG) {
|
||||
Log.w(LOG_TAG, "Error while calling remote"
|
||||
+ " findAccessibilityNodeInfosByViewTextInActiveWindow", re);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -198,30 +240,39 @@ public final class AccessibilityInteractionClient
|
||||
* id is specified and starts from the View whose accessibility id is
|
||||
* specified.
|
||||
*
|
||||
* @param connection A connection for interacting with the system.
|
||||
* @param connectionId The id of a connection for interacting with the system.
|
||||
* @param text The searched text.
|
||||
* @param accessibilityWindowId A unique window id.
|
||||
* @param accessibilityViewId A unique View accessibility id from where to start the search.
|
||||
* Use {@link android.view.View#NO_ID} to start from the root.
|
||||
* @return A list of found {@link AccessibilityNodeInfo}s.
|
||||
*/
|
||||
public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewText(
|
||||
IAccessibilityServiceConnection connection, String text, int accessibilityWindowId,
|
||||
int accessibilityViewId) {
|
||||
public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewText(int connectionId,
|
||||
String text, int accessibilityWindowId, int accessibilityViewId) {
|
||||
try {
|
||||
final int interactionId = mInteractionIdCounter.getAndIncrement();
|
||||
final float windowScale = connection.findAccessibilityNodeInfosByViewText(text,
|
||||
accessibilityWindowId, accessibilityViewId, interactionId, this,
|
||||
Thread.currentThread().getId());
|
||||
// If the scale is zero the call has failed.
|
||||
if (windowScale > 0) {
|
||||
List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
|
||||
interactionId);
|
||||
finalizeAccessibilityNodeInfos(infos, connection, windowScale);
|
||||
return infos;
|
||||
IAccessibilityServiceConnection connection = getConnection(connectionId);
|
||||
if (connection != null) {
|
||||
final int interactionId = mInteractionIdCounter.getAndIncrement();
|
||||
final float windowScale = connection.findAccessibilityNodeInfosByViewText(text,
|
||||
accessibilityWindowId, accessibilityViewId, interactionId, this,
|
||||
Thread.currentThread().getId());
|
||||
// If the scale is zero the call has failed.
|
||||
if (windowScale > 0) {
|
||||
List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
|
||||
interactionId);
|
||||
finalizeAccessibilityNodeInfos(infos, connectionId, windowScale);
|
||||
return infos;
|
||||
}
|
||||
} else {
|
||||
if (DEBUG) {
|
||||
Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
|
||||
}
|
||||
}
|
||||
} catch (RemoteException re) {
|
||||
/* ignore */
|
||||
if (DEBUG) {
|
||||
Log.w(LOG_TAG, "Error while calling remote"
|
||||
+ " findAccessibilityNodeInfosByViewText", re);
|
||||
}
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
@ -229,24 +280,33 @@ public final class AccessibilityInteractionClient
|
||||
/**
|
||||
* Performs an accessibility action on an {@link AccessibilityNodeInfo}.
|
||||
*
|
||||
* @param connection A connection for interacting with the system.
|
||||
* @param connectionId The id of a connection for interacting with the system.
|
||||
* @param accessibilityWindowId The id of the window.
|
||||
* @param accessibilityViewId A unique View accessibility id.
|
||||
* @param action The action to perform.
|
||||
* @return Whether the action was performed.
|
||||
*/
|
||||
public boolean performAccessibilityAction(IAccessibilityServiceConnection connection,
|
||||
int accessibilityWindowId, int accessibilityViewId, int action) {
|
||||
public boolean performAccessibilityAction(int connectionId, int accessibilityWindowId,
|
||||
int accessibilityViewId, int action) {
|
||||
try {
|
||||
final int interactionId = mInteractionIdCounter.getAndIncrement();
|
||||
final boolean success = connection.performAccessibilityAction(
|
||||
accessibilityWindowId, accessibilityViewId, action, interactionId, this,
|
||||
Thread.currentThread().getId());
|
||||
if (success) {
|
||||
return getPerformAccessibilityActionResult(interactionId);
|
||||
IAccessibilityServiceConnection connection = getConnection(connectionId);
|
||||
if (connection != null) {
|
||||
final int interactionId = mInteractionIdCounter.getAndIncrement();
|
||||
final boolean success = connection.performAccessibilityAction(
|
||||
accessibilityWindowId, accessibilityViewId, action, interactionId, this,
|
||||
Thread.currentThread().getId());
|
||||
if (success) {
|
||||
return getPerformAccessibilityActionResult(interactionId);
|
||||
}
|
||||
} else {
|
||||
if (DEBUG) {
|
||||
Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
|
||||
}
|
||||
}
|
||||
} catch (RemoteException re) {
|
||||
/* ignore */
|
||||
if (DEBUG) {
|
||||
Log.w(LOG_TAG, "Error while calling remote performAccessibilityAction", re);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -406,14 +466,14 @@ public final class AccessibilityInteractionClient
|
||||
* Finalize an {@link AccessibilityNodeInfo} before passing it to the client.
|
||||
*
|
||||
* @param info The info.
|
||||
* @param connection The current connection to the system.
|
||||
* @param connectionId The id of the connection to the system.
|
||||
* @param windowScale The source window compatibility scale.
|
||||
*/
|
||||
private void finalizeAccessibilityNodeInfo(AccessibilityNodeInfo info,
|
||||
IAccessibilityServiceConnection connection, float windowScale) {
|
||||
private void finalizeAccessibilityNodeInfo(AccessibilityNodeInfo info, int connectionId,
|
||||
float windowScale) {
|
||||
if (info != null) {
|
||||
applyCompatibilityScaleIfNeeded(info, windowScale);
|
||||
info.setConnection(connection);
|
||||
info.setConnectionId(connectionId);
|
||||
info.setSealed(true);
|
||||
}
|
||||
}
|
||||
@ -422,16 +482,16 @@ public final class AccessibilityInteractionClient
|
||||
* Finalize {@link AccessibilityNodeInfo}s before passing them to the client.
|
||||
*
|
||||
* @param infos The {@link AccessibilityNodeInfo}s.
|
||||
* @param connection The current connection to the system.
|
||||
* @param connectionId The id of the connection to the system.
|
||||
* @param windowScale The source window compatibility scale.
|
||||
*/
|
||||
private void finalizeAccessibilityNodeInfos(List<AccessibilityNodeInfo> infos,
|
||||
IAccessibilityServiceConnection connection, float windowScale) {
|
||||
int connectionId, float windowScale) {
|
||||
if (infos != null) {
|
||||
final int infosCount = infos.size();
|
||||
for (int i = 0; i < infosCount; i++) {
|
||||
AccessibilityNodeInfo info = infos.get(i);
|
||||
finalizeAccessibilityNodeInfo(info, connection, windowScale);
|
||||
finalizeAccessibilityNodeInfo(info, connectionId, windowScale);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -449,4 +509,39 @@ public final class AccessibilityInteractionClient
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a cached accessibility service connection.
|
||||
*
|
||||
* @param connectionId The connection id.
|
||||
* @return The cached connection if such.
|
||||
*/
|
||||
public IAccessibilityServiceConnection getConnection(int connectionId) {
|
||||
synchronized (mConnectionCache) {
|
||||
return mConnectionCache.get(connectionId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a cached accessibility service connection.
|
||||
*
|
||||
* @param connectionId The connection id.
|
||||
* @param connection The connection.
|
||||
*/
|
||||
public void addConnection(int connectionId, IAccessibilityServiceConnection connection) {
|
||||
synchronized (mConnectionCache) {
|
||||
mConnectionCache.put(connectionId, connection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a cached accessibility service connection.
|
||||
*
|
||||
* @param connectionId The connection id.
|
||||
*/
|
||||
public void removeConnection(int connectionId) {
|
||||
synchronized (mConnectionCache) {
|
||||
mConnectionCache.remove(connectionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
package android.view.accessibility;
|
||||
|
||||
import android.accessibilityservice.IAccessibilityServiceConnection;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
@ -53,6 +52,8 @@ public class AccessibilityNodeInfo implements Parcelable {
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private static final int UNDEFINED = -1;
|
||||
|
||||
// Actions.
|
||||
|
||||
/**
|
||||
@ -107,9 +108,9 @@ public class AccessibilityNodeInfo implements Parcelable {
|
||||
private boolean mSealed;
|
||||
|
||||
// Data.
|
||||
private int mAccessibilityViewId = View.NO_ID;
|
||||
private int mAccessibilityWindowId = View.NO_ID;
|
||||
private int mParentAccessibilityViewId = View.NO_ID;
|
||||
private int mAccessibilityViewId = UNDEFINED;
|
||||
private int mAccessibilityWindowId = UNDEFINED;
|
||||
private int mParentAccessibilityViewId = UNDEFINED;
|
||||
private int mBooleanProperties;
|
||||
private final Rect mBoundsInParent = new Rect();
|
||||
private final Rect mBoundsInScreen = new Rect();
|
||||
@ -122,7 +123,7 @@ public class AccessibilityNodeInfo implements Parcelable {
|
||||
private SparseIntArray mChildAccessibilityIds = new SparseIntArray();
|
||||
private int mActions;
|
||||
|
||||
private IAccessibilityServiceConnection mConnection;
|
||||
private int mConnectionId = UNDEFINED;
|
||||
|
||||
/**
|
||||
* Hide constructor from clients.
|
||||
@ -181,7 +182,7 @@ public class AccessibilityNodeInfo implements Parcelable {
|
||||
return null;
|
||||
}
|
||||
AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
|
||||
return client.findAccessibilityNodeInfoByAccessibilityId(mConnection,
|
||||
return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
|
||||
mAccessibilityWindowId, childAccessibilityViewId);
|
||||
}
|
||||
|
||||
@ -253,7 +254,7 @@ public class AccessibilityNodeInfo implements Parcelable {
|
||||
return false;
|
||||
}
|
||||
AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
|
||||
return client.performAccessibilityAction(mConnection, mAccessibilityWindowId,
|
||||
return client.performAccessibilityAction(mConnectionId, mAccessibilityWindowId,
|
||||
mAccessibilityViewId, action);
|
||||
}
|
||||
|
||||
@ -277,7 +278,7 @@ public class AccessibilityNodeInfo implements Parcelable {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
|
||||
return client.findAccessibilityNodeInfosByViewText(mConnection, text,
|
||||
return client.findAccessibilityNodeInfosByViewText(mConnectionId, text,
|
||||
mAccessibilityWindowId, mAccessibilityViewId);
|
||||
}
|
||||
|
||||
@ -297,7 +298,7 @@ public class AccessibilityNodeInfo implements Parcelable {
|
||||
return null;
|
||||
}
|
||||
AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
|
||||
return client.findAccessibilityNodeInfoByAccessibilityId(mConnection,
|
||||
return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
|
||||
mAccessibilityWindowId, mParentAccessibilityViewId);
|
||||
}
|
||||
|
||||
@ -755,15 +756,16 @@ public class AccessibilityNodeInfo implements Parcelable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the connection for interacting with the system.
|
||||
* Sets the unique id of the IAccessibilityServiceConnection over which
|
||||
* this instance can send requests to the system.
|
||||
*
|
||||
* @param connection The client token.
|
||||
* @param connectionId The connection id.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public final void setConnection(IAccessibilityServiceConnection connection) {
|
||||
public void setConnectionId(int connectionId) {
|
||||
enforceNotSealed();
|
||||
mConnection = connection;
|
||||
mConnectionId = connectionId;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -900,16 +902,11 @@ public class AccessibilityNodeInfo implements Parcelable {
|
||||
* </p>
|
||||
*/
|
||||
public void writeToParcel(Parcel parcel, int flags) {
|
||||
if (mConnection == null) {
|
||||
parcel.writeInt(0);
|
||||
} else {
|
||||
parcel.writeInt(1);
|
||||
parcel.writeStrongBinder(mConnection.asBinder());
|
||||
}
|
||||
parcel.writeInt(isSealed() ? 1 : 0);
|
||||
parcel.writeInt(mAccessibilityViewId);
|
||||
parcel.writeInt(mAccessibilityWindowId);
|
||||
parcel.writeInt(mParentAccessibilityViewId);
|
||||
parcel.writeInt(mConnectionId);
|
||||
|
||||
SparseIntArray childIds = mChildAccessibilityIds;
|
||||
final int childIdsSize = childIds.size();
|
||||
@ -949,10 +946,10 @@ public class AccessibilityNodeInfo implements Parcelable {
|
||||
*/
|
||||
private void init(AccessibilityNodeInfo other) {
|
||||
mSealed = other.mSealed;
|
||||
mConnection = other.mConnection;
|
||||
mAccessibilityViewId = other.mAccessibilityViewId;
|
||||
mParentAccessibilityViewId = other.mParentAccessibilityViewId;
|
||||
mAccessibilityWindowId = other.mAccessibilityWindowId;
|
||||
mConnectionId = other.mConnectionId;
|
||||
mBoundsInParent.set(other.mBoundsInParent);
|
||||
mBoundsInScreen.set(other.mBoundsInScreen);
|
||||
mPackageName = other.mPackageName;
|
||||
@ -970,14 +967,11 @@ public class AccessibilityNodeInfo implements Parcelable {
|
||||
* @param parcel A parcel containing the state of a {@link AccessibilityNodeInfo}.
|
||||
*/
|
||||
private void initFromParcel(Parcel parcel) {
|
||||
if (parcel.readInt() == 1) {
|
||||
mConnection = IAccessibilityServiceConnection.Stub.asInterface(
|
||||
parcel.readStrongBinder());
|
||||
}
|
||||
mSealed = (parcel.readInt() == 1);
|
||||
mAccessibilityViewId = parcel.readInt();
|
||||
mAccessibilityWindowId = parcel.readInt();
|
||||
mParentAccessibilityViewId = parcel.readInt();
|
||||
mConnectionId = parcel.readInt();
|
||||
|
||||
SparseIntArray childIds = mChildAccessibilityIds;
|
||||
final int childrenSize = parcel.readInt();
|
||||
@ -1011,10 +1005,10 @@ public class AccessibilityNodeInfo implements Parcelable {
|
||||
*/
|
||||
private void clear() {
|
||||
mSealed = false;
|
||||
mConnection = null;
|
||||
mAccessibilityViewId = View.NO_ID;
|
||||
mParentAccessibilityViewId = View.NO_ID;
|
||||
mAccessibilityWindowId = View.NO_ID;
|
||||
mAccessibilityViewId = UNDEFINED;
|
||||
mParentAccessibilityViewId = UNDEFINED;
|
||||
mAccessibilityWindowId = UNDEFINED;
|
||||
mConnectionId = UNDEFINED;
|
||||
mChildAccessibilityIds.clear();
|
||||
mBoundsInParent.set(0, 0, 0, 0);
|
||||
mBoundsInScreen.set(0, 0, 0, 0);
|
||||
@ -1048,9 +1042,8 @@ public class AccessibilityNodeInfo implements Parcelable {
|
||||
}
|
||||
|
||||
private boolean canPerformRequestOverConnection(int accessibilityViewId) {
|
||||
return (mAccessibilityWindowId != View.NO_ID
|
||||
&& accessibilityViewId != View.NO_ID
|
||||
&& mConnection != null);
|
||||
return (mConnectionId != UNDEFINED && mAccessibilityWindowId != UNDEFINED
|
||||
&& accessibilityViewId != UNDEFINED);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
package android.view.accessibility;
|
||||
|
||||
import android.accessibilityservice.IAccessibilityServiceConnection;
|
||||
import android.os.Parcelable;
|
||||
import android.view.View;
|
||||
|
||||
@ -78,8 +77,8 @@ public class AccessibilityRecord {
|
||||
|
||||
int mAddedCount= UNDEFINED;
|
||||
int mRemovedCount = UNDEFINED;
|
||||
int mSourceViewId = View.NO_ID;
|
||||
int mSourceWindowId = View.NO_ID;
|
||||
int mSourceViewId = UNDEFINED;
|
||||
int mSourceWindowId = UNDEFINED;
|
||||
|
||||
CharSequence mClassName;
|
||||
CharSequence mContentDescription;
|
||||
@ -87,7 +86,8 @@ public class AccessibilityRecord {
|
||||
Parcelable mParcelableData;
|
||||
|
||||
final List<CharSequence> mText = new ArrayList<CharSequence>();
|
||||
IAccessibilityServiceConnection mConnection;
|
||||
|
||||
int mConnectionId = UNDEFINED;
|
||||
|
||||
/*
|
||||
* Hide constructor.
|
||||
@ -108,8 +108,8 @@ public class AccessibilityRecord {
|
||||
mSourceWindowId = source.getAccessibilityWindowId();
|
||||
mSourceViewId = source.getAccessibilityViewId();
|
||||
} else {
|
||||
mSourceWindowId = View.NO_ID;
|
||||
mSourceViewId = View.NO_ID;
|
||||
mSourceWindowId = UNDEFINED;
|
||||
mSourceViewId = UNDEFINED;
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,32 +119,20 @@ public class AccessibilityRecord {
|
||||
* <strong>Note:</strong> It is a client responsibility to recycle the received info
|
||||
* by calling {@link AccessibilityNodeInfo#recycle() AccessibilityNodeInfo#recycle()}
|
||||
* to avoid creating of multiple instances.
|
||||
*
|
||||
* </p>
|
||||
* @return The info of the source.
|
||||
*/
|
||||
public AccessibilityNodeInfo getSource() {
|
||||
enforceSealed();
|
||||
if (mSourceWindowId == View.NO_ID || mSourceViewId == View.NO_ID || mConnection == null) {
|
||||
if (mConnectionId == UNDEFINED || mSourceWindowId == UNDEFINED
|
||||
|| mSourceViewId == UNDEFINED) {
|
||||
return null;
|
||||
}
|
||||
AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
|
||||
return client.findAccessibilityNodeInfoByAccessibilityId(mConnection, mSourceWindowId,
|
||||
return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mSourceWindowId,
|
||||
mSourceViewId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
@ -560,6 +548,19 @@ public class AccessibilityRecord {
|
||||
mParcelableData = parcelableData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the unique id of the IAccessibilityServiceConnection over which
|
||||
* this instance can send requests to the system.
|
||||
*
|
||||
* @param connectionId The connection id.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public void setConnectionId(int connectionId) {
|
||||
enforceNotSealed();
|
||||
mConnectionId = connectionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if this instance is sealed.
|
||||
*
|
||||
@ -708,7 +709,7 @@ public class AccessibilityRecord {
|
||||
mText.addAll(record.mText);
|
||||
mSourceWindowId = record.mSourceWindowId;
|
||||
mSourceViewId = record.mSourceViewId;
|
||||
mConnection = record.mConnection;
|
||||
mConnectionId = record.mConnectionId;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -732,8 +733,9 @@ public class AccessibilityRecord {
|
||||
mBeforeText = null;
|
||||
mParcelableData = null;
|
||||
mText.clear();
|
||||
mSourceViewId = View.NO_ID;
|
||||
mSourceWindowId = View.NO_ID;
|
||||
mSourceViewId = UNDEFINED;
|
||||
mSourceWindowId = UNDEFINED;
|
||||
mConnectionId = UNDEFINED;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -49,5 +49,5 @@ interface IAccessibilityManager {
|
||||
|
||||
void removeAccessibilityInteractionConnection(IWindow windowToken);
|
||||
|
||||
IAccessibilityServiceConnection registerEventListener(IEventListener client);
|
||||
void registerEventListener(IEventListener client);
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import android.os.SystemClock;
|
||||
import android.test.ActivityInstrumentationTestCase2;
|
||||
import android.test.suitebuilder.annotation.LargeTest;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.view.accessibility.AccessibilityInteractionClient;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
@ -54,28 +55,31 @@ public class InterrogationActivityTest
|
||||
// Timeout before give up wait for the system to process an accessibility setting change.
|
||||
private static final int TIMEOUT_PROPAGATE_ACCESSIBLITY_SETTING = 2000;
|
||||
|
||||
// Timeout for the accessibility state of an Activity to be fully initialized.
|
||||
private static final int TIMEOUT_ACCESSIBLITY_STATE_INITIALIZED_MILLIS = 100;
|
||||
|
||||
// Handle to a connection to the AccessibilityManagerService
|
||||
private static IAccessibilityServiceConnection sConnection;
|
||||
private static int sConnectionId = View.NO_ID;
|
||||
|
||||
// The last received accessibility event
|
||||
private static volatile AccessibilityEvent sLastFocusAccessibilityEvent;
|
||||
private volatile AccessibilityEvent mLastAccessibilityEvent;
|
||||
|
||||
public InterrogationActivityTest() {
|
||||
super(InterrogationActivity.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
ensureConnection();
|
||||
bringUpActivityWithInitalizedAccessbility();
|
||||
}
|
||||
|
||||
@LargeTest
|
||||
public void testFindAccessibilityNodeInfoByViewId() throws Exception {
|
||||
final long startTimeMillis = SystemClock.uptimeMillis();
|
||||
try {
|
||||
// hook into the system first
|
||||
IAccessibilityServiceConnection connection = getConnection();
|
||||
|
||||
// bring up the activity
|
||||
getActivity();
|
||||
|
||||
AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
|
||||
.findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5);
|
||||
.findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
|
||||
assertNotNull(button);
|
||||
assertEquals(0, button.getChildCount());
|
||||
|
||||
@ -120,15 +124,9 @@ public class InterrogationActivityTest
|
||||
public void testFindAccessibilityNodeInfoByViewText() throws Exception {
|
||||
final long startTimeMillis = SystemClock.uptimeMillis();
|
||||
try {
|
||||
// hook into the system first
|
||||
IAccessibilityServiceConnection connection = getConnection();
|
||||
|
||||
// bring up the activity
|
||||
getActivity();
|
||||
|
||||
// find a view by text
|
||||
List<AccessibilityNodeInfo> buttons = AccessibilityInteractionClient.getInstance()
|
||||
.findAccessibilityNodeInfosByViewTextInActiveWindow(connection, "butto");
|
||||
List<AccessibilityNodeInfo> buttons = AccessibilityInteractionClient.getInstance()
|
||||
.findAccessibilityNodeInfosByViewTextInActiveWindow(sConnectionId, "butto");
|
||||
assertEquals(9, buttons.size());
|
||||
} finally {
|
||||
if (DEBUG) {
|
||||
@ -143,15 +141,11 @@ public class InterrogationActivityTest
|
||||
public void testFindAccessibilityNodeInfoByViewTextContentDescription() throws Exception {
|
||||
final long startTimeMillis = SystemClock.uptimeMillis();
|
||||
try {
|
||||
// hook into the system first
|
||||
IAccessibilityServiceConnection connection = getConnection();
|
||||
|
||||
// bring up the activity
|
||||
getActivity();
|
||||
bringUpActivityWithInitalizedAccessbility();
|
||||
|
||||
// find a view by text
|
||||
List<AccessibilityNodeInfo> buttons = AccessibilityInteractionClient.getInstance()
|
||||
.findAccessibilityNodeInfosByViewTextInActiveWindow(connection,
|
||||
List<AccessibilityNodeInfo> buttons = AccessibilityInteractionClient.getInstance()
|
||||
.findAccessibilityNodeInfosByViewTextInActiveWindow(sConnectionId,
|
||||
"contentDescription");
|
||||
assertEquals(1, buttons.size());
|
||||
} finally {
|
||||
@ -167,12 +161,6 @@ public class InterrogationActivityTest
|
||||
public void testTraverseAllViews() throws Exception {
|
||||
final long startTimeMillis = SystemClock.uptimeMillis();
|
||||
try {
|
||||
// hook into the system first
|
||||
IAccessibilityServiceConnection connection = getConnection();
|
||||
|
||||
// bring up the activity
|
||||
getActivity();
|
||||
|
||||
// make list of expected nodes
|
||||
List<String> classNameAndTextList = new ArrayList<String>();
|
||||
classNameAndTextList.add("android.widget.LinearLayout");
|
||||
@ -190,7 +178,7 @@ public class InterrogationActivityTest
|
||||
classNameAndTextList.add("android.widget.ButtonButton9");
|
||||
|
||||
AccessibilityNodeInfo root = AccessibilityInteractionClient.getInstance()
|
||||
.findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.root);
|
||||
.findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.root);
|
||||
assertNotNull("We must find the existing root.", root);
|
||||
|
||||
Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>();
|
||||
@ -227,23 +215,17 @@ public class InterrogationActivityTest
|
||||
public void testPerformAccessibilityActionFocus() throws Exception {
|
||||
final long startTimeMillis = SystemClock.uptimeMillis();
|
||||
try {
|
||||
// hook into the system first
|
||||
IAccessibilityServiceConnection connection = getConnection();
|
||||
|
||||
// bring up the activity
|
||||
getActivity();
|
||||
|
||||
// find a view and make sure it is not focused
|
||||
AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
|
||||
.findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5);
|
||||
.findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, 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 = AccessibilityInteractionClient.getInstance()
|
||||
.findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5);
|
||||
button = AccessibilityInteractionClient.getInstance()
|
||||
.findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
|
||||
assertTrue(button.isFocused());
|
||||
} finally {
|
||||
if (DEBUG) {
|
||||
@ -257,15 +239,9 @@ public class InterrogationActivityTest
|
||||
public void testPerformAccessibilityActionClearFocus() throws Exception {
|
||||
final long startTimeMillis = SystemClock.uptimeMillis();
|
||||
try {
|
||||
// hook into the system first
|
||||
IAccessibilityServiceConnection connection = getConnection();
|
||||
|
||||
// bring up the activity
|
||||
getActivity();
|
||||
|
||||
// find a view and make sure it is not focused
|
||||
AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
|
||||
.findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5);
|
||||
.findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
|
||||
assertFalse(button.isFocused());
|
||||
|
||||
// focus the view
|
||||
@ -273,7 +249,7 @@ public class InterrogationActivityTest
|
||||
|
||||
// find the view again and make sure it is focused
|
||||
button = AccessibilityInteractionClient.getInstance()
|
||||
.findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5);
|
||||
.findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
|
||||
assertTrue(button.isFocused());
|
||||
|
||||
// unfocus the view
|
||||
@ -281,7 +257,7 @@ public class InterrogationActivityTest
|
||||
|
||||
// find the view again and make sure it is not focused
|
||||
button = AccessibilityInteractionClient.getInstance()
|
||||
.findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5);
|
||||
.findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
|
||||
assertFalse(button.isFocused());
|
||||
} finally {
|
||||
if (DEBUG) {
|
||||
@ -296,15 +272,9 @@ public class InterrogationActivityTest
|
||||
public void testPerformAccessibilityActionSelect() throws Exception {
|
||||
final long startTimeMillis = SystemClock.uptimeMillis();
|
||||
try {
|
||||
// hook into the system first
|
||||
IAccessibilityServiceConnection connection = getConnection();
|
||||
|
||||
// bring up the activity
|
||||
getActivity();
|
||||
|
||||
// find a view and make sure it is not selected
|
||||
AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
|
||||
.findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5);
|
||||
.findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
|
||||
assertFalse(button.isSelected());
|
||||
|
||||
// select the view
|
||||
@ -312,7 +282,7 @@ public class InterrogationActivityTest
|
||||
|
||||
// find the view again and make sure it is selected
|
||||
button = AccessibilityInteractionClient.getInstance()
|
||||
.findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5);
|
||||
.findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
|
||||
assertTrue(button.isSelected());
|
||||
} finally {
|
||||
if (DEBUG) {
|
||||
@ -326,15 +296,9 @@ public class InterrogationActivityTest
|
||||
public void testPerformAccessibilityActionClearSelection() throws Exception {
|
||||
final long startTimeMillis = SystemClock.uptimeMillis();
|
||||
try {
|
||||
// hook into the system first
|
||||
IAccessibilityServiceConnection connection = getConnection();
|
||||
|
||||
// bring up the activity
|
||||
getActivity();
|
||||
|
||||
// find a view and make sure it is not selected
|
||||
AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
|
||||
.findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5);
|
||||
.findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
|
||||
assertFalse(button.isSelected());
|
||||
|
||||
// select the view
|
||||
@ -342,15 +306,15 @@ public class InterrogationActivityTest
|
||||
|
||||
// find the view again and make sure it is selected
|
||||
button = AccessibilityInteractionClient.getInstance()
|
||||
.findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5);
|
||||
.findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, 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 = AccessibilityInteractionClient.getInstance()
|
||||
.findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5);
|
||||
button = AccessibilityInteractionClient.getInstance()
|
||||
.findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
|
||||
assertFalse(button.isSelected());
|
||||
} finally {
|
||||
if (DEBUG) {
|
||||
@ -365,30 +329,24 @@ public class InterrogationActivityTest
|
||||
public void testAccessibilityEventGetSource() throws Exception {
|
||||
final long startTimeMillis = SystemClock.uptimeMillis();
|
||||
try {
|
||||
// hook into the system first
|
||||
IAccessibilityServiceConnection connection = getConnection();
|
||||
|
||||
// bring up the activity
|
||||
getActivity();
|
||||
|
||||
// find a view and make sure it is not focused
|
||||
AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
|
||||
.findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5);
|
||||
.findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
|
||||
assertFalse(button.isSelected());
|
||||
|
||||
// focus the view
|
||||
assertTrue(button.performAction(ACTION_FOCUS));
|
||||
|
||||
synchronized (sConnection) {
|
||||
synchronized (this) {
|
||||
try {
|
||||
sConnection.wait(500);
|
||||
wait(TIMEOUT_ACCESSIBLITY_STATE_INITIALIZED_MILLIS);
|
||||
} catch (InterruptedException ie) {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
// check that last event source
|
||||
AccessibilityNodeInfo source = sLastFocusAccessibilityEvent.getSource();
|
||||
AccessibilityNodeInfo source = mLastAccessibilityEvent.getSource();
|
||||
assertNotNull(source);
|
||||
|
||||
// bounds
|
||||
@ -430,15 +388,9 @@ public class InterrogationActivityTest
|
||||
public void testObjectContract() throws Exception {
|
||||
final long startTimeMillis = SystemClock.uptimeMillis();
|
||||
try {
|
||||
// hook into the system first
|
||||
IAccessibilityServiceConnection connection = getConnection();
|
||||
|
||||
// bring up the activity
|
||||
getActivity();
|
||||
|
||||
// find a view and make sure it is not focused
|
||||
AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
|
||||
.findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5);
|
||||
.findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
|
||||
AccessibilityNodeInfo parent = button.getParent();
|
||||
final int childCount = parent.getChildCount();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
@ -459,24 +411,57 @@ public class InterrogationActivityTest
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void scrubClass(Class<?> testCaseClass) {
|
||||
/* intentionally do not scrub */
|
||||
private void bringUpActivityWithInitalizedAccessbility() {
|
||||
mLastAccessibilityEvent = null;
|
||||
// bring up the activity
|
||||
getActivity();
|
||||
|
||||
final long startTimeMillis = SystemClock.uptimeMillis();
|
||||
while (true) {
|
||||
if (mLastAccessibilityEvent != null) {
|
||||
final int eventType = mLastAccessibilityEvent.getEventType();
|
||||
if (eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
final long remainingTimeMillis = TIMEOUT_ACCESSIBLITY_STATE_INITIALIZED_MILLIS
|
||||
- (SystemClock.uptimeMillis() - startTimeMillis);
|
||||
if (remainingTimeMillis <= 0) {
|
||||
return;
|
||||
}
|
||||
synchronized (this) {
|
||||
try {
|
||||
wait(remainingTimeMillis);
|
||||
} catch (InterruptedException e) {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IAccessibilityServiceConnection getConnection() throws Exception {
|
||||
if (sConnection == null) {
|
||||
private void ensureConnection() throws Exception {
|
||||
if (sConnectionId == View.NO_ID) {
|
||||
IEventListener listener = new IEventListener.Stub() {
|
||||
public void setConnection(IAccessibilityServiceConnection connection) {}
|
||||
public void setConnection(IAccessibilityServiceConnection connection,
|
||||
int connectionId) {
|
||||
sConnectionId = connectionId;
|
||||
if (connection != null) {
|
||||
AccessibilityInteractionClient.getInstance().addConnection(connectionId,
|
||||
connection);
|
||||
} else {
|
||||
AccessibilityInteractionClient.getInstance().removeConnection(connectionId);
|
||||
}
|
||||
synchronized (this) {
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public void onInterrupt() {}
|
||||
|
||||
public void onAccessibilityEvent(AccessibilityEvent event) {
|
||||
if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED) {
|
||||
sLastFocusAccessibilityEvent = AccessibilityEvent.obtain(event);
|
||||
}
|
||||
synchronized (sConnection) {
|
||||
sConnection.notifyAll();
|
||||
mLastAccessibilityEvent = AccessibilityEvent.obtain(event);
|
||||
synchronized (this) {
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -485,28 +470,11 @@ public class InterrogationActivityTest
|
||||
AccessibilityManager.getInstance(getInstrumentation().getContext());
|
||||
|
||||
synchronized (this) {
|
||||
if (!accessibilityManager.isEnabled()) {
|
||||
// Make sure we wake ourselves as the desired state is propagated.
|
||||
accessibilityManager.addAccessibilityStateChangeListener(
|
||||
new AccessibilityManager.AccessibilityStateChangeListener() {
|
||||
public void onAccessibilityStateChanged(boolean enabled) {
|
||||
synchronized (this) {
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
});
|
||||
IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
|
||||
IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
|
||||
ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
|
||||
sConnection = manager.registerEventListener(listener);
|
||||
|
||||
wait(TIMEOUT_PROPAGATE_ACCESSIBLITY_SETTING);
|
||||
} else {
|
||||
IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
|
||||
ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
|
||||
sConnection = manager.registerEventListener(listener);
|
||||
}
|
||||
manager.registerEventListener(listener);
|
||||
wait(TIMEOUT_PROPAGATE_ACCESSIBLITY_SETTING);
|
||||
}
|
||||
}
|
||||
return sConnection;
|
||||
}
|
||||
}
|
||||
|
@ -115,8 +115,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
|
||||
|
||||
private final Set<ComponentName> mEnabledServices = new HashSet<ComponentName>();
|
||||
|
||||
private final SparseArray<IAccessibilityInteractionConnection> mWindowIdToInteractionConnectionMap =
|
||||
new SparseArray<IAccessibilityInteractionConnection>();
|
||||
private final SparseArray<AccessibilityConnectionWrapper> mWindowIdToInteractionConnectionWrapperMap =
|
||||
new SparseArray<AccessibilityConnectionWrapper>();
|
||||
|
||||
private final SparseArray<IBinder> mWindowIdToWindowTokenMap = new SparseArray<IBinder>();
|
||||
|
||||
@ -439,16 +439,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
|
||||
final IWindow addedWindowToken = windowToken;
|
||||
final IAccessibilityInteractionConnection addedConnection = connection;
|
||||
final int windowId = sNextWindowId++;
|
||||
addedConnection.asBinder().linkToDeath(new DeathRecipient() {
|
||||
public void binderDied() {
|
||||
synchronized (mLock) {
|
||||
addedConnection.asBinder().unlinkToDeath(this, 0);
|
||||
removeAccessibilityInteractionConnection(addedWindowToken);
|
||||
}
|
||||
}
|
||||
}, 0);
|
||||
AccessibilityConnectionWrapper wrapper = new AccessibilityConnectionWrapper(windowId,
|
||||
connection);
|
||||
wrapper.linkToDeath();
|
||||
mWindowIdToWindowTokenMap.put(windowId, addedWindowToken.asBinder());
|
||||
mWindowIdToInteractionConnectionMap.put(windowId, connection);
|
||||
mWindowIdToInteractionConnectionWrapperMap.put(windowId, wrapper);
|
||||
if (DEBUG) {
|
||||
Slog.i(LOG_TAG, "Adding interaction connection to windowId: " + windowId);
|
||||
}
|
||||
@ -462,18 +457,17 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (mWindowIdToWindowTokenMap.valueAt(i) == windowToken.asBinder()) {
|
||||
final int windowId = mWindowIdToWindowTokenMap.keyAt(i);
|
||||
mWindowIdToWindowTokenMap.remove(windowId);
|
||||
mWindowIdToInteractionConnectionMap.remove(windowId);
|
||||
if (DEBUG) {
|
||||
Slog.i(LOG_TAG, "Removing interaction connection to windowId: " + windowId);
|
||||
}
|
||||
AccessibilityConnectionWrapper wrapper =
|
||||
mWindowIdToInteractionConnectionWrapperMap.get(windowId);
|
||||
wrapper.unlinkToDeath();
|
||||
removeAccessibilityInteractionConnectionLocked(windowId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IAccessibilityServiceConnection registerEventListener(IEventListener listener) {
|
||||
public void registerEventListener(IEventListener listener) {
|
||||
mSecurityPolicy.enforceCallingPermission(Manifest.permission.RETRIEVE_WINDOW_CONTENT,
|
||||
FUNCTION_REGISTER_EVENT_LISTENER);
|
||||
ComponentName componentName = new ComponentName("foo.bar",
|
||||
@ -501,7 +495,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
|
||||
accessibilityServiceInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
|
||||
Service service = new Service(componentName, accessibilityServiceInfo, true);
|
||||
service.onServiceConnected(componentName, listener.asBinder());
|
||||
return service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an AccessibilityInteractionConnection.
|
||||
*
|
||||
* @param windowId The id of the window to which the connection is targeted.
|
||||
*/
|
||||
private void removeAccessibilityInteractionConnectionLocked(int windowId) {
|
||||
mWindowIdToWindowTokenMap.remove(windowId);
|
||||
mWindowIdToInteractionConnectionWrapperMap.remove(windowId);
|
||||
if (DEBUG) {
|
||||
Slog.i(LOG_TAG, "Removing interaction connection to windowId: " + windowId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -594,6 +600,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
|
||||
*/
|
||||
private void notifyEventListenerLocked(Service service, int eventType) {
|
||||
IEventListener listener = service.mServiceInterface;
|
||||
|
||||
// If the service died/was disabled while the message for dispatching
|
||||
// the accessibility event was propagating the listener may be null.
|
||||
if (listener == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
AccessibilityEvent event = service.mPendingEvents.get(eventType);
|
||||
|
||||
// Check for null here because there is a concurrent scenario in which this
|
||||
@ -618,7 +631,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
|
||||
service.mPendingEvents.remove(eventType);
|
||||
try {
|
||||
if (mSecurityPolicy.canRetrieveWindowContent(service)) {
|
||||
event.setConnection(service);
|
||||
event.setConnectionId(service.mId);
|
||||
} else {
|
||||
event.setSource(null);
|
||||
}
|
||||
@ -666,6 +679,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
|
||||
mComponentNameToServiceMap.remove(service.mComponentName);
|
||||
mHandler.removeMessages(service.mId);
|
||||
service.unlinkToOwnDeath();
|
||||
service.dispose();
|
||||
updateInputFilterLocked();
|
||||
return removed;
|
||||
}
|
||||
@ -895,6 +909,33 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
|
||||
sendStateToClientsLocked();
|
||||
}
|
||||
|
||||
private class AccessibilityConnectionWrapper implements DeathRecipient {
|
||||
private final int mWindowId;
|
||||
private final IAccessibilityInteractionConnection mConnection;
|
||||
|
||||
public AccessibilityConnectionWrapper(int windowId,
|
||||
IAccessibilityInteractionConnection connection) {
|
||||
mWindowId = windowId;
|
||||
mConnection = connection;
|
||||
}
|
||||
|
||||
public void linkToDeath() throws RemoteException {
|
||||
mConnection.asBinder().linkToDeath(this, 0);
|
||||
}
|
||||
|
||||
public void unlinkToDeath() {
|
||||
mConnection.asBinder().unlinkToDeath(this, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void binderDied() {
|
||||
unlinkToDeath();
|
||||
synchronized (mLock) {
|
||||
removeAccessibilityInteractionConnectionLocked(mWindowId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class represents an accessibility service. It stores all per service
|
||||
* data required for the service management, provides API for starting/stopping the
|
||||
@ -997,7 +1038,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
|
||||
if (!mIsAutomation) {
|
||||
mContext.unbindService(this);
|
||||
}
|
||||
mService = null;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -1021,7 +1061,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
|
||||
mService = service;
|
||||
mServiceInterface = IEventListener.Stub.asInterface(service);
|
||||
try {
|
||||
mServiceInterface.setConnection(this);
|
||||
mServiceInterface.setConnection(this, mId);
|
||||
synchronized (mLock) {
|
||||
tryAddServiceLocked(this);
|
||||
}
|
||||
@ -1123,14 +1163,16 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
|
||||
if (!permissionGranted) {
|
||||
return 0;
|
||||
} else {
|
||||
connection = mWindowIdToInteractionConnectionMap.get(accessibilityWindowId);
|
||||
if (connection == null) {
|
||||
AccessibilityConnectionWrapper wrapper =
|
||||
mWindowIdToInteractionConnectionWrapperMap.get(accessibilityWindowId);
|
||||
if (wrapper == null) {
|
||||
if (DEBUG) {
|
||||
Slog.e(LOG_TAG, "No interaction connection to window: "
|
||||
+ accessibilityWindowId);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
connection = wrapper.mConnection;
|
||||
}
|
||||
}
|
||||
final int interrogatingPid = Binder.getCallingPid();
|
||||
@ -1159,14 +1201,16 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
|
||||
if (!permissionGranted) {
|
||||
return false;
|
||||
} else {
|
||||
connection = mWindowIdToInteractionConnectionMap.get(accessibilityWindowId);
|
||||
if (connection == null) {
|
||||
AccessibilityConnectionWrapper wrapper =
|
||||
mWindowIdToInteractionConnectionWrapperMap.get(accessibilityWindowId);
|
||||
if (wrapper == null) {
|
||||
if (DEBUG) {
|
||||
Slog.e(LOG_TAG, "No interaction connection to window: "
|
||||
+ accessibilityWindowId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
connection = wrapper.mConnection;
|
||||
}
|
||||
}
|
||||
final int interrogatingPid = Binder.getCallingPid();
|
||||
@ -1197,9 +1241,21 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
|
||||
mService.unlinkToDeath(this, 0);
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
try {
|
||||
// Clear the proxy in the other process so this
|
||||
// IAccessibilityServiceConnection can be garbage collected.
|
||||
mServiceInterface.setConnection(null, mId);
|
||||
} catch (RemoteException re) {
|
||||
/* ignore */
|
||||
}
|
||||
mService = null;
|
||||
mServiceInterface = null;
|
||||
}
|
||||
|
||||
public void binderDied() {
|
||||
synchronized (mLock) {
|
||||
mService.unlinkToDeath(this, 0);
|
||||
unlinkToOwnDeath();
|
||||
tryRemoveServiceLocked(this);
|
||||
// We no longer have an automation service, so restore
|
||||
// the state based on values in the settings database.
|
||||
@ -1214,7 +1270,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
|
||||
if (DEBUG) {
|
||||
Slog.i(LOG_TAG, "Trying to get interaction connection to windowId: " + windowId);
|
||||
}
|
||||
return mWindowIdToInteractionConnectionMap.get(windowId);
|
||||
AccessibilityConnectionWrapper wrapper =
|
||||
mWindowIdToInteractionConnectionWrapperMap.get(windowId);
|
||||
return (wrapper != null) ? wrapper.mConnection : null;
|
||||
}
|
||||
|
||||
private float getCompatibilityScale(int windowId) {
|
||||
|
Reference in New Issue
Block a user