• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 com.android.cts.devicepolicy;
18 
19 import static android.Manifest.permission.BODY_SENSORS;
20 import static android.Manifest.permission.CAMERA;
21 import static android.Manifest.permission.RECORD_AUDIO;
22 import static android.content.pm.PackageManager.PERMISSION_DENIED;
23 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
24 
25 import static com.google.common.truth.Truth.assertWithMessage;
26 
27 import static org.junit.Assert.fail;
28 
29 import android.Manifest;
30 import android.app.AppOpsManager;
31 import android.app.admin.DevicePolicyManager;
32 import android.content.ComponentName;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.content.pm.PackageInfo;
36 import android.content.pm.PackageManager;
37 import android.os.Process;
38 import android.support.test.uiautomator.By;
39 import android.support.test.uiautomator.BySelector;
40 import android.support.test.uiautomator.UiDevice;
41 import android.support.test.uiautomator.UiObject2;
42 import android.support.test.uiautomator.Until;
43 import android.util.Log;
44 
45 import androidx.test.core.app.ApplicationProvider;
46 import androidx.test.platform.app.InstrumentationRegistry;
47 
48 import java.lang.reflect.Field;
49 import java.lang.reflect.Modifier;
50 import java.util.ArrayList;
51 import java.util.Arrays;
52 import java.util.HashSet;
53 import java.util.List;
54 import java.util.Set;
55 import java.util.regex.Pattern;
56 
57 public class PermissionUtils {
58     private static final String LOG_TAG = PermissionUtils.class.getSimpleName();
59     private static final Set<String> LOCATION_PERMISSIONS = new HashSet<String>();
60 
61     private static final Context sContext = ApplicationProvider.getApplicationContext();
62 
63     static {
64         LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_FINE_LOCATION);
65         LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION);
66         LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_COARSE_LOCATION);
67     }
68 
69     private static final String ACTION_CHECK_HAS_PERMISSION
70             = "com.android.cts.permission.action.CHECK_HAS_PERMISSION";
71     private static final String ACTION_REQUEST_PERMISSION
72             = "com.android.cts.permission.action.REQUEST_PERMISSION";
73     private static final String EXTRA_PERMISSION = "com.android.cts.permission.extra.PERMISSION";
74 
launchActivityAndCheckPermission(PermissionBroadcastReceiver receiver, String permission, int expected, String packageName, String activityName)75     public static void launchActivityAndCheckPermission(PermissionBroadcastReceiver receiver,
76             String permission, int expected, String packageName, String activityName)
77             throws Exception {
78         launchActivityWithAction(permission, ACTION_CHECK_HAS_PERMISSION,
79                 packageName, activityName);
80         assertBroadcastReceived(receiver, expected);
81     }
82 
assertBroadcastReceived(PermissionBroadcastReceiver receiver, int expected)83     private static void assertBroadcastReceived(PermissionBroadcastReceiver receiver,
84             int expected) throws Exception {
85         int actual = receiver.waitForBroadcast();
86         assertWithMessage("value returned by %s (%s=%s, %s=%s)", receiver,
87                 expected, permissionToString(expected),
88                 actual, permissionToString(actual))
89                         .that(actual).isEqualTo(expected);
90     }
91 
launchActivityAndRequestPermission(PermissionBroadcastReceiver receiver, String permission, int expected, String packageName, String activityName)92     public static void launchActivityAndRequestPermission(PermissionBroadcastReceiver receiver,
93             String permission, int expected, String packageName, String activityName)
94             throws Exception {
95         launchActivityWithAction(permission, ACTION_REQUEST_PERMISSION,
96                 packageName, activityName);
97         assertBroadcastReceived(receiver, expected);
98     }
99 
launchActivityAndRequestPermission(PermissionBroadcastReceiver receiver, UiDevice device, String permission, int expected, String packageName, String activityName)100     public static void launchActivityAndRequestPermission(PermissionBroadcastReceiver
101             receiver, UiDevice device, String permission, int expected,
102             String packageName, String activityName) throws Exception {
103         final List<String> resNames = new ArrayList<>();
104         switch(expected) {
105             case PERMISSION_DENIED:
106                 resNames.add("permission_deny_button");
107                 resNames.add("permission_deny_and_dont_ask_again_button");
108                 break;
109             case PERMISSION_GRANTED:
110                 resNames.add("permission_allow_button");
111                 // For some permissions, different buttons may be available.
112                 if (LOCATION_PERMISSIONS.contains(permission)
113                         || RECORD_AUDIO.equals(permission)
114                         || CAMERA.equals(permission)
115                         || BODY_SENSORS.equals(permission)) {
116                     resNames.add("permission_allow_foreground_only_button");
117                     resNames.add("permission_allow_one_time_button");
118                 }
119                 break;
120             default:
121                 throw new IllegalArgumentException("Invalid expected permission");
122         }
123         launchActivityWithAction(permission, ACTION_REQUEST_PERMISSION,
124                 packageName, activityName);
125         pressPermissionPromptButton(device, expected, resNames.toArray(new String[0]));
126         assertBroadcastReceived(receiver, expected);
127     }
128 
launchActivityWithAction(String permission, String action, String packageName, String activityName)129     private static void launchActivityWithAction(String permission, String action,
130             String packageName, String activityName) {
131         Intent launchIntent = new Intent();
132         launchIntent.setComponent(new ComponentName(packageName, activityName));
133         launchIntent.putExtra(EXTRA_PERMISSION, permission);
134         launchIntent.setAction(action);
135         launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
136         Log.d(LOG_TAG, "Launching activity (with intent " + launchIntent + ") for permission "
137                 + permission + " on uid " + Process.myUid());
138         getContext().startActivity(launchIntent);
139     }
140 
checkPermission(String permission, int expected, String packageName)141     public static void checkPermission(String permission, int expected, String packageName) {
142         checkPermission(getContext(), permission, expected, packageName);
143     }
144 
checkPermission(Context context, String permission, int expected, String packageName)145     public static void checkPermission(Context context, String permission, int expected,
146             String packageName) {
147         PackageManager pm = context.getPackageManager();
148         Log.d(LOG_TAG, "checkPermission(" + permission + ", " + expected + ", " + packageName
149                 + "): " + "using " + pm + " on user " + context.getUser());
150         assertPermission(permission, packageName, pm.checkPermission(permission, packageName),
151                 expected);
152     }
153 
assertPermission(String permission, String packageName, int actual, int expected)154     private static void assertPermission(String permission, String packageName, int actual,
155             int expected) {
156         assertWithMessage("Wrong status for permission %s on package %s (where %s=%s and %s=%s)",
157                 permission, packageName,
158                 expected, permissionToString(expected), actual, permissionToString(actual))
159                         .that(actual).isEqualTo(expected);
160     }
161 
162     /**
163      * Correctly checks a runtime permission. This also works for pre-{@code M} apps.
164      */
checkPermissionAndAppOps(String permission, int expected, String packageName)165     public static void checkPermissionAndAppOps(String permission, int expected, String packageName)
166             throws Exception {
167         checkPermissionAndAppOps(getContext(), permission, expected, packageName);
168     }
169 
170     /**
171      * Correctly checks a runtime permission. This also works for pre-{@code M} apps.
172      */
checkPermissionAndAppOps(Context context, String permission, int expected, String packageName)173     public static void checkPermissionAndAppOps(Context context, String permission, int expected,
174             String packageName) throws Exception {
175         assertPermission(permission, packageName,
176                 checkPermissionAndAppOps(context, permission, packageName), expected);
177     }
178 
checkPermissionAndAppOps(Context context, String permission, String packageName)179     private static int checkPermissionAndAppOps(Context context, String permission,
180             String packageName) throws Exception {
181         Log.d(LOG_TAG, "checkPermissionAndAppOps(): user=" + context.getUser()
182                 + ", permission=" + permission + ", packageName=" + packageName);
183         PackageInfo packageInfo = context.getPackageManager().getPackageInfo(packageName, 0);
184         if (context.checkPermission(permission, -1, packageInfo.applicationInfo.uid)
185                 == PERMISSION_DENIED) {
186             return PERMISSION_DENIED;
187         }
188 
189         AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
190         if (appOpsManager != null && appOpsManager.noteProxyOpNoThrow(
191                 AppOpsManager.permissionToOp(permission), packageName,
192                 packageInfo.applicationInfo.uid, null, null)
193                 != AppOpsManager.MODE_ALLOWED) {
194             return PERMISSION_DENIED;
195         }
196 
197         return PERMISSION_GRANTED;
198     }
199 
getContext()200     public static Context getContext() {
201         return InstrumentationRegistry.getInstrumentation().getContext();
202     }
203 
pressPermissionPromptButton(UiDevice device, int expectedAction, String[] resNames)204     private static void pressPermissionPromptButton(UiDevice device, int expectedAction,
205             String[] resNames) {
206         UiObject2 button = findPermissionPromptButton(device, expectedAction, resNames);
207         Log.d(LOG_TAG, "Clicking on '" + button.getText() + "'");
208         button.click();
209     }
210 
findPermissionPromptButton(UiDevice device, int expectedAction, String[] resNames)211     private static UiObject2 findPermissionPromptButton(UiDevice device, int expectedAction,
212             String[] resNames) {
213         if ((resNames == null) || (resNames.length == 0)) {
214             throw new IllegalArgumentException("resNames must not be null or empty");
215         }
216         String action;
217         switch (expectedAction) {
218             case PERMISSION_DENIED:
219                 action = "PERMISSION_DENIED";
220                 break;
221             case PERMISSION_GRANTED:
222                 action = "PERMISSION_GRANTED";
223                 break;
224             default:
225                 throw new IllegalArgumentException("Invalid expected action: "
226                         + expectedAction);
227         }
228 
229         // The dialog was moved from the packageinstaller to the permissioncontroller.
230         // Search in multiple packages so the test is not affixed to a particular package.
231         String[] possiblePackages = new String[]{
232                 "com.android.permissioncontroller.permission.ui",
233                 "com.android.packageinstaller",
234                 "com.android.permissioncontroller"};
235 
236         Log.v(LOG_TAG, "findPermissionPromptButton(): pkgs= " + Arrays.toString(possiblePackages)
237                 + ", action=" + action + ", resIds=" + Arrays.toString(resNames));
238         for (String resName : resNames) {
239             for (String possiblePkg : possiblePackages) {
240                 BySelector selector = By
241                         .clazz(android.widget.Button.class.getName())
242                         .res(possiblePkg, resName);
243                 Log.v(LOG_TAG, "trying " + selector);
244                 device.wait(Until.hasObject(selector), 5000);
245                 UiObject2 button = device.findObject(selector);
246                 Log.d(LOG_TAG, String.format("Resource %s in Package %s found? %b", resName,
247                         possiblePkg, button != null));
248                 if (button != null) {
249                     return button;
250                 }
251             }
252         }
253 
254         if (!sContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
255             fail("Did not find button for action " + action + " on packages "
256                     + Arrays.toString(possiblePackages));
257         }
258         return findPermissionPromptButtonAutomotive(device, expectedAction);
259     }
260 
findPermissionPromptButtonAutomotive(UiDevice device, int expectedAction)261     private static UiObject2 findPermissionPromptButtonAutomotive(UiDevice device,
262             int expectedAction) {
263         // TODO: ideally the UI should use a more specific resource, so it doesn't need to search
264         // for text
265         Pattern resPattern = Pattern.compile(".*car_ui_list_item_title");
266         Pattern textPattern;
267         String action;
268         switch (expectedAction) {
269             case PERMISSION_DENIED:
270                 action = "PERMISSION_DENIED";
271                 textPattern = Pattern.compile("^Don’t allow$");
272                 break;
273             case PERMISSION_GRANTED:
274                 action = "PERMISSION_GRANTED";
275                 textPattern = Pattern.compile("^Allow|While using the app$");
276                 break;
277             default:
278                 throw new IllegalArgumentException("Invalid expected action: " + expectedAction);
279         }
280         Log.i(LOG_TAG, "Button not found on automotive build; searching for " + resPattern
281                 + " res and " + textPattern + " text instead");
282         BySelector selector = By
283                 .clazz(android.widget.TextView.class.getName())
284                  .text(textPattern)
285                 .res(resPattern);
286         Log.v(LOG_TAG, "selector: " + selector);
287         device.wait(Until.hasObject(selector), 5000);
288         UiObject2 button = device.findObject(selector);
289         Log.d(LOG_TAG, "button: " + button + (button == null ? "" : " (" + button.getText() + ")"));
290         assertWithMessage("Found button with res %s and text '%s'", resPattern, textPattern)
291                 .that(button).isNotNull();
292 
293         return button;
294     }
295 
permissionGrantStateToString(int state)296     public static String permissionGrantStateToString(int state) {
297         return constantToString(DevicePolicyManager.class, "PERMISSION_GRANT_STATE_", state);
298     }
299 
permissionPolicyToString(int policy)300     public static String permissionPolicyToString(int policy) {
301         return constantToString(DevicePolicyManager.class, "PERMISSION_POLICY_", policy);
302     }
303 
permissionToString(int permission)304     public static String permissionToString(int permission) {
305         return constantToString(PackageManager.class, "PERMISSION_", permission);
306     }
307 
308     // Copied from DebugUtils
constantToString(Class<?> clazz, String prefix, int value)309     private static String constantToString(Class<?> clazz, String prefix, int value) {
310         for (Field field : clazz.getDeclaredFields()) {
311             final int modifiers = field.getModifiers();
312             try {
313                 if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)
314                         && field.getType().equals(int.class) && field.getName().startsWith(prefix)
315                         && field.getInt(null) == value) {
316                     return constNameWithoutPrefix(prefix, field);
317                 }
318             } catch (IllegalAccessException ignored) {
319             }
320         }
321         return prefix + Integer.toString(value);
322     }
323 
324     // Copied from DebugUtils
constNameWithoutPrefix(String prefix, Field field)325     private static String constNameWithoutPrefix(String prefix, Field field) {
326         return field.getName().substring(prefix.length());
327     }
328 }
329