blob: f02e2f3ce57e43a709600c3011ffa0c3d4c9e756 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.cts.devicepolicy;
import static android.Manifest.permission.CAMERA;
import static android.Manifest.permission.RECORD_AUDIO;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static com.google.common.truth.Truth.assertWithMessage;
import android.Manifest;
import android.app.AppOpsManager;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Process;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.BySelector;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject2;
import android.support.test.uiautomator.Until;
import android.util.Log;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.platform.app.InstrumentationRegistry;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
public class PermissionUtils {
private static final String LOG_TAG = PermissionUtils.class.getSimpleName();
private static final Set<String> LOCATION_PERMISSIONS = new HashSet<String>();
private static final Context sContext = ApplicationProvider.getApplicationContext();
static {
LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_FINE_LOCATION);
LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION);
LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_COARSE_LOCATION);
}
private static final String ACTION_CHECK_HAS_PERMISSION
= "com.android.cts.permission.action.CHECK_HAS_PERMISSION";
private static final String ACTION_REQUEST_PERMISSION
= "com.android.cts.permission.action.REQUEST_PERMISSION";
private static final String EXTRA_PERMISSION = "com.android.cts.permission.extra.PERMISSION";
public static void launchActivityAndCheckPermission(PermissionBroadcastReceiver receiver,
String permission, int expected, String packageName, String activityName)
throws Exception {
launchActivityWithAction(permission, ACTION_CHECK_HAS_PERMISSION,
packageName, activityName);
assertBroadcastReceived(receiver, expected);
}
private static void assertBroadcastReceived(PermissionBroadcastReceiver receiver,
int expected) throws Exception {
int actual = receiver.waitForBroadcast();
assertWithMessage("value returned by %s (%s=%s, %s=%s)", receiver,
expected, permissionToString(expected),
actual, permissionToString(actual))
.that(actual).isEqualTo(expected);
}
public static void launchActivityAndRequestPermission(PermissionBroadcastReceiver receiver,
String permission, int expected, String packageName, String activityName)
throws Exception {
launchActivityWithAction(permission, ACTION_REQUEST_PERMISSION,
packageName, activityName);
assertBroadcastReceived(receiver, expected);
}
public static void launchActivityAndRequestPermission(PermissionBroadcastReceiver
receiver, UiDevice device, String permission, int expected,
String packageName, String activityName) throws Exception {
final List<String> resNames = new ArrayList<>();
switch(expected) {
case PERMISSION_DENIED:
resNames.add("permission_deny_button");
resNames.add("permission_deny_and_dont_ask_again_button");
break;
case PERMISSION_GRANTED:
resNames.add("permission_allow_button");
// For some permissions, different buttons may be available.
if (LOCATION_PERMISSIONS.contains(permission)
|| RECORD_AUDIO.equals(permission)
|| CAMERA.equals(permission)) {
resNames.add("permission_allow_foreground_only_button");
resNames.add("permission_allow_one_time_button");
}
break;
default:
throw new IllegalArgumentException("Invalid expected permission");
}
launchActivityWithAction(permission, ACTION_REQUEST_PERMISSION,
packageName, activityName);
pressPermissionPromptButton(device, resNames.toArray(new String[0]));
assertBroadcastReceived(receiver, expected);
}
private static void launchActivityWithAction(String permission, String action,
String packageName, String activityName) {
Intent launchIntent = new Intent();
launchIntent.setComponent(new ComponentName(packageName, activityName));
launchIntent.putExtra(EXTRA_PERMISSION, permission);
launchIntent.setAction(action);
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
Log.d(LOG_TAG, "Launching activity with intent " + launchIntent + " on uid "
+ Process.myUid());
getContext().startActivity(launchIntent);
}
public static void checkPermission(String permission, int expected, String packageName) {
assertPermission(permission, packageName, getContext().getPackageManager()
.checkPermission(permission, packageName), expected);
}
private static void assertPermission(String permission, String packageName, int actual,
int expected) {
assertWithMessage("Wrong status for permission %s on package %s", permission, packageName)
.that(actual).isEqualTo(expected);
}
/**
* Correctly check a runtime permission. This also works for pre-m apps.
*/
public static void checkPermissionAndAppOps(String permission, int expected, String packageName)
throws Exception {
assertPermission(permission, packageName, checkPermissionAndAppOps(permission, packageName),
expected);
}
private static int checkPermissionAndAppOps(String permission, String packageName)
throws Exception {
PackageInfo packageInfo = getContext().getPackageManager().getPackageInfo(packageName, 0);
if (getContext().checkPermission(permission, -1, packageInfo.applicationInfo.uid)
== PERMISSION_DENIED) {
return PERMISSION_DENIED;
}
AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
if (appOpsManager != null && appOpsManager.noteProxyOpNoThrow(
AppOpsManager.permissionToOp(permission), packageName,
packageInfo.applicationInfo.uid, null, null)
!= AppOpsManager.MODE_ALLOWED) {
return PERMISSION_DENIED;
}
return PERMISSION_GRANTED;
}
public static Context getContext() {
return InstrumentationRegistry.getInstrumentation().getContext();
}
private static void pressPermissionPromptButton(UiDevice mDevice, String[] resNames) {
if ((resNames == null) || (resNames.length == 0)) {
throw new IllegalArgumentException("resNames must not be null or empty");
}
// The dialog was moved from the packageinstaller to the permissioncontroller.
// Search in multiple packages so the test is not affixed to a particular package.
String[] possiblePackages = new String[]{
"com.android.permissioncontroller.permission.ui",
"com.android.packageinstaller",
"com.android.permissioncontroller"};
Log.v(LOG_TAG, "pressPermissionPromptButton(): pkgs= " + Arrays.toString(possiblePackages)
+ ", resIds=" + Arrays.toString(resNames));
boolean foundButton = false;
for (String resName : resNames) {
for (String possiblePkg : possiblePackages) {
BySelector selector = By
.clazz(android.widget.Button.class.getName())
.res(possiblePkg, resName);
Log.v(LOG_TAG, "trying " + selector);
mDevice.wait(Until.hasObject(selector), 5000);
UiObject2 button = mDevice.findObject(selector);
Log.d(LOG_TAG, String.format("Resource %s in Package %s found? %b", resName,
possiblePkg, button != null));
if (button != null) {
foundButton = true;
Log.d(LOG_TAG, "Clicking on " + button.getText());
button.click();
break;
}
}
if (foundButton) {
break;
}
}
if (!sContext.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
assertWithMessage("Found button on packages %s", Arrays.toString(possiblePackages))
.that(foundButton).isTrue();
return;
}
// TODO: ideally the UI should use a more specific resource
Pattern resPattern = Pattern.compile(".*car_ui_list_item_title");
Log.i(LOG_TAG, "Button not found on automotive build; searching for " + resPattern
+ " regex instead");
BySelector selector = By
.clazz(android.widget.TextView.class.getName())
.res(resPattern);
Log.v(LOG_TAG, "selector: " + selector);
mDevice.wait(Until.hasObject(selector), 5000);
UiObject2 button = mDevice.findObject(selector);
Log.d(LOG_TAG, "button: " + button);
if (button != null) {
String text = button.getText();
// TODO: ideally value of text should not be hardcoded, but they are defined by
// resources on PermissionController app (grant_dialog_button_allow and
// grant_dialog_button_allow_foreground)
if (text.equals("Allow") || text.equals("While using the app")) {
foundButton = true;
Log.d(LOG_TAG, "Clicking on '" + text + "'");
button.click();
}
}
assertWithMessage("Couldn't find any button with regex %s", resPattern)
.that(foundButton).isTrue();
}
public static String permissionGrantStateToString(int state) {
return constantToString(DevicePolicyManager.class, "PERMISSION_GRANT_STATE_", state);
}
public static String permissionPolicyToString(int policy) {
return constantToString(DevicePolicyManager.class, "PERMISSION_POLICY_", policy);
}
public static String permissionToString(int permission) {
return constantToString(PackageManager.class, "PERMISSION_", permission);
}
// Copied from DebugUtils
private static String constantToString(Class<?> clazz, String prefix, int value) {
for (Field field : clazz.getDeclaredFields()) {
final int modifiers = field.getModifiers();
try {
if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)
&& field.getType().equals(int.class) && field.getName().startsWith(prefix)
&& field.getInt(null) == value) {
return constNameWithoutPrefix(prefix, field);
}
} catch (IllegalAccessException ignored) {
}
}
return prefix + Integer.toString(value);
}
// Copied from DebugUtils
private static String constNameWithoutPrefix(String prefix, Field field) {
return field.getName().substring(prefix.length());
}
}