• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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