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 
PermissionHelper(Instrumentation instrumentation)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      */
getInstance(Instrumentation instrumentation)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      */
populateDangerousPermissionGroupInfo()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      */
getPermissionByPackage(String packageName, Boolean permitted)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      */
verifyExtraDangerousPermissionNotGranted(String packageName, String[] permittedGroups)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      */
verifyNotPermittedDangerousPermissionDenied(String packageName, String[] notPermittedGroups)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      */
verifyNormalPermissionsAutoGranted(String packageName)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      */
verifyPermissionSettingStatus(String appName, String permission, PermissionStatus expected)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      */
verifyDefaultDangerousPermissionGranted(String packageName, String[] permittedGroups, Boolean byGroup)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      */
openAppPermissionView(String appName)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      */
togglePermissionSetting(String appName, String permission, Boolean toBeSet)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      */
grantOrRevokePermissionViaAdb(String packageName, String permissionName, PermissionOp permissionOp)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      */
getAllDangerousPermissionsByPermGrpNames(String[] permissionGroupsToCheck)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      */
getPlatformDangerousPermissionGroupNames()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      */
ensureAppHasDefaultPermissions(String packageName, String[] granted, String[] denied)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      */
getPermissionDescGroupNames(String appName)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