1 /*
2  * Copyright (C) 2019 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.permission.cts;
18 
19 import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
20 import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
21 import static android.Manifest.permission.PACKAGE_USAGE_STATS;
22 import static android.app.AppOpsManager.MODE_ALLOWED;
23 import static android.app.AppOpsManager.MODE_FOREGROUND;
24 import static android.app.AppOpsManager.MODE_IGNORED;
25 import static android.app.AppOpsManager.OPSTR_GET_USAGE_STATS;
26 import static android.app.AppOpsManager.permissionToOp;
27 import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
28 import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
29 import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED;
30 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
31 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
32 import static android.content.pm.PackageManager.GET_PERMISSIONS;
33 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
34 import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
35 
36 import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
37 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
38 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
39 
40 import android.app.AppOpsManager;
41 import android.app.UiAutomation;
42 import android.content.Context;
43 import android.content.pm.PackageInfo;
44 import android.content.pm.PermissionInfo;
45 import android.os.Build;
46 import android.os.Process;
47 import android.os.UserHandle;
48 
49 import androidx.annotation.NonNull;
50 import androidx.test.platform.app.InstrumentationRegistry;
51 
52 import java.util.ArrayList;
53 import java.util.Arrays;
54 import java.util.List;
55 
56 /**
57  * Common utils for permission tests
58  */
59 public class PermissionUtils {
60     private static String GRANT_RUNTIME_PERMISSIONS = "android.permission.GRANT_RUNTIME_PERMISSIONS";
61     private static String MANAGE_APP_OPS_MODES = "android.permission.MANAGE_APP_OPS_MODES";
62 
63     private static final int TESTED_FLAGS = FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_USER_FIXED
64             | FLAG_PERMISSION_REVOKE_ON_UPGRADE | FLAG_PERMISSION_REVIEW_REQUIRED
65             | FLAG_PERMISSION_REVOKE_WHEN_REQUESTED;
66 
67     private static final Context sContext =
68             InstrumentationRegistry.getInstrumentation().getTargetContext();
69     private static final UiAutomation sUiAutomation =
70             InstrumentationRegistry.getInstrumentation().getUiAutomation();
71 
PermissionUtils()72     private PermissionUtils() {
73         // this class should never be instantiated
74     }
75 
76     /**
77      * Get the state of an app-op.
78      *
79      * @param packageName The package the app-op belongs to
80      * @param permission The permission the app-op belongs to
81      *
82      * @return The mode the op is on
83      */
getAppOp(@onNull String packageName, @NonNull String permission)84     public static int getAppOp(@NonNull String packageName, @NonNull String permission)
85             throws Exception {
86         return sContext.getSystemService(AppOpsManager.class).unsafeCheckOpRaw(
87                         permissionToOp(permission),
88                         sContext.getPackageManager().getPackageUid(packageName, 0), packageName);
89     }
90 
91     /**
92      * Install an APK.
93      *
94      * @param apkFile The apk to install
95      */
install(@onNull String apkFile)96     public static void install(@NonNull String apkFile) {
97         final int sdkVersion = Build.VERSION.SDK_INT
98                 + (Build.VERSION.RELEASE_OR_CODENAME.equals("REL") ? 0 : 1);
99         boolean forceQueryable = sdkVersion > Build.VERSION_CODES.Q;
100         runShellCommand("pm install -r --force-sdk "
101                 + (forceQueryable ? "--force-queryable " : "")
102                 + apkFile);
103     }
104 
105     /**
106      * Uninstall a package.
107      *
108      * @param packageName Name of package to be uninstalled
109      */
uninstallApp(@onNull String packageName)110     public static void uninstallApp(@NonNull String packageName) {
111         runShellCommand("pm uninstall " + packageName);
112     }
113 
114     /**
115      * Set a new state for an app-op (using the permission-name)
116      *
117      * @param packageName The package the app-op belongs to
118      * @param permission The permission the app-op belongs to
119      * @param mode The new mode
120      */
setAppOp(@onNull String packageName, @NonNull String permission, int mode)121     public static void setAppOp(@NonNull String packageName, @NonNull String permission, int mode) {
122         setAppOpByName(packageName, permissionToOp(permission), mode);
123     }
124 
125     /**
126      * Set a new state for an app-op (using the app-op-name)
127      *
128      * @param packageName The package the app-op belongs to
129      * @param op The name of the op
130      * @param mode The new mode
131      */
setAppOpByName(@onNull String packageName, @NonNull String op, int mode)132     public static void setAppOpByName(@NonNull String packageName, @NonNull String op, int mode) {
133         runWithShellPermissionIdentity(
134                 () -> sContext.getSystemService(AppOpsManager.class).setUidMode(op,
135                         sContext.getPackageManager().getPackageUid(packageName, 0), mode),
136                 MANAGE_APP_OPS_MODES);
137     }
138 
139     /**
140      * Checks a permission. Does <u>not</u> check the appOp.
141      *
142      * <p>Users should use {@link #isGranted} instead.
143      *
144      * @param packageName The package that might have the permission granted
145      * @param permission The permission that might be granted
146      *
147      * @return {@code true} iff the permission is granted
148      */
isPermissionGranted(@onNull String packageName, @NonNull String permission)149     public static boolean isPermissionGranted(@NonNull String packageName,
150             @NonNull String permission) throws Exception {
151         return sContext.checkPermission(permission, Process.myPid(),
152                 sContext.getPackageManager().getPackageUid(packageName, 0))
153                 == PERMISSION_GRANTED;
154     }
155 
156     /**
157      * Checks if a permission is granted for a package.
158      *
159      * <p>This correctly handles pre-M apps by checking the app-ops instead.
160      * <p>This also correctly handles the location background permission, but does not handle any
161      * other background permission
162      *
163      * @param packageName The package that might have the permission granted
164      * @param permission The permission that might be granted
165      *
166      * @return {@code true} iff the permission is granted
167      */
isGranted(@onNull String packageName, @NonNull String permission)168     public static boolean isGranted(@NonNull String packageName, @NonNull String permission)
169             throws Exception {
170         if (!isPermissionGranted(packageName, permission)) {
171             return false;
172         }
173 
174         if (permission.equals(ACCESS_BACKGROUND_LOCATION)) {
175             // The app-op for background location is encoded into the mode of the foreground
176             // location
177             return getAppOp(packageName, ACCESS_COARSE_LOCATION) == MODE_ALLOWED;
178         } else {
179             int mode = getAppOp(packageName, permission);
180             return mode == MODE_ALLOWED || mode == MODE_FOREGROUND;
181         }
182     }
183 
184     /**
185      * Grant a permission to an app.
186      *
187      * <p>This correctly handles pre-M apps by setting the app-ops.
188      * <p>This also correctly handles the location background permission, but does not handle any
189      * other background permission
190      *
191      * @param packageName The app that should have the permission granted
192      * @param permission The permission to grant
193      */
grantPermission(@onNull String packageName, @NonNull String permission)194     public static void grantPermission(@NonNull String packageName, @NonNull String permission)
195             throws Exception {
196         sUiAutomation.grantRuntimePermission(packageName, permission);
197 
198         if (permission.equals(ACCESS_BACKGROUND_LOCATION)) {
199             // The app-op for background location is encoded into the mode of the foreground
200             // location
201             if (isPermissionGranted(packageName, ACCESS_COARSE_LOCATION)) {
202                 setAppOp(packageName, ACCESS_COARSE_LOCATION, MODE_ALLOWED);
203             } else {
204                 setAppOp(packageName, ACCESS_COARSE_LOCATION, MODE_FOREGROUND);
205             }
206         } else if (permission.equals(ACCESS_COARSE_LOCATION)) {
207             // The app-op for location depends on the state of the bg location
208             if (isPermissionGranted(packageName, ACCESS_BACKGROUND_LOCATION)) {
209                 setAppOp(packageName, ACCESS_COARSE_LOCATION, MODE_ALLOWED);
210             } else {
211                 setAppOp(packageName, ACCESS_COARSE_LOCATION, MODE_FOREGROUND);
212             }
213         } else if (permission.equals(PACKAGE_USAGE_STATS)) {
214             setAppOpByName(packageName, OPSTR_GET_USAGE_STATS, MODE_ALLOWED);
215         } else if (permissionToOp(permission) != null) {
216             setAppOp(packageName, permission, MODE_ALLOWED);
217         }
218     }
219 
220     /**
221      * Revoke a permission from an app.
222      *
223      * <p>This correctly handles pre-M apps by setting the app-ops.
224      * <p>This also correctly handles the location background permission, but does not handle any
225      * other background permission
226      *
227      * @param packageName The app that should have the permission revoked
228      * @param permission The permission to revoke
229      */
revokePermission(@onNull String packageName, @NonNull String permission)230     public static void revokePermission(@NonNull String packageName, @NonNull String permission)
231             throws Exception {
232         sUiAutomation.revokeRuntimePermission(packageName, permission);
233 
234         if (permission.equals(ACCESS_BACKGROUND_LOCATION)) {
235             // The app-op for background location is encoded into the mode of the foreground
236             // location
237             if (isGranted(packageName, ACCESS_COARSE_LOCATION)) {
238                 setAppOp(packageName, ACCESS_COARSE_LOCATION, MODE_FOREGROUND);
239             }
240         } else if (permission.equals(PACKAGE_USAGE_STATS)) {
241             setAppOpByName(packageName, OPSTR_GET_USAGE_STATS, MODE_IGNORED);
242         } else if (permissionToOp(permission) != null) {
243             setAppOp(packageName, permission, MODE_IGNORED);
244         }
245     }
246 
247     /**
248      * Clear permission state (not app-op state) of package.
249      *
250      * @param packageName Package to clear
251      */
clearAppState(@onNull String packageName)252     public static void clearAppState(@NonNull String packageName) {
253         runShellCommand("pm clear --user current " + packageName);
254     }
255 
256     /**
257      * Get the flags of a permission.
258      *
259      * @param packageName Package the permission belongs to
260      * @param permission Name of the permission
261      *
262      * @return Permission flags
263      */
getPermissionFlags(@onNull String packageName, @NonNull String permission)264     public static int getPermissionFlags(@NonNull String packageName, @NonNull String permission) {
265         try {
266             return callWithShellPermissionIdentity(
267                     () -> sContext.getPackageManager().getPermissionFlags(permission, packageName,
268                             UserHandle.getUserHandleForUid(Process.myUid())) & TESTED_FLAGS,
269                     GRANT_RUNTIME_PERMISSIONS);
270         } catch (Exception e) {
271             throw new IllegalStateException(e);
272         }
273     }
274 
275     /**
276      * Set the flags of a permission.
277      *
278      * @param packageName Package the permission belongs to
279      * @param permission Name of the permission
280      * @param mask Mask of permissions to set
281      * @param flags Permissions to set
282      */
setPermissionFlags(@onNull String packageName, @NonNull String permission, int mask, int flags)283     public static void setPermissionFlags(@NonNull String packageName, @NonNull String permission,
284             int mask, int flags) {
285         runWithShellPermissionIdentity(
286                 () -> sContext.getPackageManager().updatePermissionFlags(permission, packageName,
287                         mask, flags, UserHandle.getUserHandleForUid(Process.myUid())),
288                 GRANT_RUNTIME_PERMISSIONS);
289     }
290 
291     /**
292      * Get all permissions an app requests. This includes the split permissions.
293      *
294      * @param packageName The package that requests the permissions.
295      *
296      * @return The permissions requested by the app
297      */
getPermissions(@onNull String packageName)298     public static @NonNull List<String> getPermissions(@NonNull String packageName)
299             throws Exception {
300         PackageInfo appInfo = sContext.getPackageManager().getPackageInfo(packageName,
301                 GET_PERMISSIONS);
302 
303         return Arrays.asList(appInfo.requestedPermissions);
304     }
305 
306     /**
307      * Get all runtime permissions that an app requests. This includes the split permissions.
308      *
309      * @param packageName The package that requests the permissions.
310      *
311      * @return The runtime permissions requested by the app
312      */
getRuntimePermissions(@onNull String packageName)313     public static @NonNull List<String> getRuntimePermissions(@NonNull String packageName)
314             throws Exception {
315         ArrayList<String> runtimePermissions = new ArrayList<>();
316 
317         for (String perm : getPermissions(packageName)) {
318             PermissionInfo info = sContext.getPackageManager().getPermissionInfo(perm, 0);
319             if ((info.getProtection() & PROTECTION_DANGEROUS) != 0) {
320                 runtimePermissions.add(perm);
321             }
322         }
323 
324         return runtimePermissions;
325     }
326 
327 }
328