1 /*
2  * Copyright (C) 2019 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.auto;
18 
19 import static com.android.permissioncontroller.Constants.EXTRA_SESSION_ID;
20 import static com.android.permissioncontroller.Constants.INVALID_SESSION_ID;
21 import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW;
22 import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW_ALWAYS;
23 import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW_FOREGROUND;
24 import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__DENY;
25 import static com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_INTERACTED;
26 import static com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_RESULT;
27 
28 import android.Manifest;
29 import android.app.Activity;
30 import android.app.AlertDialog;
31 import android.app.Dialog;
32 import android.content.Context;
33 import android.content.DialogInterface;
34 import android.content.Intent;
35 import android.content.pm.PackageManager;
36 import android.graphics.drawable.Drawable;
37 import android.hardware.SensorPrivacyManager;
38 import android.os.Build;
39 import android.os.Bundle;
40 import android.os.UserHandle;
41 import android.text.BidiFormatter;
42 import android.util.Log;
43 import android.view.View;
44 import android.widget.RadioButton;
45 
46 import androidx.annotation.NonNull;
47 import androidx.annotation.Nullable;
48 import androidx.annotation.RequiresApi;
49 import androidx.core.content.res.TypedArrayUtils;
50 import androidx.fragment.app.DialogFragment;
51 import androidx.fragment.app.Fragment;
52 import androidx.lifecycle.ViewModelProvider;
53 import androidx.preference.PreferenceCategory;
54 import androidx.preference.PreferenceGroup;
55 import androidx.preference.PreferenceScreen;
56 import androidx.preference.PreferenceViewHolder;
57 import androidx.preference.TwoStatePreference;
58 
59 import com.android.car.ui.AlertDialogBuilder;
60 import com.android.modules.utils.build.SdkLevel;
61 import com.android.permission.flags.Flags;
62 import com.android.permissioncontroller.R;
63 import com.android.permissioncontroller.auto.AutoSettingsFrameFragment;
64 import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler;
65 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel;
66 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ChangeRequest;
67 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModelFactory;
68 import com.android.permissioncontroller.permission.ui.v33.AdvancedConfirmDialogArgs;
69 import com.android.permissioncontroller.permission.utils.KotlinUtils;
70 import com.android.permissioncontroller.permission.utils.LocationUtils;
71 import com.android.permissioncontroller.permission.utils.PackageRemovalMonitor;
72 import com.android.permissioncontroller.permission.utils.Utils;
73 import com.android.settingslib.RestrictedLockUtils;
74 
75 import kotlin.Pair;
76 
77 import java.util.Collection;
78 import java.util.List;
79 import java.util.Map;
80 
81 /** Settings related to a particular permission for the given app. */
82 public class AutoAppPermissionFragment extends AutoSettingsFrameFragment
83         implements AppPermissionViewModel.ConfirmDialogShowingFragment {
84 
85     private static final String LOG_TAG = "AppPermissionFragment";
86     private static final long POST_DELAY_MS = 20;
87     private static final String BLOCKED_APP_PREF_KEY = "blocked_app";
88     private static final String REQUIRED_APP_PREF_KEY = "required_app";
89 
90     @NonNull
91     private TwoStatePreference mAllowPermissionPreference;
92     @NonNull
93     private TwoStatePreference mAlwaysPermissionPreference;
94     @NonNull
95     private TwoStatePreference mForegroundOnlyPermissionPreference;
96     @NonNull
97     private TwoStatePreference mDenyPermissionPreference;
98     @NonNull
99     private AutoTwoTargetPreference mDetailsPreference;
100 
101     @NonNull
102     private AppPermissionViewModel mViewModel;
103     @NonNull
104     private String mPackageName;
105     @NonNull
106     private String mPermGroupName;
107     @NonNull
108     private UserHandle mUser;
109     @NonNull
110     private String mPackageLabel;
111     @NonNull
112     private String mPermGroupLabel;
113     private Drawable mPackageIcon;
114 
115     private SensorPrivacyManager mSensorPrivacyManager;
116     private Collection<String> mAutomotiveLocationBypassAllowlist;
117     private List<String> mCameraPrivacyAllowlist;
118 
119     /**
120      * Listens for changes to the app the permission is currently getting granted to. {@code null}
121      * when unregistered.
122      */
123     @Nullable
124     private PackageRemovalMonitor mPackageRemovalMonitor;
125 
126     /**
127      * Returns a new {@link AutoAppPermissionFragment}.
128      *
129      * @param packageName the package name for which the permission is being changed
130      * @param permName the name of the permission being changed
131      * @param groupName the name of the permission group being changed
132      * @param userHandle the user for which the permission is being changed
133      */
134     @NonNull
newInstance(@onNull String packageName, @NonNull String permName, @Nullable String groupName, @NonNull UserHandle userHandle, @NonNull long sessionId)135     public static AutoAppPermissionFragment newInstance(@NonNull String packageName,
136             @NonNull String permName, @Nullable String groupName, @NonNull UserHandle userHandle,
137             @NonNull long sessionId) {
138         AutoAppPermissionFragment fragment = new AutoAppPermissionFragment();
139         Bundle arguments = new Bundle();
140         arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
141         if (groupName == null) {
142             arguments.putString(Intent.EXTRA_PERMISSION_NAME, permName);
143         } else {
144             arguments.putString(Intent.EXTRA_PERMISSION_GROUP_NAME, groupName);
145         }
146         arguments.putParcelable(Intent.EXTRA_USER, userHandle);
147         arguments.putLong(EXTRA_SESSION_ID, sessionId);
148         fragment.setArguments(arguments);
149         return fragment;
150     }
151 
152     @Override
onCreatePreferences(Bundle bundle, String s)153     public void onCreatePreferences(Bundle bundle, String s) {
154         setPreferenceScreen(getPreferenceManager().createPreferenceScreen(requireContext()));
155     }
156 
157     @Override
onCreate(@ullable Bundle savedInstanceState)158     public void onCreate(@Nullable Bundle savedInstanceState) {
159         super.onCreate(savedInstanceState);
160         mPackageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
161         mPermGroupName = getArguments().getString(Intent.EXTRA_PERMISSION_GROUP_NAME);
162         if (mPermGroupName == null) {
163             mPermGroupName = getArguments().getString(Intent.EXTRA_PERMISSION_NAME);
164         }
165         mUser = getArguments().getParcelable(Intent.EXTRA_USER);
166         mPackageLabel = BidiFormatter.getInstance().unicodeWrap(
167                 KotlinUtils.INSTANCE.getPackageLabel(getActivity().getApplication(), mPackageName,
168                         mUser));
169         mPermGroupLabel = KotlinUtils.INSTANCE.getPermGroupLabel(getContext(),
170                 mPermGroupName).toString();
171         mPackageIcon = KotlinUtils.INSTANCE.getBadgedPackageIcon(getActivity().getApplication(),
172                 mPackageName, mUser);
173         setHeaderLabel(
174                 requireContext().getString(R.string.app_permission_title, mPermGroupLabel));
175         if (SdkLevel.isAtLeastV()) {
176             mSensorPrivacyManager = requireContext().getSystemService(SensorPrivacyManager.class);
177             mCameraPrivacyAllowlist = mSensorPrivacyManager.getCameraPrivacyAllowlist();
178             if (Flags.addBannersToPrivacySensitiveAppsForAaos()) {
179                 mAutomotiveLocationBypassAllowlist =
180                         LocationUtils.getAutomotiveLocationBypassAllowlist(requireContext());
181             }
182         }
183     }
184 
185     @Override
onViewCreated(@onNull View view, @Nullable Bundle savedInstanceState)186     public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
187         super.onViewCreated(view, savedInstanceState);
188 
189         PreferenceScreen screen = getPreferenceScreen();
190         screen.addPreference(
191                 AutoPermissionsUtils.createHeaderPreference(requireContext(),
192                         mPackageIcon, mPackageName, mPackageLabel));
193 
194         // Add permissions selector preferences.
195         PreferenceGroup permissionSelector = new PreferenceCategory(requireContext());
196         permissionSelector.setTitle(
197                 getString(R.string.app_permission_header, mPermGroupLabel));
198         screen.addPreference(permissionSelector);
199 
200         mAllowPermissionPreference = new SelectedPermissionPreference(requireContext());
201         mAllowPermissionPreference.setTitle(R.string.app_permission_button_allow);
202         permissionSelector.addPreference(mAllowPermissionPreference);
203 
204         mAlwaysPermissionPreference = new SelectedPermissionPreference(requireContext());
205         mAlwaysPermissionPreference.setTitle(R.string.app_permission_button_allow_always);
206         permissionSelector.addPreference(mAlwaysPermissionPreference);
207 
208         mForegroundOnlyPermissionPreference = new SelectedPermissionPreference(requireContext());
209         mForegroundOnlyPermissionPreference.setTitle(
210                 R.string.app_permission_button_allow_foreground);
211         permissionSelector.addPreference(mForegroundOnlyPermissionPreference);
212 
213         mDenyPermissionPreference = new SelectedPermissionPreference(requireContext());
214         mDenyPermissionPreference.setTitle(R.string.app_permission_button_deny);
215         permissionSelector.addPreference(mDenyPermissionPreference);
216 
217         mAllowPermissionPreference.setOnPreferenceClickListener(v -> {
218             checkOnlyOneButtonOverride(AppPermissionViewModel.ButtonType.ALLOW);
219             setResult(GrantPermissionsViewHandler.GRANTED_ALWAYS);
220             requestChange(ChangeRequest.GRANT_FOREGROUND,
221                     APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW);
222             return true;
223         });
224         mAlwaysPermissionPreference.setOnPreferenceClickListener(v -> {
225             checkOnlyOneButtonOverride(AppPermissionViewModel.ButtonType.ALLOW_ALWAYS);
226             setResult(GrantPermissionsViewHandler.GRANTED_ALWAYS);
227             requestChange(ChangeRequest.GRANT_BOTH,
228                     APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW_ALWAYS);
229             return true;
230         });
231         mForegroundOnlyPermissionPreference.setOnPreferenceClickListener(v -> {
232             checkOnlyOneButtonOverride(AppPermissionViewModel.ButtonType.ALLOW_FOREGROUND);
233             setResult(GrantPermissionsViewHandler.GRANTED_FOREGROUND_ONLY);
234             requestChange(ChangeRequest.GRANT_FOREGROUND_ONLY,
235                     APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW_FOREGROUND);
236             return true;
237         });
238         mDenyPermissionPreference.setOnPreferenceClickListener(v -> {
239             checkOnlyOneButtonOverride(AppPermissionViewModel.ButtonType.DENY);
240             setResult(GrantPermissionsViewHandler.DENIED);
241             requestChange(ChangeRequest.REVOKE_BOTH,
242                     APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__DENY);
243             return true;
244         });
245 
246         mDetailsPreference = new AutoTwoTargetPreference(requireContext());
247         screen.addPreference(mDetailsPreference);
248     }
249 
250     @Override
onStart()251     public void onStart() {
252         super.onStart();
253         Activity activity = requireActivity();
254 
255         // Get notified when the package is removed.
256         mPackageRemovalMonitor = new PackageRemovalMonitor(requireContext(), mPackageName) {
257             @Override
258             public void onPackageRemoved() {
259                 Log.w(LOG_TAG, mPackageName + " was uninstalled");
260                 activity.setResult(Activity.RESULT_CANCELED);
261                 activity.finish();
262             }
263         };
264         mPackageRemovalMonitor.register();
265 
266         // Check if the package was removed while this activity was not started.
267         try {
268             activity.createPackageContextAsUser(mPackageName, /* flags= */ 0,
269                     mUser).getPackageManager().getPackageInfo(mPackageName,
270                     /* flags= */ 0);
271         } catch (PackageManager.NameNotFoundException e) {
272             Log.w(LOG_TAG, mPackageName + " was uninstalled while this activity was stopped", e);
273             activity.setResult(Activity.RESULT_CANCELED);
274             activity.finish();
275         }
276 
277         long sessionId = getArguments().getLong(EXTRA_SESSION_ID, INVALID_SESSION_ID);
278         AppPermissionViewModelFactory factory = new AppPermissionViewModelFactory(
279                 getActivity().getApplication(), mPackageName, mPermGroupName, mUser, sessionId);
280         mViewModel = new ViewModelProvider(this, factory).get(AppPermissionViewModel.class);
281         mViewModel.getButtonStateLiveData().observe(this, this::setRadioButtonsState);
282         mViewModel.getDetailResIdLiveData().observe(this, this::setDetail);
283         mViewModel.getShowAdminSupportLiveData().observe(this, this::setAdminSupportDetail);
284         if (SdkLevel.isAtLeastV()) {
285             if (Manifest.permission_group.CAMERA.equals(mPermGroupName)) {
286                 mViewModel.getSensorStatusLiveData().observe(this, this::setSensorStatus);
287             }
288             if (Flags.addBannersToPrivacySensitiveAppsForAaos()) {
289                 if (Manifest.permission_group.LOCATION.equals(mPermGroupName)) {
290                     mViewModel.getSensorStatusLiveData().observe(this, this::setSensorStatus);
291                 }
292                 if (Manifest.permission_group.MICROPHONE.equals(mPermGroupName)) {
293                     mViewModel.getSensorStatusLiveData().observe(this, this::setSensorStatus);
294                 }
295             }
296         }
297     }
298 
299     @Override
onStop()300     public void onStop() {
301         super.onStop();
302 
303         if (mPackageRemovalMonitor != null) {
304             mPackageRemovalMonitor.unregister();
305             mPackageRemovalMonitor = null;
306         }
307     }
308 
309     @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
setSensorStatus(Boolean sensorStatus)310     private void setSensorStatus(Boolean sensorStatus) {
311         Boolean isRequiredApp = null;
312         Boolean isRequiredAppCard = null;
313         if (Manifest.permission_group.CAMERA.equals(mPermGroupName)) {
314             int state = mSensorPrivacyManager.getSensorPrivacyState(
315                     SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE,
316                     SensorPrivacyManager.Sensors.CAMERA);
317             isRequiredApp = mCameraPrivacyAllowlist.contains(mPackageName);
318             isRequiredAppCard =
319                     state == SensorPrivacyManager.StateTypes.ENABLED_EXCEPT_ALLOWLISTED_APPS
320                             && isRequiredApp;
321         } else if (Manifest.permission_group.LOCATION.equals(mPermGroupName)) {
322             isRequiredApp = mAutomotiveLocationBypassAllowlist.contains(mPackageName);
323             isRequiredAppCard =
324                     isRequiredApp && LocationUtils.isAutomotiveLocationBypassEnabled(
325                             getPreferenceManager().getContext());
326         } else if (Manifest.permission_group.MICROPHONE.equals(mPermGroupName)) {
327             isRequiredApp = false;
328             isRequiredAppCard = false;
329         }
330 
331         if (isRequiredApp != null && isRequiredAppCard != null) {
332             if (sensorStatus) {
333                 setSensorCard(isRequiredAppCard, isRequiredApp);
334             } else {
335                 removeSensorCard(isRequiredAppCard);
336             }
337         }
338     }
339 
340     @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
setSensorCard(boolean isRequiredAppCard, boolean isRequiredApp)341     private void setSensorCard(boolean isRequiredAppCard, boolean isRequiredApp) {
342         if (isRequiredAppCard) {
343             setRequiredAppCard();
344         } else {
345             setBlockedAppCard(isRequiredApp);
346         }
347     }
348 
setRequiredAppCard()349     private void setRequiredAppCard() {
350         AutoCardViewPreference sensorCard = findPreference(REQUIRED_APP_PREF_KEY);
351         if (sensorCard == null) {
352             sensorCard = createRequiredAppCard();
353             if (getPreferenceScreen() != null) {
354                 getPreferenceScreen().addPreference(sensorCard);
355             }
356         }
357         sensorCard.setVisible(true);
358     }
359 
setBlockedAppCard(boolean isRequiredApp)360     private void setBlockedAppCard(boolean isRequiredApp) {
361         AutoCardViewPreference sensorCard = findPreference(BLOCKED_APP_PREF_KEY);
362         if (sensorCard == null) {
363             sensorCard = createBlockedAppCard(isRequiredApp);
364             if (getPreferenceScreen() != null) {
365                 getPreferenceScreen().addPreference(sensorCard);
366             }
367         }
368         sensorCard.setVisible(true);
369     }
370 
createRequiredAppCard()371     private AutoCardViewPreference createRequiredAppCard() {
372         Context context = getPreferenceManager().getContext();
373         AutoCardViewPreference sensorCard = new AutoCardViewPreference(context);
374         sensorCard.setKey(REQUIRED_APP_PREF_KEY);
375         sensorCard.setIcon(KotlinUtils.INSTANCE.getPermGroupIcon(context, mPermGroupName));
376         sensorCard.setTitle(context.getString(R.string.automotive_required_app_title));
377         sensorCard.setSummary(context.getString(R.string.automotive_required_app_summary));
378         sensorCard.setVisible(true);
379         sensorCard.setOrder(-1);
380         return sensorCard;
381     }
382 
createBlockedAppCard(boolean isRequiredApp)383     private AutoCardViewPreference createBlockedAppCard(boolean isRequiredApp) {
384         Context context = getPreferenceManager().getContext();
385 
386         AutoCardViewPreference sensorCard = new AutoCardViewPreference(context);
387         sensorCard.setKey(BLOCKED_APP_PREF_KEY);
388         sensorCard.setIcon(Utils.getBlockedIcon(mPermGroupName));
389         sensorCard.setTitle(context.getString(Utils.getBlockedTitleAutomotive(mPermGroupName)));
390         if (isRequiredApp) {
391             sensorCard.setSummary(context.getString(
392                     R.string.automotive_blocked_required_app_summary));
393         } else {
394             sensorCard.setSummary(context.getString(
395                     R.string.automotive_blocked_infotainment_app_summary));
396         }
397         sensorCard.setVisible(true);
398         sensorCard.setOrder(-1);
399         return sensorCard;
400     }
401 
402     @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
removeSensorCard(boolean isRequiredAppCard)403     private void removeSensorCard(boolean isRequiredAppCard) {
404         if (isRequiredAppCard) {
405             removeRequiredAppCard();
406         } else {
407             removeBlockedAppCard();
408         }
409     }
410 
removeRequiredAppCard()411     private void removeRequiredAppCard() {
412         AutoCardViewPreference sensorCard = findPreference(REQUIRED_APP_PREF_KEY);
413         if (sensorCard != null) {
414             sensorCard.setVisible(false);
415         }
416     }
417 
removeBlockedAppCard()418     private void removeBlockedAppCard() {
419         AutoCardViewPreference sensorCard = findPreference(BLOCKED_APP_PREF_KEY);
420         if (sensorCard != null) {
421             sensorCard.setVisible(false);
422         }
423     }
424 
425     @Override
showConfirmDialog(ChangeRequest changeRequest, int messageId, int buttonPressed, boolean oneTime)426     public void showConfirmDialog(ChangeRequest changeRequest, int messageId,
427             int buttonPressed, boolean oneTime) {
428         Bundle args = new Bundle();
429 
430         args.putInt(ConfirmDialog.MSG, messageId);
431         args.putSerializable(ConfirmDialog.CHANGE_REQUEST, changeRequest);
432         args.putSerializable(ConfirmDialog.BUTTON, buttonPressed);
433 
434         ConfirmDialog confirmDialog = new ConfirmDialog();
435         confirmDialog.setArguments(args);
436         confirmDialog.setTargetFragment(this, 0);
437         confirmDialog.show(requireFragmentManager().beginTransaction(),
438                 ConfirmDialog.class.getName());
439     }
440 
setResult(@rantPermissionsViewHandler.Result int result)441     private void setResult(@GrantPermissionsViewHandler.Result int result) {
442         Intent intent = new Intent()
443                 .putExtra(EXTRA_RESULT_PERMISSION_INTERACTED,
444                         requireArguments().getString(Intent.EXTRA_PERMISSION_GROUP_NAME))
445                 .putExtra(EXTRA_RESULT_PERMISSION_RESULT, result);
446         requireActivity().setResult(Activity.RESULT_OK, intent);
447     }
448 
setRadioButtonsState( Map<AppPermissionViewModel.ButtonType, AppPermissionViewModel.ButtonState> states)449     private void setRadioButtonsState(
450             Map<AppPermissionViewModel.ButtonType, AppPermissionViewModel.ButtonState> states) {
451         setButtonState(mAllowPermissionPreference,
452                 states.get(AppPermissionViewModel.ButtonType.ALLOW));
453         setButtonState(mAlwaysPermissionPreference,
454                 states.get(AppPermissionViewModel.ButtonType.ALLOW_ALWAYS));
455         setButtonState(mForegroundOnlyPermissionPreference,
456                 states.get(AppPermissionViewModel.ButtonType.ALLOW_FOREGROUND));
457         setButtonState(mDenyPermissionPreference,
458                 states.get(AppPermissionViewModel.ButtonType.DENY));
459     }
460 
setButtonState(TwoStatePreference button, AppPermissionViewModel.ButtonState state)461     private void setButtonState(TwoStatePreference button,
462             AppPermissionViewModel.ButtonState state) {
463         button.setVisible(state.isShown());
464         if (state.isShown()) {
465             button.setChecked(state.isChecked());
466             button.setEnabled(state.isEnabled());
467         }
468     }
469 
470     /**
471      * Helper method to handle the UX edge case where the confirmation dialog is shown and two
472      * buttons are selected at once. This happens since the Auto UI doesn't use a proper radio
473      * group, so there is nothing that enforces that tapping on a button unchecks a previously
474      * checked button. Apart from this case, this UI is not necessary since the UI is entirely
475      * driven by the ViewModel.
476      */
checkOnlyOneButtonOverride(AppPermissionViewModel.ButtonType buttonType)477     private void checkOnlyOneButtonOverride(AppPermissionViewModel.ButtonType buttonType) {
478         mAllowPermissionPreference.setChecked(
479                 buttonType == AppPermissionViewModel.ButtonType.ALLOW);
480         mAlwaysPermissionPreference.setChecked(
481                 buttonType == AppPermissionViewModel.ButtonType.ALLOW_ALWAYS);
482         mForegroundOnlyPermissionPreference.setChecked(
483                 buttonType == AppPermissionViewModel.ButtonType.ALLOW_FOREGROUND);
484         mDenyPermissionPreference.setChecked(buttonType == AppPermissionViewModel.ButtonType.DENY);
485     }
486 
setDetail(Pair<Integer, Integer> detailResIds)487     private void setDetail(Pair<Integer, Integer> detailResIds) {
488         if (detailResIds == null) {
489             mDetailsPreference.setVisible(false);
490             return;
491         }
492         if (detailResIds.getSecond() != null) {
493             mDetailsPreference.setWidgetLayoutResource(R.layout.settings_preference_widget);
494             mDetailsPreference.setOnSecondTargetClickListener(
495                     v -> showAllPermissions(mPermGroupName));
496             mDetailsPreference.setSummary(
497                     getString(detailResIds.getFirst(), detailResIds.getSecond()));
498         } else {
499             mDetailsPreference.setSummary(detailResIds.getFirst());
500         }
501     }
502 
503     /**
504      * Show all individual permissions in this group in a new fragment.
505      */
showAllPermissions(@onNull String filterGroup)506     private void showAllPermissions(@NonNull String filterGroup) {
507         Fragment frag = AutoAllAppPermissionsFragment.newInstance(mPackageName,
508                 filterGroup, mUser,
509                 getArguments().getLong(EXTRA_SESSION_ID, INVALID_SESSION_ID));
510         requireFragmentManager().beginTransaction()
511                 .replace(android.R.id.content, frag)
512                 .addToBackStack("AllPerms")
513                 .commit();
514     }
515 
setAdminSupportDetail(RestrictedLockUtils.EnforcedAdmin admin)516     private void setAdminSupportDetail(RestrictedLockUtils.EnforcedAdmin admin) {
517         if (admin != null) {
518             mDetailsPreference.setWidgetLayoutResource(R.layout.info_preference_widget);
519             mDetailsPreference.setOnSecondTargetClickListener(v ->
520                     RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(), admin)
521             );
522         }
523     }
524 
525     /**
526      * Request to grant/revoke permissions group.
527      */
requestChange(ChangeRequest changeRequest, int buttonClicked)528     private void requestChange(ChangeRequest changeRequest,
529             int buttonClicked) {
530         mViewModel.requestChange(/* setOneTime= */false, /* fragment= */ this,
531                 /* defaultDeny= */this, changeRequest, buttonClicked);
532     }
533 
534     /** Preference used to represent apps that can be picked as a default app. */
535     private static class SelectedPermissionPreference extends TwoStatePreference {
536 
SelectedPermissionPreference(Context context)537         SelectedPermissionPreference(Context context) {
538             super(context, null,
539                     TypedArrayUtils.getAttr(context, androidx.preference.R.attr.preferenceStyle,
540                             android.R.attr.preferenceStyle));
541             setPersistent(false);
542             setLayoutResource(R.layout.car_radio_button_preference);
543             setWidgetLayoutResource(R.layout.radio_button_preference_widget);
544         }
545 
546         @Override
onBindViewHolder(PreferenceViewHolder holder)547         public void onBindViewHolder(PreferenceViewHolder holder) {
548             super.onBindViewHolder(holder);
549 
550             RadioButton radioButton = (RadioButton) holder.findViewById(R.id.radio_button);
551             radioButton.setChecked(isChecked());
552         }
553     }
554 
555     /**
556      * A dialog warning the user that they are about to deny a permission that was granted by
557      * default.
558      *
559      * @see #showConfirmDialog(ChangeRequest, int, int, boolean)
560      */
561     public static class ConfirmDialog extends DialogFragment {
562         private static final String MSG = ConfirmDialog.class.getName() + ".arg.msg";
563         private static final String CHANGE_REQUEST = ConfirmDialog.class.getName()
564                 + ".arg.changeRequest";
565         private static final String BUTTON = ConfirmDialog.class.getName()
566                 + ".arg.button";
567         private static int sCode =  APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW;
568 
569         @NonNull
570         @Override
onCreateDialog(Bundle savedInstanceState)571         public Dialog onCreateDialog(Bundle savedInstanceState) {
572             // TODO(b/229024576): This code is duplicated, refactor ConfirmDialog for easier
573             // NFF sharing
574             boolean isGrantFileAccess = getArguments().getSerializable(CHANGE_REQUEST)
575                     == ChangeRequest.GRANT_ALL_FILE_ACCESS;
576             boolean isGrantStorageSupergroup = getArguments().getSerializable(CHANGE_REQUEST)
577                     == ChangeRequest.GRANT_STORAGE_SUPERGROUP;
578             int positiveButtonStringResId = R.string.grant_dialog_button_deny_anyway;
579             if (isGrantFileAccess || isGrantStorageSupergroup) {
580                 positiveButtonStringResId = R.string.grant_dialog_button_allow;
581             }
582             AutoAppPermissionFragment fragment = (AutoAppPermissionFragment) getTargetFragment();
583             return new AlertDialogBuilder(getContext())
584                     .setMessage(requireArguments().getInt(MSG))
585                     .setNegativeButton(R.string.cancel,
586                             (dialog, which) -> dialog.cancel())
587                     .setPositiveButton(positiveButtonStringResId,
588                             (dialog, which) -> {
589                                 if (isGrantFileAccess) {
590                                     fragment.mViewModel.setAllFilesAccess(true);
591                                     fragment.mViewModel.requestChange(false, fragment,
592                                             fragment, ChangeRequest.GRANT_BOTH, sCode);
593                                 } else if (isGrantStorageSupergroup) {
594                                     fragment.mViewModel.requestChange(false, fragment,
595                                             fragment, ChangeRequest.GRANT_BOTH, sCode);
596                                 } else {
597                                     fragment.mViewModel.onDenyAnyWay((ChangeRequest)
598                                                     getArguments().getSerializable(CHANGE_REQUEST),
599                                             getArguments().getInt(BUTTON),
600                                             /* oneTime= */ false);
601                                 }
602                             })
603                     .create();
604         }
605 
606         @Override
onCancel(DialogInterface dialog)607         public void onCancel(DialogInterface dialog) {
608             AutoAppPermissionFragment fragment = (AutoAppPermissionFragment) getTargetFragment();
609             fragment.setRadioButtonsState(fragment.mViewModel.getButtonStateLiveData().getValue());
610         }
611     }
612 
613     @Override
614     public void showAdvancedConfirmDialog(AdvancedConfirmDialogArgs args) {
615         AlertDialog.Builder b = new AlertDialog.Builder(getContext())
616                 .setIcon(args.getIconId())
617                 .setMessage(args.getMessageId())
618                 .setOnCancelListener((DialogInterface dialog) -> {
619                     setRadioButtonsState(mViewModel.getButtonStateLiveData().getValue());
620                 })
621                 .setNegativeButton(args.getNegativeButtonTextId(),
622                         (DialogInterface dialog, int which) -> {
623                             setRadioButtonsState(mViewModel.getButtonStateLiveData().getValue());
624                         })
625                 .setPositiveButton(args.getPositiveButtonTextId(),
626                         (DialogInterface dialog, int which) -> {
627                             mViewModel.requestChange(args.getSetOneTime(),
628                                     AutoAppPermissionFragment.this, AutoAppPermissionFragment.this,
629                                     args.getChangeRequest(), args.getButtonClicked());
630                         });
631         if (args.getTitleId() != 0) {
632             b.setTitle(args.getTitleId());
633         }
634         b.show();
635     }
636 }
637