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.functional.permissiontests; 18 19 import android.app.UiAutomation; 20 import android.content.Context; 21 import android.content.pm.PackageInfo; 22 import android.content.pm.PackageManager; 23 import android.content.pm.PackageManager.NameNotFoundException; 24 import android.os.ParcelFileDescriptor; 25 import android.os.SystemClock; 26 import android.support.test.launcherhelper.ILauncherStrategy; 27 import android.support.test.launcherhelper.LauncherStrategyFactory; 28 import android.support.test.uiautomator.By; 29 import android.support.test.uiautomator.Direction; 30 import android.support.test.uiautomator.UiDevice; 31 import android.support.test.uiautomator.UiObject2; 32 import android.support.test.uiautomator.Until; 33 import android.util.Log; 34 35 import junit.framework.Assert; 36 37 import java.io.BufferedReader; 38 import java.io.FileInputStream; 39 import java.io.IOException; 40 import java.io.InputStreamReader; 41 import java.util.ArrayList; 42 import java.util.Arrays; 43 import java.util.Hashtable; 44 import java.util.List; 45 46 public class PermissionHelper { 47 public static final String TEST_TAG = "PermissionTest"; 48 public static final String SETTINGS_PACKAGE = "com.android.settings"; 49 public final int TIMEOUT = 500; 50 public static PermissionHelper mInstance = null; 51 private UiDevice mDevice; 52 private Context mContext; 53 private static UiAutomation mUiAutomation; 54 public static Hashtable<String, List<String>> mPermissionGroupInfo = null; 55 ILauncherStrategy mLauncherStrategy; 56 PermissionHelper(UiDevice device, Context context, UiAutomation uiAutomation)57 private PermissionHelper(UiDevice device, Context context, UiAutomation uiAutomation) { 58 mDevice = device; 59 mContext = context; 60 mUiAutomation = uiAutomation; 61 mLauncherStrategy = LauncherStrategyFactory.getInstance(mDevice).getLauncherStrategy(); 62 } 63 getInstance(UiDevice device, Context context, UiAutomation uiAutomation)64 public static PermissionHelper getInstance(UiDevice device, Context context, 65 UiAutomation uiAutomation) { 66 if (mInstance == null) { 67 mInstance = new PermissionHelper(device, context, uiAutomation); 68 PermissionHelper.populateDangerousPermissionGroupInfo(); 69 } 70 return mInstance; 71 } 72 73 /** 74 * Returns list of all dangerous permission of the system 75 */ populateDangerousPermissionGroupInfo()76 private static void populateDangerousPermissionGroupInfo() { 77 ParcelFileDescriptor pfd = mUiAutomation.executeShellCommand("pm list permissions -g -d"); 78 try (BufferedReader reader = new BufferedReader( 79 new InputStreamReader(new FileInputStream(pfd.getFileDescriptor())))) { 80 String line; 81 List<String> permissions = new ArrayList<String>(); 82 String groupName = null; 83 while ((line = reader.readLine()) != null) { 84 if (line.isEmpty()) { 85 // Do nothing 86 } else if (line.startsWith("group")) { 87 if (mPermissionGroupInfo == null) { 88 mPermissionGroupInfo = new Hashtable<String, List<String>>(); 89 } else { 90 mPermissionGroupInfo.put(groupName, permissions); 91 permissions = new ArrayList<String>(); 92 } 93 groupName = line.split(":")[1]; 94 } else if (line.startsWith(" permission:")) { 95 permissions.add(line.split(":")[1]); 96 } 97 } 98 mPermissionGroupInfo.put(groupName, permissions); 99 } catch (IOException e) { 100 Log.e(TEST_TAG, e.getMessage()); 101 } 102 } 103 104 /** 105 * Returns list of permission asked by package 106 * @param packageName : PackageName for which permission list to be returned 107 * @param permitted : set 'true' for normal and default granted dangerous permissions, 'false' 108 * otherwise 109 * @return 110 */ getPermissionByPackage(String packageName, Boolean permitted)111 public List<String> getPermissionByPackage(String packageName, Boolean permitted) { 112 List<String> selectedPermissions = new ArrayList<String>(); 113 String[] requestedPermissions = null; 114 int[] requestedPermissionFlags = null; 115 PackageInfo packageInfo = null; 116 try { 117 packageInfo = getPackageManager().getPackageInfo(packageName, 118 PackageManager.GET_PERMISSIONS); 119 } catch (NameNotFoundException e) { 120 throw new RuntimeException(String.format("%s package isn't found", packageName)); 121 } 122 123 requestedPermissions = packageInfo.requestedPermissions; 124 requestedPermissionFlags = packageInfo.requestedPermissionsFlags; 125 for (int i = 0; i < requestedPermissions.length; ++i) { 126 // requestedPermissionFlags 1 = Denied, 3 = Granted 127 if (permitted && requestedPermissionFlags[i] == 3) { 128 selectedPermissions.add(requestedPermissions[i]); 129 } else if (!permitted && requestedPermissionFlags[i] == 1) { 130 selectedPermissions.add(requestedPermissions[i]); 131 } 132 } 133 return selectedPermissions; 134 } 135 136 /** 137 * Verify any dangerous permission not mentioned in manifest aren't granted 138 * @param packageName 139 * @param permittedGroups 140 */ verifyExtraDangerousPermissionNotGranted(String packageName, String[] permittedGroups)141 public void verifyExtraDangerousPermissionNotGranted(String packageName, 142 String[] permittedGroups) { 143 List<String> allPermittedDangerousPermsList = getAllDangerousPermissionsByPermGrpNames( 144 permittedGroups); 145 List<String> allPermissionsForPackageList = getPermissionByPackage(packageName, 146 Boolean.TRUE); 147 List<String> allPlatformDangerousPermissionList = getPlatformDangerousPermissionGroupNames(); 148 allPermissionsForPackageList.retainAll(allPlatformDangerousPermissionList); 149 allPermissionsForPackageList.removeAll(allPermittedDangerousPermsList); 150 Assert.assertTrue( 151 String.format("For package %s some extra dangerous permissions have been granted", 152 packageName), 153 allPermissionsForPackageList.isEmpty()); 154 } 155 156 /** 157 * Verify any dangerous permission mentioned in manifest that is not default for privileged app 158 * isn't granted. Example: Location permission for Camera app 159 * @param packageName 160 * @param notPermittedGroups 161 */ verifyNotPermittedDangerousPermissionDenied(String packageName, String[] notPermittedGroups)162 public void verifyNotPermittedDangerousPermissionDenied(String packageName, 163 String[] notPermittedGroups) { 164 List<String> allNotPermittedDangerousPermsList = getAllDangerousPermissionsByPermGrpNames( 165 notPermittedGroups); 166 List<String> allPermissionsForPackageList = getPermissionByPackage(packageName, 167 Boolean.TRUE); 168 int allNotPermittedDangerousPermsCount = allNotPermittedDangerousPermsList.size(); 169 allNotPermittedDangerousPermsList.removeAll(allPermissionsForPackageList); 170 Assert.assertTrue( 171 String.format("For package %s not permissible dangerous permissions been granted", 172 packageName), 173 allNotPermittedDangerousPermsList.size() == allNotPermittedDangerousPermsCount); 174 } 175 176 /** 177 * Verify any normal permission mentioned in manifest is auto granted 178 * @param packageName 179 */ verifyNormalPermissionsAutoGranted(String packageName)180 public void verifyNormalPermissionsAutoGranted(String packageName) { 181 List<String> allDeniedPermissionsForPackageList = getPermissionByPackage(packageName, 182 Boolean.FALSE); 183 List<String> allPlatformDangerousPermissionList = getPlatformDangerousPermissionGroupNames(); 184 allDeniedPermissionsForPackageList.removeAll(allPlatformDangerousPermissionList); 185 if (!allDeniedPermissionsForPackageList.isEmpty()) { 186 for (int i = 0; i < allDeniedPermissionsForPackageList.size(); ++i) { 187 Log.d(TEST_TAG, String.format("%s should have been auto granted", 188 allDeniedPermissionsForPackageList.get(i))); 189 } 190 } 191 Assert.assertTrue( 192 String.format("For package %s few normal permission have been denied", packageName), 193 allDeniedPermissionsForPackageList.isEmpty()); 194 } 195 196 /** 197 * Verifies via UI that a permission is set/unset for an app 198 * @param appName 199 * @param permission 200 * @param expected : 'ON' or 'OFF' 201 * @return 202 */ verifyPermissionSettingStatus(String appName, String permission, PermissionStatus expected)203 public Boolean verifyPermissionSettingStatus(String appName, String permission, 204 PermissionStatus expected) { 205 if (!expected.equals(PermissionStatus.ON) && !expected.equals(PermissionStatus.OFF)) { 206 throw new RuntimeException(String.format("%s isn't valid permission status", expected)); 207 } 208 openAppPermissionView(appName); 209 UiObject2 permissionView = mDevice 210 .wait(Until.findObject(By.res("android:id/list_container")), TIMEOUT); 211 List<UiObject2> permissionsList = permissionView.getChildren().get(0).getChildren(); 212 for (UiObject2 permDesc : permissionsList) { 213 if (permDesc.getChildren().get(1).getChildren().get(0).getText().equals(permission)) { 214 String status = permDesc.getChildren().get(2).getChildren().get(0).getText(); 215 return status.equals(expected.toString().toUpperCase()); 216 } 217 } 218 Assert.fail("Permission is not found"); 219 return Boolean.FALSE; 220 } 221 222 /** 223 * Verify default dangerous permission mentioned in manifest for system privileged apps are auto 224 * permitted Example: Camera permission for Camera app 225 * @param packageName 226 * @param permittedGroups 227 */ verifyDefaultDangerousPermissionGranted(String packageName, String[] permittedGroups, Boolean byGroup)228 public void verifyDefaultDangerousPermissionGranted(String packageName, 229 String[] permittedGroups, 230 Boolean byGroup) { 231 List<String> allPermittedDangerousPermsList = new ArrayList<String>(); 232 if (byGroup) { 233 allPermittedDangerousPermsList = getAllDangerousPermissionsByPermGrpNames( 234 permittedGroups); 235 } else { 236 allPermittedDangerousPermsList.addAll(Arrays.asList(permittedGroups)); 237 } 238 239 List<String> allPermissionsForPackageList = getPermissionByPackage(packageName, 240 Boolean.TRUE); 241 allPermittedDangerousPermsList.removeAll(allPermissionsForPackageList); 242 for (String permission : allPermittedDangerousPermsList) { 243 Log.d(TEST_TAG, 244 String.format("%s - > %s hasn't been granted yet", packageName, permission)); 245 } 246 Assert.assertTrue(String.format("For %s some Permissions aren't granted yet", packageName), 247 allPermittedDangerousPermsList.isEmpty()); 248 } 249 250 /** 251 * For a given app, opens the permission settings window settings -> apps -> permissions 252 * @param appName 253 */ openAppPermissionView(String appName)254 public void openAppPermissionView(String appName) { 255 mDevice.pressHome(); 256 launchApp(SETTINGS_PACKAGE, "Settings"); 257 UiObject2 app = null; 258 UiObject2 view = null; 259 int maxAttempt = 5; 260 while ((maxAttempt-- > 0) 261 && ((app = mDevice.wait(Until.findObject(By.res("android:id/title").text("Apps")), 262 TIMEOUT)) == null)) { 263 view = mDevice.wait(Until.findObject(By.res(SETTINGS_PACKAGE, "main_content")), 264 TIMEOUT); 265 // todo scroll may be different for device and build 266 view.scroll(Direction.DOWN, 1.0f); 267 } 268 269 mDevice.wait(Until.findObject(By.res("android:id/title").text("Apps")), 270 TIMEOUT) 271 .clickAndWait(Until.newWindow(), TIMEOUT); 272 app = null; 273 view = null; 274 maxAttempt = 10; 275 while ((maxAttempt-- > 0) 276 && ((app = mDevice.wait(Until.findObject(By.res("android:id/title").text(appName)), 277 TIMEOUT)) == null)) { 278 view = mDevice.wait(Until.findObject(By.res("com.android.settings:id/main_content")), 279 TIMEOUT); 280 // todo scroll may be different for device and build 281 view.scroll(Direction.DOWN, 1.0f); 282 } 283 app.clickAndWait(Until.newWindow(), TIMEOUT); 284 mDevice.wait(Until.findObject(By.res("android:id/title").text("Permissions")), 285 TIMEOUT).clickAndWait(Until.newWindow(), TIMEOUT); 286 } 287 288 /** 289 * Toggles permission for an app via UI 290 * @param appName 291 * @param permission 292 * @param toBeSet 293 */ togglePermissionSetting(String appName, String permission, Boolean toBeSet)294 public void togglePermissionSetting(String appName, String permission, Boolean toBeSet) { 295 openAppPermissionView(appName); 296 UiObject2 permissionView = mDevice 297 .wait(Until.findObject(By.res("android:id/list_container")), TIMEOUT); 298 List<UiObject2> permissionsList = permissionView.getChildren().get(0).getChildren(); 299 for (UiObject2 obj : permissionsList) { 300 if (obj.getChildren().get(1).getChildren().get(0).getText().equals(permission)) { 301 String status = obj.getChildren().get(2).getChildren().get(0).getText(); 302 if ((toBeSet && !status.equals(PermissionStatus.ON.toString())) 303 || (!toBeSet && status.equals(PermissionStatus.ON.toString()))) { 304 obj.getChildren().get(2).getChildren().get(0).click(); 305 mDevice.waitForIdle(); 306 } 307 break; 308 } 309 } 310 } 311 312 /** 313 * Grant or revoke permission via adb command 314 * @param packageName 315 * @param permissionName 316 * @param permissionOp : Accepted values are 'grant' and 'revoke' 317 */ grantOrRevokePermissionViaAdb(String packageName, String permissionName, PermissionOp permissionOp)318 public void grantOrRevokePermissionViaAdb(String packageName, String permissionName, 319 PermissionOp permissionOp) { 320 if (permissionOp == null) { 321 throw new RuntimeException("null operation can't be executed"); 322 } 323 String command = String.format("pm %s %s %s", permissionOp.toString().toLowerCase(), 324 packageName, permissionName); 325 Log.d(TEST_TAG, String.format("executing - %s", command)); 326 mUiAutomation.executeShellCommand(command); 327 mDevice.waitForIdle(); 328 } 329 330 /** 331 * returns list of specific permissions in a dangerous permission group 332 * @param permissionGroupsToCheck 333 * @return 334 */ getAllDangerousPermissionsByPermGrpNames(String[] permissionGroupsToCheck)335 public List<String> getAllDangerousPermissionsByPermGrpNames(String[] permissionGroupsToCheck) { 336 List<String> allDangerousPermissions = new ArrayList<String>(); 337 for (String s : permissionGroupsToCheck) { 338 String grpName = String.format("android.permission-group.%s", s.toUpperCase()); 339 if (PermissionHelper.mPermissionGroupInfo.keySet().contains(grpName)) { 340 allDangerousPermissions.addAll(PermissionHelper.mPermissionGroupInfo.get(grpName)); 341 } 342 } 343 344 return allDangerousPermissions; 345 } 346 347 /** 348 * Returns platform dangerous permission group names 349 * @return 350 */ getPlatformDangerousPermissionGroupNames()351 public List<String> getPlatformDangerousPermissionGroupNames() { 352 List<String> allDangerousPermissions = new ArrayList<String>(); 353 for (List<String> prmsList : PermissionHelper.mPermissionGroupInfo.values()) { 354 allDangerousPermissions.addAll(prmsList); 355 } 356 return allDangerousPermissions; 357 } 358 359 /** 360 * To ensure that all default dangerous permissions mentioned in manifest are granted for any 361 * privileged app 362 * @param packageName 363 * @param granted 364 * @param denied 365 */ ensureAppHasDefaultPermissions(String packageName, String[] granted, String[] denied)366 public void ensureAppHasDefaultPermissions(String packageName, String[] granted, 367 String[] denied) { 368 List<String> defaultGranted = getAllDangerousPermissionsByPermGrpNames(granted); 369 List<String> currentGranted = getPermissionByPackage(packageName, Boolean.TRUE); 370 List<String> defaultDenied = getAllDangerousPermissionsByPermGrpNames(denied); 371 List<String> currentDenied = getPermissionByPackage(packageName, Boolean.FALSE); 372 defaultGranted.removeAll(currentGranted); 373 for (String permission : defaultGranted) { 374 grantOrRevokePermissionViaAdb(packageName, permission, PermissionOp.GRANT); 375 } 376 defaultDenied.removeAll(currentDenied); 377 for (String permission : defaultDenied) { 378 grantOrRevokePermissionViaAdb(packageName, permission, PermissionOp.REVOKE); 379 } 380 } 381 382 /** 383 * Get permission description via UI 384 * @param appName 385 * @return 386 */ getPermissionDescGroupNames(String appName)387 public List<String> getPermissionDescGroupNames(String appName) { 388 List<String> groupNames = new ArrayList<String>(); 389 openAppPermissionView(appName); 390 mDevice.wait(Until.findObject(By.desc("More options")), TIMEOUT).click(); 391 mDevice.wait(Until.findObject(By.text("All permissions")), TIMEOUT).click(); 392 UiObject2 permissionsListView = mDevice 393 .wait(Until.findObject(By.res("android:id/list_container")), TIMEOUT); 394 List<UiObject2> permissionList = permissionsListView 395 .findObjects(By.clazz("android.widget.TextView")); 396 for (UiObject2 obj : permissionList) { 397 if (obj.getText() != null && obj.getText() != "" && obj.getVisibleBounds().left == 0) { 398 if (obj.getText().equals("Other app capabilities")) 399 break; 400 groupNames.add(obj.getText().toUpperCase()); 401 } 402 } 403 return groupNames; 404 } 405 getPackageManager()406 public PackageManager getPackageManager() { 407 return mContext.getPackageManager(); 408 } 409 launchApp(String packageName, String appName)410 public void launchApp(String packageName, String appName) { 411 if (!mDevice.hasObject(By.pkg(packageName).depth(0))) { 412 mLauncherStrategy.launch(appName, packageName); 413 } 414 } 415 cleanPackage(String packageName)416 public void cleanPackage(String packageName) { 417 mUiAutomation.executeShellCommand(String.format("pm clear %s", packageName)); 418 SystemClock.sleep(2 * TIMEOUT); 419 } 420 421 /** Supported operations on permission */ 422 public enum PermissionOp { 423 GRANT, REVOKE; 424 } 425 426 /** Available permission status */ 427 public enum PermissionStatus { 428 ON, OFF; 429 } 430 } 431