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