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