diff options
Diffstat (limited to 'PermissionController/src')
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)) { |