Merge "Match attributes to actions for pip a11y" into oc-dev
This commit is contained in:
@@ -26,6 +26,7 @@ import android.view.accessibility.IAccessibilityInteractionConnection;
|
||||
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@@ -96,7 +97,7 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection
|
||||
mNodeFromOriginalWindow = info;
|
||||
} else {
|
||||
Slog.e(LOG_TAG, "Callback with unexpected interactionId");
|
||||
throw new RuntimeException("Callback with unexpected interactionId"); // Remove
|
||||
return;
|
||||
}
|
||||
|
||||
mSingleNodeCallbackHappened = true;
|
||||
@@ -119,7 +120,7 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection
|
||||
mNodesWithReplacementActions = infos;
|
||||
} else {
|
||||
Slog.e(LOG_TAG, "Callback with unexpected interactionId");
|
||||
throw new RuntimeException("Callback with unexpected interactionId"); // Remove
|
||||
return;
|
||||
}
|
||||
callbackForSingleNode = mSingleNodeCallbackHappened;
|
||||
callbackForMultipleNodes = mMultiNodeCallbackHappened;
|
||||
@@ -147,7 +148,7 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection
|
||||
if (DEBUG) {
|
||||
Slog.e(LOG_TAG, "Extra callback");
|
||||
}
|
||||
throw new RuntimeException("Extra callback"); // Replace with return before submit
|
||||
return;
|
||||
}
|
||||
if (mNodeFromOriginalWindow != null) {
|
||||
replaceActionsOnInfoLocked(mNodeFromOriginalWindow);
|
||||
@@ -172,7 +173,7 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection
|
||||
if (DEBUG) {
|
||||
Slog.e(LOG_TAG, "Extra callback");
|
||||
}
|
||||
throw new RuntimeException("Extra callback"); // Replace with return before submit
|
||||
return;
|
||||
}
|
||||
if (mNodesFromOriginalWindow != null) {
|
||||
for (int i = 0; i < mNodesFromOriginalWindow.size(); i++) {
|
||||
@@ -180,7 +181,8 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection
|
||||
}
|
||||
}
|
||||
recycleReplaceActionNodesLocked();
|
||||
nodesToReturn = mNodesFromOriginalWindow;
|
||||
nodesToReturn = (mNodesFromOriginalWindow == null)
|
||||
? null : new ArrayList<>(mNodesFromOriginalWindow);
|
||||
mDone = true;
|
||||
}
|
||||
try {
|
||||
@@ -195,6 +197,12 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection
|
||||
@GuardedBy("mLock")
|
||||
private void replaceActionsOnInfoLocked(AccessibilityNodeInfo info) {
|
||||
info.removeAllActions();
|
||||
info.setClickable(false);
|
||||
info.setFocusable(false);
|
||||
info.setContextClickable(false);
|
||||
info.setScrollable(false);
|
||||
info.setLongClickable(false);
|
||||
info.setDismissable(false);
|
||||
// We currently only replace actions for the root node
|
||||
if ((info.getSourceNodeId() == AccessibilityNodeInfo.ROOT_NODE_ID)
|
||||
&& mNodesWithReplacementActions != null) {
|
||||
@@ -213,6 +221,12 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -220,6 +234,7 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection
|
||||
|
||||
@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();
|
||||
|
||||
@@ -25,6 +25,9 @@ import android.view.accessibility.AccessibilityWindowInfo;
|
||||
import android.view.accessibility.IAccessibilityInteractionConnection;
|
||||
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
|
||||
|
||||
import org.hamcrest.BaseMatcher;
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
@@ -45,12 +48,13 @@ import static junit.framework.TestCase.assertTrue;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Matchers.anyObject;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.MockitoAnnotations.initMocks;
|
||||
|
||||
/**
|
||||
@@ -63,15 +67,50 @@ public class ActionReplacingCallbackTest {
|
||||
private static final int NON_ROOT_NODE_ID = 0xAAAA5555;
|
||||
private static final long INTERROGATING_TID = 0x1234FACE;
|
||||
|
||||
private static final AccessibilityAction[] ACTIONS_FROM_REPLACER =
|
||||
{ACTION_CLICK, ACTION_EXPAND};
|
||||
private static final AccessibilityAction[] A11Y_FOCUS_ACTIONS =
|
||||
{ACTION_ACCESSIBILITY_FOCUS, ACTION_CLEAR_ACCESSIBILITY_FOCUS};
|
||||
// We expect both the replacer actions and a11y focus actions to appear
|
||||
private static final AccessibilityAction[] REQUIRED_ACTIONS_ON_ROOT_TO_SERVICE =
|
||||
{ACTION_CLICK, ACTION_EXPAND, ACTION_ACCESSIBILITY_FOCUS,
|
||||
ACTION_CLEAR_ACCESSIBILITY_FOCUS};
|
||||
|
||||
private static final Matcher<AccessibilityNodeInfo> HAS_NO_ACTIONS =
|
||||
new BaseMatcher<AccessibilityNodeInfo>() {
|
||||
@Override
|
||||
public boolean matches(Object o) {
|
||||
AccessibilityNodeInfo node = (AccessibilityNodeInfo) o;
|
||||
if (!node.getActionList().isEmpty()) return false;
|
||||
return (!node.isScrollable() && !node.isLongClickable() && !node.isClickable()
|
||||
&& !node.isContextClickable() && !node.isDismissable() && !node.isFocusable());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("Has no actions");
|
||||
}
|
||||
};
|
||||
|
||||
private static final Matcher<AccessibilityNodeInfo> HAS_EXPECTED_ACTIONS_ON_ROOT =
|
||||
new BaseMatcher<AccessibilityNodeInfo>() {
|
||||
@Override
|
||||
public boolean matches(Object o) {
|
||||
AccessibilityNodeInfo node = (AccessibilityNodeInfo) o;
|
||||
List<AccessibilityAction> actions = node.getActionList();
|
||||
if ((actions.size() != 4) || !actions.contains(ACTION_CLICK)
|
||||
|| !actions.contains(ACTION_EXPAND)
|
||||
|| !actions.contains(ACTION_ACCESSIBILITY_FOCUS)) {
|
||||
return false;
|
||||
}
|
||||
return (!node.isScrollable() && !node.isLongClickable()
|
||||
&& !node.isLongClickable() && node.isClickable()
|
||||
&& !node.isContextClickable() && !node.isDismissable()
|
||||
&& !node.isFocusable());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("Has only 4 actions expected on root");
|
||||
}
|
||||
};
|
||||
|
||||
@Mock IAccessibilityInteractionConnectionCallback mMockServiceCallback;
|
||||
@Mock IAccessibilityInteractionConnection mMockReplacerConnection;
|
||||
|
||||
@@ -118,9 +157,10 @@ public class ActionReplacingCallbackTest {
|
||||
eq(INTERACTION_ID));
|
||||
AccessibilityNodeInfo infoSentToService = mInfoCaptor.getValue();
|
||||
assertEquals(AccessibilityNodeInfo.ROOT_NODE_ID, infoSentToService.getSourceNodeId());
|
||||
assertInfoHasExactlyTheseActions(infoSentToService, REQUIRED_ACTIONS_ON_ROOT_TO_SERVICE);
|
||||
assertThat(infoSentToService, HAS_EXPECTED_ACTIONS_ON_ROOT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCallbacks_singleNonrootNodeThenReplacer_returnsNodeWithNoActions()
|
||||
throws RemoteException {
|
||||
AccessibilityNodeInfo infoFromApp = AccessibilityNodeInfo.obtain();
|
||||
@@ -136,9 +176,10 @@ public class ActionReplacingCallbackTest {
|
||||
eq(INTERACTION_ID));
|
||||
AccessibilityNodeInfo infoSentToService = mInfoCaptor.getValue();
|
||||
assertEquals(NON_ROOT_NODE_ID, infoSentToService.getSourceNodeId());
|
||||
assertTrue(infoSentToService.getActionList().isEmpty());
|
||||
assertThat(infoSentToService, HAS_NO_ACTIONS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCallbacks_replacerThenSingleRootNode_returnsNodeWithReplacedActions()
|
||||
throws RemoteException {
|
||||
mActionReplacingCallback.setFindAccessibilityNodeInfosResult(getReplacerNodes(),
|
||||
@@ -154,9 +195,10 @@ public class ActionReplacingCallbackTest {
|
||||
eq(INTERACTION_ID));
|
||||
AccessibilityNodeInfo infoSentToService = mInfoCaptor.getValue();
|
||||
assertEquals(AccessibilityNodeInfo.ROOT_NODE_ID, infoSentToService.getSourceNodeId());
|
||||
assertInfoHasExactlyTheseActions(infoSentToService, REQUIRED_ACTIONS_ON_ROOT_TO_SERVICE);
|
||||
assertThat(infoSentToService, HAS_EXPECTED_ACTIONS_ON_ROOT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCallbacks_multipleNodesThenReplacer_clearsActionsAndAddsSomeToRoot()
|
||||
throws RemoteException {
|
||||
mActionReplacingCallback
|
||||
@@ -173,11 +215,11 @@ public class ActionReplacingCallbackTest {
|
||||
mInfoListCaptor.getValue(), AccessibilityNodeInfo.ROOT_NODE_ID);
|
||||
AccessibilityNodeInfo otherInfoSentToService = getNodeWithIdFromList(
|
||||
mInfoListCaptor.getValue(), NON_ROOT_NODE_ID);
|
||||
assertInfoHasExactlyTheseActions(
|
||||
rootInfoSentToService, REQUIRED_ACTIONS_ON_ROOT_TO_SERVICE);
|
||||
assertTrue(otherInfoSentToService.getActionList().isEmpty());
|
||||
assertThat(rootInfoSentToService, HAS_EXPECTED_ACTIONS_ON_ROOT);
|
||||
assertThat(otherInfoSentToService, HAS_NO_ACTIONS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCallbacks_replacerThenMultipleNodes_clearsActionsAndAddsSomeToRoot()
|
||||
throws RemoteException {
|
||||
mActionReplacingCallback.setFindAccessibilityNodeInfosResult(getReplacerNodes(),
|
||||
@@ -194,18 +236,18 @@ public class ActionReplacingCallbackTest {
|
||||
mInfoListCaptor.getValue(), AccessibilityNodeInfo.ROOT_NODE_ID);
|
||||
AccessibilityNodeInfo otherInfoSentToService = getNodeWithIdFromList(
|
||||
mInfoListCaptor.getValue(), NON_ROOT_NODE_ID);
|
||||
assertInfoHasExactlyTheseActions(
|
||||
rootInfoSentToService, REQUIRED_ACTIONS_ON_ROOT_TO_SERVICE);
|
||||
assertTrue(otherInfoSentToService.getActionList().isEmpty());
|
||||
assertThat(rootInfoSentToService, HAS_EXPECTED_ACTIONS_ON_ROOT);
|
||||
assertThat(otherInfoSentToService, HAS_NO_ACTIONS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructor_actionReplacerThrowsException_passesDataToService()
|
||||
throws RemoteException {
|
||||
doThrow(RemoteException.class).when(mMockReplacerConnection)
|
||||
.findAccessibilityNodeInfoByAccessibilityId(eq(AccessibilityNodeInfo.ROOT_NODE_ID),
|
||||
(Region) anyObject(), mInteractionIdCaptor.capture(),
|
||||
eq(mActionReplacingCallback), eq(0), eq(INTERROGATING_PID),
|
||||
eq(INTERROGATING_TID), (MagnificationSpec) anyObject(), eq(null));
|
||||
(Region) anyObject(), anyInt(), (ActionReplacingCallback) anyObject(),
|
||||
eq(0), eq(INTERROGATING_PID), eq(INTERROGATING_TID),
|
||||
(MagnificationSpec) anyObject(), eq(null));
|
||||
ActionReplacingCallback actionReplacingCallback = new ActionReplacingCallback(
|
||||
mMockServiceCallback, mMockReplacerConnection, INTERACTION_ID, INTERROGATING_PID,
|
||||
INTERROGATING_TID);
|
||||
@@ -214,16 +256,17 @@ public class ActionReplacingCallbackTest {
|
||||
AccessibilityNodeInfo infoFromApp = AccessibilityNodeInfo.obtain();
|
||||
infoFromApp.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID, APP_WINDOW_ID);
|
||||
infoFromApp.addAction(ACTION_CONTEXT_CLICK);
|
||||
infoFromApp.setContextClickable(true);
|
||||
actionReplacingCallback.setFindAccessibilityNodeInfoResult(infoFromApp, INTERACTION_ID);
|
||||
|
||||
verify(mMockServiceCallback).setFindAccessibilityNodeInfoResult(mInfoCaptor.capture(),
|
||||
eq(INTERACTION_ID));
|
||||
AccessibilityNodeInfo infoSentToService = mInfoCaptor.getValue();
|
||||
assertEquals(AccessibilityNodeInfo.ROOT_NODE_ID, infoSentToService.getSourceNodeId());
|
||||
assertEquals(1, infoSentToService.getActionList().size());
|
||||
assertEquals(ACTION_CONTEXT_CLICK, infoSentToService.getActionList().get(0));
|
||||
assertThat(infoSentToService, HAS_NO_ACTIONS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetPerformAccessibilityActionResult_actsAsPassThrough() throws RemoteException {
|
||||
mActionReplacingCallback.setPerformAccessibilityActionResult(true, INTERACTION_ID);
|
||||
verify(mMockServiceCallback).setPerformAccessibilityActionResult(true, INTERACTION_ID);
|
||||
@@ -236,9 +279,9 @@ public class ActionReplacingCallbackTest {
|
||||
AccessibilityNodeInfo root = AccessibilityNodeInfo.obtain();
|
||||
root.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID,
|
||||
AccessibilityWindowInfo.PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID);
|
||||
for (AccessibilityAction action : ACTIONS_FROM_REPLACER) {
|
||||
root.addAction(action);
|
||||
}
|
||||
root.addAction(ACTION_CLICK);
|
||||
root.addAction(ACTION_EXPAND);
|
||||
root.setClickable(true);
|
||||
|
||||
// Second node should have no effect
|
||||
AccessibilityNodeInfo other = AccessibilityNodeInfo.obtain();
|
||||
@@ -249,13 +292,6 @@ public class ActionReplacingCallbackTest {
|
||||
return Arrays.asList(root, other);
|
||||
}
|
||||
|
||||
private void assertInfoHasExactlyTheseActions(
|
||||
AccessibilityNodeInfo info, AccessibilityAction[] actions) {
|
||||
List<AccessibilityAction> nodeActions = info.getActionList();
|
||||
assertEquals(new HashSet<AccessibilityAction>(nodeActions),
|
||||
new HashSet<AccessibilityAction>(Arrays.asList(actions)));
|
||||
}
|
||||
|
||||
private AccessibilityNodeInfo getNodeWithIdFromList(
|
||||
List<AccessibilityNodeInfo> infos, long id) {
|
||||
for (AccessibilityNodeInfo info : infos) {
|
||||
|
||||
Reference in New Issue
Block a user