InterrogationActivityTest.java revision 8bd69610aafc6995126965d1d23b771fe02a9084
1/** 2 * Copyright (C) 2011 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 15package android.accessibilityservice; 16 17import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_FOCUS; 18import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_SELECTION; 19import static android.view.accessibility.AccessibilityNodeInfo.ACTION_FOCUS; 20import static android.view.accessibility.AccessibilityNodeInfo.ACTION_SELECT; 21 22import com.android.frameworks.coretests.R; 23 24import android.content.Context; 25import android.graphics.Rect; 26import android.os.RemoteException; 27import android.os.ServiceManager; 28import android.os.SystemClock; 29import android.provider.Settings; 30import android.test.ActivityInstrumentationTestCase2; 31import android.test.suitebuilder.annotation.LargeTest; 32import android.util.Log; 33import android.view.accessibility.AccessibilityEvent; 34import android.view.accessibility.AccessibilityInteractionClient; 35import android.view.accessibility.AccessibilityManager; 36import android.view.accessibility.AccessibilityNodeInfo; 37import android.view.accessibility.IAccessibilityManager; 38 39import java.lang.reflect.Method; 40import java.lang.reflect.Modifier; 41import java.util.ArrayList; 42import java.util.LinkedList; 43import java.util.List; 44import java.util.Queue; 45 46/** 47 * Activity for testing the accessibility APIs for "interrogation" of 48 * the screen content. These APIs allow exploring the screen and 49 * requesting an action to be performed on a given view from an 50 * AccessiiblityService. 51 */ 52public class InterrogationActivityTest 53 extends ActivityInstrumentationTestCase2<InterrogationActivity> { 54 private static final boolean DEBUG = true; 55 56 private static String LOG_TAG = "InterrogationActivityTest"; 57 58 // Timeout before give up wait for the system to process an accessibility setting change. 59 private static final int TIMEOUT_PROPAGATE_ACCESSIBLITY_SETTING = 2000; 60 61 // Helpers to figure out the first and last test methods 62 // This is a workaround for the lack of such support in JUnit3 63 private static int sTestMethodCount; 64 private static int sExecutedTestMethodCount; 65 66 // Handle to a connection to the AccessibilityManagerService 67 private static IAccessibilityServiceConnection sConnection; 68 69 // The last received accessibility event 70 private static volatile AccessibilityEvent sLastFocusAccessibilityEvent; 71 72 public InterrogationActivityTest() { 73 super(InterrogationActivity.class); 74 sTestMethodCount = getTestMethodCount(); 75 } 76 77 @LargeTest 78 public void testFindAccessibilityNodeInfoByViewId() throws Exception { 79 beforeClassIfNeeded(); 80 final long startTimeMillis = SystemClock.uptimeMillis(); 81 try { 82 // bring up the activity 83 getActivity(); 84 85 AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance() 86 .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5); 87 assertNotNull(button); 88 assertEquals(0, button.getChildCount()); 89 90 // bounds 91 Rect bounds = new Rect(); 92 button.getBoundsInParent(bounds); 93 assertEquals(0, bounds.left); 94 assertEquals(0, bounds.top); 95 assertEquals(160, bounds.right); 96 assertEquals(100, bounds.bottom); 97 98 // char sequence attributes 99 assertEquals("com.android.frameworks.coretests", button.getPackageName()); 100 assertEquals("android.widget.Button", button.getClassName()); 101 assertEquals("Button5", button.getText()); 102 assertNull(button.getContentDescription()); 103 104 // boolean attributes 105 assertTrue(button.isFocusable()); 106 assertTrue(button.isClickable()); 107 assertTrue(button.isEnabled()); 108 assertFalse(button.isFocused()); 109 assertTrue(button.isClickable()); 110 assertFalse(button.isPassword()); 111 assertFalse(button.isSelected()); 112 assertFalse(button.isCheckable()); 113 assertFalse(button.isChecked()); 114 115 // actions 116 assertEquals(ACTION_FOCUS | ACTION_SELECT | ACTION_CLEAR_SELECTION, 117 button.getActions()); 118 } finally { 119 afterClassIfNeeded(); 120 if (DEBUG) { 121 final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; 122 Log.i(LOG_TAG, "testFindAccessibilityNodeInfoByViewId: " 123 + elapsedTimeMillis + "ms"); 124 } 125 } 126 } 127 128 @LargeTest 129 public void testFindAccessibilityNodeInfoByViewText() throws Exception { 130 beforeClassIfNeeded(); 131 final long startTimeMillis = SystemClock.uptimeMillis(); 132 try { 133 // bring up the activity 134 getActivity(); 135 136 // find a view by text 137 List<AccessibilityNodeInfo> buttons = AccessibilityInteractionClient.getInstance() 138 .findAccessibilityNodeInfosByViewTextInActiveWindow(getConnection(), "butto"); 139 assertEquals(9, buttons.size()); 140 } finally { 141 afterClassIfNeeded(); 142 if (DEBUG) { 143 final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; 144 Log.i(LOG_TAG, "testFindAccessibilityNodeInfoByViewText: " 145 + elapsedTimeMillis + "ms"); 146 } 147 } 148 } 149 150 @LargeTest 151 public void testTraverseAllViews() throws Exception { 152 beforeClassIfNeeded(); 153 final long startTimeMillis = SystemClock.uptimeMillis(); 154 try { 155 // bring up the activity 156 getActivity(); 157 158 // make list of expected nodes 159 List<String> classNameAndTextList = new ArrayList<String>(); 160 classNameAndTextList.add("android.widget.LinearLayout"); 161 classNameAndTextList.add("android.widget.LinearLayout"); 162 classNameAndTextList.add("android.widget.LinearLayout"); 163 classNameAndTextList.add("android.widget.LinearLayout"); 164 classNameAndTextList.add("android.widget.ButtonButton1"); 165 classNameAndTextList.add("android.widget.ButtonButton2"); 166 classNameAndTextList.add("android.widget.ButtonButton3"); 167 classNameAndTextList.add("android.widget.ButtonButton4"); 168 classNameAndTextList.add("android.widget.ButtonButton5"); 169 classNameAndTextList.add("android.widget.ButtonButton6"); 170 classNameAndTextList.add("android.widget.ButtonButton7"); 171 classNameAndTextList.add("android.widget.ButtonButton8"); 172 classNameAndTextList.add("android.widget.ButtonButton9"); 173 174 AccessibilityNodeInfo root = AccessibilityInteractionClient.getInstance() 175 .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.root); 176 assertNotNull("We must find the existing root.", root); 177 178 Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>(); 179 fringe.add(root); 180 181 // do a BFS traversal and check nodes 182 while (!fringe.isEmpty()) { 183 AccessibilityNodeInfo current = fringe.poll(); 184 185 CharSequence className = current.getClassName(); 186 CharSequence text = current.getText(); 187 String receivedClassNameAndText = className.toString() 188 + ((text != null) ? text.toString() : ""); 189 String expectedClassNameAndText = classNameAndTextList.remove(0); 190 191 assertEquals("Did not get the expected node info", 192 expectedClassNameAndText, receivedClassNameAndText); 193 194 final int childCount = current.getChildCount(); 195 for (int i = 0; i < childCount; i++) { 196 AccessibilityNodeInfo child = current.getChild(i); 197 fringe.add(child); 198 } 199 } 200 } finally { 201 afterClassIfNeeded(); 202 if (DEBUG) { 203 final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; 204 Log.i(LOG_TAG, "testTraverseAllViews: " + elapsedTimeMillis + "ms"); 205 } 206 } 207 } 208 209 @LargeTest 210 public void testPerformAccessibilityActionFocus() throws Exception { 211 beforeClassIfNeeded(); 212 final long startTimeMillis = SystemClock.uptimeMillis(); 213 try { 214 // bring up the activity 215 getActivity(); 216 217 // find a view and make sure it is not focused 218 AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance() 219 .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5); 220 assertFalse(button.isFocused()); 221 222 // focus the view 223 assertTrue(button.performAction(ACTION_FOCUS)); 224 225 // find the view again and make sure it is focused 226 button = AccessibilityInteractionClient.getInstance() 227 .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5); 228 assertTrue(button.isFocused()); 229 } finally { 230 afterClassIfNeeded(); 231 if (DEBUG) { 232 final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; 233 Log.i(LOG_TAG, "testPerformAccessibilityActionFocus: " + elapsedTimeMillis + "ms"); 234 } 235 } 236 } 237 238 @LargeTest 239 public void testPerformAccessibilityActionClearFocus() throws Exception { 240 beforeClassIfNeeded(); 241 final long startTimeMillis = SystemClock.uptimeMillis(); 242 try { 243 // bring up the activity 244 getActivity(); 245 246 // find a view and make sure it is not focused 247 AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance() 248 .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5); 249 assertFalse(button.isFocused()); 250 251 // focus the view 252 assertTrue(button.performAction(ACTION_FOCUS)); 253 254 // find the view again and make sure it is focused 255 button = AccessibilityInteractionClient.getInstance() 256 .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5); 257 assertTrue(button.isFocused()); 258 259 // unfocus the view 260 assertTrue(button.performAction(ACTION_CLEAR_FOCUS)); 261 262 // find the view again and make sure it is not focused 263 button = AccessibilityInteractionClient.getInstance() 264 .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5); 265 assertFalse(button.isFocused()); 266 } finally { 267 afterClassIfNeeded(); 268 if (DEBUG) { 269 final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; 270 Log.i(LOG_TAG, "testPerformAccessibilityActionClearFocus: " 271 + elapsedTimeMillis + "ms"); 272 } 273 } 274 } 275 276 @LargeTest 277 public void testPerformAccessibilityActionSelect() throws Exception { 278 beforeClassIfNeeded(); 279 final long startTimeMillis = SystemClock.uptimeMillis(); 280 try { 281 // bring up the activity 282 getActivity(); 283 284 // find a view and make sure it is not selected 285 AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance() 286 .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5); 287 assertFalse(button.isSelected()); 288 289 // select the view 290 assertTrue(button.performAction(ACTION_SELECT)); 291 292 // find the view again and make sure it is selected 293 button = AccessibilityInteractionClient.getInstance() 294 .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5); 295 assertTrue(button.isSelected()); 296 } finally { 297 afterClassIfNeeded(); 298 if (DEBUG) { 299 final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; 300 Log.i(LOG_TAG, "testPerformAccessibilityActionSelect: " + elapsedTimeMillis + "ms"); 301 } 302 } 303 } 304 305 @LargeTest 306 public void testPerformAccessibilityActionClearSelection() throws Exception { 307 beforeClassIfNeeded(); 308 final long startTimeMillis = SystemClock.uptimeMillis(); 309 try { 310 // bring up the activity 311 getActivity(); 312 313 // find a view and make sure it is not selected 314 AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance() 315 .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5); 316 assertFalse(button.isSelected()); 317 318 // select the view 319 assertTrue(button.performAction(ACTION_SELECT)); 320 321 // find the view again and make sure it is selected 322 button = AccessibilityInteractionClient.getInstance() 323 .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5); 324 assertTrue(button.isSelected()); 325 326 // unselect the view 327 assertTrue(button.performAction(ACTION_CLEAR_SELECTION)); 328 329 // find the view again and make sure it is not selected 330 button = AccessibilityInteractionClient.getInstance() 331 .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5); 332 assertFalse(button.isSelected()); 333 } finally { 334 afterClassIfNeeded(); 335 if (DEBUG) { 336 final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; 337 Log.i(LOG_TAG, "testPerformAccessibilityActionClearSelection: " 338 + elapsedTimeMillis + "ms"); 339 } 340 } 341 } 342 343 @LargeTest 344 public void testAccessibilityEventGetSource() throws Exception { 345 beforeClassIfNeeded(); 346 final long startTimeMillis = SystemClock.uptimeMillis(); 347 try { 348 // bring up the activity 349 getActivity(); 350 351 // find a view and make sure it is not focused 352 AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance() 353 .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5); 354 assertFalse(button.isSelected()); 355 356 // focus the view 357 assertTrue(button.performAction(ACTION_FOCUS)); 358 359 synchronized (sConnection) { 360 try { 361 sConnection.wait(500); 362 } catch (InterruptedException ie) { 363 /* ignore */ 364 } 365 } 366 367 // check that last event source 368 AccessibilityNodeInfo source = sLastFocusAccessibilityEvent.getSource(); 369 assertNotNull(source); 370 371 // bounds 372 Rect buttonBounds = new Rect(); 373 button.getBoundsInParent(buttonBounds); 374 Rect sourceBounds = new Rect(); 375 source.getBoundsInParent(sourceBounds); 376 377 assertEquals(buttonBounds.left, sourceBounds.left); 378 assertEquals(buttonBounds.right, sourceBounds.right); 379 assertEquals(buttonBounds.top, sourceBounds.top); 380 assertEquals(buttonBounds.bottom, sourceBounds.bottom); 381 382 // char sequence attributes 383 assertEquals(button.getPackageName(), source.getPackageName()); 384 assertEquals(button.getClassName(), source.getClassName()); 385 assertEquals(button.getText(), source.getText()); 386 assertSame(button.getContentDescription(), source.getContentDescription()); 387 388 // boolean attributes 389 assertSame(button.isFocusable(), source.isFocusable()); 390 assertSame(button.isClickable(), source.isClickable()); 391 assertSame(button.isEnabled(), source.isEnabled()); 392 assertNotSame(button.isFocused(), source.isFocused()); 393 assertSame(button.isLongClickable(), source.isLongClickable()); 394 assertSame(button.isPassword(), source.isPassword()); 395 assertSame(button.isSelected(), source.isSelected()); 396 assertSame(button.isCheckable(), source.isCheckable()); 397 assertSame(button.isChecked(), source.isChecked()); 398 } finally { 399 afterClassIfNeeded(); 400 if (DEBUG) { 401 final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; 402 Log.i(LOG_TAG, "testAccessibilityEventGetSource: " + elapsedTimeMillis + "ms"); 403 } 404 } 405 } 406 407 @LargeTest 408 public void testObjectContract() throws Exception { 409 beforeClassIfNeeded(); 410 final long startTimeMillis = SystemClock.uptimeMillis(); 411 try { 412 // bring up the activity 413 getActivity(); 414 415 // find a view and make sure it is not focused 416 AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance() 417 .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5); 418 AccessibilityNodeInfo parent = button.getParent(); 419 final int childCount = parent.getChildCount(); 420 for (int i = 0; i < childCount; i++) { 421 AccessibilityNodeInfo child = parent.getChild(i); 422 assertNotNull(child); 423 if (child.equals(button)) { 424 assertEquals("Equal objects must have same hasCode.", button.hashCode(), 425 child.hashCode()); 426 return; 427 } 428 } 429 fail("Parent's children do not have the info whose parent is the parent."); 430 } finally { 431 afterClassIfNeeded(); 432 if (DEBUG) { 433 final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; 434 Log.i(LOG_TAG, "testObjectContract: " + elapsedTimeMillis + "ms"); 435 } 436 } 437 } 438 439 @Override 440 protected void scrubClass(Class<?> testCaseClass) { 441 /* intentionally do not scrub */ 442 } 443 444 /** 445 * Sets accessibility in a given state by writing the state to the 446 * settings and waiting until the accessibility manager service picks 447 * it up for max {@link #TIMEOUT_PROPAGATE_ACCESSIBLITY_SETTING}. 448 * 449 * @param state The accessibility state. 450 * @throws Exception If any error occurs. 451 */ 452 private void ensureAccessibilityState(boolean state) throws Exception { 453 Context context = getInstrumentation().getContext(); 454 // If the local manager ready => nothing to do. 455 AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(context); 456 if (accessibilityManager.isEnabled() == state) { 457 return; 458 } 459 synchronized (this) { 460 // Check if the system already knows about the desired state. 461 final boolean currentState = Settings.Secure.getInt(context.getContentResolver(), 462 Settings.Secure.ACCESSIBILITY_ENABLED) == 1; 463 if (currentState != state) { 464 // Make sure we wake ourselves as the desired state is propagated. 465 accessibilityManager.addAccessibilityStateChangeListener( 466 new AccessibilityManager.AccessibilityStateChangeListener() { 467 public void onAccessibilityStateChanged(boolean enabled) { 468 synchronized (this) { 469 notifyAll(); 470 } 471 } 472 }); 473 Settings.Secure.putInt(context.getContentResolver(), 474 Settings.Secure.ACCESSIBILITY_ENABLED, state ? 1 : 0); 475 } 476 // No while one attempt and that is it. 477 try { 478 wait(TIMEOUT_PROPAGATE_ACCESSIBLITY_SETTING); 479 } catch (InterruptedException ie) { 480 /* ignore */ 481 } 482 } 483 if (accessibilityManager.isEnabled() != state) { 484 throw new IllegalStateException("Could not set accessibility state to: " + state); 485 } 486 } 487 488 /** 489 * Execute some set up code before any test method. 490 * 491 * NOTE: I miss Junit4's @BeforeClass 492 * 493 * @throws Exception If an error occurs. 494 */ 495 private void beforeClassIfNeeded() throws Exception { 496 sExecutedTestMethodCount++; 497 if (sExecutedTestMethodCount == 1) { 498 ensureAccessibilityState(true); 499 } 500 } 501 502 /** 503 * Execute some clean up code after all test methods. 504 * 505 * NOTE: I miss Junit4's @AfterClass 506 * 507 * @throws Exception If an error occurs. 508 */ 509 public void afterClassIfNeeded() throws Exception { 510 if (sExecutedTestMethodCount == sTestMethodCount) { 511 sExecutedTestMethodCount = 0; 512 ensureAccessibilityState(false); 513 } 514 } 515 516 private static IAccessibilityServiceConnection getConnection() throws Exception { 517 if (sConnection == null) { 518 IEventListener listener = new IEventListener.Stub() { 519 public void setConnection(IAccessibilityServiceConnection connection) 520 throws RemoteException { 521 AccessibilityServiceInfo info = new AccessibilityServiceInfo(); 522 info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK; 523 info.feedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN; 524 info.notificationTimeout = 0; 525 info.flags = AccessibilityServiceInfo.DEFAULT; 526 connection.setServiceInfo(info); 527 } 528 529 public void onInterrupt() {} 530 531 public void onAccessibilityEvent(AccessibilityEvent event) { 532 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED) { 533 sLastFocusAccessibilityEvent = AccessibilityEvent.obtain(event); 534 } 535 synchronized (sConnection) { 536 sConnection.notifyAll(); 537 } 538 } 539 }; 540 IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface( 541 ServiceManager.getService(Context.ACCESSIBILITY_SERVICE)); 542 sConnection = manager.registerEventListener(listener); 543 } 544 return sConnection; 545 } 546 547 /** 548 * @return The number of test methods. 549 */ 550 private int getTestMethodCount() { 551 int testMethodCount = 0; 552 for (Method method : getClass().getMethods()) { 553 final int modifiers = method.getModifiers(); 554 if (method.getName().startsWith("test") 555 && (modifiers & Modifier.PUBLIC) != 0 556 && (modifiers & Modifier.STATIC) == 0) { 557 testMethodCount++; 558 } 559 } 560 return testMethodCount; 561 } 562} 563