| /* |
| * 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(); |
| } |
| } |
| } |
| } |