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