/* * 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, 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. * *

This does not 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 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 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. * *

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 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 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. * *

>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 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 extendedBySplitPerms = new ArrayList<>(); extendedBySplitPerms.add(permission); List 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 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 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(); } } } }