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