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