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