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.ReviewPermissionsViewModel; 40 import com.android.permissioncontroller.permission.ui.model.ReviewPermissionsViewModel.PermissionSummary; 41 import com.android.permissioncontroller.permission.ui.model.ReviewPermissionsViewModel.PermissionTarget; 42 import com.android.permissioncontroller.permission.ui.model.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( 166 com.android.settingslib.widget.restricted.R.layout.restricted_icon); 167 168 setOnPreferenceClickListener((v) -> { 169 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(), admin); 170 return true; 171 }); 172 } else { 173 setEnabled(false); 174 } 175 176 updateSummaryForFixedByPolicyPermissionGroup(); 177 } else if (arePermissionsIndividuallyControlled) { 178 setOnPreferenceClickListener((pref) -> { 179 Bundle args = AllAppPermissionsFragment.createArgs(mGroup.getPackageName(), 180 mGroup.getPermGroupName(), UserHandle.getUserHandleForUid( 181 mGroup.getPackageInfo().getUid())); 182 mViewModel.showAllPermissions(mFragment, args); 183 return false; 184 }); 185 186 setSwitchOnClickListener(v -> { 187 Switch switchView = (Switch) v; 188 requestChange(switchView.isChecked(), PermissionTarget.PERMISSION_BOTH); 189 190 // Update UI as the switch widget might be in wrong state 191 updateUi(); 192 }); 193 194 updateSummaryForIndividuallyControlledPermissionGroup(); 195 } else { 196 if (mGroup.getHasPermWithBackgroundMode()) { 197 if (!mGroup.getHasBackgroundGroup()) { 198 // The group has background permissions but the app did not request any. I.e. 199 // The app can only switch between 'never" and "only in foreground". 200 setOnPreferenceChangeListener((pref, newValue) -> 201 requestChange((Boolean) newValue, 202 PermissionTarget.PERMISSION_FOREGROUND)); 203 204 updateSummaryForPermissionGroupWithBackgroundPermission(); 205 } else { 206 if (mGroup.getBackground().isPolicyFixed()) { 207 setOnPreferenceChangeListener((pref, newValue) -> 208 requestChange((Boolean) newValue, 209 PermissionTarget.PERMISSION_FOREGROUND)); 210 211 updateSummaryForFixedByPolicyPermissionGroup(); 212 } else if (mGroup.getForeground().isPolicyFixed()) { 213 setOnPreferenceChangeListener((pref, newValue) -> 214 requestChange((Boolean) newValue, 215 PermissionTarget.PERMISSION_BACKGROUND)); 216 217 updateSummaryForFixedByPolicyPermissionGroup(); 218 } else { 219 updateSummaryForPermissionGroupWithBackgroundPermission(); 220 221 setOnPreferenceClickListener((pref) -> { 222 showBackgroundChooserDialog(); 223 return true; 224 }); 225 226 setSwitchOnClickListener(v -> { 227 Switch switchView = (Switch) v; 228 229 if (switchView.isChecked()) { 230 showBackgroundChooserDialog(); 231 } else { 232 requestChange(false, PermissionTarget.PERMISSION_BOTH); 233 } 234 235 // Update UI as the switch widget might be in wrong state 236 updateUi(); 237 }); 238 } 239 } 240 } else { 241 setOnPreferenceChangeListener((pref, newValue) -> 242 requestChange((Boolean) newValue, PermissionTarget.PERMISSION_BOTH)); 243 } 244 } 245 } 246 247 /** 248 * Update the summary in the case the permission group has individually controlled permissions. 249 */ updateSummaryForIndividuallyControlledPermissionGroup()250 private void updateSummaryForIndividuallyControlledPermissionGroup() { 251 PermissionSummary summary = mViewModel.getSummaryForIndividuallyControlledPermGroup(mGroup); 252 setSummary(getContext().getString(getResource(summary.getMsg()), summary.getRevokeCount())); 253 } 254 255 /** 256 * Update the summary of a permission group that has background permission. 257 * 258 * <p>This does not apply to permission groups that are fixed by policy</p> 259 */ updateSummaryForPermissionGroupWithBackgroundPermission()260 private void updateSummaryForPermissionGroupWithBackgroundPermission() { 261 PermissionSummary summary = mViewModel.getSummaryForPermGroupWithBackgroundPermission( 262 mState); 263 setSummary(getResource(summary.getMsg())); 264 } 265 266 /** 267 * Update the summary of a permission group that is at least partially fixed by policy. 268 */ updateSummaryForFixedByPolicyPermissionGroup()269 private void updateSummaryForFixedByPolicyPermissionGroup() { 270 PermissionSummary summary = mViewModel.getSummaryForFixedByPolicyPermissionGroup(mState, 271 mGroup, getContext()); 272 if (summary.getMsg() == SummaryMessage.NO_SUMMARY) { 273 return; 274 } 275 if (summary.isEnterprise()) { 276 switch (summary.getMsg()) { 277 case ENABLED_BY_ADMIN_BACKGROUND_ONLY: 278 setSummary(Utils.getEnterpriseString( 279 getContext(), 280 BACKGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE, 281 getResource(summary.getMsg()))); 282 break; 283 case DISABLED_BY_ADMIN_BACKGROUND_ONLY: 284 setSummary(Utils.getEnterpriseString( 285 getContext(), 286 BACKGROUND_ACCESS_DISABLED_BY_ADMIN_MESSAGE, 287 getResource(summary.getMsg()))); 288 break; 289 case ENABLED_BY_ADMIN_FOREGROUND_ONLY: 290 setSummary(Utils.getEnterpriseString( 291 getContext(), 292 FOREGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE, 293 getResource(summary.getMsg()))); 294 break; 295 default: 296 throw new IllegalArgumentException("Missing enterprise summary " 297 + "case for " + summary.getMsg()); 298 } 299 } else { 300 setSummary(getResource(summary.getMsg())); 301 } 302 } 303 getResource(SummaryMessage summary)304 int getResource(SummaryMessage summary) { 305 switch (summary) { 306 case DISABLED_BY_ADMIN: 307 return com.android.settingslib.widget.restricted.R.string.disabled_by_admin; 308 case ENABLED_BY_ADMIN: 309 return com.android.settingslib.widget.restricted.R.string.enabled_by_admin; 310 case ENABLED_SYSTEM_FIXED: 311 return R.string.permission_summary_enabled_system_fixed; 312 case ENFORCED_BY_POLICY: 313 return R.string.permission_summary_enforced_by_policy; 314 case ENABLED_BY_ADMIN_FOREGROUND_ONLY: 315 return R.string.permission_summary_enabled_by_admin_foreground_only; 316 case ENABLED_BY_POLICY_FOREGROUND_ONLY: 317 return R.string.permission_summary_enabled_by_policy_foreground_only; 318 case ENABLED_BY_ADMIN_BACKGROUND_ONLY: 319 return R.string.permission_summary_enabled_by_admin_background_only; 320 case ENABLED_BY_POLICY_BACKGROUND_ONLY: 321 return R.string.permission_summary_enabled_by_policy_foreground_only; 322 case DISABLED_BY_ADMIN_BACKGROUND_ONLY: 323 return R.string.permission_summary_disabled_by_admin_background_only; 324 case DISABLED_BY_POLICY_BACKGROUND_ONLY: 325 return R.string.permission_summary_disabled_by_policy_background_only; 326 case REVOKED_NONE: 327 return R.string.permission_revoked_none; 328 case REVOKED_ALL: 329 return R.string.permission_revoked_all; 330 case REVOKED_COUNT: 331 return R.string.permission_revoked_count; 332 case ACCESS_ALWAYS: 333 return R.string.permission_access_always; 334 case ACCESS_ONLY_FOREGROUND: 335 return R.string.permission_access_only_foreground; 336 case ACCESS_NEVER: 337 return R.string.permission_access_never; 338 default: 339 throw new IllegalArgumentException("No resource found"); 340 } 341 } 342 343 /** 344 * Get the label of the app the permission group belongs to. (App permission groups are all 345 * permissions of a group an app has requested.) 346 * 347 * @return The label of the app 348 */ getAppLabel()349 private String getAppLabel() { 350 String label = Utils.getAppLabel(mViewModel.getPackageInfo().applicationInfo, 351 mViewModel.getApp()); 352 return BidiFormatter.getInstance().unicodeWrap(label); 353 } 354 355 /** 356 * Request to grant/revoke permissions group. 357 * 358 * <p>Does <u>not</u> handle: 359 * <ul> 360 * <li>Individually granted permissions</li> 361 * <li>Permission groups with background permissions</li> 362 * </ul> 363 * <p><u>Does</u> handle: 364 * <ul> 365 * <li>Default grant permissions</li> 366 * </ul> 367 * 368 * @param requestGrant If this group should be granted 369 * @param changeTarget Which permission group (foreground/background/both) should be changed 370 * @return If the request was processed. 371 */ requestChange(boolean requestGrant, PermissionTarget changeTarget)372 private boolean requestChange(boolean requestGrant, PermissionTarget changeTarget) { 373 if (LocationUtils.isLocationGroupAndProvider(getContext(), mGroup.getPermGroupName(), 374 mGroup.getPackageName())) { 375 LocationUtils.showLocationDialog(getContext(), getAppLabel()); 376 return false; 377 } 378 if (requestGrant) { 379 mCallBacks.onPreferenceChanged(getKey()); 380 //allow additional state 381 mState = PermissionTarget.Companion.fromInt(mState.or(changeTarget)); 382 } else { 383 boolean requestToRevokeGrantedByDefault = false; 384 if (changeTarget.and(PermissionTarget.PERMISSION_FOREGROUND) 385 != PermissionTarget.PERMISSION_NONE.getValue()) { 386 requestToRevokeGrantedByDefault = mGroup.isGrantedByDefault(); 387 } 388 if (changeTarget.and(PermissionTarget.PERMISSION_BACKGROUND) 389 != PermissionTarget.PERMISSION_NONE.getValue()) { 390 if (mGroup.getHasBackgroundGroup()) { 391 requestToRevokeGrantedByDefault |= 392 mGroup.getBackground().isGrantedByDefault(); 393 } 394 } 395 396 if ((requestToRevokeGrantedByDefault || !mGroup.getSupportsRuntimePerms()) 397 && mCallBacks.shouldConfirmDefaultPermissionRevoke()) { 398 showDefaultDenyDialog(changeTarget, requestToRevokeGrantedByDefault); 399 return false; 400 } else { 401 mCallBacks.onPreferenceChanged(getKey()); 402 mState = PermissionTarget.Companion.fromInt(mState.and(~changeTarget.getValue())); 403 } 404 } 405 406 updateUi(); 407 408 return true; 409 } 410 411 /** 412 * Show a dialog that warns the user that she/he is about to revoke permissions that were 413 * granted by default. 414 * 415 * <p>The order of operation to revoke a permission granted by default is: 416 * <ol> 417 * <li>{@code showDefaultDenyDialog}</li> 418 * <li>{@link DefaultDenyDialog#onCreateDialog}</li> 419 * <li>{@link PermissionPreferenceOwnerFragment#onDenyAnyWay}</li> 420 * <li>{@link PermissionPreference#onDenyAnyWay}</li> 421 * </ol> 422 * 423 * @param changeTarget Whether background or foreground should be changed 424 */ showDefaultDenyDialog(PermissionTarget changeTarget, boolean showGrantedByDefaultWarning)425 private void showDefaultDenyDialog(PermissionTarget changeTarget, 426 boolean showGrantedByDefaultWarning) { 427 if (!mFragment.isResumed()) { 428 return; 429 } 430 431 Bundle args = new Bundle(); 432 args.putInt(DefaultDenyDialog.MSG, showGrantedByDefaultWarning ? R.string.system_warning 433 : R.string.old_sdk_deny_warning); 434 args.putString(DefaultDenyDialog.KEY, getKey()); 435 args.putInt(DefaultDenyDialog.CHANGE_TARGET, changeTarget.getValue()); 436 437 DefaultDenyDialog deaultDenyDialog = new DefaultDenyDialog(); 438 deaultDenyDialog.setArguments(args); 439 deaultDenyDialog.show(mFragment.getChildFragmentManager().beginTransaction(), 440 "denyDefault"); 441 } 442 443 /** 444 * Show a dialog that asks the user if foreground/background/none access should be enabled. 445 * 446 * <p>The order of operation to grant foreground/background/none access is: 447 * <ol> 448 * <li>{@code showBackgroundChooserDialog}</li> 449 * <li>{@link BackgroundAccessChooser#onCreateDialog}</li> 450 * <li>{@link PermissionPreferenceOwnerFragment#onBackgroundAccessChosen}</li> 451 * <li>{@link PermissionPreference#onBackgroundAccessChosen}</li> 452 * </ol> 453 */ showBackgroundChooserDialog()454 private void showBackgroundChooserDialog() { 455 if (!mFragment.isResumed()) { 456 return; 457 } 458 459 if (LocationUtils.isLocationGroupAndProvider(getContext(), mGroup.getPermGroupName(), 460 mGroup.getPackageName())) { 461 LocationUtils.showLocationDialog(getContext(), getAppLabel()); 462 return; 463 } 464 465 Bundle args = new Bundle(); 466 args.putCharSequence(BackgroundAccessChooser.TITLE, 467 getRequestMessage(getAppLabel(), mGroup.getPackageName(), mGroup.getPermGroupName(), 468 getContext(), Utils.getRequest(mGroup.getPermGroupName()))); 469 args.putString(BackgroundAccessChooser.KEY, getKey()); 470 471 472 if (mState != PermissionTarget.PERMISSION_NONE) { 473 if (mState == PermissionTarget.PERMISSION_BOTH) { 474 args.putInt(BackgroundAccessChooser.SELECTION, 475 BackgroundAccessChooser.ALWAYS_OPTION); 476 } else { 477 args.putInt(BackgroundAccessChooser.SELECTION, 478 BackgroundAccessChooser.FOREGROUND_ONLY_OPTION); 479 } 480 } else { 481 args.putInt(BackgroundAccessChooser.SELECTION, BackgroundAccessChooser.NEVER_OPTION); 482 } 483 484 BackgroundAccessChooser chooserDialog = new BackgroundAccessChooser(); 485 chooserDialog.setArguments(args); 486 chooserDialog.show(mFragment.getChildFragmentManager().beginTransaction(), 487 "backgroundChooser"); 488 } 489 490 /** 491 * Once we user has confirmed that he/she wants to revoke a permission that was granted by 492 * default, actually revoke the permissions. 493 * 494 * @see #showDefaultDenyDialog(PermissionTarget, boolean) 495 */ onDenyAnyWay(PermissionTarget changeTarget)496 void onDenyAnyWay(PermissionTarget changeTarget) { 497 mCallBacks.onPreferenceChanged(getKey()); 498 499 boolean hasDefaultPermissions = false; 500 if (changeTarget.and(PermissionTarget.PERMISSION_FOREGROUND) 501 != PermissionTarget.PERMISSION_NONE.getValue()) { 502 hasDefaultPermissions = mGroup.isGrantedByDefault(); 503 mState = PermissionTarget.Companion.fromInt(mState.and( 504 ~PermissionTarget.PERMISSION_FOREGROUND.getValue())); 505 } 506 if (changeTarget.and(PermissionTarget.PERMISSION_BACKGROUND) 507 != PermissionTarget.PERMISSION_NONE.getValue()) { 508 if (mGroup.getHasBackgroundGroup()) { 509 hasDefaultPermissions |= mGroup.getBackground().isGrantedByDefault(); 510 mState = PermissionTarget.Companion.fromInt(mState.and( 511 ~PermissionTarget.PERMISSION_BACKGROUND.getValue())); 512 } 513 } 514 515 if (hasDefaultPermissions || !mGroup.getSupportsRuntimePerms()) { 516 mCallBacks.hasConfirmDefaultPermissionRevoke(); 517 } 518 updateUi(); 519 } 520 521 /** 522 * Process the return from a {@link BackgroundAccessChooser} dialog. 523 * 524 * <p>These dialog are started when the user want to grant a permission group that has 525 * background permissions. 526 * 527 * @param choosenItem The item that the user chose 528 */ onBackgroundAccessChosen(int choosenItem)529 void onBackgroundAccessChosen(int choosenItem) { 530 531 switch (choosenItem) { 532 case BackgroundAccessChooser.ALWAYS_OPTION: 533 requestChange(true, PermissionTarget.PERMISSION_BOTH); 534 break; 535 case BackgroundAccessChooser.FOREGROUND_ONLY_OPTION: 536 if (mState.and(PermissionTarget.PERMISSION_BACKGROUND) 537 != PermissionTarget.PERMISSION_NONE.getValue()) { 538 requestChange(false, PermissionTarget.PERMISSION_BACKGROUND); 539 } 540 requestChange(true, PermissionTarget.PERMISSION_FOREGROUND); 541 break; 542 case BackgroundAccessChooser.NEVER_OPTION: 543 if (mState != PermissionTarget.PERMISSION_NONE) { 544 requestChange(false, PermissionTarget.PERMISSION_BOTH); 545 } 546 break; 547 } 548 } 549 550 /** 551 * A dialog warning the user that she/he is about to deny a permission that was granted by 552 * default. 553 * 554 * @see #showDefaultDenyDialog(PermissionTarget, boolean) 555 */ 556 public static class DefaultDenyDialog extends DialogFragment { 557 private static final String MSG = DefaultDenyDialog.class.getName() + ".arg.msg"; 558 private static final String CHANGE_TARGET = DefaultDenyDialog.class.getName() 559 + ".arg.changeTarget"; 560 private static final String KEY = DefaultDenyDialog.class.getName() + ".arg.key"; 561 562 @Override onCreateDialog(Bundle savedInstanceState)563 public Dialog onCreateDialog(Bundle savedInstanceState) { 564 AlertDialog.Builder b = new AlertDialog.Builder(getContext()) 565 .setMessage(getArguments().getInt(MSG)) 566 .setNegativeButton(R.string.cancel, null) 567 .setPositiveButton(R.string.grant_dialog_button_deny_anyway, 568 (DialogInterface dialog, int which) -> ( 569 (PermissionPreferenceOwnerFragment) getParentFragment()) 570 .onDenyAnyWay(getArguments().getString(KEY), 571 PermissionTarget.Companion.fromInt( 572 getArguments().getInt(CHANGE_TARGET)))); 573 574 return b.create(); 575 } 576 } 577 578 /** 579 * If a permission group has background permission this chooser is used to let the user 580 * choose how the permission group should be granted. 581 * 582 * @see #showBackgroundChooserDialog() 583 */ 584 public static class BackgroundAccessChooser extends DialogFragment { 585 private static final String TITLE = BackgroundAccessChooser.class.getName() + ".arg.title"; 586 private static final String KEY = BackgroundAccessChooser.class.getName() + ".arg.key"; 587 private static final String SELECTION = BackgroundAccessChooser.class.getName() 588 + ".arg.selection"; 589 590 // Needs to match the entries in R.array.background_access_chooser_dialog_choices 591 static final int ALWAYS_OPTION = 0; 592 static final int FOREGROUND_ONLY_OPTION = 1; 593 static final int NEVER_OPTION = 2; 594 595 @Override onCreateDialog(Bundle savedInstanceState)596 public Dialog onCreateDialog(Bundle savedInstanceState) { 597 AlertDialog.Builder b = new AlertDialog.Builder(getActivity()) 598 .setTitle(getArguments().getCharSequence(TITLE)) 599 .setSingleChoiceItems(R.array.background_access_chooser_dialog_choices, 600 getArguments().getInt(SELECTION), 601 (dialog, which) -> { 602 dismissAllowingStateLoss(); 603 ((PermissionPreferenceOwnerFragment) getParentFragment()) 604 .onBackgroundAccessChosen(getArguments().getString(KEY), 605 which); 606 } 607 ); 608 609 return b.create(); 610 } 611 } 612 } 613