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.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