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