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 android.app.admin.DevicePolicyResources.Strings.PermissionSettings.BACKGROUND_ACCESS_DISABLED_BY_ADMIN_MESSAGE; 20 import static android.app.admin.DevicePolicyResources.Strings.PermissionSettings.BACKGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE; 21 import static android.app.admin.DevicePolicyResources.Strings.PermissionSettings.FOREGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE; 22 23 import static com.android.permissioncontroller.permission.utils.Utils.getRequestMessage; 24 25 import android.app.AlertDialog; 26 import android.app.Dialog; 27 import android.content.DialogInterface; 28 import android.os.Bundle; 29 import android.os.UserHandle; 30 import android.text.BidiFormatter; 31 import android.widget.Switch; 32 33 import androidx.annotation.LayoutRes; 34 import androidx.fragment.app.DialogFragment; 35 import androidx.preference.PreferenceFragmentCompat; 36 37 import com.android.permissioncontroller.R; 38 import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup; 39 import com.android.permissioncontroller.permission.ui.model.v33.ReviewPermissionsViewModel; 40 import com.android.permissioncontroller.permission.ui.model.v33.ReviewPermissionsViewModel.PermissionSummary; 41 import com.android.permissioncontroller.permission.ui.model.v33.ReviewPermissionsViewModel.PermissionTarget; 42 import com.android.permissioncontroller.permission.ui.model.v33.ReviewPermissionsViewModel.SummaryMessage; 43 import com.android.permissioncontroller.permission.utils.LocationUtils; 44 import com.android.permissioncontroller.permission.utils.Utils; 45 import com.android.settingslib.RestrictedLockUtils; 46 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; 47 48 /** 49 * A preference for representing a permission group requested by an app. 50 */ 51 class PermissionPreference extends MultiTargetSwitchPreference { 52 53 /** 54 * holds state for the permission group represented by this preference. 55 */ 56 private PermissionTarget mState = PermissionTarget.PERMISSION_NONE; 57 private final LightAppPermGroup mGroup; 58 private final ReviewPermissionsViewModel mViewModel; 59 private final PreferenceFragmentCompat mFragment; 60 private final PermissionPreferenceChangeListener mCallBacks; 61 private final @LayoutRes int mOriginalWidgetLayoutRes; 62 63 /** Callbacks for the permission to the fragment showing a list of permissions */ 64 interface PermissionPreferenceChangeListener { 65 /** 66 * Checks if the user has to confirm a revocation of a permission granted by default. 67 * 68 * @return {@code true} iff the user has to confirm it 69 */ shouldConfirmDefaultPermissionRevoke()70 boolean shouldConfirmDefaultPermissionRevoke(); 71 72 /** 73 * Notify the listener that the user confirmed that she/he wants to revoke permissions that 74 * were granted by default. 75 */ hasConfirmDefaultPermissionRevoke()76 void hasConfirmDefaultPermissionRevoke(); 77 78 /** 79 * Notify the listener that this preference has changed. 80 * 81 * @param key The key uniquely identifying this preference 82 */ onPreferenceChanged(String key)83 void onPreferenceChanged(String key); 84 } 85 86 /** 87 * Callbacks from dialogs to the fragment. These callbacks are supposed to directly cycle back 88 * to the permission that created the dialog. 89 */ 90 interface PermissionPreferenceOwnerFragment { 91 /** 92 * The {@link DefaultDenyDialog} can only interact with the fragment, not the preference 93 * that created it. Hence this call goes to the fragment, which then finds the preference an 94 * calls {@link #onDenyAnyWay(PermissionTarget)}. 95 * 96 * @param key Key uniquely identifying the preference that created the default deny dialog 97 * @param changeTarget Whether background or foreground permissions should be changed 98 * 99 * @see #showDefaultDenyDialog(PermissionTarget, boolean) 100 */ onDenyAnyWay(String key, PermissionTarget changeTarget)101 void onDenyAnyWay(String key, PermissionTarget changeTarget); 102 103 /** 104 * The {@link BackgroundAccessChooser} can only interact with the fragment, not the 105 * preference that created it. Hence this call goes to the fragment, which then finds the 106 * preference an calls {@link #onBackgroundAccessChosen(int)}}. 107 * 108 * @param key Key uniquely identifying the preference that created the background access 109 * chooser 110 * @param chosenItem The index of the item selected by the user. 111 * 112 * @see #showBackgroundChooserDialog() 113 */ onBackgroundAccessChosen(String key, int chosenItem)114 void onBackgroundAccessChosen(String key, int chosenItem); 115 } 116 PermissionPreference(PreferenceFragmentCompat fragment, LightAppPermGroup group, PermissionPreferenceChangeListener callbacks, ReviewPermissionsViewModel reviewPermissionsViewModel)117 PermissionPreference(PreferenceFragmentCompat fragment, LightAppPermGroup group, 118 PermissionPreferenceChangeListener callbacks, 119 ReviewPermissionsViewModel reviewPermissionsViewModel) { 120 super(fragment.getPreferenceManager().getContext()); 121 122 mFragment = fragment; 123 mGroup = group; 124 mViewModel = reviewPermissionsViewModel; 125 mCallBacks = callbacks; 126 mOriginalWidgetLayoutRes = getWidgetLayoutResource(); 127 setState(group); 128 setPersistent(false); 129 updateUi(); 130 } 131 getState()132 PermissionTarget getState() { 133 return mState; 134 } 135 setState(LightAppPermGroup appPermGroup)136 private void setState(LightAppPermGroup appPermGroup) { 137 if (appPermGroup.isReviewRequired()) { 138 mState = PermissionTarget.PERMISSION_FOREGROUND; 139 if (appPermGroup.getHasBackgroundGroup()) { 140 mState = PermissionTarget.PERMISSION_BOTH; 141 } 142 } 143 } 144 145 /** 146 * Update the preference after the state might have changed. 147 */ updateUi()148 void updateUi() { 149 boolean arePermissionsIndividuallyControlled = 150 Utils.areGroupPermissionsIndividuallyControlled(getContext(), 151 mGroup.getPermGroupName()); 152 EnforcedAdmin admin = mViewModel.getAdmin(getContext(), mGroup); 153 154 // Reset ui state 155 setEnabled(true); 156 setWidgetLayoutResource(mOriginalWidgetLayoutRes); 157 setOnPreferenceClickListener(null); 158 setSwitchOnClickListener(null); 159 setSummary(null); 160 161 setChecked(mState != PermissionTarget.PERMISSION_NONE); 162 163 if (mViewModel.isFixedOrForegroundDisabled(mGroup)) { 164 if (admin != null) { 165 setWidgetLayoutResource(R.layout.restricted_icon); 166 167 setOnPreferenceClickListener((v) -> { 168 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(), admin); 169 return true; 170 }); 171 } else { 172 setEnabled(false); 173 } 174 175 updateSummaryForFixedByPolicyPermissionGroup(); 176 } else if (arePermissionsIndividuallyControlled) { 177 setOnPreferenceClickListener((pref) -> { 178 Bundle args = AllAppPermissionsFragment.createArgs(mGroup.getPackageName(), 179 mGroup.getPermGroupName(), UserHandle.getUserHandleForUid( 180 mGroup.getPackageInfo().getUid())); 181 mViewModel.showAllPermissions(mFragment, args); 182 return false; 183 }); 184 185 setSwitchOnClickListener(v -> { 186 Switch switchView = (Switch) v; 187 requestChange(switchView.isChecked(), PermissionTarget.PERMISSION_BOTH); 188 189 // Update UI as the switch widget might be in wrong state 190 updateUi(); 191 }); 192 193 updateSummaryForIndividuallyControlledPermissionGroup(); 194 } else { 195 if (mGroup.getHasPermWithBackgroundMode()) { 196 if (!mGroup.getHasBackgroundGroup()) { 197 // The group has background permissions but the app did not request any. I.e. 198 // The app can only switch between 'never" and "only in foreground". 199 setOnPreferenceChangeListener((pref, newValue) -> 200 requestChange((Boolean) newValue, 201 PermissionTarget.PERMISSION_FOREGROUND)); 202 203 updateSummaryForPermissionGroupWithBackgroundPermission(); 204 } else { 205 if (mGroup.getBackground().isPolicyFixed()) { 206 setOnPreferenceChangeListener((pref, newValue) -> 207 requestChange((Boolean) newValue, 208 PermissionTarget.PERMISSION_FOREGROUND)); 209 210 updateSummaryForFixedByPolicyPermissionGroup(); 211 } else if (mGroup.getForeground().isPolicyFixed()) { 212 setOnPreferenceChangeListener((pref, newValue) -> 213 requestChange((Boolean) newValue, 214 PermissionTarget.PERMISSION_BACKGROUND)); 215 216 updateSummaryForFixedByPolicyPermissionGroup(); 217 } else { 218 updateSummaryForPermissionGroupWithBackgroundPermission(); 219 220 setOnPreferenceClickListener((pref) -> { 221 showBackgroundChooserDialog(); 222 return true; 223 }); 224 225 setSwitchOnClickListener(v -> { 226 Switch switchView = (Switch) v; 227 228 if (switchView.isChecked()) { 229 showBackgroundChooserDialog(); 230 } else { 231 requestChange(false, PermissionTarget.PERMISSION_BOTH); 232 } 233 234 // Update UI as the switch widget might be in wrong state 235 updateUi(); 236 }); 237 } 238 } 239 } else { 240 setOnPreferenceChangeListener((pref, newValue) -> 241 requestChange((Boolean) newValue, PermissionTarget.PERMISSION_BOTH)); 242 } 243 } 244 } 245 246 /** 247 * Update the summary in the case the permission group has individually controlled permissions. 248 */ updateSummaryForIndividuallyControlledPermissionGroup()249 private void updateSummaryForIndividuallyControlledPermissionGroup() { 250 PermissionSummary summary = mViewModel.getSummaryForIndividuallyControlledPermGroup(mGroup); 251 setSummary(getContext().getString(getResource(summary.getMsg()), summary.getRevokeCount())); 252 } 253 254 /** 255 * Update the summary of a permission group that has background permission. 256 * 257 * <p>This does not apply to permission groups that are fixed by policy</p> 258 */ updateSummaryForPermissionGroupWithBackgroundPermission()259 private void updateSummaryForPermissionGroupWithBackgroundPermission() { 260 PermissionSummary summary = mViewModel.getSummaryForPermGroupWithBackgroundPermission( 261 mState); 262 setSummary(getResource(summary.getMsg())); 263 } 264 265 /** 266 * Update the summary of a permission group that is at least partially fixed by policy. 267 */ updateSummaryForFixedByPolicyPermissionGroup()268 private void updateSummaryForFixedByPolicyPermissionGroup() { 269 PermissionSummary summary = mViewModel.getSummaryForFixedByPolicyPermissionGroup(mState, 270 mGroup, getContext()); 271 if (summary.getMsg() == SummaryMessage.NO_SUMMARY) { 272 return; 273 } 274 if (summary.isEnterprise()) { 275 switch (summary.getMsg()) { 276 case ENABLED_BY_ADMIN_BACKGROUND_ONLY: 277 setSummary(Utils.getEnterpriseString( 278 getContext(), 279 BACKGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE, 280 getResource(summary.getMsg()))); 281 break; 282 case DISABLED_BY_ADMIN_BACKGROUND_ONLY: 283 setSummary(Utils.getEnterpriseString( 284 getContext(), 285 BACKGROUND_ACCESS_DISABLED_BY_ADMIN_MESSAGE, 286 getResource(summary.getMsg()))); 287 break; 288 case ENABLED_BY_ADMIN_FOREGROUND_ONLY: 289 setSummary(Utils.getEnterpriseString( 290 getContext(), 291 FOREGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE, 292 getResource(summary.getMsg()))); 293 break; 294 default: 295 throw new IllegalArgumentException("Missing enterprise summary " 296 + "case for " + summary.getMsg()); 297 } 298 } else { 299 setSummary(getResource(summary.getMsg())); 300 } 301 } 302 getResource(SummaryMessage summary)303 int getResource(SummaryMessage summary) { 304 switch (summary) { 305 case DISABLED_BY_ADMIN: 306 return R.string.disabled_by_admin; 307 case ENABLED_BY_ADMIN: 308 return R.string.enabled_by_admin; 309 case ENABLED_SYSTEM_FIXED: 310 return R.string.permission_summary_enabled_system_fixed; 311 case ENFORCED_BY_POLICY: 312 return R.string.permission_summary_enforced_by_policy; 313 case ENABLED_BY_ADMIN_FOREGROUND_ONLY: 314 return R.string.permission_summary_enabled_by_admin_foreground_only; 315 case ENABLED_BY_POLICY_FOREGROUND_ONLY: 316 return R.string.permission_summary_enabled_by_policy_foreground_only; 317 case ENABLED_BY_ADMIN_BACKGROUND_ONLY: 318 return R.string.permission_summary_enabled_by_admin_background_only; 319 case ENABLED_BY_POLICY_BACKGROUND_ONLY: 320 return R.string.permission_summary_enabled_by_policy_foreground_only; 321 case DISABLED_BY_ADMIN_BACKGROUND_ONLY: 322 return R.string.permission_summary_disabled_by_admin_background_only; 323 case DISABLED_BY_POLICY_BACKGROUND_ONLY: 324 return R.string.permission_summary_disabled_by_policy_background_only; 325 case REVOKED_NONE: 326 return R.string.permission_revoked_none; 327 case REVOKED_ALL: 328 return R.string.permission_revoked_all; 329 case REVOKED_COUNT: 330 return R.string.permission_revoked_count; 331 case ACCESS_ALWAYS: 332 return R.string.permission_access_always; 333 case ACCESS_ONLY_FOREGROUND: 334 return R.string.permission_access_only_foreground; 335 case ACCESS_NEVER: 336 return R.string.permission_access_never; 337 default: 338 throw new IllegalArgumentException("No resource found"); 339 } 340 } 341 342 /** 343 * Get the label of the app the permission group belongs to. (App permission groups are all 344 * permissions of a group an app has requested.) 345 * 346 * @return The label of the app 347 */ getAppLabel()348 private String getAppLabel() { 349 String label = Utils.getAppLabel(mViewModel.getPackageInfo().applicationInfo, 350 mViewModel.getApp()); 351 return BidiFormatter.getInstance().unicodeWrap(label); 352 } 353 354 /** 355 * Request to grant/revoke permissions group. 356 * 357 * <p>Does <u>not</u> handle: 358 * <ul> 359 * <li>Individually granted permissions</li> 360 * <li>Permission groups with background permissions</li> 361 * </ul> 362 * <p><u>Does</u> handle: 363 * <ul> 364 * <li>Default grant permissions</li> 365 * </ul> 366 * 367 * @param requestGrant If this group should be granted 368 * @param changeTarget Which permission group (foreground/background/both) should be changed 369 * @return If the request was processed. 370 */ requestChange(boolean requestGrant, PermissionTarget changeTarget)371 private boolean requestChange(boolean requestGrant, PermissionTarget changeTarget) { 372 if (LocationUtils.isLocationGroupAndProvider(getContext(), mGroup.getPermGroupName(), 373 mGroup.getPackageName())) { 374 LocationUtils.showLocationDialog(getContext(), getAppLabel()); 375 return false; 376 } 377 if (requestGrant) { 378 mCallBacks.onPreferenceChanged(getKey()); 379 //allow additional state 380 mState = PermissionTarget.Companion.fromInt(mState.or(changeTarget)); 381 } else { 382 boolean requestToRevokeGrantedByDefault = false; 383 if (changeTarget.and(PermissionTarget.PERMISSION_FOREGROUND) 384 != PermissionTarget.PERMISSION_NONE.getValue()) { 385 requestToRevokeGrantedByDefault = mGroup.isGrantedByDefault(); 386 } 387 if (changeTarget.and(PermissionTarget.PERMISSION_BACKGROUND) 388 != PermissionTarget.PERMISSION_NONE.getValue()) { 389 if (mGroup.getHasBackgroundGroup()) { 390 requestToRevokeGrantedByDefault |= 391 mGroup.getBackground().isGrantedByDefault(); 392 } 393 } 394 395 if ((requestToRevokeGrantedByDefault || !mGroup.getSupportsRuntimePerms()) 396 && mCallBacks.shouldConfirmDefaultPermissionRevoke()) { 397 showDefaultDenyDialog(changeTarget, requestToRevokeGrantedByDefault); 398 return false; 399 } else { 400 mCallBacks.onPreferenceChanged(getKey()); 401 mState = PermissionTarget.Companion.fromInt(mState.and(~changeTarget.getValue())); 402 } 403 } 404 405 updateUi(); 406 407 return true; 408 } 409 410 /** 411 * Show a dialog that warns the user that she/he is about to revoke permissions that were 412 * granted by default. 413 * 414 * <p>The order of operation to revoke a permission granted by default is: 415 * <ol> 416 * <li>{@code showDefaultDenyDialog}</li> 417 * <li>{@link DefaultDenyDialog#onCreateDialog}</li> 418 * <li>{@link PermissionPreferenceOwnerFragment#onDenyAnyWay}</li> 419 * <li>{@link PermissionPreference#onDenyAnyWay}</li> 420 * </ol> 421 * 422 * @param changeTarget Whether background or foreground should be changed 423 */ showDefaultDenyDialog(PermissionTarget changeTarget, boolean showGrantedByDefaultWarning)424 private void showDefaultDenyDialog(PermissionTarget changeTarget, 425 boolean showGrantedByDefaultWarning) { 426 if (!mFragment.isResumed()) { 427 return; 428 } 429 430 Bundle args = new Bundle(); 431 args.putInt(DefaultDenyDialog.MSG, showGrantedByDefaultWarning ? R.string.system_warning 432 : R.string.old_sdk_deny_warning); 433 args.putString(DefaultDenyDialog.KEY, getKey()); 434 args.putInt(DefaultDenyDialog.CHANGE_TARGET, changeTarget.getValue()); 435 436 DefaultDenyDialog deaultDenyDialog = new DefaultDenyDialog(); 437 deaultDenyDialog.setArguments(args); 438 deaultDenyDialog.show(mFragment.getChildFragmentManager().beginTransaction(), 439 "denyDefault"); 440 } 441 442 /** 443 * Show a dialog that asks the user if foreground/background/none access should be enabled. 444 * 445 * <p>The order of operation to grant foreground/background/none access is: 446 * <ol> 447 * <li>{@code showBackgroundChooserDialog}</li> 448 * <li>{@link BackgroundAccessChooser#onCreateDialog}</li> 449 * <li>{@link PermissionPreferenceOwnerFragment#onBackgroundAccessChosen}</li> 450 * <li>{@link PermissionPreference#onBackgroundAccessChosen}</li> 451 * </ol> 452 */ showBackgroundChooserDialog()453 private void showBackgroundChooserDialog() { 454 if (!mFragment.isResumed()) { 455 return; 456 } 457 458 if (LocationUtils.isLocationGroupAndProvider(getContext(), mGroup.getPermGroupName(), 459 mGroup.getPackageName())) { 460 LocationUtils.showLocationDialog(getContext(), getAppLabel()); 461 return; 462 } 463 464 Bundle args = new Bundle(); 465 args.putCharSequence(BackgroundAccessChooser.TITLE, 466 getRequestMessage(getAppLabel(), mGroup.getPackageName(), mGroup.getPermGroupName(), 467 getContext(), Utils.getRequest(mGroup.getPermGroupName()))); 468 args.putString(BackgroundAccessChooser.KEY, getKey()); 469 470 471 if (mState != PermissionTarget.PERMISSION_NONE) { 472 if (mState == PermissionTarget.PERMISSION_BOTH) { 473 args.putInt(BackgroundAccessChooser.SELECTION, 474 BackgroundAccessChooser.ALWAYS_OPTION); 475 } else { 476 args.putInt(BackgroundAccessChooser.SELECTION, 477 BackgroundAccessChooser.FOREGROUND_ONLY_OPTION); 478 } 479 } else { 480 args.putInt(BackgroundAccessChooser.SELECTION, BackgroundAccessChooser.NEVER_OPTION); 481 } 482 483 BackgroundAccessChooser chooserDialog = new BackgroundAccessChooser(); 484 chooserDialog.setArguments(args); 485 chooserDialog.show(mFragment.getChildFragmentManager().beginTransaction(), 486 "backgroundChooser"); 487 } 488 489 /** 490 * Once we user has confirmed that he/she wants to revoke a permission that was granted by 491 * default, actually revoke the permissions. 492 * 493 * @see #showDefaultDenyDialog(PermissionTarget, boolean) 494 */ onDenyAnyWay(PermissionTarget changeTarget)495 void onDenyAnyWay(PermissionTarget changeTarget) { 496 mCallBacks.onPreferenceChanged(getKey()); 497 498 boolean hasDefaultPermissions = false; 499 if (changeTarget.and(PermissionTarget.PERMISSION_FOREGROUND) 500 != PermissionTarget.PERMISSION_NONE.getValue()) { 501 hasDefaultPermissions = mGroup.isGrantedByDefault(); 502 mState = PermissionTarget.Companion.fromInt(mState.and( 503 ~PermissionTarget.PERMISSION_FOREGROUND.getValue())); 504 } 505 if (changeTarget.and(PermissionTarget.PERMISSION_BACKGROUND) 506 != PermissionTarget.PERMISSION_NONE.getValue()) { 507 if (mGroup.getHasBackgroundGroup()) { 508 hasDefaultPermissions |= mGroup.getBackground().isGrantedByDefault(); 509 mState = PermissionTarget.Companion.fromInt(mState.and( 510 ~PermissionTarget.PERMISSION_BACKGROUND.getValue())); 511 } 512 } 513 514 if (hasDefaultPermissions || !mGroup.getSupportsRuntimePerms()) { 515 mCallBacks.hasConfirmDefaultPermissionRevoke(); 516 } 517 updateUi(); 518 } 519 520 /** 521 * Process the return from a {@link BackgroundAccessChooser} dialog. 522 * 523 * <p>These dialog are started when the user want to grant a permission group that has 524 * background permissions. 525 * 526 * @param choosenItem The item that the user chose 527 */ onBackgroundAccessChosen(int choosenItem)528 void onBackgroundAccessChosen(int choosenItem) { 529 530 switch (choosenItem) { 531 case BackgroundAccessChooser.ALWAYS_OPTION: 532 requestChange(true, PermissionTarget.PERMISSION_BOTH); 533 break; 534 case BackgroundAccessChooser.FOREGROUND_ONLY_OPTION: 535 if (mState.and(PermissionTarget.PERMISSION_BACKGROUND) 536 != PermissionTarget.PERMISSION_NONE.getValue()) { 537 requestChange(false, PermissionTarget.PERMISSION_BACKGROUND); 538 } 539 requestChange(true, PermissionTarget.PERMISSION_FOREGROUND); 540 break; 541 case BackgroundAccessChooser.NEVER_OPTION: 542 if (mState != PermissionTarget.PERMISSION_NONE) { 543 requestChange(false, PermissionTarget.PERMISSION_BOTH); 544 } 545 break; 546 } 547 } 548 549 /** 550 * A dialog warning the user that she/he is about to deny a permission that was granted by 551 * default. 552 * 553 * @see #showDefaultDenyDialog(PermissionTarget, boolean) 554 */ 555 public static class DefaultDenyDialog extends DialogFragment { 556 private static final String MSG = DefaultDenyDialog.class.getName() + ".arg.msg"; 557 private static final String CHANGE_TARGET = DefaultDenyDialog.class.getName() 558 + ".arg.changeTarget"; 559 private static final String KEY = DefaultDenyDialog.class.getName() + ".arg.key"; 560 561 @Override onCreateDialog(Bundle savedInstanceState)562 public Dialog onCreateDialog(Bundle savedInstanceState) { 563 AlertDialog.Builder b = new AlertDialog.Builder(getContext()) 564 .setMessage(getArguments().getInt(MSG)) 565 .setNegativeButton(R.string.cancel, null) 566 .setPositiveButton(R.string.grant_dialog_button_deny_anyway, 567 (DialogInterface dialog, int which) -> ( 568 (PermissionPreferenceOwnerFragment) getParentFragment()) 569 .onDenyAnyWay(getArguments().getString(KEY), 570 PermissionTarget.Companion.fromInt( 571 getArguments().getInt(CHANGE_TARGET)))); 572 573 return b.create(); 574 } 575 } 576 577 /** 578 * If a permission group has background permission this chooser is used to let the user 579 * choose how the permission group should be granted. 580 * 581 * @see #showBackgroundChooserDialog() 582 */ 583 public static class BackgroundAccessChooser extends DialogFragment { 584 private static final String TITLE = BackgroundAccessChooser.class.getName() + ".arg.title"; 585 private static final String KEY = BackgroundAccessChooser.class.getName() + ".arg.key"; 586 private static final String SELECTION = BackgroundAccessChooser.class.getName() 587 + ".arg.selection"; 588 589 // Needs to match the entries in R.array.background_access_chooser_dialog_choices 590 static final int ALWAYS_OPTION = 0; 591 static final int FOREGROUND_ONLY_OPTION = 1; 592 static final int NEVER_OPTION = 2; 593 594 @Override onCreateDialog(Bundle savedInstanceState)595 public Dialog onCreateDialog(Bundle savedInstanceState) { 596 AlertDialog.Builder b = new AlertDialog.Builder(getActivity()) 597 .setTitle(getArguments().getCharSequence(TITLE)) 598 .setSingleChoiceItems(R.array.background_access_chooser_dialog_choices, 599 getArguments().getInt(SELECTION), 600 (dialog, which) -> { 601 dismissAllowingStateLoss(); 602 ((PermissionPreferenceOwnerFragment) getParentFragment()) 603 .onBackgroundAccessChosen(getArguments().getString(KEY), 604 which); 605 } 606 ); 607 608 return b.create(); 609 } 610 } 611 } 612