summaryrefslogtreecommitdiff
path: root/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAppPermissionFragment.java
diff options
context:
space:
mode:
authorNate Myren <ntmyren@google.com>2019-11-01 14:36:47 -0700
committerNate Myren <ntmyren@google.com>2019-11-01 21:39:08 +0000
commit6ac1ace3308b35619c5067c012ddada80bc7b362 (patch)
tree1c7129dfe8c50ea398be71a956ba983a232e8ada /PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAppPermissionFragment.java
parentdf9488fa1f42971057fe9f3ea43b6b5f7149acf9 (diff)
downloadPermission-6ac1ace3308b35619c5067c012ddada80bc7b362.tar.gz
Refactor packageinstaller to permissioncontroller V2
renamed "packageinstaller" package to "permissioncontroller" Fixes: 141707344 Test: flash new permissioncontroller, ensure UI still functions correctly. Change-Id: Ib58c0486cf148d1ca3167fdf8cb6d185eef78f64
Diffstat (limited to 'PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAppPermissionFragment.java')
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAppPermissionFragment.java812
1 files changed, 812 insertions, 0 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
new file mode 100644
index 000000000..0125660e7
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAppPermissionFragment.java
@@ -0,0 +1,812 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.ui.auto;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageItemInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.util.Log;
+import android.view.View;
+import android.widget.RadioButton;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.content.res.TypedArrayUtils;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.Fragment;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.PreferenceViewHolder;
+import androidx.preference.TwoStatePreference;
+
+import com.android.permissioncontroller.R;
+import com.android.permissioncontroller.auto.AutoSettingsFrameFragment;
+import com.android.permissioncontroller.permission.model.AppPermissionGroup;
+import com.android.permissioncontroller.permission.model.Permission;
+import com.android.permissioncontroller.permission.utils.LocationUtils;
+import com.android.permissioncontroller.permission.utils.PackageRemovalMonitor;
+import com.android.permissioncontroller.permission.utils.SafetyNetLogger;
+import com.android.permissioncontroller.permission.utils.Utils;
+import com.android.settingslib.RestrictedLockUtils;
+
+import java.lang.annotation.Retention;
+import java.util.List;
+
+/** Settings related to a particular permission for the given app. */
+public class AutoAppPermissionFragment extends AutoSettingsFrameFragment {
+
+ private static final String LOG_TAG = "AppPermissionFragment";
+
+ @Retention(SOURCE)
+ @IntDef(value = {CHANGE_FOREGROUND, CHANGE_BACKGROUND}, flag = true)
+ @interface ChangeTarget {
+ }
+
+ static final int CHANGE_FOREGROUND = 1;
+ static final int CHANGE_BACKGROUND = 2;
+ static final int CHANGE_BOTH = CHANGE_FOREGROUND | CHANGE_BACKGROUND;
+
+ @NonNull
+ private AppPermissionGroup mGroup;
+
+ @NonNull
+ private TwoStatePreference mAlwaysPermissionPreference;
+ @NonNull
+ private TwoStatePreference mForegroundOnlyPermissionPreference;
+ @NonNull
+ private TwoStatePreference mDenyPermissionPreference;
+ @NonNull
+ private AutoTwoTargetPreference mDetailsPreference;
+
+ private boolean mHasConfirmedRevoke;
+
+ /**
+ * Listens for changes to the permission of the app the permission is currently getting
+ * granted to. {@code null} when unregistered.
+ */
+ @Nullable
+ private PackageManager.OnPermissionsChangedListener mPermissionChangeListener;
+
+ /**
+ * Listens for changes to the app the permission is currently getting granted to. {@code null}
+ * when unregistered.
+ */
+ @Nullable
+ private PackageRemovalMonitor mPackageRemovalMonitor;
+
+ /**
+ * Returns a new {@link AutoAppPermissionFragment}.
+ *
+ * @param packageName the package name for which the permission is being changed
+ * @param permName the name of the permission being changed
+ * @param groupName the name of the permission group being changed
+ * @param userHandle the user for which the permission is being changed
+ */
+ @NonNull
+ public static AutoAppPermissionFragment newInstance(@NonNull String packageName,
+ @NonNull String permName, @Nullable String groupName, @NonNull UserHandle userHandle) {
+ AutoAppPermissionFragment fragment = new AutoAppPermissionFragment();
+ Bundle arguments = new Bundle();
+ arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
+ if (groupName == null) {
+ arguments.putString(Intent.EXTRA_PERMISSION_NAME, permName);
+ } else {
+ arguments.putString(Intent.EXTRA_PERMISSION_GROUP_NAME, groupName);
+ }
+ arguments.putParcelable(Intent.EXTRA_USER, userHandle);
+ fragment.setArguments(arguments);
+ return fragment;
+ }
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mHasConfirmedRevoke = false;
+
+ mGroup = getAppPermissionGroup();
+ if (mGroup == null) {
+ requireActivity().setResult(Activity.RESULT_CANCELED);
+ requireActivity().finish();
+ return;
+ }
+
+ setHeaderLabel(
+ getContext().getString(R.string.app_permission_title, mGroup.getFullLabel()));
+ }
+
+ private AppPermissionGroup getAppPermissionGroup() {
+ Activity activity = getActivity();
+ Context context = getPreferenceManager().getContext();
+
+ String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
+ String groupName = getArguments().getString(Intent.EXTRA_PERMISSION_GROUP_NAME);
+ if (groupName == null) {
+ groupName = getArguments().getString(Intent.EXTRA_PERMISSION_NAME);
+ }
+ PackageItemInfo groupInfo = Utils.getGroupInfo(groupName, context);
+ List<PermissionInfo> groupPermInfos = Utils.getGroupPermissionInfos(groupName, context);
+ if (groupInfo == null || groupPermInfos == null) {
+ Log.i(LOG_TAG, "Illegal group: " + groupName);
+ return null;
+ }
+ UserHandle userHandle = getArguments().getParcelable(Intent.EXTRA_USER);
+ PackageInfo packageInfo = AutoPermissionsUtils.getPackageInfo(activity, packageName,
+ userHandle);
+ if (packageInfo == null) {
+ Log.i(LOG_TAG, "PackageInfo is null");
+ return null;
+ }
+ AppPermissionGroup group = AppPermissionGroup.create(context, packageInfo, groupInfo,
+ groupPermInfos, false);
+
+ if (group == null || !Utils.shouldShowPermission(context, group)) {
+ Log.i(LOG_TAG, "Illegal group: " + (group == null ? "null" : group.getName()));
+ return null;
+ }
+
+ return group;
+ }
+
+ @Override
+ public void onCreatePreferences(Bundle bundle, String s) {
+ setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getContext()));
+ }
+
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ PreferenceScreen screen = getPreferenceScreen();
+ screen.addPreference(
+ AutoPermissionsUtils.createHeaderPreference(getContext(),
+ mGroup.getApp().applicationInfo));
+
+ // Add permissions selector preferences.
+ PreferenceGroup permissionSelector = new PreferenceCategory(getContext());
+ permissionSelector.setTitle(
+ getContext().getString(R.string.app_permission_header, mGroup.getFullLabel()));
+ screen.addPreference(permissionSelector);
+
+ mAlwaysPermissionPreference = new SelectedPermissionPreference(getContext());
+ mAlwaysPermissionPreference.setTitle(R.string.app_permission_button_allow_always);
+ permissionSelector.addPreference(mAlwaysPermissionPreference);
+
+ mForegroundOnlyPermissionPreference = new SelectedPermissionPreference(getContext());
+ mForegroundOnlyPermissionPreference.setTitle(
+ R.string.app_permission_button_allow_foreground);
+ permissionSelector.addPreference(mForegroundOnlyPermissionPreference);
+
+ mDenyPermissionPreference = new SelectedPermissionPreference(getContext());
+ mDenyPermissionPreference.setTitle(R.string.app_permission_button_deny);
+ permissionSelector.addPreference(mDenyPermissionPreference);
+
+ mDetailsPreference = new AutoTwoTargetPreference(getContext());
+ screen.addPreference(mDetailsPreference);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ Activity activity = requireActivity();
+
+ mPermissionChangeListener = new PermissionChangeListener(
+ mGroup.getApp().applicationInfo.uid);
+ PackageManager pm = activity.getPackageManager();
+ pm.addOnPermissionsChangeListener(mPermissionChangeListener);
+
+ // Get notified when the package is removed.
+ String packageName = mGroup.getApp().packageName;
+ mPackageRemovalMonitor = new PackageRemovalMonitor(getContext(), packageName) {
+ @Override
+ public void onPackageRemoved() {
+ Log.w(LOG_TAG, packageName + " was uninstalled");
+ activity.setResult(Activity.RESULT_CANCELED);
+ activity.finish();
+ }
+ };
+ mPackageRemovalMonitor.register();
+
+ // Check if the package was removed while this activity was not started.
+ try {
+ activity.createPackageContextAsUser(packageName, /* flags= */ 0,
+ mGroup.getUser()).getPackageManager().getPackageInfo(packageName,
+ /* flags= */ 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(LOG_TAG, packageName + " was uninstalled while this activity was stopped", e);
+ activity.setResult(Activity.RESULT_CANCELED);
+ activity.finish();
+ }
+
+ // Re-create the permission group in case permissions have changed and update the UI.
+ mGroup = getAppPermissionGroup();
+ updateUi();
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+
+ if (mPackageRemovalMonitor != null) {
+ mPackageRemovalMonitor.unregister();
+ mPackageRemovalMonitor = null;
+ }
+
+ if (mPermissionChangeListener != null) {
+ getActivity().getPackageManager().removeOnPermissionsChangeListener(
+ mPermissionChangeListener);
+ mPermissionChangeListener = null;
+ }
+ }
+
+ private void updateUi() {
+ mDetailsPreference.setOnSecondTargetClickListener(null);
+ mDetailsPreference.setVisible(false);
+
+ if (mGroup.areRuntimePermissionsGranted()) {
+ if (!mGroup.hasPermissionWithBackgroundMode()
+ || (mGroup.getBackgroundPermissions() != null
+ && mGroup.getBackgroundPermissions().areRuntimePermissionsGranted())) {
+ setSelectedPermissionState(mAlwaysPermissionPreference);
+ } else {
+ setSelectedPermissionState(mForegroundOnlyPermissionPreference);
+ }
+ } else {
+ setSelectedPermissionState(mDenyPermissionPreference);
+ }
+
+ mAlwaysPermissionPreference.setOnPreferenceClickListener(
+ v -> requestChange(/* requestGrant= */true, CHANGE_BOTH));
+ mForegroundOnlyPermissionPreference.setOnPreferenceClickListener(v -> {
+ requestChange(/* requestGrant= */false, CHANGE_BACKGROUND);
+ requestChange(/* requestGrant= */true, CHANGE_FOREGROUND);
+ return true;
+ });
+ mDenyPermissionPreference.setOnPreferenceClickListener(
+ v -> requestChange(/* requestGrant= */ false, CHANGE_BOTH));
+
+ // Set the allow and foreground-only button states appropriately.
+ if (mGroup.hasPermissionWithBackgroundMode()) {
+ if (mGroup.getBackgroundPermissions() == null) {
+ mAlwaysPermissionPreference.setVisible(false);
+ } else {
+ mForegroundOnlyPermissionPreference.setVisible(true);
+ mAlwaysPermissionPreference.setTitle(R.string.app_permission_button_allow_always);
+ }
+ } else {
+ mForegroundOnlyPermissionPreference.setVisible(false);
+ mAlwaysPermissionPreference.setTitle(R.string.app_permission_button_allow);
+ }
+
+ // Handle the UI for various special cases.
+ if (isSystemFixed() || isPolicyFullyFixed() || isForegroundDisabledByPolicy()) {
+ // Disable changing permissions and potentially show administrator message.
+ mAlwaysPermissionPreference.setEnabled(false);
+ mForegroundOnlyPermissionPreference.setEnabled(false);
+ mDenyPermissionPreference.setEnabled(false);
+
+ RestrictedLockUtils.EnforcedAdmin admin = getAdmin();
+ if (admin != null) {
+ mDetailsPreference.setWidgetLayoutResource(R.layout.info_preference_widget);
+ mDetailsPreference.setOnSecondTargetClickListener(
+ preference -> RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
+ getContext(), admin));
+ }
+
+ updateDetailForFixedByPolicyPermissionGroup();
+ } else if (Utils.areGroupPermissionsIndividuallyControlled(getContext(),
+ mGroup.getName())) {
+ // If the permissions are individually controlled, also show a link to the page that
+ // lets you control them.
+ mDetailsPreference.setWidgetLayoutResource(R.layout.settings_preference_widget);
+ mDetailsPreference.setOnSecondTargetClickListener(
+ preference -> showAllPermissions(mGroup.getName()));
+
+ updateDetailForIndividuallyControlledPermissionGroup();
+ } else {
+ if (mGroup.hasPermissionWithBackgroundMode()) {
+ if (mGroup.getBackgroundPermissions() == null) {
+ // The group has background permissions but the app did not request any. I.e.
+ // The app can only switch between 'never" and "only in foreground".
+ mAlwaysPermissionPreference.setEnabled(false);
+
+ mDenyPermissionPreference.setOnPreferenceClickListener(v -> requestChange(false,
+ CHANGE_FOREGROUND));
+ } else {
+ if (isBackgroundPolicyFixed()) {
+ // If background policy is fixed, we only allow switching the foreground.
+ // Note that this assumes that the background policy is fixed to deny,
+ // since if it is fixed to grant, so is the foreground.
+ mAlwaysPermissionPreference.setEnabled(false);
+ setSelectedPermissionState(mForegroundOnlyPermissionPreference);
+
+ mDenyPermissionPreference.setOnPreferenceClickListener(
+ v -> requestChange(false, CHANGE_FOREGROUND));
+
+ updateDetailForFixedByPolicyPermissionGroup();
+ } else if (isForegroundPolicyFixed()) {
+ // Foreground permissions are fixed to allow (the first case above handles
+ // fixing to deny), so we only allow toggling background permissions.
+ mDenyPermissionPreference.setEnabled(false);
+
+ mAlwaysPermissionPreference.setOnPreferenceClickListener(
+ v -> requestChange(true, CHANGE_BACKGROUND));
+ mForegroundOnlyPermissionPreference.setOnPreferenceClickListener(
+ v -> requestChange(false, CHANGE_BACKGROUND));
+
+ updateDetailForFixedByPolicyPermissionGroup();
+ } else {
+ // The default tri-state case is handled by default.
+ }
+ }
+
+ } else {
+ // The default bi-state case is handled by default.
+ }
+ }
+ }
+
+ /**
+ * Set the given permission state as the only checked permission state.
+ */
+ private void setSelectedPermissionState(@NonNull TwoStatePreference permissionState) {
+ permissionState.setChecked(true);
+ if (permissionState != mAlwaysPermissionPreference) {
+ mAlwaysPermissionPreference.setChecked(false);
+ }
+ if (permissionState != mForegroundOnlyPermissionPreference) {
+ mForegroundOnlyPermissionPreference.setChecked(false);
+ }
+ if (permissionState != mDenyPermissionPreference) {
+ mDenyPermissionPreference.setChecked(false);
+ }
+ }
+
+ /**
+ * Are any permissions of this group fixed by the system, i.e. not changeable by the user.
+ *
+ * @return {@code true} iff any permission is fixed
+ */
+ private boolean isSystemFixed() {
+ return mGroup.isSystemFixed();
+ }
+
+ /**
+ * Is any foreground permissions of this group fixed by the policy, i.e. not changeable by the
+ * user.
+ *
+ * @return {@code true} iff any foreground permission is fixed
+ */
+ private boolean isForegroundPolicyFixed() {
+ return mGroup.isPolicyFixed();
+ }
+
+ /**
+ * Is any background permissions of this group fixed by the policy, i.e. not changeable by the
+ * user.
+ *
+ * @return {@code true} iff any background permission is fixed
+ */
+ private boolean isBackgroundPolicyFixed() {
+ return mGroup.getBackgroundPermissions() != null
+ && mGroup.getBackgroundPermissions().isPolicyFixed();
+ }
+
+ /**
+ * Are there permissions fixed, so that the user cannot change the preference at all?
+ *
+ * @return {@code true} iff the permissions of this group are fixed
+ */
+ private boolean isPolicyFullyFixed() {
+ return isForegroundPolicyFixed() && (mGroup.getBackgroundPermissions() == null
+ || isBackgroundPolicyFixed());
+ }
+
+ /**
+ * Is the foreground part of this group disabled. If the foreground is disabled, there is no
+ * need to possible grant background access.
+ *
+ * @return {@code true} iff the permissions of this group are fixed
+ */
+ private boolean isForegroundDisabledByPolicy() {
+ return isForegroundPolicyFixed() && !mGroup.areRuntimePermissionsGranted();
+ }
+
+ /**
+ * Get the app that acts as admin for this profile.
+ *
+ * @return The admin or {@code null} if there is no admin.
+ */
+ @Nullable
+ private RestrictedLockUtils.EnforcedAdmin getAdmin() {
+ return RestrictedLockUtils.getProfileOrDeviceOwner(getContext(), mGroup.getUser());
+ }
+
+ /**
+ * Update the detail in the case the permission group has individually controlled permissions.
+ */
+ private void updateDetailForIndividuallyControlledPermissionGroup() {
+ int revokedCount = 0;
+ List<Permission> permissions = mGroup.getPermissions();
+ int permissionCount = permissions.size();
+ for (int i = 0; i < permissionCount; i++) {
+ Permission permission = permissions.get(i);
+ if (!permission.isGrantedIncludingAppOp()) {
+ revokedCount++;
+ }
+ }
+
+ int resId;
+ if (revokedCount == 0) {
+ resId = R.string.permission_revoked_none;
+ } else if (revokedCount == permissionCount) {
+ resId = R.string.permission_revoked_all;
+ } else {
+ resId = R.string.permission_revoked_count;
+ }
+
+ mDetailsPreference.setSummary(getContext().getString(resId, revokedCount));
+ mDetailsPreference.setVisible(true);
+ }
+
+ /**
+ * Update the detail of a permission group that is at least partially fixed by policy.
+ */
+ private void updateDetailForFixedByPolicyPermissionGroup() {
+ RestrictedLockUtils.EnforcedAdmin admin = getAdmin();
+ AppPermissionGroup backgroundGroup = mGroup.getBackgroundPermissions();
+
+ boolean hasAdmin = admin != null;
+
+ if (isSystemFixed()) {
+ // Permission is fully controlled by the system and cannot be switched
+
+ setDetail(R.string.permission_summary_enabled_system_fixed);
+ } else if (isForegroundDisabledByPolicy()) {
+ // Permission is fully controlled by policy and cannot be switched
+
+ if (hasAdmin) {
+ setDetail(R.string.disabled_by_admin);
+ } else {
+ // Disabled state will be displayed by switch, so no need to add text for that
+ setDetail(R.string.permission_summary_enforced_by_policy);
+ }
+ } else if (isPolicyFullyFixed()) {
+ // Permission is fully controlled by policy and cannot be switched
+
+ if (backgroundGroup == null) {
+ if (hasAdmin) {
+ setDetail(R.string.enabled_by_admin);
+ } else {
+ // Enabled state will be displayed by switch, so no need to add text for
+ // that
+ setDetail(R.string.permission_summary_enforced_by_policy);
+ }
+ } else {
+ if (backgroundGroup.areRuntimePermissionsGranted()) {
+ if (hasAdmin) {
+ setDetail(R.string.enabled_by_admin);
+ } else {
+ // Enabled state will be displayed by switch, so no need to add text for
+ // that
+ setDetail(R.string.permission_summary_enforced_by_policy);
+ }
+ } else {
+ if (hasAdmin) {
+ setDetail(
+ R.string.permission_summary_enabled_by_admin_foreground_only);
+ } else {
+ setDetail(
+ R.string.permission_summary_enabled_by_policy_foreground_only);
+ }
+ }
+ }
+ } else {
+ // Part of the permission group can still be switched
+
+ if (isBackgroundPolicyFixed()) {
+ if (backgroundGroup.areRuntimePermissionsGranted()) {
+ if (hasAdmin) {
+ setDetail(R.string.permission_summary_enabled_by_admin_background_only);
+ } else {
+ setDetail(R.string.permission_summary_enabled_by_policy_background_only);
+ }
+ } else {
+ if (hasAdmin) {
+ setDetail(R.string.permission_summary_disabled_by_admin_background_only);
+ } else {
+ setDetail(R.string.permission_summary_disabled_by_policy_background_only);
+ }
+ }
+ } else if (isForegroundPolicyFixed()) {
+ if (hasAdmin) {
+ setDetail(R.string.permission_summary_enabled_by_admin_foreground_only);
+ } else {
+ setDetail(R.string.permission_summary_enabled_by_policy_foreground_only);
+ }
+ }
+ }
+ }
+
+ /**
+ * Show the given string as informative text below permission picker preferences.
+ *
+ * @param strId the resourceId of the string to display.
+ */
+ private void setDetail(int strId) {
+ mDetailsPreference.setSummary(strId);
+ mDetailsPreference.setVisible(true);
+ }
+
+ /**
+ * Show all individual permissions in this group in a new fragment.
+ */
+ private void showAllPermissions(@NonNull String filterGroup) {
+ Fragment frag = AutoAllAppPermissionsFragment.newInstance(mGroup.getApp().packageName,
+ filterGroup, UserHandle.getUserHandleForUid(mGroup.getApp().applicationInfo.uid));
+ getFragmentManager().beginTransaction()
+ .replace(android.R.id.content, frag)
+ .addToBackStack("AllPerms")
+ .commit();
+ }
+
+ /**
+ * Request to grant/revoke permissions group.
+ *
+ * <p>Does <u>not</u> handle:
+ * <ul>
+ * <li>Individually granted permissions</li>
+ * <li>Permission groups with background permissions</li>
+ * </ul>
+ * <p><u>Does</u> handle:
+ * <ul>
+ * <li>Default grant permissions</li>
+ * </ul>
+ *
+ * @param requestGrant If this group should be granted
+ * @param changeTarget Which permission group (foreground/background/both) should be changed
+ * @return If the request was processed.
+ */
+ private boolean requestChange(boolean requestGrant, @ChangeTarget int changeTarget) {
+ if (LocationUtils.isLocationGroupAndProvider(getContext(), mGroup.getName(),
+ mGroup.getApp().packageName)) {
+ LocationUtils.showLocationDialog(getContext(),
+ Utils.getAppLabel(mGroup.getApp().applicationInfo, getContext()));
+
+ // The request was denied, so update the buttons.
+ updateUi();
+ return false;
+ }
+
+ if (requestGrant) {
+ if ((changeTarget & CHANGE_FOREGROUND) != 0) {
+ if (!mGroup.areRuntimePermissionsGranted()) {
+ SafetyNetLogger.logPermissionToggled(mGroup);
+ }
+
+ mGroup.grantRuntimePermissions(false);
+ }
+ if ((changeTarget & CHANGE_BACKGROUND) != 0) {
+ if (mGroup.getBackgroundPermissions() != null) {
+ if (!mGroup.getBackgroundPermissions().areRuntimePermissionsGranted()) {
+ SafetyNetLogger.logPermissionToggled(mGroup.getBackgroundPermissions());
+ }
+
+ mGroup.getBackgroundPermissions().grantRuntimePermissions(false);
+ }
+ }
+ } else {
+ boolean showDefaultDenyDialog = false;
+
+ if ((changeTarget & CHANGE_FOREGROUND) != 0
+ && mGroup.areRuntimePermissionsGranted()) {
+ showDefaultDenyDialog = mGroup.hasGrantedByDefaultPermission()
+ || !mGroup.doesSupportRuntimePermissions()
+ || mGroup.hasInstallToRuntimeSplit();
+ }
+ if ((changeTarget & CHANGE_BACKGROUND) != 0) {
+ if (mGroup.getBackgroundPermissions() != null
+ && mGroup.getBackgroundPermissions().areRuntimePermissionsGranted()) {
+ AppPermissionGroup bgPerm = mGroup.getBackgroundPermissions();
+ showDefaultDenyDialog |= bgPerm.hasGrantedByDefaultPermission()
+ || !bgPerm.doesSupportRuntimePermissions()
+ || bgPerm.hasInstallToRuntimeSplit();
+ }
+ }
+
+ if (showDefaultDenyDialog && !mHasConfirmedRevoke) {
+ showDefaultDenyDialog(changeTarget);
+ updateUi();
+ return false;
+ } else {
+ if ((changeTarget & CHANGE_FOREGROUND) != 0
+ && mGroup.areRuntimePermissionsGranted()) {
+ if (mGroup.areRuntimePermissionsGranted()) {
+ SafetyNetLogger.logPermissionToggled(mGroup);
+ }
+
+ mGroup.revokeRuntimePermissions(false);
+ }
+ if ((changeTarget & CHANGE_BACKGROUND) != 0) {
+ if (mGroup.getBackgroundPermissions() != null
+ && mGroup.getBackgroundPermissions().areRuntimePermissionsGranted()) {
+ if (mGroup.getBackgroundPermissions().areRuntimePermissionsGranted()) {
+ SafetyNetLogger.logPermissionToggled(mGroup.getBackgroundPermissions());
+ }
+
+ mGroup.getBackgroundPermissions().revokeRuntimePermissions(false);
+ }
+ }
+ }
+ }
+
+ updateUi();
+
+ return true;
+ }
+
+ /**
+ * Show a dialog that warns the user that she/he is about to revoke permissions that were
+ * granted by default.
+ *
+ * <p>The order of operation to revoke a permission granted by default is:
+ * <ol>
+ * <li>{@code showDefaultDenyDialog}</li>
+ * <li>{@link DefaultDenyDialog#onCreateDialog}</li>
+ * <li>{@link AutoAppPermissionFragment#onDenyAnyWay}</li>
+ * </ol>
+ *
+ * @param changeTarget Whether background or foreground should be changed
+ */
+ private void showDefaultDenyDialog(@ChangeTarget int changeTarget) {
+ Bundle args = new Bundle();
+
+ boolean showGrantedByDefaultWarning = false;
+ if ((changeTarget & CHANGE_FOREGROUND) != 0) {
+ showGrantedByDefaultWarning = mGroup.hasGrantedByDefaultPermission();
+ }
+ if ((changeTarget & CHANGE_BACKGROUND) != 0) {
+ if (mGroup.getBackgroundPermissions() != null) {
+ showGrantedByDefaultWarning |=
+ mGroup.getBackgroundPermissions().hasGrantedByDefaultPermission();
+ }
+ }
+
+ args.putInt(DefaultDenyDialog.MSG, showGrantedByDefaultWarning ? R.string.system_warning
+ : R.string.old_sdk_deny_warning);
+ args.putInt(DefaultDenyDialog.CHANGE_TARGET, changeTarget);
+
+ DefaultDenyDialog defaultDenyDialog = new DefaultDenyDialog();
+ defaultDenyDialog.setArguments(args);
+ defaultDenyDialog.setTargetFragment(this, 0);
+ defaultDenyDialog.show(getFragmentManager().beginTransaction(),
+ DefaultDenyDialog.class.getName());
+ }
+
+ /**
+ * Once we user has confirmed that he/she wants to revoke a permission that was granted by
+ * default, actually revoke the permissions.
+ *
+ * @param changeTarget whether to change foreground, background, or both.
+ * @see #showDefaultDenyDialog(int)
+ */
+ void onDenyAnyWay(@ChangeTarget int changeTarget) {
+ boolean hasDefaultPermissions = false;
+ if ((changeTarget & CHANGE_FOREGROUND) != 0) {
+ if (mGroup.areRuntimePermissionsGranted()) {
+ SafetyNetLogger.logPermissionToggled(mGroup);
+ }
+
+ mGroup.revokeRuntimePermissions(false);
+ hasDefaultPermissions = mGroup.hasGrantedByDefaultPermission();
+ }
+ if ((changeTarget & CHANGE_BACKGROUND) != 0) {
+ if (mGroup.getBackgroundPermissions() != null) {
+ if (mGroup.getBackgroundPermissions().areRuntimePermissionsGranted()) {
+ SafetyNetLogger.logPermissionToggled(mGroup.getBackgroundPermissions());
+ }
+
+ mGroup.getBackgroundPermissions().revokeRuntimePermissions(false);
+ hasDefaultPermissions |=
+ mGroup.getBackgroundPermissions().hasGrantedByDefaultPermission();
+ }
+ }
+
+ if (hasDefaultPermissions || !mGroup.doesSupportRuntimePermissions()) {
+ mHasConfirmedRevoke = true;
+ }
+ updateUi();
+ }
+
+ /** Preference used to represent apps that can be picked as a default app. */
+ private static class SelectedPermissionPreference extends TwoStatePreference {
+
+ SelectedPermissionPreference(Context context) {
+ super(context, null, TypedArrayUtils.getAttr(context, R.attr.preferenceStyle,
+ android.R.attr.preferenceStyle));
+ setPersistent(false);
+ setLayoutResource(R.layout.car_radio_button_preference);
+ setWidgetLayoutResource(R.layout.radio_button_preference_widget);
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+
+ RadioButton radioButton = (RadioButton) holder.findViewById(R.id.radio_button);
+ radioButton.setChecked(isChecked());
+ }
+ }
+
+ /**
+ * A dialog warning the user that they are about to deny a permission that was granted by
+ * default.
+ *
+ * @see #showDefaultDenyDialog(int)
+ */
+ public static class DefaultDenyDialog extends DialogFragment {
+ private static final String MSG = DefaultDenyDialog.class.getName() + ".arg.msg";
+ private static final String CHANGE_TARGET = DefaultDenyDialog.class.getName()
+ + ".arg.changeTarget";
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ AutoAppPermissionFragment fragment = (AutoAppPermissionFragment) getTargetFragment();
+ AlertDialog.Builder b = new AlertDialog.Builder(getContext())
+ .setMessage(getArguments().getInt(MSG))
+ .setNegativeButton(R.string.cancel,
+ (dialog, which) -> fragment.updateUi())
+ .setPositiveButton(R.string.grant_dialog_button_deny_anyway,
+ (dialog, which) ->
+ fragment.onDenyAnyWay(getArguments().getInt(CHANGE_TARGET)));
+
+ return b.create();
+ }
+ }
+
+ /**
+ * A listener for permission changes.
+ */
+ private class PermissionChangeListener implements PackageManager.OnPermissionsChangedListener {
+ private final int mUid;
+
+ PermissionChangeListener(int uid) {
+ mUid = uid;
+ }
+
+ @Override
+ public void onPermissionsChanged(int uid) {
+ if (uid == mUid) {
+ Log.w(LOG_TAG, "Permissions changed.");
+ mGroup = getAppPermissionGroup();
+ updateUi();
+ }
+ }
+ }
+}