ActionReplacingCallbackTest.java revision f00cd14f17c0acd6bffe78947d32ea0a2900d139
1/* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.server.accessibility; 18 19import android.graphics.Region; 20import android.os.RemoteException; 21import android.view.MagnificationSpec; 22import android.view.accessibility.AccessibilityNodeInfo; 23import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; 24import android.view.accessibility.AccessibilityWindowInfo; 25import android.view.accessibility.IAccessibilityInteractionConnection; 26import android.view.accessibility.IAccessibilityInteractionConnectionCallback; 27 28import org.junit.Before; 29import org.junit.Test; 30import org.mockito.ArgumentCaptor; 31import org.mockito.Captor; 32import org.mockito.Mock; 33 34import java.util.Arrays; 35import java.util.HashSet; 36import java.util.List; 37 38import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS; 39import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS; 40import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK; 41import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE; 42import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND; 43import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_CONTEXT_CLICK; 44import static junit.framework.TestCase.assertTrue; 45import static org.junit.Assert.assertEquals; 46import static org.junit.Assert.assertFalse; 47import static org.junit.Assert.assertNotEquals; 48import static org.mockito.Matchers.anyObject; 49import static org.mockito.Matchers.eq; 50import static org.mockito.Mockito.doThrow; 51import static org.mockito.Mockito.verify; 52import static org.mockito.Mockito.verifyNoMoreInteractions; 53import static org.mockito.Mockito.when; 54import static org.mockito.MockitoAnnotations.initMocks; 55 56/** 57 * Tests for ActionReplacingCallback 58 */ 59public class ActionReplacingCallbackTest { 60 private static final int INTERACTION_ID = 0xBEEF; 61 private static final int INTERROGATING_PID = 0xFEED; 62 private static final int APP_WINDOW_ID = 0xACE; 63 private static final int NON_ROOT_NODE_ID = 0xAAAA5555; 64 private static final long INTERROGATING_TID = 0x1234FACE; 65 66 private static final AccessibilityAction[] ACTIONS_FROM_REPLACER = 67 {ACTION_CLICK, ACTION_EXPAND}; 68 private static final AccessibilityAction[] A11Y_FOCUS_ACTIONS = 69 {ACTION_ACCESSIBILITY_FOCUS, ACTION_CLEAR_ACCESSIBILITY_FOCUS}; 70 // We expect both the replacer actions and a11y focus actions to appear 71 private static final AccessibilityAction[] REQUIRED_ACTIONS_ON_ROOT_TO_SERVICE = 72 {ACTION_CLICK, ACTION_EXPAND, ACTION_ACCESSIBILITY_FOCUS, 73 ACTION_CLEAR_ACCESSIBILITY_FOCUS}; 74 75 @Mock IAccessibilityInteractionConnectionCallback mMockServiceCallback; 76 @Mock IAccessibilityInteractionConnection mMockReplacerConnection; 77 78 @Captor private ArgumentCaptor<Integer> mInteractionIdCaptor; 79 @Captor private ArgumentCaptor<AccessibilityNodeInfo> mInfoCaptor; 80 @Captor private ArgumentCaptor<List<AccessibilityNodeInfo>> mInfoListCaptor; 81 82 private ActionReplacingCallback mActionReplacingCallback; 83 private int mReplacerInteractionId; 84 85 @Before 86 public void setUp() throws RemoteException { 87 initMocks(this); 88 mActionReplacingCallback = new ActionReplacingCallback( 89 mMockServiceCallback, mMockReplacerConnection, INTERACTION_ID, INTERROGATING_PID, 90 INTERROGATING_TID); 91 verify(mMockReplacerConnection).findAccessibilityNodeInfoByAccessibilityId( 92 eq(AccessibilityNodeInfo.ROOT_NODE_ID), (Region) anyObject(), 93 mInteractionIdCaptor.capture(), eq(mActionReplacingCallback), eq(0), 94 eq(INTERROGATING_PID), eq(INTERROGATING_TID), (MagnificationSpec) anyObject(), 95 eq(null)); 96 mReplacerInteractionId = mInteractionIdCaptor.getValue().intValue(); 97 } 98 99 @Test 100 public void testConstructor_registersToGetRootNodeOfActionReplacer() throws RemoteException { 101 assertNotEquals(INTERACTION_ID, mReplacerInteractionId); 102 verifyNoMoreInteractions(mMockServiceCallback); 103 } 104 105 @Test 106 public void testCallbacks_singleRootNodeThenReplacer_returnsNodeWithReplacedActions() 107 throws RemoteException { 108 AccessibilityNodeInfo infoFromApp = AccessibilityNodeInfo.obtain(); 109 infoFromApp.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID, APP_WINDOW_ID); 110 infoFromApp.addAction(ACTION_CONTEXT_CLICK); 111 mActionReplacingCallback.setFindAccessibilityNodeInfoResult(infoFromApp, INTERACTION_ID); 112 verifyNoMoreInteractions(mMockServiceCallback); 113 114 mActionReplacingCallback.setFindAccessibilityNodeInfosResult(getReplacerNodes(), 115 mReplacerInteractionId); 116 117 verify(mMockServiceCallback).setFindAccessibilityNodeInfoResult(mInfoCaptor.capture(), 118 eq(INTERACTION_ID)); 119 AccessibilityNodeInfo infoSentToService = mInfoCaptor.getValue(); 120 assertEquals(AccessibilityNodeInfo.ROOT_NODE_ID, infoSentToService.getSourceNodeId()); 121 assertInfoHasExactlyTheseActions(infoSentToService, REQUIRED_ACTIONS_ON_ROOT_TO_SERVICE); 122 } 123 124 public void testCallbacks_singleNonrootNodeThenReplacer_returnsNodeWithNoActions() 125 throws RemoteException { 126 AccessibilityNodeInfo infoFromApp = AccessibilityNodeInfo.obtain(); 127 infoFromApp.setSourceNodeId(NON_ROOT_NODE_ID, APP_WINDOW_ID); 128 infoFromApp.addAction(ACTION_CONTEXT_CLICK); 129 mActionReplacingCallback.setFindAccessibilityNodeInfoResult(infoFromApp, INTERACTION_ID); 130 verifyNoMoreInteractions(mMockServiceCallback); 131 132 mActionReplacingCallback.setFindAccessibilityNodeInfosResult(getReplacerNodes(), 133 mReplacerInteractionId); 134 135 verify(mMockServiceCallback).setFindAccessibilityNodeInfoResult(mInfoCaptor.capture(), 136 eq(INTERACTION_ID)); 137 AccessibilityNodeInfo infoSentToService = mInfoCaptor.getValue(); 138 assertEquals(NON_ROOT_NODE_ID, infoSentToService.getSourceNodeId()); 139 assertTrue(infoSentToService.getActionList().isEmpty()); 140 } 141 142 public void testCallbacks_replacerThenSingleRootNode_returnsNodeWithReplacedActions() 143 throws RemoteException { 144 mActionReplacingCallback.setFindAccessibilityNodeInfosResult(getReplacerNodes(), 145 mReplacerInteractionId); 146 verifyNoMoreInteractions(mMockServiceCallback); 147 148 AccessibilityNodeInfo infoFromApp = AccessibilityNodeInfo.obtain(); 149 infoFromApp.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID, APP_WINDOW_ID); 150 infoFromApp.addAction(ACTION_CONTEXT_CLICK); 151 mActionReplacingCallback.setFindAccessibilityNodeInfoResult(infoFromApp, INTERACTION_ID); 152 153 verify(mMockServiceCallback).setFindAccessibilityNodeInfoResult(mInfoCaptor.capture(), 154 eq(INTERACTION_ID)); 155 AccessibilityNodeInfo infoSentToService = mInfoCaptor.getValue(); 156 assertEquals(AccessibilityNodeInfo.ROOT_NODE_ID, infoSentToService.getSourceNodeId()); 157 assertInfoHasExactlyTheseActions(infoSentToService, REQUIRED_ACTIONS_ON_ROOT_TO_SERVICE); 158 } 159 160 public void testCallbacks_multipleNodesThenReplacer_clearsActionsAndAddsSomeToRoot() 161 throws RemoteException { 162 mActionReplacingCallback 163 .setFindAccessibilityNodeInfosResult(getAppNodeList(), INTERACTION_ID); 164 verifyNoMoreInteractions(mMockServiceCallback); 165 166 mActionReplacingCallback.setFindAccessibilityNodeInfosResult(getReplacerNodes(), 167 mReplacerInteractionId); 168 169 verify(mMockServiceCallback).setFindAccessibilityNodeInfosResult(mInfoListCaptor.capture(), 170 eq(INTERACTION_ID)); 171 assertEquals(2, mInfoListCaptor.getValue().size()); 172 AccessibilityNodeInfo rootInfoSentToService = getNodeWithIdFromList( 173 mInfoListCaptor.getValue(), AccessibilityNodeInfo.ROOT_NODE_ID); 174 AccessibilityNodeInfo otherInfoSentToService = getNodeWithIdFromList( 175 mInfoListCaptor.getValue(), NON_ROOT_NODE_ID); 176 assertInfoHasExactlyTheseActions( 177 rootInfoSentToService, REQUIRED_ACTIONS_ON_ROOT_TO_SERVICE); 178 assertTrue(otherInfoSentToService.getActionList().isEmpty()); 179 } 180 181 public void testCallbacks_replacerThenMultipleNodes_clearsActionsAndAddsSomeToRoot() 182 throws RemoteException { 183 mActionReplacingCallback.setFindAccessibilityNodeInfosResult(getReplacerNodes(), 184 mReplacerInteractionId); 185 verifyNoMoreInteractions(mMockServiceCallback); 186 187 mActionReplacingCallback 188 .setFindAccessibilityNodeInfosResult(getAppNodeList(), INTERACTION_ID); 189 190 verify(mMockServiceCallback).setFindAccessibilityNodeInfosResult(mInfoListCaptor.capture(), 191 eq(INTERACTION_ID)); 192 assertEquals(2, mInfoListCaptor.getValue().size()); 193 AccessibilityNodeInfo rootInfoSentToService = getNodeWithIdFromList( 194 mInfoListCaptor.getValue(), AccessibilityNodeInfo.ROOT_NODE_ID); 195 AccessibilityNodeInfo otherInfoSentToService = getNodeWithIdFromList( 196 mInfoListCaptor.getValue(), NON_ROOT_NODE_ID); 197 assertInfoHasExactlyTheseActions( 198 rootInfoSentToService, REQUIRED_ACTIONS_ON_ROOT_TO_SERVICE); 199 assertTrue(otherInfoSentToService.getActionList().isEmpty()); 200 } 201 202 public void testConstructor_actionReplacerThrowsException_passesDataToService() 203 throws RemoteException { 204 doThrow(RemoteException.class).when(mMockReplacerConnection) 205 .findAccessibilityNodeInfoByAccessibilityId(eq(AccessibilityNodeInfo.ROOT_NODE_ID), 206 (Region) anyObject(), mInteractionIdCaptor.capture(), 207 eq(mActionReplacingCallback), eq(0), eq(INTERROGATING_PID), 208 eq(INTERROGATING_TID), (MagnificationSpec) anyObject(), eq(null)); 209 ActionReplacingCallback actionReplacingCallback = new ActionReplacingCallback( 210 mMockServiceCallback, mMockReplacerConnection, INTERACTION_ID, INTERROGATING_PID, 211 INTERROGATING_TID); 212 213 verifyNoMoreInteractions(mMockServiceCallback); 214 AccessibilityNodeInfo infoFromApp = AccessibilityNodeInfo.obtain(); 215 infoFromApp.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID, APP_WINDOW_ID); 216 infoFromApp.addAction(ACTION_CONTEXT_CLICK); 217 actionReplacingCallback.setFindAccessibilityNodeInfoResult(infoFromApp, INTERACTION_ID); 218 219 verify(mMockServiceCallback).setFindAccessibilityNodeInfoResult(mInfoCaptor.capture(), 220 eq(INTERACTION_ID)); 221 AccessibilityNodeInfo infoSentToService = mInfoCaptor.getValue(); 222 assertEquals(AccessibilityNodeInfo.ROOT_NODE_ID, infoSentToService.getSourceNodeId()); 223 assertEquals(1, infoSentToService.getActionList().size()); 224 assertEquals(ACTION_CONTEXT_CLICK, infoSentToService.getActionList().get(0)); 225 } 226 227 public void testSetPerformAccessibilityActionResult_actsAsPassThrough() throws RemoteException { 228 mActionReplacingCallback.setPerformAccessibilityActionResult(true, INTERACTION_ID); 229 verify(mMockServiceCallback).setPerformAccessibilityActionResult(true, INTERACTION_ID); 230 mActionReplacingCallback.setPerformAccessibilityActionResult(false, INTERACTION_ID); 231 verify(mMockServiceCallback).setPerformAccessibilityActionResult(false, INTERACTION_ID); 232 } 233 234 235 private List<AccessibilityNodeInfo> getReplacerNodes() { 236 AccessibilityNodeInfo root = AccessibilityNodeInfo.obtain(); 237 root.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID, 238 AccessibilityWindowInfo.PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID); 239 for (AccessibilityAction action : ACTIONS_FROM_REPLACER) { 240 root.addAction(action); 241 } 242 243 // Second node should have no effect 244 AccessibilityNodeInfo other = AccessibilityNodeInfo.obtain(); 245 other.setSourceNodeId(NON_ROOT_NODE_ID, 246 AccessibilityWindowInfo.PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID); 247 other.addAction(ACTION_COLLAPSE); 248 249 return Arrays.asList(root, other); 250 } 251 252 private void assertInfoHasExactlyTheseActions( 253 AccessibilityNodeInfo info, AccessibilityAction[] actions) { 254 List<AccessibilityAction> nodeActions = info.getActionList(); 255 assertEquals(new HashSet<AccessibilityAction>(nodeActions), 256 new HashSet<AccessibilityAction>(Arrays.asList(actions))); 257 } 258 259 private AccessibilityNodeInfo getNodeWithIdFromList( 260 List<AccessibilityNodeInfo> infos, long id) { 261 for (AccessibilityNodeInfo info : infos) { 262 if (info.getSourceNodeId() == id) { 263 return info; 264 } 265 } 266 assertTrue("Didn't find node", false); 267 return null; 268 } 269 270 private List<AccessibilityNodeInfo> getAppNodeList() { 271 AccessibilityNodeInfo rootInfoFromApp = AccessibilityNodeInfo.obtain(); 272 rootInfoFromApp.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID, APP_WINDOW_ID); 273 rootInfoFromApp.addAction(ACTION_CONTEXT_CLICK); 274 AccessibilityNodeInfo otherInfoFromApp = AccessibilityNodeInfo.obtain(); 275 otherInfoFromApp.setSourceNodeId(NON_ROOT_NODE_ID, APP_WINDOW_ID); 276 otherInfoFromApp.addAction(ACTION_CLICK); 277 return Arrays.asList(rootInfoFromApp, otherInfoFromApp); 278 } 279} 280