• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.ADJUST_RUNTIME_PERMISSIONS_POLICY;
22 import static android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS;
23 import static android.Manifest.permission.MANAGE_APP_OPS_MODES;
24 import static android.Manifest.permission.PACKAGE_USAGE_STATS;
25 import static android.app.AppOpsManager.MODE_ALLOWED;
26 import static android.app.AppOpsManager.MODE_FOREGROUND;
27 import static android.app.AppOpsManager.MODE_IGNORED;
28 import static android.app.AppOpsManager.OPSTR_GET_USAGE_STATS;
29 import static android.app.AppOpsManager.permissionToOp;
30 import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
31 import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
32 import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED;
33 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
34 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
35 import static android.content.pm.PackageManager.GET_PERMISSIONS;
36 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
37 import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
38 import static android.permission.cts.TestUtils.awaitJobUntilRequestedState;
39 
40 import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
41 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
42 import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
43 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
44 import static com.android.compatibility.common.util.SystemUtil.waitForBroadcastDispatch;
45 
46 import android.app.AppOpsManager;
47 import android.app.UiAutomation;
48 import android.content.Context;
49 import android.content.Intent;
50 import android.content.pm.PackageInfo;
51 import android.content.pm.PermissionInfo;
52 import android.content.pm.ResolveInfo;
53 import android.os.Build;
54 import android.os.Process;
55 import android.os.UserHandle;
56 import android.util.Log;
57 
58 import androidx.annotation.NonNull;
59 import androidx.test.platform.app.InstrumentationRegistry;
60 
61 import com.android.modules.utils.build.SdkLevel;
62 
63 import java.util.ArrayList;
64 import java.util.Arrays;
65 import java.util.List;
66 
67 /**
68  * Common utils for permission tests
69  */
70 public class PermissionUtils {
71     private static final int TESTED_FLAGS = FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_USER_FIXED
72             | FLAG_PERMISSION_REVOKE_ON_UPGRADE | FLAG_PERMISSION_REVIEW_REQUIRED
73             | FLAG_PERMISSION_REVOKE_WHEN_REQUESTED;
74 
75     private static final String LOG_TAG = PermissionUtils.class.getSimpleName();
76     private static final Context sContext =
77             InstrumentationRegistry.getInstrumentation().getTargetContext();
78     private static final UiAutomation sUiAutomation =
79             InstrumentationRegistry.getInstrumentation().getUiAutomation();
80 
PermissionUtils()81     private PermissionUtils() {
82         // this class should never be instantiated
83     }
84 
85     /**
86      * Get the state of an app-op.
87      *
88      * @param packageName The package the app-op belongs to
89      * @param permission The permission the app-op belongs to
90      *
91      * @return The mode the op is on
92      */
getAppOp(@onNull String packageName, @NonNull String permission)93     public static int getAppOp(@NonNull String packageName, @NonNull String permission)
94             throws Exception {
95         return sContext.getSystemService(AppOpsManager.class).unsafeCheckOpRaw(
96                         permissionToOp(permission),
97                         sContext.getPackageManager().getPackageUid(packageName, 0), packageName);
98     }
99 
100     /**
101      * Install an APK.
102      *
103      * @param apkFile The apk to install
104      */
install(@onNull String apkFile)105     public static void install(@NonNull String apkFile) {
106         install(apkFile, false);
107     }
108 
109     /**
110      * Install an APK.
111      *
112      * @param apkFile The apk to install
113      * @param grantPermissions whether to grant requested runtime permissions
114      */
install(@onNull String apkFile, boolean grantPermissions)115     public static void install(@NonNull String apkFile, boolean grantPermissions) {
116         final int sdkVersion = Build.VERSION.SDK_INT
117                 + (Build.VERSION.RELEASE_OR_CODENAME.equals("REL") ? 0 : 1);
118         boolean forceQueryable = sdkVersion > Build.VERSION_CODES.Q;
119         runShellCommandOrThrow("pm install -r --force-sdk "
120                 + (SdkLevel.isAtLeastU() ? "--bypass-low-target-sdk-block " : "")
121                 + (forceQueryable ? "--force-queryable " : "")
122                 + (grantPermissions ? "-g " : "")
123                 + apkFile);
124     }
125 
126     /**
127      * Uninstall a package.
128      *
129      * @param packageName Name of package to be uninstalled
130      */
uninstallApp(@onNull String packageName)131     public static void uninstallApp(@NonNull String packageName) {
132         runShellCommand("pm uninstall " + packageName);
133     }
134 
135     /**
136      * Set a new state for an app-op (using the permission-name)
137      *
138      * @param packageName The package the app-op belongs to
139      * @param permission The permission the app-op belongs to
140      * @param mode The new mode
141      */
setAppOp(@onNull String packageName, @NonNull String permission, int mode)142     public static void setAppOp(@NonNull String packageName, @NonNull String permission, int mode) {
143         setAppOpByName(packageName, permissionToOp(permission), mode);
144     }
145 
146     /**
147      * Set a new state for an app-op (using the app-op-name)
148      *
149      * @param packageName The package the app-op belongs to
150      * @param op The name of the op
151      * @param mode The new mode
152      */
setAppOpByName(@onNull String packageName, @NonNull String op, int mode)153     public static void setAppOpByName(@NonNull String packageName, @NonNull String op, int mode) {
154         runWithShellPermissionIdentity(
155                 () -> sContext.getSystemService(AppOpsManager.class).setUidMode(op,
156                         sContext.getPackageManager().getPackageUid(packageName, 0), mode),
157                 MANAGE_APP_OPS_MODES);
158     }
159 
160     /**
161      * Checks a permission. Does <u>not</u> check the appOp.
162      *
163      * <p>Users should use {@link #isGranted} instead.
164      *
165      * @param packageName The package that might have the permission granted
166      * @param permission The permission that might be granted
167      *
168      * @return {@code true} iff the permission is granted
169      */
isPermissionGranted(@onNull String packageName, @NonNull String permission)170     public static boolean isPermissionGranted(@NonNull String packageName,
171             @NonNull String permission) throws Exception {
172         return sContext.checkPermission(permission, Process.myPid(),
173                 sContext.getPackageManager().getPackageUid(packageName, 0))
174                 == PERMISSION_GRANTED;
175     }
176 
177     /**
178      * Checks if a permission is granted for a package.
179      *
180      * <p>This correctly handles pre-M apps by checking the app-ops instead.
181      * <p>This also correctly handles the location background permission, but does not handle any
182      * other background permission
183      *
184      * @param packageName The package that might have the permission granted
185      * @param permission The permission that might be granted
186      *
187      * @return {@code true} iff the permission is granted
188      */
isGranted(@onNull String packageName, @NonNull String permission)189     public static boolean isGranted(@NonNull String packageName, @NonNull String permission)
190             throws Exception {
191         if (!isPermissionGranted(packageName, permission)) {
192             return false;
193         }
194 
195         if (permission.equals(ACCESS_BACKGROUND_LOCATION)) {
196             // The app-op for background location is encoded into the mode of the foreground
197             // location
198             return getAppOp(packageName, ACCESS_COARSE_LOCATION) == MODE_ALLOWED;
199         } else {
200             int mode = getAppOp(packageName, permission);
201             return mode == MODE_ALLOWED || mode == MODE_FOREGROUND;
202         }
203     }
204 
205     /**
206      * Grant a permission to an app.
207      *
208      * <p>This correctly handles pre-M apps by setting the app-ops.
209      * <p>This also correctly handles the location background permission, but does not handle any
210      * other background permission
211      *
212      * @param packageName The app that should have the permission granted
213      * @param permission The permission to grant
214      */
grantPermission(@onNull String packageName, @NonNull String permission)215     public static void grantPermission(@NonNull String packageName, @NonNull String permission)
216             throws Exception {
217         sUiAutomation.grantRuntimePermission(packageName, permission);
218 
219         if (permission.equals(ACCESS_BACKGROUND_LOCATION)) {
220             // The app-op for background location is encoded into the mode of the foreground
221             // location
222             if (isPermissionGranted(packageName, ACCESS_COARSE_LOCATION)) {
223                 setAppOp(packageName, ACCESS_COARSE_LOCATION, MODE_ALLOWED);
224             } else {
225                 setAppOp(packageName, ACCESS_COARSE_LOCATION, MODE_FOREGROUND);
226             }
227         } else if (permission.equals(ACCESS_COARSE_LOCATION)) {
228             // The app-op for location depends on the state of the bg location
229             if (isPermissionGranted(packageName, ACCESS_BACKGROUND_LOCATION)) {
230                 setAppOp(packageName, ACCESS_COARSE_LOCATION, MODE_ALLOWED);
231             } else {
232                 setAppOp(packageName, ACCESS_COARSE_LOCATION, MODE_FOREGROUND);
233             }
234         } else if (permission.equals(PACKAGE_USAGE_STATS)) {
235             setAppOpByName(packageName, OPSTR_GET_USAGE_STATS, MODE_ALLOWED);
236         } else if (permissionToOp(permission) != null) {
237             setAppOp(packageName, permission, MODE_ALLOWED);
238         }
239     }
240 
241     /**
242      * Revoke a permission from an app.
243      *
244      * <p>This correctly handles pre-M apps by setting the app-ops.
245      * <p>This also correctly handles the location background permission, but does not handle any
246      * other background permission
247      *
248      * @param packageName The app that should have the permission revoked
249      * @param permission The permission to revoke
250      */
revokePermission(@onNull String packageName, @NonNull String permission)251     public static void revokePermission(@NonNull String packageName, @NonNull String permission)
252             throws Exception {
253         sUiAutomation.revokeRuntimePermission(packageName, permission);
254 
255         if (permission.equals(ACCESS_BACKGROUND_LOCATION)) {
256             // The app-op for background location is encoded into the mode of the foreground
257             // location
258             if (isGranted(packageName, ACCESS_COARSE_LOCATION)) {
259                 setAppOp(packageName, ACCESS_COARSE_LOCATION, MODE_FOREGROUND);
260             }
261         } else if (permission.equals(PACKAGE_USAGE_STATS)) {
262             setAppOpByName(packageName, OPSTR_GET_USAGE_STATS, MODE_IGNORED);
263         } else if (permissionToOp(permission) != null) {
264             setAppOp(packageName, permission, MODE_IGNORED);
265         }
266     }
267 
268     /**
269      * Clear permission state (not app-op state) of package.
270      *
271      * @param packageName Package to clear
272      */
clearAppState(@onNull String packageName)273     public static void clearAppState(@NonNull String packageName) {
274         runShellCommand("pm clear --user " + sContext.getUserId() + " " + packageName);
275     }
276 
277     /**
278      * Get all the flags of a permission.
279      *
280      * @param packageName Package the permission belongs to
281      * @param permission Name of the permission
282      *
283      * @return Permission flags
284      */
getAllPermissionFlags(@onNull String packageName, @NonNull String permission)285     public static int getAllPermissionFlags(@NonNull String packageName,
286             @NonNull String permission) {
287         try {
288             return callWithShellPermissionIdentity(
289                     () -> sContext.getPackageManager().getPermissionFlags(permission, packageName,
290                             UserHandle.getUserHandleForUid(Process.myUid())),
291                     GRANT_RUNTIME_PERMISSIONS);
292         } catch (Exception e) {
293             throw new IllegalStateException(e);
294         }
295     }
296 
297     /**
298      * Get the flags of a permission.
299      *
300      * @param packageName Package the permission belongs to
301      * @param permission Name of the permission
302      *
303      * @return Permission flags
304      */
getPermissionFlags(@onNull String packageName, @NonNull String permission)305     public static int getPermissionFlags(@NonNull String packageName, @NonNull String permission) {
306         return getAllPermissionFlags(packageName, permission) & TESTED_FLAGS;
307     }
308 
309     /**
310      * Set the flags of a permission.
311      *
312      * @param packageName Package the permission belongs to
313      * @param permission Name of the permission
314      * @param mask Mask of permissions to set
315      * @param flags Permissions to set
316      */
setPermissionFlags(@onNull String packageName, @NonNull String permission, int mask, int flags)317     public static void setPermissionFlags(@NonNull String packageName, @NonNull String permission,
318             int mask, int flags) {
319         runWithShellPermissionIdentity(
320                 () -> sContext.getPackageManager().updatePermissionFlags(permission, packageName,
321                         mask, flags, UserHandle.getUserHandleForUid(Process.myUid())),
322                 GRANT_RUNTIME_PERMISSIONS, ADJUST_RUNTIME_PERMISSIONS_POLICY);
323     }
324 
325     /**
326      * Get all permissions an app requests. This includes the split permissions.
327      *
328      * @param packageName The package that requests the permissions.
329      *
330      * @return The permissions requested by the app
331      */
getPermissions(@onNull String packageName)332     public static @NonNull List<String> getPermissions(@NonNull String packageName)
333             throws Exception {
334         PackageInfo appInfo = sContext.getPackageManager().getPackageInfo(packageName,
335                 GET_PERMISSIONS);
336 
337         return Arrays.asList(appInfo.requestedPermissions);
338     }
339 
340     /**
341      * Get all runtime permissions that an app requests. This includes the split permissions.
342      *
343      * @param packageName The package that requests the permissions.
344      *
345      * @return The runtime permissions requested by the app
346      */
getRuntimePermissions(@onNull String packageName)347     public static @NonNull List<String> getRuntimePermissions(@NonNull String packageName)
348             throws Exception {
349         ArrayList<String> runtimePermissions = new ArrayList<>();
350 
351         for (String perm : getPermissions(packageName)) {
352             PermissionInfo info = sContext.getPackageManager().getPermissionInfo(perm, 0);
353             if ((info.getProtection() & PROTECTION_DANGEROUS) != 0) {
354                 runtimePermissions.add(perm);
355             }
356         }
357 
358         return runtimePermissions;
359     }
360 
361     /**
362      * Reset permission controller state & re-schedule the job.
363      */
resetPermissionControllerJob(@onNull UiAutomation automation, @NonNull String packageName, int jobId, long timeout, @NonNull String intentAction, @NonNull String onBootReceiver)364     public static void resetPermissionControllerJob(@NonNull UiAutomation automation,
365             @NonNull String packageName, int jobId, long timeout, @NonNull String intentAction,
366             @NonNull String onBootReceiver) throws Exception {
367         clearAppState(packageName);
368         awaitJobUntilRequestedState(packageName, jobId, timeout, automation, "unknown");
369         scheduleJob(automation, packageName, jobId, timeout, intentAction, onBootReceiver);
370 
371         runShellCommand("cmd jobscheduler reset-execution-quota -u "
372                 + Process.myUserHandle().getIdentifier() + " " + packageName);
373         runShellCommand("cmd jobscheduler reset-schedule-quota");
374     }
375 
376     /**
377      * schedules a job for the privacy signal in Permission Controller
378      */
scheduleJob(@onNull UiAutomation automation, @NonNull String packageName, int jobId, long timeout, @NonNull String intentAction, @NonNull String broadcastReceiver)379     public static void scheduleJob(@NonNull UiAutomation automation,
380             @NonNull String packageName, int jobId, long timeout, @NonNull String intentAction,
381             @NonNull String broadcastReceiver) throws Exception {
382         long startTime = System.currentTimeMillis();
383         String jobStatus = "";
384         simulateReboot(packageName, intentAction, broadcastReceiver);
385 
386         while ((System.currentTimeMillis() - startTime) < timeout
387                 && !isJobScheduled(jobStatus)) {
388             String cmd =
389                     "cmd jobscheduler get-job-state -u " + Process.myUserHandle().getIdentifier()
390                             + " " + packageName + " " + jobId;
391             jobStatus = runShellCommand(automation, cmd).trim();
392             Log.v(LOG_TAG, "Job: " + jobId + ", job status " + jobStatus);
393             try {
394                 Thread.sleep(500);
395             } catch (InterruptedException e) {
396                 // ignore interrupt
397             }
398         }
399         if (!isJobScheduled(jobStatus)) {
400             throw new IllegalStateException("The job didn't get scheduled in time.");
401         }
402     }
403 
isJobScheduled(String jobStatus)404     private static boolean isJobScheduled(String jobStatus) throws Exception {
405         return jobStatus.contains("waiting") || jobStatus.contains("pending")
406             || jobStatus.contains("ready") || jobStatus.contains("active");
407     }
408 
simulateReboot(@onNull String packageName, @NonNull String intentAction, @NonNull String broadcastReceiver)409     private static void simulateReboot(@NonNull String packageName, @NonNull String intentAction,
410             @NonNull String broadcastReceiver) {
411         Intent jobSetupReceiverIntent = new Intent(intentAction);
412         jobSetupReceiverIntent.setPackage(packageName);
413         jobSetupReceiverIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
414 
415         // Query for the setup broadcast receiver
416         List<ResolveInfo> resolveInfos =
417                 sContext.getPackageManager().queryBroadcastReceivers(jobSetupReceiverIntent, 0);
418 
419         if (resolveInfos.size() > 0) {
420             sContext.sendBroadcast(jobSetupReceiverIntent);
421         } else {
422             Intent intent = new Intent();
423             intent.setClassName(packageName, broadcastReceiver);
424             intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
425             intent.setPackage(packageName);
426             sContext.sendBroadcast(intent);
427         }
428         waitForBroadcastDispatch(intentAction);
429     }
430 }
431