blob: 18de0cd1988bb27dcf30d8e957c05f119ba98059 [file] [log] [blame]
/*
* Copyright (C) 2015 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.packageinstaller.permission.ui;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import static com.android.packageinstaller.PermissionControllerStatsLog.GRANT_PERMISSIONS_ACTIVITY_BUTTON_ACTIONS;
import static com.android.packageinstaller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_DENIED;
import static com.android.packageinstaller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_GRANTED;
import static com.android.packageinstaller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED;
import static com.android.packageinstaller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_POLICY_FIXED;
import static com.android.packageinstaller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_RESTRICTED_PERMISSION;
import static com.android.packageinstaller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_USER_FIXED;
import static com.android.packageinstaller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED;
import static com.android.packageinstaller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_WITH_PREJUDICE;
import static com.android.packageinstaller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED;
import static com.android.packageinstaller.permission.ui.GrantPermissionsViewHandler.DENIED;
import static com.android.packageinstaller.permission.ui.GrantPermissionsViewHandler.DENIED_DO_NOT_ASK_AGAIN;
import static com.android.packageinstaller.permission.ui.GrantPermissionsViewHandler.GRANTED_ALWAYS;
import static com.android.packageinstaller.permission.ui.GrantPermissionsViewHandler.GRANTED_FOREGROUND_ONLY;
import static com.android.packageinstaller.permission.utils.Utils.getRequestMessage;
import android.app.Activity;
import android.app.KeyguardManager;
import android.app.admin.DevicePolicyManager;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.graphics.drawable.Icon;
import android.os.Build;
import android.os.Bundle;
import android.os.UserHandle;
import android.permission.PermissionManager;
import android.text.Html;
import android.text.Spanned;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.packageinstaller.DeviceUtils;
import com.android.packageinstaller.PermissionControllerStatsLog;
import com.android.packageinstaller.permission.model.AppPermissionGroup;
import com.android.packageinstaller.permission.model.AppPermissions;
import com.android.packageinstaller.permission.model.Permission;
import com.android.packageinstaller.permission.ui.auto.GrantPermissionsAutoViewHandler;
import com.android.packageinstaller.permission.utils.ArrayUtils;
import com.android.packageinstaller.permission.utils.PackageRemovalMonitor;
import com.android.packageinstaller.permission.utils.SafetyNetLogger;
import com.android.permissioncontroller.R;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class GrantPermissionsActivity extends Activity
implements GrantPermissionsViewHandler.ResultListener {
private static final String LOG_TAG = "GrantPermissionsActivity";
private static final String KEY_REQUEST_ID = GrantPermissionsActivity.class.getName()
+ "_REQUEST_ID";
public static int NUM_BUTTONS = 5;
public static int LABEL_ALLOW_BUTTON = 0;
public static int LABEL_ALLOW_ALWAYS_BUTTON = 1;
public static int LABEL_ALLOW_FOREGROUND_BUTTON = 2;
public static int LABEL_DENY_BUTTON = 3;
public static int LABEL_DENY_AND_DONT_ASK_AGAIN_BUTTON = 4;
/** Unique Id of a request */
private long mRequestId;
private String[] mRequestedPermissions;
private CharSequence[] mButtonLabels;
private ArrayMap<Pair<String, Boolean>, GroupState> mRequestGrantPermissionGroups =
new ArrayMap<>();
private GrantPermissionsViewHandler mViewHandler;
private AppPermissions mAppPermissions;
boolean mResultSet;
/**
* Listens for changes to the permission of the app the permissions are currently getting
* granted to. {@code null} when unregistered.
*/
private @Nullable PackageManager.OnPermissionsChangedListener mPermissionChangeListener;
/**
* Listens for changes to the app the permissions are currently getting granted to. {@code null}
* when unregistered.
*/
private @Nullable PackageRemovalMonitor mPackageRemovalMonitor;
/** Package that requested the permission grant */
private String mCallingPackage;
/** uid of {@link #mCallingPackage} */
private int mCallingUid;
private int getPermissionPolicy() {
DevicePolicyManager devicePolicyManager = getSystemService(DevicePolicyManager.class);
return devicePolicyManager.getPermissionPolicy(null);
}
/**
* Try to add a single permission that is requested to be granted.
*
* <p>This does <u>not</u> expand the permissions into the {@link #computeAffectedPermissions
* affected permissions}.
*
* @param group The group the permission belongs to (might be a background permission group)
* @param permName The name of the permission to add
* @param isFirstInstance Is this the first time the groupStates get created
*/
private void addRequestedPermissions(AppPermissionGroup group, String permName,
boolean isFirstInstance) {
if (!group.isGrantingAllowed()) {
reportRequestResult(permName,
PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED);
// Skip showing groups that we know cannot be granted.
return;
}
Permission permission = group.getPermission(permName);
// If the permission is restricted it does not show in the UI and
// is not added to the group at all, so check that first.
if (permission == null && ArrayUtils.contains(
mAppPermissions.getPackageInfo().requestedPermissions, permName)) {
reportRequestResult(permName,
PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_RESTRICTED_PERMISSION);
return;
// We allow the user to choose only non-fixed permissions. A permission
// is fixed either by device policy or the user denying with prejudice.
} else if (group.isUserFixed()) {
reportRequestResult(permName,
PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_USER_FIXED);
return;
} else if (group.isPolicyFixed() && !group.areRuntimePermissionsGranted()
|| permission.isPolicyFixed()) {
reportRequestResult(permName,
PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_POLICY_FIXED);
return;
}
Pair<String, Boolean> groupKey = new Pair<>(group.getName(),
group.isBackgroundGroup());
GroupState state = mRequestGrantPermissionGroups.get(groupKey);
if (state == null) {
state = new GroupState(group);
mRequestGrantPermissionGroups.put(groupKey, state);
}
state.affectedPermissions = ArrayUtils.appendString(
state.affectedPermissions, permName);
boolean skipGroup = false;
switch (getPermissionPolicy()) {
case DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT: {
final String[] filterPermissions = new String[]{permName};
group.grantRuntimePermissions(false, filterPermissions);
group.setPolicyFixed(filterPermissions);
state.mState = GroupState.STATE_ALLOWED;
skipGroup = true;
reportRequestResult(permName,
PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_GRANTED);
} break;
case DevicePolicyManager.PERMISSION_POLICY_AUTO_DENY: {
final String[] filterPermissions = new String[]{permName};
group.setPolicyFixed(filterPermissions);
state.mState = GroupState.STATE_DENIED;
skipGroup = true;
reportRequestResult(permName,
PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_DENIED);
} break;
default: {
if (group.areRuntimePermissionsGranted()) {
group.grantRuntimePermissions(false, new String[]{permName});
state.mState = GroupState.STATE_ALLOWED;
skipGroup = true;
reportRequestResult(permName,
PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_GRANTED);
}
} break;
}
if (skipGroup && isFirstInstance) {
// Only allow to skip groups when this is the first time the dialog was created.
// Otherwise the number of groups changes between instances of the dialog.
state.mState = GroupState.STATE_SKIPPED;
}
}
/**
* Report the result of a grant of a permission.
*
* @param permission The permission that was granted or denied
* @param result The permission grant result
*/
private void reportRequestResult(@NonNull String permission, int result) {
boolean isImplicit = !ArrayUtils.contains(mRequestedPermissions, permission);
Log.v(LOG_TAG,
"Permission grant result requestId=" + mRequestId + " callingUid=" + mCallingUid
+ " callingPackage=" + mCallingPackage + " permission=" + permission
+ " isImplicit=" + isImplicit + " result=" + result);
PermissionControllerStatsLog.write(
PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED, mRequestId,
mCallingUid, mCallingPackage, permission, isImplicit, result);
}
/**
* Report the result of a grant of a permission.
*
* @param permissions The permissions that were granted or denied
* @param result The permission grant result
*/
private void reportRequestResult(@NonNull String[] permissions, int result) {
for (String permission : permissions) {
reportRequestResult(permission, result);
}
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
if (icicle == null) {
mRequestId = new Random().nextLong();
} else {
mRequestId = icicle.getLong(KEY_REQUEST_ID);
}
getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
// Cache this as this can only read on onCreate, not later.
mCallingPackage = getCallingPackage();
setFinishOnTouchOutside(false);
setTitle(R.string.permission_request_title);
mRequestedPermissions = getIntent().getStringArrayExtra(
PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES);
if (mRequestedPermissions == null) {
mRequestedPermissions = new String[0];
}
final int requestedPermCount = mRequestedPermissions.length;
if (requestedPermCount == 0) {
setResultAndFinish();
return;
}
PackageInfo callingPackageInfo = getCallingPackageInfo();
if (callingPackageInfo == null || callingPackageInfo.requestedPermissions == null
|| callingPackageInfo.requestedPermissions.length <= 0) {
setResultAndFinish();
return;
}
// Don't allow legacy apps to request runtime permissions.
if (callingPackageInfo.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
// Returning empty arrays means a cancellation.
mRequestedPermissions = new String[0];
setResultAndFinish();
return;
}
mCallingUid = callingPackageInfo.applicationInfo.uid;
UserHandle userHandle = UserHandle.getUserHandleForUid(mCallingUid);
if (DeviceUtils.isTelevision(this)) {
mViewHandler = new com.android.packageinstaller.permission.ui.television
.GrantPermissionsViewHandlerImpl(this,
mCallingPackage).setResultListener(this);
} else if (DeviceUtils.isWear(this)) {
mViewHandler = new GrantPermissionsWatchViewHandler(this).setResultListener(this);
} else if (DeviceUtils.isAuto(this)) {
mViewHandler = new GrantPermissionsAutoViewHandler(this, mCallingPackage, userHandle)
.setResultListener(this);
} else {
mViewHandler = new com.android.packageinstaller.permission.ui.handheld
.GrantPermissionsViewHandlerImpl(this, mCallingPackage, userHandle)
.setResultListener(this);
}
mAppPermissions = new AppPermissions(this, callingPackageInfo, false,
new Runnable() {
@Override
public void run() {
setResultAndFinish();
}
});
for (String requestedPermission : mRequestedPermissions) {
if (requestedPermission == null) {
continue;
}
ArrayList<String> affectedPermissions =
computeAffectedPermissions(requestedPermission);
int numAffectedPermissions = affectedPermissions.size();
for (int i = 0; i < numAffectedPermissions; i++) {
AppPermissionGroup group =
mAppPermissions.getGroupForPermission(affectedPermissions.get(i));
if (group == null) {
reportRequestResult(affectedPermissions.get(i),
PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED);
continue;
}
addRequestedPermissions(group, affectedPermissions.get(i), icicle == null);
}
}
int numGroupStates = mRequestGrantPermissionGroups.size();
for (int groupStateNum = 0; groupStateNum < numGroupStates; groupStateNum++) {
GroupState groupState = mRequestGrantPermissionGroups.valueAt(groupStateNum);
AppPermissionGroup group = groupState.mGroup;
// Restore permission group state after lifecycle events
if (icicle != null) {
groupState.mState = icicle.getInt(
getInstanceStateKey(mRequestGrantPermissionGroups.keyAt(groupStateNum)),
groupState.mState);
}
// Do not attempt to grant background access if foreground access is not either already
// granted or requested
if (group.isBackgroundGroup()) {
// Check if a foreground permission is already granted
boolean foregroundGroupAlreadyGranted = mAppPermissions.getPermissionGroup(
group.getName()).areRuntimePermissionsGranted();
boolean hasForegroundRequest = (getForegroundGroupState(group.getName()) != null);
if (!foregroundGroupAlreadyGranted && !hasForegroundRequest) {
// The background permission cannot be granted at this time
int numPermissions = groupState.affectedPermissions.length;
for (int permissionNum = 0; permissionNum < numPermissions; permissionNum++) {
Log.w(LOG_TAG,
"Cannot grant " + groupState.affectedPermissions[permissionNum]
+ " as the matching foreground permission is not already "
+ "granted.");
}
groupState.mState = GroupState.STATE_SKIPPED;
reportRequestResult(groupState.affectedPermissions,
PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED);
}
}
}
setContentView(mViewHandler.createView());
Window window = getWindow();
WindowManager.LayoutParams layoutParams = window.getAttributes();
mViewHandler.updateWindowAttributes(layoutParams);
window.setAttributes(layoutParams);
// Restore UI state after lifecycle events. This has to be before
// showNextPermissionGroupGrantRequest is called. showNextPermissionGroupGrantRequest might
// update the UI and the UI behaves differently for updates and initial creations.
if (icicle != null) {
mViewHandler.loadInstanceState(icicle);
}
if (!showNextPermissionGroupGrantRequest()) {
setResultAndFinish();
}
}
/**
* Update the {@link #mRequestedPermissions} if the system reports them as granted.
*
* <p>This also updates the {@link #mAppPermissions} state and switches to the next group grant
* request if the current group becomes granted.
*/
private void updateIfPermissionsWereGranted() {
PackageManager pm = getPackageManager();
boolean mightShowNextGroup = true;
int numGroupStates = mRequestGrantPermissionGroups.size();
for (int i = 0; i < numGroupStates; i++) {
GroupState groupState = mRequestGrantPermissionGroups.valueAt(i);
if (groupState == null || groupState.mState != GroupState.STATE_UNKNOWN) {
// Group has already been approved / denied via the UI by the user
continue;
}
boolean allAffectedPermissionsOfThisGroupAreGranted = true;
if (groupState.affectedPermissions == null) {
// It is not clear which permissions belong to this group, hence never skip this
// view
allAffectedPermissionsOfThisGroupAreGranted = false;
} else {
for (int permNum = 0; permNum < groupState.affectedPermissions.length;
permNum++) {
if (pm.checkPermission(groupState.affectedPermissions[permNum], mCallingPackage)
== PERMISSION_DENIED) {
allAffectedPermissionsOfThisGroupAreGranted = false;
break;
}
}
}
if (allAffectedPermissionsOfThisGroupAreGranted) {
groupState.mState = GroupState.STATE_ALLOWED;
if (mightShowNextGroup) {
// The UI currently displays the first group with
// mState == STATE_UNKNOWN. So we are switching to next group until we
// could not allow a group that was still unknown
if (!showNextPermissionGroupGrantRequest()) {
setResultAndFinish();
}
}
} else {
mightShowNextGroup = false;
}
}
}
@Override
protected void onStart() {
super.onStart();
try {
mPermissionChangeListener = new PermissionChangeListener();
} catch (NameNotFoundException e) {
setResultAndFinish();
return;
}
PackageManager pm = getPackageManager();
pm.addOnPermissionsChangeListener(mPermissionChangeListener);
// get notified when the package is removed
mPackageRemovalMonitor = new PackageRemovalMonitor(this, mCallingPackage) {
@Override
public void onPackageRemoved() {
Log.w(LOG_TAG, mCallingPackage + " was uninstalled");
finish();
}
};
mPackageRemovalMonitor.register();
// check if the package was removed while this activity was not started
try {
pm.getPackageInfo(mCallingPackage, 0);
} catch (NameNotFoundException e) {
Log.w(LOG_TAG, mCallingPackage + " was uninstalled while this activity was stopped", e);
finish();
}
updateIfPermissionsWereGranted();
}
@Override
protected void onStop() {
super.onStop();
if (mPackageRemovalMonitor != null) {
mPackageRemovalMonitor.unregister();
mPackageRemovalMonitor = null;
}
if (mPermissionChangeListener != null) {
getPackageManager().removeOnPermissionsChangeListener(mPermissionChangeListener);
mPermissionChangeListener = null;
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
View rootView = getWindow().getDecorView();
if (rootView.getTop() != 0) {
// We are animating the top view, need to compensate for that in motion events.
ev.setLocation(ev.getX(), ev.getY() - rootView.getTop());
}
return super.dispatchTouchEvent(ev);
}
/**
* Compose a key that stores the GroupState.mState in the instance state.
*
* @param requestGrantPermissionGroupsKey The key of the permission group
*
* @return A unique key to be used in the instance state
*/
private static String getInstanceStateKey(
Pair<String, Boolean> requestGrantPermissionGroupsKey) {
return GrantPermissionsActivity.class.getName() + "_"
+ requestGrantPermissionGroupsKey.first + "_"
+ requestGrantPermissionGroupsKey.second;
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mViewHandler.saveInstanceState(outState);
outState.putLong(KEY_REQUEST_ID, mRequestId);
int numGroups = mRequestGrantPermissionGroups.size();
for (int i = 0; i < numGroups; i++) {
int state = mRequestGrantPermissionGroups.valueAt(i).mState;
if (state != GroupState.STATE_UNKNOWN) {
outState.putInt(getInstanceStateKey(mRequestGrantPermissionGroups.keyAt(i)), state);
}
}
}
/**
* @return the background group state for the permission group with the {@code name}
*/
private GroupState getBackgroundGroupState(String name) {
return mRequestGrantPermissionGroups.get(new Pair<>(name, true));
}
/**
* @return the foreground group state for the permission group with the {@code name}
*/
private GroupState getForegroundGroupState(String name) {
return mRequestGrantPermissionGroups.get(new Pair<>(name, false));
}
private boolean shouldShowRequestForGroupState(GroupState groupState) {
if (groupState.mState == GroupState.STATE_SKIPPED) {
return false;
}
GroupState foregroundGroup = getForegroundGroupState(groupState.mGroup.getName());
if (groupState.mGroup.isBackgroundGroup()
&& (foregroundGroup != null && shouldShowRequestForGroupState(foregroundGroup))) {
// If an app requests both foreground and background permissions of the same group,
// we only show one request
return false;
}
return true;
}
private boolean showNextPermissionGroupGrantRequest() {
int numGroupStates = mRequestGrantPermissionGroups.size();
int numGrantRequests = 0;
for (int i = 0; i < numGroupStates; i++) {
if (shouldShowRequestForGroupState(mRequestGrantPermissionGroups.valueAt(i))) {
numGrantRequests++;
}
}
int currentIndex = 0;
for (GroupState groupState : mRequestGrantPermissionGroups.values()) {
if (!shouldShowRequestForGroupState(groupState)) {
continue;
}
if (groupState.mState == GroupState.STATE_UNKNOWN) {
GroupState foregroundGroupState;
GroupState backgroundGroupState;
if (groupState.mGroup.isBackgroundGroup()) {
backgroundGroupState = groupState;
foregroundGroupState = getForegroundGroupState(groupState.mGroup.getName());
} else {
foregroundGroupState = groupState;
backgroundGroupState = getBackgroundGroupState(groupState.mGroup.getName());
}
CharSequence appLabel = mAppPermissions.getAppLabel();
Icon icon;
try {
icon = Icon.createWithResource(groupState.mGroup.getIconPkg(),
groupState.mGroup.getIconResId());
} catch (Resources.NotFoundException e) {
Log.e(LOG_TAG, "Cannot load icon for group" + groupState.mGroup.getName(), e);
icon = null;
}
// If no background permissions are granted yet, we need to ask for background
// permissions
boolean needBackgroundPermission = false;
boolean isBackgroundPermissionUserSet = false;
if (backgroundGroupState != null) {
if (!backgroundGroupState.mGroup.areRuntimePermissionsGranted()) {
needBackgroundPermission = true;
isBackgroundPermissionUserSet = backgroundGroupState.mGroup.isUserSet();
}
}
// If no foreground permissions are granted yet, we need to ask for foreground
// permissions
boolean needForegroundPermission = false;
boolean isForegroundPermissionUserSet = false;
if (foregroundGroupState != null) {
if (!foregroundGroupState.mGroup.areRuntimePermissionsGranted()) {
needForegroundPermission = true;
isForegroundPermissionUserSet = foregroundGroupState.mGroup.isUserSet();
}
}
// The button doesn't show when its label is null
mButtonLabels = new CharSequence[NUM_BUTTONS];
mButtonLabels[LABEL_ALLOW_BUTTON] = getString(R.string.grant_dialog_button_allow);
mButtonLabels[LABEL_ALLOW_ALWAYS_BUTTON] = null;
mButtonLabels[LABEL_ALLOW_FOREGROUND_BUTTON] = null;
mButtonLabels[LABEL_DENY_BUTTON] = getString(R.string.grant_dialog_button_deny);
if (isForegroundPermissionUserSet || isBackgroundPermissionUserSet) {
mButtonLabels[LABEL_DENY_AND_DONT_ASK_AGAIN_BUTTON] =
getString(R.string.grant_dialog_button_deny_and_dont_ask_again);
} else {
mButtonLabels[LABEL_DENY_AND_DONT_ASK_AGAIN_BUTTON] = null;
}
int messageId;
int detailMessageId = 0;
if (needForegroundPermission) {
messageId = groupState.mGroup.getRequest();
if (groupState.mGroup.hasPermissionWithBackgroundMode()) {
mButtonLabels[LABEL_ALLOW_BUTTON] = null;
mButtonLabels[LABEL_ALLOW_FOREGROUND_BUTTON] =
getString(R.string.grant_dialog_button_allow_foreground);
if (needBackgroundPermission) {
mButtonLabels[LABEL_ALLOW_ALWAYS_BUTTON] =
getString(R.string.grant_dialog_button_allow_always);
if (isForegroundPermissionUserSet || isBackgroundPermissionUserSet) {
mButtonLabels[LABEL_DENY_BUTTON] = null;
}
}
} else {
detailMessageId = groupState.mGroup.getRequestDetail();
}
} else {
if (needBackgroundPermission) {
messageId = groupState.mGroup.getBackgroundRequest();
detailMessageId = groupState.mGroup.getBackgroundRequestDetail();
mButtonLabels[LABEL_ALLOW_BUTTON] =
getString(R.string.grant_dialog_button_allow_background);
mButtonLabels[LABEL_DENY_BUTTON] =
getString(R.string.grant_dialog_button_deny_background);
mButtonLabels[LABEL_DENY_AND_DONT_ASK_AGAIN_BUTTON] =
getString(R.string
.grant_dialog_button_deny_background_and_dont_ask_again);
} else {
// Not reached as the permissions should be auto-granted
return false;
}
}
CharSequence message = getRequestMessage(appLabel, groupState.mGroup, this,
messageId);
Spanned detailMessage = null;
if (detailMessageId != 0) {
try {
detailMessage = Html.fromHtml(
getPackageManager().getResourcesForApplication(
groupState.mGroup.getDeclaringPackage()).getString(
detailMessageId), 0);
} catch (NameNotFoundException ignored) {
}
}
// Set the permission message as the title so it can be announced.
setTitle(message);
mViewHandler.updateUi(groupState.mGroup.getName(), numGrantRequests, currentIndex,
icon, message, detailMessage, mButtonLabels);
return true;
}
if (groupState.mState != GroupState.STATE_SKIPPED) {
currentIndex++;
}
}
return false;
}
@Override
public void onPermissionGrantResult(String name,
@GrantPermissionsViewHandler.Result int result) {
logGrantPermissionActivityButtons(name, result);
GroupState foregroundGroupState = getForegroundGroupState(name);
GroupState backgroundGroupState = getBackgroundGroupState(name);
if (result == GRANTED_ALWAYS || result == GRANTED_FOREGROUND_ONLY
|| result == DENIED_DO_NOT_ASK_AGAIN) {
KeyguardManager kgm = getSystemService(KeyguardManager.class);
if (kgm.isDeviceLocked()) {
kgm.requestDismissKeyguard(this, new KeyguardManager.KeyguardDismissCallback() {
@Override
public void onDismissError() {
Log.e(LOG_TAG, "Cannot dismiss keyguard perm=" + name + " result="
+ result);
}
@Override
public void onDismissCancelled() {
// do nothing (i.e. stay at the current permission group)
}
@Override
public void onDismissSucceeded() {
// Now the keyguard is dismissed, hence the device is not locked
// anymore
onPermissionGrantResult(name, result);
}
});
return;
}
}
switch (result) {
case GRANTED_ALWAYS :
if (foregroundGroupState != null) {
onPermissionGrantResultSingleState(foregroundGroupState, true, false);
}
if (backgroundGroupState != null) {
onPermissionGrantResultSingleState(backgroundGroupState, true, false);
}
break;
case GRANTED_FOREGROUND_ONLY :
if (foregroundGroupState != null) {
onPermissionGrantResultSingleState(foregroundGroupState, true, false);
}
if (backgroundGroupState != null) {
onPermissionGrantResultSingleState(backgroundGroupState, false, false);
}
break;
case DENIED :
if (foregroundGroupState != null) {
onPermissionGrantResultSingleState(foregroundGroupState, false, false);
}
if (backgroundGroupState != null) {
onPermissionGrantResultSingleState(backgroundGroupState, false, false);
}
break;
case DENIED_DO_NOT_ASK_AGAIN :
if (foregroundGroupState != null) {
onPermissionGrantResultSingleState(foregroundGroupState, false, true);
}
if (backgroundGroupState != null) {
onPermissionGrantResultSingleState(backgroundGroupState, false, true);
}
break;
}
if (!showNextPermissionGroupGrantRequest()) {
setResultAndFinish();
}
}
/**
* Grants or revoked the affected permissions for a single {@link groupState}.
*
* @param groupState The group state with the permissions to grant/revoke
* @param granted {@code true} if the permissions should be granted, {@code false} if they
* should be revoked
* @param doNotAskAgain if the permissions should be revoked should be app be allowed to ask
* again for the same permissions?
*/
private void onPermissionGrantResultSingleState(GroupState groupState, boolean granted,
boolean doNotAskAgain) {
if (groupState != null && groupState.mGroup != null
&& groupState.mState == GroupState.STATE_UNKNOWN) {
if (granted) {
groupState.mGroup.grantRuntimePermissions(doNotAskAgain,
groupState.affectedPermissions);
groupState.mState = GroupState.STATE_ALLOWED;
reportRequestResult(groupState.affectedPermissions,
PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED);
} else {
groupState.mGroup.revokeRuntimePermissions(doNotAskAgain,
groupState.affectedPermissions);
groupState.mState = GroupState.STATE_DENIED;
reportRequestResult(groupState.affectedPermissions, doNotAskAgain
?
PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_WITH_PREJUDICE
: PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED);
}
}
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
// We do not allow backing out.
return keyCode == KeyEvent.KEYCODE_BACK;
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
// We do not allow backing out.
return keyCode == KeyEvent.KEYCODE_BACK;
}
@Override
public void finish() {
setResultIfNeeded(RESULT_CANCELED);
super.finish();
}
private PackageInfo getCallingPackageInfo() {
try {
return getPackageManager().getPackageInfo(mCallingPackage,
PackageManager.GET_PERMISSIONS);
} catch (NameNotFoundException e) {
Log.i(LOG_TAG, "No package: " + mCallingPackage, e);
return null;
}
}
private void setResultIfNeeded(int resultCode) {
if (!mResultSet) {
mResultSet = true;
logRequestedPermissionGroups();
Intent result = new Intent(PackageManager.ACTION_REQUEST_PERMISSIONS);
result.putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES, mRequestedPermissions);
PackageManager pm = getPackageManager();
int numRequestedPermissions = mRequestedPermissions.length;
int[] grantResults = new int[numRequestedPermissions];
for (int i = 0; i < numRequestedPermissions; i++) {
grantResults[i] = pm.checkPermission(mRequestedPermissions[i], mCallingPackage);
}
result.putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS, grantResults);
setResult(resultCode, result);
}
}
private void setResultAndFinish() {
setResultIfNeeded(RESULT_OK);
finish();
}
private void logRequestedPermissionGroups() {
if (mRequestGrantPermissionGroups.isEmpty()) {
return;
}
final int groupCount = mRequestGrantPermissionGroups.size();
List<AppPermissionGroup> groups = new ArrayList<>(groupCount);
for (GroupState groupState : mRequestGrantPermissionGroups.values()) {
groups.add(groupState.mGroup);
}
SafetyNetLogger.logPermissionsRequested(mAppPermissions.getPackageInfo(), groups);
}
/**
* Get the actually requested permissions when a permission is requested.
*
* <p>>In some cases requesting to grant a single permission requires the system to grant
* additional permissions. E.g. before N-MR1 a single permission of a group caused the whole
* group to be granted. Another case are permissions that are split into two. For apps that
* target an SDK before the split, this method automatically adds the split off permission.
*
* @param permission The requested permission
*
* @return The actually requested permissions
*/
private ArrayList<String> computeAffectedPermissions(String permission) {
int requestingAppTargetSDK =
mAppPermissions.getPackageInfo().applicationInfo.targetSdkVersion;
// If a permission is split, all permissions the original permission is split into are
// affected
ArrayList<String> extendedBySplitPerms = new ArrayList<>();
extendedBySplitPerms.add(permission);
List<PermissionManager.SplitPermissionInfo> splitPerms = getSystemService(
PermissionManager.class).getSplitPermissions();
int numSplitPerms = splitPerms.size();
for (int i = 0; i < numSplitPerms; i++) {
PermissionManager.SplitPermissionInfo splitPerm = splitPerms.get(i);
if (requestingAppTargetSDK < splitPerm.getTargetSdk()
&& permission.equals(splitPerm.getSplitPermission())) {
extendedBySplitPerms.addAll(splitPerm.getNewPermissions());
}
}
// For <= N_MR1 apps all permissions of the groups of the requested permissions are affected
if (requestingAppTargetSDK <= Build.VERSION_CODES.N_MR1) {
ArrayList<String> extendedBySplitPermsAndGroup = new ArrayList<>();
int numExtendedBySplitPerms = extendedBySplitPerms.size();
for (int splitPermNum = 0; splitPermNum < numExtendedBySplitPerms; splitPermNum++) {
AppPermissionGroup group = mAppPermissions.getGroupForPermission(
extendedBySplitPerms.get(splitPermNum));
if (group == null) {
continue;
}
ArrayList<Permission> permissionsInGroup = group.getPermissions();
int numPermissionsInGroup = permissionsInGroup.size();
for (int permNum = 0; permNum < numPermissionsInGroup; permNum++) {
extendedBySplitPermsAndGroup.add(permissionsInGroup.get(permNum).getName());
}
}
return extendedBySplitPermsAndGroup;
} else {
return extendedBySplitPerms;
}
}
private void logGrantPermissionActivityButtons(String permissionGroupName, int grantResult) {
int clickedButton = 0;
int presentedButtons = getButtonState();
switch (grantResult) {
case GRANTED_ALWAYS:
if ((presentedButtons & (1 << LABEL_ALLOW_BUTTON)) != 0) {
clickedButton = 1 << LABEL_ALLOW_BUTTON;
} else {
clickedButton = 1 << LABEL_ALLOW_ALWAYS_BUTTON;
}
break;
case GRANTED_FOREGROUND_ONLY:
clickedButton = 1 << LABEL_ALLOW_FOREGROUND_BUTTON;
break;
case DENIED:
clickedButton = 1 << LABEL_DENY_BUTTON;
break;
case DENIED_DO_NOT_ASK_AGAIN:
clickedButton = 1 << LABEL_DENY_AND_DONT_ASK_AGAIN_BUTTON;
break;
default:
break;
}
PermissionControllerStatsLog.write(GRANT_PERMISSIONS_ACTIVITY_BUTTON_ACTIONS,
permissionGroupName, mCallingUid, mCallingPackage, presentedButtons,
clickedButton);
Log.v(LOG_TAG, "Logged buttons presented and clicked permissionGroupName="
+ permissionGroupName + " uid=" + mCallingUid + " package=" + mCallingPackage
+ " presentedButtons=" + presentedButtons + " clickedButton=" + clickedButton);
}
private int getButtonState() {
if (mButtonLabels == null) {
return 0;
}
int buttonState = 0;
for (int i = NUM_BUTTONS - 1; i >= 0; i--) {
buttonState *= 2;
if (mButtonLabels[i] != null) {
buttonState++;
}
}
return buttonState;
}
private static final class GroupState {
static final int STATE_UNKNOWN = 0;
static final int STATE_ALLOWED = 1;
static final int STATE_DENIED = 2;
static final int STATE_SKIPPED = 3;
final AppPermissionGroup mGroup;
int mState = STATE_UNKNOWN;
/** Permissions of this group that need to be granted, null == no permissions of group */
String[] affectedPermissions;
GroupState(AppPermissionGroup group) {
mGroup = group;
}
}
private class PermissionChangeListener implements PackageManager.OnPermissionsChangedListener {
final int mCallingPackageUid;
PermissionChangeListener() throws NameNotFoundException {
mCallingPackageUid = getPackageManager().getPackageUid(mCallingPackage, 0);
}
@Override
public void onPermissionsChanged(int uid) {
if (uid == mCallingPackageUid) {
updateIfPermissionsWereGranted();
}
}
}
}