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