1 /* 2 * Copyright (C) 2016 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 com.android.cts.usepermission; 18 19 import static junit.framework.Assert.assertEquals; 20 import static org.junit.Assert.assertNotNull; 21 import static org.junit.Assert.fail; 22 23 import android.Manifest; 24 import android.app.Activity; 25 import android.app.Instrumentation; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.pm.PackageManager; 29 import android.content.res.Resources; 30 import android.net.Uri; 31 import android.os.Bundle; 32 import android.os.SystemClock; 33 import android.provider.Settings; 34 import android.support.test.InstrumentationRegistry; 35 import android.support.test.runner.AndroidJUnit4; 36 import android.support.test.uiautomator.By; 37 import android.support.test.uiautomator.UiDevice; 38 import android.support.test.uiautomator.UiObject; 39 import android.support.test.uiautomator.UiObject2; 40 import android.support.test.uiautomator.UiScrollable; 41 import android.support.test.uiautomator.UiSelector; 42 import android.util.ArrayMap; 43 import android.util.Log; 44 import android.view.accessibility.AccessibilityEvent; 45 import android.view.accessibility.AccessibilityNodeInfo; 46 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; 47 import android.widget.ScrollView; 48 import android.widget.Switch; 49 import java.util.List; 50 import java.util.Map; 51 import java.util.concurrent.Callable; 52 import java.util.concurrent.TimeoutException; 53 import junit.framework.Assert; 54 import org.junit.Before; 55 import org.junit.runner.RunWith; 56 57 @RunWith(AndroidJUnit4.class) 58 public abstract class BasePermissionsTest { 59 private static final String PLATFORM_PACKAGE_NAME = "android"; 60 61 private static final long IDLE_TIMEOUT_MILLIS = 500; 62 private static final long GLOBAL_TIMEOUT_MILLIS = 5000; 63 64 private static final long RETRY_TIMEOUT = 3 * GLOBAL_TIMEOUT_MILLIS; 65 private static final String LOG_TAG = "BasePermissionsTest"; 66 67 private static Map<String, String> sPermissionToLabelResNameMap = new ArrayMap<>(); 68 69 private Context mContext; 70 private Resources mPlatformResources; 71 private boolean mWatch; 72 getInstrumentation()73 protected static Instrumentation getInstrumentation() { 74 return InstrumentationRegistry.getInstrumentation(); 75 } 76 assertPermissionRequestResult(BasePermissionActivity.Result result, int requestCode, String[] permissions, boolean[] granted)77 protected static void assertPermissionRequestResult(BasePermissionActivity.Result result, 78 int requestCode, String[] permissions, boolean[] granted) { 79 assertEquals(requestCode, result.requestCode); 80 for (int i = 0; i < permissions.length; i++) { 81 assertEquals(permissions[i], result.permissions[i]); 82 assertEquals(granted[i] ? PackageManager.PERMISSION_GRANTED 83 : PackageManager.PERMISSION_DENIED, result.grantResults[i]); 84 85 } 86 } 87 getUiDevice()88 protected static UiDevice getUiDevice() { 89 return UiDevice.getInstance(getInstrumentation()); 90 } 91 launchActivity(String packageName, Class<?> clazz, Bundle extras)92 protected static Activity launchActivity(String packageName, 93 Class<?> clazz, Bundle extras) { 94 Intent intent = new Intent(Intent.ACTION_MAIN); 95 intent.setClassName(packageName, clazz.getName()); 96 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 97 if (extras != null) { 98 intent.putExtras(extras); 99 } 100 Activity activity = getInstrumentation().startActivitySync(intent); 101 getInstrumentation().waitForIdleSync(); 102 103 return activity; 104 } 105 initPermissionToLabelMap(boolean permissionReviewMode)106 private void initPermissionToLabelMap(boolean permissionReviewMode) { 107 if (!permissionReviewMode) { 108 // Contacts 109 sPermissionToLabelResNameMap.put( 110 Manifest.permission.READ_CONTACTS, "@android:string/permgrouplab_contacts"); 111 sPermissionToLabelResNameMap.put( 112 Manifest.permission.WRITE_CONTACTS, "@android:string/permgrouplab_contacts"); 113 // Calendar 114 sPermissionToLabelResNameMap.put( 115 Manifest.permission.READ_CALENDAR, "@android:string/permgrouplab_calendar"); 116 sPermissionToLabelResNameMap.put( 117 Manifest.permission.WRITE_CALENDAR, "@android:string/permgrouplab_calendar"); 118 // SMS 119 sPermissionToLabelResNameMap.put( 120 Manifest.permission.SEND_SMS, "@android:string/permgrouplab_sms"); 121 sPermissionToLabelResNameMap.put( 122 Manifest.permission.RECEIVE_SMS, "@android:string/permgrouplab_sms"); 123 sPermissionToLabelResNameMap.put( 124 Manifest.permission.READ_SMS, "@android:string/permgrouplab_sms"); 125 sPermissionToLabelResNameMap.put( 126 Manifest.permission.RECEIVE_WAP_PUSH, "@android:string/permgrouplab_sms"); 127 sPermissionToLabelResNameMap.put( 128 Manifest.permission.RECEIVE_MMS, "@android:string/permgrouplab_sms"); 129 sPermissionToLabelResNameMap.put( 130 "android.permission.READ_CELL_BROADCASTS", "@android:string/permgrouplab_sms"); 131 // Storage 132 sPermissionToLabelResNameMap.put( 133 Manifest.permission.READ_EXTERNAL_STORAGE, 134 "@android:string/permgrouplab_storage"); 135 sPermissionToLabelResNameMap.put( 136 Manifest.permission.WRITE_EXTERNAL_STORAGE, 137 "@android:string/permgrouplab_storage"); 138 // Location 139 sPermissionToLabelResNameMap.put( 140 Manifest.permission.ACCESS_FINE_LOCATION, 141 "@android:string/permgrouplab_location"); 142 sPermissionToLabelResNameMap.put( 143 Manifest.permission.ACCESS_COARSE_LOCATION, 144 "@android:string/permgrouplab_location"); 145 // Phone 146 sPermissionToLabelResNameMap.put( 147 Manifest.permission.READ_PHONE_STATE, "@android:string/permgrouplab_phone"); 148 sPermissionToLabelResNameMap.put( 149 Manifest.permission.CALL_PHONE, "@android:string/permgrouplab_phone"); 150 sPermissionToLabelResNameMap.put( 151 "android.permission.ACCESS_IMS_CALL_SERVICE", 152 "@android:string/permgrouplab_phone"); 153 sPermissionToLabelResNameMap.put( 154 Manifest.permission.READ_CALL_LOG, "@android:string/permgrouplab_phone"); 155 sPermissionToLabelResNameMap.put( 156 Manifest.permission.WRITE_CALL_LOG, "@android:string/permgrouplab_phone"); 157 sPermissionToLabelResNameMap.put( 158 Manifest.permission.ADD_VOICEMAIL, "@android:string/permgrouplab_phone"); 159 sPermissionToLabelResNameMap.put( 160 Manifest.permission.USE_SIP, "@android:string/permgrouplab_phone"); 161 sPermissionToLabelResNameMap.put( 162 Manifest.permission.PROCESS_OUTGOING_CALLS, 163 "@android:string/permgrouplab_phone"); 164 // Microphone 165 sPermissionToLabelResNameMap.put( 166 Manifest.permission.RECORD_AUDIO, "@android:string/permgrouplab_microphone"); 167 // Camera 168 sPermissionToLabelResNameMap.put( 169 Manifest.permission.CAMERA, "@android:string/permgrouplab_camera"); 170 // Body sensors 171 sPermissionToLabelResNameMap.put( 172 Manifest.permission.BODY_SENSORS, "@android:string/permgrouplab_sensors"); 173 } else { 174 // Contacts 175 sPermissionToLabelResNameMap.put( 176 Manifest.permission.READ_CONTACTS, "@android:string/permlab_readContacts"); 177 sPermissionToLabelResNameMap.put( 178 Manifest.permission.WRITE_CONTACTS, "@android:string/permlab_writeContacts"); 179 // Calendar 180 sPermissionToLabelResNameMap.put( 181 Manifest.permission.READ_CALENDAR, "@android:string/permgrouplab_calendar"); 182 sPermissionToLabelResNameMap.put( 183 Manifest.permission.WRITE_CALENDAR, "@android:string/permgrouplab_calendar"); 184 // SMS 185 sPermissionToLabelResNameMap.put( 186 Manifest.permission.SEND_SMS, "@android:string/permlab_sendSms"); 187 sPermissionToLabelResNameMap.put( 188 Manifest.permission.RECEIVE_SMS, "@android:string/permlab_receiveSms"); 189 sPermissionToLabelResNameMap.put( 190 Manifest.permission.READ_SMS, "@android:string/permlab_readSms"); 191 sPermissionToLabelResNameMap.put( 192 Manifest.permission.RECEIVE_WAP_PUSH, "@android:string/permlab_receiveWapPush"); 193 sPermissionToLabelResNameMap.put( 194 Manifest.permission.RECEIVE_MMS, "@android:string/permlab_receiveMms"); 195 sPermissionToLabelResNameMap.put( 196 "android.permission.READ_CELL_BROADCASTS", 197 "@android:string/permlab_readCellBroadcasts"); 198 // Storage 199 sPermissionToLabelResNameMap.put( 200 Manifest.permission.READ_EXTERNAL_STORAGE, 201 "@android:string/permgrouplab_storage"); 202 sPermissionToLabelResNameMap.put( 203 Manifest.permission.WRITE_EXTERNAL_STORAGE, 204 "@android:string/permgrouplab_storage"); 205 // Location 206 sPermissionToLabelResNameMap.put( 207 Manifest.permission.ACCESS_FINE_LOCATION, 208 "@android:string/permgrouplab_location"); 209 sPermissionToLabelResNameMap.put( 210 Manifest.permission.ACCESS_COARSE_LOCATION, 211 "@android:string/permgrouplab_location"); 212 // Phone 213 sPermissionToLabelResNameMap.put( 214 Manifest.permission.READ_PHONE_STATE, "@android:string/permlab_readPhoneState"); 215 sPermissionToLabelResNameMap.put( 216 Manifest.permission.CALL_PHONE, "@android:string/permlab_callPhone"); 217 sPermissionToLabelResNameMap.put( 218 "android.permission.ACCESS_IMS_CALL_SERVICE", 219 "@android:string/permlab_accessImsCallService"); 220 sPermissionToLabelResNameMap.put( 221 Manifest.permission.READ_CALL_LOG, "@android:string/permlab_readCallLog"); 222 sPermissionToLabelResNameMap.put( 223 Manifest.permission.WRITE_CALL_LOG, "@android:string/permlab_writeCallLog"); 224 sPermissionToLabelResNameMap.put( 225 Manifest.permission.ADD_VOICEMAIL, "@android:string/permlab_addVoicemail"); 226 sPermissionToLabelResNameMap.put( 227 Manifest.permission.USE_SIP, "@android:string/permlab_use_sip"); 228 sPermissionToLabelResNameMap.put( 229 Manifest.permission.PROCESS_OUTGOING_CALLS, 230 "@android:string/permlab_processOutgoingCalls"); 231 // Microphone 232 sPermissionToLabelResNameMap.put( 233 Manifest.permission.RECORD_AUDIO, "@android:string/permgrouplab_microphone"); 234 // Camera 235 sPermissionToLabelResNameMap.put( 236 Manifest.permission.CAMERA, "@android:string/permgrouplab_camera"); 237 // Body sensors 238 sPermissionToLabelResNameMap.put( 239 Manifest.permission.BODY_SENSORS, "@android:string/permgrouplab_sensors"); 240 } 241 } 242 243 @Before beforeTest()244 public void beforeTest() { 245 mContext = InstrumentationRegistry.getTargetContext(); 246 try { 247 Context platformContext = mContext.createPackageContext(PLATFORM_PACKAGE_NAME, 0); 248 mPlatformResources = platformContext.getResources(); 249 } catch (PackageManager.NameNotFoundException e) { 250 /* cannot happen */ 251 } 252 253 PackageManager packageManager = mContext.getPackageManager(); 254 mWatch = packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH); 255 initPermissionToLabelMap(packageManager.isPermissionReviewModeEnabled()); 256 257 UiObject2 button = getUiDevice().findObject(By.text("Close")); 258 if (button != null) { 259 button.click(); 260 } 261 } 262 requestPermissions( String[] permissions, int requestCode, Class<?> clazz, Runnable postRequestAction)263 protected BasePermissionActivity.Result requestPermissions( 264 String[] permissions, int requestCode, Class<?> clazz, Runnable postRequestAction) 265 throws Exception { 266 // Start an activity 267 BasePermissionActivity activity = (BasePermissionActivity) launchActivity( 268 getInstrumentation().getTargetContext().getPackageName(), clazz, null); 269 270 activity.waitForOnCreate(); 271 272 // Request the permissions 273 activity.requestPermissions(permissions, requestCode); 274 275 // Define a more conservative idle criteria 276 getInstrumentation().getUiAutomation().waitForIdle( 277 IDLE_TIMEOUT_MILLIS, GLOBAL_TIMEOUT_MILLIS); 278 279 // Perform the post-request action 280 if (postRequestAction != null) { 281 postRequestAction.run(); 282 } 283 284 BasePermissionActivity.Result result = activity.getResult(); 285 activity.finish(); 286 return result; 287 } 288 clickAllowButton()289 protected void clickAllowButton() throws Exception { 290 scrollToBottomIfWatch(); 291 getUiDevice().findObject(new UiSelector().resourceId( 292 "com.android.packageinstaller:id/permission_allow_button")).click(); 293 } 294 clickDenyButton()295 protected void clickDenyButton() throws Exception { 296 scrollToBottomIfWatch(); 297 getUiDevice().findObject(new UiSelector().resourceId( 298 "com.android.packageinstaller:id/permission_deny_button")).click(); 299 } 300 clickDontAskAgainCheckbox()301 protected void clickDontAskAgainCheckbox() throws Exception { 302 getUiDevice().findObject(new UiSelector().resourceId( 303 "com.android.packageinstaller:id/do_not_ask_checkbox")).click(); 304 } 305 clickDontAskAgainButton()306 protected void clickDontAskAgainButton() throws Exception { 307 scrollToBottomIfWatch(); 308 getUiDevice().findObject(new UiSelector().resourceId( 309 "com.android.packageinstaller:id/permission_deny_dont_ask_again_button")).click(); 310 } 311 grantPermission(String permission)312 protected void grantPermission(String permission) throws Exception { 313 grantPermissions(new String[]{permission}); 314 } 315 grantPermissions(String[] permissions)316 protected void grantPermissions(String[] permissions) throws Exception { 317 setPermissionGrantState(permissions, true, false); 318 } 319 revokePermission(String permission)320 protected void revokePermission(String permission) throws Exception { 321 revokePermissions(new String[] {permission}, false); 322 } 323 revokePermissions(String[] permissions, boolean legacyApp)324 protected void revokePermissions(String[] permissions, boolean legacyApp) throws Exception { 325 setPermissionGrantState(permissions, false, legacyApp); 326 } 327 scrollToBottomIfWatch()328 private void scrollToBottomIfWatch() throws Exception { 329 if (mWatch) { 330 UiScrollable scrollable = 331 new UiScrollable(new UiSelector().className(ScrollView.class)); 332 if (scrollable.exists()) { 333 scrollable.flingToEnd(10); 334 } 335 } 336 } 337 setPermissionGrantState(String[] permissions, boolean granted, boolean legacyApp)338 private void setPermissionGrantState(String[] permissions, boolean granted, 339 boolean legacyApp) throws Exception { 340 getUiDevice().pressBack(); 341 waitForIdle(); 342 getUiDevice().pressBack(); 343 waitForIdle(); 344 getUiDevice().pressBack(); 345 waitForIdle(); 346 347 if (isTv()) { 348 getUiDevice().pressHome(); 349 waitForIdle(); 350 } 351 352 // Open the app details settings 353 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); 354 intent.addCategory(Intent.CATEGORY_DEFAULT); 355 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 356 intent.setData(Uri.parse("package:" + mContext.getPackageName())); 357 startActivity(intent); 358 359 waitForIdle(); 360 361 // Open the permissions UI 362 String label = mContext.getResources().getString(R.string.Permissions); 363 AccessibilityNodeInfo permLabelView = getNodeTimed(() -> findByText(label), true); 364 Assert.assertNotNull("Permissions label should be present", permLabelView); 365 366 AccessibilityNodeInfo permItemView = findCollectionItem(permLabelView); 367 Assert.assertNotNull("Permissions item should be present", permItemView); 368 369 click(permItemView); 370 371 waitForIdle(); 372 373 for (String permission : permissions) { 374 // Find the permission toggle 375 String permissionLabel = getPermissionLabel(permission); 376 377 AccessibilityNodeInfo labelView = getNodeTimed(() -> findByText(permissionLabel), true); 378 Assert.assertNotNull("Permission label should be present", labelView); 379 380 AccessibilityNodeInfo itemView = findCollectionItem(labelView); 381 Assert.assertNotNull("Permission item should be present", itemView); 382 383 final AccessibilityNodeInfo toggleView = findSwitch(itemView); 384 Assert.assertNotNull("Permission toggle should be present", toggleView); 385 386 final boolean wasGranted = toggleView.isChecked(); 387 if (granted != wasGranted) { 388 // Toggle the permission 389 390 if (!itemView.getActionList().contains(AccessibilityAction.ACTION_CLICK)) { 391 click(toggleView); 392 } else { 393 click(itemView); 394 } 395 396 waitForIdle(); 397 398 if (wasGranted && legacyApp) { 399 scrollToBottomIfWatch(); 400 String packageName = getInstrumentation().getContext().getPackageManager() 401 .getPermissionControllerPackageName(); 402 String resIdName = "com.android.packageinstaller" 403 + ":string/grant_dialog_button_deny_anyway"; 404 Resources resources = getInstrumentation().getContext() 405 .createPackageContext(packageName, 0).getResources(); 406 final int confirmResId = resources.getIdentifier(resIdName, null, null); 407 String confirmTitle = resources.getString(confirmResId); 408 UiObject denyAnyway = getUiDevice().findObject(new UiSelector() 409 .textStartsWith(confirmTitle)); 410 denyAnyway.click(); 411 412 waitForIdle(); 413 } 414 } 415 } 416 417 getUiDevice().pressBack(); 418 waitForIdle(); 419 getUiDevice().pressBack(); 420 waitForIdle(); 421 } 422 getPermissionLabel(String permission)423 private String getPermissionLabel(String permission) throws Exception { 424 String labelResName = sPermissionToLabelResNameMap.get(permission); 425 assertNotNull("Unknown permisison " + permission, labelResName); 426 final int resourceId = mPlatformResources.getIdentifier(labelResName, null, null); 427 return mPlatformResources.getString(resourceId); 428 } 429 startActivity(final Intent intent)430 private void startActivity(final Intent intent) throws Exception { 431 getInstrumentation().getUiAutomation().executeAndWaitForEvent( 432 () -> { 433 try { 434 getInstrumentation().getContext().startActivity(intent); 435 } catch (Exception e) { 436 Log.e(LOG_TAG, "Cannot start activity: " + intent, e); 437 fail("Cannot start activity: " + intent); 438 } 439 }, (AccessibilityEvent event) -> event.getEventType() 440 == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED 441 , GLOBAL_TIMEOUT_MILLIS); 442 } 443 findByText(String text)444 private AccessibilityNodeInfo findByText(String text) throws Exception { 445 AccessibilityNodeInfo root = getInstrumentation().getUiAutomation().getRootInActiveWindow(); 446 AccessibilityNodeInfo result = findByText(root, text); 447 if (result != null) { 448 return result; 449 } 450 return findByTextInCollection(root, text); 451 } 452 findByText(AccessibilityNodeInfo root, String text)453 private static AccessibilityNodeInfo findByText(AccessibilityNodeInfo root, String text) { 454 List<AccessibilityNodeInfo> nodes = root.findAccessibilityNodeInfosByText(text); 455 for (AccessibilityNodeInfo node : nodes) { 456 if (node.getText().toString().equals(text)) { 457 return node; 458 } 459 } 460 return null; 461 } 462 findByTextInCollection(AccessibilityNodeInfo root, String text)463 private static AccessibilityNodeInfo findByTextInCollection(AccessibilityNodeInfo root, 464 String text) throws Exception { 465 AccessibilityNodeInfo result; 466 final int childCount = root.getChildCount(); 467 for (int i = 0; i < childCount; i++) { 468 AccessibilityNodeInfo child = root.getChild(i); 469 if (child == null) { 470 continue; 471 } 472 if (child.getCollectionInfo() != null) { 473 scrollTop(child); 474 result = getNodeTimed(() -> findByText(child, text), false); 475 if (result != null) { 476 return result; 477 } 478 try { 479 while (child.getActionList().contains(AccessibilityAction.ACTION_SCROLL_FORWARD)) { 480 scrollForward(child); 481 result = getNodeTimed(() -> findByText(child, text), false); 482 if (result != null) { 483 return result; 484 } 485 } 486 } catch (TimeoutException e) { 487 /* ignore */ 488 } 489 } else { 490 result = findByTextInCollection(child, text); 491 if (result != null) { 492 return result; 493 } 494 } 495 } 496 return null; 497 } 498 scrollTop(AccessibilityNodeInfo node)499 private static void scrollTop(AccessibilityNodeInfo node) throws Exception { 500 try { 501 while (node.getActionList().contains(AccessibilityAction.ACTION_SCROLL_BACKWARD)) { 502 scroll(node, false); 503 } 504 } catch (TimeoutException e) { 505 /* ignore */ 506 } 507 } 508 scrollForward(AccessibilityNodeInfo node)509 private static void scrollForward(AccessibilityNodeInfo node) throws Exception { 510 scroll(node, true); 511 } 512 scroll(AccessibilityNodeInfo node, boolean forward)513 private static void scroll(AccessibilityNodeInfo node, boolean forward) throws Exception { 514 getInstrumentation().getUiAutomation().executeAndWaitForEvent( 515 () -> { 516 if (isTv()) { 517 if (forward) { 518 getUiDevice().pressDPadDown(); 519 } else { 520 for (int i = 0; i < 50; i++) { 521 getUiDevice().pressDPadUp(); 522 } 523 } 524 } else { 525 node.performAction(forward 526 ? AccessibilityNodeInfo.ACTION_SCROLL_FORWARD 527 : AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); 528 } 529 }, 530 (AccessibilityEvent event) -> event.getEventType() 531 == AccessibilityEvent.TYPE_VIEW_SCROLLED 532 || event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED, 533 GLOBAL_TIMEOUT_MILLIS); 534 node.refresh(); 535 waitForIdle(); 536 } 537 click(AccessibilityNodeInfo node)538 private static void click(AccessibilityNodeInfo node) throws Exception { 539 getInstrumentation().getUiAutomation().executeAndWaitForEvent( 540 () -> node.performAction(AccessibilityNodeInfo.ACTION_CLICK), 541 (AccessibilityEvent event) -> event.getEventType() 542 == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED 543 || event.getEventType() == AccessibilityEvent.TYPE_WINDOWS_CHANGED, 544 GLOBAL_TIMEOUT_MILLIS); 545 } 546 findCollectionItem(AccessibilityNodeInfo current)547 private static AccessibilityNodeInfo findCollectionItem(AccessibilityNodeInfo current) 548 throws Exception { 549 AccessibilityNodeInfo result = current; 550 while (result != null) { 551 if (result.getCollectionItemInfo() != null) { 552 return result; 553 } 554 result = result.getParent(); 555 } 556 return null; 557 } 558 findSwitch(AccessibilityNodeInfo root)559 private static AccessibilityNodeInfo findSwitch(AccessibilityNodeInfo root) throws Exception { 560 if (Switch.class.getName().equals(root.getClassName().toString())) { 561 return root; 562 } 563 final int childCount = root.getChildCount(); 564 for (int i = 0; i < childCount; i++) { 565 AccessibilityNodeInfo child = root.getChild(i); 566 if (child == null) { 567 continue; 568 } 569 if (Switch.class.getName().equals(child.getClassName().toString())) { 570 return child; 571 } 572 AccessibilityNodeInfo result = findSwitch(child); 573 if (result != null) { 574 return result; 575 } 576 } 577 return null; 578 } 579 getNodeTimed( Callable<AccessibilityNodeInfo> callable, boolean retry)580 private static AccessibilityNodeInfo getNodeTimed( 581 Callable<AccessibilityNodeInfo> callable, boolean retry) throws Exception { 582 final long startTimeMillis = SystemClock.uptimeMillis(); 583 while (true) { 584 try { 585 AccessibilityNodeInfo node = callable.call(); 586 587 if (node != null) { 588 return node; 589 } 590 } catch (NullPointerException e) { 591 Log.e(LOG_TAG, "NPE while finding AccessibilityNodeInfo", e); 592 } 593 594 final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; 595 if (!retry || elapsedTimeMillis > RETRY_TIMEOUT) { 596 return null; 597 } 598 SystemClock.sleep(2 * elapsedTimeMillis); 599 } 600 } 601 waitForIdle()602 private static void waitForIdle() throws TimeoutException { 603 getInstrumentation().getUiAutomation().waitForIdle(IDLE_TIMEOUT_MILLIS, 604 GLOBAL_TIMEOUT_MILLIS); 605 } 606 isTv()607 private static boolean isTv() { 608 return getInstrumentation().getContext().getPackageManager() 609 .hasSystemFeature(PackageManager.FEATURE_LEANBACK); 610 } 611 } 612