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