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