Merge "[FUI18] Expose notifyNetworkStatus as system API" into sc-dev
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index a1ba3b7..09452828 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -86,19 +86,12 @@
// accessibility from hanging
private static final long REQUEST_PREPARER_TIMEOUT_MS = 500;
- // Callbacks should have the same configuration of the flags below to allow satisfying a pending
- // node request on prefetch
- private static final int FLAGS_AFFECTING_REPORTED_DATA =
- AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
- | AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS;
-
private final ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList =
new ArrayList<AccessibilityNodeInfo>();
private final Object mLock = new Object();
- @VisibleForTesting
- public final PrivateHandler mHandler;
+ private final PrivateHandler mHandler;
private final ViewRootImpl mViewRootImpl;
@@ -121,9 +114,6 @@
private AddNodeInfosForViewId mAddNodeInfosForViewId;
@GuardedBy("mLock")
- private ArrayList<Message> mPendingFindNodeByIdMessages;
-
- @GuardedBy("mLock")
private int mNumActiveRequestPreparers;
@GuardedBy("mLock")
private List<MessageHolder> mMessagesWaitingForRequestPreparer;
@@ -138,7 +128,6 @@
mViewRootImpl = viewRootImpl;
mPrefetcher = new AccessibilityNodePrefetcher();
mA11yManager = mViewRootImpl.mContext.getSystemService(AccessibilityManager.class);
- mPendingFindNodeByIdMessages = new ArrayList<>();
}
private void scheduleMessage(Message message, int interrogatingPid, long interrogatingTid,
@@ -188,9 +177,6 @@
args.arg4 = arguments;
message.obj = args;
- synchronized (mLock) {
- mPendingFindNodeByIdMessages.add(message);
- }
scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
}
@@ -329,9 +315,6 @@
}
private void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) {
- synchronized (mLock) {
- mPendingFindNodeByIdMessages.remove(message);
- }
final int flags = message.arg1;
SomeArgs args = (SomeArgs) message.obj;
@@ -346,58 +329,22 @@
args.recycle();
- View rootView = null;
- AccessibilityNodeInfo rootNode = null;
+ List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
+ infos.clear();
try {
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
- rootView = findViewByAccessibilityId(accessibilityViewId);
- if (rootView != null && isShown(rootView)) {
- rootNode = populateAccessibilityNodeInfoForView(
- rootView, arguments, virtualDescendantId);
+ final View root = findViewByAccessibilityId(accessibilityViewId);
+ if (root != null && isShown(root)) {
+ mPrefetcher.prefetchAccessibilityNodeInfos(
+ root, virtualDescendantId, flags, infos, arguments);
}
} finally {
- updateInfoForViewportAndReturnFindNodeResult(
- rootNode == null ? null : AccessibilityNodeInfo.obtain(rootNode),
- callback, interactionId, spec, interactiveRegion);
+ updateInfosForViewportAndReturnFindNodeResult(
+ infos, callback, interactionId, spec, interactiveRegion);
}
- ArrayList<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
- infos.clear();
- mPrefetcher.prefetchAccessibilityNodeInfos(
- rootView, rootNode == null ? null : AccessibilityNodeInfo.obtain(rootNode),
- virtualDescendantId, flags, infos);
- mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
- updateInfosForViewPort(infos, spec, interactiveRegion);
- returnPrefetchResult(interactionId, infos, callback);
- returnPendingFindAccessibilityNodeInfosInPrefetch(rootNode, infos, flags);
- }
-
- private AccessibilityNodeInfo populateAccessibilityNodeInfoForView(
- View view, Bundle arguments, int virtualViewId) {
- AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
- // Determine if we'll be populating extra data
- final String extraDataRequested = (arguments == null) ? null
- : arguments.getString(EXTRA_DATA_REQUESTED_KEY);
- AccessibilityNodeInfo root = null;
- if (provider == null) {
- root = view.createAccessibilityNodeInfo();
- if (root != null) {
- if (extraDataRequested != null) {
- view.addExtraDataToAccessibilityNodeInfo(root, extraDataRequested, arguments);
- }
- }
- } else {
- root = provider.createAccessibilityNodeInfo(virtualViewId);
- if (root != null) {
- if (extraDataRequested != null) {
- provider.addExtraDataToAccessibilityNodeInfo(
- virtualViewId, root, extraDataRequested, arguments);
- }
- }
- }
- return root;
}
public void findAccessibilityNodeInfosByViewIdClientThread(long accessibilityNodeId,
@@ -456,7 +403,6 @@
mAddNodeInfosForViewId.reset();
}
} finally {
- mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
updateInfosForViewportAndReturnFindNodeResult(
infos, callback, interactionId, spec, interactiveRegion);
}
@@ -539,7 +485,6 @@
}
}
} finally {
- mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
updateInfosForViewportAndReturnFindNodeResult(
infos, callback, interactionId, spec, interactiveRegion);
}
@@ -631,7 +576,6 @@
}
}
} finally {
- mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
updateInfoForViewportAndReturnFindNodeResult(
focused, callback, interactionId, spec, interactiveRegion);
}
@@ -686,7 +630,6 @@
}
}
} finally {
- mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
updateInfoForViewportAndReturnFindNodeResult(
next, callback, interactionId, spec, interactiveRegion);
}
@@ -843,6 +786,33 @@
}
}
+ private void applyAppScaleAndMagnificationSpecIfNeeded(List<AccessibilityNodeInfo> infos,
+ MagnificationSpec spec) {
+ if (infos == null) {
+ return;
+ }
+ final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
+ if (shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) {
+ final int infoCount = infos.size();
+ for (int i = 0; i < infoCount; i++) {
+ AccessibilityNodeInfo info = infos.get(i);
+ applyAppScaleAndMagnificationSpecIfNeeded(info, spec);
+ }
+ }
+ }
+
+ private void adjustIsVisibleToUserIfNeeded(List<AccessibilityNodeInfo> infos,
+ Region interactiveRegion) {
+ if (interactiveRegion == null || infos == null) {
+ return;
+ }
+ final int infoCount = infos.size();
+ for (int i = 0; i < infoCount; i++) {
+ AccessibilityNodeInfo info = infos.get(i);
+ adjustIsVisibleToUserIfNeeded(info, interactiveRegion);
+ }
+ }
+
private void adjustIsVisibleToUserIfNeeded(AccessibilityNodeInfo info,
Region interactiveRegion) {
if (interactiveRegion == null || info == null) {
@@ -863,6 +833,17 @@
return false;
}
+ private void adjustBoundsInScreenIfNeeded(List<AccessibilityNodeInfo> infos) {
+ if (infos == null || shouldBypassAdjustBoundsInScreen()) {
+ return;
+ }
+ final int infoCount = infos.size();
+ for (int i = 0; i < infoCount; i++) {
+ final AccessibilityNodeInfo info = infos.get(i);
+ adjustBoundsInScreenIfNeeded(info);
+ }
+ }
+
private void adjustBoundsInScreenIfNeeded(AccessibilityNodeInfo info) {
if (info == null || shouldBypassAdjustBoundsInScreen()) {
return;
@@ -910,6 +891,17 @@
return screenMatrix == null || screenMatrix.isIdentity();
}
+ private void associateLeashedParentIfNeeded(List<AccessibilityNodeInfo> infos) {
+ if (infos == null || shouldBypassAssociateLeashedParent()) {
+ return;
+ }
+ final int infoCount = infos.size();
+ for (int i = 0; i < infoCount; i++) {
+ final AccessibilityNodeInfo info = infos.get(i);
+ associateLeashedParentIfNeeded(info);
+ }
+ }
+
private void associateLeashedParentIfNeeded(AccessibilityNodeInfo info) {
if (info == null || shouldBypassAssociateLeashedParent()) {
return;
@@ -983,46 +975,18 @@
return (appScale != 1.0f || (spec != null && !spec.isNop()));
}
- private void updateInfosForViewPort(List<AccessibilityNodeInfo> infos, MagnificationSpec spec,
- Region interactiveRegion) {
- for (int i = 0; i < infos.size(); i++) {
- updateInfoForViewPort(infos.get(i), spec, interactiveRegion);
- }
- }
-
- private void updateInfoForViewPort(AccessibilityNodeInfo info, MagnificationSpec spec,
- Region interactiveRegion) {
- associateLeashedParentIfNeeded(info);
- applyScreenMatrixIfNeeded(info);
- adjustBoundsInScreenIfNeeded(info);
- // To avoid applyAppScaleAndMagnificationSpecIfNeeded changing the bounds of node,
- // then impact the visibility result, we need to adjust visibility before apply scale.
- adjustIsVisibleToUserIfNeeded(info, interactiveRegion);
- applyAppScaleAndMagnificationSpecIfNeeded(info, spec);
- }
-
private void updateInfosForViewportAndReturnFindNodeResult(List<AccessibilityNodeInfo> infos,
IAccessibilityInteractionConnectionCallback callback, int interactionId,
MagnificationSpec spec, Region interactiveRegion) {
- if (infos != null) {
- updateInfosForViewPort(infos, spec, interactiveRegion);
- }
- returnFindNodesResult(infos, callback, interactionId);
- }
-
- private void returnFindNodeResult(AccessibilityNodeInfo info,
- IAccessibilityInteractionConnectionCallback callback,
- int interactionId) {
try {
- callback.setFindAccessibilityNodeInfoResult(info, interactionId);
- } catch (RemoteException re) {
- /* ignore - the other side will time out */
- }
- }
-
- private void returnFindNodesResult(List<AccessibilityNodeInfo> infos,
- IAccessibilityInteractionConnectionCallback callback, int interactionId) {
- try {
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+ associateLeashedParentIfNeeded(infos);
+ applyScreenMatrixIfNeeded(infos);
+ adjustBoundsInScreenIfNeeded(infos);
+ // To avoid applyAppScaleAndMagnificationSpecIfNeeded changing the bounds of node,
+ // then impact the visibility result, we need to adjust visibility before apply scale.
+ adjustIsVisibleToUserIfNeeded(infos, interactiveRegion);
+ applyAppScaleAndMagnificationSpecIfNeeded(infos, spec);
callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
if (infos != null) {
infos.clear();
@@ -1032,80 +996,22 @@
}
}
- private void returnPendingFindAccessibilityNodeInfosInPrefetch(AccessibilityNodeInfo rootNode,
- List<AccessibilityNodeInfo> infos, int flags) {
-
- AccessibilityNodeInfo satisfiedPendingRequestPrefetchedNode = null;
- IAccessibilityInteractionConnectionCallback satisfiedPendingRequestCallback = null;
- int satisfiedPendingRequestInteractionId = AccessibilityInteractionClient.NO_ID;
-
- synchronized (mLock) {
- for (int i = 0; i < mPendingFindNodeByIdMessages.size(); i++) {
- final Message pendingMessage = mPendingFindNodeByIdMessages.get(i);
- final int pendingFlags = pendingMessage.arg1;
- if ((pendingFlags & FLAGS_AFFECTING_REPORTED_DATA)
- != (flags & FLAGS_AFFECTING_REPORTED_DATA)) {
- continue;
- }
- SomeArgs args = (SomeArgs) pendingMessage.obj;
- final int accessibilityViewId = args.argi1;
- final int virtualDescendantId = args.argi2;
-
- satisfiedPendingRequestPrefetchedNode = nodeWithIdFromList(rootNode,
- infos, AccessibilityNodeInfo.makeNodeId(
- accessibilityViewId, virtualDescendantId));
-
- if (satisfiedPendingRequestPrefetchedNode != null) {
- satisfiedPendingRequestCallback =
- (IAccessibilityInteractionConnectionCallback) args.arg1;
- satisfiedPendingRequestInteractionId = args.argi3;
- mHandler.removeMessages(
- PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID,
- pendingMessage.obj);
- args.recycle();
- break;
- }
- }
- mPendingFindNodeByIdMessages.clear();
- }
-
- if (satisfiedPendingRequestPrefetchedNode != null) {
- returnFindNodeResult(
- AccessibilityNodeInfo.obtain(satisfiedPendingRequestPrefetchedNode),
- satisfiedPendingRequestCallback, satisfiedPendingRequestInteractionId);
- }
- }
-
- private AccessibilityNodeInfo nodeWithIdFromList(AccessibilityNodeInfo rootNode,
- List<AccessibilityNodeInfo> infos, long nodeId) {
- if (rootNode != null && rootNode.getSourceNodeId() == nodeId) {
- return rootNode;
- }
- for (int j = 0; j < infos.size(); j++) {
- AccessibilityNodeInfo info = infos.get(j);
- if (info.getSourceNodeId() == nodeId) {
- return info;
- }
- }
- return null;
- }
-
- private void returnPrefetchResult(int interactionId, List<AccessibilityNodeInfo> infos,
- IAccessibilityInteractionConnectionCallback callback) {
- if (infos.size() > 0) {
- try {
- callback.setPrefetchAccessibilityNodeInfoResult(infos, interactionId);
- } catch (RemoteException re) {
- /* ignore - other side isn't too bothered if this doesn't arrive */
- }
- }
- }
-
private void updateInfoForViewportAndReturnFindNodeResult(AccessibilityNodeInfo info,
IAccessibilityInteractionConnectionCallback callback, int interactionId,
MagnificationSpec spec, Region interactiveRegion) {
- updateInfoForViewPort(info, spec, interactiveRegion);
- returnFindNodeResult(info, callback, interactionId);
+ try {
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+ associateLeashedParentIfNeeded(info);
+ applyScreenMatrixIfNeeded(info);
+ adjustBoundsInScreenIfNeeded(info);
+ // To avoid applyAppScaleAndMagnificationSpecIfNeeded changing the bounds of node,
+ // then impact the visibility result, we need to adjust visibility before apply scale.
+ adjustIsVisibleToUserIfNeeded(info, interactiveRegion);
+ applyAppScaleAndMagnificationSpecIfNeeded(info, spec);
+ callback.setFindAccessibilityNodeInfoResult(info, interactionId);
+ } catch (RemoteException re) {
+ /* ignore - the other side will time out */
+ }
}
private boolean handleClickableSpanActionUiThread(
@@ -1148,45 +1054,56 @@
private final ArrayList<View> mTempViewList = new ArrayList<View>();
- public void prefetchAccessibilityNodeInfos(View view, AccessibilityNodeInfo root,
- int virtualViewId, int fetchFlags, List<AccessibilityNodeInfo> outInfos) {
- if (root == null) {
- return;
- }
+ public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int fetchFlags,
+ List<AccessibilityNodeInfo> outInfos, Bundle arguments) {
AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
+ // Determine if we'll be populating extra data
+ final String extraDataRequested = (arguments == null) ? null
+ : arguments.getString(EXTRA_DATA_REQUESTED_KEY);
if (provider == null) {
- if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
- prefetchPredecessorsOfRealNode(view, outInfos);
- }
- if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
- prefetchSiblingsOfRealNode(view, outInfos);
- }
- if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
- prefetchDescendantsOfRealNode(view, outInfos);
+ AccessibilityNodeInfo root = view.createAccessibilityNodeInfo();
+ if (root != null) {
+ if (extraDataRequested != null) {
+ view.addExtraDataToAccessibilityNodeInfo(
+ root, extraDataRequested, arguments);
+ }
+ outInfos.add(root);
+ if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
+ prefetchPredecessorsOfRealNode(view, outInfos);
+ }
+ if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
+ prefetchSiblingsOfRealNode(view, outInfos);
+ }
+ if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
+ prefetchDescendantsOfRealNode(view, outInfos);
+ }
}
} else {
- if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
- prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos);
- }
- if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
- prefetchSiblingsOfVirtualNode(root, view, provider, outInfos);
- }
- if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
- prefetchDescendantsOfVirtualNode(root, provider, outInfos);
+ final AccessibilityNodeInfo root =
+ provider.createAccessibilityNodeInfo(virtualViewId);
+ if (root != null) {
+ if (extraDataRequested != null) {
+ provider.addExtraDataToAccessibilityNodeInfo(
+ virtualViewId, root, extraDataRequested, arguments);
+ }
+ outInfos.add(root);
+ if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
+ prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos);
+ }
+ if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
+ prefetchSiblingsOfVirtualNode(root, view, provider, outInfos);
+ }
+ if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
+ prefetchDescendantsOfVirtualNode(root, provider, outInfos);
+ }
}
}
if (ENFORCE_NODE_TREE_CONSISTENT) {
- enforceNodeTreeConsistent(root, outInfos);
+ enforceNodeTreeConsistent(outInfos);
}
}
- private boolean shouldStopPrefetching(List prefetchededInfos) {
- return mHandler.hasUserInteractiveMessagesWaiting()
- || prefetchededInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE;
- }
-
- private void enforceNodeTreeConsistent(
- AccessibilityNodeInfo root, List<AccessibilityNodeInfo> nodes) {
+ private void enforceNodeTreeConsistent(List<AccessibilityNodeInfo> nodes) {
LongSparseArray<AccessibilityNodeInfo> nodeMap =
new LongSparseArray<AccessibilityNodeInfo>();
final int nodeCount = nodes.size();
@@ -1197,6 +1114,7 @@
// If the nodes are a tree it does not matter from
// which node we start to search for the root.
+ AccessibilityNodeInfo root = nodeMap.valueAt(0);
AccessibilityNodeInfo parent = root;
while (parent != null) {
root = parent;
@@ -1263,11 +1181,9 @@
private void prefetchPredecessorsOfRealNode(View view,
List<AccessibilityNodeInfo> outInfos) {
- if (shouldStopPrefetching(outInfos)) {
- return;
- }
ViewParent parent = view.getParentForAccessibility();
- while (parent instanceof View && !shouldStopPrefetching(outInfos)) {
+ while (parent instanceof View
+ && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
View parentView = (View) parent;
AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo();
if (info != null) {
@@ -1279,9 +1195,6 @@
private void prefetchSiblingsOfRealNode(View current,
List<AccessibilityNodeInfo> outInfos) {
- if (shouldStopPrefetching(outInfos)) {
- return;
- }
ViewParent parent = current.getParentForAccessibility();
if (parent instanceof ViewGroup) {
ViewGroup parentGroup = (ViewGroup) parent;
@@ -1291,7 +1204,7 @@
parentGroup.addChildrenForAccessibility(children);
final int childCount = children.size();
for (int i = 0; i < childCount; i++) {
- if (shouldStopPrefetching(outInfos)) {
+ if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
return;
}
View child = children.get(i);
@@ -1319,7 +1232,7 @@
private void prefetchDescendantsOfRealNode(View root,
List<AccessibilityNodeInfo> outInfos) {
- if (shouldStopPrefetching(outInfos) || !(root instanceof ViewGroup)) {
+ if (!(root instanceof ViewGroup)) {
return;
}
HashMap<View, AccessibilityNodeInfo> addedChildren =
@@ -1330,7 +1243,7 @@
root.addChildrenForAccessibility(children);
final int childCount = children.size();
for (int i = 0; i < childCount; i++) {
- if (shouldStopPrefetching(outInfos)) {
+ if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
return;
}
View child = children.get(i);
@@ -1355,7 +1268,7 @@
} finally {
children.clear();
}
- if (!shouldStopPrefetching(outInfos)) {
+ if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) {
View addedChild = entry.getKey();
AccessibilityNodeInfo virtualRoot = entry.getValue();
@@ -1377,7 +1290,7 @@
long parentNodeId = root.getParentNodeId();
int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
- if (shouldStopPrefetching(outInfos)) {
+ if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
return;
}
final int virtualDescendantId =
@@ -1422,7 +1335,7 @@
if (parent != null) {
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
- if (shouldStopPrefetching(outInfos)) {
+ if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
return;
}
final long childNodeId = parent.getChildId(i);
@@ -1447,7 +1360,7 @@
final int initialOutInfosSize = outInfos.size();
final int childCount = root.getChildCount();
for (int i = 0; i < childCount; i++) {
- if (shouldStopPrefetching(outInfos)) {
+ if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
return;
}
final long childNodeId = root.getChildId(i);
@@ -1457,7 +1370,7 @@
outInfos.add(child);
}
}
- if (!shouldStopPrefetching(outInfos)) {
+ if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
final int addedChildCount = outInfos.size() - initialOutInfosSize;
for (int i = 0; i < addedChildCount; i++) {
AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i);
@@ -1566,10 +1479,6 @@
boolean hasAccessibilityCallback(Message message) {
return message.what < FIRST_NO_ACCESSIBILITY_CALLBACK_MSG ? true : false;
}
-
- boolean hasUserInteractiveMessagesWaiting() {
- return hasMessagesOrCallbacks();
- }
}
private final class AddNodeInfosForViewId implements Predicate<View> {
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 9556c25..f63749b 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -23,9 +23,7 @@
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
-import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
@@ -115,8 +113,6 @@
private final Object mInstanceLock = new Object();
- private Handler mMainHandler;
-
private volatile int mInteractionId = -1;
private AccessibilityNodeInfo mFindAccessibilityNodeInfoResult;
@@ -127,11 +123,6 @@
private Message mSameThreadMessage;
- private int mInteractionIdWaitingForPrefetchResult;
- private int mConnectionIdWaitingForPrefetchResult;
- private String[] mPackageNamesForNextPrefetchResult;
- private Runnable mPrefetchResultRunnable;
-
/**
* @return The client for the current thread.
*/
@@ -206,9 +197,6 @@
private AccessibilityInteractionClient() {
/* reducing constructor visibility */
- if (Looper.getMainLooper() != null) {
- mMainHandler = new Handler(Looper.getMainLooper());
- }
}
/**
@@ -463,16 +451,16 @@
Binder.restoreCallingIdentity(identityToken);
}
if (packageNames != null) {
- AccessibilityNodeInfo info =
- getFindAccessibilityNodeInfoResultAndClear(interactionId);
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_MASK) != 0
- && info != null) {
- setInteractionWaitingForPrefetchResult(interactionId, connectionId,
- packageNames);
- }
- finalizeAndCacheAccessibilityNodeInfo(info, connectionId,
+ List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
+ interactionId);
+ finalizeAndCacheAccessibilityNodeInfos(infos, connectionId,
bypassCache, packageNames);
- return info;
+ if (infos != null && !infos.isEmpty()) {
+ for (int i = 1; i < infos.size(); i++) {
+ infos.get(i).recycle();
+ }
+ return infos.get(0);
+ }
}
} else {
if (DEBUG) {
@@ -486,15 +474,6 @@
return null;
}
- private void setInteractionWaitingForPrefetchResult(int interactionId, int connectionId,
- String[] packageNames) {
- synchronized (mInstanceLock) {
- mInteractionIdWaitingForPrefetchResult = interactionId;
- mConnectionIdWaitingForPrefetchResult = connectionId;
- mPackageNamesForNextPrefetchResult = packageNames;
- }
- }
-
private static String idToString(int accessibilityWindowId, long accessibilityNodeId) {
return accessibilityWindowId + "/"
+ AccessibilityNodeInfo.idToString(accessibilityNodeId);
@@ -850,60 +829,6 @@
}
/**
- * {@inheritDoc}
- */
- @Override
- public void setPrefetchAccessibilityNodeInfoResult(@NonNull List<AccessibilityNodeInfo> infos,
- int interactionId) {
- List<AccessibilityNodeInfo> infosCopy = null;
- int mConnectionIdWaitingForPrefetchResultCopy = -1;
- String[] mPackageNamesForNextPrefetchResultCopy = null;
-
- synchronized (mInstanceLock) {
- if (!infos.isEmpty() && mInteractionIdWaitingForPrefetchResult == interactionId) {
- if (mMainHandler != null) {
- if (mPrefetchResultRunnable != null) {
- mMainHandler.removeCallbacks(mPrefetchResultRunnable);
- mPrefetchResultRunnable = null;
- }
- /**
- * TODO(b/180957109): AccessibilityCache is prone to deadlocks
- * We post caching the prefetched nodes in the main thread. Using the binder
- * thread results in "Long monitor contention with owner main" logs where
- * service response times may exceed 5 seconds. This is due to the cache calling
- * out to the system when refreshing nodes with the lock held.
- */
- mPrefetchResultRunnable = () -> finalizeAndCacheAccessibilityNodeInfos(
- infos, mConnectionIdWaitingForPrefetchResult, false,
- mPackageNamesForNextPrefetchResult);
- mMainHandler.post(mPrefetchResultRunnable);
-
- } else {
- for (AccessibilityNodeInfo info : infos) {
- infosCopy.add(new AccessibilityNodeInfo(info));
- }
- mConnectionIdWaitingForPrefetchResultCopy =
- mConnectionIdWaitingForPrefetchResult;
- mPackageNamesForNextPrefetchResultCopy =
- new String[mPackageNamesForNextPrefetchResult.length];
- for (int i = 0; i < mPackageNamesForNextPrefetchResult.length; i++) {
- mPackageNamesForNextPrefetchResultCopy[i] =
- mPackageNamesForNextPrefetchResult[i];
-
- }
- }
- }
-
- }
-
- if (infosCopy != null) {
- finalizeAndCacheAccessibilityNodeInfos(
- infosCopy, mConnectionIdWaitingForPrefetchResultCopy, false,
- mPackageNamesForNextPrefetchResultCopy);
- }
- }
-
- /**
* Gets the result of a request to perform an accessibility action.
*
* @param interactionId The interaction id to match the result with the request.
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
index 231e75a..049bb31 100644
--- a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
@@ -47,15 +47,6 @@
int interactionId);
/**
- * Sets the result of a prefetch request that returns {@link AccessibilityNodeInfo}s.
- *
- * @param root The {@link AccessibilityNodeInfo} for which the prefetching is based off of.
- * @param infos The result {@link AccessibilityNodeInfo}s.
- */
- void setPrefetchAccessibilityNodeInfoResult(
- in List<AccessibilityNodeInfo> infos, int interactionId);
-
- /**
* Sets the result of a request to perform an accessibility action.
*
* @param Whether the action was performed.
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
index 7e1e7f4..ab24f89 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
@@ -33,6 +33,9 @@
import org.junit.runner.RunWith;
import org.mockito.Mock;
+import java.util.Arrays;
+import java.util.List;
+
/**
* Tests for AccessibilityInteractionClient
*/
@@ -62,7 +65,7 @@
final long accessibilityNodeId = 0x4321L;
AccessibilityNodeInfo nodeFromConnection = AccessibilityNodeInfo.obtain();
nodeFromConnection.setSourceNodeId(accessibilityNodeId, windowId);
- mMockConnection.mInfoToReturn = nodeFromConnection;
+ mMockConnection.mInfosToReturn = Arrays.asList(nodeFromConnection);
AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
AccessibilityNodeInfo node = client.findAccessibilityNodeInfoByAccessibilityId(
MOCK_CONNECTION_ID, windowId, accessibilityNodeId, true, 0, null);
@@ -72,7 +75,7 @@
}
private static class MockConnection extends AccessibilityServiceConnectionImpl {
- AccessibilityNodeInfo mInfoToReturn;
+ List<AccessibilityNodeInfo> mInfosToReturn;
@Override
public String[] findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
@@ -80,7 +83,7 @@
IAccessibilityInteractionConnectionCallback callback, int flags, long threadId,
Bundle arguments) {
try {
- callback.setFindAccessibilityNodeInfoResult(mInfoToReturn, interactionId);
+ callback.setFindAccessibilityNodeInfosResult(mInfosToReturn, interactionId);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
index 6828dd9..bafb641 100644
--- a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
+++ b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
@@ -40,34 +40,29 @@
private final IAccessibilityInteractionConnectionCallback mServiceCallback;
private final IAccessibilityInteractionConnection mConnectionWithReplacementActions;
private final int mInteractionId;
- private final int mNodeWithReplacementActionsInteractionId;
private final Object mLock = new Object();
@GuardedBy("mLock")
- private boolean mReplacementNodeIsReadyOrFailed;
-
- @GuardedBy("mLock")
- AccessibilityNodeInfo mNodeWithReplacementActions;
+ List<AccessibilityNodeInfo> mNodesWithReplacementActions;
@GuardedBy("mLock")
List<AccessibilityNodeInfo> mNodesFromOriginalWindow;
@GuardedBy("mLock")
- boolean mSetFindNodeFromOriginalWindowCalled = false;
-
- @GuardedBy("mLock")
AccessibilityNodeInfo mNodeFromOriginalWindow;
+ // Keep track of whether or not we've been called back for a single node
@GuardedBy("mLock")
- boolean mSetFindNodesFromOriginalWindowCalled = false;
+ boolean mSingleNodeCallbackHappened;
-
+ // Keep track of whether or not we've been called back for multiple node
@GuardedBy("mLock")
- List<AccessibilityNodeInfo> mPrefetchedNodesFromOriginalWindow;
+ boolean mMultiNodeCallbackHappened;
+ // We shouldn't get any more callbacks after we've called back the original service, but
+ // keep track to make sure we catch such strange things
@GuardedBy("mLock")
- boolean mSetPrefetchFromOriginalWindowCalled = false;
-
+ boolean mDone;
public ActionReplacingCallback(IAccessibilityInteractionConnectionCallback serviceCallback,
IAccessibilityInteractionConnection connectionWithReplacementActions,
@@ -75,20 +70,19 @@
mServiceCallback = serviceCallback;
mConnectionWithReplacementActions = connectionWithReplacementActions;
mInteractionId = interactionId;
- mNodeWithReplacementActionsInteractionId = interactionId + 1;
// Request the root node of the replacing window
final long identityToken = Binder.clearCallingIdentity();
try {
mConnectionWithReplacementActions.findAccessibilityNodeInfoByAccessibilityId(
- AccessibilityNodeInfo.ROOT_NODE_ID, null,
- mNodeWithReplacementActionsInteractionId, this, 0,
+ AccessibilityNodeInfo.ROOT_NODE_ID, null, interactionId + 1, this, 0,
interrogatingPid, interrogatingTid, null, null);
} catch (RemoteException re) {
if (DEBUG) {
Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfoByAccessibilityId()");
}
- mReplacementNodeIsReadyOrFailed = true;
+ // Pretend we already got a (null) list of replacement nodes
+ mMultiNodeCallbackHappened = true;
} finally {
Binder.restoreCallingIdentity(identityToken);
}
@@ -96,67 +90,46 @@
@Override
public void setFindAccessibilityNodeInfoResult(AccessibilityNodeInfo info, int interactionId) {
- synchronized (mLock) {
+ boolean readyForCallback;
+ synchronized(mLock) {
if (interactionId == mInteractionId) {
mNodeFromOriginalWindow = info;
- mSetFindNodeFromOriginalWindowCalled = true;
- } else if (interactionId == mNodeWithReplacementActionsInteractionId) {
- mNodeWithReplacementActions = info;
- mReplacementNodeIsReadyOrFailed = true;
} else {
Slog.e(LOG_TAG, "Callback with unexpected interactionId");
return;
}
+
+ mSingleNodeCallbackHappened = true;
+ readyForCallback = mMultiNodeCallbackHappened;
}
- replaceInfoActionsAndCallServiceIfReady();
+ if (readyForCallback) {
+ replaceInfoActionsAndCallService();
+ }
}
@Override
public void setFindAccessibilityNodeInfosResult(List<AccessibilityNodeInfo> infos,
int interactionId) {
- synchronized (mLock) {
+ boolean callbackForSingleNode;
+ boolean callbackForMultipleNodes;
+ synchronized(mLock) {
if (interactionId == mInteractionId) {
mNodesFromOriginalWindow = infos;
- mSetFindNodesFromOriginalWindowCalled = true;
- } else if (interactionId == mNodeWithReplacementActionsInteractionId) {
- setNodeWithReplacementActionsFromList(infos);
- mReplacementNodeIsReadyOrFailed = true;
+ } else if (interactionId == mInteractionId + 1) {
+ mNodesWithReplacementActions = infos;
} else {
Slog.e(LOG_TAG, "Callback with unexpected interactionId");
return;
}
+ callbackForSingleNode = mSingleNodeCallbackHappened;
+ callbackForMultipleNodes = mMultiNodeCallbackHappened;
+ mMultiNodeCallbackHappened = true;
}
- replaceInfoActionsAndCallServiceIfReady();
- }
-
- @Override
- public void setPrefetchAccessibilityNodeInfoResult(List<AccessibilityNodeInfo> infos,
- int interactionId)
- throws RemoteException {
- synchronized (mLock) {
- if (interactionId == mInteractionId) {
- mPrefetchedNodesFromOriginalWindow = infos;
- mSetPrefetchFromOriginalWindowCalled = true;
- } else {
- Slog.e(LOG_TAG, "Callback with unexpected interactionId");
- return;
- }
+ if (callbackForSingleNode) {
+ replaceInfoActionsAndCallService();
}
- replaceInfoActionsAndCallServiceIfReady();
- }
-
- private void replaceInfoActionsAndCallServiceIfReady() {
- replaceInfoActionsAndCallService();
- replaceInfosActionsAndCallService();
- replacePrefetchInfosActionsAndCallService();
- }
-
- private void setNodeWithReplacementActionsFromList(List<AccessibilityNodeInfo> infos) {
- for (int i = 0; i < infos.size(); i++) {
- AccessibilityNodeInfo info = infos.get(i);
- if (info.getSourceNodeId() == AccessibilityNodeInfo.ROOT_NODE_ID) {
- mNodeWithReplacementActions = info;
- }
+ if (callbackForMultipleNodes) {
+ replaceInfosActionsAndCallService();
}
}
@@ -169,81 +142,55 @@
private void replaceInfoActionsAndCallService() {
final AccessibilityNodeInfo nodeToReturn;
- boolean doCallback = false;
synchronized (mLock) {
- doCallback = mReplacementNodeIsReadyOrFailed
- && mSetFindNodeFromOriginalWindowCalled;
- if (doCallback && mNodeFromOriginalWindow != null) {
- replaceActionsOnInfoLocked(mNodeFromOriginalWindow);
- mSetFindNodeFromOriginalWindowCalled = false;
- }
- nodeToReturn = mNodeFromOriginalWindow;
- }
- if (doCallback) {
- try {
- mServiceCallback.setFindAccessibilityNodeInfoResult(nodeToReturn, mInteractionId);
- } catch (RemoteException re) {
+ if (mDone) {
if (DEBUG) {
- Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfoResult");
+ Slog.e(LOG_TAG, "Extra callback");
}
+ return;
+ }
+ if (mNodeFromOriginalWindow != null) {
+ replaceActionsOnInfoLocked(mNodeFromOriginalWindow);
+ }
+ recycleReplaceActionNodesLocked();
+ nodeToReturn = mNodeFromOriginalWindow;
+ mDone = true;
+ }
+ try {
+ mServiceCallback.setFindAccessibilityNodeInfoResult(nodeToReturn, mInteractionId);
+ } catch (RemoteException re) {
+ if (DEBUG) {
+ Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfoResult");
}
}
}
private void replaceInfosActionsAndCallService() {
- List<AccessibilityNodeInfo> nodesToReturn = null;
- boolean doCallback = false;
+ final List<AccessibilityNodeInfo> nodesToReturn;
synchronized (mLock) {
- doCallback = mReplacementNodeIsReadyOrFailed
- && mSetFindNodesFromOriginalWindowCalled;
- if (doCallback) {
- nodesToReturn = replaceActionsLocked(mNodesFromOriginalWindow);
- mSetFindNodesFromOriginalWindowCalled = false;
- }
- }
- if (doCallback) {
- try {
- mServiceCallback.setFindAccessibilityNodeInfosResult(nodesToReturn, mInteractionId);
- } catch (RemoteException re) {
+ if (mDone) {
if (DEBUG) {
- Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfosResult");
+ Slog.e(LOG_TAG, "Extra callback");
+ }
+ return;
+ }
+ if (mNodesFromOriginalWindow != null) {
+ for (int i = 0; i < mNodesFromOriginalWindow.size(); i++) {
+ replaceActionsOnInfoLocked(mNodesFromOriginalWindow.get(i));
}
}
+ recycleReplaceActionNodesLocked();
+ nodesToReturn = (mNodesFromOriginalWindow == null)
+ ? null : new ArrayList<>(mNodesFromOriginalWindow);
+ mDone = true;
}
- }
-
- private void replacePrefetchInfosActionsAndCallService() {
- List<AccessibilityNodeInfo> nodesToReturn = null;
- boolean doCallback = false;
- synchronized (mLock) {
- doCallback = mReplacementNodeIsReadyOrFailed
- && mSetPrefetchFromOriginalWindowCalled;
- if (doCallback) {
- nodesToReturn = replaceActionsLocked(mPrefetchedNodesFromOriginalWindow);
- mSetPrefetchFromOriginalWindowCalled = false;
+ try {
+ mServiceCallback.setFindAccessibilityNodeInfosResult(nodesToReturn, mInteractionId);
+ } catch (RemoteException re) {
+ if (DEBUG) {
+ Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfosResult");
}
}
- if (doCallback) {
- try {
- mServiceCallback.setPrefetchAccessibilityNodeInfoResult(
- nodesToReturn, mInteractionId);
- } catch (RemoteException re) {
- if (DEBUG) {
- Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfosResult");
- }
- }
- }
- }
-
- @GuardedBy("mLock")
- private List<AccessibilityNodeInfo> replaceActionsLocked(List<AccessibilityNodeInfo> infos) {
- if (infos != null) {
- for (int i = 0; i < infos.size(); i++) {
- replaceActionsOnInfoLocked(infos.get(i));
- }
- }
- return (infos == null)
- ? null : new ArrayList<>(infos);
}
@GuardedBy("mLock")
@@ -257,22 +204,40 @@
info.setDismissable(false);
// We currently only replace actions for the root node
if ((info.getSourceNodeId() == AccessibilityNodeInfo.ROOT_NODE_ID)
- && mNodeWithReplacementActions != null) {
- List<AccessibilityAction> actions = mNodeWithReplacementActions.getActionList();
- if (actions != null) {
- for (int j = 0; j < actions.size(); j++) {
- info.addAction(actions.get(j));
+ && mNodesWithReplacementActions != null) {
+ // This list should always contain a single node with the root ID
+ for (int i = 0; i < mNodesWithReplacementActions.size(); i++) {
+ AccessibilityNodeInfo nodeWithReplacementActions =
+ mNodesWithReplacementActions.get(i);
+ if (nodeWithReplacementActions.getSourceNodeId()
+ == AccessibilityNodeInfo.ROOT_NODE_ID) {
+ List<AccessibilityAction> actions = nodeWithReplacementActions.getActionList();
+ if (actions != null) {
+ for (int j = 0; j < actions.size(); j++) {
+ info.addAction(actions.get(j));
+ }
+ // The PIP needs to be able to take accessibility focus
+ info.addAction(AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS);
+ info.addAction(AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
+ }
+ info.setClickable(nodeWithReplacementActions.isClickable());
+ info.setFocusable(nodeWithReplacementActions.isFocusable());
+ info.setContextClickable(nodeWithReplacementActions.isContextClickable());
+ info.setScrollable(nodeWithReplacementActions.isScrollable());
+ info.setLongClickable(nodeWithReplacementActions.isLongClickable());
+ info.setDismissable(nodeWithReplacementActions.isDismissable());
}
- // The PIP needs to be able to take accessibility focus
- info.addAction(AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS);
- info.addAction(AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
}
- info.setClickable(mNodeWithReplacementActions.isClickable());
- info.setFocusable(mNodeWithReplacementActions.isFocusable());
- info.setContextClickable(mNodeWithReplacementActions.isContextClickable());
- info.setScrollable(mNodeWithReplacementActions.isScrollable());
- info.setLongClickable(mNodeWithReplacementActions.isLongClickable());
- info.setDismissable(mNodeWithReplacementActions.isDismissable());
}
}
+
+ @GuardedBy("mLock")
+ private void recycleReplaceActionNodesLocked() {
+ if (mNodesWithReplacementActions == null) return;
+ for (int i = mNodesWithReplacementActions.size() - 1; i >= 0; i--) {
+ AccessibilityNodeInfo nodeWithReplacementAction = mNodesWithReplacementActions.get(i);
+ nodeWithReplacementAction.recycle();
+ }
+ mNodesWithReplacementActions = null;
+ }
}
diff --git a/services/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
new file mode 100644
index 0000000..a0771c0
--- /dev/null
+++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
@@ -0,0 +1,380 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.policy;
+
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.XmlResourceParser;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+
+import com.android.internal.policy.IShortcutService;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/**
+ * Manages quick launch shortcuts by:
+ * <li> Keeping the local copy in sync with the database (this is an observer)
+ * <li> Returning a shortcut-matching intent to clients
+ * <li> Returning particular kind of application intent by special key.
+ */
+class ModifierShortcutManager {
+ private static final String TAG = "ShortcutManager";
+
+ private static final String TAG_BOOKMARKS = "bookmarks";
+ private static final String TAG_BOOKMARK = "bookmark";
+
+ private static final String ATTRIBUTE_PACKAGE = "package";
+ private static final String ATTRIBUTE_CLASS = "class";
+ private static final String ATTRIBUTE_SHORTCUT = "shortcut";
+ private static final String ATTRIBUTE_CATEGORY = "category";
+ private static final String ATTRIBUTE_SHIFT = "shift";
+
+ private final SparseArray<ShortcutInfo> mIntentShortcuts = new SparseArray<>();
+ private final SparseArray<ShortcutInfo> mShiftShortcuts = new SparseArray<>();
+
+ private LongSparseArray<IShortcutService> mShortcutKeyServices = new LongSparseArray<>();
+
+ /* Table of Application Launch keys. Maps from key codes to intent categories.
+ *
+ * These are special keys that are used to launch particular kinds of applications,
+ * such as a web browser. HID defines nearly a hundred of them in the Consumer (0x0C)
+ * usage page. We don't support quite that many yet...
+ */
+ static SparseArray<String> sApplicationLaunchKeyCategories;
+ static {
+ sApplicationLaunchKeyCategories = new SparseArray<String>();
+ sApplicationLaunchKeyCategories.append(
+ KeyEvent.KEYCODE_EXPLORER, Intent.CATEGORY_APP_BROWSER);
+ sApplicationLaunchKeyCategories.append(
+ KeyEvent.KEYCODE_ENVELOPE, Intent.CATEGORY_APP_EMAIL);
+ sApplicationLaunchKeyCategories.append(
+ KeyEvent.KEYCODE_CONTACTS, Intent.CATEGORY_APP_CONTACTS);
+ sApplicationLaunchKeyCategories.append(
+ KeyEvent.KEYCODE_CALENDAR, Intent.CATEGORY_APP_CALENDAR);
+ sApplicationLaunchKeyCategories.append(
+ KeyEvent.KEYCODE_MUSIC, Intent.CATEGORY_APP_MUSIC);
+ sApplicationLaunchKeyCategories.append(
+ KeyEvent.KEYCODE_CALCULATOR, Intent.CATEGORY_APP_CALCULATOR);
+ }
+
+ private final Context mContext;
+ private boolean mSearchKeyShortcutPending = false;
+ private boolean mConsumeSearchKeyUp = true;
+
+ ModifierShortcutManager(Context context) {
+ mContext = context;
+ loadShortcuts();
+ }
+
+ /**
+ * Gets the shortcut intent for a given keycode+modifier. Make sure you
+ * strip whatever modifier is used for invoking shortcuts (for example,
+ * if 'Sym+A' should invoke a shortcut on 'A', you should strip the
+ * 'Sym' bit from the modifiers before calling this method.
+ * <p>
+ * This will first try an exact match (with modifiers), and then try a
+ * match without modifiers (primary character on a key).
+ *
+ * @param kcm The key character map of the device on which the key was pressed.
+ * @param keyCode The key code.
+ * @param metaState The meta state, omitting any modifiers that were used
+ * to invoke the shortcut.
+ * @return The intent that matches the shortcut, or null if not found.
+ */
+ private Intent getIntent(KeyCharacterMap kcm, int keyCode, int metaState) {
+ ShortcutInfo shortcut = null;
+
+ // If the Shift key is pressed, then search for the shift shortcuts.
+ boolean isShiftOn = (metaState & KeyEvent.META_SHIFT_ON) == KeyEvent.META_SHIFT_ON;
+ SparseArray<ShortcutInfo> shortcutMap = isShiftOn ? mShiftShortcuts : mIntentShortcuts;
+
+ // First try the exact keycode (with modifiers).
+ int shortcutChar = kcm.get(keyCode, metaState);
+ if (shortcutChar != 0) {
+ shortcut = shortcutMap.get(shortcutChar);
+ }
+
+ // Next try the primary character on that key.
+ if (shortcut == null) {
+ shortcutChar = Character.toLowerCase(kcm.getDisplayLabel(keyCode));
+ if (shortcutChar != 0) {
+ shortcut = shortcutMap.get(shortcutChar);
+ }
+ }
+
+ return (shortcut != null) ? shortcut.intent : null;
+ }
+
+ private void loadShortcuts() {
+ PackageManager packageManager = mContext.getPackageManager();
+ try {
+ XmlResourceParser parser = mContext.getResources().getXml(
+ com.android.internal.R.xml.bookmarks);
+ XmlUtils.beginDocument(parser, TAG_BOOKMARKS);
+
+ while (true) {
+ XmlUtils.nextElement(parser);
+
+ if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
+ break;
+ }
+
+ if (!TAG_BOOKMARK.equals(parser.getName())) {
+ break;
+ }
+
+ String packageName = parser.getAttributeValue(null, ATTRIBUTE_PACKAGE);
+ String className = parser.getAttributeValue(null, ATTRIBUTE_CLASS);
+ String shortcutName = parser.getAttributeValue(null, ATTRIBUTE_SHORTCUT);
+ String categoryName = parser.getAttributeValue(null, ATTRIBUTE_CATEGORY);
+ String shiftName = parser.getAttributeValue(null, ATTRIBUTE_SHIFT);
+
+ if (TextUtils.isEmpty(shortcutName)) {
+ Log.w(TAG, "Unable to get shortcut for: " + packageName + "/" + className);
+ continue;
+ }
+
+ final int shortcutChar = shortcutName.charAt(0);
+ final boolean isShiftShortcut = (shiftName != null && shiftName.equals("true"));
+
+ final Intent intent;
+ final String title;
+ if (packageName != null && className != null) {
+ ActivityInfo info = null;
+ ComponentName componentName = new ComponentName(packageName, className);
+ try {
+ info = packageManager.getActivityInfo(componentName,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.MATCH_UNINSTALLED_PACKAGES);
+ } catch (PackageManager.NameNotFoundException e) {
+ String[] packages = packageManager.canonicalToCurrentPackageNames(
+ new String[] { packageName });
+ componentName = new ComponentName(packages[0], className);
+ try {
+ info = packageManager.getActivityInfo(componentName,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.MATCH_UNINSTALLED_PACKAGES);
+ } catch (PackageManager.NameNotFoundException e1) {
+ Log.w(TAG, "Unable to add bookmark: " + packageName
+ + "/" + className, e);
+ continue;
+ }
+ }
+
+ intent = new Intent(Intent.ACTION_MAIN);
+ intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ intent.setComponent(componentName);
+ title = info.loadLabel(packageManager).toString();
+ } else if (categoryName != null) {
+ intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, categoryName);
+ title = "";
+ } else {
+ Log.w(TAG, "Unable to add bookmark for shortcut " + shortcutName
+ + ": missing package/class or category attributes");
+ continue;
+ }
+
+ ShortcutInfo shortcut = new ShortcutInfo(title, intent);
+ if (isShiftShortcut) {
+ mShiftShortcuts.put(shortcutChar, shortcut);
+ } else {
+ mIntentShortcuts.put(shortcutChar, shortcut);
+ }
+ }
+ } catch (XmlPullParserException e) {
+ Log.w(TAG, "Got exception parsing bookmarks.", e);
+ } catch (IOException e) {
+ Log.w(TAG, "Got exception parsing bookmarks.", e);
+ }
+ }
+
+ void registerShortcutKey(long shortcutCode, IShortcutService shortcutService)
+ throws RemoteException {
+ IShortcutService service = mShortcutKeyServices.get(shortcutCode);
+ if (service != null && service.asBinder().pingBinder()) {
+ throw new RemoteException("Key already exists.");
+ }
+
+ mShortcutKeyServices.put(shortcutCode, shortcutService);
+ }
+
+ /**
+ * Handle the shortcut to {@link IShortcutService}
+ * @param keyCode The key code of the event.
+ * @param metaState The meta key modifier state.
+ * @return True if invoked the shortcut, otherwise false.
+ */
+ private boolean handleShortcutService(int keyCode, int metaState) {
+ long shortcutCode = keyCode;
+ if ((metaState & KeyEvent.META_CTRL_ON) != 0) {
+ shortcutCode |= ((long) KeyEvent.META_CTRL_ON) << Integer.SIZE;
+ }
+
+ if ((metaState & KeyEvent.META_ALT_ON) != 0) {
+ shortcutCode |= ((long) KeyEvent.META_ALT_ON) << Integer.SIZE;
+ }
+
+ if ((metaState & KeyEvent.META_SHIFT_ON) != 0) {
+ shortcutCode |= ((long) KeyEvent.META_SHIFT_ON) << Integer.SIZE;
+ }
+
+ if ((metaState & KeyEvent.META_META_ON) != 0) {
+ shortcutCode |= ((long) KeyEvent.META_META_ON) << Integer.SIZE;
+ }
+
+ IShortcutService shortcutService = mShortcutKeyServices.get(shortcutCode);
+ if (shortcutService != null) {
+ try {
+ shortcutService.notifyShortcutKeyPressed(shortcutCode);
+ } catch (RemoteException e) {
+ mShortcutKeyServices.delete(shortcutCode);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Handle the shortcut to {@link Intent}
+ *
+ * @param kcm the {@link KeyCharacterMap} associated with the keyboard device.
+ * @param keyCode The key code of the event.
+ * @param metaState The meta key modifier state.
+ * @return True if invoked the shortcut, otherwise false.
+ */
+ private boolean handleIntentShortcut(KeyCharacterMap kcm, int keyCode, int metaState) {
+ // Shortcuts are invoked through Search+key, so intercept those here
+ // Any printing key that is chorded with Search should be consumed
+ // even if no shortcut was invoked. This prevents text from being
+ // inadvertently inserted when using a keyboard that has built-in macro
+ // shortcut keys (that emit Search+x) and some of them are not registered.
+ if (mSearchKeyShortcutPending) {
+ if (kcm.isPrintingKey(keyCode)) {
+ mConsumeSearchKeyUp = true;
+ mSearchKeyShortcutPending = false;
+ } else {
+ return false;
+ }
+ } else if ((metaState & KeyEvent.META_META_MASK) != 0) {
+ // Invoke shortcuts using Meta.
+ metaState &= ~KeyEvent.META_META_MASK;
+ } else {
+ // Handle application launch keys.
+ String category = sApplicationLaunchKeyCategories.get(keyCode);
+ if (category != null) {
+ Intent intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, category);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ try {
+ mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+ } catch (ActivityNotFoundException ex) {
+ Slog.w(TAG, "Dropping application launch key because "
+ + "the activity to which it is registered was not found: "
+ + "keyCode=" + KeyEvent.keyCodeToString(keyCode) + ","
+ + " category=" + category, ex);
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ final Intent shortcutIntent = getIntent(kcm, keyCode, metaState);
+ if (shortcutIntent != null) {
+ shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ try {
+ mContext.startActivityAsUser(shortcutIntent, UserHandle.CURRENT);
+ } catch (ActivityNotFoundException ex) {
+ Slog.w(TAG, "Dropping shortcut key combination because "
+ + "the activity to which it is registered was not found: "
+ + "META+ or SEARCH" + KeyEvent.keyCodeToString(keyCode), ex);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Handle the shortcut from {@link KeyEvent}
+ *
+ * @param event Description of the key event.
+ * @return True if invoked the shortcut, otherwise false.
+ */
+ boolean interceptKey(KeyEvent event) {
+ if (event.getRepeatCount() != 0) {
+ return false;
+ }
+
+ final int metaState = event.getModifiers();
+ final int keyCode = event.getKeyCode();
+ if (keyCode == KeyEvent.KEYCODE_SEARCH) {
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ mSearchKeyShortcutPending = true;
+ mConsumeSearchKeyUp = false;
+ } else {
+ mSearchKeyShortcutPending = false;
+ if (mConsumeSearchKeyUp) {
+ mConsumeSearchKeyUp = false;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ if (event.getAction() != KeyEvent.ACTION_DOWN) {
+ return false;
+ }
+
+ final KeyCharacterMap kcm = event.getKeyCharacterMap();
+ if (handleIntentShortcut(kcm, keyCode, metaState)) {
+ return true;
+ }
+
+ if (handleShortcutService(keyCode, metaState)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ private static final class ShortcutInfo {
+ public final String title;
+ public final Intent intent;
+
+ ShortcutInfo(String title, Intent intent) {
+ this.title = title;
+ this.intent = intent;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index c50efc7..bce218f 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -163,7 +163,6 @@
import android.speech.RecognizerIntent;
import android.telecom.TelecomManager;
import android.util.Log;
-import android.util.LongSparseArray;
import android.util.MutableBoolean;
import android.util.PrintWriterPrinter;
import android.util.Slog;
@@ -318,29 +317,6 @@
*/
private boolean mKeyguardDrawnOnce;
- /* Table of Application Launch keys. Maps from key codes to intent categories.
- *
- * These are special keys that are used to launch particular kinds of applications,
- * such as a web browser. HID defines nearly a hundred of them in the Consumer (0x0C)
- * usage page. We don't support quite that many yet...
- */
- static SparseArray<String> sApplicationLaunchKeyCategories;
- static {
- sApplicationLaunchKeyCategories = new SparseArray<String>();
- sApplicationLaunchKeyCategories.append(
- KeyEvent.KEYCODE_EXPLORER, Intent.CATEGORY_APP_BROWSER);
- sApplicationLaunchKeyCategories.append(
- KeyEvent.KEYCODE_ENVELOPE, Intent.CATEGORY_APP_EMAIL);
- sApplicationLaunchKeyCategories.append(
- KeyEvent.KEYCODE_CONTACTS, Intent.CATEGORY_APP_CONTACTS);
- sApplicationLaunchKeyCategories.append(
- KeyEvent.KEYCODE_CALENDAR, Intent.CATEGORY_APP_CALENDAR);
- sApplicationLaunchKeyCategories.append(
- KeyEvent.KEYCODE_MUSIC, Intent.CATEGORY_APP_MUSIC);
- sApplicationLaunchKeyCategories.append(
- KeyEvent.KEYCODE_CALCULATOR, Intent.CATEGORY_APP_CALCULATOR);
- }
-
/** Amount of time (in milliseconds) to wait for windows drawn before powering on. */
static final int WAITING_FOR_DRAWN_TIMEOUT = 1000;
@@ -419,8 +395,6 @@
boolean mSafeMode;
private WindowState mKeyguardCandidate = null;
- private LongSparseArray<IShortcutService> mShortcutKeyServices = new LongSparseArray<>();
-
// Whether to allow dock apps with METADATA_DOCK_HOME to temporarily take over the Home key.
// This is for car dock and this is updated from resource.
private boolean mEnableCarDockHomeCapture = true;
@@ -516,8 +490,6 @@
Intent mCarDockIntent;
Intent mDeskDockIntent;
Intent mVrHeadsetHomeIntent;
- boolean mSearchKeyShortcutPending;
- boolean mConsumeSearchKeyUp;
boolean mPendingMetaAction;
boolean mPendingCapsLockToggle;
@@ -578,7 +550,7 @@
private static final int BRIGHTNESS_STEPS = 10;
SettingsObserver mSettingsObserver;
- ShortcutManager mShortcutManager;
+ ModifierShortcutManager mModifierShortcutManager;
PowerManager.WakeLock mBroadcastWakeLock;
PowerManager.WakeLock mPowerKeyWakeLock;
boolean mHavePendingMediaKeyRepeatWithWakeLock;
@@ -1772,7 +1744,7 @@
mWakeGestureListener = new MyWakeGestureListener(mContext, mHandler);
mSettingsObserver = new SettingsObserver(mHandler);
mSettingsObserver.observe();
- mShortcutManager = new ShortcutManager(context);
+ mModifierShortcutManager = new ModifierShortcutManager(context);
mUiMode = context.getResources().getInteger(
com.android.internal.R.integer.config_defaultUiModeType);
mHomeIntent = new Intent(Intent.ACTION_MAIN, null);
@@ -2574,6 +2546,15 @@
mPendingCapsLockToggle = false;
}
+ if (isUserSetupComplete() && !keyguardOn) {
+ if (mModifierShortcutManager.interceptKey(event)) {
+ dismissKeyboardShortcutsMenu();
+ mPendingMetaAction = false;
+ mPendingCapsLockToggle = false;
+ return key_consumed;
+ }
+ }
+
switch(keyCode) {
case KeyEvent.KEYCODE_HOME:
// First we always handle the home key here, so applications
@@ -2599,20 +2580,6 @@
}
}
break;
- case KeyEvent.KEYCODE_SEARCH:
- if (down) {
- if (repeatCount == 0) {
- mSearchKeyShortcutPending = true;
- mConsumeSearchKeyUp = false;
- }
- } else {
- mSearchKeyShortcutPending = false;
- if (mConsumeSearchKeyUp) {
- mConsumeSearchKeyUp = false;
- return key_consumed;
- }
- }
- return 0;
case KeyEvent.KEYCODE_APP_SWITCH:
if (!keyguardOn) {
if (down && repeatCount == 0) {
@@ -2820,114 +2787,11 @@
break;
}
- // Shortcuts are invoked through Search+key, so intercept those here
- // Any printing key that is chorded with Search should be consumed
- // even if no shortcut was invoked. This prevents text from being
- // inadvertently inserted when using a keyboard that has built-in macro
- // shortcut keys (that emit Search+x) and some of them are not registered.
- if (mSearchKeyShortcutPending) {
- final KeyCharacterMap kcm = event.getKeyCharacterMap();
- if (kcm.isPrintingKey(keyCode)) {
- mConsumeSearchKeyUp = true;
- mSearchKeyShortcutPending = false;
- if (down && repeatCount == 0 && !keyguardOn) {
- Intent shortcutIntent = mShortcutManager.getIntent(kcm, keyCode, metaState);
- if (shortcutIntent != null) {
- shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- try {
- startActivityAsUser(shortcutIntent, UserHandle.CURRENT);
- dismissKeyboardShortcutsMenu();
- } catch (ActivityNotFoundException ex) {
- Slog.w(TAG, "Dropping shortcut key combination because "
- + "the activity to which it is registered was not found: "
- + "SEARCH+" + KeyEvent.keyCodeToString(keyCode), ex);
- }
- } else {
- Slog.i(TAG, "Dropping unregistered shortcut key combination: "
- + "SEARCH+" + KeyEvent.keyCodeToString(keyCode));
- }
- }
- return key_consumed;
- }
- }
-
- // Invoke shortcuts using Meta.
- if (down && repeatCount == 0 && !keyguardOn
- && (metaState & KeyEvent.META_META_ON) != 0) {
- final KeyCharacterMap kcm = event.getKeyCharacterMap();
- if (kcm.isPrintingKey(keyCode)) {
- Intent shortcutIntent = mShortcutManager.getIntent(kcm, keyCode,
- metaState & ~(KeyEvent.META_META_ON
- | KeyEvent.META_META_LEFT_ON | KeyEvent.META_META_RIGHT_ON));
- if (shortcutIntent != null) {
- shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- try {
- startActivityAsUser(shortcutIntent, UserHandle.CURRENT);
- dismissKeyboardShortcutsMenu();
- } catch (ActivityNotFoundException ex) {
- Slog.w(TAG, "Dropping shortcut key combination because "
- + "the activity to which it is registered was not found: "
- + "META+" + KeyEvent.keyCodeToString(keyCode), ex);
- }
- return key_consumed;
- }
- }
- }
-
- // Handle application launch keys.
- if (down && repeatCount == 0 && !keyguardOn) {
- String category = sApplicationLaunchKeyCategories.get(keyCode);
- if (category != null) {
- Intent intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, category);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- try {
- startActivityAsUser(intent, UserHandle.CURRENT);
- dismissKeyboardShortcutsMenu();
- } catch (ActivityNotFoundException ex) {
- Slog.w(TAG, "Dropping application launch key because "
- + "the activity to which it is registered was not found: "
- + "keyCode=" + keyCode + ", category=" + category, ex);
- }
- return key_consumed;
- }
- }
-
if (isValidGlobalKey(keyCode)
&& mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) {
return key_consumed;
}
- if (down) {
- long shortcutCode = keyCode;
- if (event.isCtrlPressed()) {
- shortcutCode |= ((long) KeyEvent.META_CTRL_ON) << Integer.SIZE;
- }
-
- if (event.isAltPressed()) {
- shortcutCode |= ((long) KeyEvent.META_ALT_ON) << Integer.SIZE;
- }
-
- if (event.isShiftPressed()) {
- shortcutCode |= ((long) KeyEvent.META_SHIFT_ON) << Integer.SIZE;
- }
-
- if (event.isMetaPressed()) {
- shortcutCode |= ((long) KeyEvent.META_META_ON) << Integer.SIZE;
- }
-
- IShortcutService shortcutService = mShortcutKeyServices.get(shortcutCode);
- if (shortcutService != null) {
- try {
- if (isUserSetupComplete()) {
- shortcutService.notifyShortcutKeyPressed(shortcutCode);
- }
- } catch (RemoteException e) {
- mShortcutKeyServices.delete(shortcutCode);
- }
- return key_consumed;
- }
- }
-
// Reserve all the META modifier combos for system behavior
if ((metaState & KeyEvent.META_META_ON) != 0) {
return key_consumed;
@@ -3113,12 +2977,7 @@
public void registerShortcutKey(long shortcutCode, IShortcutService shortcutService)
throws RemoteException {
synchronized (mLock) {
- IShortcutService service = mShortcutKeyServices.get(shortcutCode);
- if (service != null && service.asBinder().pingBinder()) {
- throw new RemoteException("Key already exists.");
- }
-
- mShortcutKeyServices.put(shortcutCode, shortcutService);
+ mModifierShortcutManager.registerShortcutKey(shortcutCode, shortcutService);
}
}
diff --git a/services/core/java/com/android/server/policy/ShortcutManager.java b/services/core/java/com/android/server/policy/ShortcutManager.java
deleted file mode 100644
index ab404db..0000000
--- a/services/core/java/com/android/server/policy/ShortcutManager.java
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.policy;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.res.XmlResourceParser;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.SparseArray;
-import android.view.KeyCharacterMap;
-import android.view.KeyEvent;
-
-import com.android.internal.util.XmlUtils;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-
-/**
- * Manages quick launch shortcuts by:
- * <li> Keeping the local copy in sync with the database (this is an observer)
- * <li> Returning a shortcut-matching intent to clients
- */
-class ShortcutManager {
- private static final String TAG = "ShortcutManager";
-
- private static final String TAG_BOOKMARKS = "bookmarks";
- private static final String TAG_BOOKMARK = "bookmark";
-
- private static final String ATTRIBUTE_PACKAGE = "package";
- private static final String ATTRIBUTE_CLASS = "class";
- private static final String ATTRIBUTE_SHORTCUT = "shortcut";
- private static final String ATTRIBUTE_CATEGORY = "category";
- private static final String ATTRIBUTE_SHIFT = "shift";
-
- private final SparseArray<ShortcutInfo> mShortcuts = new SparseArray<>();
- private final SparseArray<ShortcutInfo> mShiftShortcuts = new SparseArray<>();
-
- private final Context mContext;
-
- public ShortcutManager(Context context) {
- mContext = context;
- loadShortcuts();
- }
-
- /**
- * Gets the shortcut intent for a given keycode+modifier. Make sure you
- * strip whatever modifier is used for invoking shortcuts (for example,
- * if 'Sym+A' should invoke a shortcut on 'A', you should strip the
- * 'Sym' bit from the modifiers before calling this method.
- * <p>
- * This will first try an exact match (with modifiers), and then try a
- * match without modifiers (primary character on a key).
- *
- * @param kcm The key character map of the device on which the key was pressed.
- * @param keyCode The key code.
- * @param metaState The meta state, omitting any modifiers that were used
- * to invoke the shortcut.
- * @return The intent that matches the shortcut, or null if not found.
- */
- public Intent getIntent(KeyCharacterMap kcm, int keyCode, int metaState) {
- ShortcutInfo shortcut = null;
-
- // If the Shift key is pressed, then search for the shift shortcuts.
- boolean isShiftOn = (metaState & KeyEvent.META_SHIFT_ON) == KeyEvent.META_SHIFT_ON;
- SparseArray<ShortcutInfo> shortcutMap = isShiftOn ? mShiftShortcuts : mShortcuts;
-
- // First try the exact keycode (with modifiers).
- int shortcutChar = kcm.get(keyCode, metaState);
- if (shortcutChar != 0) {
- shortcut = shortcutMap.get(shortcutChar);
- }
-
- // Next try the primary character on that key.
- if (shortcut == null) {
- shortcutChar = Character.toLowerCase(kcm.getDisplayLabel(keyCode));
- if (shortcutChar != 0) {
- shortcut = shortcutMap.get(shortcutChar);
- }
- }
-
- return (shortcut != null) ? shortcut.intent : null;
- }
-
- private void loadShortcuts() {
- PackageManager packageManager = mContext.getPackageManager();
- try {
- XmlResourceParser parser = mContext.getResources().getXml(
- com.android.internal.R.xml.bookmarks);
- XmlUtils.beginDocument(parser, TAG_BOOKMARKS);
-
- while (true) {
- XmlUtils.nextElement(parser);
-
- if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
- break;
- }
-
- if (!TAG_BOOKMARK.equals(parser.getName())) {
- break;
- }
-
- String packageName = parser.getAttributeValue(null, ATTRIBUTE_PACKAGE);
- String className = parser.getAttributeValue(null, ATTRIBUTE_CLASS);
- String shortcutName = parser.getAttributeValue(null, ATTRIBUTE_SHORTCUT);
- String categoryName = parser.getAttributeValue(null, ATTRIBUTE_CATEGORY);
- String shiftName = parser.getAttributeValue(null, ATTRIBUTE_SHIFT);
-
- if (TextUtils.isEmpty(shortcutName)) {
- Log.w(TAG, "Unable to get shortcut for: " + packageName + "/" + className);
- continue;
- }
-
- final int shortcutChar = shortcutName.charAt(0);
- final boolean isShiftShortcut = (shiftName != null && shiftName.equals("true"));
-
- final Intent intent;
- final String title;
- if (packageName != null && className != null) {
- ActivityInfo info = null;
- ComponentName componentName = new ComponentName(packageName, className);
- try {
- info = packageManager.getActivityInfo(componentName,
- PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
- | PackageManager.MATCH_UNINSTALLED_PACKAGES);
- } catch (PackageManager.NameNotFoundException e) {
- String[] packages = packageManager.canonicalToCurrentPackageNames(
- new String[] { packageName });
- componentName = new ComponentName(packages[0], className);
- try {
- info = packageManager.getActivityInfo(componentName,
- PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
- | PackageManager.MATCH_UNINSTALLED_PACKAGES);
- } catch (PackageManager.NameNotFoundException e1) {
- Log.w(TAG, "Unable to add bookmark: " + packageName
- + "/" + className, e);
- continue;
- }
- }
-
- intent = new Intent(Intent.ACTION_MAIN);
- intent.addCategory(Intent.CATEGORY_LAUNCHER);
- intent.setComponent(componentName);
- title = info.loadLabel(packageManager).toString();
- } else if (categoryName != null) {
- intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, categoryName);
- title = "";
- } else {
- Log.w(TAG, "Unable to add bookmark for shortcut " + shortcutName
- + ": missing package/class or category attributes");
- continue;
- }
-
- ShortcutInfo shortcut = new ShortcutInfo(title, intent);
- if (isShiftShortcut) {
- mShiftShortcuts.put(shortcutChar, shortcut);
- } else {
- mShortcuts.put(shortcutChar, shortcut);
- }
- }
- } catch (XmlPullParserException e) {
- Log.w(TAG, "Got exception parsing bookmarks.", e);
- } catch (IOException e) {
- Log.w(TAG, "Got exception parsing bookmarks.", e);
- }
- }
-
- private static final class ShortcutInfo {
- public final String title;
- public final Intent intent;
-
- public ShortcutInfo(String title, Intent intent) {
- this.title = title;
- this.intent = intent;
- }
- }
-}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 80173b5..68f5c58 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5058,6 +5058,10 @@
}
} else {
// No longer managed by any organizer.
+ final TaskDisplayArea taskDisplayArea = getDisplayArea();
+ if (taskDisplayArea != null) {
+ taskDisplayArea.removeLaunchRootTask(this);
+ }
setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, false /* set */);
if (mCreatedByOrganizer) {
removeImmediately("setTaskOrganizer");
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index badd7fd..76869e5 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -1135,12 +1135,7 @@
"Can't set not mCreatedByOrganizer as launch root tr=" + rootTask);
}
- LaunchRootTaskDef def = null;
- for (int i = mLaunchRootTasks.size() - 1; i >= 0; --i) {
- if (mLaunchRootTasks.get(i).task.mTaskId != rootTask.mTaskId) continue;
- def = mLaunchRootTasks.get(i);
- }
-
+ LaunchRootTaskDef def = getLaunchRootTaskDef(rootTask);
if (def != null) {
// Remove so we add to the end of the list.
mLaunchRootTasks.remove(def);
@@ -1156,6 +1151,23 @@
}
}
+ void removeLaunchRootTask(Task rootTask) {
+ LaunchRootTaskDef def = getLaunchRootTaskDef(rootTask);
+ if (def != null) {
+ mLaunchRootTasks.remove(def);
+ }
+ }
+
+ private @Nullable LaunchRootTaskDef getLaunchRootTaskDef(Task rootTask) {
+ LaunchRootTaskDef def = null;
+ for (int i = mLaunchRootTasks.size() - 1; i >= 0; --i) {
+ if (mLaunchRootTasks.get(i).task.mTaskId != rootTask.mTaskId) continue;
+ def = mLaunchRootTasks.get(i);
+ break;
+ }
+ return def;
+ }
+
Task getLaunchRootTask(int windowingMode, int activityType, ActivityOptions options) {
// Try to use the launch root task in options if available.
if (options != null) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java
deleted file mode 100644
index 170f561..0000000
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java
+++ /dev/null
@@ -1,581 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.accessibility;
-
-
-import static android.view.accessibility.AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
-import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS;
-import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS;
-import static android.view.accessibility.AccessibilityNodeInfo.ROOT_NODE_ID;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyList;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.app.Instrumentation;
-import android.content.Context;
-import android.os.RemoteException;
-import android.view.AccessibilityInteractionController;
-import android.view.View;
-import android.view.ViewRootImpl;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityNodeIdManager;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeProvider;
-import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
-import android.widget.FrameLayout;
-import android.widget.TextView;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Tests that verify expected node and prefetched node results when finding a view by node id. We
- * send some requests to the controller via View methods to control message timing.
- */
-@RunWith(AndroidJUnit4.class)
-public class AccessibilityInteractionControllerNodeRequestsTest {
- private AccessibilityInteractionController mAccessibilityInteractionController;
- @Mock
- private IAccessibilityInteractionConnectionCallback mMockClientCallback1;
- @Mock
- private IAccessibilityInteractionConnectionCallback mMockClientCallback2;
-
- @Captor
- private ArgumentCaptor<AccessibilityNodeInfo> mFindInfoCaptor;
- @Captor private ArgumentCaptor<List<AccessibilityNodeInfo>> mPrefetchInfoListCaptor;
-
- private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
- private static final int MOCK_CLIENT_1_THREAD_AND_PROCESS_ID = 1;
- private static final int MOCK_CLIENT_2_THREAD_AND_PROCESS_ID = 2;
-
- private static final String FRAME_LAYOUT_DESCRIPTION = "frameLayout";
- private static final String TEXT_VIEW_1_DESCRIPTION = "textView1";
- private static final String TEXT_VIEW_2_DESCRIPTION = "textView2";
-
- private TestFrameLayout mFrameLayout;
- private TestTextView mTextView1;
- private TestTextView2 mTextView2;
-
- private boolean mSendClient1RequestForTextAfterTextPrefetched;
- private boolean mSendClient2RequestForTextAfterTextPrefetched;
- private boolean mSendRequestForTextAndIncludeUnImportantViews;
- private int mMockClient1InteractionId;
- private int mMockClient2InteractionId;
-
- @Before
- public void setUp() throws Throwable {
- MockitoAnnotations.initMocks(this);
-
- mInstrumentation.runOnMainSync(() -> {
- final Context context = mInstrumentation.getTargetContext();
- final ViewRootImpl viewRootImpl = new ViewRootImpl(context, context.getDisplay());
-
- mFrameLayout = new TestFrameLayout(context);
- mTextView1 = new TestTextView(context);
- mTextView2 = new TestTextView2(context);
-
- mFrameLayout.addView(mTextView1);
- mFrameLayout.addView(mTextView2);
-
- // The controller retrieves views through this manager, and registration happens on
- // when attached to a window, which we don't have. We can simply reference FrameLayout
- // with ROOT_NODE_ID
- AccessibilityNodeIdManager.getInstance().registerViewWithId(
- mTextView1, mTextView1.getAccessibilityViewId());
- AccessibilityNodeIdManager.getInstance().registerViewWithId(
- mTextView2, mTextView2.getAccessibilityViewId());
-
- try {
- viewRootImpl.setView(mFrameLayout, new WindowManager.LayoutParams(), null);
-
- } catch (WindowManager.BadTokenException e) {
- // activity isn't running, we will ignore BadTokenException.
- }
-
- mAccessibilityInteractionController =
- new AccessibilityInteractionController(viewRootImpl);
- });
-
- }
-
- @After
- public void tearDown() throws Throwable {
- AccessibilityNodeIdManager.getInstance().unregisterViewWithId(
- mTextView1.getAccessibilityViewId());
- AccessibilityNodeIdManager.getInstance().unregisterViewWithId(
- mTextView2.getAccessibilityViewId());
- }
-
- /**
- * Tests a basic request for the root node with prefetch flag
- * {@link AccessibilityNodeInfo#FLAG_PREFETCH_DESCENDANTS}
- *
- * @throws RemoteException
- */
- @Test
- public void testFindRootView_withOneClient_shouldReturnRootNodeAndPrefetchDescendants()
- throws RemoteException {
- // Request for our FrameLayout
- sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1,
- mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS);
- mInstrumentation.waitForIdleSync();
-
- // Verify we get FrameLayout
- verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult(
- mFindInfoCaptor.capture(), eq(mMockClient1InteractionId));
- AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue();
- assertEquals(FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription());
-
- verify(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult(
- mPrefetchInfoListCaptor.capture(), eq(mMockClient1InteractionId));
- // The descendants are our two TextViews
- List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue();
- assertEquals(2, prefetchedNodes.size());
- assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetchedNodes.get(0).getContentDescription());
- assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(1).getContentDescription());
-
- }
-
- /**
- * Tests a basic request for TestTextView1's node with prefetch flag
- * {@link AccessibilityNodeInfo#FLAG_PREFETCH_SIBLINGS}
- *
- * @throws RemoteException
- */
- @Test
- public void testFindTextView_withOneClient_shouldReturnNodeAndPrefetchedSiblings()
- throws RemoteException {
- // Request for TextView1
- sendNodeRequestToController(AccessibilityNodeInfo.makeNodeId(
- mTextView1.getAccessibilityViewId(), AccessibilityNodeProvider.HOST_VIEW_ID),
- mMockClientCallback1, mMockClient1InteractionId, FLAG_PREFETCH_SIBLINGS);
- mInstrumentation.waitForIdleSync();
-
- // Verify we get TextView1
- verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult(
- mFindInfoCaptor.capture(), eq(mMockClient1InteractionId));
- AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue();
- assertEquals(TEXT_VIEW_1_DESCRIPTION, infoSentToService.getContentDescription());
-
- // Verify the prefetched sibling of TextView1 is TextView2
- verify(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult(
- mPrefetchInfoListCaptor.capture(), eq(mMockClient1InteractionId));
- // TextView2 is the prefetched sibling
- List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue();
- assertEquals(1, prefetchedNodes.size());
- assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(0).getContentDescription());
- }
-
- /**
- * Tests a series of controller requests to prevent prefetching.
- * Request 1: Client 1 requests the root node
- * Request 2: When the root node is initialized in
- * {@link TestFrameLayout#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)},
- * Client 2 requests TestTextView1's node
- *
- * Request 2 on the queue prevents prefetching for Request 1.
- *
- * @throws RemoteException
- */
- @Test
- public void testFindRootAndTextNodes_withTwoClients_shouldPreventClient1Prefetch()
- throws RemoteException {
- mFrameLayout.setAccessibilityDelegate(new View.AccessibilityDelegate() {
- @Override
- public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(host, info);
- final long nodeId = AccessibilityNodeInfo.makeNodeId(
- mTextView1.getAccessibilityViewId(),
- AccessibilityNodeProvider.HOST_VIEW_ID);
-
- // Enqueue a request when this node is found from a different service for
- // TextView1
- sendNodeRequestToController(nodeId, mMockClientCallback2,
- mMockClient2InteractionId, FLAG_PREFETCH_SIBLINGS);
- }
- });
- // Client 1 request for FrameLayout
- sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1,
- mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS);
-
- mInstrumentation.waitForIdleSync();
-
- // Verify client 1 gets FrameLayout
- verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult(
- mFindInfoCaptor.capture(), eq(mMockClient1InteractionId));
- AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue();
- assertEquals(FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription());
-
- // The second request is put in the queue in the FrameLayout's onInitializeA11yNodeInfo,
- // meaning prefetching is interrupted and does not even begin for the first request
- verify(mMockClientCallback1, never())
- .setPrefetchAccessibilityNodeInfoResult(anyList(), anyInt());
-
- // Verify client 2 gets TextView1
- verify(mMockClientCallback2).setFindAccessibilityNodeInfoResult(
- mFindInfoCaptor.capture(), eq(mMockClient2InteractionId));
- infoSentToService = mFindInfoCaptor.getValue();
- assertEquals(TEXT_VIEW_1_DESCRIPTION, infoSentToService.getContentDescription());
-
- // Verify the prefetched sibling of TextView1 is TextView2 (FLAG_PREFETCH_SIBLINGS)
- verify(mMockClientCallback2).setPrefetchAccessibilityNodeInfoResult(
- mPrefetchInfoListCaptor.capture(), eq(mMockClient2InteractionId));
- List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue();
- assertEquals(1, prefetchedNodes.size());
- assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(0).getContentDescription());
- }
-
- /**
- * Tests a series of controller same-service requests to interrupt prefetching and satisfy a
- * pending node request.
- * Request 1: Request the root node
- * Request 2: When TextTextView1's node is initialized as part of Request 1's prefetching,
- * request TestTextView1's node
- *
- * Request 1 prefetches TestTextView1's node, is interrupted by a pending request, and checks
- * if its prefetched nodes satisfy any pending requests. It satisfies Request 2's request for
- * TestTextView1's node. Request 2 is fulfilled, so it is removed from queue and does not
- * prefetch.
- *
- * @throws RemoteException
- */
- @Test
- public void testFindRootAndTextNode_withOneClient_shouldInterruptPrefetchAndSatisfyPendingMsg()
- throws RemoteException {
- mSendClient1RequestForTextAfterTextPrefetched = true;
-
- mTextView1.setAccessibilityDelegate(new View.AccessibilityDelegate(){
- @Override
- public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(host, info);
- info.setContentDescription(TEXT_VIEW_1_DESCRIPTION);
- final long nodeId = AccessibilityNodeInfo.makeNodeId(
- mTextView1.getAccessibilityViewId(),
- AccessibilityNodeProvider.HOST_VIEW_ID);
-
- if (mSendClient1RequestForTextAfterTextPrefetched) {
- // Prevent a loop when processing second request
- mSendClient1RequestForTextAfterTextPrefetched = false;
- // TextView1 is prefetched here after the FrameLayout is found. Now enqueue a
- // same-client request for TextView1
- sendNodeRequestToController(nodeId, mMockClientCallback1,
- ++mMockClient1InteractionId, FLAG_PREFETCH_SIBLINGS);
-
- }
- }
- });
- // Client 1 requests FrameLayout
- sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1,
- mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS);
-
- // Flush out all messages
- mInstrumentation.waitForIdleSync();
-
- // When TextView1 is prefetched for FrameLayout, we put a message on the queue in
- // TextView1's onInitializeA11yNodeInfo that requests for TextView1. The service thus get
- // two node results for FrameLayout and TextView1.
- verify(mMockClientCallback1, times(2))
- .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), anyInt());
-
- List<AccessibilityNodeInfo> foundNodes = mFindInfoCaptor.getAllValues();
- assertEquals(FRAME_LAYOUT_DESCRIPTION, foundNodes.get(0).getContentDescription());
- assertEquals(TEXT_VIEW_1_DESCRIPTION, foundNodes.get(1).getContentDescription());
-
- // The controller will look at FrameLayout's prefetched nodes and find matching nodes in
- // pending requests. The prefetched TextView1 matches the second request. The second
- // request was removed from queue and prefetching for this request never occurred.
- verify(mMockClientCallback1, times(1))
- .setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(),
- eq(mMockClient1InteractionId - 1));
- List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue();
- assertEquals(1, prefetchedNodes.size());
- assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetchedNodes.get(0).getContentDescription());
- }
-
- /**
- * Like above, but tests a series of controller requests from different services to interrupt
- * prefetching and satisfy a pending node request.
- *
- * @throws RemoteException
- */
- @Test
- public void testFindRootAndTextNode_withTwoClients_shouldInterruptPrefetchAndSatisfyPendingMsg()
- throws RemoteException {
- mSendClient2RequestForTextAfterTextPrefetched = true;
- mTextView1.setAccessibilityDelegate(new View.AccessibilityDelegate(){
- @Override
- public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(host, info);
- info.setContentDescription(TEXT_VIEW_1_DESCRIPTION);
- final long nodeId = AccessibilityNodeInfo.makeNodeId(
- mTextView1.getAccessibilityViewId(),
- AccessibilityNodeProvider.HOST_VIEW_ID);
-
- if (mSendClient2RequestForTextAfterTextPrefetched) {
- mSendClient2RequestForTextAfterTextPrefetched = false;
- // TextView1 is prefetched here. Now enqueue client 2's request for
- // TextView1
- sendNodeRequestToController(nodeId, mMockClientCallback2,
- mMockClient2InteractionId, FLAG_PREFETCH_SIBLINGS);
- }
- }
- });
- // Client 1 requests FrameLayout
- sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1,
- mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS);
-
- mInstrumentation.waitForIdleSync();
-
- // Verify client 1 gets FrameLayout
- verify(mMockClientCallback1, times(1))
- .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), anyInt());
- assertEquals(FRAME_LAYOUT_DESCRIPTION,
- mFindInfoCaptor.getValue().getContentDescription());
-
- // Verify client 1 has prefetched nodes
- verify(mMockClientCallback1, times(1))
- .setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(),
- eq(mMockClient1InteractionId));
-
- // Verify client 1's only prefetched node is TextView1
- List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue();
- assertEquals(1, prefetchedNodes.size());
- assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetchedNodes.get(0).getContentDescription());
-
- // Verify client 2 gets TextView1
- verify(mMockClientCallback2, times(1))
- .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), anyInt());
-
- assertEquals(TEXT_VIEW_1_DESCRIPTION, mFindInfoCaptor.getValue().getContentDescription());
-
- // The second request was removed from queue and prefetching for this client request never
- // occurred as it was satisfied.
- verify(mMockClientCallback2, never())
- .setPrefetchAccessibilityNodeInfoResult(anyList(), anyInt());
-
- }
-
- @Test
- public void testFindNodeById_withTwoDifferentPrefetchFlags_shouldNotSatisfyPendingRequest()
- throws RemoteException {
- mSendRequestForTextAndIncludeUnImportantViews = true;
- mTextView1.setAccessibilityDelegate(new View.AccessibilityDelegate(){
- @Override
- public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(host, info);
- info.setContentDescription(TEXT_VIEW_1_DESCRIPTION);
- final long nodeId = AccessibilityNodeInfo.makeNodeId(
- mTextView1.getAccessibilityViewId(),
- AccessibilityNodeProvider.HOST_VIEW_ID);
-
- if (mSendRequestForTextAndIncludeUnImportantViews) {
- mSendRequestForTextAndIncludeUnImportantViews = false;
- // TextView1 is prefetched here for client 1. Now enqueue a request from a
- // different client that holds different fetch flags for TextView1
- sendNodeRequestToController(nodeId, mMockClientCallback2,
- mMockClient2InteractionId,
- FLAG_PREFETCH_SIBLINGS | FLAG_INCLUDE_NOT_IMPORTANT_VIEWS);
- }
- }
- });
-
- // Mockito does not make copies of objects when called. It holds references, so
- // the captor would point to client 2's results after all requests are processed. Verify
- // prefetched node immediately
- doAnswer(invocation -> {
- List<AccessibilityNodeInfo> prefetched = invocation.getArgument(0);
- assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetched.get(0).getContentDescription());
- return null;
- }).when(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult(anyList(),
- eq(mMockClient1InteractionId));
-
- // Client 1 requests FrameLayout
- sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1,
- mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS);
-
- mInstrumentation.waitForIdleSync();
-
- // Verify client 1 gets FrameLayout
- verify(mMockClientCallback1, times(1))
- .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(),
- eq(mMockClient1InteractionId));
-
- assertEquals(FRAME_LAYOUT_DESCRIPTION,
- mFindInfoCaptor.getValue().getContentDescription());
-
- // Verify client 1 has prefetched results. The only prefetched node is TextView1
- // (from above doAnswer)
- verify(mMockClientCallback1, times(1))
- .setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(),
- eq(mMockClient1InteractionId));
-
- // Verify client 2 gets TextView1
- verify(mMockClientCallback2, times(1))
- .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(),
- eq(mMockClient2InteractionId));
- assertEquals(TEXT_VIEW_1_DESCRIPTION,
- mFindInfoCaptor.getValue().getContentDescription());
- // Verify client 2 has TextView2 as a prefetched node
- verify(mMockClientCallback2, times(1))
- .setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(),
- eq(mMockClient2InteractionId));
- List<AccessibilityNodeInfo> prefetchedNode = mPrefetchInfoListCaptor.getValue();
- assertEquals(1, prefetchedNode.size());
- assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNode.get(0).getContentDescription());
- }
-
- private void sendNodeRequestToController(long requestedNodeId,
- IAccessibilityInteractionConnectionCallback callback, int interactionId,
- int prefetchFlags) {
- final int processAndThreadId = callback == mMockClientCallback1
- ? MOCK_CLIENT_1_THREAD_AND_PROCESS_ID
- : MOCK_CLIENT_2_THREAD_AND_PROCESS_ID;
-
- mAccessibilityInteractionController.findAccessibilityNodeInfoByAccessibilityIdClientThread(
- requestedNodeId,
- null, interactionId,
- callback, prefetchFlags,
- processAndThreadId,
- processAndThreadId, null, null);
-
- }
-
- private class TestFrameLayout extends FrameLayout {
-
- TestFrameLayout(Context context) {
- super(context);
- }
-
- @Override
- public int getWindowVisibility() {
- // We aren't attached to a window so let's pretend
- return VISIBLE;
- }
-
- @Override
- public boolean isShown() {
- // Controller check
- return true;
- }
-
- @Override
- public int getAccessibilityViewId() {
- // static id doesn't reset after tests so return the same one
- return 0;
- }
-
- @Override
- public void addChildrenForAccessibility(ArrayList<View> outChildren) {
- // ViewGroup#addChildrenForAccessbility sorting logic will switch these two
- outChildren.add(mTextView1);
- outChildren.add(mTextView2);
- }
-
- @Override
- public boolean includeForAccessibility() {
- return true;
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setContentDescription(FRAME_LAYOUT_DESCRIPTION);
- }
- }
-
- private class TestTextView extends TextView {
- TestTextView(Context context) {
- super(context);
- }
-
- @Override
- public int getWindowVisibility() {
- return VISIBLE;
- }
-
- @Override
- public boolean isShown() {
- return true;
- }
-
- @Override
- public int getAccessibilityViewId() {
- return 1;
- }
-
- @Override
- public boolean includeForAccessibility() {
- return true;
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setContentDescription(TEXT_VIEW_1_DESCRIPTION);
- }
- }
-
- private class TestTextView2 extends TextView {
- TestTextView2(Context context) {
- super(context);
- }
-
- @Override
- public int getWindowVisibility() {
- return VISIBLE;
- }
-
- @Override
- public boolean isShown() {
- return true;
- }
-
- @Override
- public int getAccessibilityViewId() {
- return 2;
- }
-
- @Override
- public boolean includeForAccessibility() {
- return true;
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setContentDescription(TEXT_VIEW_2_DESCRIPTION);
- }
- }
-}