summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java239
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt1602
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/NewGrantPermissionsViewModel.kt255
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/HealthGrantBehavior.kt7
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/LocationGrantBehavior.kt21
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt7
6 files changed, 219 insertions, 1912 deletions
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java
index 4b052a753..5e41cbaf3 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java
@@ -20,9 +20,9 @@ import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
import static android.Manifest.permission_group.LOCATION;
import static android.Manifest.permission_group.READ_MEDIA_VISUAL;
-import static android.health.connect.HealthPermissions.HEALTH_PERMISSION_GROUP;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+
import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.CANCELED;
import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED;
import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED_DO_NOT_ASK_AGAIN;
@@ -33,8 +33,8 @@ import static com.android.permissioncontroller.permission.ui.GrantPermissionsVie
import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_USER_SELECTED;
import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.LINKED_TO_PERMISSION_RATIONALE;
import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.LINKED_TO_SETTINGS;
-import static com.android.permissioncontroller.permission.ui.model.GrantPermissionsViewModel.APP_PERMISSION_REQUEST_CODE;
-import static com.android.permissioncontroller.permission.ui.model.GrantPermissionsViewModel.PHOTO_PICKER_REQUEST_CODE;
+import static com.android.permissioncontroller.permission.ui.model.NewGrantPermissionsViewModel.APP_PERMISSION_REQUEST_CODE;
+import static com.android.permissioncontroller.permission.ui.model.NewGrantPermissionsViewModel.PHOTO_PICKER_REQUEST_CODE;
import static com.android.permissioncontroller.permission.utils.Utils.getRequestMessage;
import android.Manifest;
@@ -56,6 +56,7 @@ import android.text.Annotation;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.ClickableSpan;
+import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.view.MotionEvent;
@@ -76,22 +77,25 @@ import com.android.modules.utils.build.SdkLevel;
import com.android.permissioncontroller.DeviceUtils;
import com.android.permissioncontroller.R;
import com.android.permissioncontroller.permission.ui.auto.GrantPermissionsAutoViewHandler;
-import com.android.permissioncontroller.permission.ui.model.GrantPermissionsViewModel;
-import com.android.permissioncontroller.permission.ui.model.GrantPermissionsViewModel.RequestInfo;
-import com.android.permissioncontroller.permission.ui.model.GrantPermissionsViewModelFactory;
+import com.android.permissioncontroller.permission.ui.model.DenyButton;
import com.android.permissioncontroller.permission.ui.model.NewGrantPermissionsViewModel;
+import com.android.permissioncontroller.permission.ui.model.NewGrantPermissionsViewModel.RequestInfo;
import com.android.permissioncontroller.permission.ui.model.NewGrantPermissionsViewModelFactory;
+import com.android.permissioncontroller.permission.ui.model.Prompt;
import com.android.permissioncontroller.permission.ui.wear.GrantPermissionsWearViewHandler;
import com.android.permissioncontroller.permission.utils.ContextCompat;
import com.android.permissioncontroller.permission.utils.KotlinUtils;
+import com.android.permissioncontroller.permission.utils.PermissionMapping;
import com.android.permissioncontroller.permission.utils.Utils;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
+import java.util.Set;
/**
* An activity which displays runtime permission prompts on behalf of an app.
@@ -167,7 +171,7 @@ public class GrantPermissionsActivity extends SettingsActivity
private int mRequestCounts = 0;
private List<RequestInfo> mRequestInfos = new ArrayList<>();
private GrantPermissionsViewHandler mViewHandler;
- private GrantPermissionsViewModel mViewModel;
+ private NewGrantPermissionsViewModel mViewModel;
/**
* A list of other GrantPermissionActivities for the same package which passed their list of
* permissions to this one. They need to be informed when this activity finishes.
@@ -245,15 +249,9 @@ public class GrantPermissionsActivity extends SettingsActivity
return;
}
- boolean useNewViewModel = KotlinUtils.INSTANCE.isNewGrantDialogBackendEnabled();
- if (useNewViewModel) {
- mRequestedPermissions = removeNullOrEmptyPermissions(requestedPermissionsArray);
- if (mIsSystemTriggered) {
- mSystemRequestedPermissions.addAll(mRequestedPermissions);
- }
- } else {
- mRequestedPermissions = GrantPermissionsViewModel.Companion
- .getSanitizedPermissionsList(requestedPermissionsArray, permissionsSdkLevel);
+ mRequestedPermissions = removeNullOrEmptyPermissions(requestedPermissionsArray);
+ if (mIsSystemTriggered) {
+ mSystemRequestedPermissions.addAll(mRequestedPermissions);
}
if (mRequestedPermissions.isEmpty()) {
@@ -301,18 +299,11 @@ public class GrantPermissionsActivity extends SettingsActivity
}
if (!mDelegated) {
- if (useNewViewModel) {
- NewGrantPermissionsViewModelFactory factory =
- new NewGrantPermissionsViewModelFactory(getApplication(), mTargetPackage,
- mRequestedPermissions, mSystemRequestedPermissions, mSessionId,
- icicle);
- mViewModel = factory.create(NewGrantPermissionsViewModel.class);
- } else {
- GrantPermissionsViewModelFactory factory = new GrantPermissionsViewModelFactory(
- getApplication(), mTargetPackage, mRequestedPermissions, mSessionId,
- icicle);
- mViewModel = factory.create(GrantPermissionsViewModel.class);
- }
+ NewGrantPermissionsViewModelFactory factory =
+ new NewGrantPermissionsViewModelFactory(getApplication(), mTargetPackage,
+ mRequestedPermissions, mSystemRequestedPermissions, mSessionId,
+ icicle);
+ mViewModel = factory.create(NewGrantPermissionsViewModel.class);
mViewModel.getRequestInfosLiveData().observe(this, this::onRequestInfoLoad);
}
@@ -396,17 +387,10 @@ public class GrantPermissionsActivity extends SettingsActivity
Bundle oldState = new Bundle();
mViewModel.getRequestInfosLiveData().removeObservers(this);
mViewModel.saveInstanceState(oldState);
- if (KotlinUtils.INSTANCE.isNewGrantDialogBackendEnabled()) {
- NewGrantPermissionsViewModelFactory factory = new NewGrantPermissionsViewModelFactory(
- getApplication(), mTargetPackage, mRequestedPermissions,
- mSystemRequestedPermissions, mSessionId, oldState);
- mViewModel = factory.create(GrantPermissionsViewModel.class);
- } else {
- GrantPermissionsViewModelFactory factory = new GrantPermissionsViewModelFactory(
- getApplication(), mTargetPackage, mRequestedPermissions,
- mSessionId, oldState);
- mViewModel = factory.create(GrantPermissionsViewModel.class);
- }
+ NewGrantPermissionsViewModelFactory factory = new NewGrantPermissionsViewModelFactory(
+ getApplication(), mTargetPackage, mRequestedPermissions,
+ mSystemRequestedPermissions, mSessionId, oldState);
+ mViewModel = factory.create(NewGrantPermissionsViewModel.class);
mViewModel.getRequestInfosLiveData().observe(this, this::onRequestInfoLoad);
if (follower != null) {
follower.mViewModel = mViewModel;
@@ -453,82 +437,34 @@ public class GrantPermissionsActivity extends SettingsActivity
// Only the top activity can receive activity results
Activity top = mFollowerActivities.isEmpty() ? this : mFollowerActivities.get(0);
- if (info.getSendToSettingsImmediately()) {
+ if (info.getPrompt() == Prompt.NO_UI_SETTINGS_REDIRECT) {
mViewModel.sendDirectlyToSettings(top, info.getGroupName());
return;
- } else if (info.getOpenPhotoPicker()) {
+ } else if (info.getPrompt() == Prompt.NO_UI_PHOTO_PICKER_REDIRECT) {
mViewModel.openPhotoPicker(top, GRANTED_USER_SELECTED);
return;
- } else if (!info.getFilteredPermissions().isEmpty()) {
+ } else if (info.getPrompt() == Prompt.NO_UI_FILTER_THIS_GROUP) {
// Filtered permissions should be removed from the requested permissions list entirely,
// and not have status returned to the app
- mRequestedPermissions.removeAll(info.getFilteredPermissions());
+ List<String> permissionsToFilter =
+ PermissionMapping.getPlatformPermissionNamesOfGroup(info.getGroupName());
+ mRequestedPermissions.removeAll(permissionsToFilter);
mRequestInfos.remove(info);
onRequestInfoLoad(mRequestInfos);
return;
- }
-
- if (Utils.isHealthPermissionUiEnabled() && HEALTH_PERMISSION_GROUP.equals(
- info.getGroupName())) {
+ } else if (info.getPrompt() == Prompt.NO_UI_HEALTH_REDIRECT) {
mViewModel.handleHealthConnectPermissions(top);
return;
}
String appLabel = KotlinUtils.INSTANCE.getPackageLabel(getApplication(),
mTargetPackage, Process.myUserHandle());
-
- Icon icon = null;
int deviceId = ContextCompat.getDeviceId(this);
- int messageId = 0;
- switch (info.getMessage()) {
- case FG_MESSAGE:
- messageId = Utils.getRequest(info.getGroupName(), deviceId);
- break;
- case FG_FINE_LOCATION_MESSAGE:
- messageId = Utils.getFineLocationRequest(deviceId);
- break;
- case FG_COARSE_LOCATION_MESSAGE:
- messageId = Utils.getCoarseLocationRequest(deviceId);
- break;
- case BG_MESSAGE:
- messageId = Utils.getBackgroundRequest(info.getGroupName(), deviceId);
- break;
- case UPGRADE_MESSAGE:
- messageId = Utils.getUpgradeRequest(info.getGroupName(), deviceId);
- break;
- case STORAGE_SUPERGROUP_MESSAGE_Q_TO_S:
- icon = Icon.createWithResource(getPackageName(), mStoragePermGroupIcon);
- messageId = R.string.permgrouprequest_storage_q_to_s;
- break;
- case STORAGE_SUPERGROUP_MESSAGE_PRE_Q:
- icon = Icon.createWithResource(getPackageName(), mStoragePermGroupIcon);
- messageId = R.string.permgrouprequest_storage_pre_q;
- break;
- case MORE_PHOTOS_MESSAGE:
- messageId = Utils.getMorePhotosRequest(deviceId);
- break;
- default:
- Log.w(LOG_TAG, "Unhandled message type: " + info.getMessage());
- }
-
+ int messageId = getMessageId(info.getGroupName(), info.getPrompt(), deviceId);
CharSequence message = getRequestMessage(appLabel, mTargetPackage, info.getGroupName(),
getDeviceName(info.getDeviceId()), this, deviceId, messageId);
- int detailMessageId = 0;
- switch (info.getDetailMessage()) {
- case FG_MESSAGE:
- detailMessageId = Utils.getRequestDetail(info.getGroupName());
- break;
- case BG_MESSAGE:
- detailMessageId = Utils.getBackgroundRequestDetail(info.getGroupName());
- break;
- case UPGRADE_MESSAGE:
- detailMessageId = Utils.getUpgradeRequestDetail(info.getGroupName());
- break;
- default:
- Log.w(LOG_TAG, "Unhandled detail message type: " + info.getDetailMessage());
- }
-
+ int detailMessageId = getDetailMessageId(info.getGroupName(), info.getPrompt());
Spanned detailMessage = null;
if (detailMessageId != 0) {
detailMessage =
@@ -551,10 +487,16 @@ public class GrantPermissionsActivity extends SettingsActivity
}
}
+ Icon icon = null;
try {
- icon = icon != null ? icon : Icon.createWithResource(
- info.getGroupInfo().getPackageName(),
- info.getGroupInfo().getIcon());
+ if (info.getPrompt() == Prompt.STORAGE_SUPERGROUP_Q_TO_S
+ || info.getPrompt() == Prompt.STORAGE_SUPERGROUP_PRE_Q) {
+ icon = Icon.createWithResource(getPackageName(), mStoragePermGroupIcon);
+ } else {
+ icon = Icon.createWithResource(
+ info.getGroupInfo().getPackageName(),
+ info.getGroupInfo().getIcon());
+ }
} catch (Resources.NotFoundException e) {
Log.e(LOG_TAG, "Cannot load icon for group" + info.getGroupName(), e);
}
@@ -566,14 +508,8 @@ public class GrantPermissionsActivity extends SettingsActivity
setTitle(message);
}
- ArrayList<Integer> idxs = new ArrayList<>();
- mButtonVisibilities = new boolean[info.getButtonVisibilities().size()];
- for (int i = 0; i < info.getButtonVisibilities().size(); i++) {
- mButtonVisibilities[i] = info.getButtonVisibilities().get(i);
- if (mButtonVisibilities[i]) {
- idxs.add(i);
- }
- }
+ mButtonVisibilities = getButtonsForPrompt(info.getPrompt(), info.getDeny(),
+ info.getShowRationale());
CharSequence permissionRationaleMessage = null;
if (isPermissionRationaleVisible()) {
@@ -583,10 +519,7 @@ public class GrantPermissionsActivity extends SettingsActivity
info.getGroupName()));
}
- boolean[] locationVisibilities = new boolean[info.getLocationVisibilities().size()];
- for (int i = 0; i < info.getLocationVisibilities().size(); i++) {
- locationVisibilities[i] = info.getLocationVisibilities().get(i);
- }
+ boolean[] locationVisibilities = getLocationButtonsForPrompt(info.getPrompt());
if (mRequestCounts < mRequestInfos.size()) {
mRequestCounts = mRequestInfos.size();
@@ -628,6 +561,90 @@ public class GrantPermissionsActivity extends SettingsActivity
throw new IllegalArgumentException("No device name for device: " + deviceId);
}
+ private int getMessageId(String permGroupName, Prompt prompt, int deviceId) {
+ return switch (prompt) {
+ case UPGRADE_SETTINGS_LINK, OT_UPGRADE_SETTINGS_LINK ->
+ Utils.getUpgradeRequest(permGroupName, deviceId);
+ case SETTINGS_LINK_FOR_BG, SETTINGS_LINK_WITH_OT ->
+ Utils.getBackgroundRequest(permGroupName, deviceId);
+ case LOCATION_FINE_UPGRADE -> Utils.getFineLocationRequest(deviceId);
+ case LOCATION_COARSE_ONLY -> Utils.getCoarseLocationRequest(deviceId);
+ case STORAGE_SUPERGROUP_PRE_Q -> R.string.permgrouprequest_storage_pre_q;
+ case STORAGE_SUPERGROUP_Q_TO_S -> R.string.permgrouprequest_storage_q_to_s;
+ case SELECT_MORE_PHOTOS -> Utils.getMorePhotosRequest(deviceId);
+ default -> Utils.getRequest(permGroupName, deviceId);
+ };
+ }
+
+ private int getDetailMessageId(String permGroupName, Prompt prompt) {
+ return switch (prompt) {
+ case UPGRADE_SETTINGS_LINK, OT_UPGRADE_SETTINGS_LINK ->
+ Utils.getUpgradeRequestDetail(permGroupName);
+ case SETTINGS_LINK_FOR_BG, SETTINGS_LINK_WITH_OT ->
+ Utils.getBackgroundRequestDetail(permGroupName);
+ default -> 0;
+ };
+ }
+
+ private boolean[] getButtonsForPrompt(Prompt prompt, DenyButton denyButton,
+ boolean shouldShowRationale) {
+ ArraySet<Integer> buttons = new ArraySet<>();
+ switch (prompt) {
+ case BASIC, STORAGE_SUPERGROUP_PRE_Q, STORAGE_SUPERGROUP_Q_TO_S ->
+ buttons.add(ALLOW_BUTTON);
+ case FG_ONLY, SETTINGS_LINK_FOR_BG -> buttons.add(ALLOW_FOREGROUND_BUTTON);
+ case ONE_TIME_FG, SETTINGS_LINK_WITH_OT, LOCATION_TWO_BUTTON_COARSE_HIGHLIGHT,
+ LOCATION_TWO_BUTTON_FINE_HIGHLIGHT, LOCATION_COARSE_ONLY,
+ LOCATION_FINE_UPGRADE ->
+ buttons.addAll(Arrays.asList(ALLOW_FOREGROUND_BUTTON, ALLOW_ONE_TIME_BUTTON));
+ case SELECT_PHOTOS, SELECT_MORE_PHOTOS ->
+ buttons.addAll(Arrays.asList(ALLOW_ALL_BUTTON, ALLOW_SELECTED_BUTTON));
+ }
+
+ switch (denyButton) {
+ case DENY -> buttons.add(DENY_BUTTON);
+ case DENY_DONT_ASK_AGAIN -> buttons.add(DENY_AND_DONT_ASK_AGAIN_BUTTON);
+ case DONT_SELECT_MORE -> buttons.add(DONT_ALLOW_MORE_SELECTED_BUTTON);
+ case NO_UPGRADE -> buttons.add(NO_UPGRADE_BUTTON);
+ case NO_UPGRADE_OT -> buttons.add(NO_UPGRADE_OT_BUTTON);
+ case NO_UPGRADE_AND_DONT_ASK_AGAIN ->
+ buttons.add(NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON);
+ case NO_UPGRADE_AND_DONT_ASK_AGAIN_OT ->
+ buttons.add(NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON);
+ }
+
+ if (shouldShowRationale) {
+ buttons.add(LINK_TO_PERMISSION_RATIONALE);
+ }
+ return convertSetToBoolList(buttons, NEXT_BUTTON);
+ }
+
+ private boolean[] getLocationButtonsForPrompt(Prompt prompt) {
+ ArraySet<Integer> locationButtons = new ArraySet<>();
+ switch (prompt) {
+ case LOCATION_TWO_BUTTON_COARSE_HIGHLIGHT ->
+ locationButtons.addAll(Arrays.asList(LOCATION_ACCURACY_LAYOUT,
+ DIALOG_WITH_BOTH_LOCATIONS, COARSE_RADIO_BUTTON));
+ case LOCATION_TWO_BUTTON_FINE_HIGHLIGHT ->
+ locationButtons.addAll(Arrays.asList(LOCATION_ACCURACY_LAYOUT,
+ DIALOG_WITH_BOTH_LOCATIONS, FINE_RADIO_BUTTON));
+ case LOCATION_COARSE_ONLY ->
+ locationButtons.addAll(Arrays.asList(LOCATION_ACCURACY_LAYOUT,
+ DIALOG_WITH_COARSE_LOCATION_ONLY));
+ case LOCATION_FINE_UPGRADE ->
+ locationButtons.addAll(Arrays.asList(LOCATION_ACCURACY_LAYOUT,
+ DIALOG_WITH_FINE_LOCATION_ONLY));
+ }
+ return convertSetToBoolList(locationButtons, NEXT_LOCATION_DIALOG);
+ }
+
+ private boolean[] convertSetToBoolList(Set<Integer> buttonSet, int size) {
+ boolean[] buttonArray = new boolean[size];
+ for (int button: buttonSet) {
+ buttonArray[button] = true;
+ }
+ return buttonArray;
+ }
// LINT.IfChange(dispatchTouchEvent)
@Override
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt
deleted file mode 100644
index 87be1f20d..000000000
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt
+++ /dev/null
@@ -1,1602 +0,0 @@
-/*
- * Copyright (C) 2022 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.
- */
-@file:Suppress("DEPRECATION")
-
-package com.android.permissioncontroller.permission.ui.model
-
-import android.Manifest
-import android.Manifest.permission.ACCESS_COARSE_LOCATION
-import android.Manifest.permission.ACCESS_FINE_LOCATION
-import android.Manifest.permission.POST_NOTIFICATIONS
-import android.Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
-import android.Manifest.permission_group.LOCATION
-import android.Manifest.permission_group.READ_MEDIA_VISUAL
-import android.annotation.SuppressLint
-import android.app.Activity
-import android.app.Application
-import android.app.admin.DevicePolicyManager
-import android.content.Intent
-import android.content.pm.PackageManager
-import android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED
-import android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED
-import android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET
-import android.health.connect.HealthConnectManager.ACTION_REQUEST_HEALTH_PERMISSIONS
-import android.health.connect.HealthConnectManager.isHealthPermission
-import android.health.connect.HealthPermissions.HEALTH_PERMISSION_GROUP
-import android.os.Build
-import android.os.Bundle
-import android.os.Process
-import android.os.UserManager
-import android.permission.PermissionManager
-import android.provider.MediaStore
-import android.util.Log
-import androidx.core.util.Consumer
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
-import com.android.modules.utils.build.SdkLevel
-import com.android.permission.safetylabel.SafetyLabel
-import com.android.permissioncontroller.Constants
-import com.android.permissioncontroller.DeviceUtils
-import com.android.permissioncontroller.PermissionControllerStatsLog
-import com.android.permissioncontroller.PermissionControllerStatsLog.GRANT_PERMISSIONS_ACTIVITY_BUTTON_ACTIONS
-import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED
-import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_DENIED
-import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_GRANTED
-import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED
-import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_POLICY_FIXED
-import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_RESTRICTED_PERMISSION
-import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_USER_FIXED
-import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__PHOTOS_SELECTED
-import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED
-import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_IN_SETTINGS
-import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_WITH_PREJUDICE
-import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_WITH_PREJUDICE_IN_SETTINGS
-import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED
-import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED_IN_SETTINGS
-import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED_ONE_TIME
-import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_IGNORED
-import com.android.permissioncontroller.auto.DrivingDecisionReminderService
-import com.android.permissioncontroller.permission.data.LightAppPermGroupLiveData
-import com.android.permissioncontroller.permission.data.LightPackageInfoLiveData
-import com.android.permissioncontroller.permission.data.PackagePermissionsLiveData
-import com.android.permissioncontroller.permission.data.v34.SafetyLabelInfoLiveData
-import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData
-import com.android.permissioncontroller.permission.data.get
-import com.android.permissioncontroller.permission.model.AppPermissionGroup
-import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup
-import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo
-import com.android.permissioncontroller.permission.model.livedatatypes.LightPermGroupInfo
-import com.android.permissioncontroller.permission.service.PermissionChangeStorageImpl
-import com.android.permissioncontroller.permission.service.v33.PermissionDecisionStorageImpl
-import com.android.permissioncontroller.permission.ui.AutoGrantPermissionsNotifier
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_ALL_BUTTON
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_BUTTON
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_FOREGROUND_BUTTON
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_ONE_TIME_BUTTON
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_SELECTED_BUTTON
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.COARSE_RADIO_BUTTON
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DENY_AND_DONT_ASK_AGAIN_BUTTON
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DENY_BUTTON
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DIALOG_WITH_BOTH_LOCATIONS
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DIALOG_WITH_COARSE_LOCATION_ONLY
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DIALOG_WITH_FINE_LOCATION_ONLY
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DONT_ALLOW_MORE_SELECTED_BUTTON
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.FINE_RADIO_BUTTON
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.INTENT_PHOTOS_SELECTED
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.LINK_TO_PERMISSION_RATIONALE
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.LINK_TO_SETTINGS
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.LOCATION_ACCURACY_LAYOUT
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NEXT_BUTTON
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NEXT_LOCATION_DIALOG
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_BUTTON
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_OT_BUTTON
-import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.CANCELED
-import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED
-import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED_DO_NOT_ASK_AGAIN
-import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED_MORE
-import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_ALWAYS
-import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_FOREGROUND_ONLY
-import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_ONE_TIME
-import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_USER_SELECTED
-import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity
-import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_INTERACTED
-import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_RESULT
-import com.android.permissioncontroller.permission.ui.v34.PermissionRationaleActivity
-import com.android.permissioncontroller.permission.utils.ContextCompat
-import com.android.permissioncontroller.permission.utils.v31.AdminRestrictedPermissionsUtils
-import com.android.permissioncontroller.permission.utils.KotlinUtils
-import com.android.permissioncontroller.permission.utils.KotlinUtils.getDefaultPrecision
-import com.android.permissioncontroller.permission.utils.KotlinUtils.grantBackgroundRuntimePermissions
-import com.android.permissioncontroller.permission.utils.KotlinUtils.grantForegroundRuntimePermissions
-import com.android.permissioncontroller.permission.utils.KotlinUtils.isLocationAccuracyEnabled
-import com.android.permissioncontroller.permission.utils.KotlinUtils.isPhotoPickerPromptSupported
-import com.android.permissioncontroller.permission.utils.KotlinUtils.revokeBackgroundRuntimePermissions
-import com.android.permissioncontroller.permission.utils.KotlinUtils.revokeForegroundRuntimePermissions
-import com.android.permissioncontroller.permission.utils.PermissionMapping
-import com.android.permissioncontroller.permission.utils.PermissionMapping.getPartialStorageGrantPermissionsForGroup
-import com.android.permissioncontroller.permission.utils.SafetyNetLogger
-import com.android.permissioncontroller.permission.utils.Utils
-import com.android.permissioncontroller.permission.utils.v34.SafetyLabelUtils
-
-/**
- * ViewModel for the GrantPermissionsActivity. Tracks all permission groups that are affected by
- * the permissions requested by the user, and generates a RequestInfo object for each group, if
- * action is needed. It will not return any data if one of the requests is malformed.
- *
- * @param app: The current application
- * @param packageName: The packageName permissions are being requested for
- * @param requestedPermissions: The list of permissions requested
- * @param sessionId: A long to identify this session
- * @param storedState: Previous state, if this activity was stopped and is being recreated
- */
-open class GrantPermissionsViewModel(
- private val app: Application,
- private val packageName: String,
- private val requestedPermissions: List<String>,
- private val sessionId: Long,
- private val storedState: Bundle?
-) : ViewModel() {
- private val LOG_TAG = GrantPermissionsViewModel::class.java.simpleName
- private val user = Process.myUserHandle()
- private val packageInfoLiveData = LightPackageInfoLiveData[packageName, user]
- private val safetyLabelInfoLiveData =
- if (SdkLevel.isAtLeastU() && requestedPermissions
- .mapNotNull { PermissionMapping.getGroupOfPlatformPermission(it) }
- .any { PermissionMapping.isSafetyLabelAwarePermissionGroup(it) }) {
- SafetyLabelInfoLiveData[packageName, user]
- } else {
- null
- }
- private val dpm = app.getSystemService(DevicePolicyManager::class.java)!!
- private val permissionPolicy = dpm.getPermissionPolicy(null)
- private val permGroupsToSkip = mutableListOf<String>()
- private var groupStates = mutableMapOf<Pair<String, Boolean>, GroupState>()
-
- private var autoGrantNotifier: AutoGrantPermissionsNotifier? = null
- private fun getAutoGrantNotifier(): AutoGrantPermissionsNotifier {
- autoGrantNotifier = AutoGrantPermissionsNotifier(app, packageInfo.toPackageInfo(app)!!)
- return autoGrantNotifier!!
- }
-
- private lateinit var packageInfo: LightPackageInfo
-
- // All permissions that could possibly be affected by the provided requested permissions, before
- // filtering system fixed, auto grant, etc.
- private var unfilteredAffectedPermissions = requestedPermissions
-
- private val splitPermissionTargetSdkMap = mutableMapOf<String, Int>()
-
- private var appPermGroupLiveDatas = mutableMapOf<String, LightAppPermGroupLiveData>()
-
- /**
- * A class which represents a correctly requested permission group, and the buttons and messages
- * which should be shown with it.
- */
- // TODO: 284183336, once the old viewModel is gone, this should be replaced with a Prompt and
- // DenyButton
- data class RequestInfo(
- val groupInfo: LightPermGroupInfo,
- val buttonVisibilities: List<Boolean> = List(NEXT_BUTTON) { false },
- val locationVisibilities: List<Boolean> = List(NEXT_LOCATION_DIALOG) { false },
- val message: RequestMessage = RequestMessage.FG_MESSAGE,
- val detailMessage: RequestMessage = RequestMessage.NO_MESSAGE,
- val sendToSettingsImmediately: Boolean = false,
- val openPhotoPicker: Boolean = false,
- // Unused for now, will be included in the GrantPermissions refactor
- val filteredPermissions: Collection<String> = emptyList(),
- val deviceId: Int = ContextCompat.DEVICE_ID_DEFAULT,
- ) {
- val groupName = groupInfo.name
- }
-
- open var activityResultCallback: Consumer<Intent>? = null
-
- /**
- * A LiveData which holds a list of the currently pending RequestInfos
- */
- open val requestInfosLiveData = object :
- SmartUpdateMediatorLiveData<List<RequestInfo>>() {
- private val LOG_TAG = GrantPermissionsViewModel::class.java.simpleName
- private val packagePermissionsLiveData = PackagePermissionsLiveData[packageName, user]
-
- init {
- addSource(packagePermissionsLiveData) { onPackageLoaded() }
- addSource(packageInfoLiveData) { onPackageLoaded() }
- if (safetyLabelInfoLiveData != null) {
- addSource(safetyLabelInfoLiveData) { onPackageLoaded() }
- }
-
- // Load package state, if available
- onPackageLoaded()
- }
-
- private fun onPackageLoaded() {
- if (packageInfoLiveData.isStale ||
- packagePermissionsLiveData.isStale ||
- (safetyLabelInfoLiveData != null && safetyLabelInfoLiveData.isStale)) {
- return
- }
-
- val groups = packagePermissionsLiveData.value
- val pI = packageInfoLiveData.value
- if (groups == null || groups.isEmpty() || pI == null) {
- Log.e(LOG_TAG, "Package $packageName not found")
- value = null
- return
- }
- packageInfo = pI
-
- if (packageInfo.requestedPermissions.isEmpty() ||
- packageInfo.targetSdkVersion < Build.VERSION_CODES.M) {
- Log.e(LOG_TAG, "Package $packageName has no requested permissions, or " +
- "is a pre-M app")
- value = null
- return
- }
-
- val allAffectedPermissions = requestedPermissions.toMutableSet()
- for (requestedPerm in requestedPermissions) {
- allAffectedPermissions.addAll(computeAffectedPermissions(requestedPerm, groups))
- }
- unfilteredAffectedPermissions = allAffectedPermissions.toList()
-
- setAppPermGroupsLiveDatas(groups.toMutableMap().apply {
- remove(PackagePermissionsLiveData.NON_RUNTIME_NORMAL_PERMS)
- })
-
- for (splitPerm in app.getSystemService(
- PermissionManager::class.java)!!.splitPermissions) {
- splitPermissionTargetSdkMap[splitPerm.splitPermission] = splitPerm.targetSdk
- }
- }
-
- private fun setAppPermGroupsLiveDatas(groups: Map<String, List<String>>) {
- val requestedGroups = groups.filter { (_, perms) ->
- perms.any { it in unfilteredAffectedPermissions }
- }
-
- if (requestedGroups.isEmpty()) {
- Log.e(LOG_TAG, "None of " +
- "$unfilteredAffectedPermissions in $groups")
- value = null
- return
- }
-
- val getLiveDataFun = { groupName: String ->
- LightAppPermGroupLiveData[packageName, groupName, user]
- }
- setSourcesToDifference(requestedGroups.keys, appPermGroupLiveDatas, getLiveDataFun)
- }
-
- override fun onUpdate() {
- if (appPermGroupLiveDatas.any { it.value.isStale }) {
- return
- }
- var newGroups = false
- for ((groupName, groupLiveData) in appPermGroupLiveDatas) {
- val appPermGroup = groupLiveData.value
- if (appPermGroup == null || groupName in permGroupsToSkip) {
- if (appPermGroup == null) {
- Log.e(LOG_TAG, "Group $packageName $groupName invalid")
- }
- groupStates[groupName to true]?.state = STATE_SKIPPED
- groupStates[groupName to false]?.state = STATE_SKIPPED
- continue
- }
-
- packageInfo = appPermGroup.packageInfo
-
- val states = groupStates.filter { it.key.first == groupName }
- if (states.isNotEmpty()) {
- for ((key, state) in states) {
- val allAffectedGranted = state.affectedPermissions.all { perm ->
- appPermGroup.permissions[perm]?.isGrantedIncludingAppOp == true &&
- appPermGroup.permissions[perm]?.isRevokeWhenRequested == false
- }
- if (allAffectedGranted) {
- groupStates[key]!!.state = STATE_ALLOWED
- }
- }
- } else {
- newGroups = true
- }
- }
-
- if (newGroups) {
- groupStates = getRequiredGroupStates(
- appPermGroupLiveDatas.mapNotNull { it.value.value })
- }
- setRequestInfosFromGroupStates()
- }
-
- private fun setRequestInfosFromGroupStates() {
- val requestInfos = mutableListOf<RequestInfo>()
- for ((key, groupState) in groupStates) {
- val groupInfo = groupState.group.permGroupInfo
- val (groupName, isBackground) = key
- if (groupState.state != STATE_UNKNOWN) {
- continue
- }
- val fgState = groupStates[groupName to false]
- val bgState = groupStates[groupName to true]
- var needFgPermissions = false
- var needBgPermissions = false
- var isFgUserSet = false
- var isBgUserSet = false
- var minSdkForOrderedSplitPermissions = Build.VERSION_CODES.R
- if (fgState?.group != null) {
- val fgGroup = fgState.group
- for (perm in fgState.affectedPermissions) {
- minSdkForOrderedSplitPermissions = maxOf(minSdkForOrderedSplitPermissions,
- splitPermissionTargetSdkMap.getOrDefault(perm, 0))
- if (fgGroup.permissions[perm]?.isGrantedIncludingAppOp == false) {
- // If any of the requested permissions is not granted,
- // needFgPermissions = true
- needFgPermissions = true
- // If any of the requested permission's UserSet is true and the
- // permission is not granted, isFgUserSet = true.
- if (fgGroup.permissions[perm]?.isUserSet == true) {
- isFgUserSet = true
- }
- }
- }
- }
- if (bgState?.group?.background?.isGranted == false) {
- needBgPermissions = true
- isBgUserSet = bgState.group.background.isUserSet
- }
-
- val buttonVisibilities = MutableList(NEXT_BUTTON) { false }
- buttonVisibilities[ALLOW_BUTTON] = true
- buttonVisibilities[DENY_BUTTON] = true
- buttonVisibilities[ALLOW_ONE_TIME_BUTTON] =
- PermissionMapping.supportsOneTimeGrant(groupName)
- var message = RequestMessage.FG_MESSAGE
- // Whether or not to use the foreground, background, or no detail message.
- // null ==
- var detailMessage = RequestMessage.NO_MESSAGE
-
- if (KotlinUtils.isPhotoPickerPromptEnabled() &&
- groupState.group.permGroupName == READ_MEDIA_VISUAL &&
- groupState.group.packageInfo.targetSdkVersion >= Build.VERSION_CODES.TIRAMISU) {
- // If the USER_SELECTED permission is user fixed and granted, or the app is only
- // requesting USER_SELECTED, direct straight to photo picker
- val userPerm = groupState.group.permissions[READ_MEDIA_VISUAL_USER_SELECTED]
- if ((userPerm?.isUserFixed == true && userPerm.isGrantedIncludingAppOp) ||
- groupState.affectedPermissions == listOf(READ_MEDIA_VISUAL_USER_SELECTED)) {
- requestInfos.add(RequestInfo(groupInfo, openPhotoPicker = true))
- continue
- } else if (isPartialStorageGrant(groupState.group)) {
- // More photos dialog
- message = RequestMessage.MORE_PHOTOS_MESSAGE
- buttonVisibilities[DENY_AND_DONT_ASK_AGAIN_BUTTON] = false
- buttonVisibilities[DENY_BUTTON] = false
- buttonVisibilities[DONT_ALLOW_MORE_SELECTED_BUTTON] = true
- } else {
- // Standard photos dialog
- buttonVisibilities[DENY_AND_DONT_ASK_AGAIN_BUTTON] = isFgUserSet
- buttonVisibilities[DENY_BUTTON] = !isFgUserSet
- }
- buttonVisibilities[ALLOW_SELECTED_BUTTON] = true
- buttonVisibilities[ALLOW_BUTTON] = false
- buttonVisibilities[ALLOW_ALL_BUTTON] = true
- } else if (groupState.group.packageInfo.targetSdkVersion >=
- minSdkForOrderedSplitPermissions) {
- if (isBackground || Utils.hasPermWithBackgroundModeCompat(groupState.group)) {
- if (needFgPermissions) {
- if (needBgPermissions) {
- if (groupState.group.permGroupName
- .equals(Manifest.permission_group.CAMERA) ||
- groupState.group.permGroupName
- .equals(Manifest.permission_group.MICROPHONE)) {
- if (groupState.group.packageInfo.targetSdkVersion >=
- Build.VERSION_CODES.S) {
- Log.e(LOG_TAG,
- "For S apps, background permissions must be " +
- "requested after foreground permissions are" +
- " already granted")
- value = null
- return
- } else {
- // Case: sdk < S, BG&FG mic/camera permission requested
- buttonVisibilities[ALLOW_BUTTON] = false
- buttonVisibilities[ALLOW_FOREGROUND_BUTTON] = true
- buttonVisibilities[DENY_BUTTON] = !isFgUserSet
- buttonVisibilities[DENY_AND_DONT_ASK_AGAIN_BUTTON] =
- isFgUserSet
- if (needBgPermissions) {
- // Case: sdk < R, BG/FG permission requesting both
- message = RequestMessage.BG_MESSAGE
- detailMessage = RequestMessage.BG_MESSAGE
- }
- }
- } else {
- // Shouldn't be reached as background must be requested as a
- // singleton
- Log.e(LOG_TAG, "For R+ apps, background permissions must be " +
- "requested after foreground permissions are already" +
- " granted")
- value = null
- return
- }
- } else {
- buttonVisibilities[ALLOW_BUTTON] = false
- buttonVisibilities[ALLOW_FOREGROUND_BUTTON] = true
- buttonVisibilities[DENY_BUTTON] = !isFgUserSet
- buttonVisibilities[DENY_AND_DONT_ASK_AGAIN_BUTTON] = isFgUserSet
- }
- } else if (needBgPermissions) {
- // Case: sdk >= R, BG/FG permission requesting BG only
- if (storedState != null && storedState.containsKey(getInstanceStateKey(
- groupInfo.name, groupState.isBackground))) {
- // If we're restoring state, and we had this groupInfo in our
- // previous state, that means that we likely sent the user to
- // settings already. Don't send the user back.
- permGroupsToSkip.add(groupInfo.name)
- groupState.state = STATE_SKIPPED
- } else {
- requestInfos.add(RequestInfo(
- groupInfo, sendToSettingsImmediately = true))
- }
- continue
- } else {
- // Not reached as the permissions should be auto-granted
- value = null
- return
- }
- } else {
- // Case: sdk >= R, Requesting normal permission
- buttonVisibilities[DENY_BUTTON] = !isFgUserSet
- buttonVisibilities[DENY_AND_DONT_ASK_AGAIN_BUTTON] = isFgUserSet
- }
- } else {
- if (isBackground || Utils.hasPermWithBackgroundModeCompat(groupState.group)) {
- if (needFgPermissions) {
- // Case: sdk < R, BG/FG permission requesting both or FG only
- buttonVisibilities[ALLOW_BUTTON] = false
- buttonVisibilities[ALLOW_FOREGROUND_BUTTON] = true
- buttonVisibilities[DENY_BUTTON] = !isFgUserSet
- buttonVisibilities[DENY_AND_DONT_ASK_AGAIN_BUTTON] = isFgUserSet
- if (needBgPermissions) {
- // Case: sdk < R, BG/FG permission requesting both
- message = RequestMessage.BG_MESSAGE
- detailMessage = RequestMessage.BG_MESSAGE
- }
- } else if (needBgPermissions) {
- // Case: sdk < R, BG/FG permission requesting BG only
- if (!groupState.group.foreground.isGranted) {
- Log.e(LOG_TAG, "Background permissions can't be requested " +
- "solely before foreground permissions are granted.")
- value = null
- return
- }
- message = RequestMessage.UPGRADE_MESSAGE
- detailMessage = RequestMessage.UPGRADE_MESSAGE
- buttonVisibilities[ALLOW_BUTTON] = false
- buttonVisibilities[DENY_BUTTON] = false
- buttonVisibilities[ALLOW_ONE_TIME_BUTTON] = false
- if (groupState.group.isOneTime) {
- buttonVisibilities[NO_UPGRADE_OT_BUTTON] = !isBgUserSet
- buttonVisibilities[NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON] =
- isBgUserSet
- } else {
- buttonVisibilities[NO_UPGRADE_BUTTON] = !isBgUserSet
- buttonVisibilities[NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON] =
- isBgUserSet
- }
- } else {
- // Not reached as the permissions should be auto-granted
- value = null
- return
- }
- } else {
- // If no permissions needed, do nothing
- if (!needFgPermissions && !needBgPermissions) {
- value = null
- return
- }
- // Case: sdk < R, Requesting normal permission
- buttonVisibilities[DENY_BUTTON] = !isFgUserSet
- buttonVisibilities[DENY_AND_DONT_ASK_AGAIN_BUTTON] = isFgUserSet
- }
- }
- buttonVisibilities[LINK_TO_SETTINGS] =
- detailMessage != RequestMessage.NO_MESSAGE
-
- // Show location permission dialogs based on location permissions
- val locationVisibilities = MutableList(NEXT_LOCATION_DIALOG) { false }
- if (groupState.group.permGroupName == LOCATION &&
- isLocationAccuracyEnabledForApp(groupState.group)) {
- if (needFgPermissions) {
- locationVisibilities[LOCATION_ACCURACY_LAYOUT] = true
- if (fgState != null &&
- fgState.affectedPermissions.contains(ACCESS_FINE_LOCATION)) {
- val coarseLocationPerm =
- groupState.group.allPermissions[ACCESS_COARSE_LOCATION]
- if (coarseLocationPerm?.isGrantedIncludingAppOp == true) {
- // Upgrade flow
- locationVisibilities[DIALOG_WITH_FINE_LOCATION_ONLY] = true
- message = RequestMessage.FG_FINE_LOCATION_MESSAGE
- // If COARSE was granted one time, hide 'While in use' button
- if (coarseLocationPerm.isOneTime) {
- buttonVisibilities[ALLOW_FOREGROUND_BUTTON] = false
- }
- } else {
- if (!fgState.affectedPermissions.contains(ACCESS_COARSE_LOCATION)) {
- Log.e(LOG_TAG, "ACCESS_FINE_LOCATION must be requested " +
- "with ACCESS_COARSE_LOCATION.")
- value = null
- return
- }
- // Normal flow with both Coarse and Fine locations
- locationVisibilities[DIALOG_WITH_BOTH_LOCATIONS] = true
- // Steps to decide location accuracy default state
- // 1. If none of the FINE and COARSE isSelectedLocationAccuracy
- // flags is set, then use default precision from device config.
- // 2. Otherwise set to whichever isSelectedLocationAccuracy is true.
- val fineLocationPerm =
- groupState.group.allPermissions[ACCESS_FINE_LOCATION]
- if (coarseLocationPerm?.isSelectedLocationAccuracy == false &&
- fineLocationPerm?.isSelectedLocationAccuracy == false) {
- if (getDefaultPrecision()) {
- locationVisibilities[FINE_RADIO_BUTTON] = true
- } else {
- locationVisibilities[COARSE_RADIO_BUTTON] = true
- }
- } else if (coarseLocationPerm?.isSelectedLocationAccuracy == true) {
- locationVisibilities[COARSE_RADIO_BUTTON] = true
- } else {
- locationVisibilities[FINE_RADIO_BUTTON] = true
- }
- }
- } else if (fgState != null && fgState.affectedPermissions
- .contains(ACCESS_COARSE_LOCATION)) {
- // Request Coarse only
- locationVisibilities[DIALOG_WITH_COARSE_LOCATION_ONLY] = true
- message = RequestMessage.FG_COARSE_LOCATION_MESSAGE
- }
- }
- }
-
- if (SdkLevel.isAtLeastT()) {
- // If app is T+, requests for the STORAGE group are ignored
- if (packageInfo.targetSdkVersion > Build.VERSION_CODES.S_V2 &&
- groupState.group.permGroupName == Manifest.permission_group.STORAGE) {
- continue
- }
- // If app is <T and requests STORAGE, grant dialogs has special text
- if (groupState.group.permGroupName in
- PermissionMapping.STORAGE_SUPERGROUP_PERMISSIONS) {
- if (packageInfo.targetSdkVersion < Build.VERSION_CODES.Q) {
- message = RequestMessage.STORAGE_SUPERGROUP_MESSAGE_PRE_Q
- } else if (packageInfo.targetSdkVersion <= Build.VERSION_CODES.S_V2) {
- message = RequestMessage.STORAGE_SUPERGROUP_MESSAGE_Q_TO_S
- }
- }
- }
-
- val safetyLabel = safetyLabelInfoLiveData?.value?.safetyLabel
- val showPermissionRationale =
- shouldShowPermissionRationale(safetyLabel, groupState.group.permGroupName)
- buttonVisibilities[LINK_TO_PERMISSION_RATIONALE] = showPermissionRationale
-
- requestInfos.add(RequestInfo(
- groupInfo,
- buttonVisibilities,
- locationVisibilities,
- message,
- detailMessage))
- }
-
- sortPermissionGroups(requestInfos)
-
- value = if (requestInfos.any { it.sendToSettingsImmediately } &&
- requestInfos.size > 1) {
- Log.e(LOG_TAG, "For R+ apps, background permissions must be requested " +
- "individually")
- null
- } else {
- requestInfos
- }
- }
- }
-
- fun sortPermissionGroups(requestInfos: MutableList<RequestInfo>) {
- requestInfos.sortWith { rhs, lhs ->
- val rhsHasOneTime = rhs.buttonVisibilities[ALLOW_ONE_TIME_BUTTON]
- val lhsHasOneTime = lhs.buttonVisibilities[ALLOW_ONE_TIME_BUTTON]
- if (rhsHasOneTime && !lhsHasOneTime) {
- -1
- } else if ((!rhsHasOneTime && lhsHasOneTime) ||
- Utils.isHealthPermissionGroup(rhs.groupName)
- ) {
- 1
- } else {
- rhs.groupName.compareTo(lhs.groupName)
- }
- }
- }
-
- private fun shouldShowPermissionRationale(
- safetyLabel: SafetyLabel?,
- permissionGroupName: String?
- ): Boolean {
- if (safetyLabel == null || permissionGroupName == null) {
- return false
- }
-
- val purposes = SafetyLabelUtils.getSafetyLabelSharingPurposesForGroup(safetyLabel,
- permissionGroupName)
- return purposes.isNotEmpty()
- }
-
- /**
- * Converts a list of LightAppPermGroups into a list of GroupStates
- */
- private fun getRequiredGroupStates(
- groups: List<LightAppPermGroup>
- ): MutableMap<Pair<String, Boolean>, GroupState> {
- val groupStates = mutableMapOf<Pair<String, Boolean>, GroupState>()
- val filteredPermissions = unfilteredAffectedPermissions.filter { perm ->
- val group = getGroupWithPerm(perm, groups)
- group != null && isPermissionGrantableAndNotFixed(perm, group)
- }
- for (perm in filteredPermissions) {
- val group = getGroupWithPerm(perm, groups)!!
-
- val isBackground = perm in group.backgroundPermNames
- val groupStateInfo = groupStates.getOrPut(group.permGroupName to isBackground) {
- GroupState(group, isBackground)
- }
-
- var currGroupState = groupStateInfo.state
- if (storedState != null && currGroupState != STATE_UNKNOWN) {
- currGroupState = storedState.getInt(getInstanceStateKey(group.permGroupName,
- isBackground), STATE_UNKNOWN)
- }
-
- val otherGroupPermissions = filteredPermissions.filter { it in group.permissions }
- val groupStateOfPerm = getGroupState(perm, group, otherGroupPermissions)
- if (groupStateOfPerm != STATE_UNKNOWN) {
- currGroupState = groupStateOfPerm
- }
-
- if (group.permGroupName in permGroupsToSkip) {
- currGroupState = STATE_SKIPPED
- }
-
- if (currGroupState != STATE_UNKNOWN) {
- groupStateInfo.state = currGroupState
- }
- // If we saved state, load it
- groupStateInfo.affectedPermissions.add(perm)
- }
- return groupStates
- }
-
- /**
- * 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 perm The requested permission
- *
- * @return The actually requested permissions
- */
- private fun computeAffectedPermissions(
- perm: String,
- appPermissions: Map<String, List<String>>
- ): List<String> {
- val requestingAppTargetSDK = packageInfo.targetSdkVersion
-
- // If a permission is split, all permissions the original permission is split into are
- // affected
- val extendedBySplitPerms = mutableListOf(perm)
-
- val splitPerms = app.getSystemService(PermissionManager::class.java)!!.splitPermissions
- for (splitPerm in splitPerms) {
- if (requestingAppTargetSDK < splitPerm.targetSdk && perm == splitPerm.splitPermission) {
- extendedBySplitPerms.addAll(splitPerm.newPermissions)
- }
- }
-
- // For <= N_MR1 apps all permissions of the groups of the requested permissions are affected
- if (requestingAppTargetSDK <= Build.VERSION_CODES.N_MR1) {
- val extendedBySplitPermsAndGroup = mutableListOf<String>()
-
- for (splitPerm in extendedBySplitPerms) {
- val groups = appPermissions.filter { splitPerm in it.value }
- if (groups.isEmpty()) {
- continue
- }
-
- val permissionsInGroup = groups.values.first()
- for (permissionInGroup in permissionsInGroup) {
- extendedBySplitPermsAndGroup.add(permissionInGroup)
- }
- }
-
- return extendedBySplitPermsAndGroup
- } else {
- return extendedBySplitPerms
- }
- }
-
- private fun isPermissionGrantableAndNotFixed(perm: String, group: LightAppPermGroup): Boolean {
- // 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 (perm in group.packageInfo.requestedPermissions && perm !in group.permissions) {
- reportRequestResult(perm,
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_RESTRICTED_PERMISSION)
- return false
- }
-
- if (HEALTH_PERMISSION_GROUP == group.permGroupName) {
- return !(group.permissions[perm]?.isUserFixed ?: true)
- }
-
- val subGroup = if (perm in group.backgroundPermNames) {
- group.background
- } else {
- group.foreground
- }
-
- val lightPermission = group.permissions[perm] ?: return false
-
- if (!subGroup.isGrantable) {
- reportRequestResult(perm, PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED)
- // Skip showing groups that we know cannot be granted.
- return false
- } else if (subGroup.isUserFixed) {
- if (perm == ACCESS_COARSE_LOCATION && isLocationAccuracyEnabledForApp(group)) {
- val coarsePerm = group.permissions[perm]
- if (coarsePerm != null && !coarsePerm.isUserFixed) {
- // If the location group is user fixed but ACCESS_COARSE_LOCATION is not, then
- // ACCESS_FINE_LOCATION must be user fixed. In this case ACCESS_COARSE_LOCATION
- // is still grantable.
- return true
- }
- } else if (perm in getPartialStorageGrantPermissionsForGroup(group) &&
- lightPermission.isGrantedIncludingAppOp) {
- // If a partial storage permission is granted as fixed, we should immediately show
- // the photo picker
- return true
- }
- reportRequestResult(perm,
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_USER_FIXED)
- return false
- } else if (subGroup.isPolicyFixed && !subGroup.isGranted || lightPermission.isPolicyFixed) {
- reportRequestResult(perm,
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_POLICY_FIXED)
- return false
- }
-
- return true
- }
-
- private fun getGroupState(
- perm: String,
- group: LightAppPermGroup,
- groupRequestedPermissions: List<String>
- ): Int {
- val policyState = getStateFromPolicy(perm, group)
- if (policyState != STATE_UNKNOWN) {
- return policyState
- }
-
- if (perm == POST_NOTIFICATIONS &&
- packageInfo.targetSdkVersion <= Build.VERSION_CODES.S_V2 &&
- group.foreground.isUserSet) {
- return STATE_SKIPPED
- } else if (perm == READ_MEDIA_VISUAL_USER_SELECTED) {
- val partialPerms = getPartialStorageGrantPermissionsForGroup(group)
- val otherRequestedPerms = unfilteredAffectedPermissions.filter { otherPerm ->
- otherPerm in group.permissions && otherPerm !in partialPerms
- }
- if (otherRequestedPerms.isEmpty()) {
- // If the app requested USER_SELECTED while not supporting the photo picker, or if
- // the app explicitly requested only USER_SELECTED and/or ACCESS_MEDIA_LOCATION,
- // then skip the request
- return STATE_SKIPPED
- }
- }
-
- val isBackground = perm in group.backgroundPermNames
-
- val hasForegroundRequest = groupRequestedPermissions.any {
- it !in group.backgroundPermNames
- }
-
- // Do not attempt to grant background access if foreground access is not either already
- // granted or requested
- if (isBackground && !group.foreground.isGrantedExcludingRWROrAllRWR &&
- !hasForegroundRequest) {
- Log.w(LOG_TAG, "Cannot grant $perm as the matching foreground permission is not " +
- "already granted.")
- val affectedPermissions = groupRequestedPermissions.filter {
- it in group.backgroundPermNames
- }
- reportRequestResult(affectedPermissions,
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED)
- return STATE_SKIPPED
- }
-
- if ((isBackground && group.background.isGrantedExcludingRWROrAllRWR ||
- !isBackground && group.foreground.isGrantedExcludingRWROrAllRWR) &&
- canAutoGrantWholeGroup(group)) {
- if (group.permissions[perm]?.isGrantedIncludingAppOp == false) {
- if (isBackground) {
- grantBackgroundRuntimePermissions(app, group, listOf(perm))
- } else {
- grantForegroundRuntimePermissions(app, group, listOf(perm), group.isOneTime)
- }
- KotlinUtils.setGroupFlags(app, group, FLAG_PERMISSION_USER_SET to false,
- FLAG_PERMISSION_USER_FIXED to false, filterPermissions = listOf(perm))
- reportRequestResult(perm,
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_GRANTED)
- }
-
- return if (storedState == null) {
- STATE_SKIPPED
- } else {
- STATE_ALLOWED
- }
- }
- return STATE_UNKNOWN
- }
-
- /**
- * Determines if remaining permissions in the group can be auto granted based on
- * granted permissions in the group.
- */
- private fun canAutoGrantWholeGroup(group: LightAppPermGroup): Boolean {
- // If FINE location is not granted, do not grant it automatically when COARSE
- // location is already granted.
- if (group.permGroupName == LOCATION && isLocationAccuracyEnabledForApp(group) &&
- group.allPermissions[ACCESS_FINE_LOCATION]?.isGrantedIncludingAppOp == false) {
- return false
- }
- // If READ_MEDIA_VISUAL_USER_SELECTED is the only permission in the group that is granted,
- // do not grant.
- if (isPartialStorageGrant(group) || HEALTH_PERMISSION_GROUP == group.permGroupName) {
- return false
- }
- return true
- }
-
- /**
- * A partial storage grant happens when:
- * An app which doesn't support the photo picker has READ_MEDIA_VISUAL_USER_SELECTED granted, or
- * An app which does support the photo picker has READ_MEDIA_VISUAL_USER_SELECTED and/or
- * ACCESS_MEDIA_LOCATION granted
- */
- private fun isPartialStorageGrant(group: LightAppPermGroup): Boolean {
- if (!isPhotoPickerPromptSupported() || group.permGroupName != READ_MEDIA_VISUAL) {
- return false
- }
-
- val partialPerms = getPartialStorageGrantPermissionsForGroup(group)
- return group.isGranted && group.permissions.values.all {
- it.name in partialPerms || (it.name !in partialPerms && !it.isGrantedIncludingAppOp)
- }
- }
-
- private fun getStateFromPolicy(perm: String, group: LightAppPermGroup): Int {
- val isBackground = perm in group.backgroundPermNames
- var skipGroup = false
- var state = STATE_UNKNOWN
- when (permissionPolicy) {
- DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT -> {
- if (AdminRestrictedPermissionsUtils.mayAdminGrantPermission(
- app, perm, user.identifier)) {
- if (isBackground) {
- grantBackgroundRuntimePermissions(app, group, listOf(perm))
- } else {
- grantForegroundRuntimePermissions(app, group, listOf(perm))
- }
- KotlinUtils.setGroupFlags(app, group, FLAG_PERMISSION_POLICY_FIXED to true,
- FLAG_PERMISSION_USER_SET to false, FLAG_PERMISSION_USER_FIXED to false,
- filterPermissions = listOf(perm))
- state = STATE_ALLOWED
- skipGroup = true
-
- getAutoGrantNotifier().onPermissionAutoGranted(perm)
- reportRequestResult(perm,
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_GRANTED)
- }
- }
-
- DevicePolicyManager.PERMISSION_POLICY_AUTO_DENY -> {
- if (group.permissions[perm]?.isPolicyFixed == false) {
- KotlinUtils.setGroupFlags(app, group, FLAG_PERMISSION_POLICY_FIXED to true,
- FLAG_PERMISSION_USER_SET to false, FLAG_PERMISSION_USER_FIXED to false,
- filterPermissions = listOf(perm))
- }
- state = STATE_DENIED
- skipGroup = true
-
- reportRequestResult(perm,
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_DENIED)
- }
- }
- if (skipGroup && storedState == null) {
- return STATE_SKIPPED
- }
- return state
- }
-
- /**
- * Upon the user clicking a button, grant permissions, if applicable.
- *
- * @param groupName The name of the permission group which was changed
- * @param affectedForegroundPermissions The name of the foreground permission which was changed
- * @param result The choice the user made regarding the group.
- */
- open fun onPermissionGrantResult(
- groupName: String?,
- affectedForegroundPermissions: List<String>?,
- result: Int
- ) {
- onPermissionGrantResult(groupName, affectedForegroundPermissions, result, false)
- }
-
- private fun onPermissionGrantResult(
- groupName: String?,
- affectedForegroundPermissions: List<String>?,
- result: Int,
- alreadyRequestedStorageGroupsIfNeeded: Boolean
- ) {
- if (groupName == null) {
- return
- }
-
- // If this is a legacy app, and a storage group is requested: request all storage groups
- if (!alreadyRequestedStorageGroupsIfNeeded &&
- groupName in PermissionMapping.STORAGE_SUPERGROUP_PERMISSIONS &&
- packageInfo.targetSdkVersion <= Build.VERSION_CODES.S_V2) {
- for (storageGroupName in PermissionMapping.STORAGE_SUPERGROUP_PERMISSIONS) {
- val groupPerms = appPermGroupLiveDatas[storageGroupName]
- ?.value?.allPermissions?.keys?.toList()
- onPermissionGrantResult(storageGroupName, groupPerms, result, true)
- }
- return
- }
-
- val foregroundGroupState = groupStates[groupName to false]
- val backgroundGroupState = groupStates[groupName to true]
- when (result) {
- CANCELED -> {
- if (foregroundGroupState != null) {
- reportRequestResult(foregroundGroupState.affectedPermissions,
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_IGNORED)
- foregroundGroupState.state = STATE_SKIPPED
- }
- if (backgroundGroupState != null) {
- reportRequestResult(backgroundGroupState.affectedPermissions,
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_IGNORED)
- backgroundGroupState.state = STATE_SKIPPED
- }
- requestInfosLiveData.update()
- return
- }
- GRANTED_ALWAYS -> {
- if (foregroundGroupState != null) {
- onPermissionGrantResultSingleState(foregroundGroupState,
- affectedForegroundPermissions, granted = true, isOneTime = false,
- doNotAskAgain = false)
- }
- if (backgroundGroupState != null) {
- onPermissionGrantResultSingleState(backgroundGroupState,
- affectedForegroundPermissions, granted = true, isOneTime = false,
- doNotAskAgain = false)
- }
- }
- GRANTED_FOREGROUND_ONLY -> {
- if (foregroundGroupState != null) {
- onPermissionGrantResultSingleState(foregroundGroupState,
- affectedForegroundPermissions, granted = true, isOneTime = false,
- doNotAskAgain = false)
- }
- if (backgroundGroupState != null) {
- onPermissionGrantResultSingleState(backgroundGroupState,
- affectedForegroundPermissions, granted = false, isOneTime = false,
- doNotAskAgain = false)
- }
- }
- GRANTED_ONE_TIME -> {
- if (foregroundGroupState != null) {
- onPermissionGrantResultSingleState(foregroundGroupState,
- affectedForegroundPermissions, granted = true, isOneTime = true,
- doNotAskAgain = false)
- }
- if (backgroundGroupState != null) {
- onPermissionGrantResultSingleState(backgroundGroupState,
- affectedForegroundPermissions, granted = false, isOneTime = true,
- doNotAskAgain = false)
- }
- }
- GRANTED_USER_SELECTED, DENIED_MORE -> {
- if (foregroundGroupState != null) {
- grantUserSelectedVisualGroupPermissions(foregroundGroupState)
- }
- }
- DENIED -> {
- if (foregroundGroupState != null) {
- onPermissionGrantResultSingleState(foregroundGroupState,
- affectedForegroundPermissions, granted = false, isOneTime = false,
- doNotAskAgain = false)
- }
- if (backgroundGroupState != null) {
- onPermissionGrantResultSingleState(backgroundGroupState,
- affectedForegroundPermissions, granted = false, isOneTime = false,
- doNotAskAgain = false)
- }
- }
- DENIED_DO_NOT_ASK_AGAIN -> {
- if (foregroundGroupState != null) {
- onPermissionGrantResultSingleState(foregroundGroupState,
- affectedForegroundPermissions, granted = false, isOneTime = false,
- doNotAskAgain = true)
- }
- if (backgroundGroupState != null) {
- onPermissionGrantResultSingleState(backgroundGroupState,
- affectedForegroundPermissions, granted = false, isOneTime = false,
- doNotAskAgain = true)
- }
- }
- }
- }
-
- private fun grantUserSelectedVisualGroupPermissions(groupState: GroupState) {
- val userSelectedPerm =
- groupState.group.permissions[READ_MEDIA_VISUAL_USER_SELECTED] ?: return
- if (userSelectedPerm.isImplicit) {
- val nonSelectedPerms = groupState.group.permissions.keys
- .filter { it != READ_MEDIA_VISUAL_USER_SELECTED }
- // If the permission is implicit, grant USER_SELECTED as user set, and all other
- // permissions as one time, and without app ops.
- grantForegroundRuntimePermissions(app, groupState.group,
- listOf(READ_MEDIA_VISUAL_USER_SELECTED))
- grantForegroundRuntimePermissions(app, groupState.group,
- nonSelectedPerms, isOneTime = true, userFixed = false, withoutAppOps = true)
- val appPermGroup = AppPermissionGroup.create(app, packageName,
- groupState.group.permGroupName, groupState.group.userHandle, false)
- appPermGroup.setSelfRevoked()
- appPermGroup.persistChanges(false, null, nonSelectedPerms.toSet())
- } else {
- val partialPerms = getPartialStorageGrantPermissionsForGroup(groupState.group).filter {
- it in groupState.affectedPermissions
- }
- val nonSelectedPerms = groupState.affectedPermissions.filter { it !in partialPerms }
- val setUserFixed = userSelectedPerm.isUserFixed || userSelectedPerm.isUserSet
- grantForegroundRuntimePermissions(app, groupState.group,
- partialPerms.toList(), userFixed = setUserFixed)
- revokeForegroundRuntimePermissions(app, groupState.group,
- userFixed = setUserFixed, oneTime = false, filterPermissions = nonSelectedPerms)
- }
- groupState.state = STATE_ALLOWED
- reportButtonClickResult(groupState, true,
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__PHOTOS_SELECTED)
- }
-
- @SuppressLint("NewApi")
- private fun onPermissionGrantResultSingleState(
- groupState: GroupState,
- affectedForegroundPermissions: List<String>?,
- granted: Boolean,
- isOneTime: Boolean,
- doNotAskAgain: Boolean
- ) {
- if (groupState.state != STATE_UNKNOWN) {
- // We already dealt with this group, don't re-grant/re-revoke
- return
- }
- val result: Int
- if (granted) {
- result = if (isOneTime) {
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED_ONE_TIME
- } else {
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED
- }
- if (groupState.isBackground) {
- grantBackgroundRuntimePermissions(app, groupState.group,
- groupState.affectedPermissions)
- } else {
- if (affectedForegroundPermissions == null) {
- grantForegroundRuntimePermissions(app, groupState.group,
- groupState.affectedPermissions, isOneTime)
- // This prevents weird flag state when app targetSDK switches from S+ to R-
- if (groupState.affectedPermissions.contains(ACCESS_FINE_LOCATION)) {
- KotlinUtils.setFlagsWhenLocationAccuracyChanged(
- app, groupState.group, true)
- }
- } else {
- val newGroup = grantForegroundRuntimePermissions(app,
- groupState.group, affectedForegroundPermissions, isOneTime)
- if (!isOneTime || newGroup.isOneTime) {
- KotlinUtils.setFlagsWhenLocationAccuracyChanged(app, newGroup,
- affectedForegroundPermissions.contains(ACCESS_FINE_LOCATION))
- }
- }
- }
- groupState.state = STATE_ALLOWED
- } else {
- if (groupState.isBackground) {
- revokeBackgroundRuntimePermissions(app, groupState.group,
- userFixed = doNotAskAgain, filterPermissions = groupState.affectedPermissions)
- } else {
- if (affectedForegroundPermissions == null ||
- affectedForegroundPermissions.contains(ACCESS_COARSE_LOCATION)) {
- revokeForegroundRuntimePermissions(app, groupState.group,
- userFixed = doNotAskAgain,
- filterPermissions = groupState.affectedPermissions, oneTime = isOneTime)
- } else {
- revokeForegroundRuntimePermissions(app, groupState.group,
- userFixed = doNotAskAgain,
- filterPermissions = affectedForegroundPermissions, oneTime = isOneTime)
- }
- }
- result = if (doNotAskAgain) {
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_WITH_PREJUDICE
- } else {
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED
- }
- groupState.state = STATE_DENIED
- }
- reportButtonClickResult(groupState, granted, result)
- }
-
- private fun reportButtonClickResult(groupState: GroupState, granted: Boolean, result: Int) {
- reportRequestResult(groupState.affectedPermissions, result)
- // group state has changed, reload liveData
- requestInfosLiveData.update()
- PermissionDecisionStorageImpl.recordPermissionDecision(app.applicationContext,
- packageName, groupState.group.permGroupName, granted)
- PermissionChangeStorageImpl.recordPermissionChange(packageName)
- if (granted) {
- startDrivingDecisionReminderServiceIfNecessary(groupState.group.permGroupName)
- }
- }
-
- /**
- * When distraction optimization is required (the vehicle is in motion), the user may want to
- * review their permission grants when they are less distracted.
- */
- private fun startDrivingDecisionReminderServiceIfNecessary(permGroupName: String) {
- if (!DeviceUtils.isAuto(app.applicationContext)) {
- return
- }
- DrivingDecisionReminderService.startServiceIfCurrentlyRestricted(
- Utils.getUserContext(app, user), packageName, permGroupName)
- }
-
- private fun getGroupWithPerm(
- perm: String,
- groups: List<LightAppPermGroup>
- ): LightAppPermGroup? {
- val groupsWithPerm = groups.filter { perm in it.permissions }
- if (groupsWithPerm.isEmpty()) {
- return null
- }
- return groupsWithPerm.first()
- }
-
- /**
- * An internal class which represents the state of a current AppPermissionGroup grant request.
- */
- internal class GroupState(
- internal val group: LightAppPermGroup,
- internal val isBackground: Boolean,
- internal val affectedPermissions: MutableList<String> = mutableListOf(),
- internal var state: Int = STATE_UNKNOWN
- ) {
- override fun toString(): String {
- val stateStr: String = when (state) {
- STATE_UNKNOWN -> "unknown"
- STATE_ALLOWED -> "granted"
- STATE_DENIED -> "denied"
- else -> "skipped"
- }
- return "${group.permGroupName} $isBackground $stateStr $affectedPermissions"
- }
- }
-
- private fun reportRequestResult(permissions: List<String>, result: Int) {
- for (perm in permissions) {
- reportRequestResult(perm, result)
- }
- }
-
- /**
- * 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 fun reportRequestResult(permission: String, result: Int) {
- val isImplicit = permission !in requestedPermissions
- val isPermissionRationaleShown = shouldShowPermissionRationale(
- safetyLabelInfoLiveData?.value?.safetyLabel,
- PermissionMapping.getGroupOfPlatformPermission(permission))
-
- Log.i(LOG_TAG, "Permission grant result requestId=$sessionId " +
- "callingUid=${packageInfo.uid} callingPackage=$packageName permission=$permission " +
- "isImplicit=$isImplicit result=$result " +
- "isPermissionRationaleShown=$isPermissionRationaleShown")
-
- PermissionControllerStatsLog.write(
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED, sessionId,
- packageInfo.uid, packageName, permission, isImplicit, result,
- isPermissionRationaleShown)
- }
-
- /**
- * Save the group states of the view model, to allow for state restoration after lifecycle
- * events
- *
- * @param outState The bundle in which to store state
- */
- open fun saveInstanceState(outState: Bundle) {
- for ((groupKey, groupState) in groupStates) {
- val (groupName, isBackground) = groupKey
- outState.putInt(getInstanceStateKey(groupName, isBackground), groupState.state)
- }
- }
-
- /**
- * Determine if the activity should return permission state to the caller
- *
- * @return Whether or not state should be returned. False only if the package is pre-M, true
- * otherwise.
- */
- open fun shouldReturnPermissionState(): Boolean {
- return if (packageInfoLiveData.value != null) {
- packageInfoLiveData.value!!.targetSdkVersion >= Build.VERSION_CODES.M
- } else {
- // Should not be reached, as this method shouldn't be called before data is passed to
- // the activity for the first time
- try {
- Utils.getUserContext(app, user).packageManager
- .getApplicationInfo(packageName, 0).targetSdkVersion >= Build.VERSION_CODES.M
- } catch (e: PackageManager.NameNotFoundException) {
- true
- }
- }
- }
-
- open fun handleHealthConnectPermissions(activity: Activity) {
- if (activityResultCallback == null) {
- activityResultCallback = Consumer {
- permGroupsToSkip.add(HEALTH_PERMISSION_GROUP)
- requestInfosLiveData.update()
- }
- val healthPermissions = unfilteredAffectedPermissions.filter { permission ->
- isHealthPermission(activity, permission)
- }.toTypedArray()
- val intent: Intent = Intent(ACTION_REQUEST_HEALTH_PERMISSIONS)
- .putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
- .putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES, healthPermissions)
- .putExtra(Intent.EXTRA_USER, Process.myUserHandle())
- .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
- activity.startActivityForResult(intent, APP_PERMISSION_REQUEST_CODE)
- }
- }
-
- /**
- * Send the user directly to the AppPermissionFragment. Used for R+ apps.
- *
- * @param activity The current activity
- * @param groupName The name of the permission group whose fragment should be opened
- */
- open fun sendDirectlyToSettings(activity: Activity, groupName: String) {
- if (activityResultCallback == null) {
- activityResultCallback = Consumer { data ->
- if (data?.getStringExtra(EXTRA_RESULT_PERMISSION_INTERACTED) == null) {
- // User didn't interact, count against rate limit
- val group = groupStates[groupName to false]?.group
- ?: groupStates[groupName to true]?.group ?: return@Consumer
- if (group.background.isUserSet) {
- KotlinUtils.setGroupFlags(app, group, FLAG_PERMISSION_USER_FIXED to true,
- filterPermissions = group.backgroundPermNames)
- } else {
- KotlinUtils.setGroupFlags(app, group, FLAG_PERMISSION_USER_SET to true,
- filterPermissions = group.backgroundPermNames)
- }
- }
-
- permGroupsToSkip.add(groupName)
- // Update our liveData now that there is a new skipped group
- requestInfosLiveData.update()
- }
- startAppPermissionFragment(activity, groupName)
- }
- }
-
- open fun openPhotoPicker(activity: Activity, result: Int) {
- if (activityResultCallback != null) {
- return
- }
- if (groupStates[READ_MEDIA_VISUAL to false]?.affectedPermissions == null) {
- return
- }
- activityResultCallback = Consumer { data ->
- val anySelected = data?.getBooleanExtra(INTENT_PHOTOS_SELECTED, true) == true
- if (anySelected) {
- onPermissionGrantResult(READ_MEDIA_VISUAL, null, result)
- } else {
- onPermissionGrantResult(READ_MEDIA_VISUAL, null, CANCELED)
- }
- requestInfosLiveData.update()
- }
- // A clone profile doesn't have a MediaProvider. If this user is a clone profile, open
- // the photo picker in the parent profile
- val userManager = activity.getSystemService(UserManager::class.java)!!
- val user = if (userManager.isCloneProfile) {
- userManager.getProfileParent(Process.myUserHandle()) ?: Process.myUserHandle()
- } else {
- Process.myUserHandle()
- }
- activity.startActivityForResultAsUser(Intent(MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP)
- .putExtra(Intent.EXTRA_UID, packageInfo.uid)
- .setType(KotlinUtils.getMimeTypeForPermissions(unfilteredAffectedPermissions)),
- PHOTO_PICKER_REQUEST_CODE, user)
- }
-
- /**
- * Send the user to the AppPermissionFragment from a link. Used for Q- apps
- *
- * @param activity The current activity
- * @param groupName The name of the permission group whose fragment should be opened
- */
- open fun sendToSettingsFromLink(activity: Activity, groupName: String) {
- startAppPermissionFragment(activity, groupName)
- activityResultCallback = Consumer { data ->
- val returnGroupName = data?.getStringExtra(EXTRA_RESULT_PERMISSION_INTERACTED)
- if (returnGroupName != null) {
- permGroupsToSkip.add(returnGroupName)
- val result = data.getIntExtra(EXTRA_RESULT_PERMISSION_RESULT, -1)
- logSettingsInteraction(returnGroupName, result)
- requestInfosLiveData.update()
- }
- }
- }
-
- /**
- * Shows the Permission Rationale Dialog. For use with U+ only, otherwise no-op.
- *
- * @param activity The current activity
- * @param groupName The name of the permission group whose fragment should be opened
- */
- open fun showPermissionRationaleActivity(activity: Activity, groupName: String) {
- if (!SdkLevel.isAtLeastU()) {
- return
- }
-
- val intent = Intent(activity, PermissionRationaleActivity::class.java).apply {
- putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
- putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, groupName)
- putExtra(Constants.EXTRA_SESSION_ID, sessionId)
- }
- activityResultCallback = Consumer { data ->
- val returnGroupName = data?.getStringExtra(EXTRA_RESULT_PERMISSION_INTERACTED)
- if (returnGroupName != null) {
- permGroupsToSkip.add(returnGroupName)
- val result = data.getIntExtra(EXTRA_RESULT_PERMISSION_RESULT, CANCELED)
- logSettingsInteraction(returnGroupName, result)
- requestInfosLiveData.update()
- }
- }
- activity.startActivityForResult(intent, APP_PERMISSION_REQUEST_CODE)
- }
-
- private fun startAppPermissionFragment(activity: Activity, groupName: String) {
- val intent = Intent(Intent.ACTION_MANAGE_APP_PERMISSION)
- .putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
- .putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, groupName)
- .putExtra(Intent.EXTRA_USER, user)
- .putExtra(ManagePermissionsActivity.EXTRA_CALLER_NAME,
- GrantPermissionsActivity::class.java.name)
- .putExtra(Constants.EXTRA_SESSION_ID, sessionId)
- .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
- activity.startActivityForResult(intent, APP_PERMISSION_REQUEST_CODE)
- }
-
- private fun getInstanceStateKey(groupName: String, isBackground: Boolean): String {
- return "${this::class.java.name}_${groupName}_$isBackground"
- }
-
- private fun logSettingsInteraction(groupName: String, result: Int) {
- val foregroundGroupState = groupStates[groupName to false]
- val backgroundGroupState = groupStates[groupName to true]
- val deniedPrejudiceInSettings =
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_WITH_PREJUDICE_IN_SETTINGS
- when (result) {
- GRANTED_ALWAYS -> {
- if (foregroundGroupState != null) {
- reportRequestResult(foregroundGroupState.affectedPermissions,
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED_IN_SETTINGS)
- }
- if (backgroundGroupState != null) {
- reportRequestResult(backgroundGroupState.affectedPermissions,
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED_IN_SETTINGS)
- }
- }
- GRANTED_FOREGROUND_ONLY -> {
- if (foregroundGroupState != null) {
- reportRequestResult(foregroundGroupState.affectedPermissions,
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED_IN_SETTINGS)
- }
- if (backgroundGroupState != null) {
- reportRequestResult(backgroundGroupState.affectedPermissions,
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_IN_SETTINGS)
- }
- }
- DENIED -> {
- if (foregroundGroupState != null) {
- reportRequestResult(foregroundGroupState.affectedPermissions,
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_IN_SETTINGS)
- }
- if (backgroundGroupState != null) {
- reportRequestResult(backgroundGroupState.affectedPermissions,
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_IN_SETTINGS)
- }
- }
- DENIED_DO_NOT_ASK_AGAIN -> {
- if (foregroundGroupState != null) {
- reportRequestResult(foregroundGroupState.affectedPermissions,
- deniedPrejudiceInSettings)
- }
- if (backgroundGroupState != null) {
- reportRequestResult(backgroundGroupState.affectedPermissions,
- deniedPrejudiceInSettings)
- }
- }
- }
- }
-
- private fun isLocationAccuracyEnabledForApp(group: LightAppPermGroup): Boolean {
- return isLocationAccuracyEnabled() &&
- group.packageInfo.targetSdkVersion >= Build.VERSION_CODES.S
- }
-
- /**
- * Log all permission groups which were requested
- */
- open fun logRequestedPermissionGroups() {
- if (groupStates.isEmpty()) {
- return
- }
- val groups = groupStates.map { it.value.group }
- SafetyNetLogger.logPermissionsRequested(packageName, packageInfo.uid, groups)
- }
-
- /**
- * Log information about the buttons which were shown and clicked by the user.
- *
- * @param groupName The name of the permission group which was interacted with
- * @param selectedPrecision Selected precision of the location permission - bit flags indicate
- * which locations were chosen
- * @param clickedButton The button that was clicked by the user
- * @param presentedButtons All buttons which were shown to the user
- */
- open fun logClickedButtons(
- groupName: String?,
- selectedPrecision: Int,
- clickedButton: Int,
- presentedButtons: Int,
- isPermissionRationaleShown: Boolean
- ) {
- if (groupName == null) {
- return
- }
-
- if (!requestInfosLiveData.isInitialized || !packageInfoLiveData.isInitialized) {
- Log.wtf(LOG_TAG, "Logged buttons presented and clicked permissionGroupName=" +
- "$groupName package=$packageName presentedButtons=$presentedButtons " +
- "clickedButton=$clickedButton isPermissionRationaleShown=" +
- "$isPermissionRationaleShown sessionId=$sessionId, but requests were not yet" +
- "initialized", IllegalStateException())
- return
- }
-
- PermissionControllerStatsLog.write(GRANT_PERMISSIONS_ACTIVITY_BUTTON_ACTIONS,
- groupName, packageInfo.uid, packageName, presentedButtons, clickedButton, sessionId,
- packageInfo.targetSdkVersion, selectedPrecision,
- isPermissionRationaleShown)
- Log.i(LOG_TAG, "Logged buttons presented and clicked permissionGroupName=" +
- "$groupName uid=${packageInfo.uid} selectedPrecision=$selectedPrecision " +
- "package=$packageName presentedButtons=$presentedButtons " +
- "clickedButton=$clickedButton isPermissionRationaleShown=" +
- "$isPermissionRationaleShown sessionId=$sessionId " +
- "targetSdk=${packageInfo.targetSdkVersion}")
- }
-
- /**
- * Use the autoGrantNotifier to notify of auto-granted permissions.
- */
- open fun autoGrantNotify() {
- autoGrantNotifier?.notifyOfAutoGrantPermissions(true)
- }
-
- companion object {
- const val APP_PERMISSION_REQUEST_CODE = 1
- const val PHOTO_PICKER_REQUEST_CODE = 2
- private const val STATE_UNKNOWN = 0
- private const val STATE_ALLOWED = 1
- private const val STATE_DENIED = 2
- private const val STATE_SKIPPED = 3
- private const val STATE_ALREADY_ALLOWED = 4
-
- /**
- * An enum that represents the type of message which should be shown- foreground,
- * background, upgrade, or no message.
- */
- enum class RequestMessage {
- FG_MESSAGE,
- BG_MESSAGE,
- UPGRADE_MESSAGE,
- NO_MESSAGE,
- FG_FINE_LOCATION_MESSAGE,
- FG_COARSE_LOCATION_MESSAGE,
- STORAGE_SUPERGROUP_MESSAGE_Q_TO_S,
- STORAGE_SUPERGROUP_MESSAGE_PRE_Q,
- MORE_PHOTOS_MESSAGE,
- }
-
- /**
- * Make a copy of a list of permissions that is filtered to remove permissions blocked
- * according to the target SDK level.
- */
- fun getSanitizedPermissionsList(
- permissions: Array<String?>,
- targetSdkVersion: Int
- ): List<String> {
- return permissions
- .filter { !it.isNullOrEmpty() }
- // POST_NOTIFICATIONS is actively disallowed to be declared by apps below T.
- // Others we don't care as much if they were declared but not used.
- .filter { targetSdkVersion >= Build.VERSION_CODES.TIRAMISU ||
- it != POST_NOTIFICATIONS }
- .filterIsInstance<String>()
- }
- }
-}
-
-/**
- * Factory for an AppPermissionViewModel
- *
- * @param app The current application
- * @param packageName The name of the package this ViewModel represents
- */
-class GrantPermissionsViewModelFactory(
- private val app: Application,
- private val packageName: String,
- private val requestedPermissions: List<String>,
- private val sessionId: Long,
- private val savedState: Bundle?
-) : ViewModelProvider.Factory {
- override fun <T : ViewModel> create(modelClass: Class<T>): T {
- @Suppress("UNCHECKED_CAST")
- return GrantPermissionsViewModel(app, packageName, requestedPermissions,
- sessionId, savedState) as T
- }
-}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/NewGrantPermissionsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/NewGrantPermissionsViewModel.kt
index 50f22b7df..8f2169dfe 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/NewGrantPermissionsViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/NewGrantPermissionsViewModel.kt
@@ -78,32 +78,12 @@ import com.android.permissioncontroller.permission.data.v34.SafetyLabelInfoLiveD
import com.android.permissioncontroller.permission.model.AppPermissionGroup
import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup
import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo
+import com.android.permissioncontroller.permission.model.livedatatypes.LightPermGroupInfo
import com.android.permissioncontroller.permission.service.PermissionChangeStorageImpl
import com.android.permissioncontroller.permission.service.v33.PermissionDecisionStorageImpl
import com.android.permissioncontroller.permission.ui.AutoGrantPermissionsNotifier
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_ALL_BUTTON
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_BUTTON
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_FOREGROUND_BUTTON
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_ONE_TIME_BUTTON
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_SELECTED_BUTTON
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.COARSE_RADIO_BUTTON
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DENY_AND_DONT_ASK_AGAIN_BUTTON
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DENY_BUTTON
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DIALOG_WITH_BOTH_LOCATIONS
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DIALOG_WITH_COARSE_LOCATION_ONLY
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DIALOG_WITH_FINE_LOCATION_ONLY
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DONT_ALLOW_MORE_SELECTED_BUTTON
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.FINE_RADIO_BUTTON
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.INTENT_PHOTOS_SELECTED
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.LINK_TO_PERMISSION_RATIONALE
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.LOCATION_ACCURACY_LAYOUT
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NEXT_BUTTON
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NEXT_LOCATION_DIALOG
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_BUTTON
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_OT_BUTTON
import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.CANCELED
import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED
import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED_DO_NOT_ASK_AGAIN
@@ -115,7 +95,6 @@ import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandle
import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity
import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_INTERACTED
import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_RESULT
-import com.android.permissioncontroller.permission.ui.model.GrantPermissionsViewModel.Companion.RequestMessage
import com.android.permissioncontroller.permission.ui.model.grantPermissions.BackgroundGrantBehavior
import com.android.permissioncontroller.permission.ui.model.grantPermissions.BasicGrantBehavior
import com.android.permissioncontroller.permission.ui.model.grantPermissions.GrantBehavior
@@ -157,8 +136,8 @@ class NewGrantPermissionsViewModel(
private val systemRequestedPermissions: List<String>,
private val sessionId: Long,
private val storedState: Bundle?
-) : GrantPermissionsViewModel(app, packageName, requestedPermissions, sessionId, storedState) {
- private val LOG_TAG = GrantPermissionsViewModel::class.java.simpleName
+) : ViewModel() {
+ private val LOG_TAG = NewGrantPermissionsViewModel::class.java.simpleName
private val user = Process.myUserHandle()
private val packageInfoLiveData = LightPackageInfoLiveData[packageName, user]
private val safetyLabelInfoLiveData =
@@ -187,7 +166,7 @@ class NewGrantPermissionsViewModel(
private var appPermGroupLiveDatas = mutableMapOf<String, LightAppPermGroupLiveData>()
- override var activityResultCallback: Consumer<Intent>? = null
+ var activityResultCallback: Consumer<Intent>? = null
/**
* An internal class which represents the state of a current AppPermissionGroup grant request.
@@ -216,14 +195,19 @@ class NewGrantPermissionsViewModel(
}
}
- /**
- * A LiveData which holds a list of the currently pending RequestInfos
- * TODO 284183336: Once the old ViewModel is gone, remove RequestInfo, and replace it with
- * just a prompt + denyButton
- */
- override val requestInfosLiveData = object :
+ data class RequestInfo(
+ val groupInfo: LightPermGroupInfo,
+ val prompt: Prompt,
+ val deny: DenyButton,
+ val showRationale: Boolean,
+ val deviceId: Int = ContextCompat.DEVICE_ID_DEFAULT
+ ) {
+ val groupName = groupInfo.name
+ }
+
+ val requestInfosLiveData = object :
SmartUpdateMediatorLiveData<List<RequestInfo>>() {
- private val LOG_TAG = GrantPermissionsViewModel::class.java.simpleName
+ private val LOG_TAG = NewGrantPermissionsViewModel::class.java.simpleName
private val packagePermissionsLiveData = PackagePermissionsLiveData[packageName, user]
init {
@@ -356,11 +340,20 @@ class NewGrantPermissionsViewModel(
val denyBehavior = behavior.getDenyButton(groupState.group,
groupState.affectedPermissions, prompt)
- requestInfos.add(convertPromptToRequestInfo(groupState, prompt, denyBehavior))
+ val safetyLabel = safetyLabelInfoLiveData?.value?.safetyLabel
+ val permissionResourceDeviceId = ContextCompat.DEVICE_ID_DEFAULT
+ // TODO(b/298623935) Determine the device ID of the permission resource
+ // More logic is required at that point to properly identify permissions which can be
+ // device-aware (once implemented)
+ // e.g. check VDM for the VirtualDevice's capabilities
+ // e.g. check the permission request for the target device ID.
+ requestInfos.add(RequestInfo(groupState.group.permGroupInfo, prompt, denyBehavior,
+ shouldShowPermissionRationale(safetyLabel, groupState.group.permGroupName),
+ permissionResourceDeviceId))
}
sortPermissionGroups(requestInfos)
- value = if (requestInfos.any { it.sendToSettingsImmediately } &&
+ value = if (requestInfos.any { it.prompt == Prompt.NO_UI_SETTINGS_REDIRECT } &&
requestInfos.size > 1) {
Log.e(LOG_TAG, "For R+ apps, background permissions must be requested " +
"individually")
@@ -371,6 +364,30 @@ class NewGrantPermissionsViewModel(
}
}
+ private fun sortPermissionGroups(
+ requestInfos: MutableList<RequestInfo>
+ ) {
+ requestInfos.sortWith { rhs, lhs ->
+ val rhsHasOneTime = isOneTimePrompt(rhs.prompt)
+ val lhsHasOneTime = isOneTimePrompt(lhs.prompt)
+ if (rhsHasOneTime && !lhsHasOneTime) {
+ -1
+ } else if ((!rhsHasOneTime && lhsHasOneTime) ||
+ Utils.isHealthPermissionGroup(rhs.groupName)
+ ) {
+ 1
+ } else {
+ rhs.groupName.compareTo(lhs.groupName)
+ }
+ }
+ }
+
+ private fun isOneTimePrompt(prompt: Prompt): Boolean {
+ return prompt in setOf(Prompt.ONE_TIME_FG, Prompt.SETTINGS_LINK_WITH_OT,
+ Prompt.LOCATION_TWO_BUTTON_COARSE_HIGHLIGHT, Prompt.LOCATION_TWO_BUTTON_FINE_HIGHLIGHT,
+ Prompt.LOCATION_COARSE_ONLY, Prompt.LOCATION_FINE_UPGRADE)
+ }
+
private fun shouldShowPermissionRationale(
safetyLabel: SafetyLabel?,
permissionGroupName: String?
@@ -569,7 +586,7 @@ class NewGrantPermissionsViewModel(
* @param affectedForegroundPermissions The name of the foreground permission which was changed
* @param result The choice the user made regarding the group.
*/
- override fun onPermissionGrantResult(
+ fun onPermissionGrantResult(
groupName: String?,
affectedForegroundPermissions: List<String>?,
result: Int
@@ -821,7 +838,7 @@ class NewGrantPermissionsViewModel(
*
* @param outState The bundle in which to store state
*/
- override fun saveInstanceState(outState: Bundle) {
+ fun saveInstanceState(outState: Bundle) {
for ((groupName, groupState) in groupStates) {
outState.putInt(groupName, groupState.state)
}
@@ -833,7 +850,7 @@ class NewGrantPermissionsViewModel(
* @return Whether or not state should be returned. False only if the package is pre-M, true
* otherwise.
*/
- override fun shouldReturnPermissionState(): Boolean {
+ fun shouldReturnPermissionState(): Boolean {
return if (packageInfoLiveData.value != null) {
packageInfoLiveData.value!!.targetSdkVersion >= Build.VERSION_CODES.M
} else {
@@ -848,7 +865,7 @@ class NewGrantPermissionsViewModel(
}
}
- override fun handleHealthConnectPermissions(activity: Activity) {
+ fun handleHealthConnectPermissions(activity: Activity) {
if (activityResultCallback == null) {
activityResultCallback = Consumer {
groupStates[HEALTH_PERMISSION_GROUP]?.state = STATE_SKIPPED
@@ -872,7 +889,7 @@ class NewGrantPermissionsViewModel(
* @param activity The current activity
* @param groupName The name of the permission group whose fragment should be opened
*/
- override fun sendDirectlyToSettings(activity: Activity, groupName: String) {
+ fun sendDirectlyToSettings(activity: Activity, groupName: String) {
if (activityResultCallback == null) {
activityResultCallback = Consumer { data ->
if (data?.getStringExtra(EXTRA_RESULT_PERMISSION_INTERACTED) == null) {
@@ -895,7 +912,7 @@ class NewGrantPermissionsViewModel(
}
}
- override fun openPhotoPicker(activity: Activity, result: Int) {
+ fun openPhotoPicker(activity: Activity, result: Int) {
if (activityResultCallback != null) {
return
}
@@ -923,7 +940,7 @@ class NewGrantPermissionsViewModel(
* @param activity The current activity
* @param groupName The name of the permission group whose fragment should be opened
*/
- override fun sendToSettingsFromLink(activity: Activity, groupName: String) {
+ fun sendToSettingsFromLink(activity: Activity, groupName: String) {
startAppPermissionFragment(activity, groupName)
activityResultCallback = Consumer { data ->
val returnGroupName = data?.getStringExtra(EXTRA_RESULT_PERMISSION_INTERACTED)
@@ -942,7 +959,7 @@ class NewGrantPermissionsViewModel(
* @param activity The current activity
* @param groupName The name of the permission group whose fragment should be opened
*/
- override fun showPermissionRationaleActivity(activity: Activity, groupName: String) {
+ fun showPermissionRationaleActivity(activity: Activity, groupName: String) {
if (!SdkLevel.isAtLeastU()) {
return
}
@@ -1027,7 +1044,7 @@ class NewGrantPermissionsViewModel(
/**
* Log all permission groups which were requested
*/
- override fun logRequestedPermissionGroups() {
+ fun logRequestedPermissionGroups() {
if (groupStates.isEmpty()) {
return
}
@@ -1044,7 +1061,7 @@ class NewGrantPermissionsViewModel(
* @param clickedButton The button that was clicked by the user
* @param presentedButtons All buttons which were shown to the user
*/
- override fun logClickedButtons(
+ fun logClickedButtons(
groupName: String?,
selectedPrecision: Int,
clickedButton: Int,
@@ -1079,154 +1096,10 @@ class NewGrantPermissionsViewModel(
/**
* Use the autoGrantNotifier to notify of auto-granted permissions.
*/
- override fun autoGrantNotify() {
+ fun autoGrantNotify() {
autoGrantNotifier?.notifyOfAutoGrantPermissions(true)
}
- // TODO: 284183336, once the old viewModel is gone, this should be moved to the activity
- private fun convertPromptToRequestInfo(
- groupState: GroupState,
- prompt: Prompt,
- deny: DenyButton
- ): RequestInfo {
- val buttons = mutableSetOf<Int>()
- val locationButtons = mutableSetOf<Int>()
- var message = RequestMessage.FG_MESSAGE
- var detailMessage = RequestMessage.NO_MESSAGE
- when (prompt) {
- Prompt.BASIC -> buttons.add(ALLOW_BUTTON)
- Prompt.FG_ONLY -> buttons.add(ALLOW_FOREGROUND_BUTTON)
- Prompt.ONE_TIME_FG ->
- buttons.addAll(listOf(ALLOW_FOREGROUND_BUTTON, ALLOW_ONE_TIME_BUTTON))
- Prompt.SETTINGS_LINK_FOR_BG -> {
- buttons.add(ALLOW_FOREGROUND_BUTTON)
- message = RequestMessage.BG_MESSAGE
- detailMessage = RequestMessage.BG_MESSAGE
- }
- Prompt.SETTINGS_LINK_WITH_OT -> {
- buttons.addAll(listOf(ALLOW_FOREGROUND_BUTTON, ALLOW_ONE_TIME_BUTTON))
- message = RequestMessage.BG_MESSAGE
- detailMessage = RequestMessage.BG_MESSAGE
- }
- Prompt.UPGRADE_SETTINGS_LINK -> {
- message = RequestMessage.UPGRADE_MESSAGE
- detailMessage = RequestMessage.UPGRADE_MESSAGE
- }
- Prompt.OT_UPGRADE_SETTINGS_LINK -> {
- message = RequestMessage.UPGRADE_MESSAGE
- detailMessage = RequestMessage.UPGRADE_MESSAGE
- }
- Prompt.LOCATION_TWO_BUTTON -> {
- buttons.addAll(listOf(ALLOW_FOREGROUND_BUTTON, ALLOW_ONE_TIME_BUTTON))
- locationButtons.addAll(listOf(LOCATION_ACCURACY_LAYOUT,
- DIALOG_WITH_BOTH_LOCATIONS, getSelectedLocation(groupState.group)))
- }
- Prompt.LOCATION_COARSE_ONLY -> {
- buttons.addAll(listOf(ALLOW_FOREGROUND_BUTTON, ALLOW_ONE_TIME_BUTTON))
- locationButtons.addAll(listOf(LOCATION_ACCURACY_LAYOUT,
- DIALOG_WITH_COARSE_LOCATION_ONLY))
- message = RequestMessage.FG_COARSE_LOCATION_MESSAGE
- }
- Prompt.LOCATION_FINE_UPGRADE -> {
- buttons.addAll(listOf(ALLOW_FOREGROUND_BUTTON, ALLOW_ONE_TIME_BUTTON))
- locationButtons.addAll(listOf(LOCATION_ACCURACY_LAYOUT,
- DIALOG_WITH_FINE_LOCATION_ONLY))
- message = RequestMessage.FG_FINE_LOCATION_MESSAGE
- }
- Prompt.SELECT_PHOTOS -> {
- buttons.addAll(listOf(ALLOW_ALL_BUTTON, ALLOW_SELECTED_BUTTON))
- }
- Prompt.SELECT_MORE_PHOTOS -> {
- buttons.addAll(listOf(ALLOW_ALL_BUTTON, ALLOW_SELECTED_BUTTON))
- message = GrantPermissionsViewModel.Companion.RequestMessage.MORE_PHOTOS_MESSAGE
- }
- Prompt.STORAGE_SUPERGROUP_PRE_Q -> {
- buttons.add(ALLOW_BUTTON)
- message = RequestMessage.STORAGE_SUPERGROUP_MESSAGE_PRE_Q
- }
- Prompt.STORAGE_SUPERGROUP_Q_TO_S -> {
- buttons.add(ALLOW_BUTTON)
- message = RequestMessage.STORAGE_SUPERGROUP_MESSAGE_Q_TO_S
- }
- Prompt.NO_UI_SETTINGS_REDIRECT -> {
- return RequestInfo(groupState.group.permGroupInfo, sendToSettingsImmediately = true)
- }
- Prompt.NO_UI_PHOTO_PICKER_REDIRECT -> {
- return RequestInfo(groupState.group.permGroupInfo, openPhotoPicker = true)
- }
- Prompt.NO_UI_HEALTH_REDIRECT -> {
- return RequestInfo(groupState.group.permGroupInfo)
- }
- Prompt.NO_UI_FILTER_THIS_GROUP -> {
- return RequestInfo(groupState.group.permGroupInfo,
- filteredPermissions = groupState.affectedPermissions)
- }
- Prompt.NO_UI_REJECT_ALL_GROUPS, Prompt.NO_UI_REJECT_THIS_GROUP -> {
- /* These have no buttons */
- }
- }
-
- when (deny) {
- DenyButton.DENY -> buttons.add(DENY_BUTTON)
- DenyButton.DENY_DONT_ASK_AGAIN -> buttons.add(DENY_AND_DONT_ASK_AGAIN_BUTTON)
- DenyButton.DONT_SELECT_MORE -> buttons.add(DONT_ALLOW_MORE_SELECTED_BUTTON)
- DenyButton.NO_UPGRADE -> buttons.add(NO_UPGRADE_BUTTON)
- DenyButton.NO_UPGRADE_OT -> buttons.add(NO_UPGRADE_OT_BUTTON)
- DenyButton.NO_UPGRADE_AND_DONT_ASK_AGAIN ->
- buttons.add(NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON)
- DenyButton.NO_UPGRADE_AND_DONT_ASK_AGAIN_OT -> NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON
- DenyButton.NONE -> {}
- }
-
- val safetyLabel = safetyLabelInfoLiveData?.value?.safetyLabel
- if (shouldShowPermissionRationale(safetyLabel, groupState.group.permGroupName)) {
- buttons.add(LINK_TO_PERMISSION_RATIONALE)
- }
- val buttonArray = convertSetToBoolList(buttons, NEXT_BUTTON)
- val locationArray = convertSetToBoolList(locationButtons, NEXT_LOCATION_DIALOG)
-
- val permissionResourceDeviceId = ContextCompat.DEVICE_ID_DEFAULT
- // TODO(b/298623935) Determine the device ID of the permission resource
- // More logic is required at that point to properly identify permissions which can be
- // device-aware (once implemented)
- // e.g. check VDM for the VirtualDevice's capabilities
- // e.g. check the permission request for the target device ID.
-
- return RequestInfo(groupState.group.permGroupInfo, buttonArray, locationArray, message,
- detailMessage, deviceId = permissionResourceDeviceId)
- }
-
- private fun getSelectedLocation(group: LightAppPermGroup): Int {
- // Steps to decide location accuracy default state
- // 1. If none of the FINE and COARSE isSelectedLocationAccuracy
- // flags is set, then use default precision from device config.
- // 2. Otherwise set to whichever isSelectedLocationAccuracy is true.
- val coarseLocationPerm =
- group.allPermissions[ACCESS_COARSE_LOCATION]
- val fineLocationPerm =
- group.allPermissions[ACCESS_FINE_LOCATION]
- return if (coarseLocationPerm?.isSelectedLocationAccuracy == false &&
- fineLocationPerm?.isSelectedLocationAccuracy == false) {
- if (KotlinUtils.getDefaultPrecision()) {
- FINE_RADIO_BUTTON
- } else {
- COARSE_RADIO_BUTTON
- }
- } else if (coarseLocationPerm?.isSelectedLocationAccuracy == true) {
- COARSE_RADIO_BUTTON
- } else {
- FINE_RADIO_BUTTON
- }
- }
-
- private fun convertSetToBoolList(buttonSet: Set<Int>, maxSize: Int): List<Boolean> {
- val buttonArray = MutableList(maxSize) { false }
- for (button in buttonSet) {
- buttonArray[button] = true
- }
- return buttonArray
- }
-
private fun isStateUnknown(state: Int?): Boolean {
return state == null || state == STATE_UNKNOWN || state == STATE_FG_GRANTED_BG_UNKNOWN
}
@@ -1271,7 +1144,9 @@ enum class Prompt {
SETTINGS_LINK_WITH_OT, // Same as above, but with a one time button
UPGRADE_SETTINGS_LINK, // Keep foreground, with link to settings to grant background
OT_UPGRADE_SETTINGS_LINK, // Same as above, but the button is "keep one time"
- LOCATION_TWO_BUTTON, // Choose coarse/fine, foreground/one time/deny
+ LOCATION_TWO_BUTTON_COARSE_HIGHLIGHT, // Choose coarse/fine, foreground/one time/deny, coarse
+ // button highlighted
+ LOCATION_TWO_BUTTON_FINE_HIGHLIGHT, // Same as above, but fine location highlighted
LOCATION_COARSE_ONLY, // Only coarse location, foreground/one time/deny
LOCATION_FINE_UPGRADE, // Upgrade coarse to fine, upgrade to fine/ one time/ keep coarse
SELECT_PHOTOS, // Select photos/allow all photos/deny
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/HealthGrantBehavior.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/HealthGrantBehavior.kt
index 7bc520c22..9ca2f09ec 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/HealthGrantBehavior.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/HealthGrantBehavior.kt
@@ -19,6 +19,7 @@ package com.android.permissioncontroller.permission.ui.model.grantPermissions
import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup
import com.android.permissioncontroller.permission.ui.model.DenyButton
import com.android.permissioncontroller.permission.ui.model.Prompt
+import com.android.permissioncontroller.permission.utils.Utils
/**
* Health permissions always redirect to the health connect UI.
@@ -29,7 +30,11 @@ object HealthGrantBehavior : GrantBehavior() {
requestedPerms: Set<String>,
isSystemTriggeredPrompt: Boolean
): Prompt {
- return Prompt.NO_UI_HEALTH_REDIRECT
+ return if (Utils.isHealthPermissionUiEnabled()) {
+ Prompt.NO_UI_HEALTH_REDIRECT
+ } else {
+ Prompt.NO_UI_REJECT_THIS_GROUP
+ }
}
override fun getDenyButton(
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/LocationGrantBehavior.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/LocationGrantBehavior.kt
index c541c4f4d..f2df4322e 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/LocationGrantBehavior.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/LocationGrantBehavior.kt
@@ -44,8 +44,10 @@ object LocationGrantBehavior : GrantBehavior() {
} else if (requestedPerms.contains(ACCESS_FINE_LOCATION)) {
if (coarseGranted) {
Prompt.LOCATION_FINE_UPGRADE
+ } else if (isFineLocationHighlighted(group)){
+ Prompt.LOCATION_TWO_BUTTON_FINE_HIGHLIGHT
} else {
- Prompt.LOCATION_TWO_BUTTON
+ Prompt.LOCATION_TWO_BUTTON_COARSE_HIGHLIGHT
}
} else if (requestedPerms.contains(ACCESS_COARSE_LOCATION) && !coarseGranted) {
Prompt.LOCATION_COARSE_ONLY
@@ -103,4 +105,21 @@ object LocationGrantBehavior : GrantBehavior() {
return KotlinUtils.isLocationAccuracyEnabled() &&
group.packageInfo.targetSdkVersion >= Build.VERSION_CODES.S
}
+
+ private fun isFineLocationHighlighted(group: LightAppPermGroup): Boolean {
+ // Steps to decide location accuracy default state
+ // 1. If none of the FINE and COARSE isSelectedLocationAccuracy
+ // flags is set, then use default precision from device config.
+ // 2. Otherwise set to whichever isSelectedLocationAccuracy is true.
+ val coarseLocationPerm =
+ group.allPermissions[ACCESS_COARSE_LOCATION]
+ val fineLocationPerm =
+ group.allPermissions[ACCESS_FINE_LOCATION]
+ return if (coarseLocationPerm?.isSelectedLocationAccuracy == false &&
+ fineLocationPerm?.isSelectedLocationAccuracy == false) {
+ return KotlinUtils.getDefaultPrecision()
+ } else {
+ fineLocationPerm?.isSelectedLocationAccuracy == true
+ }
+ }
} \ No newline at end of file
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt b/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt
index ee0606610..6c7966f2b 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt
@@ -166,8 +166,6 @@ object KotlinUtils {
private const val PROPERTY_SAFETY_LABEL_CHANGES_JOB_SERVICE_KILL_SWITCH =
"safety_label_changes_job_service_kill_switch"
- private const val PROPERTY_NEW_GRANT_DIALOG_BACKEND = "new_grand_dialog_backend"
-
/**
* Whether to show Camera and Mic Icons.
*
@@ -320,11 +318,6 @@ object KotlinUtils {
)
}
- fun isNewGrantDialogBackendEnabled(): Boolean {
- return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
- PROPERTY_NEW_GRANT_DIALOG_BACKEND, true)
- }
-
/**
* Given a Map, and a List, determines which elements are in the list, but not the map, and vice
* versa. Used primarily for determining which liveDatas are already being watched, and which