Merge "Add and implement cache API"

This commit is contained in:
Sally Yuen
2022-01-05 20:11:36 +00:00
committed by Android (Google) Code Review
8 changed files with 277 additions and 5 deletions

View File

@@ -3014,6 +3014,8 @@ package android.accessibilityservice {
public abstract class AccessibilityService extends android.app.Service {
ctor public AccessibilityService();
method public boolean clearCache();
method public boolean clearCachedSubtree(@NonNull android.view.accessibility.AccessibilityNodeInfo);
method public final void disableSelf();
method public final boolean dispatchGesture(@NonNull android.accessibilityservice.GestureDescription, @Nullable android.accessibilityservice.AccessibilityService.GestureResultCallback, @Nullable android.os.Handler);
method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
@@ -3028,6 +3030,8 @@ package android.accessibilityservice {
method @NonNull public final android.accessibilityservice.TouchInteractionController getTouchInteractionController(int);
method public java.util.List<android.view.accessibility.AccessibilityWindowInfo> getWindows();
method @NonNull public final android.util.SparseArray<java.util.List<android.view.accessibility.AccessibilityWindowInfo>> getWindowsOnAllDisplays();
method public boolean isCacheEnabled();
method public boolean isNodeInCache(@NonNull android.view.accessibility.AccessibilityNodeInfo);
method public abstract void onAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
method public final android.os.IBinder onBind(android.content.Intent);
method @Deprecated protected boolean onGesture(int);
@@ -3038,6 +3042,7 @@ package android.accessibilityservice {
method public void onSystemActionsChanged();
method public final boolean performGlobalAction(int);
method public void setAccessibilityFocusAppearance(int, @ColorInt int);
method public boolean setCacheEnabled(boolean);
method public void setGestureDetectionPassthroughRegion(int, @NonNull android.graphics.Region);
method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo);
method public void setTouchExplorationPassthroughRegion(int, @NonNull android.graphics.Region);

View File

@@ -59,6 +59,7 @@ import android.view.MotionEvent;
import android.view.SurfaceView;
import android.view.WindowManager;
import android.view.WindowManagerImpl;
import android.view.accessibility.AccessibilityCache;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityInteractionClient;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -748,7 +749,6 @@ public abstract class AccessibilityService extends Service {
private FingerprintGestureController mFingerprintGestureController;
/**
* Callback for {@link android.view.accessibility.AccessibilityEvent}s.
*
@@ -2076,6 +2076,85 @@ public abstract class AccessibilityService extends Service {
available);
}
/** Sets the cache status.
*
* <p>If {@code enabled}, enable the cache and prefetching. Otherwise, disable the cache
* and prefetching.
* Note: By default the cache is enabled.
* @param enabled whether to enable or disable the cache.
* @return {@code true} if the cache and connection are not null, so the cache status is set.
*/
public boolean setCacheEnabled(boolean enabled) {
AccessibilityCache cache =
AccessibilityInteractionClient.getCache(mConnectionId);
if (cache == null) {
return false;
}
final IAccessibilityServiceConnection connection =
AccessibilityInteractionClient.getConnection(mConnectionId);
if (connection == null) {
return false;
}
try {
connection.setCacheEnabled(enabled);
cache.setEnabled(enabled);
return true;
} catch (RemoteException re) {
Log.w(LOG_TAG, "Error while setting status of cache", re);
re.rethrowFromSystemServer();
}
return false;
}
/** Invalidates {@code node} and its subtree in the cache.
* @param node the node to invalidate.
* @return {@code true} if the subtree rooted at {@code node} was invalidated.
*/
public boolean clearCachedSubtree(@NonNull AccessibilityNodeInfo node) {
AccessibilityCache cache =
AccessibilityInteractionClient.getCache(mConnectionId);
if (cache == null) {
return false;
}
return cache.clearSubTree(node);
}
/** Clears the cache.
* @return {@code true} if the cache was cleared
*/
public boolean clearCache() {
AccessibilityCache cache =
AccessibilityInteractionClient.getCache(mConnectionId);
if (cache == null) {
return false;
}
cache.clear();
return true;
}
/** Checks if {@code node} is in the cache.
* @param node the node to check.
* @return {@code true} if {@code node} is in the cache.
*/
public boolean isNodeInCache(@NonNull AccessibilityNodeInfo node) {
AccessibilityCache cache =
AccessibilityInteractionClient.getCache(mConnectionId);
if (cache == null) {
return false;
}
return cache.isNodeInCache(node);
}
/** Returns {@code true} if the cache is enabled. */
public boolean isCacheEnabled() {
AccessibilityCache cache =
AccessibilityInteractionClient.getCache(mConnectionId);
if (cache == null) {
return false;
}
return cache.isEnabled();
}
/** This is called when the system action list is changed. */
public void onSystemActionsChanged() {
}
@@ -2613,11 +2692,11 @@ public abstract class AccessibilityService extends Service {
mCallback.init(mConnectionId, windowToken);
mCallback.onServiceConnected();
} else {
AccessibilityInteractionClient.getInstance(mContext)
.clearCache(mConnectionId);
AccessibilityInteractionClient.getInstance(mContext).removeConnection(
mConnectionId);
mConnectionId = AccessibilityInteractionClient.NO_ID;
AccessibilityInteractionClient.getInstance(mContext)
.clearCache(mConnectionId);
mCallback.init(AccessibilityInteractionClient.NO_ID, null);
}
return;

View File

@@ -124,6 +124,8 @@ interface IAccessibilityServiceConnection {
void setFocusAppearance(int strokeWidth, int color);
void setCacheEnabled(boolean enabled);
oneway void logTrace(long timestamp, String where, long loggingTypes, String callingParams,
int processId, long threadId, int callingUid, in Bundle serializedCallingStackInBundle);

View File

@@ -46,6 +46,8 @@ public class AccessibilityCache {
private static final boolean CHECK_INTEGRITY = Build.IS_ENG;
private boolean mEnabled = true;
/**
* {@link AccessibilityEvent} types that are critical for the cache to stay up to date
*
@@ -98,6 +100,21 @@ public class AccessibilityCache {
mAccessibilityNodeRefresher = nodeRefresher;
}
/** Returns if the cache is enabled. */
public boolean isEnabled() {
synchronized (mLock) {
return mEnabled;
}
}
/** Sets enabled status. */
public void setEnabled(boolean enabled) {
synchronized (mLock) {
mEnabled = enabled;
clear();
}
}
/**
* Sets all {@link AccessibilityWindowInfo}s of all displays into the cache.
* The key of SparseArray is display ID.
@@ -110,6 +127,12 @@ public class AccessibilityCache {
SparseArray<List<AccessibilityWindowInfo>> windowsOnAllDisplays,
long populationTimeStamp) {
synchronized (mLock) {
if (!mEnabled) {
if (DEBUG) {
Log.i(LOG_TAG, "Cache is disabled");
}
return;
}
if (DEBUG) {
Log.i(LOG_TAG, "Set windows");
}
@@ -148,6 +171,12 @@ public class AccessibilityCache {
*/
public void addWindow(AccessibilityWindowInfo window) {
synchronized (mLock) {
if (!mEnabled) {
if (DEBUG) {
Log.i(LOG_TAG, "Cache is disabled");
}
return;
}
if (DEBUG) {
Log.i(LOG_TAG, "Caching window: " + window.getId() + " at display Id [ "
+ window.getDisplayId() + " ]");
@@ -177,6 +206,12 @@ public class AccessibilityCache {
public void onAccessibilityEvent(AccessibilityEvent event) {
AccessibilityNodeInfo nodeToRefresh = null;
synchronized (mLock) {
if (!mEnabled) {
if (DEBUG) {
Log.i(LOG_TAG, "Cache is disabled");
}
return;
}
if (DEBUG) {
Log.i(LOG_TAG, "onAccessibilityEvent(" + event + ")");
}
@@ -292,6 +327,12 @@ public class AccessibilityCache {
*/
public AccessibilityNodeInfo getNode(int windowId, long accessibilityNodeId) {
synchronized(mLock) {
if (!mEnabled) {
if (DEBUG) {
Log.i(LOG_TAG, "Cache is disabled");
}
return null;
}
LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId);
if (nodes == null) {
return null;
@@ -309,6 +350,28 @@ public class AccessibilityCache {
}
}
/** Returns {@code true} if {@code info} is in the cache. */
public boolean isNodeInCache(AccessibilityNodeInfo info) {
if (info == null) {
return false;
}
int windowId = info.getWindowId();
long accessibilityNodeId = info.getSourceNodeId();
synchronized (mLock) {
if (!mEnabled) {
if (DEBUG) {
Log.i(LOG_TAG, "Cache is disabled");
}
return false;
}
LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId);
if (nodes == null) {
return false;
}
return nodes.get(accessibilityNodeId) != null;
}
}
/**
* Gets all {@link AccessibilityWindowInfo}s of all displays from the cache.
*
@@ -317,6 +380,12 @@ public class AccessibilityCache {
*/
public SparseArray<List<AccessibilityWindowInfo>> getWindowsOnAllDisplays() {
synchronized (mLock) {
if (!mEnabled) {
if (DEBUG) {
Log.i(LOG_TAG, "Cache is disabled");
}
return null;
}
if (!mIsAllWindowsCached) {
return null;
}
@@ -373,6 +442,12 @@ public class AccessibilityCache {
*/
public AccessibilityWindowInfo getWindow(int windowId) {
synchronized (mLock) {
if (!mEnabled) {
if (DEBUG) {
Log.i(LOG_TAG, "Cache is disabled");
}
return null;
}
final int displayCounts = mWindowCacheByDisplay.size();
for (int i = 0; i < displayCounts; i++) {
final SparseArray<AccessibilityWindowInfo> windowsOfDisplay =
@@ -397,6 +472,12 @@ public class AccessibilityCache {
*/
public void add(AccessibilityNodeInfo info) {
synchronized(mLock) {
if (!mEnabled) {
if (DEBUG) {
Log.i(LOG_TAG, "Cache is disabled");
}
return;
}
if (VERBOSE) {
Log.i(LOG_TAG, "add(" + info + ")");
}
@@ -522,6 +603,12 @@ public class AccessibilityCache {
*/
public AccessibilityNodeInfo getFocus(int focusType, long initialNodeId, int windowId) {
synchronized (mLock) {
if (!mEnabled) {
if (DEBUG) {
Log.i(LOG_TAG, "Cache is disabled");
}
return null;
}
int currentFocusWindowId;
long currentFocusId;
if (focusType == FOCUS_ACCESSIBILITY) {
@@ -602,6 +689,23 @@ public class AccessibilityCache {
mNodeCache.remove(windowId);
}
/** Clears a subtree rooted at {@code info}. */
public boolean clearSubTree(AccessibilityNodeInfo info) {
if (info == null) {
return false;
}
synchronized (mLock) {
if (!mEnabled) {
if (DEBUG) {
Log.i(LOG_TAG, "Cache is disabled");
}
return false;
}
clearSubTreeLocked(info.getWindowId(), info.getSourceNodeId());
return true;
}
}
/**
* Clears a subtree rooted at the node with the given id that is
* hosted in a given window.

View File

@@ -18,6 +18,7 @@ package android.view.accessibility;
import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CLIENT;
import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK;
import static android.os.Build.VERSION_CODES.S;
import android.accessibilityservice.IAccessibilityServiceConnection;
import android.annotation.NonNull;
@@ -225,6 +226,9 @@ public final class AccessibilityInteractionClient
*/
public static void addConnection(int connectionId, IAccessibilityServiceConnection connection,
boolean initializeCache) {
if (connectionId == NO_ID) {
return;
}
synchronized (sConnectionCache) {
sConnectionCache.put(connectionId, connection);
if (!initializeCache) {
@@ -554,6 +558,10 @@ public final class AccessibilityInteractionClient
}
return cachedInfo;
}
if (!cache.isEnabled()) {
// Skip prefetching if cache is disabled.
prefetchFlags &= ~AccessibilityNodeInfo.FLAG_PREFETCH_MASK;
}
if (DEBUG) {
Log.i(LOG_TAG, "Node cache miss for "
+ idToString(accessibilityWindowId, accessibilityNodeId));
@@ -970,9 +978,9 @@ public final class AccessibilityInteractionClient
/**
* Clears the cache associated with {@code connectionId}
* @param connectionId the connection id
* TODO(207417185): Modify UnsupportedAppUsage
*/
@UnsupportedAppUsage()
@UnsupportedAppUsage(maxTargetSdk = S, publicAlternatives =
"{@link android.accessibilityservice.AccessibilityService#clearCache()}")
public void clearCache(int connectionId) {
AccessibilityCache cache = getCache(connectionId);
if (cache == null) {

View File

@@ -26,6 +26,8 @@ import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyObject;
@@ -997,6 +999,60 @@ public class AccessibilityCacheTest {
}
}
@Test
public void enable_cacheEnabled() {
mAccessibilityCache.setEnabled(false);
assertFalse(mAccessibilityCache.isEnabled());
mAccessibilityCache.setEnabled(true);
assertTrue(mAccessibilityCache.isEnabled());
}
@Test
public void disable_cacheDisabled() {
mAccessibilityCache.setEnabled(false);
assertFalse(mAccessibilityCache.isEnabled());
}
@Test
public void queryNode_nodeIsInCache() {
AccessibilityNodeInfo info = new AccessibilityNodeInfo();
mAccessibilityCache.add(info);
assertTrue(mAccessibilityCache.isNodeInCache(info));
}
@Test
public void clearSubtreeWithNode_nodeInCacheInvalidated() {
AccessibilityNodeInfo info = new AccessibilityNodeInfo();
info.setSource(getMockViewWithA11yAndWindowIds(1, 1));
mAccessibilityCache.add(info);
mAccessibilityCache.clearSubTree(info);
assertFalse(mAccessibilityCache.isNodeInCache(info));
}
@Test
public void clearSubtreeWithNode_subtreeInCacheInvalidated() {
AccessibilityNodeInfo info = new AccessibilityNodeInfo();
View parentView = getMockViewWithA11yAndWindowIds(1, 1);
info.setSource(parentView);
AccessibilityNodeInfo childInfo = new AccessibilityNodeInfo();
View childView = getMockViewWithA11yAndWindowIds(2, 1);
childInfo.setSource(childView);
childInfo.setParent(parentView);
info.addChild(childView);
mAccessibilityCache.add(info);
mAccessibilityCache.add(childInfo);
mAccessibilityCache.clearSubTree(info);
assertFalse(mAccessibilityCache.isNodeInCache(info));
assertFalse(mAccessibilityCache.isNodeInCache(childInfo));
}
private AccessibilityWindowInfo obtainAccessibilityWindowInfo(int windowId, int layer) {
AccessibilityWindowInfo windowInfo = AccessibilityWindowInfo.obtain();
windowInfo.setId(windowId);

View File

@@ -173,6 +173,8 @@ public class AccessibilityServiceConnectionImpl extends IAccessibilityServiceCon
public void setFocusAppearance(int strokeWidth, int color) {}
public void setCacheEnabled(boolean enabled) {}
public void logTrace(long timestamp, String where, String callingParams, int processId,
long threadId, int callingUid, Bundle callingStack) {}

View File

@@ -2032,6 +2032,22 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
}
}
@Override
public void setCacheEnabled(boolean enabled) {
if (svcConnTracingEnabled()) {
logTraceSvcConn("setCacheEnabled", "enabled=" + enabled);
}
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
mUsesAccessibilityCache = enabled;
mSystemSupport.onClientChangeLocked(true);
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public void logTrace(long timestamp, String where, long loggingTypes, String callingParams,
int processId, long threadId, int callingUid, Bundle callingStack) {