1 /*
2  * Copyright (C) 2020 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.television;
18 
19 import static android.Manifest.permission_group.STORAGE;
20 
21 import static com.android.permissioncontroller.Constants.EXTRA_SESSION_ID;
22 import static com.android.permissioncontroller.Constants.INVALID_SESSION_ID;
23 import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW;
24 import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW_ALWAYS;
25 import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW_FOREGROUND;
26 import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ASK_EVERY_TIME;
27 import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__DENY;
28 import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__DENY_FOREGROUND;
29 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED;
30 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED_DO_NOT_ASK_AGAIN;
31 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_ALWAYS;
32 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_FOREGROUND_ONLY;
33 import static com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_CALLER_NAME;
34 import static com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_INTERACTED;
35 import static com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_RESULT;
36 import static com.android.permissioncontroller.permission.ui.handheld.UtilsKt.pressBack;
37 
38 import android.app.Activity;
39 import android.app.AlertDialog;
40 import android.app.Dialog;
41 import android.content.Context;
42 import android.content.DialogInterface;
43 import android.content.Intent;
44 import android.content.pm.PackageInfo;
45 import android.content.pm.PackageManager;
46 import android.graphics.drawable.Drawable;
47 import android.os.Bundle;
48 import android.os.Handler;
49 import android.os.Looper;
50 import android.os.UserHandle;
51 import android.text.BidiFormatter;
52 import android.util.Log;
53 import android.view.LayoutInflater;
54 import android.view.View;
55 import android.view.ViewGroup;
56 import android.widget.Toast;
57 
58 import androidx.annotation.NonNull;
59 import androidx.annotation.Nullable;
60 import androidx.annotation.StringRes;
61 import androidx.fragment.app.DialogFragment;
62 import androidx.preference.Preference;
63 import androidx.preference.PreferenceScreen;
64 import androidx.preference.PreferenceViewHolder;
65 import androidx.lifecycle.ViewModelProvider;
66 
67 import com.android.permissioncontroller.permission.model.AppPermissionGroup;
68 import com.android.permissioncontroller.permission.model.AppPermissions;
69 import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler;
70 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel;
71 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonState;
72 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType;
73 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ChangeRequest;
74 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModelFactory;
75 import com.android.permissioncontroller.permission.utils.KotlinUtils;
76 import com.android.permissioncontroller.permission.utils.Utils;
77 import com.android.permissioncontroller.R;
78 
79 import java.util.Map;
80 import java.util.Objects;
81 
82 /**
83  * Show and manage a single permission group for an app.
84  *
85  * <p>Allows the user to control whether the app is granted the permission.
86  */
87 public class AppPermissionFragment extends SettingsWithHeader
88         implements AppPermissionViewModel.ConfirmDialogShowingFragment {
89     private static final String LOG_TAG = "AppPermissionFragment";
90     private static final long POST_DELAY_MS = 20;
91 
92     static final String GRANT_CATEGORY = "grant_category";
93 
94     private @NonNull AppPermissionViewModel mViewModel;
95     private @NonNull RadioButtonPreference mAllowButton;
96     private @NonNull RadioButtonPreference mAllowAlwaysButton;
97     private @NonNull RadioButtonPreference mAllowForegroundButton;
98     private @NonNull RadioButtonPreference mAskOneTimeButton;
99     private @NonNull RadioButtonPreference mAskButton;
100     private @NonNull RadioButtonPreference mDenyButton;
101     private @NonNull RadioButtonPreference mDenyForegroundButton;
102     private @NonNull String mPackageName;
103     private @NonNull String mPermGroupName;
104     private @NonNull UserHandle mUser;
105     private boolean mIsStorageGroup;
106     private boolean mIsInitialLoad;
107     private long mSessionId;
108 
109     private @NonNull String mPackageLabel;
110     private @NonNull String mPermGroupLabel;
111     private Drawable mPackageIcon;
112     private Utils.ForegroundCapableType mForegroundCapableType;
113 
114     /**
115      * Create a bundle with the arguments needed by this fragment
116      *
117      * @param packageName   The name of the package
118      * @param permName      The name of the permission whose group this fragment is for (optional)
119      * @param groupName     The name of the permission group (required if permName not specified)
120      * @param userHandle    The user of the app permission group
121      * @param caller        The name of the fragment we called from
122      * @param sessionId     The current session ID
123      * @param grantCategory The grant status of this app permission group. Used to initially set
124      *                      the button state
125      * @return A bundle with all of the args placed
126      */
createArgs(@onNull String packageName, @Nullable String permName, @Nullable String groupName, @NonNull UserHandle userHandle, @Nullable String caller, long sessionId, @Nullable String grantCategory)127     public static Bundle createArgs(@NonNull String packageName,
128             @Nullable String permName, @Nullable String groupName,
129             @NonNull UserHandle userHandle, @Nullable String caller, long sessionId, @Nullable
130             String grantCategory) {
131         Bundle arguments = new Bundle();
132         arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
133         if (groupName == null) {
134             arguments.putString(Intent.EXTRA_PERMISSION_NAME, permName);
135         } else {
136             arguments.putString(Intent.EXTRA_PERMISSION_GROUP_NAME, groupName);
137         }
138         arguments.putParcelable(Intent.EXTRA_USER, userHandle);
139         arguments.putString(EXTRA_CALLER_NAME, caller);
140         arguments.putLong(EXTRA_SESSION_ID, sessionId);
141         arguments.putString(GRANT_CATEGORY, grantCategory);
142         return arguments;
143     }
144 
145     @Override
onCreate(Bundle savedInstanceState)146     public void onCreate(Bundle savedInstanceState) {
147         super.onCreate(savedInstanceState);
148 
149         mPackageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
150         mPermGroupName = getArguments().getString(Intent.EXTRA_PERMISSION_GROUP_NAME);
151         if (mPermGroupName == null) {
152             mPermGroupName = getArguments().getString(Intent.EXTRA_PERMISSION_NAME);
153         }
154         if (mPackageName == null || mPermGroupName == null) {
155             if (mPackageName == null) {
156                 Log.e(LOG_TAG, "Package name is null: " + Intent.EXTRA_PACKAGE_NAME);
157             }
158             if (mPermGroupName == null) {
159                 Log.e(LOG_TAG, "Permission group is null: " + Intent.EXTRA_PERMISSION_GROUP_NAME);
160             }
161             final Activity activity = getActivity();
162             Toast.makeText(activity, R.string.app_not_found_dlg_title, Toast.LENGTH_LONG).show();
163             activity.finish();
164             return;
165         }
166         mIsStorageGroup = Objects.equals(mPermGroupName, STORAGE);
167         mUser = getArguments().getParcelable(Intent.EXTRA_USER);
168         mPackageLabel = BidiFormatter.getInstance().unicodeWrap(
169                 KotlinUtils.INSTANCE.getPackageLabel(getActivity().getApplication(), mPackageName,
170                         mUser));
171         mPermGroupLabel = KotlinUtils.INSTANCE.getPermGroupLabel(getContext(),
172                 mPermGroupName).toString();
173         mPackageIcon = KotlinUtils.INSTANCE.getBadgedPackageIcon(getActivity().getApplication(),
174                 mPackageName, mUser);
175         try {
176             mForegroundCapableType = Utils.getForegroundCapableType(getContext(), mPackageName);
177         } catch (PackageManager.NameNotFoundException e) {
178             Log.e(LOG_TAG, "Package " + mPackageName + " not found", e);
179         }
180 
181         mSessionId = getArguments().getLong(EXTRA_SESSION_ID, INVALID_SESSION_ID);
182 
183         AppPermissionViewModelFactory factory = new AppPermissionViewModelFactory(
184                 getActivity().getApplication(), mPackageName, mPermGroupName, mUser, mSessionId,
185                 mForegroundCapableType);
186         mViewModel = new ViewModelProvider(this, factory).get(AppPermissionViewModel.class);
187         Handler delayHandler = new Handler(Looper.getMainLooper());
188         mViewModel.getButtonStateLiveData().observe(this, buttonState -> {
189             if (mIsInitialLoad) {
190                 setRadioButtonsState(buttonState);
191             } else {
192                 delayHandler.removeCallbacksAndMessages(null);
193                 delayHandler.postDelayed(() -> setRadioButtonsState(buttonState), POST_DELAY_MS);
194             }
195         });
196     }
197 
198     @Override
onViewCreated(View view, Bundle savedInstanceState)199     public void onViewCreated(View view, Bundle savedInstanceState) {
200         super.onViewCreated(view, savedInstanceState);
201         mIsInitialLoad = true;
202         setHeader(mPackageIcon, mPackageLabel, null,
203                 getString(R.string.app_permissions_decor_title));
204         createPreferences();
205         updatePreferences();
206     }
207 
208     @Override
onResume()209     public void onResume() {
210         super.onResume();
211         updatePreferences();
212     }
213 
createPreferences()214     public void createPreferences() {
215         PreferenceScreen screen = getPreferenceScreen();
216         Context context = getContext();
217         screen.removeAll();
218 
219         PackageInfo packageInfo = getPackageInfo(getActivity(), mPackageName);
220         AppPermissions appPermissions = new AppPermissions(getActivity(), packageInfo, true,
221                 () -> getActivity().finish());
222         AppPermissionGroup group = appPermissions.getPermissionGroup(mPermGroupName);
223         Drawable icon = Utils.loadDrawable(context.getPackageManager(),
224                 group.getIconPkg(), group.getIconResId());
225 
226         screen.addPreference(createHeaderLineTwoPreference(context));
227 
228         Preference permHeader = new Preference(context);
229         permHeader.setTitle(mPermGroupLabel);
230         permHeader.setSummary(context.getString(R.string.app_permission_header, mPermGroupLabel));
231         permHeader.setSelectable(false);
232         permHeader.setIcon(Utils.applyTint(getContext(), icon, android.R.attr.colorControlNormal));
233         screen.addPreference(permHeader);
234 
235         mAllowButton = new RadioButtonPreference(context, R.string.app_permission_button_allow);
236         mAllowAlwaysButton =
237                 new RadioButtonPreference(context, R.string.app_permission_button_allow_always);
238         mAllowForegroundButton =
239                 new RadioButtonPreference(context, R.string.app_permission_button_allow_foreground);
240         mAskOneTimeButton = new RadioButtonPreference(context, R.string.app_permission_button_ask);
241         mAskButton = new RadioButtonPreference(context, R.string.app_permission_button_ask);
242         mDenyButton = new RadioButtonPreference(context, R.string.app_permission_button_deny);
243         mDenyForegroundButton =
244                 new RadioButtonPreference(context, R.string.app_permission_button_deny);
245 
246         for (Preference preference : new Preference[] {
247                 mAllowButton,
248                 mAllowAlwaysButton,
249                 mAllowForegroundButton,
250                 mAskOneTimeButton,
251                 mAskButton,
252                 mDenyButton,
253                 mDenyForegroundButton}) {
254             preference.setVisible(false);
255             preference.setIcon(android.R.color.transparent);
256             screen.addPreference(preference);
257         }
258     }
259 
updatePreferences()260     public void updatePreferences() {
261         if (mViewModel.getButtonStateLiveData().getValue() != null) {
262             setRadioButtonsState(mViewModel.getButtonStateLiveData().getValue());
263         }
264     }
265 
setRadioButtonsState(Map<ButtonType, ButtonState> states)266     private void setRadioButtonsState(Map<ButtonType, ButtonState> states) {
267         if (states == null && mViewModel.getButtonStateLiveData().isInitialized()) {
268             pressBack(this);
269             Log.w(LOG_TAG, "invalid package " + mPackageName + " or perm group "
270                     + mPermGroupName);
271             Toast.makeText(
272                     getActivity(), R.string.app_not_found_dlg_title, Toast.LENGTH_LONG).show();
273             return;
274         } else if (states == null) {
275             return;
276         }
277 
278         mAllowButton.setOnPreferenceClickListener((v) -> {
279             mViewModel.requestChange(false, this, this, ChangeRequest.GRANT_FOREGROUND,
280                     APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW);
281             setResult(GRANTED_ALWAYS);
282             return false;
283         });
284         mAllowAlwaysButton.setOnPreferenceClickListener((v) -> {
285             if (mIsStorageGroup) {
286                 showConfirmDialog(ChangeRequest.GRANT_All_FILE_ACCESS,
287                         R.string.special_file_access_dialog, -1, false);
288             } else {
289                 mViewModel.requestChange(false, this, this, ChangeRequest.GRANT_BOTH,
290                         APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW_ALWAYS);
291             }
292             setResult(GRANTED_ALWAYS);
293             return false;
294         });
295         mAllowForegroundButton.setOnPreferenceClickListener((v) -> {
296             if (mIsStorageGroup) {
297                 mViewModel.setAllFilesAccess(false);
298                 mViewModel.requestChange(false, this, this, ChangeRequest.GRANT_BOTH,
299                         APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW);
300                 setResult(GRANTED_ALWAYS);
301                 return false;
302             } else {
303                 mViewModel.requestChange(false, this, this, ChangeRequest.GRANT_FOREGROUND_ONLY,
304                         APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW_FOREGROUND);
305                 setResult(GRANTED_FOREGROUND_ONLY);
306                 return false;
307             }
308         });
309         // mAskOneTimeButton only shows if checked hence should do nothing
310         mAskButton.setOnPreferenceClickListener((v) -> {
311             mViewModel.requestChange(true, this, this, ChangeRequest.REVOKE_BOTH,
312                     APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ASK_EVERY_TIME);
313             setResult(DENIED);
314             return false;
315         });
316         mDenyButton.setOnPreferenceClickListener((v) -> {
317             mViewModel.requestChange(false, this, this, ChangeRequest.REVOKE_BOTH,
318                     APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__DENY);
319             setResult(DENIED_DO_NOT_ASK_AGAIN);
320             return false;
321         });
322         mDenyForegroundButton.setOnPreferenceClickListener((v) -> {
323             mViewModel.requestChange(false, this, this, ChangeRequest.REVOKE_FOREGROUND,
324                     APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__DENY_FOREGROUND);
325             setResult(DENIED_DO_NOT_ASK_AGAIN);
326             return false;
327         });
328 
329         setButtonState(mAllowButton, states.get(ButtonType.ALLOW));
330         setButtonState(mAllowAlwaysButton, states.get(ButtonType.ALLOW_ALWAYS));
331         setButtonState(mAllowForegroundButton, states.get(ButtonType.ALLOW_FOREGROUND));
332         setButtonState(mAskOneTimeButton, states.get(ButtonType.ASK_ONCE));
333         setButtonState(mAskButton, states.get(ButtonType.ASK));
334         setButtonState(mDenyButton, states.get(ButtonType.DENY));
335         setButtonState(mDenyForegroundButton, states.get(ButtonType.DENY_FOREGROUND));
336 
337         mIsInitialLoad = false;
338     }
339 
setButtonState(RadioButtonPreference button, AppPermissionViewModel.ButtonState state)340     private void setButtonState(RadioButtonPreference button, AppPermissionViewModel.ButtonState state) {
341         button.setVisible(state.isShown());
342         if (state.isShown()) {
343             button.setChecked(state.isChecked());
344             button.setEnabled(state.isEnabled());
345         }
346         if (state.isShown() && state.isChecked()) {
347             scrollToPreference(button);
348         }
349     }
350     /**
351      * Creates a heading below decor_title and above the rest of the preferences. This heading
352      * displays the app name and banner icon. It's used in both system and additional permissions
353      * fragments for each app. The styling used is the same as a leanback preference with a
354      * customized background color
355      * @param context The context the preferences created on
356      * @return The preference header to be inserted as the first preference in the list.
357      */
createHeaderLineTwoPreference(Context context)358     private Preference createHeaderLineTwoPreference(Context context) {
359         Preference headerLineTwo = new Preference(context) {
360             @Override
361             public void onBindViewHolder(PreferenceViewHolder holder) {
362                 super.onBindViewHolder(holder);
363                 holder.itemView.setBackgroundColor(
364                         getResources().getColor(R.color.lb_header_banner_color));
365             }
366         };
367         headerLineTwo.setKey(HEADER_PREFERENCE_KEY);
368         headerLineTwo.setSelectable(false);
369         headerLineTwo.setTitle(mPackageLabel);
370         headerLineTwo.setIcon(mPackageIcon);
371         return headerLineTwo;
372     }
373 
getPackageInfo(Activity activity, String packageName)374     private static PackageInfo getPackageInfo(Activity activity, String packageName) {
375         try {
376             return activity.getPackageManager().getPackageInfo(
377                     packageName, PackageManager.GET_PERMISSIONS);
378         } catch (PackageManager.NameNotFoundException e) {
379             Log.i(LOG_TAG, "No package:" + activity.getCallingPackage(), e);
380             return null;
381         }
382     }
383 
setResult(@rantPermissionsViewHandler.Result int result)384     private void setResult(@GrantPermissionsViewHandler.Result int result) {
385         Intent intent = new Intent()
386                 .putExtra(EXTRA_RESULT_PERMISSION_INTERACTED, mPermGroupName)
387                 .putExtra(EXTRA_RESULT_PERMISSION_RESULT, result);
388         getActivity().setResult(Activity.RESULT_OK, intent);
389     }
390 
391     /**
392      * Show a dialog that warns the user that they are about to revoke permissions that were
393      * granted by default, or that they are about to grant full file access to an app.
394      *
395      *
396      * The order of operation to revoke a permission granted by default is:
397      * 1. `showConfirmDialog`
398      * 1. [ConfirmDialog.onCreateDialog]
399      * 1. [AppPermissionViewModel.onDenyAnyWay] or [AppPermissionViewModel.onConfirmFileAccess]
400      * TODO: Remove once data can be passed between dialogs and fragments with nav component
401      *
402      * @param changeRequest Whether background or foreground should be changed
403      * @param messageId     The Id of the string message to show
404      * @param buttonPressed Button which was pressed to initiate the dialog, one of
405      *                      AppPermissionFragmentActionReported.button_pressed constants
406      * @param oneTime       Whether the one-time (ask) button was clicked rather than the deny
407      *                      button
408      */
409     @Override
showConfirmDialog(ChangeRequest changeRequest, @StringRes int messageId, int buttonPressed, boolean oneTime)410     public void showConfirmDialog(ChangeRequest changeRequest, @StringRes int messageId,
411             int buttonPressed, boolean oneTime) {
412         Bundle args = getArguments().deepCopy();
413         args.putInt(ConfirmDialog.MSG, messageId);
414         args.putSerializable(ConfirmDialog.CHANGE_REQUEST, changeRequest);
415         args.putInt(ConfirmDialog.BUTTON, buttonPressed);
416         args.putBoolean(ConfirmDialog.ONE_TIME, oneTime);
417         ConfirmDialog defaultDenyDialog = new ConfirmDialog();
418         defaultDenyDialog.setCancelable(true);
419         defaultDenyDialog.setArguments(args);
420         defaultDenyDialog.setTargetFragment(this, 0);
421         defaultDenyDialog.show(getFragmentManager(),
422                 ConfirmDialog.class.getName());
423     }
424 
425     /**
426      * A dialog warning the user that they are about to deny a permission that was granted by
427      * default, or that they are denying a permission on a Pre-M app
428      *
429      * @see AppPermissionViewModel.ConfirmDialogShowingFragment#showConfirmDialog(ChangeRequest,
430      * int, int, boolean)
431      * @see #showConfirmDialog(ChangeRequest, int, int)
432      */
433     public static class ConfirmDialog extends DialogFragment {
434         static final String MSG = ConfirmDialog.class.getName() + ".arg.msg";
435         static final String CHANGE_REQUEST = ConfirmDialog.class.getName()
436                 + ".arg.changeRequest";
437         private static final String KEY = ConfirmDialog.class.getName() + ".arg.key";
438         private static final String BUTTON = ConfirmDialog.class.getName() + ".arg.button";
439         private static final String ONE_TIME = ConfirmDialog.class.getName() + ".arg.onetime";
440 
441         @Override
onCreateDialog(Bundle savedInstanceState)442         public Dialog onCreateDialog(Bundle savedInstanceState) {
443             AppPermissionFragment fragment = (AppPermissionFragment) getTargetFragment();
444             boolean isGrantFileAccess = getArguments().getSerializable(CHANGE_REQUEST)
445                     == ChangeRequest.GRANT_All_FILE_ACCESS;
446             int positiveButtonStringResId = R.string.grant_dialog_button_deny_anyway;
447             if (isGrantFileAccess) {
448                 positiveButtonStringResId = R.string.grant_dialog_button_allow;
449             }
450             AlertDialog.Builder b = new AlertDialog.Builder(getContext())
451                     .setMessage(getArguments().getInt(MSG))
452                     .setNegativeButton(R.string.cancel,
453                             (DialogInterface dialog, int which) -> dialog.cancel())
454                     .setPositiveButton(positiveButtonStringResId,
455                             (DialogInterface dialog, int which) -> {
456                                 if (isGrantFileAccess) {
457                                     fragment.mViewModel.setAllFilesAccess(true);
458                                 } else {
459                                     fragment.mViewModel.onDenyAnyWay((ChangeRequest)
460                                                     getArguments().getSerializable(CHANGE_REQUEST),
461                                             getArguments().getInt(BUTTON),
462                                             getArguments().getBoolean(ONE_TIME));
463                                 }
464                             });
465             Dialog d = b.create();
466             d.setCanceledOnTouchOutside(true);
467             return d;
468         }
469 
470         @Override
onCancel(DialogInterface dialog)471         public void onCancel(DialogInterface dialog) {
472             AppPermissionFragment fragment = (AppPermissionFragment) getTargetFragment();
473             fragment.setRadioButtonsState(fragment.mViewModel.getButtonStateLiveData().getValue());
474         }
475     }
476 }
477