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