1 /**
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11  * express or implied. See the License for the specific language governing permissions and
12  * limitations under the License.
13  */
14 
15 package android.accessibilityservice.cts;
16 
17 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
18 import static android.accessibilityservice.cts.utils.AsyncUtils.DEFAULT_TIMEOUT_MS;
19 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS;
20 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS;
21 
22 import static org.junit.Assert.assertEquals;
23 import static org.junit.Assert.assertFalse;
24 import static org.junit.Assert.assertNotNull;
25 import static org.junit.Assert.assertNull;
26 import static org.junit.Assert.assertTrue;
27 import static org.junit.Assert.fail;
28 
29 import android.accessibilityservice.AccessibilityServiceInfo;
30 import android.accessibilityservice.cts.R;
31 import android.accessibilityservice.cts.activities.AccessibilityFocusAndInputFocusSyncActivity;
32 import android.app.Instrumentation;
33 import android.app.UiAutomation;
34 import android.platform.test.annotations.Presubmit;
35 import android.test.suitebuilder.annotation.MediumTest;
36 import android.view.View;
37 import android.view.accessibility.AccessibilityEvent;
38 import android.view.accessibility.AccessibilityNodeInfo;
39 
40 import androidx.test.InstrumentationRegistry;
41 import androidx.test.rule.ActivityTestRule;
42 import androidx.test.runner.AndroidJUnit4;
43 
44 import org.junit.AfterClass;
45 import org.junit.Before;
46 import org.junit.BeforeClass;
47 import org.junit.Rule;
48 import org.junit.Test;
49 import org.junit.runner.RunWith;
50 
51 import java.util.LinkedList;
52 import java.util.Queue;
53 import java.util.concurrent.atomic.AtomicBoolean;
54 
55 /**
56  * Test cases for testing the accessibility focus APIs exposed to accessibility
57  * services. These APIs allow moving accessibility focus in the view tree from
58  * an AccessiiblityService. Specifically, this activity is for verifying the the
59  * sync between accessibility and input focus.
60  */
61 @RunWith(AndroidJUnit4.class)
62 public class AccessibilityFocusAndInputFocusSyncTest {
63     private static Instrumentation sInstrumentation;
64     private static UiAutomation sUiAutomation;
65 
66     private AccessibilityFocusAndInputFocusSyncActivity mActivity;
67 
68     @Rule
69     public ActivityTestRule<AccessibilityFocusAndInputFocusSyncActivity> mActivityRule =
70             new ActivityTestRule<>(AccessibilityFocusAndInputFocusSyncActivity.class, false, false);
71 
72     @BeforeClass
oneTimeSetup()73     public static void oneTimeSetup() throws Exception {
74         sInstrumentation = InstrumentationRegistry.getInstrumentation();
75         sUiAutomation = sInstrumentation.getUiAutomation();
76         AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
77         info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE;
78         info.flags &= ~AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
79         sUiAutomation.setServiceInfo(info);
80     }
81 
82     @AfterClass
postTestTearDown()83     public static void postTestTearDown() {
84         sUiAutomation.destroy();
85     }
86 
87     @Before
setUp()88     public void setUp() throws Exception {
89         mActivity = launchActivityAndWaitForItToBeOnscreen(
90                 sInstrumentation, sUiAutomation, mActivityRule);
91     }
92 
93     @MediumTest
94     @Presubmit
95     @Test
testFindAccessibilityFocus()96     public void testFindAccessibilityFocus() throws Exception {
97         sInstrumentation.runOnMainSync(() -> {
98             mActivity.findViewById(R.id.firstEditText).requestFocus();
99         });
100         // Get the view that has input and accessibility focus.
101         final AccessibilityNodeInfo expected = sUiAutomation
102                 .getRootInActiveWindow().findAccessibilityNodeInfosByText(
103                         sInstrumentation.getContext().getString(R.string.firstEditText)).get(0);
104         assertNotNull(expected);
105         assertFalse(expected.isAccessibilityFocused());
106         assertTrue(expected.isFocused());
107 
108         sUiAutomation.executeAndWaitForEvent(
109                 () -> assertTrue(expected.performAction(ACTION_ACCESSIBILITY_FOCUS)),
110                 (event) ->
111                         event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
112                 DEFAULT_TIMEOUT_MS);
113 
114         // Get the second expected node info.
115         AccessibilityNodeInfo received = sUiAutomation
116                 .getRootInActiveWindow().findFocus(AccessibilityNodeInfo.FOCUS_ACCESSIBILITY);
117         assertNotNull(received);
118         assertTrue(received.isAccessibilityFocused());
119 
120         // Make sure we got the expected focusable.
121         assertEquals(expected, received);
122     }
123 
124     @MediumTest
125     @Presubmit
126     @Test
testInitialStateNoAccessibilityFocus()127     public void testInitialStateNoAccessibilityFocus() throws Exception {
128         // Get the root which is only accessibility focused.
129         AccessibilityNodeInfo focused = sUiAutomation
130                 .getRootInActiveWindow().findFocus(AccessibilityNodeInfo.FOCUS_ACCESSIBILITY);
131         assertNull(focused);
132     }
133 
134     @MediumTest
135     @Test
testActionAccessibilityFocus()136     public void testActionAccessibilityFocus() throws Exception {
137         // Get the root linear layout info.
138         final AccessibilityNodeInfo rootLinearLayout = sUiAutomation
139                 .getRootInActiveWindow().findAccessibilityNodeInfosByText(
140                         sInstrumentation.getContext().getString(R.string.rootLinearLayout)).get(0);
141         assertNotNull(rootLinearLayout);
142         assertFalse(rootLinearLayout.isAccessibilityFocused());
143 
144         sUiAutomation.executeAndWaitForEvent(
145                 () -> assertTrue(rootLinearLayout.performAction(ACTION_ACCESSIBILITY_FOCUS)),
146                 (event) ->
147                         event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
148                 DEFAULT_TIMEOUT_MS);
149 
150         // Get the node info again.
151         rootLinearLayout.refresh();
152 
153         // Check if the node info is focused.
154         assertTrue(rootLinearLayout.isAccessibilityFocused());
155     }
156 
157     @MediumTest
158     @Presubmit
159     @Test
testActionClearAccessibilityFocus()160     public void testActionClearAccessibilityFocus() throws Exception {
161         // Get the root linear layout info.
162         final AccessibilityNodeInfo rootLinearLayout = sUiAutomation
163                 .getRootInActiveWindow().findAccessibilityNodeInfosByText(
164                         sInstrumentation.getContext().getString(R.string.rootLinearLayout)).get(0);
165         assertNotNull(rootLinearLayout);
166 
167         sUiAutomation.executeAndWaitForEvent(
168                 () -> assertTrue(rootLinearLayout.performAction(ACTION_ACCESSIBILITY_FOCUS)),
169                 (event) ->
170                         event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
171                 DEFAULT_TIMEOUT_MS);
172 
173         // Refresh the node info.
174         rootLinearLayout.refresh();
175 
176         // Check if the node info is focused.
177         assertTrue(rootLinearLayout.isAccessibilityFocused());
178 
179         sUiAutomation.executeAndWaitForEvent(
180                 () -> assertTrue(rootLinearLayout.performAction(ACTION_CLEAR_ACCESSIBILITY_FOCUS)),
181                 (event) -> event.getEventType()
182                         == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED,
183                 DEFAULT_TIMEOUT_MS);
184 
185         // Refresh the node info.
186         rootLinearLayout.refresh();
187 
188         // Check if the node info is not focused.
189         assertFalse(rootLinearLayout.isAccessibilityFocused());
190     }
191 
192     @MediumTest
193     @Presubmit
194     @Test
testOnlyOneNodeHasAccessibilityFocus()195     public void testOnlyOneNodeHasAccessibilityFocus() throws Exception {
196         // Get the first not focused edit text.
197         final AccessibilityNodeInfo firstEditText = sUiAutomation
198                 .getRootInActiveWindow().findAccessibilityNodeInfosByText(
199                         sInstrumentation.getContext().getString(R.string.firstEditText)).get(0);
200         assertNotNull(firstEditText);
201         assertTrue(firstEditText.isFocusable());
202         assertFalse(firstEditText.isAccessibilityFocused());
203 
204         sUiAutomation.executeAndWaitForEvent(
205                 () -> assertTrue(firstEditText.performAction(ACTION_ACCESSIBILITY_FOCUS)),
206                 (event) ->
207                         event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
208                 DEFAULT_TIMEOUT_MS);
209 
210         // Get the second not focused edit text.
211         final AccessibilityNodeInfo secondEditText = sUiAutomation
212                 .getRootInActiveWindow().findAccessibilityNodeInfosByText(
213                         sInstrumentation.getContext().getString(R.string.secondEditText)).get(0);
214         assertNotNull(secondEditText);
215         assertTrue(secondEditText.isFocusable());
216         assertFalse(secondEditText.isFocused());
217         assertFalse(secondEditText.isAccessibilityFocused());
218 
219         sUiAutomation.executeAndWaitForEvent(
220                 () -> assertTrue(secondEditText.performAction(ACTION_ACCESSIBILITY_FOCUS)),
221                 (event) ->
222                         event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
223                 DEFAULT_TIMEOUT_MS);
224 
225         // Get the node info again.
226         secondEditText.refresh();
227 
228         // Make sure no other node has accessibility focus.
229         AccessibilityNodeInfo root = sUiAutomation.getRootInActiveWindow();
230         Queue<AccessibilityNodeInfo> workQueue = new LinkedList<AccessibilityNodeInfo>();
231         workQueue.add(root);
232         while (!workQueue.isEmpty()) {
233             AccessibilityNodeInfo current = workQueue.poll();
234             if (current.isAccessibilityFocused() && !current.equals(secondEditText)) {
235                 fail();
236             }
237             final int childCount = current.getChildCount();
238             for (int i = 0; i < childCount; i++) {
239                 AccessibilityNodeInfo child = current.getChild(i);
240                 if (child != null) {
241                     workQueue.offer(child);
242                 }
243             }
244         }
245     }
246 
247     @Presubmit
248     @Test
testScreenReaderFocusableAttribute_reportedToAccessibility()249     public void testScreenReaderFocusableAttribute_reportedToAccessibility() {
250         final AccessibilityNodeInfo secondButton = sUiAutomation.getRootInActiveWindow()
251                 .findAccessibilityNodeInfosByText(
252                         sInstrumentation.getContext().getString(R.string.secondButton)).get(0);
253         assertTrue("Screen reader focusability not propagated from xml to accessibility",
254                 secondButton.isScreenReaderFocusable());
255 
256         // Verify the setter and getter work
257         final AtomicBoolean isScreenReaderFocusableAtomic = new AtomicBoolean(false);
258         sInstrumentation.runOnMainSync(() -> {
259             View secondButtonView = mActivity.findViewById(R.id.secondButton);
260             secondButtonView.setScreenReaderFocusable(false);
261             isScreenReaderFocusableAtomic.set(secondButtonView.isScreenReaderFocusable());
262         });
263 
264         assertFalse("isScreenReaderFocusable did not change after value set",
265                 isScreenReaderFocusableAtomic.get());
266 
267         secondButton.refresh();
268         assertFalse(
269                 "Screen reader focusability not propagated to accessibility after calling setter",
270                 secondButton.isScreenReaderFocusable());
271     }
272 }
273