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