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