summaryrefslogtreecommitdiff
path: root/PermissionController/src/com
diff options
context:
space:
mode:
authorNate Myren <ntmyren@google.com>2023-10-10 09:47:47 -0700
committerNate Myren <ntmyren@google.com>2023-10-19 20:11:54 +0000
commit5a8be5b16f8f440cfc946bf0d140237d1380689f (patch)
tree8617e069eab2d5cd1fbcd5a977d9b973647214e4 /PermissionController/src/com
parent1bcb4e9f54ddeff87b0f30edbf794438a8846b79 (diff)
downloadPermission-5a8be5b16f8f440cfc946bf0d140237d1380689f.tar.gz
Add "Edit Photos" button in settings
When clicked, this button will open the photo picker, and allow users to change which photos the app in question has access to. This also consolidates the photo picker launching logic. Bug: 303834669 Test: atest PhotoPickerPermissionTest Relnote: add a button that allows users to select photos in settings Change-Id: I8ca75795644915428db8e1b7fda5f15672818720 Merged-In: I8ca75795644915428db8e1b7fda5f15672818720
Diffstat (limited to 'PermissionController/src/com')
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAppPermissionFragment.java2
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java47
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt44
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt21
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/television/AppPermissionFragment.java4
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt25
6 files changed, 82 insertions, 61 deletions
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAppPermissionFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAppPermissionFragment.java
index 2de936469..d3a89c3ed 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAppPermissionFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAppPermissionFragment.java
@@ -418,7 +418,7 @@ public class AutoAppPermissionFragment extends AutoSettingsFrameFragment
// TODO(b/229024576): This code is duplicated, refactor ConfirmDialog for easier
// NFF sharing
boolean isGrantFileAccess = getArguments().getSerializable(CHANGE_REQUEST)
- == ChangeRequest.GRANT_All_FILE_ACCESS;
+ == ChangeRequest.GRANT_ALL_FILE_ACCESS;
boolean isGrantStorageSupergroup = getArguments().getSerializable(CHANGE_REQUEST)
== ChangeRequest.GRANT_STORAGE_SUPERGROUP;
int positiveButtonStringResId = R.string.grant_dialog_button_deny_anyway;
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java
index cab0de15e..7fa51dd8a 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java
@@ -103,6 +103,7 @@ public class AppPermissionFragment extends SettingsWithLargeHeader
implements AppPermissionViewModel.ConfirmDialogShowingFragment {
private static final String LOG_TAG = "AppPermissionFragment";
private static final long POST_DELAY_MS = 20;
+ private static final long EDIT_PHOTOS_BUTTON_ANIMATION_LENGTH_MS = 200L;
static final String GRANT_CATEGORY = "grant_category";
@@ -117,6 +118,9 @@ public class AppPermissionFragment extends SettingsWithLargeHeader
private @NonNull RadioButton mSelectPhotosButton;
private @NonNull RadioButton mDenyButton;
private @NonNull RadioButton mDenyForegroundButton;
+ private @NonNull ImageView mEditPhotosButton;
+ private @NonNull View mSelectPhotosLayout;
+ private @NonNull View mEditPhotosDivider;
private @NonNull View mLocationAccuracy;
private @NonNull Switch mLocationAccuracySwitch;
private @NonNull View mDivider;
@@ -128,6 +132,8 @@ public class AppPermissionFragment extends SettingsWithLargeHeader
private @NonNull UserHandle mUser;
private boolean mIsStorageGroup;
private boolean mIsInitialLoad;
+ // This prevents the user from clicking the photo picker button multiple times in succession
+ private boolean mPhotoPickerTriggered;
private long mSessionId;
private @NonNull String mPackageLabel;
@@ -209,7 +215,6 @@ public class AppPermissionFragment extends SettingsWithLargeHeader
if (mIsStorageGroup) {
mViewModel.getFullStorageStateLiveData().observe(this, this::setSpecialStorageState);
}
- mViewModel.registerPhotoPickerResultIfNeeded(this);
mRoleManager = Utils.getSystemServiceSafe(getContext(), RoleManager.class);
}
@@ -261,12 +266,15 @@ public class AppPermissionFragment extends SettingsWithLargeHeader
mSelectPhotosButton = root.requireViewById(R.id.select_radio_button);
mDenyButton = root.requireViewById(R.id.deny_radio_button);
mDenyForegroundButton = root.requireViewById(R.id.deny_foreground_radio_button);
+
mDivider = root.requireViewById(R.id.two_target_divider);
mWidgetFrame = root.requireViewById(R.id.widget_frame);
mPermissionDetails = root.requireViewById(R.id.permission_details);
mLocationAccuracy = root.requireViewById(R.id.location_accuracy);
mLocationAccuracySwitch = root.requireViewById(R.id.location_accuracy_switch);
-
+ mSelectPhotosLayout = root.requireViewById(R.id.radio_select_layout);
+ mEditPhotosButton = root.requireViewById(R.id.edit_selected_button);
+ mEditPhotosDivider = root.requireViewById(R.id.edit_photos_divider);
mNestedScrollView = root.requireViewById(R.id.nested_scroll_view);
if (mViewModel.getButtonStateLiveData().getValue() != null) {
@@ -280,6 +288,9 @@ public class AppPermissionFragment extends SettingsWithLargeHeader
mDenyButton.setVisibility(View.GONE);
mDenyForegroundButton.setVisibility(View.GONE);
mLocationAccuracy.setVisibility(View.GONE);
+ mSelectPhotosLayout.setVisibility(View.GONE);
+ mEditPhotosDivider.setAlpha(0f);
+ mEditPhotosButton.setAlpha(0f);
}
if (mViewModel.getFullStorageStateLiveData().isInitialized() && mIsStorageGroup) {
@@ -302,6 +313,12 @@ public class AppPermissionFragment extends SettingsWithLargeHeader
return root;
}
+ public void onResume() {
+ super.onResume();
+ // If we're returning to the fragment, photo picker hasn't been triggered
+ mPhotoPickerTriggered = false;
+ }
+
private void showPermissionRationaleDialog(boolean showPermissionRationale) {
if (!showPermissionRationale) {
mAppPermissionRationaleContainer.setVisibility(View.GONE);
@@ -380,7 +397,7 @@ public class AppPermissionFragment extends SettingsWithLargeHeader
});
mAllowAlwaysButton.setOnClickListener((v) -> {
if (mIsStorageGroup) {
- showConfirmDialog(ChangeRequest.GRANT_All_FILE_ACCESS,
+ showConfirmDialog(ChangeRequest.GRANT_ALL_FILE_ACCESS,
R.string.special_file_access_dialog, -1, false);
} else {
mViewModel.requestChange(false, this, this, ChangeRequest.GRANT_BOTH,
@@ -412,6 +429,13 @@ public class AppPermissionFragment extends SettingsWithLargeHeader
mViewModel.requestChange(false, this, this, ChangeRequest.PHOTOS_SELECTED,
buttonPressed);
});
+ mEditPhotosButton.setOnClickListener((v) -> {
+ ButtonState selectState = states.get(ButtonType.SELECT_PHOTOS);
+ if (selectState != null && selectState.isChecked() && !mPhotoPickerTriggered) {
+ mPhotoPickerTriggered = true;
+ mViewModel.openPhotoPicker(this);
+ }
+ });
mDenyButton.setOnClickListener((v) -> {
if (mViewModel.getFullStorageStateLiveData().getValue() != null
&& !mViewModel.getFullStorageStateLiveData().getValue().isLegacy()) {
@@ -485,6 +509,21 @@ public class AppPermissionFragment extends SettingsWithLargeHeader
if (mIsInitialLoad) {
button.jumpDrawablesToCurrentState();
}
+
+ if (button == mSelectPhotosButton) {
+ mSelectPhotosLayout.setVisibility(visible);
+ float endOpacity = state.isChecked() ? 1f : 0f;
+ // On initial load, do not show the fade in/out animation
+ if (mIsInitialLoad) {
+ mEditPhotosDivider.setAlpha(endOpacity);
+ mEditPhotosButton.setAlpha(endOpacity);
+ return;
+ }
+ mEditPhotosButton.animate().alpha(endOpacity)
+ .setDuration(EDIT_PHOTOS_BUTTON_ANIMATION_LENGTH_MS);
+ mEditPhotosDivider.animate().alpha(endOpacity)
+ .setDuration(EDIT_PHOTOS_BUTTON_ANIMATION_LENGTH_MS);
+ }
}
private void setSpecialStorageState(FullStoragePackageState storageState, View v) {
@@ -630,7 +669,7 @@ public class AppPermissionFragment extends SettingsWithLargeHeader
// NFF sharing
AppPermissionFragment fragment = (AppPermissionFragment) getParentFragment();
boolean isGrantFileAccess = getArguments().getSerializable(CHANGE_REQUEST)
- == ChangeRequest.GRANT_All_FILE_ACCESS;
+ == ChangeRequest.GRANT_ALL_FILE_ACCESS;
int positiveButtonStringResId = R.string.grant_dialog_button_deny_anyway;
if (isGrantFileAccess) {
positiveButtonStringResId = R.string.grant_dialog_button_allow;
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt
index d6197b15f..cc29acbd7 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt
@@ -29,19 +29,14 @@ import android.app.AppOpsManager.MODE_ALLOWED
import android.app.AppOpsManager.MODE_ERRORED
import android.app.AppOpsManager.OPSTR_MANAGE_EXTERNAL_STORAGE
import android.app.Application
-import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.os.UserHandle
-import android.provider.MediaStore
import android.util.Log
-import androidx.activity.result.ActivityResultLauncher
-import androidx.activity.result.contract.ActivityResultContract
import androidx.annotation.ChecksSdkIntAtLeast
import androidx.annotation.RequiresApi
import androidx.annotation.StringRes
-import androidx.core.util.Consumer
import androidx.fragment.app.Fragment
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
@@ -80,6 +75,7 @@ import com.android.permissioncontroller.permission.utils.KotlinUtils
import com.android.permissioncontroller.permission.utils.KotlinUtils.getDefaultPrecision
import com.android.permissioncontroller.permission.utils.KotlinUtils.isLocationAccuracyEnabled
import com.android.permissioncontroller.permission.utils.KotlinUtils.isPhotoPickerPromptEnabled
+import com.android.permissioncontroller.permission.utils.KotlinUtils.openPhotoPickerForApp
import com.android.permissioncontroller.permission.utils.LocationUtils
import com.android.permissioncontroller.permission.utils.PermissionMapping
import com.android.permissioncontroller.permission.utils.PermissionMapping.getPartialStorageGrantPermissionsForGroup
@@ -113,7 +109,6 @@ class AppPermissionViewModel(
companion object {
private val LOG_TAG = AppPermissionViewModel::class.java.simpleName
private const val DEVICE_PROFILE_ROLE_PREFIX = "android.app.role"
- const val PHOTO_PICKER_REQUEST_CODE = 1
}
interface ConfirmDialogShowingFragment {
@@ -135,7 +130,7 @@ class AppPermissionViewModel(
GRANT_BOTH(GRANT_FOREGROUND.value or GRANT_BACKGROUND.value),
REVOKE_BOTH(REVOKE_FOREGROUND.value or REVOKE_BACKGROUND.value),
GRANT_FOREGROUND_ONLY(GRANT_FOREGROUND.value or REVOKE_BACKGROUND.value),
- GRANT_All_FILE_ACCESS(1 shl 4),
+ GRANT_ALL_FILE_ACCESS(1 shl 4),
GRANT_FINE_LOCATION(1 shl 5),
REVOKE_FINE_LOCATION(1 shl 6),
GRANT_STORAGE_SUPERGROUP(1 shl 7),
@@ -167,8 +162,6 @@ class AppPermissionViewModel(
permGroupName == Manifest.permission_group.STORAGE && !SdkLevel.isAtLeastT()
private var hasConfirmedRevoke = false
private var lightAppPermGroup: LightAppPermGroup? = null
- private var photoPickerLauncher: ActivityResultLauncher<Unit>? = null
- private var photoPickerResultConsumer: Consumer<Int>? = null
private val mediaStorageSupergroupPermGroups = mutableMapOf<String, LightAppPermGroup>()
@@ -513,32 +506,6 @@ class AppPermissionViewModel(
return !userSelectedPerm.isImplicit
}
- fun registerPhotoPickerResultIfNeeded(fragment: Fragment) {
- if (permGroupName != READ_MEDIA_VISUAL) {
- return
- }
- photoPickerLauncher =
- fragment.registerForActivityResult(
- object : ActivityResultContract<Unit, Int>() {
- override fun parseResult(resultCode: Int, intent: Intent?): Int {
- return resultCode
- }
-
- override fun createIntent(context: Context, input: Unit): Intent {
- return Intent(MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP)
- .putExtra(Intent.EXTRA_UID, lightAppPermGroup?.packageInfo?.uid)
- .setType(
- KotlinUtils.getMimeTypeForPermissions(
- lightAppPermGroup?.foregroundPermNames ?: emptyList()
- )
- )
- }
- }
- ) { result ->
- photoPickerResultConsumer?.accept(result)
- }
- }
-
private fun isFineLocationChecked(group: LightAppPermGroup): Boolean {
if (shouldShowLocationAccuracy == true) {
val coarseLocation = group.permissions[ACCESS_COARSE_LOCATION]!!
@@ -687,6 +654,12 @@ class AppPermissionViewModel(
fragment.findNavController().navigateSafe(actionId, args)
}
+ fun openPhotoPicker(fragment: Fragment) {
+ val appPermGroup = lightAppPermGroup ?: return
+ openPhotoPickerForApp(fragment.requireActivity(), appPermGroup.packageInfo.uid,
+ appPermGroup.foregroundPermNames, 0)
+ }
+
/**
* Request to grant/revoke permissions group.
*
@@ -991,7 +964,6 @@ class AppPermissionViewModel(
groupName: String,
targetSdk: Int
) {
-
val aural = groupName == Manifest.permission_group.READ_MEDIA_AURAL
val visual = groupName == Manifest.permission_group.READ_MEDIA_VISUAL
val allow = changeRequest === ChangeRequest.GRANT_STORAGE_SUPERGROUP
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt
index d7bb351b4..3f19db475 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt
@@ -39,9 +39,7 @@ 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
@@ -125,6 +123,7 @@ import com.android.permissioncontroller.permission.utils.KotlinUtils.grantForegr
import com.android.permissioncontroller.permission.utils.KotlinUtils.isLocationAccuracyEnabled
import com.android.permissioncontroller.permission.utils.KotlinUtils.isPhotoPickerPromptEnabled
import com.android.permissioncontroller.permission.utils.KotlinUtils.isPhotoPickerPromptSupported
+import com.android.permissioncontroller.permission.utils.KotlinUtils.openPhotoPickerForApp
import com.android.permissioncontroller.permission.utils.KotlinUtils.revokeBackgroundRuntimePermissions
import com.android.permissioncontroller.permission.utils.KotlinUtils.revokeForegroundRuntimePermissions
import com.android.permissioncontroller.permission.utils.PermissionMapping
@@ -1623,22 +1622,8 @@ class GrantPermissionsViewModel(
}
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
- )
+ openPhotoPickerForApp(activity, packageInfo.uid, unfilteredAffectedPermissions,
+ PHOTO_PICKER_REQUEST_CODE)
}
/**
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/television/AppPermissionFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/television/AppPermissionFragment.java
index e2df47009..399a694d0 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/television/AppPermissionFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/television/AppPermissionFragment.java
@@ -289,7 +289,7 @@ public class AppPermissionFragment extends SettingsWithHeader
});
mAllowAlwaysButton.setOnPreferenceClickListener((v) -> {
if (mIsStorageGroup) {
- showConfirmDialog(ChangeRequest.GRANT_All_FILE_ACCESS,
+ showConfirmDialog(ChangeRequest.GRANT_ALL_FILE_ACCESS,
R.string.special_file_access_dialog, -1, false);
} else {
mViewModel.requestChange(false, this, this, ChangeRequest.GRANT_BOTH,
@@ -464,7 +464,7 @@ public class AppPermissionFragment extends SettingsWithHeader
public Dialog onCreateDialog(Bundle savedInstanceState) {
AppPermissionFragment fragment = (AppPermissionFragment) getTargetFragment();
boolean isGrantFileAccess = getArguments().getSerializable(CHANGE_REQUEST)
- == ChangeRequest.GRANT_All_FILE_ACCESS;
+ == ChangeRequest.GRANT_ALL_FILE_ACCESS;
int positiveButtonStringResId = R.string.grant_dialog_button_deny_anyway;
if (isGrantFileAccess) {
positiveButtonStringResId = R.string.grant_dialog_button_allow;
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt b/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt
index 5d18881ec..f2aa0ae66 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt
@@ -25,6 +25,7 @@ import android.Manifest.permission.POST_NOTIFICATIONS
import android.Manifest.permission.READ_MEDIA_IMAGES
import android.Manifest.permission.READ_MEDIA_VIDEO
import android.Manifest.permission_group.NOTIFICATIONS
+import android.app.Activity
import android.app.ActivityManager
import android.app.AppOpsManager
import android.app.AppOpsManager.MODE_ALLOWED
@@ -59,8 +60,10 @@ import android.health.connect.HealthConnectManager
import android.os.Build
import android.os.Bundle
import android.os.UserHandle
+import android.os.UserManager
import android.permission.PermissionManager
import android.provider.DeviceConfig
+import android.provider.MediaStore
import android.provider.Settings
import android.safetylabel.SafetyLabelConstants.PERMISSION_RATIONALE_ENABLED
import android.safetylabel.SafetyLabelConstants.SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED
@@ -647,6 +650,28 @@ object KotlinUtils {
}
}
+ fun openPhotoPickerForApp(
+ activity: Activity,
+ uid: Int,
+ requestedPermissions: List<String>,
+ requestCode: Int
+ ) {
+ // A clone profile doesn't have a MediaProvider. If the app's user is a clone profile, open
+ // the photo picker in the parent profile
+ val appUser = UserHandle.getUserHandleForUid(uid)
+ val userManager =
+ activity.createContextAsUser(appUser, 0).getSystemService(UserManager::class.java)!!
+ val user = if (userManager.isCloneProfile) {
+ userManager.getProfileParent(appUser) ?: appUser
+ } else {
+ appUser
+ }
+ val pickerIntent = Intent(MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP)
+ .putExtra(Intent.EXTRA_UID, uid)
+ .setType(getMimeTypeForPermissions(requestedPermissions))
+ activity.startActivityForResultAsUser(pickerIntent, requestCode, user)
+ }
+
/** Return a specific MIME type, if a set of permissions is associated with one */
fun getMimeTypeForPermissions(permissions: List<String>): String? {
if (permissions.contains(READ_MEDIA_IMAGES) && !permissions.contains(READ_MEDIA_VIDEO)) {