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.AccessibilityEventFilterUtils.filterForEventTypeWithAction; 18 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen; 19 import static android.accessibilityservice.cts.utils.AsyncUtils.DEFAULT_TIMEOUT_MS; 20 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED; 21 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED; 22 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS; 23 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS; 24 25 import static org.junit.Assert.assertEquals; 26 import static org.junit.Assert.assertFalse; 27 import static org.junit.Assert.assertNotNull; 28 import static org.junit.Assert.assertNull; 29 import static org.junit.Assert.assertTrue; 30 import static org.junit.Assert.fail; 31 32 import android.accessibility.cts.common.AccessibilityDumpOnFailureRule; 33 import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule; 34 import android.accessibilityservice.AccessibilityServiceInfo; 35 import android.accessibilityservice.cts.activities.AccessibilityFocusAndInputFocusSyncActivity; 36 import android.app.Instrumentation; 37 import android.app.UiAutomation; 38 import android.content.Context; 39 import android.graphics.Bitmap; 40 import android.graphics.Color; 41 import android.graphics.Point; 42 import android.os.Environment; 43 import android.os.SystemClock; 44 import android.platform.test.annotations.Presubmit; 45 import android.view.Display; 46 import android.view.View; 47 import android.view.accessibility.AccessibilityManager; 48 import android.view.accessibility.AccessibilityNodeInfo; 49 50 import androidx.test.InstrumentationRegistry; 51 import androidx.test.filters.MediumTest; 52 import androidx.test.rule.ActivityTestRule; 53 import androidx.test.runner.AndroidJUnit4; 54 55 import com.android.compatibility.common.util.BitmapUtils; 56 import com.android.compatibility.common.util.CddTest; 57 import com.android.compatibility.common.util.PollingCheck; 58 59 import org.junit.AfterClass; 60 import org.junit.Before; 61 import org.junit.BeforeClass; 62 import org.junit.Rule; 63 import org.junit.Test; 64 import org.junit.rules.RuleChain; 65 import org.junit.rules.TestName; 66 import org.junit.runner.RunWith; 67 68 import java.util.LinkedList; 69 import java.util.Queue; 70 import java.util.concurrent.atomic.AtomicBoolean; 71 72 /** 73 * Test cases for testing the accessibility focus APIs exposed to accessibility 74 * services. These APIs allow moving accessibility focus in the view tree from 75 * an AccessiiblityService. Specifically, this activity is for verifying the the 76 * sync between accessibility and input focus. 77 */ 78 @RunWith(AndroidJUnit4.class) 79 @CddTest(requirements = {"3.10/C-1-1,C-1-2"}) 80 @Presubmit 81 public class AccessibilityFocusAndInputFocusSyncTest { 82 /** 83 * The delay time is for next UI frame rendering out. 84 */ 85 private static final long SCREEN_FRAME_RENDERING_OUT_TIME_MILLIS = 500; 86 87 private static Instrumentation sInstrumentation; 88 private static UiAutomation sUiAutomation; 89 private static Context sContext; 90 private static AccessibilityManager sAccessibilityManager; 91 private static int sFocusStrokeWidthDefaultValue; 92 private static int sFocusColorDefaultValue; 93 94 private AccessibilityFocusAndInputFocusSyncActivity mActivity; 95 96 private ActivityTestRule<AccessibilityFocusAndInputFocusSyncActivity> mActivityRule = 97 new ActivityTestRule<>(AccessibilityFocusAndInputFocusSyncActivity.class, false, false); 98 99 private InstrumentedAccessibilityServiceTestRule<StubFocusIndicatorService> 100 mFocusIndicatorServiceRule = new InstrumentedAccessibilityServiceTestRule<>( 101 StubFocusIndicatorService.class, false); 102 103 private AccessibilityDumpOnFailureRule mDumpOnFailureRule = 104 new AccessibilityDumpOnFailureRule(); 105 106 @Rule 107 public final RuleChain mRuleChain = RuleChain 108 .outerRule(mActivityRule) 109 .around(mFocusIndicatorServiceRule) 110 .around(mDumpOnFailureRule); 111 112 /* Test name rule that tracks the current test method under execution */ 113 @Rule public TestName mTestName = new TestName(); 114 115 @BeforeClass oneTimeSetup()116 public static void oneTimeSetup() throws Exception { 117 sInstrumentation = InstrumentationRegistry.getInstrumentation(); 118 sUiAutomation = sInstrumentation.getUiAutomation( 119 UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES); 120 121 sContext = sInstrumentation.getContext(); 122 sAccessibilityManager = sContext.getSystemService(AccessibilityManager.class); 123 assertNotNull(sAccessibilityManager); 124 sFocusStrokeWidthDefaultValue = sAccessibilityManager.getAccessibilityFocusStrokeWidth(); 125 sFocusColorDefaultValue = sAccessibilityManager.getAccessibilityFocusColor(); 126 } 127 128 @AfterClass postTestTearDown()129 public static void postTestTearDown() { 130 sUiAutomation.destroy(); 131 } 132 133 @Before setUp()134 public void setUp() throws Exception { 135 AccessibilityServiceInfo info = sUiAutomation.getServiceInfo(); 136 info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE; 137 info.flags &= ~AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS; 138 sUiAutomation.setServiceInfo(info); 139 140 mActivity = launchActivityAndWaitForItToBeOnscreen( 141 sInstrumentation, sUiAutomation, mActivityRule); 142 } 143 144 @MediumTest 145 @Test testFindAccessibilityFocus()146 public void testFindAccessibilityFocus() throws Exception { 147 sInstrumentation.runOnMainSync(() -> { 148 mActivity.findViewById(R.id.firstEditText).requestFocus(); 149 }); 150 // Get the view that has input and accessibility focus. 151 final AccessibilityNodeInfo expected = sUiAutomation 152 .getRootInActiveWindow().findAccessibilityNodeInfosByText( 153 sContext.getString(R.string.firstEditText)).get(0); 154 assertNotNull(expected); 155 assertFalse(expected.isAccessibilityFocused()); 156 assertTrue(expected.isFocused()); 157 158 sUiAutomation.executeAndWaitForEvent( 159 () -> assertTrue(expected.performAction(ACTION_ACCESSIBILITY_FOCUS)), 160 filterForEventTypeWithAction( 161 TYPE_VIEW_ACCESSIBILITY_FOCUSED, ACTION_ACCESSIBILITY_FOCUS), 162 DEFAULT_TIMEOUT_MS); 163 164 // Get the second expected node info. 165 AccessibilityNodeInfo received = sUiAutomation 166 .getRootInActiveWindow().findFocus(AccessibilityNodeInfo.FOCUS_ACCESSIBILITY); 167 assertNotNull(received); 168 assertTrue(received.isAccessibilityFocused()); 169 170 // Make sure we got the expected focusable. 171 assertEquals(expected, received); 172 } 173 174 @MediumTest 175 @Test testInitialStateNoAccessibilityFocus()176 public void testInitialStateNoAccessibilityFocus() throws Exception { 177 // Get the root which is only accessibility focused. 178 AccessibilityNodeInfo focused = sUiAutomation 179 .getRootInActiveWindow().findFocus(AccessibilityNodeInfo.FOCUS_ACCESSIBILITY); 180 assertNull(focused); 181 } 182 183 @MediumTest 184 @Test testActionAccessibilityFocus()185 public void testActionAccessibilityFocus() throws Exception { 186 // Get the root linear layout info. 187 final AccessibilityNodeInfo rootLinearLayout = sUiAutomation 188 .getRootInActiveWindow().findAccessibilityNodeInfosByText( 189 sContext.getString(R.string.rootLinearLayout)).get(0); 190 assertNotNull(rootLinearLayout); 191 assertFalse(rootLinearLayout.isAccessibilityFocused()); 192 193 sUiAutomation.executeAndWaitForEvent( 194 () -> assertTrue(rootLinearLayout.performAction(ACTION_ACCESSIBILITY_FOCUS)), 195 filterForEventTypeWithAction( 196 TYPE_VIEW_ACCESSIBILITY_FOCUSED, ACTION_ACCESSIBILITY_FOCUS), 197 DEFAULT_TIMEOUT_MS); 198 199 // Get the node info again. 200 rootLinearLayout.refresh(); 201 202 // Check if the node info is focused. 203 assertTrue(rootLinearLayout.isAccessibilityFocused()); 204 } 205 206 @MediumTest 207 @Test testActionClearAccessibilityFocus()208 public void testActionClearAccessibilityFocus() throws Exception { 209 // Get the root linear layout info. 210 final AccessibilityNodeInfo rootLinearLayout = sUiAutomation 211 .getRootInActiveWindow().findAccessibilityNodeInfosByText( 212 sContext.getString(R.string.rootLinearLayout)).get(0); 213 assertNotNull(rootLinearLayout); 214 215 sUiAutomation.executeAndWaitForEvent( 216 () -> assertTrue(rootLinearLayout.performAction(ACTION_ACCESSIBILITY_FOCUS)), 217 filterForEventTypeWithAction( 218 TYPE_VIEW_ACCESSIBILITY_FOCUSED, ACTION_ACCESSIBILITY_FOCUS), 219 DEFAULT_TIMEOUT_MS); 220 221 // Refresh the node info. 222 rootLinearLayout.refresh(); 223 224 // Check if the node info is focused. 225 assertTrue(rootLinearLayout.isAccessibilityFocused()); 226 227 sUiAutomation.executeAndWaitForEvent( 228 () -> assertTrue(rootLinearLayout.performAction(ACTION_CLEAR_ACCESSIBILITY_FOCUS)), 229 filterForEventTypeWithAction( 230 TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED, ACTION_CLEAR_ACCESSIBILITY_FOCUS), 231 DEFAULT_TIMEOUT_MS); 232 233 // Refresh the node info. 234 rootLinearLayout.refresh(); 235 236 // Check if the node info is not focused. 237 assertFalse(rootLinearLayout.isAccessibilityFocused()); 238 } 239 240 @MediumTest 241 @Test testOnlyOneNodeHasAccessibilityFocus()242 public void testOnlyOneNodeHasAccessibilityFocus() throws Exception { 243 // Get the first not focused edit text. 244 final AccessibilityNodeInfo firstEditText = sUiAutomation 245 .getRootInActiveWindow().findAccessibilityNodeInfosByText( 246 sContext.getString(R.string.firstEditText)).get(0); 247 assertNotNull(firstEditText); 248 assertTrue(firstEditText.isFocusable()); 249 assertFalse(firstEditText.isAccessibilityFocused()); 250 251 sUiAutomation.executeAndWaitForEvent( 252 () -> assertTrue(firstEditText.performAction(ACTION_ACCESSIBILITY_FOCUS)), 253 filterForEventTypeWithAction( 254 TYPE_VIEW_ACCESSIBILITY_FOCUSED, ACTION_ACCESSIBILITY_FOCUS), 255 DEFAULT_TIMEOUT_MS); 256 257 // Get the second not focused edit text. 258 final AccessibilityNodeInfo secondEditText = sUiAutomation 259 .getRootInActiveWindow().findAccessibilityNodeInfosByText( 260 sContext.getString(R.string.secondEditText)).get(0); 261 assertNotNull(secondEditText); 262 assertTrue(secondEditText.isFocusable()); 263 assertFalse(secondEditText.isFocused()); 264 assertFalse(secondEditText.isAccessibilityFocused()); 265 266 sUiAutomation.executeAndWaitForEvent( 267 () -> assertTrue(secondEditText.performAction(ACTION_ACCESSIBILITY_FOCUS)), 268 filterForEventTypeWithAction( 269 TYPE_VIEW_ACCESSIBILITY_FOCUSED, ACTION_ACCESSIBILITY_FOCUS), 270 DEFAULT_TIMEOUT_MS); 271 272 // Get the node info again. 273 secondEditText.refresh(); 274 275 // Make sure no other node has accessibility focus. 276 AccessibilityNodeInfo root = sUiAutomation.getRootInActiveWindow(); 277 Queue<AccessibilityNodeInfo> workQueue = new LinkedList<AccessibilityNodeInfo>(); 278 workQueue.add(root); 279 while (!workQueue.isEmpty()) { 280 AccessibilityNodeInfo current = workQueue.poll(); 281 if (current.isAccessibilityFocused() && !current.equals(secondEditText)) { 282 fail(); 283 } 284 final int childCount = current.getChildCount(); 285 for (int i = 0; i < childCount; i++) { 286 AccessibilityNodeInfo child = current.getChild(i); 287 if (child != null) { 288 workQueue.offer(child); 289 } 290 } 291 } 292 } 293 294 @Test testScreenReaderFocusableAttribute_reportedToAccessibility()295 public void testScreenReaderFocusableAttribute_reportedToAccessibility() { 296 final AccessibilityNodeInfo secondButton = sUiAutomation.getRootInActiveWindow() 297 .findAccessibilityNodeInfosByText( 298 sContext.getString(R.string.secondButton)).get(0); 299 assertTrue("Screen reader focusability not propagated from xml to accessibility", 300 secondButton.isScreenReaderFocusable()); 301 302 // Verify the setter and getter work 303 final AtomicBoolean isScreenReaderFocusableAtomic = new AtomicBoolean(false); 304 sInstrumentation.runOnMainSync(() -> { 305 View secondButtonView = mActivity.findViewById(R.id.secondButton); 306 secondButtonView.setScreenReaderFocusable(false); 307 isScreenReaderFocusableAtomic.set(secondButtonView.isScreenReaderFocusable()); 308 }); 309 310 assertFalse("isScreenReaderFocusable did not change after value set", 311 isScreenReaderFocusableAtomic.get()); 312 313 secondButton.refresh(); 314 assertFalse( 315 "Screen reader focusability not propagated to accessibility after calling setter", 316 secondButton.isScreenReaderFocusable()); 317 } 318 319 @Test testSetFocusAppearanceDataAfterServiceEnabled()320 public void testSetFocusAppearanceDataAfterServiceEnabled() { 321 final StubFocusIndicatorService service = 322 mFocusIndicatorServiceRule.enableService(); 323 final int focusColor = sFocusColorDefaultValue == Color.BLUE ? Color.RED : Color.BLUE; 324 325 try { 326 setFocusAppearanceDataAndCheckItCorrect(service, sFocusStrokeWidthDefaultValue + 10, 327 focusColor); 328 } finally { 329 setFocusAppearanceDataAndCheckItCorrect(service, sFocusStrokeWidthDefaultValue, 330 sFocusColorDefaultValue); 331 332 service.disableSelfAndRemove(); 333 } 334 } 335 336 @Test testChangeFocusColor_expectedColorIsChanged()337 public void testChangeFocusColor_expectedColorIsChanged() throws Exception { 338 final StubFocusIndicatorService service = 339 mFocusIndicatorServiceRule.enableService(); 340 341 try { 342 // Get the root linear layout info. 343 final AccessibilityNodeInfo rootLinearLayout = sUiAutomation 344 .getRootInActiveWindow().findAccessibilityNodeInfosByText( 345 sContext.getString(R.string.rootLinearLayout)).get(0); 346 347 final Bitmap blueColorFocusScreenshot = screenshotAfterChangeFocusColor(service, 348 rootLinearLayout, Color.BLUE); 349 350 final Bitmap redColorFocusScreenshot = screenshotAfterChangeFocusColor(service, 351 rootLinearLayout, Color.RED); 352 353 assertTrue(isBitmapDifferent(blueColorFocusScreenshot, redColorFocusScreenshot)); 354 } finally { 355 setFocusAppearanceDataAndCheckItCorrect(service, sFocusStrokeWidthDefaultValue, 356 sFocusColorDefaultValue); 357 358 service.disableSelfAndRemove(); 359 } 360 } 361 screenshotAfterChangeFocusColor(StubFocusIndicatorService service, AccessibilityNodeInfo unAccessibilityFocusedNode, int color)362 private Bitmap screenshotAfterChangeFocusColor(StubFocusIndicatorService service, 363 AccessibilityNodeInfo unAccessibilityFocusedNode, int color) throws Exception { 364 assertFalse(unAccessibilityFocusedNode.isAccessibilityFocused()); 365 366 setFocusAppearanceDataAndCheckItCorrect(service, sFocusStrokeWidthDefaultValue, color); 367 sUiAutomation.executeAndWaitForEvent( 368 () -> assertTrue(unAccessibilityFocusedNode.performAction( 369 ACTION_ACCESSIBILITY_FOCUS)), 370 filterForEventTypeWithAction(TYPE_VIEW_ACCESSIBILITY_FOCUSED, 371 ACTION_ACCESSIBILITY_FOCUS), 372 DEFAULT_TIMEOUT_MS); 373 Thread.sleep(SCREEN_FRAME_RENDERING_OUT_TIME_MILLIS); 374 375 final Bitmap screenshot = sUiAutomation.takeScreenshot(); 376 377 sUiAutomation.executeAndWaitForEvent( 378 () -> assertTrue(unAccessibilityFocusedNode.performAction( 379 ACTION_CLEAR_ACCESSIBILITY_FOCUS)), 380 filterForEventTypeWithAction(TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED, 381 ACTION_CLEAR_ACCESSIBILITY_FOCUS), 382 DEFAULT_TIMEOUT_MS); 383 384 return screenshot; 385 } 386 isBitmapDifferent(Bitmap bitmap1, Bitmap bitmap2)387 private boolean isBitmapDifferent(Bitmap bitmap1, Bitmap bitmap2) { 388 final Display display = mActivity.getWindowManager().getDefaultDisplay(); 389 final Point displaySize = new Point(); 390 display.getRealSize(displaySize); 391 392 final int[] pixelsOne = new int[displaySize.x * displaySize.y]; 393 final Bitmap bitmapOne = bitmap1.copy(Bitmap.Config.ARGB_8888, false); 394 bitmapOne.getPixels(pixelsOne, 0, displaySize.x, 0, 0, displaySize.x, 395 displaySize.y); 396 397 final int[] pixelsTwo = new int[displaySize.x * displaySize.y]; 398 final Bitmap bitmapTwo = bitmap2.copy(Bitmap.Config.ARGB_8888, false); 399 bitmapTwo.getPixels(pixelsTwo, 0, displaySize.x, 0, 0, displaySize.x, 400 displaySize.y); 401 402 for (int i = pixelsOne.length - 1; i > 0; i--) { 403 if ((Color.red(pixelsOne[i]) != Color.red(pixelsTwo[i])) 404 || (Color.green(pixelsOne[i]) != Color.green(pixelsTwo[i])) 405 || (Color.blue(pixelsOne[i]) != Color.blue(pixelsTwo[i]))) { 406 return true; 407 } 408 } 409 410 saveFailureScreenshot(bitmap1, bitmap2); 411 return false; 412 } 413 saveFailureScreenshot(Bitmap bitmap1, Bitmap bitmap2)414 private void saveFailureScreenshot(Bitmap bitmap1, Bitmap bitmap2) { 415 final String directoryName = Environment.getExternalStorageDirectory() 416 + "/" + getClass().getSimpleName(); 417 418 final String fileName1 = String.format("%s_%s_%s.png", mTestName.getMethodName(), "Bitmap1", 419 SystemClock.uptimeMillis()); 420 BitmapUtils.saveBitmap(bitmap1, directoryName, fileName1); 421 422 final String fileName2 = String.format("%s_%s_%s.png", mTestName.getMethodName(), "Bitmap2", 423 SystemClock.uptimeMillis()); 424 BitmapUtils.saveBitmap(bitmap2, directoryName, fileName2); 425 } 426 setFocusAppearanceDataAndCheckItCorrect(StubFocusIndicatorService service, int focusStrokeWidthValue, int focusColorValue)427 private void setFocusAppearanceDataAndCheckItCorrect(StubFocusIndicatorService service, 428 int focusStrokeWidthValue, int focusColorValue) { 429 service.setAccessibilityFocusAppearance(focusStrokeWidthValue, 430 focusColorValue); 431 // Checks if the color and the stroke values from AccessibilityManager is 432 // updated as in expectation. 433 PollingCheck.waitFor(()->isFocusAppearanceDataUpdated(sAccessibilityManager, 434 focusStrokeWidthValue, focusColorValue)); 435 } 436 isFocusAppearanceDataUpdated(AccessibilityManager manager, int strokeWidth, int color)437 private static boolean isFocusAppearanceDataUpdated(AccessibilityManager manager, 438 int strokeWidth, int color) { 439 return manager.getAccessibilityFocusStrokeWidth() == strokeWidth 440 && manager.getAccessibilityFocusColor() == color; 441 } 442 } 443