1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.permissioncontroller.permission.ui.handheld; 18 19 import static com.android.permissioncontroller.permission.utils.Utils.DEFAULT_MAX_LABEL_SIZE_PX; 20 import static com.android.permissioncontroller.permission.utils.Utils.getRequestMessage; 21 22 import static java.lang.annotation.RetentionPolicy.SOURCE; 23 24 import android.app.AlertDialog; 25 import android.app.Dialog; 26 import android.content.DialogInterface; 27 import android.os.Bundle; 28 import android.os.UserHandle; 29 import android.text.BidiFormatter; 30 import android.text.TextUtils; 31 import android.widget.Switch; 32 33 import androidx.annotation.IntDef; 34 import androidx.annotation.LayoutRes; 35 import androidx.fragment.app.DialogFragment; 36 import androidx.fragment.app.Fragment; 37 import androidx.preference.PreferenceFragmentCompat; 38 39 import com.android.permissioncontroller.R; 40 import com.android.permissioncontroller.permission.model.AppPermissionGroup; 41 import com.android.permissioncontroller.permission.model.Permission; 42 import com.android.permissioncontroller.permission.utils.LocationUtils; 43 import com.android.permissioncontroller.permission.utils.Utils; 44 import com.android.settingslib.RestrictedLockUtils; 45 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; 46 47 import java.lang.annotation.Retention; 48 import java.util.List; 49 50 /** 51 * A preference for representing a permission group requested by an app. 52 */ 53 class PermissionPreference extends MultiTargetSwitchPreference { 54 @Retention(SOURCE) 55 @IntDef(value = {CHANGE_FOREGROUND, CHANGE_BACKGROUND}, flag = true) 56 @interface ChangeTarget {} 57 static final int CHANGE_FOREGROUND = 1; 58 static final int CHANGE_BACKGROUND = 2; 59 static final int CHANGE_BOTH = CHANGE_FOREGROUND | CHANGE_BACKGROUND; 60 61 private final AppPermissionGroup mGroup; 62 private final PreferenceFragmentCompat mFragment; 63 private final PermissionPreferenceChangeListener mCallBacks; 64 private final @LayoutRes int mOriginalWidgetLayoutRes; 65 66 /** Callbacks for the permission to the fragment showing a list of permissions */ 67 interface PermissionPreferenceChangeListener { 68 /** 69 * Checks if the user has to confirm a revocation of a permission granted by default. 70 * 71 * @return {@code true} iff the user has to confirm it 72 */ shouldConfirmDefaultPermissionRevoke()73 boolean shouldConfirmDefaultPermissionRevoke(); 74 75 /** 76 * Notify the listener that the user confirmed that she/he wants to revoke permissions that 77 * were granted by default. 78 */ hasConfirmDefaultPermissionRevoke()79 void hasConfirmDefaultPermissionRevoke(); 80 81 /** 82 * Notify the listener that this preference has changed. 83 * 84 * @param key The key uniquely identifying this preference 85 */ onPreferenceChanged(String key)86 void onPreferenceChanged(String key); 87 } 88 89 /** 90 * Callbacks from dialogs to the fragment. These callbacks are supposed to directly cycle back 91 * to the permission tha created the dialog. 92 */ 93 interface PermissionPreferenceOwnerFragment { 94 /** 95 * The {@link DefaultDenyDialog} can only interact with the fragment, not the preference 96 * that created it. Hence this call goes to the fragment, which then finds the preference an 97 * calls {@link #onDenyAnyWay(int)}. 98 * 99 * @param key Key uniquely identifying the preference that created the default deny dialog 100 * @param changeTarget Whether background or foreground permissions should be changed 101 * 102 * @see #showDefaultDenyDialog(int) 103 */ onDenyAnyWay(String key, @ChangeTarget int changeTarget)104 void onDenyAnyWay(String key, @ChangeTarget int changeTarget); 105 106 /** 107 * The {@link BackgroundAccessChooser} can only interact with the fragment, not the 108 * preference that created it. Hence this call goes to the fragment, which then finds the 109 * preference an calls {@link #onBackgroundAccessChosen(int)}}. 110 * 111 * @param key Key uniquely identifying the preference that created the background access 112 * chooser 113 * @param chosenItem The index of the item selected by the user. 114 * 115 * @see #showBackgroundChooserDialog() 116 */ onBackgroundAccessChosen(String key, int chosenItem)117 void onBackgroundAccessChosen(String key, int chosenItem); 118 } 119 PermissionPreference(PreferenceFragmentCompat fragment, AppPermissionGroup group, PermissionPreferenceChangeListener callbacks)120 PermissionPreference(PreferenceFragmentCompat fragment, AppPermissionGroup group, 121 PermissionPreferenceChangeListener callbacks) { 122 super(fragment.getPreferenceManager().getContext()); 123 124 mFragment = fragment; 125 mGroup = group; 126 mCallBacks = callbacks; 127 mOriginalWidgetLayoutRes = getWidgetLayoutResource(); 128 129 setPersistent(false); 130 updateUi(); 131 } 132 133 /** 134 * Are any permissions of this group fixed by the system, i.e. not changeable by the user. 135 * 136 * @return {@code true} iff any permission is fixed 137 */ isSystemFixed()138 private boolean isSystemFixed() { 139 return mGroup.isSystemFixed(); 140 } 141 142 /** 143 * Is any foreground permissions of this group fixed by the policy, i.e. not changeable by the 144 * user. 145 * 146 * @return {@code true} iff any foreground permission is fixed 147 */ isForegroundPolicyFixed()148 private boolean isForegroundPolicyFixed() { 149 return mGroup.isPolicyFixed(); 150 } 151 152 /** 153 * Is any background permissions of this group fixed by the policy, i.e. not changeable by the 154 * user. 155 * 156 * @return {@code true} iff any background permission is fixed 157 */ isBackgroundPolicyFixed()158 private boolean isBackgroundPolicyFixed() { 159 return mGroup.getBackgroundPermissions() != null 160 && mGroup.getBackgroundPermissions().isPolicyFixed(); 161 } 162 163 /** 164 * Are there permissions fixed, so that the user cannot change the preference at all? 165 * 166 * @return {@code true} iff the permissions of this group are fixed 167 */ isPolicyFullyFixed()168 private boolean isPolicyFullyFixed() { 169 return isForegroundPolicyFixed() && (mGroup.getBackgroundPermissions() == null 170 || isBackgroundPolicyFixed()); 171 } 172 173 /** 174 * Is the foreground part of this group disabled. If the foreground is disabled, there is no 175 * need to possible grant background access. 176 * 177 * @return {@code true} iff the permissions of this group are fixed 178 */ isForegroundDisabledByPolicy()179 private boolean isForegroundDisabledByPolicy() { 180 return isForegroundPolicyFixed() && !mGroup.areRuntimePermissionsGranted(); 181 } 182 183 /** 184 * Get the app that acts as admin for this profile. 185 * 186 * @return The admin or {@code null} if there is no admin. 187 */ getAdmin()188 private EnforcedAdmin getAdmin() { 189 return RestrictedLockUtils.getProfileOrDeviceOwner(getContext(), mGroup.getUser()); 190 } 191 192 /** 193 * Update the preference after the state might have changed. 194 */ updateUi()195 void updateUi() { 196 boolean arePermissionsIndividuallyControlled = 197 Utils.areGroupPermissionsIndividuallyControlled(getContext(), mGroup.getName()); 198 EnforcedAdmin admin = getAdmin(); 199 200 // Reset ui state 201 setEnabled(true); 202 setWidgetLayoutResource(mOriginalWidgetLayoutRes); 203 setOnPreferenceClickListener(null); 204 setSwitchOnClickListener(null); 205 setSummary(null); 206 207 setChecked(mGroup.areRuntimePermissionsGranted()); 208 209 if (isSystemFixed() || isPolicyFullyFixed() || isForegroundDisabledByPolicy()) { 210 if (admin != null) { 211 setWidgetLayoutResource(R.layout.restricted_icon); 212 213 setOnPreferenceClickListener((v) -> { 214 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(), admin); 215 return true; 216 }); 217 } else { 218 setEnabled(false); 219 } 220 221 updateSummaryForFixedByPolicyPermissionGroup(); 222 } else if (arePermissionsIndividuallyControlled) { 223 setOnPreferenceClickListener((pref) -> { 224 showAllPermissions(mGroup.getName()); 225 return false; 226 }); 227 228 setSwitchOnClickListener(v -> { 229 Switch switchView = (Switch) v; 230 requestChange(switchView.isChecked(), CHANGE_BOTH); 231 232 // Update UI as the switch widget might be in wrong state 233 updateUi(); 234 }); 235 236 updateSummaryForIndividuallyControlledPermissionGroup(); 237 } else { 238 if (mGroup.hasPermissionWithBackgroundMode()) { 239 if (mGroup.getBackgroundPermissions() == null) { 240 // The group has background permissions but the app did not request any. I.e. 241 // The app can only switch between 'never" and "only in foreground". 242 setOnPreferenceChangeListener((pref, newValue) -> 243 requestChange((Boolean) newValue, CHANGE_FOREGROUND)); 244 245 updateSummaryForPermissionGroupWithBackgroundPermission(); 246 } else { 247 if (isBackgroundPolicyFixed()) { 248 setOnPreferenceChangeListener((pref, newValue) -> 249 requestChange((Boolean) newValue, CHANGE_FOREGROUND)); 250 251 updateSummaryForFixedByPolicyPermissionGroup(); 252 } else if (isForegroundPolicyFixed()) { 253 setOnPreferenceChangeListener((pref, newValue) -> 254 requestChange((Boolean) newValue, CHANGE_BACKGROUND)); 255 256 updateSummaryForFixedByPolicyPermissionGroup(); 257 } else { 258 updateSummaryForPermissionGroupWithBackgroundPermission(); 259 260 setOnPreferenceClickListener((pref) -> { 261 showBackgroundChooserDialog(); 262 return true; 263 }); 264 265 setSwitchOnClickListener(v -> { 266 Switch switchView = (Switch) v; 267 268 if (switchView.isChecked()) { 269 showBackgroundChooserDialog(); 270 } else { 271 requestChange(false, CHANGE_BOTH); 272 } 273 274 // Update UI as the switch widget might be in wrong state 275 updateUi(); 276 }); 277 } 278 } 279 } else { 280 setOnPreferenceChangeListener((pref, newValue) -> 281 requestChange((Boolean) newValue, CHANGE_BOTH)); 282 } 283 } 284 } 285 286 /** 287 * Update the summary in the case the permission group has individually controlled permissions. 288 */ updateSummaryForIndividuallyControlledPermissionGroup()289 private void updateSummaryForIndividuallyControlledPermissionGroup() { 290 int revokedCount = 0; 291 List<Permission> permissions = mGroup.getPermissions(); 292 final int permissionCount = permissions.size(); 293 for (int i = 0; i < permissionCount; i++) { 294 Permission permission = permissions.get(i); 295 if (!permission.isGrantedIncludingAppOp()) { 296 revokedCount++; 297 } 298 } 299 300 final int resId; 301 if (revokedCount == 0) { 302 resId = R.string.permission_revoked_none; 303 } else if (revokedCount == permissionCount) { 304 resId = R.string.permission_revoked_all; 305 } else { 306 resId = R.string.permission_revoked_count; 307 } 308 309 String summary = getContext().getString(resId, revokedCount); 310 setSummary(summary); 311 } 312 313 /** 314 * Update the summary of a permission group that has background permission. 315 * 316 * <p>This does not apply to permission groups that are fixed by policy</p> 317 */ updateSummaryForPermissionGroupWithBackgroundPermission()318 private void updateSummaryForPermissionGroupWithBackgroundPermission() { 319 AppPermissionGroup backgroundGroup = mGroup.getBackgroundPermissions(); 320 321 if (mGroup.areRuntimePermissionsGranted()) { 322 if (backgroundGroup == null) { 323 setSummary(R.string.permission_access_only_foreground); 324 } else { 325 if (backgroundGroup.areRuntimePermissionsGranted()) { 326 setSummary(R.string.permission_access_always); 327 } else { 328 setSummary(R.string.permission_access_only_foreground); 329 } 330 } 331 } else { 332 setSummary(R.string.permission_access_never); 333 } 334 } 335 336 /** 337 * Update the summary of a permission group that is at least partially fixed by policy. 338 */ updateSummaryForFixedByPolicyPermissionGroup()339 private void updateSummaryForFixedByPolicyPermissionGroup() { 340 EnforcedAdmin admin = getAdmin(); 341 AppPermissionGroup backgroundGroup = mGroup.getBackgroundPermissions(); 342 343 boolean hasAdmin = admin != null; 344 345 if (isSystemFixed()) { 346 // Permission is fully controlled by the system and cannot be switched 347 348 setSummary(R.string.permission_summary_enabled_system_fixed); 349 } else if (isForegroundDisabledByPolicy()) { 350 // Permission is fully controlled by policy and cannot be switched 351 352 if (hasAdmin) { 353 setSummary(R.string.disabled_by_admin); 354 } else { 355 // Disabled state will be displayed by switch, so no need to add text for that 356 setSummary(R.string.permission_summary_enforced_by_policy); 357 } 358 } else if (isPolicyFullyFixed()) { 359 // Permission is fully controlled by policy and cannot be switched 360 361 if (backgroundGroup == null) { 362 if (hasAdmin) { 363 setSummary(R.string.enabled_by_admin); 364 } else { 365 // Enabled state will be displayed by switch, so no need to add text for 366 // that 367 setSummary(R.string.permission_summary_enforced_by_policy); 368 } 369 } else { 370 if (backgroundGroup.areRuntimePermissionsGranted()) { 371 if (hasAdmin) { 372 setSummary(R.string.enabled_by_admin); 373 } else { 374 // Enabled state will be displayed by switch, so no need to add text for 375 // that 376 setSummary(R.string.permission_summary_enforced_by_policy); 377 } 378 } else { 379 if (hasAdmin) { 380 setSummary( 381 R.string.permission_summary_enabled_by_admin_foreground_only); 382 } else { 383 setSummary( 384 R.string.permission_summary_enabled_by_policy_foreground_only); 385 } 386 } 387 } 388 } else { 389 // Part of the permission group can still be switched 390 391 if (isBackgroundPolicyFixed()) { 392 if (backgroundGroup.areRuntimePermissionsGranted()) { 393 if (hasAdmin) { 394 setSummary(R.string.permission_summary_enabled_by_admin_background_only); 395 } else { 396 setSummary(R.string.permission_summary_enabled_by_policy_background_only); 397 } 398 } else { 399 if (hasAdmin) { 400 setSummary(R.string.permission_summary_disabled_by_admin_background_only); 401 } else { 402 setSummary(R.string.permission_summary_disabled_by_policy_background_only); 403 } 404 } 405 } else if (isForegroundPolicyFixed()) { 406 if (hasAdmin) { 407 setSummary(R.string.permission_summary_enabled_by_admin_foreground_only); 408 } else { 409 setSummary(R.string.permission_summary_enabled_by_policy_foreground_only); 410 } 411 } 412 } 413 } 414 415 /** 416 * Show all individual permissions in this group in a new fragment. 417 */ showAllPermissions(String filterGroup)418 private void showAllPermissions(String filterGroup) { 419 Fragment frag = AllAppPermissionsFragment.newInstance(mGroup.getApp().packageName, 420 filterGroup, UserHandle.getUserHandleForUid(mGroup.getApp().applicationInfo.uid)); 421 mFragment.getFragmentManager().beginTransaction() 422 .replace(android.R.id.content, frag) 423 .addToBackStack("AllPerms") 424 .commit(); 425 } 426 427 /** 428 * Get the label of the app the permission group belongs to. (App permission groups are all 429 * permissions of a group an app has requested.) 430 * 431 * @return The label of the app 432 */ getAppLabel()433 private String getAppLabel() { 434 return BidiFormatter.getInstance().unicodeWrap( 435 mGroup.getApp().applicationInfo.loadSafeLabel(getContext().getPackageManager(), 436 DEFAULT_MAX_LABEL_SIZE_PX, 437 TextUtils.SAFE_STRING_FLAG_TRIM 438 | TextUtils.SAFE_STRING_FLAG_FIRST_LINE) 439 .toString()); 440 } 441 442 /** 443 * Request to grant/revoke permissions group. 444 * 445 * <p>Does <u>not</u> handle: 446 * <ul> 447 * <li>Individually granted permissions</li> 448 * <li>Permission groups with background permissions</li> 449 * </ul> 450 * <p><u>Does</u> handle: 451 * <ul> 452 * <li>Default grant permissions</li> 453 * </ul> 454 * 455 * @param requestGrant If this group should be granted 456 * @param changeTarget Which permission group (foreground/background/both) should be changed 457 * @return If the request was processed. 458 */ requestChange(boolean requestGrant, @ChangeTarget int changeTarget)459 private boolean requestChange(boolean requestGrant, @ChangeTarget int changeTarget) { 460 if (LocationUtils.isLocationGroupAndProvider(getContext(), mGroup.getName(), 461 mGroup.getApp().packageName)) { 462 LocationUtils.showLocationDialog(getContext(), getAppLabel()); 463 return false; 464 } 465 if (requestGrant) { 466 mCallBacks.onPreferenceChanged(getKey()); 467 468 if ((changeTarget & CHANGE_FOREGROUND) != 0) { 469 mGroup.grantRuntimePermissions(true, false); 470 } 471 if ((changeTarget & CHANGE_BACKGROUND) != 0) { 472 if (mGroup.getBackgroundPermissions() != null) { 473 mGroup.getBackgroundPermissions().grantRuntimePermissions(true, false); 474 } 475 } 476 } else { 477 boolean requestToRevokeGrantedByDefault = false; 478 if ((changeTarget & CHANGE_FOREGROUND) != 0) { 479 requestToRevokeGrantedByDefault = mGroup.hasGrantedByDefaultPermission(); 480 } 481 if ((changeTarget & CHANGE_BACKGROUND) != 0) { 482 if (mGroup.getBackgroundPermissions() != null) { 483 requestToRevokeGrantedByDefault |= 484 mGroup.getBackgroundPermissions().hasGrantedByDefaultPermission(); 485 } 486 } 487 488 if ((requestToRevokeGrantedByDefault || !mGroup.doesSupportRuntimePermissions()) 489 && mCallBacks.shouldConfirmDefaultPermissionRevoke()) { 490 showDefaultDenyDialog(changeTarget); 491 return false; 492 } else { 493 mCallBacks.onPreferenceChanged(getKey()); 494 495 if ((changeTarget & CHANGE_FOREGROUND) != 0) { 496 mGroup.revokeRuntimePermissions(false); 497 } 498 if ((changeTarget & CHANGE_BACKGROUND) != 0) { 499 if (mGroup.getBackgroundPermissions() != null) { 500 mGroup.getBackgroundPermissions().revokeRuntimePermissions(false); 501 } 502 } 503 } 504 } 505 506 updateUi(); 507 508 return true; 509 } 510 511 /** 512 * Show a dialog that warns the user that she/he is about to revoke permissions that were 513 * granted by default. 514 * 515 * <p>The order of operation to revoke a permission granted by default is: 516 * <ol> 517 * <li>{@code showDefaultDenyDialog}</li> 518 * <li>{@link DefaultDenyDialog#onCreateDialog}</li> 519 * <li>{@link PermissionPreferenceOwnerFragment#onDenyAnyWay}</li> 520 * <li>{@link PermissionPreference#onDenyAnyWay}</li> 521 * </ol> 522 * 523 * @param changeTarget Whether background or foreground should be changed 524 */ showDefaultDenyDialog(@hangeTarget int changeTarget)525 private void showDefaultDenyDialog(@ChangeTarget int changeTarget) { 526 if (!mFragment.isResumed()) { 527 return; 528 } 529 530 Bundle args = new Bundle(); 531 532 boolean showGrantedByDefaultWarning = false; 533 if ((changeTarget & CHANGE_FOREGROUND) != 0) { 534 showGrantedByDefaultWarning = mGroup.hasGrantedByDefaultPermission(); 535 } 536 if ((changeTarget & CHANGE_BACKGROUND) != 0) { 537 if (mGroup.getBackgroundPermissions() != null) { 538 showGrantedByDefaultWarning |= 539 mGroup.getBackgroundPermissions().hasGrantedByDefaultPermission(); 540 } 541 } 542 543 args.putInt(DefaultDenyDialog.MSG, showGrantedByDefaultWarning ? R.string.system_warning 544 : R.string.old_sdk_deny_warning); 545 args.putString(DefaultDenyDialog.KEY, getKey()); 546 args.putInt(DefaultDenyDialog.CHANGE_TARGET, changeTarget); 547 548 DefaultDenyDialog deaultDenyDialog = new DefaultDenyDialog(); 549 deaultDenyDialog.setArguments(args); 550 deaultDenyDialog.show(mFragment.getChildFragmentManager().beginTransaction(), 551 "denyDefault"); 552 } 553 554 /** 555 * Show a dialog that asks the user if foreground/background/none access should be enabled. 556 * 557 * <p>The order of operation to grant foreground/background/none access is: 558 * <ol> 559 * <li>{@code showBackgroundChooserDialog}</li> 560 * <li>{@link BackgroundAccessChooser#onCreateDialog}</li> 561 * <li>{@link PermissionPreferenceOwnerFragment#onBackgroundAccessChosen}</li> 562 * <li>{@link PermissionPreference#onBackgroundAccessChosen}</li> 563 * </ol> 564 */ showBackgroundChooserDialog()565 private void showBackgroundChooserDialog() { 566 if (!mFragment.isResumed()) { 567 return; 568 } 569 570 if (LocationUtils.isLocationGroupAndProvider(getContext(), mGroup.getName(), 571 mGroup.getApp().packageName)) { 572 LocationUtils.showLocationDialog(getContext(), getAppLabel()); 573 return; 574 } 575 576 Bundle args = new Bundle(); 577 args.putCharSequence(BackgroundAccessChooser.TITLE, 578 getRequestMessage(getAppLabel(), mGroup, getContext(), mGroup.getRequest())); 579 args.putString(BackgroundAccessChooser.KEY, getKey()); 580 581 582 if (mGroup.areRuntimePermissionsGranted()) { 583 if (mGroup.getBackgroundPermissions().areRuntimePermissionsGranted()) { 584 args.putInt(BackgroundAccessChooser.SELECTION, 585 BackgroundAccessChooser.ALWAYS_OPTION); 586 } else { 587 args.putInt(BackgroundAccessChooser.SELECTION, 588 BackgroundAccessChooser.FOREGROUND_ONLY_OPTION); 589 } 590 } else { 591 args.putInt(BackgroundAccessChooser.SELECTION, BackgroundAccessChooser.NEVER_OPTION); 592 } 593 594 BackgroundAccessChooser chooserDialog = new BackgroundAccessChooser(); 595 chooserDialog.setArguments(args); 596 chooserDialog.show(mFragment.getChildFragmentManager().beginTransaction(), 597 "backgroundChooser"); 598 } 599 600 /** 601 * Once we user has confirmed that he/she wants to revoke a permission that was granted by 602 * default, actually revoke the permissions. 603 * 604 * @see #showDefaultDenyDialog(int) 605 */ onDenyAnyWay(@hangeTarget int changeTarget)606 void onDenyAnyWay(@ChangeTarget int changeTarget) { 607 mCallBacks.onPreferenceChanged(getKey()); 608 609 boolean hasDefaultPermissions = false; 610 if ((changeTarget & CHANGE_FOREGROUND) != 0) { 611 mGroup.revokeRuntimePermissions(false); 612 hasDefaultPermissions = mGroup.hasGrantedByDefaultPermission(); 613 } 614 if ((changeTarget & CHANGE_BACKGROUND) != 0) { 615 if (mGroup.getBackgroundPermissions() != null) { 616 mGroup.getBackgroundPermissions().revokeRuntimePermissions(false); 617 hasDefaultPermissions |= 618 mGroup.getBackgroundPermissions().hasGrantedByDefaultPermission(); 619 } 620 } 621 622 if (hasDefaultPermissions || !mGroup.doesSupportRuntimePermissions()) { 623 mCallBacks.hasConfirmDefaultPermissionRevoke(); 624 } 625 updateUi(); 626 } 627 628 /** 629 * Process the return from a {@link BackgroundAccessChooser} dialog. 630 * 631 * <p>These dialog are started when the user want to grant a permission group that has 632 * background permissions. 633 * 634 * @param choosenItem The item that the user chose 635 */ onBackgroundAccessChosen(int choosenItem)636 void onBackgroundAccessChosen(int choosenItem) { 637 AppPermissionGroup backgroundGroup = mGroup.getBackgroundPermissions(); 638 639 switch (choosenItem) { 640 case BackgroundAccessChooser.ALWAYS_OPTION: 641 requestChange(true, CHANGE_BOTH); 642 break; 643 case BackgroundAccessChooser.FOREGROUND_ONLY_OPTION: 644 if (backgroundGroup.areRuntimePermissionsGranted()) { 645 requestChange(false, CHANGE_BACKGROUND); 646 } 647 requestChange(true, CHANGE_FOREGROUND); 648 break; 649 case BackgroundAccessChooser.NEVER_OPTION: 650 if (mGroup.areRuntimePermissionsGranted() 651 || mGroup.getBackgroundPermissions().areRuntimePermissionsGranted()) { 652 requestChange(false, CHANGE_BOTH); 653 } 654 break; 655 } 656 } 657 658 /** 659 * A dialog warning the user that she/he is about to deny a permission that was granted by 660 * default. 661 * 662 * @see #showDefaultDenyDialog(int) 663 */ 664 public static class DefaultDenyDialog extends DialogFragment { 665 private static final String MSG = DefaultDenyDialog.class.getName() + ".arg.msg"; 666 private static final String CHANGE_TARGET = DefaultDenyDialog.class.getName() 667 + ".arg.changeTarget"; 668 private static final String KEY = DefaultDenyDialog.class.getName() + ".arg.key"; 669 670 @Override onCreateDialog(Bundle savedInstanceState)671 public Dialog onCreateDialog(Bundle savedInstanceState) { 672 AlertDialog.Builder b = new AlertDialog.Builder(getContext()) 673 .setMessage(getArguments().getInt(MSG)) 674 .setNegativeButton(R.string.cancel, null) 675 .setPositiveButton(R.string.grant_dialog_button_deny_anyway, 676 (DialogInterface dialog, int which) -> ( 677 (PermissionPreferenceOwnerFragment) getParentFragment()) 678 .onDenyAnyWay(getArguments().getString(KEY), 679 getArguments().getInt(CHANGE_TARGET))); 680 681 return b.create(); 682 } 683 } 684 685 /** 686 * If a permission group has background permission this chooser is used to let the user 687 * choose how the permission group should be granted. 688 * 689 * @see #showBackgroundChooserDialog() 690 */ 691 public static class BackgroundAccessChooser extends DialogFragment { 692 private static final String TITLE = BackgroundAccessChooser.class.getName() + ".arg.title"; 693 private static final String KEY = BackgroundAccessChooser.class.getName() + ".arg.key"; 694 private static final String SELECTION = BackgroundAccessChooser.class.getName() 695 + ".arg.selection"; 696 697 // Needs to match the entries in R.array.background_access_chooser_dialog_choices 698 static final int ALWAYS_OPTION = 0; 699 static final int FOREGROUND_ONLY_OPTION = 1; 700 static final int NEVER_OPTION = 2; 701 702 @Override onCreateDialog(Bundle savedInstanceState)703 public Dialog onCreateDialog(Bundle savedInstanceState) { 704 AlertDialog.Builder b = new AlertDialog.Builder(getActivity()) 705 .setTitle(getArguments().getCharSequence(TITLE)) 706 .setSingleChoiceItems(R.array.background_access_chooser_dialog_choices, 707 getArguments().getInt(SELECTION), 708 (dialog, which) -> { 709 dismissAllowingStateLoss(); 710 ((PermissionPreferenceOwnerFragment) getParentFragment()) 711 .onBackgroundAccessChosen(getArguments().getString(KEY), 712 which); 713 } 714 ); 715 716 return b.create(); 717 } 718 } 719 } 720