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.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.ActionBar;
39 import android.app.Activity;
40 import android.app.AlertDialog;
41 import android.app.Dialog;
42 import android.content.Context;
43 import android.content.DialogInterface;
44 import android.content.Intent;
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.MenuItem;
55 import android.view.View;
56 import android.view.ViewGroup;
57 import android.widget.ImageView;
58 import android.widget.RadioButton;
59 import android.widget.TextView;
60 import android.widget.Toast;
61 
62 import androidx.annotation.NonNull;
63 import androidx.annotation.Nullable;
64 import androidx.annotation.StringRes;
65 import androidx.core.widget.NestedScrollView;
66 import androidx.fragment.app.DialogFragment;
67 import androidx.lifecycle.ViewModelProvider;
68 
69 import com.android.permissioncontroller.R;
70 import com.android.permissioncontroller.permission.data.FullStoragePermissionAppsLiveData.FullStoragePackageState;
71 import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler;
72 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel;
73 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonState;
74 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType;
75 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ChangeRequest;
76 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModelFactory;
77 import com.android.permissioncontroller.permission.utils.KotlinUtils;
78 import com.android.permissioncontroller.permission.utils.Utils;
79 import com.android.settingslib.RestrictedLockUtils;
80 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
81 import com.android.settingslib.widget.ActionBarShadowController;
82 
83 import java.util.Map;
84 import java.util.Objects;
85 
86 import kotlin.Pair;
87 
88 /**
89  * Show and manage a single permission group for an app.
90  *
91  * <p>Allows the user to control whether the app is granted the permission.
92  */
93 public class AppPermissionFragment extends SettingsWithLargeHeader
94         implements AppPermissionViewModel.ConfirmDialogShowingFragment {
95     private static final String LOG_TAG = "AppPermissionFragment";
96     private static final long POST_DELAY_MS = 20;
97 
98     static final String GRANT_CATEGORY = "grant_category";
99 
100     private @NonNull AppPermissionViewModel mViewModel;
101     private @NonNull RadioButton mAllowButton;
102     private @NonNull RadioButton mAllowAlwaysButton;
103     private @NonNull RadioButton mAllowForegroundButton;
104     private @NonNull RadioButton mAskOneTimeButton;
105     private @NonNull RadioButton mAskButton;
106     private @NonNull RadioButton mDenyButton;
107     private @NonNull RadioButton mDenyForegroundButton;
108     private @NonNull View mDivider;
109     private @NonNull ViewGroup mWidgetFrame;
110     private @NonNull TextView mPermissionDetails;
111     private @NonNull NestedScrollView mNestedScrollView;
112     private @NonNull String mPackageName;
113     private @NonNull String mPermGroupName;
114     private @NonNull UserHandle mUser;
115     private boolean mIsStorageGroup;
116     private boolean mIsInitialLoad;
117     private long mSessionId;
118 
119     private @NonNull String mPackageLabel;
120     private @NonNull String mPermGroupLabel;
121     private Drawable mPackageIcon;
122     private Utils.ForegroundCapableType mForegroundCapableType;
123 
124     /**
125      * Create a bundle with the arguments needed by this fragment
126      *
127      * @param packageName   The name of the package
128      * @param permName      The name of the permission whose group this fragment is for (optional)
129      * @param groupName     The name of the permission group (required if permName not specified)
130      * @param userHandle    The user of the app permission group
131      * @param caller        The name of the fragment we called from
132      * @param sessionId     The current session ID
133      * @param grantCategory The grant status of this app permission group. Used to initially set
134      *                      the button state
135      * @return A bundle with all of the args placed
136      */
createArgs(@onNull String packageName, @Nullable String permName, @Nullable String groupName, @NonNull UserHandle userHandle, @Nullable String caller, long sessionId, @Nullable String grantCategory)137     public static Bundle createArgs(@NonNull String packageName,
138             @Nullable String permName, @Nullable String groupName,
139             @NonNull UserHandle userHandle, @Nullable String caller, long sessionId, @Nullable
140             String grantCategory) {
141         Bundle arguments = new Bundle();
142         arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
143         if (groupName == null) {
144             arguments.putString(Intent.EXTRA_PERMISSION_NAME, permName);
145         } else {
146             arguments.putString(Intent.EXTRA_PERMISSION_GROUP_NAME, groupName);
147         }
148         arguments.putParcelable(Intent.EXTRA_USER, userHandle);
149         arguments.putString(EXTRA_CALLER_NAME, caller);
150         arguments.putLong(EXTRA_SESSION_ID, sessionId);
151         arguments.putString(GRANT_CATEGORY, grantCategory);
152         return arguments;
153     }
154 
155     @Override
onCreate(Bundle savedInstanceState)156     public void onCreate(Bundle savedInstanceState) {
157         super.onCreate(savedInstanceState);
158 
159         setHasOptionsMenu(true);
160         ActionBar ab = getActivity().getActionBar();
161         if (ab != null) {
162             ab.setDisplayHomeAsUpEnabled(true);
163         }
164 
165         mPackageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
166         mPermGroupName = getArguments().getString(Intent.EXTRA_PERMISSION_GROUP_NAME);
167         if (mPermGroupName == null) {
168             mPermGroupName = getArguments().getString(Intent.EXTRA_PERMISSION_NAME);
169         }
170         mIsStorageGroup = Objects.equals(mPermGroupName, STORAGE);
171         mUser = getArguments().getParcelable(Intent.EXTRA_USER);
172         mPackageLabel = BidiFormatter.getInstance().unicodeWrap(
173                 KotlinUtils.INSTANCE.getPackageLabel(getActivity().getApplication(), mPackageName,
174                         mUser));
175         mPermGroupLabel = KotlinUtils.INSTANCE.getPermGroupLabel(getContext(),
176                 mPermGroupName).toString();
177         mPackageIcon = KotlinUtils.INSTANCE.getBadgedPackageIcon(getActivity().getApplication(),
178                 mPackageName, mUser);
179         try {
180             mForegroundCapableType = Utils.getForegroundCapableType(getContext(), mPackageName);
181         } catch (PackageManager.NameNotFoundException e) {
182             Log.e(LOG_TAG, "Package " + mPackageName + " not found", e);
183         }
184 
185         mSessionId = getArguments().getLong(EXTRA_SESSION_ID, INVALID_SESSION_ID);
186 
187         AppPermissionViewModelFactory factory = new AppPermissionViewModelFactory(
188                 getActivity().getApplication(), mPackageName, mPermGroupName, mUser, mSessionId,
189                 mForegroundCapableType);
190         mViewModel = new ViewModelProvider(this, factory).get(AppPermissionViewModel.class);
191         Handler delayHandler = new Handler(Looper.getMainLooper());
192         mViewModel.getButtonStateLiveData().observe(this, buttonState -> {
193             if (mIsInitialLoad) {
194                 setRadioButtonsState(buttonState);
195             } else {
196                 delayHandler.removeCallbacksAndMessages(null);
197                 delayHandler.postDelayed(() -> setRadioButtonsState(buttonState), POST_DELAY_MS);
198             }
199         });
200         mViewModel.getDetailResIdLiveData().observe(this, this::setDetail);
201         mViewModel.getShowAdminSupportLiveData().observe(this, this::setAdminSupportDetail);
202         if (mIsStorageGroup) {
203             mViewModel.getFullStorageStateLiveData().observe(this, this::setSpecialStorageState);
204         }
205     }
206 
207     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)208     public View onCreateView(LayoutInflater inflater, ViewGroup container,
209             Bundle savedInstanceState) {
210         Context context = getContext();
211         ViewGroup root = (ViewGroup) inflater.inflate(R.layout.app_permission, container, false);
212 
213         mIsInitialLoad = true;
214 
215         setHeader(mPackageIcon, mPackageLabel, null, null, false);
216         updateHeader(root.requireViewById(R.id.large_header));
217 
218         ((TextView) root.requireViewById(R.id.permission_message)).setText(
219                 context.getString(R.string.app_permission_header, mPermGroupLabel));
220 
221         String caller = getArguments().getString(EXTRA_CALLER_NAME);
222 
223         TextView footer1Link = root.requireViewById(R.id.footer_link_1);
224         footer1Link.setText(context.getString(R.string.app_permission_footer_app_permissions_link,
225                 mPackageLabel));
226         setBottomLinkState(footer1Link, caller, Intent.ACTION_MANAGE_APP_PERMISSIONS);
227 
228         TextView footer2Link = root.requireViewById(R.id.footer_link_2);
229         footer2Link.setText(context.getString(R.string.app_permission_footer_permission_apps_link));
230         setBottomLinkState(footer2Link, caller, Intent.ACTION_MANAGE_PERMISSION_APPS);
231 
232         mAllowButton = root.requireViewById(R.id.allow_radio_button);
233         mAllowAlwaysButton = root.requireViewById(R.id.allow_always_radio_button);
234         mAllowForegroundButton = root.requireViewById(R.id.allow_foreground_only_radio_button);
235         mAskOneTimeButton = root.requireViewById(R.id.ask_one_time_radio_button);
236         mAskButton = root.requireViewById(R.id.ask_radio_button);
237         mDenyButton = root.requireViewById(R.id.deny_radio_button);
238         mDenyForegroundButton = root.requireViewById(R.id.deny_foreground_radio_button);
239         mDivider = root.requireViewById(R.id.two_target_divider);
240         mWidgetFrame = root.requireViewById(R.id.widget_frame);
241         mPermissionDetails = root.requireViewById(R.id.permission_details);
242 
243         mNestedScrollView = root.requireViewById(R.id.nested_scroll_view);
244 
245         if (mViewModel.getButtonStateLiveData().getValue() != null) {
246             setRadioButtonsState(mViewModel.getButtonStateLiveData().getValue());
247         } else {
248             mAllowButton.setVisibility(View.GONE);
249             mAllowAlwaysButton.setVisibility(View.GONE);
250             mAllowForegroundButton.setVisibility(View.GONE);
251             mAskOneTimeButton.setVisibility(View.GONE);
252             mAskButton.setVisibility(View.GONE);
253             mDenyButton.setVisibility(View.GONE);
254             mDenyForegroundButton.setVisibility(View.GONE);
255         }
256 
257         if (mViewModel.getFullStorageStateLiveData().isInitialized() && mIsStorageGroup) {
258             setSpecialStorageState(mViewModel.getFullStorageStateLiveData().getValue(), root);
259         } else {
260             TextView storageFooter = root.requireViewById(R.id.footer_storage_special_app_access);
261             storageFooter.setVisibility(View.GONE);
262         }
263 
264         getActivity().setTitle(
265                 getPreferenceManager().getContext().getString(R.string.app_permission_title,
266                         mPermGroupLabel));
267 
268         return root;
269     }
270 
setBottomLinkState(TextView view, String caller, String action)271     private void setBottomLinkState(TextView view, String caller, String action) {
272         if ((Objects.equals(caller, AppPermissionGroupsFragment.class.getName())
273                 && action.equals(Intent.ACTION_MANAGE_APP_PERMISSIONS))
274                 || (Objects.equals(caller, PermissionAppsFragment.class.getName())
275                 && action.equals(Intent.ACTION_MANAGE_PERMISSION_APPS))) {
276             view.setVisibility(View.GONE);
277         } else {
278             view.setOnClickListener((v) -> {
279                 Bundle args;
280                 if (action.equals(Intent.ACTION_MANAGE_APP_PERMISSIONS)) {
281                     args = AppPermissionGroupsFragment.createArgs(mPackageName, mUser,
282                             mSessionId, true);
283                 } else {
284                     args = PermissionAppsFragment.createArgs(mPermGroupName, mSessionId);
285                 }
286                 mViewModel.showBottomLinkPage(this, action, args);
287             });
288         }
289     }
290 
setSpecialStorageState(FullStoragePackageState storageState)291     private void setSpecialStorageState(FullStoragePackageState storageState) {
292         setSpecialStorageState(storageState, getView());
293     }
294 
295     @Override
onStart()296     public void onStart() {
297         super.onStart();
298 
299         ActionBar ab = getActivity().getActionBar();
300         if (ab != null) {
301             ab.setElevation(0);
302         }
303 
304         ActionBarShadowController.attachToView(getActivity(), getLifecycle(), mNestedScrollView);
305     }
306 
307     @Override
onOptionsItemSelected(MenuItem item)308     public boolean onOptionsItemSelected(MenuItem item) {
309         if (item.getItemId() == android.R.id.home) {
310             pressBack(this);
311             return true;
312         }
313         return super.onOptionsItemSelected(item);
314     }
315 
setRadioButtonsState(Map<ButtonType, ButtonState> states)316     private void setRadioButtonsState(Map<ButtonType, ButtonState> states) {
317         if (states == null && mViewModel.getButtonStateLiveData().isInitialized()) {
318             pressBack(this);
319             Log.w(LOG_TAG, "invalid package " + mPackageName + " or perm group "
320                     + mPermGroupName);
321             Toast.makeText(
322                     getActivity(), R.string.app_not_found_dlg_title, Toast.LENGTH_LONG).show();
323             return;
324         } else if (states == null) {
325             return;
326         }
327 
328         mAllowButton.setOnClickListener((v) -> {
329             mViewModel.requestChange(false, this, this, ChangeRequest.GRANT_FOREGROUND,
330                     APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW);
331             setResult(GRANTED_ALWAYS);
332         });
333         mAllowAlwaysButton.setOnClickListener((v) -> {
334             if (mIsStorageGroup) {
335                 showConfirmDialog(ChangeRequest.GRANT_All_FILE_ACCESS,
336                         R.string.special_file_access_dialog, -1, false);
337             } else {
338                 mViewModel.requestChange(false, this, this, ChangeRequest.GRANT_BOTH,
339                         APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW_ALWAYS);
340             }
341             setResult(GRANTED_ALWAYS);
342         });
343         mAllowForegroundButton.setOnClickListener((v) -> {
344             if (mIsStorageGroup) {
345                 mViewModel.setAllFilesAccess(false);
346                 mViewModel.requestChange(false, this, this, ChangeRequest.GRANT_BOTH,
347                         APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW);
348                 setResult(GRANTED_ALWAYS);
349             } else {
350                 mViewModel.requestChange(false, this, this, ChangeRequest.GRANT_FOREGROUND_ONLY,
351                         APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW_FOREGROUND);
352                 setResult(GRANTED_FOREGROUND_ONLY);
353             }
354         });
355         // mAskOneTimeButton only shows if checked hence should do nothing
356         mAskButton.setOnClickListener((v) -> {
357             mViewModel.requestChange(true, this, this, ChangeRequest.REVOKE_BOTH,
358                     APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ASK_EVERY_TIME);
359             setResult(DENIED);
360         });
361         mDenyButton.setOnClickListener((v) -> {
362 
363             if (mViewModel.getFullStorageStateLiveData().getValue() != null
364                     && !mViewModel.getFullStorageStateLiveData().getValue().isLegacy()) {
365                 mViewModel.setAllFilesAccess(false);
366             }
367             mViewModel.requestChange(false, this, this, ChangeRequest.REVOKE_BOTH,
368                     APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__DENY);
369             setResult(DENIED_DO_NOT_ASK_AGAIN);
370         });
371         mDenyForegroundButton.setOnClickListener((v) -> {
372             mViewModel.requestChange(false, this, this, ChangeRequest.REVOKE_FOREGROUND,
373                     APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__DENY_FOREGROUND);
374             setResult(DENIED_DO_NOT_ASK_AGAIN);
375         });
376 
377         setButtonState(mAllowButton, states.get(ButtonType.ALLOW));
378         setButtonState(mAllowAlwaysButton, states.get(ButtonType.ALLOW_ALWAYS));
379         setButtonState(mAllowForegroundButton, states.get(ButtonType.ALLOW_FOREGROUND));
380         setButtonState(mAskOneTimeButton, states.get(ButtonType.ASK_ONCE));
381         setButtonState(mAskButton, states.get(ButtonType.ASK));
382         setButtonState(mDenyButton, states.get(ButtonType.DENY));
383         setButtonState(mDenyForegroundButton, states.get(ButtonType.DENY_FOREGROUND));
384 
385         mIsInitialLoad = false;
386 
387         if (mViewModel.getFullStorageStateLiveData().isInitialized()) {
388             setSpecialStorageState(mViewModel.getFullStorageStateLiveData().getValue());
389         }
390     }
391 
setButtonState(RadioButton button, AppPermissionViewModel.ButtonState state)392     private void setButtonState(RadioButton button, AppPermissionViewModel.ButtonState state) {
393         int visible = state.isShown() ? View.VISIBLE : View.GONE;
394         button.setVisibility(visible);
395         if (state.isShown()) {
396             button.setChecked(state.isChecked());
397             button.setEnabled(state.isEnabled());
398         }
399         if (mIsInitialLoad) {
400             button.jumpDrawablesToCurrentState();
401         }
402     }
403 
setSpecialStorageState(FullStoragePackageState storageState, View v)404     private void setSpecialStorageState(FullStoragePackageState storageState, View v) {
405         if (v == null) {
406             return;
407         }
408 
409         TextView textView = v.requireViewById(R.id.footer_storage_special_app_access);
410         if (mAllowButton == null || !mIsStorageGroup) {
411             textView.setVisibility(View.GONE);
412             return;
413         }
414 
415         mAllowAlwaysButton.setText(R.string.app_permission_button_allow_all_files);
416         mAllowForegroundButton.setText(R.string.app_permission_button_allow_media_only);
417 
418         if (storageState == null) {
419             textView.setVisibility(View.GONE);
420             return;
421         }
422 
423         if (storageState.isLegacy()) {
424             mAllowButton.setText(R.string.app_permission_button_allow_all_files);
425             textView.setVisibility(View.GONE);
426             return;
427         }
428 
429         textView.setText(R.string.app_permission_footer_special_file_access);
430         textView.setVisibility(View.VISIBLE);
431     }
432 
setResult(@rantPermissionsViewHandler.Result int result)433     private void setResult(@GrantPermissionsViewHandler.Result int result) {
434         Intent intent = new Intent()
435                 .putExtra(EXTRA_RESULT_PERMISSION_INTERACTED, mPermGroupName)
436                 .putExtra(EXTRA_RESULT_PERMISSION_RESULT, result);
437         getActivity().setResult(Activity.RESULT_OK, intent);
438     }
439 
setDetail(Pair<Integer, Integer> detailResIds)440     private void setDetail(Pair<Integer, Integer> detailResIds) {
441         if (detailResIds == null) {
442             mWidgetFrame.setVisibility(View.GONE);
443             mDivider.setVisibility(View.GONE);
444             return;
445         }
446         mWidgetFrame.setVisibility(View.VISIBLE);
447         if (detailResIds.getSecond() != null) {
448             // If the permissions are individually controlled, also show a link to the page that
449             // lets you control them.
450             mDivider.setVisibility(View.VISIBLE);
451             showRightIcon(R.drawable.ic_settings);
452             Bundle args = AllAppPermissionsFragment.createArgs(mPackageName, mPermGroupName, mUser);
453             mWidgetFrame.setOnClickListener(v -> mViewModel.showAllPermissions(this, args));
454             mPermissionDetails.setText(getPreferenceManager().getContext().getString(
455                     detailResIds.getFirst(), detailResIds.getSecond()));
456         } else {
457             mPermissionDetails.setText(getPreferenceManager().getContext().getString(
458                     detailResIds.getFirst()));
459         }
460         mPermissionDetails.setVisibility(View.VISIBLE);
461 
462     }
463 
setAdminSupportDetail(EnforcedAdmin admin)464     private void setAdminSupportDetail(EnforcedAdmin admin) {
465         if (admin != null) {
466             showRightIcon(R.drawable.ic_info);
467             mWidgetFrame.setOnClickListener(v ->
468                     RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(), admin)
469             );
470         } else {
471             mWidgetFrame.removeAllViews();
472         }
473     }
474 
475     /**
476      * Show the given icon on the right of the first radio button.
477      *
478      * @param iconId the resourceId of the drawable to use.
479      */
showRightIcon(int iconId)480     private void showRightIcon(int iconId) {
481         mWidgetFrame.removeAllViews();
482         ImageView imageView = new ImageView(getPreferenceManager().getContext());
483         imageView.setImageResource(iconId);
484         mWidgetFrame.addView(imageView);
485         mWidgetFrame.setVisibility(View.VISIBLE);
486     }
487 
488     /**
489      * Show a dialog that warns the user that she/he is about to revoke permissions that were
490      * granted by default, or that they are about to grant full file access to an app.
491      *
492      *
493      * The order of operation to revoke a permission granted by default is:
494      * 1. `showConfirmDialog`
495      * 1. [ConfirmDialog.onCreateDialog]
496      * 1. [AppPermissionViewModel.onDenyAnyWay] or [AppPermissionViewModel.onConfirmFileAccess]
497      * TODO: Remove once data can be passed between dialogs and fragments with nav component
498      *
499      * @param changeRequest Whether background or foreground should be changed
500      * @param messageId     The Id of the string message to show
501      * @param buttonPressed Button which was pressed to initiate the dialog, one of
502      *                      AppPermissionFragmentActionReported.button_pressed constants
503      * @param oneTime       Whether the one-time (ask) button was clicked rather than the deny
504      *                      button
505      */
506     @Override
showConfirmDialog(ChangeRequest changeRequest, @StringRes int messageId, int buttonPressed, boolean oneTime)507     public void showConfirmDialog(ChangeRequest changeRequest, @StringRes int messageId,
508             int buttonPressed, boolean oneTime) {
509         Bundle args = getArguments().deepCopy();
510         args.putInt(ConfirmDialog.MSG, messageId);
511         args.putSerializable(ConfirmDialog.CHANGE_REQUEST, changeRequest);
512         args.putInt(ConfirmDialog.BUTTON, buttonPressed);
513         args.putBoolean(ConfirmDialog.ONE_TIME, oneTime);
514         ConfirmDialog defaultDenyDialog = new ConfirmDialog();
515         defaultDenyDialog.setCancelable(true);
516         defaultDenyDialog.setArguments(args);
517         defaultDenyDialog.show(getChildFragmentManager().beginTransaction(),
518                 ConfirmDialog.class.getName());
519     }
520 
521     /**
522      * A dialog warning the user that they are about to deny a permission that was granted by
523      * default, or that they are denying a permission on a Pre-M app
524      *
525      * @see AppPermissionViewModel.ConfirmDialogShowingFragment#showConfirmDialog(ChangeRequest,
526      * int, int, boolean)
527      * @see #showConfirmDialog(ChangeRequest, int, int)
528      */
529     public static class ConfirmDialog extends DialogFragment {
530         static final String MSG = ConfirmDialog.class.getName() + ".arg.msg";
531         static final String CHANGE_REQUEST = ConfirmDialog.class.getName()
532                 + ".arg.changeRequest";
533         private static final String KEY = ConfirmDialog.class.getName() + ".arg.key";
534         private static final String BUTTON = ConfirmDialog.class.getName() + ".arg.button";
535         private static final String ONE_TIME = ConfirmDialog.class.getName() + ".arg.onetime";
536         private static int sCode =  APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW;
537         @Override
onCreateDialog(Bundle savedInstanceState)538         public Dialog onCreateDialog(Bundle savedInstanceState) {
539             AppPermissionFragment fragment = (AppPermissionFragment) getParentFragment();
540             boolean isGrantFileAccess = getArguments().getSerializable(CHANGE_REQUEST)
541                     == ChangeRequest.GRANT_All_FILE_ACCESS;
542             int positiveButtonStringResId = R.string.grant_dialog_button_deny_anyway;
543             if (isGrantFileAccess) {
544                 positiveButtonStringResId = R.string.grant_dialog_button_allow;
545             }
546             AlertDialog.Builder b = new AlertDialog.Builder(getContext())
547                     .setMessage(getArguments().getInt(MSG))
548                     .setNegativeButton(R.string.cancel,
549                             (DialogInterface dialog, int which) -> dialog.cancel())
550                     .setPositiveButton(positiveButtonStringResId,
551                             (DialogInterface dialog, int which) -> {
552                                 if (isGrantFileAccess) {
553                                     fragment.mViewModel.setAllFilesAccess(true);
554                                     fragment.mViewModel.requestChange(false, fragment,
555                                             fragment, ChangeRequest.GRANT_BOTH, sCode);
556                                 } else {
557                                     fragment.mViewModel.onDenyAnyWay((ChangeRequest)
558                                                     getArguments().getSerializable(CHANGE_REQUEST),
559                                             getArguments().getInt(BUTTON),
560                                             getArguments().getBoolean(ONE_TIME));
561                                 }
562                             });
563             Dialog d = b.create();
564             d.setCanceledOnTouchOutside(true);
565             return d;
566         }
567 
568         @Override
onCancel(DialogInterface dialog)569         public void onCancel(DialogInterface dialog) {
570             AppPermissionFragment fragment = (AppPermissionFragment) getParentFragment();
571             fragment.setRadioButtonsState(fragment.mViewModel.getButtonStateLiveData().getValue());
572         }
573     }
574 }
575