1 /*
2  * Copyright 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 
17 package android.view.accessibility;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertNotEquals;
21 import static org.junit.Assert.assertNotNull;
22 import static org.junit.Assert.assertNull;
23 import static org.mockito.Mockito.verifyZeroInteractions;
24 import static org.mockito.MockitoAnnotations.initMocks;
25 
26 import android.os.Bundle;
27 import android.os.RemoteException;
28 
29 import androidx.test.ext.junit.runners.AndroidJUnit4;
30 import androidx.test.filters.LargeTest;
31 
32 import libcore.util.EmptyArray;
33 
34 import org.junit.Before;
35 import org.junit.Test;
36 import org.junit.runner.RunWith;
37 import org.mockito.Mock;
38 
39 /**
40  * Tests for AccessibilityInteractionClient
41  */
42 @LargeTest
43 @RunWith(AndroidJUnit4.class)
44 public class AccessibilityInteractionClientTest {
45     private static final int MOCK_CONNECTION_ID = 0xabcd;
46     private static final int MOCK_CONNECTION_OTHER_ID = 0xabce;
47 
48 
49     private MockConnection mMockConnection = new MockConnection();
50     @Mock private AccessibilityCache mMockCache;
51 
52     @Before
setUp()53     public void setUp() {
54         initMocks(this);
55         AccessibilityInteractionClient.addConnection(
56                 MOCK_CONNECTION_ID, mMockConnection, /*initializeCache=*/true);
57     }
58 
59     /**
60      * When the AccessibilityCache refreshes the nodes it contains, it gets very confused if
61      * it is called to update itself during the refresh. It tries to update the node that it's
62      * in the process of refreshing, which leads to AccessibilityNodeInfos in inconsistent states.
63      */
64     @Test
findA11yNodeInfoByA11yId_whenBypassingCache_doesntTouchCache()65     public void findA11yNodeInfoByA11yId_whenBypassingCache_doesntTouchCache() {
66         AccessibilityInteractionClient.setCache(MOCK_CONNECTION_ID, mMockCache);
67         final int windowId = 0x1234;
68         final long accessibilityNodeId = 0x4321L;
69         AccessibilityNodeInfo nodeFromConnection = AccessibilityNodeInfo.obtain();
70         nodeFromConnection.setSourceNodeId(accessibilityNodeId, windowId);
71         mMockConnection.mInfoToReturn = nodeFromConnection;
72         AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
73         AccessibilityNodeInfo node = client.findAccessibilityNodeInfoByAccessibilityId(
74                 MOCK_CONNECTION_ID, windowId, accessibilityNodeId, true, 0, null);
75         assertEquals("Node got lost along the way", nodeFromConnection, node);
76 
77         verifyZeroInteractions(mMockCache);
78     }
79 
80     @Test
getCache_differentConnections_returnsDifferentCaches()81     public void getCache_differentConnections_returnsDifferentCaches() {
82         MockConnection mOtherMockConnection = new MockConnection();
83         AccessibilityInteractionClient.addConnection(
84                 MOCK_CONNECTION_OTHER_ID, mOtherMockConnection, /*initializeCache=*/true);
85 
86         AccessibilityCache firstCache = AccessibilityInteractionClient.getCache(MOCK_CONNECTION_ID);
87         AccessibilityCache secondCache = AccessibilityInteractionClient.getCache(
88                 MOCK_CONNECTION_OTHER_ID);
89         assertNotEquals(firstCache, secondCache);
90     }
91 
92     @Test
getCache_addConnectionWithoutCache_returnsNullCache()93     public void getCache_addConnectionWithoutCache_returnsNullCache() {
94         // Need to first remove from process cache
95         AccessibilityInteractionClient.removeConnection(MOCK_CONNECTION_OTHER_ID);
96 
97         MockConnection mOtherMockConnection = new MockConnection();
98         AccessibilityInteractionClient.addConnection(
99                 MOCK_CONNECTION_OTHER_ID, mOtherMockConnection, /*initializeCache=*/false);
100 
101         AccessibilityCache cache = AccessibilityInteractionClient.getCache(
102                 MOCK_CONNECTION_OTHER_ID);
103         assertNull(cache);
104     }
105 
106     @Test
getCache_removeConnection_returnsNull()107     public void getCache_removeConnection_returnsNull() {
108         AccessibilityCache cache = AccessibilityInteractionClient.getCache(MOCK_CONNECTION_ID);
109         assertNotNull(cache);
110 
111         AccessibilityInteractionClient.removeConnection(MOCK_CONNECTION_ID);
112         cache = AccessibilityInteractionClient.getCache(MOCK_CONNECTION_ID);
113         assertNull(cache);
114     }
115 
116     private static class MockConnection extends AccessibilityServiceConnectionImpl {
117         AccessibilityNodeInfo mInfoToReturn;
118 
119         @Override
findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId, long accessibilityNodeId, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, long threadId, Bundle arguments)120         public String[] findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
121                 long accessibilityNodeId, int interactionId,
122                 IAccessibilityInteractionConnectionCallback callback, int flags, long threadId,
123                 Bundle arguments) {
124             try {
125                 callback.setFindAccessibilityNodeInfoResult(mInfoToReturn, interactionId);
126             } catch (RemoteException e) {
127                 throw new RuntimeException(e);
128             }
129             return EmptyArray.STRING;
130         }
131     }
132 }
133