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.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; 38 import android.support.test.uiautomator.UiDevice; 39 import android.support.test.uiautomator.UiObject; 40 import android.support.test.uiautomator.UiObject2; 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.widget.Switch; 47 import junit.framework.Assert; 48 import org.junit.Before; 49 import org.junit.runner.RunWith; 50 51 import java.util.List; 52 import java.util.Map; 53 import java.util.concurrent.Callable; 54 import java.util.concurrent.TimeoutException; 55 56 @RunWith(AndroidJUnit4.class) 57 public abstract class BasePermissionsTest { 58 private static final String PLATFORM_PACKAGE_NAME = "android"; 59 60 private static final long IDLE_TIMEOUT_MILLIS = 500; 61 private static final long GLOBAL_TIMEOUT_MILLIS = 5000; 62 63 private static final long RETRY_TIMEOUT = 5000; 64 private static final String LOG_TAG = "BasePermissionsTest"; 65 66 private static Map<String, String> sPermissionToLabelResNameMap = new ArrayMap<>(); 67 static { 68 // Contacts sPermissionToLabelResNameMap.put(Manifest.permission.READ_CONTACTS, "@android:string/permgrouplab_contacts")69 sPermissionToLabelResNameMap.put(Manifest.permission.READ_CONTACTS, 70 "@android:string/permgrouplab_contacts"); sPermissionToLabelResNameMap.put(Manifest.permission.WRITE_CONTACTS, "@android:string/permgrouplab_contacts")71 sPermissionToLabelResNameMap.put(Manifest.permission.WRITE_CONTACTS, 72 "@android:string/permgrouplab_contacts"); 73 // Calendar sPermissionToLabelResNameMap.put(Manifest.permission.READ_CALENDAR, "@android:string/permgrouplab_calendar")74 sPermissionToLabelResNameMap.put(Manifest.permission.READ_CALENDAR, 75 "@android:string/permgrouplab_calendar"); sPermissionToLabelResNameMap.put(Manifest.permission.WRITE_CALENDAR, "@android:string/permgrouplab_calendar")76 sPermissionToLabelResNameMap.put(Manifest.permission.WRITE_CALENDAR, 77 "@android:string/permgrouplab_calendar"); 78 // SMS sPermissionToLabelResNameMap.put(Manifest.permission.SEND_SMS, "@android:string/permgrouplab_sms")79 sPermissionToLabelResNameMap.put(Manifest.permission.SEND_SMS, 80 "@android:string/permgrouplab_sms"); sPermissionToLabelResNameMap.put(Manifest.permission.RECEIVE_SMS, "@android:string/permgrouplab_sms")81 sPermissionToLabelResNameMap.put(Manifest.permission.RECEIVE_SMS, 82 "@android:string/permgrouplab_sms"); sPermissionToLabelResNameMap.put(Manifest.permission.READ_SMS, "@android:string/permgrouplab_sms")83 sPermissionToLabelResNameMap.put(Manifest.permission.READ_SMS, 84 "@android:string/permgrouplab_sms"); sPermissionToLabelResNameMap.put(Manifest.permission.RECEIVE_WAP_PUSH, "@android:string/permgrouplab_sms")85 sPermissionToLabelResNameMap.put(Manifest.permission.RECEIVE_WAP_PUSH, 86 "@android:string/permgrouplab_sms"); sPermissionToLabelResNameMap.put(Manifest.permission.RECEIVE_MMS, "@android:string/permgrouplab_sms")87 sPermissionToLabelResNameMap.put(Manifest.permission.RECEIVE_MMS, 88 "@android:string/permgrouplab_sms"); 89 sPermissionToLabelResNameMap.put("android.permission.READ_CELL_BROADCASTS", 90 "@android:string/permgrouplab_sms"); 91 // Storage sPermissionToLabelResNameMap.put(Manifest.permission.READ_EXTERNAL_STORAGE, "@android:string/permgrouplab_storage")92 sPermissionToLabelResNameMap.put(Manifest.permission.READ_EXTERNAL_STORAGE, 93 "@android:string/permgrouplab_storage"); sPermissionToLabelResNameMap.put(Manifest.permission.WRITE_EXTERNAL_STORAGE, "@android:string/permgrouplab_storage")94 sPermissionToLabelResNameMap.put(Manifest.permission.WRITE_EXTERNAL_STORAGE, 95 "@android:string/permgrouplab_storage"); 96 // Location sPermissionToLabelResNameMap.put(Manifest.permission.ACCESS_FINE_LOCATION, "@android:string/permgrouplab_location")97 sPermissionToLabelResNameMap.put(Manifest.permission.ACCESS_FINE_LOCATION, 98 "@android:string/permgrouplab_location"); sPermissionToLabelResNameMap.put(Manifest.permission.ACCESS_COARSE_LOCATION, "@android:string/permgrouplab_location")99 sPermissionToLabelResNameMap.put(Manifest.permission.ACCESS_COARSE_LOCATION, 100 "@android:string/permgrouplab_location"); 101 // Phone sPermissionToLabelResNameMap.put(Manifest.permission.READ_PHONE_STATE, "@android:string/permgrouplab_phone")102 sPermissionToLabelResNameMap.put(Manifest.permission.READ_PHONE_STATE, 103 "@android:string/permgrouplab_phone"); sPermissionToLabelResNameMap.put(Manifest.permission.CALL_PHONE, "@android:string/permgrouplab_phone")104 sPermissionToLabelResNameMap.put(Manifest.permission.CALL_PHONE, 105 "@android:string/permgrouplab_phone"); 106 sPermissionToLabelResNameMap.put("android.permission.ACCESS_IMS_CALL_SERVICE", 107 "@android:string/permgrouplab_phone"); sPermissionToLabelResNameMap.put(Manifest.permission.READ_CALL_LOG, "@android:string/permgrouplab_phone")108 sPermissionToLabelResNameMap.put(Manifest.permission.READ_CALL_LOG, 109 "@android:string/permgrouplab_phone"); sPermissionToLabelResNameMap.put(Manifest.permission.WRITE_CALL_LOG, "@android:string/permgrouplab_phone")110 sPermissionToLabelResNameMap.put(Manifest.permission.WRITE_CALL_LOG, 111 "@android:string/permgrouplab_phone"); sPermissionToLabelResNameMap.put(Manifest.permission.ADD_VOICEMAIL, "@android:string/permgrouplab_phone")112 sPermissionToLabelResNameMap.put(Manifest.permission.ADD_VOICEMAIL, 113 "@android:string/permgrouplab_phone"); sPermissionToLabelResNameMap.put(Manifest.permission.USE_SIP, "@android:string/permgrouplab_phone")114 sPermissionToLabelResNameMap.put(Manifest.permission.USE_SIP, 115 "@android:string/permgrouplab_phone"); sPermissionToLabelResNameMap.put(Manifest.permission.PROCESS_OUTGOING_CALLS, "@android:string/permgrouplab_phone")116 sPermissionToLabelResNameMap.put(Manifest.permission.PROCESS_OUTGOING_CALLS, 117 "@android:string/permgrouplab_phone"); 118 // Microphone sPermissionToLabelResNameMap.put(Manifest.permission.RECORD_AUDIO, "@android:string/permgrouplab_microphone")119 sPermissionToLabelResNameMap.put(Manifest.permission.RECORD_AUDIO, 120 "@android:string/permgrouplab_microphone"); 121 // Camera sPermissionToLabelResNameMap.put(Manifest.permission.CAMERA, "@android:string/permgrouplab_camera")122 sPermissionToLabelResNameMap.put(Manifest.permission.CAMERA, 123 "@android:string/permgrouplab_camera"); 124 // Body sensors sPermissionToLabelResNameMap.put(Manifest.permission.BODY_SENSORS, "@android:string/permgrouplab_sensors")125 sPermissionToLabelResNameMap.put(Manifest.permission.BODY_SENSORS, 126 "@android:string/permgrouplab_sensors"); 127 } 128 129 private Context mContext; 130 private Resources mPlatformResources; 131 getInstrumentation()132 protected static Instrumentation getInstrumentation() { 133 return InstrumentationRegistry.getInstrumentation(); 134 } 135 assertPermissionRequestResult(BasePermissionActivity.Result result, int requestCode, String[] permissions, boolean[] granted)136 protected static void assertPermissionRequestResult(BasePermissionActivity.Result result, 137 int requestCode, String[] permissions, boolean[] granted) { 138 assertEquals(requestCode, result.requestCode); 139 for (int i = 0; i < permissions.length; i++) { 140 assertEquals(permissions[i], result.permissions[i]); 141 assertEquals(granted[i] ? PackageManager.PERMISSION_GRANTED 142 : PackageManager.PERMISSION_DENIED, result.grantResults[i]); 143 144 } 145 } 146 getUiDevice()147 protected static UiDevice getUiDevice() { 148 return UiDevice.getInstance(getInstrumentation()); 149 } 150 launchActivity(String packageName, Class<?> clazz, Bundle extras)151 protected static Activity launchActivity(String packageName, 152 Class<?> clazz, Bundle extras) { 153 Intent intent = new Intent(Intent.ACTION_MAIN); 154 intent.setClassName(packageName, clazz.getName()); 155 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 156 if (extras != null) { 157 intent.putExtras(extras); 158 } 159 Activity activity = getInstrumentation().startActivitySync(intent); 160 getInstrumentation().waitForIdleSync(); 161 162 return activity; 163 } 164 165 @Before beforeTest()166 public void beforeTest() { 167 mContext = InstrumentationRegistry.getTargetContext(); 168 try { 169 Context platformContext = mContext.createPackageContext(PLATFORM_PACKAGE_NAME, 0); 170 mPlatformResources = platformContext.getResources(); 171 } catch (PackageManager.NameNotFoundException e) { 172 /* cannot happen */ 173 } 174 175 UiObject2 button = getUiDevice().findObject(By.text("Close")); 176 if (button != null) { 177 button.click(); 178 } 179 } 180 requestPermissions( String[] permissions, int requestCode, Class<?> clazz, Runnable postRequestAction)181 protected BasePermissionActivity.Result requestPermissions( 182 String[] permissions, int requestCode, Class<?> clazz, Runnable postRequestAction) 183 throws Exception { 184 // Start an activity 185 BasePermissionActivity activity = (BasePermissionActivity) launchActivity( 186 getInstrumentation().getTargetContext().getPackageName(), clazz, null); 187 188 activity.waitForOnCreate(); 189 190 // Request the permissions 191 activity.requestPermissions(permissions, requestCode); 192 193 // Define a more conservative idle criteria 194 getInstrumentation().getUiAutomation().waitForIdle( 195 IDLE_TIMEOUT_MILLIS, GLOBAL_TIMEOUT_MILLIS); 196 197 // Perform the post-request action 198 if (postRequestAction != null) { 199 postRequestAction.run(); 200 } 201 202 BasePermissionActivity.Result result = activity.getResult(); 203 activity.finish(); 204 return result; 205 } 206 clickAllowButton()207 protected void clickAllowButton() throws Exception { 208 getUiDevice().findObject(new UiSelector().resourceId( 209 "com.android.packageinstaller:id/permission_allow_button")).click(); 210 } 211 clickDenyButton()212 protected void clickDenyButton() throws Exception { 213 getUiDevice().findObject(new UiSelector().resourceId( 214 "com.android.packageinstaller:id/permission_deny_button")).click(); 215 } 216 clickDontAskAgainCheckbox()217 protected void clickDontAskAgainCheckbox() throws Exception { 218 getUiDevice().findObject(new UiSelector().resourceId( 219 "com.android.packageinstaller:id/do_not_ask_checkbox")).click(); 220 } 221 clickDontAskAgainButton()222 protected void clickDontAskAgainButton() throws Exception { 223 getUiDevice().findObject(new UiSelector().resourceId( 224 "com.android.packageinstaller:id/permission_deny_dont_ask_again_button")).click(); 225 } 226 grantPermission(String permission)227 protected void grantPermission(String permission) throws Exception { 228 grantPermissions(new String[]{permission}); 229 } 230 grantPermissions(String[] permissions)231 protected void grantPermissions(String[] permissions) throws Exception { 232 setPermissionGrantState(permissions, true, false); 233 } 234 revokePermission(String permission)235 protected void revokePermission(String permission) throws Exception { 236 revokePermissions(new String[] {permission}, false); 237 } 238 revokePermissions(String[] permissions, boolean legacyApp)239 protected void revokePermissions(String[] permissions, boolean legacyApp) throws Exception { 240 setPermissionGrantState(permissions, false, legacyApp); 241 } 242 setPermissionGrantState(String[] permissions, boolean granted, boolean legacyApp)243 private void setPermissionGrantState(String[] permissions, boolean granted, 244 boolean legacyApp) throws Exception { 245 getUiDevice().pressBack(); 246 waitForIdle(); 247 getUiDevice().pressBack(); 248 waitForIdle(); 249 getUiDevice().pressBack(); 250 waitForIdle(); 251 252 // Open the app details settings 253 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); 254 intent.addCategory(Intent.CATEGORY_DEFAULT); 255 intent.setData(Uri.parse("package:" + mContext.getPackageName())); 256 startActivity(intent); 257 258 waitForIdle(); 259 260 // Open the permissions UI 261 AccessibilityNodeInfo permLabelView = getNodeTimed(() -> findByText("Permissions")); 262 Assert.assertNotNull("Permissions label should be present", permLabelView); 263 264 AccessibilityNodeInfo permItemView = findCollectionItem(permLabelView); 265 Assert.assertNotNull("Permissions item should be present", permLabelView); 266 267 click(permItemView); 268 269 waitForIdle(); 270 271 for (String permission : permissions) { 272 // Find the permission toggle 273 String permissionLabel = getPermissionLabel(permission); 274 275 AccessibilityNodeInfo labelView = getNodeTimed(() -> findByText(permissionLabel)); 276 Assert.assertNotNull("Permission label should be present", labelView); 277 278 AccessibilityNodeInfo itemView = findCollectionItem(labelView); 279 Assert.assertNotNull("Permission item should be present", itemView); 280 281 final AccessibilityNodeInfo toggleView = findSwitch(itemView); 282 Assert.assertNotNull("Permission toggle should be present", toggleView); 283 284 final boolean wasGranted = toggleView.isChecked(); 285 if (granted != wasGranted) { 286 // Toggle the permission 287 288 if (!itemView.getActionList().contains(AccessibilityAction.ACTION_CLICK)) { 289 click(toggleView); 290 } else { 291 click(itemView); 292 } 293 294 waitForIdle(); 295 296 if (wasGranted && legacyApp) { 297 String packageName = getInstrumentation().getContext().getPackageManager() 298 .getPermissionControllerPackageName(); 299 String resIdName = "com.android.packageinstaller" 300 + ":string/grant_dialog_button_deny_anyway"; 301 Resources resources = getInstrumentation().getContext() 302 .createPackageContext(packageName, 0).getResources(); 303 final int confirmResId = resources.getIdentifier(resIdName, null, null); 304 String confirmTitle = resources.getString(confirmResId); 305 UiObject denyAnyway = getUiDevice().findObject(new UiSelector() 306 .text(confirmTitle.toUpperCase())); 307 denyAnyway.click(); 308 309 waitForIdle(); 310 } 311 } 312 } 313 314 getUiDevice().pressBack(); 315 waitForIdle(); 316 getUiDevice().pressBack(); 317 waitForIdle(); 318 } 319 getPermissionLabel(String permission)320 private String getPermissionLabel(String permission) throws Exception { 321 String labelResName = sPermissionToLabelResNameMap.get(permission); 322 assertNotNull("Unknown permisison " + permission, labelResName); 323 final int resourceId = mPlatformResources.getIdentifier(labelResName, null, null); 324 return mPlatformResources.getString(resourceId); 325 } 326 startActivity(final Intent intent)327 private void startActivity(final Intent intent) throws Exception { 328 getInstrumentation().getUiAutomation().executeAndWaitForEvent( 329 () -> { 330 try { 331 getInstrumentation().getContext().startActivity(intent); 332 } catch (Exception e) { 333 fail("Cannot start activity: " + intent); 334 } 335 }, (AccessibilityEvent event) -> event.getEventType() 336 == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED 337 , GLOBAL_TIMEOUT_MILLIS); 338 } 339 findByText(String text)340 private AccessibilityNodeInfo findByText(String text) throws Exception { 341 AccessibilityNodeInfo root = getInstrumentation().getUiAutomation().getRootInActiveWindow(); 342 AccessibilityNodeInfo result = findByText(root, text); 343 if (result != null) { 344 return result; 345 } 346 return findByTextInCollection(root, text); 347 } 348 findByText(AccessibilityNodeInfo root, String text)349 private static AccessibilityNodeInfo findByText(AccessibilityNodeInfo root, String text) { 350 List<AccessibilityNodeInfo> nodes = root.findAccessibilityNodeInfosByText(text); 351 for (AccessibilityNodeInfo node : nodes) { 352 if (node.getText().toString().equals(text)) { 353 return node; 354 } 355 } 356 return null; 357 } 358 findByTextInCollection(AccessibilityNodeInfo root, String text)359 private static AccessibilityNodeInfo findByTextInCollection(AccessibilityNodeInfo root, 360 String text) throws Exception { 361 AccessibilityNodeInfo result; 362 final int childCount = root.getChildCount(); 363 for (int i = 0; i < childCount; i++) { 364 AccessibilityNodeInfo child = root.getChild(i); 365 if (child == null) { 366 continue; 367 } 368 if (child.getCollectionInfo() != null) { 369 scrollTop(child); 370 result = getNodeTimed(() -> findByText(child, text)); 371 if (result != null) { 372 return result; 373 } 374 while (child.getActionList().contains(AccessibilityAction.ACTION_SCROLL_FORWARD)) { 375 scrollForward(child); 376 result = getNodeTimed(() -> findByText(child, text)); 377 if (result != null) { 378 return result; 379 } 380 } 381 } else { 382 result = findByTextInCollection(child, text); 383 if (result != null) { 384 return result; 385 } 386 } 387 } 388 return null; 389 } 390 scrollTop(AccessibilityNodeInfo node)391 private static void scrollTop(AccessibilityNodeInfo node) throws Exception { 392 try { 393 while (node.getActionList().contains(AccessibilityAction.ACTION_SCROLL_BACKWARD)) { 394 scroll(node, false); 395 } 396 } catch (TimeoutException e) { 397 /* ignore */ 398 } 399 } 400 scrollForward(AccessibilityNodeInfo node)401 private static void scrollForward(AccessibilityNodeInfo node) throws Exception { 402 try { 403 scroll(node, true); 404 } catch (TimeoutException e) { 405 /* ignore */ 406 } 407 } 408 scroll(AccessibilityNodeInfo node, boolean forward)409 private static void scroll(AccessibilityNodeInfo node, boolean forward) throws Exception { 410 getInstrumentation().getUiAutomation().executeAndWaitForEvent( 411 () -> node.performAction(forward 412 ? AccessibilityNodeInfo.ACTION_SCROLL_FORWARD 413 : AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD), 414 (AccessibilityEvent event) -> event.getEventType() 415 == AccessibilityEvent.TYPE_VIEW_SCROLLED, 416 GLOBAL_TIMEOUT_MILLIS); 417 waitForIdle(); 418 } 419 420 click(AccessibilityNodeInfo node)421 private static void click(AccessibilityNodeInfo node) throws Exception { 422 getInstrumentation().getUiAutomation().executeAndWaitForEvent( 423 () -> node.performAction(AccessibilityNodeInfo.ACTION_CLICK), 424 (AccessibilityEvent event) -> event.getEventType() 425 == AccessibilityEvent.TYPE_VIEW_CLICKED, 426 GLOBAL_TIMEOUT_MILLIS); 427 } 428 findCollectionItem(AccessibilityNodeInfo current)429 private static AccessibilityNodeInfo findCollectionItem(AccessibilityNodeInfo current) 430 throws Exception { 431 AccessibilityNodeInfo result = current; 432 while (result != null) { 433 if (result.getCollectionItemInfo() != null) { 434 return result; 435 } 436 result = result.getParent(); 437 } 438 return null; 439 } 440 findSwitch(AccessibilityNodeInfo root)441 private static AccessibilityNodeInfo findSwitch(AccessibilityNodeInfo root) throws Exception { 442 if (Switch.class.getName().equals(root.getClassName().toString())) { 443 return root; 444 } 445 final int childCount = root.getChildCount(); 446 for (int i = 0; i < childCount; i++) { 447 AccessibilityNodeInfo child = root.getChild(i); 448 if (child == null) { 449 continue; 450 } 451 if (Switch.class.getName().equals(child.getClassName().toString())) { 452 return child; 453 } 454 AccessibilityNodeInfo result = findSwitch(child); 455 if (result != null) { 456 return result; 457 } 458 } 459 return null; 460 } 461 getNodeTimed( Callable<AccessibilityNodeInfo> callable)462 private static AccessibilityNodeInfo getNodeTimed( 463 Callable<AccessibilityNodeInfo> callable) throws Exception { 464 final long startTimeMillis = SystemClock.uptimeMillis(); 465 while (true) { 466 try { 467 AccessibilityNodeInfo node = callable.call(); 468 469 if (node != null) { 470 return node; 471 } 472 } catch (NullPointerException e) { 473 Log.e(LOG_TAG, "NPE while finding AccessibilityNodeInfo", e); 474 } 475 476 final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; 477 if (elapsedTimeMillis > RETRY_TIMEOUT) { 478 return null; 479 } 480 SystemClock.sleep(2 * elapsedTimeMillis); 481 } 482 } 483 waitForIdle()484 private static void waitForIdle() throws TimeoutException { 485 getInstrumentation().getUiAutomation().waitForIdle(IDLE_TIMEOUT_MILLIS, 486 GLOBAL_TIMEOUT_MILLIS); 487 } 488 } 489