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