Home | History | Annotate | Download | only in helpers
      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