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.permission.ui.ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_INTERACTED;
20 import static com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_RESULT;
21 
22 import static java.lang.annotation.RetentionPolicy.SOURCE;
23 
24 import android.app.Activity;
25 import android.app.Dialog;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.pm.PackageInfo;
29 import android.content.pm.PackageItemInfo;
30 import android.content.pm.PackageManager;
31 import android.content.pm.PermissionInfo;
32 import android.os.Bundle;
33 import android.os.UserHandle;
34 import android.util.Log;
35 import android.view.View;
36 import android.widget.RadioButton;
37 
38 import androidx.annotation.IntDef;
39 import androidx.annotation.NonNull;
40 import androidx.annotation.Nullable;
41 import androidx.core.content.res.TypedArrayUtils;
42 import androidx.fragment.app.DialogFragment;
43 import androidx.fragment.app.Fragment;
44 import androidx.preference.PreferenceCategory;
45 import androidx.preference.PreferenceGroup;
46 import androidx.preference.PreferenceScreen;
47 import androidx.preference.PreferenceViewHolder;
48 import androidx.preference.TwoStatePreference;
49 
50 import com.android.car.ui.AlertDialogBuilder;
51 import com.android.permissioncontroller.R;
52 import com.android.permissioncontroller.auto.AutoSettingsFrameFragment;
53 import com.android.permissioncontroller.permission.model.AppPermissionGroup;
54 import com.android.permissioncontroller.permission.model.Permission;
55 import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler;
56 import com.android.permissioncontroller.permission.utils.LocationUtils;
57 import com.android.permissioncontroller.permission.utils.PackageRemovalMonitor;
58 import com.android.permissioncontroller.permission.utils.SafetyNetLogger;
59 import com.android.permissioncontroller.permission.utils.Utils;
60 import com.android.settingslib.RestrictedLockUtils;
61 
62 import java.lang.annotation.Retention;
63 import java.util.List;
64 
65 /** Settings related to a particular permission for the given app. */
66 public class AutoAppPermissionFragment extends AutoSettingsFrameFragment {
67 
68     private static final String LOG_TAG = "AppPermissionFragment";
69 
70     @Retention(SOURCE)
71     @IntDef(value = {CHANGE_FOREGROUND, CHANGE_BACKGROUND}, flag = true)
72     @interface ChangeTarget {
73     }
74 
75     static final int CHANGE_FOREGROUND = 1;
76     static final int CHANGE_BACKGROUND = 2;
77     static final int CHANGE_BOTH = CHANGE_FOREGROUND | CHANGE_BACKGROUND;
78 
79     @NonNull
80     private AppPermissionGroup mGroup;
81 
82     @NonNull
83     private TwoStatePreference mAlwaysPermissionPreference;
84     @NonNull
85     private TwoStatePreference mForegroundOnlyPermissionPreference;
86     @NonNull
87     private TwoStatePreference mDenyPermissionPreference;
88     @NonNull
89     private AutoTwoTargetPreference mDetailsPreference;
90 
91     private boolean mHasConfirmedRevoke;
92 
93     /**
94      * Listens for changes to the permission of the app the permission is currently getting
95      * granted to. {@code null} when unregistered.
96      */
97     @Nullable
98     private PackageManager.OnPermissionsChangedListener mPermissionChangeListener;
99 
100     /**
101      * Listens for changes to the app the permission is currently getting granted to. {@code null}
102      * when unregistered.
103      */
104     @Nullable
105     private PackageRemovalMonitor mPackageRemovalMonitor;
106 
107     /**
108      * Returns a new {@link AutoAppPermissionFragment}.
109      *
110      * @param packageName the package name for which the permission is being changed
111      * @param permName the name of the permission being changed
112      * @param groupName the name of the permission group being changed
113      * @param userHandle the user for which the permission is being changed
114      */
115     @NonNull
newInstance(@onNull String packageName, @NonNull String permName, @Nullable String groupName, @NonNull UserHandle userHandle)116     public static AutoAppPermissionFragment newInstance(@NonNull String packageName,
117             @NonNull String permName, @Nullable String groupName, @NonNull UserHandle userHandle) {
118         AutoAppPermissionFragment fragment = new AutoAppPermissionFragment();
119         Bundle arguments = new Bundle();
120         arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
121         if (groupName == null) {
122             arguments.putString(Intent.EXTRA_PERMISSION_NAME, permName);
123         } else {
124             arguments.putString(Intent.EXTRA_PERMISSION_GROUP_NAME, groupName);
125         }
126         arguments.putParcelable(Intent.EXTRA_USER, userHandle);
127         fragment.setArguments(arguments);
128         return fragment;
129     }
130 
131     @Override
onCreate(@ullable Bundle savedInstanceState)132     public void onCreate(@Nullable Bundle savedInstanceState) {
133         super.onCreate(savedInstanceState);
134 
135         mHasConfirmedRevoke = false;
136 
137         mGroup = getAppPermissionGroup();
138         if (mGroup == null) {
139             requireActivity().setResult(Activity.RESULT_CANCELED);
140             requireActivity().finish();
141             return;
142         }
143 
144         setHeaderLabel(
145                 requireContext().getString(R.string.app_permission_title, mGroup.getFullLabel()));
146     }
147 
setResult(@rantPermissionsViewHandler.Result int result)148     private void setResult(@GrantPermissionsViewHandler.Result int result) {
149         Intent intent = new Intent()
150                 .putExtra(EXTRA_RESULT_PERMISSION_INTERACTED,
151                         requireArguments().getString(Intent.EXTRA_PERMISSION_GROUP_NAME))
152                 .putExtra(EXTRA_RESULT_PERMISSION_RESULT, result);
153         requireActivity().setResult(Activity.RESULT_OK, intent);
154     }
155 
getAppPermissionGroup()156     private AppPermissionGroup getAppPermissionGroup() {
157         Activity activity = requireActivity();
158         Context context = getPreferenceManager().getContext();
159 
160         String packageName = requireArguments().getString(Intent.EXTRA_PACKAGE_NAME);
161         String groupName = requireArguments().getString(Intent.EXTRA_PERMISSION_GROUP_NAME);
162         if (groupName == null) {
163             groupName = requireArguments().getString(Intent.EXTRA_PERMISSION_NAME);
164         }
165         PackageItemInfo groupInfo = Utils.getGroupInfo(groupName, context);
166         List<PermissionInfo> groupPermInfos = Utils.getGroupPermissionInfos(groupName, context);
167         if (groupInfo == null || groupPermInfos == null) {
168             Log.i(LOG_TAG, "Illegal group: " + groupName);
169             return null;
170         }
171         UserHandle userHandle = requireArguments().getParcelable(Intent.EXTRA_USER);
172         if (userHandle == null) {
173             Log.e(LOG_TAG, "User handle is null");
174             return null;
175         }
176         PackageInfo packageInfo = AutoPermissionsUtils.getPackageInfo(activity, packageName,
177                 userHandle);
178         if (packageInfo == null) {
179             Log.i(LOG_TAG, "PackageInfo is null");
180             return null;
181         }
182         AppPermissionGroup group = AppPermissionGroup.create(context, packageInfo, groupInfo,
183                 groupPermInfos, false);
184 
185         if (group == null || !Utils.shouldShowPermission(context, group)) {
186             Log.i(LOG_TAG, "Illegal group: " + (group == null ? "null" : group.getName()));
187             return null;
188         }
189 
190         return group;
191     }
192 
193     @Override
onCreatePreferences(Bundle bundle, String s)194     public void onCreatePreferences(Bundle bundle, String s) {
195         setPreferenceScreen(getPreferenceManager().createPreferenceScreen(requireContext()));
196     }
197 
198     @Override
onViewCreated(@onNull View view, @Nullable Bundle savedInstanceState)199     public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
200         super.onViewCreated(view, savedInstanceState);
201 
202         PreferenceScreen screen = getPreferenceScreen();
203         screen.addPreference(
204                 AutoPermissionsUtils.createHeaderPreference(requireContext(),
205                         mGroup.getApp().applicationInfo));
206 
207         // Add permissions selector preferences.
208         PreferenceGroup permissionSelector = new PreferenceCategory(requireContext());
209         permissionSelector.setTitle(
210                 getString(R.string.app_permission_header, mGroup.getFullLabel()));
211         screen.addPreference(permissionSelector);
212 
213         mAlwaysPermissionPreference = new SelectedPermissionPreference(requireContext());
214         mAlwaysPermissionPreference.setTitle(R.string.app_permission_button_allow_always);
215         permissionSelector.addPreference(mAlwaysPermissionPreference);
216 
217         mForegroundOnlyPermissionPreference = new SelectedPermissionPreference(requireContext());
218         mForegroundOnlyPermissionPreference.setTitle(
219                 R.string.app_permission_button_allow_foreground);
220         permissionSelector.addPreference(mForegroundOnlyPermissionPreference);
221 
222         mDenyPermissionPreference = new SelectedPermissionPreference(requireContext());
223         mDenyPermissionPreference.setTitle(R.string.app_permission_button_deny);
224         permissionSelector.addPreference(mDenyPermissionPreference);
225 
226         mDetailsPreference = new AutoTwoTargetPreference(requireContext());
227         screen.addPreference(mDetailsPreference);
228     }
229 
230     @Override
onStart()231     public void onStart() {
232         super.onStart();
233         Activity activity = requireActivity();
234 
235         mPermissionChangeListener = new PermissionChangeListener(
236                 mGroup.getApp().applicationInfo.uid);
237         PackageManager pm = activity.getPackageManager();
238         pm.addOnPermissionsChangeListener(mPermissionChangeListener);
239 
240         // Get notified when the package is removed.
241         String packageName = mGroup.getApp().packageName;
242         mPackageRemovalMonitor = new PackageRemovalMonitor(requireContext(), packageName) {
243             @Override
244             public void onPackageRemoved() {
245                 Log.w(LOG_TAG, packageName + " was uninstalled");
246                 activity.setResult(Activity.RESULT_CANCELED);
247                 activity.finish();
248             }
249         };
250         mPackageRemovalMonitor.register();
251 
252         // Check if the package was removed while this activity was not started.
253         try {
254             activity.createPackageContextAsUser(packageName, /* flags= */ 0,
255                     mGroup.getUser()).getPackageManager().getPackageInfo(packageName,
256                     /* flags= */ 0);
257         } catch (PackageManager.NameNotFoundException e) {
258             Log.w(LOG_TAG, packageName + " was uninstalled while this activity was stopped", e);
259             activity.setResult(Activity.RESULT_CANCELED);
260             activity.finish();
261         }
262 
263         // Re-create the permission group in case permissions have changed and update the UI.
264         mGroup = getAppPermissionGroup();
265         updateUi();
266     }
267 
268     @Override
onStop()269     public void onStop() {
270         super.onStop();
271 
272         if (mPackageRemovalMonitor != null) {
273             mPackageRemovalMonitor.unregister();
274             mPackageRemovalMonitor = null;
275         }
276 
277         if (mPermissionChangeListener != null) {
278             requireActivity().getPackageManager().removeOnPermissionsChangeListener(
279                     mPermissionChangeListener);
280             mPermissionChangeListener = null;
281         }
282     }
283 
updateUi()284     private void updateUi() {
285         mDetailsPreference.setOnSecondTargetClickListener(null);
286         mDetailsPreference.setVisible(false);
287 
288         if (mGroup.areRuntimePermissionsGranted()) {
289             if (!mGroup.hasPermissionWithBackgroundMode()
290                     || (mGroup.getBackgroundPermissions() != null
291                     && mGroup.getBackgroundPermissions().areRuntimePermissionsGranted())) {
292                 setSelectedPermissionState(mAlwaysPermissionPreference);
293             } else {
294                 setSelectedPermissionState(mForegroundOnlyPermissionPreference);
295             }
296         } else {
297             setSelectedPermissionState(mDenyPermissionPreference);
298         }
299 
300         mAlwaysPermissionPreference.setOnPreferenceClickListener(v -> {
301             setResult(GrantPermissionsViewHandler.GRANTED_ALWAYS);
302             return requestChange(/* requestGrant= */true, CHANGE_BOTH);
303         });
304         mForegroundOnlyPermissionPreference.setOnPreferenceClickListener(v -> {
305             setResult(GrantPermissionsViewHandler.GRANTED_FOREGROUND_ONLY);
306             requestChange(/* requestGrant= */false, CHANGE_BACKGROUND);
307             requestChange(/* requestGrant= */true, CHANGE_FOREGROUND);
308             return true;
309         });
310         mDenyPermissionPreference.setOnPreferenceClickListener(v -> {
311             setResult(GrantPermissionsViewHandler.DENIED);
312             return requestChange(/* requestGrant= */ false, CHANGE_BOTH);
313         });
314 
315         // Set the allow and foreground-only button states appropriately.
316         if (mGroup.hasPermissionWithBackgroundMode()) {
317             if (mGroup.getBackgroundPermissions() == null) {
318                 mAlwaysPermissionPreference.setVisible(false);
319             } else {
320                 mForegroundOnlyPermissionPreference.setVisible(true);
321                 mAlwaysPermissionPreference.setTitle(R.string.app_permission_button_allow_always);
322             }
323         } else {
324             mForegroundOnlyPermissionPreference.setVisible(false);
325             mAlwaysPermissionPreference.setTitle(R.string.app_permission_button_allow);
326         }
327 
328         // Handle the UI for various special cases.
329         if (isSystemFixed() || isPolicyFullyFixed() || isForegroundDisabledByPolicy()) {
330             // Disable changing permissions and potentially show administrator message.
331             mAlwaysPermissionPreference.setEnabled(false);
332             mForegroundOnlyPermissionPreference.setEnabled(false);
333             mDenyPermissionPreference.setEnabled(false);
334 
335             RestrictedLockUtils.EnforcedAdmin admin = getAdmin();
336             if (admin != null) {
337                 mDetailsPreference.setWidgetLayoutResource(R.layout.info_preference_widget);
338                 mDetailsPreference.setOnSecondTargetClickListener(
339                         preference -> RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
340                                 getContext(), admin));
341             }
342 
343             updateDetailForFixedByPolicyPermissionGroup();
344         } else if (Utils.areGroupPermissionsIndividuallyControlled(requireContext(),
345                 mGroup.getName())) {
346             // If the permissions are individually controlled, also show a link to the page that
347             // lets you control them.
348             mDetailsPreference.setWidgetLayoutResource(R.layout.settings_preference_widget);
349             mDetailsPreference.setOnSecondTargetClickListener(
350                     preference -> showAllPermissions(mGroup.getName()));
351 
352             updateDetailForIndividuallyControlledPermissionGroup();
353         } else {
354             if (mGroup.hasPermissionWithBackgroundMode()) {
355                 if (mGroup.getBackgroundPermissions() == null) {
356                     // The group has background permissions but the app did not request any. I.e.
357                     // The app can only switch between 'never" and "only in foreground".
358                     mAlwaysPermissionPreference.setEnabled(false);
359 
360                     mDenyPermissionPreference.setOnPreferenceClickListener(v -> requestChange(false,
361                             CHANGE_FOREGROUND));
362                 } else {
363                     if (isBackgroundPolicyFixed()) {
364                         // If background policy is fixed, we only allow switching the foreground.
365                         // Note that this assumes that the background policy is fixed to deny,
366                         // since if it is fixed to grant, so is the foreground.
367                         mAlwaysPermissionPreference.setEnabled(false);
368                         setSelectedPermissionState(mForegroundOnlyPermissionPreference);
369 
370                         mDenyPermissionPreference.setOnPreferenceClickListener(
371                                 v -> requestChange(false, CHANGE_FOREGROUND));
372 
373                         updateDetailForFixedByPolicyPermissionGroup();
374                     } else if (isForegroundPolicyFixed()) {
375                         // Foreground permissions are fixed to allow (the first case above handles
376                         // fixing to deny), so we only allow toggling background permissions.
377                         mDenyPermissionPreference.setEnabled(false);
378 
379                         mAlwaysPermissionPreference.setOnPreferenceClickListener(
380                                 v -> requestChange(true, CHANGE_BACKGROUND));
381                         mForegroundOnlyPermissionPreference.setOnPreferenceClickListener(
382                                 v -> requestChange(false, CHANGE_BACKGROUND));
383 
384                         updateDetailForFixedByPolicyPermissionGroup();
385                     } else {
386                         // The default tri-state case is handled by default.
387                     }
388                 }
389 
390             } else {
391                 // The default bi-state case is handled by default.
392             }
393         }
394     }
395 
396     /**
397      * Set the given permission state as the only checked permission state.
398      */
setSelectedPermissionState(@onNull TwoStatePreference permissionState)399     private void setSelectedPermissionState(@NonNull TwoStatePreference permissionState) {
400         permissionState.setChecked(true);
401         if (permissionState != mAlwaysPermissionPreference) {
402             mAlwaysPermissionPreference.setChecked(false);
403         }
404         if (permissionState != mForegroundOnlyPermissionPreference) {
405             mForegroundOnlyPermissionPreference.setChecked(false);
406         }
407         if (permissionState != mDenyPermissionPreference) {
408             mDenyPermissionPreference.setChecked(false);
409         }
410     }
411 
412     /**
413      * Are any permissions of this group fixed by the system, i.e. not changeable by the user.
414      *
415      * @return {@code true} iff any permission is fixed
416      */
isSystemFixed()417     private boolean isSystemFixed() {
418         return mGroup.isSystemFixed();
419     }
420 
421     /**
422      * Is any foreground permissions of this group fixed by the policy, i.e. not changeable by the
423      * user.
424      *
425      * @return {@code true} iff any foreground permission is fixed
426      */
isForegroundPolicyFixed()427     private boolean isForegroundPolicyFixed() {
428         return mGroup.isPolicyFixed();
429     }
430 
431     /**
432      * Is any background permissions of this group fixed by the policy, i.e. not changeable by the
433      * user.
434      *
435      * @return {@code true} iff any background permission is fixed
436      */
isBackgroundPolicyFixed()437     private boolean isBackgroundPolicyFixed() {
438         return mGroup.getBackgroundPermissions() != null
439                 && mGroup.getBackgroundPermissions().isPolicyFixed();
440     }
441 
442     /**
443      * Are there permissions fixed, so that the user cannot change the preference at all?
444      *
445      * @return {@code true} iff the permissions of this group are fixed
446      */
isPolicyFullyFixed()447     private boolean isPolicyFullyFixed() {
448         return isForegroundPolicyFixed() && (mGroup.getBackgroundPermissions() == null
449                 || isBackgroundPolicyFixed());
450     }
451 
452     /**
453      * Is the foreground part of this group disabled. If the foreground is disabled, there is no
454      * need to possible grant background access.
455      *
456      * @return {@code true} iff the permissions of this group are fixed
457      */
isForegroundDisabledByPolicy()458     private boolean isForegroundDisabledByPolicy() {
459         return isForegroundPolicyFixed() && !mGroup.areRuntimePermissionsGranted();
460     }
461 
462     /**
463      * Get the app that acts as admin for this profile.
464      *
465      * @return The admin or {@code null} if there is no admin.
466      */
467     @Nullable
getAdmin()468     private RestrictedLockUtils.EnforcedAdmin getAdmin() {
469         return RestrictedLockUtils.getProfileOrDeviceOwner(getContext(), mGroup.getUser());
470     }
471 
472     /**
473      * Update the detail in the case the permission group has individually controlled permissions.
474      */
updateDetailForIndividuallyControlledPermissionGroup()475     private void updateDetailForIndividuallyControlledPermissionGroup() {
476         int revokedCount = 0;
477         List<Permission> permissions = mGroup.getPermissions();
478         for (Permission permission : permissions) {
479             if (!permission.isGrantedIncludingAppOp()) {
480                 revokedCount++;
481             }
482         }
483 
484         int resId;
485         if (revokedCount == 0) {
486             resId = R.string.permission_revoked_none;
487         } else if (revokedCount == permissions.size()) {
488             resId = R.string.permission_revoked_all;
489         } else {
490             resId = R.string.permission_revoked_count;
491         }
492 
493         mDetailsPreference.setSummary(getString(resId, revokedCount));
494         mDetailsPreference.setVisible(true);
495     }
496 
497     /**
498      * Update the detail of a permission group that is at least partially fixed by policy.
499      */
updateDetailForFixedByPolicyPermissionGroup()500     private void updateDetailForFixedByPolicyPermissionGroup() {
501         RestrictedLockUtils.EnforcedAdmin admin = getAdmin();
502         AppPermissionGroup backgroundGroup = mGroup.getBackgroundPermissions();
503 
504         boolean hasAdmin = admin != null;
505 
506         if (isSystemFixed()) {
507             // Permission is fully controlled by the system and cannot be switched
508 
509             setDetail(R.string.permission_summary_enabled_system_fixed);
510         } else if (isForegroundDisabledByPolicy()) {
511             // Permission is fully controlled by policy and cannot be switched
512 
513             if (hasAdmin) {
514                 setDetail(R.string.disabled_by_admin);
515             } else {
516                 // Disabled state will be displayed by switch, so no need to add text for that
517                 setDetail(R.string.permission_summary_enforced_by_policy);
518             }
519         } else if (isPolicyFullyFixed()) {
520             // Permission is fully controlled by policy and cannot be switched
521 
522             if (backgroundGroup == null) {
523                 if (hasAdmin) {
524                     setDetail(R.string.enabled_by_admin);
525                 } else {
526                     // Enabled state will be displayed by switch, so no need to add text for
527                     // that
528                     setDetail(R.string.permission_summary_enforced_by_policy);
529                 }
530             } else {
531                 if (backgroundGroup.areRuntimePermissionsGranted()) {
532                     if (hasAdmin) {
533                         setDetail(R.string.enabled_by_admin);
534                     } else {
535                         // Enabled state will be displayed by switch, so no need to add text for
536                         // that
537                         setDetail(R.string.permission_summary_enforced_by_policy);
538                     }
539                 } else {
540                     if (hasAdmin) {
541                         setDetail(
542                                 R.string.permission_summary_enabled_by_admin_foreground_only);
543                     } else {
544                         setDetail(
545                                 R.string.permission_summary_enabled_by_policy_foreground_only);
546                     }
547                 }
548             }
549         } else {
550             // Part of the permission group can still be switched
551 
552             if (isBackgroundPolicyFixed()) {
553                 if (backgroundGroup.areRuntimePermissionsGranted()) {
554                     if (hasAdmin) {
555                         setDetail(R.string.permission_summary_enabled_by_admin_background_only);
556                     } else {
557                         setDetail(R.string.permission_summary_enabled_by_policy_background_only);
558                     }
559                 } else {
560                     if (hasAdmin) {
561                         setDetail(R.string.permission_summary_disabled_by_admin_background_only);
562                     } else {
563                         setDetail(R.string.permission_summary_disabled_by_policy_background_only);
564                     }
565                 }
566             } else if (isForegroundPolicyFixed()) {
567                 if (hasAdmin) {
568                     setDetail(R.string.permission_summary_enabled_by_admin_foreground_only);
569                 } else {
570                     setDetail(R.string.permission_summary_enabled_by_policy_foreground_only);
571                 }
572             }
573         }
574     }
575 
576     /**
577      * Show the given string as informative text below permission picker preferences.
578      *
579      * @param strId the resourceId of the string to display.
580      */
setDetail(int strId)581     private void setDetail(int strId) {
582         mDetailsPreference.setSummary(strId);
583         mDetailsPreference.setVisible(true);
584     }
585 
586     /**
587      * Show all individual permissions in this group in a new fragment.
588      */
showAllPermissions(@onNull String filterGroup)589     private void showAllPermissions(@NonNull String filterGroup) {
590         Fragment frag = AutoAllAppPermissionsFragment.newInstance(mGroup.getApp().packageName,
591                 filterGroup, UserHandle.getUserHandleForUid(mGroup.getApp().applicationInfo.uid));
592         requireFragmentManager().beginTransaction()
593                 .replace(android.R.id.content, frag)
594                 .addToBackStack("AllPerms")
595                 .commit();
596     }
597 
598     /**
599      * Request to grant/revoke permissions group.
600      *
601      * <p>Does <u>not</u> handle:
602      * <ul>
603      * <li>Individually granted permissions</li>
604      * <li>Permission groups with background permissions</li>
605      * </ul>
606      * <p><u>Does</u> handle:
607      * <ul>
608      * <li>Default grant permissions</li>
609      * </ul>
610      *
611      * @param requestGrant If this group should be granted
612      * @param changeTarget Which permission group (foreground/background/both) should be changed
613      * @return If the request was processed.
614      */
requestChange(boolean requestGrant, @ChangeTarget int changeTarget)615     private boolean requestChange(boolean requestGrant, @ChangeTarget int changeTarget) {
616         if (LocationUtils.isLocationGroupAndProvider(getContext(), mGroup.getName(),
617                 mGroup.getApp().packageName)) {
618             LocationUtils.showLocationDialog(getContext(),
619                     Utils.getAppLabel(mGroup.getApp().applicationInfo, requireContext()));
620 
621             // The request was denied, so update the buttons.
622             updateUi();
623             return false;
624         }
625 
626         if (requestGrant) {
627             if ((changeTarget & CHANGE_FOREGROUND) != 0) {
628                 if (!mGroup.areRuntimePermissionsGranted()) {
629                     SafetyNetLogger.logPermissionToggled(mGroup);
630                 }
631 
632                 mGroup.grantRuntimePermissions(true, false);
633             }
634             if ((changeTarget & CHANGE_BACKGROUND) != 0) {
635                 if (mGroup.getBackgroundPermissions() != null) {
636                     if (!mGroup.getBackgroundPermissions().areRuntimePermissionsGranted()) {
637                         SafetyNetLogger.logPermissionToggled(mGroup.getBackgroundPermissions());
638                     }
639 
640                     mGroup.getBackgroundPermissions().grantRuntimePermissions(true, false);
641                 }
642             }
643         } else {
644             boolean showDefaultDenyDialog = false;
645 
646             if ((changeTarget & CHANGE_FOREGROUND) != 0
647                     && mGroup.areRuntimePermissionsGranted()) {
648                 showDefaultDenyDialog = mGroup.hasGrantedByDefaultPermission()
649                         || !mGroup.doesSupportRuntimePermissions()
650                         || mGroup.hasInstallToRuntimeSplit();
651             }
652             if ((changeTarget & CHANGE_BACKGROUND) != 0) {
653                 if (mGroup.getBackgroundPermissions() != null
654                         && mGroup.getBackgroundPermissions().areRuntimePermissionsGranted()) {
655                     AppPermissionGroup bgPerm = mGroup.getBackgroundPermissions();
656                     showDefaultDenyDialog |= bgPerm.hasGrantedByDefaultPermission()
657                             || !bgPerm.doesSupportRuntimePermissions()
658                             || bgPerm.hasInstallToRuntimeSplit();
659                 }
660             }
661 
662             if (showDefaultDenyDialog && !mHasConfirmedRevoke) {
663                 showDefaultDenyDialog(changeTarget);
664                 updateUi();
665                 return false;
666             } else {
667                 if ((changeTarget & CHANGE_FOREGROUND) != 0
668                         && mGroup.areRuntimePermissionsGranted()) {
669                     if (mGroup.areRuntimePermissionsGranted()) {
670                         SafetyNetLogger.logPermissionToggled(mGroup);
671                     }
672 
673                     mGroup.revokeRuntimePermissions(false);
674                 }
675                 if ((changeTarget & CHANGE_BACKGROUND) != 0) {
676                     if (mGroup.getBackgroundPermissions() != null
677                             && mGroup.getBackgroundPermissions().areRuntimePermissionsGranted()) {
678                         if (mGroup.getBackgroundPermissions().areRuntimePermissionsGranted()) {
679                             SafetyNetLogger.logPermissionToggled(mGroup.getBackgroundPermissions());
680                         }
681 
682                         mGroup.getBackgroundPermissions().revokeRuntimePermissions(false);
683                     }
684                 }
685             }
686         }
687 
688         updateUi();
689 
690         return true;
691     }
692 
693     /**
694      * Show a dialog that warns the user that she/he is about to revoke permissions that were
695      * granted by default.
696      *
697      * <p>The order of operation to revoke a permission granted by default is:
698      * <ol>
699      * <li>{@code showDefaultDenyDialog}</li>
700      * <li>{@link DefaultDenyDialog#onCreateDialog}</li>
701      * <li>{@link AutoAppPermissionFragment#onDenyAnyWay}</li>
702      * </ol>
703      *
704      * @param changeTarget Whether background or foreground should be changed
705      */
showDefaultDenyDialog(@hangeTarget int changeTarget)706     private void showDefaultDenyDialog(@ChangeTarget int changeTarget) {
707         Bundle args = new Bundle();
708 
709         boolean showGrantedByDefaultWarning = false;
710         if ((changeTarget & CHANGE_FOREGROUND) != 0) {
711             showGrantedByDefaultWarning = mGroup.hasGrantedByDefaultPermission();
712         }
713         if ((changeTarget & CHANGE_BACKGROUND) != 0) {
714             if (mGroup.getBackgroundPermissions() != null) {
715                 showGrantedByDefaultWarning |=
716                         mGroup.getBackgroundPermissions().hasGrantedByDefaultPermission();
717             }
718         }
719 
720         args.putInt(DefaultDenyDialog.MSG, showGrantedByDefaultWarning ? R.string.system_warning
721                 : R.string.old_sdk_deny_warning);
722         args.putInt(DefaultDenyDialog.CHANGE_TARGET, changeTarget);
723 
724         DefaultDenyDialog defaultDenyDialog = new DefaultDenyDialog();
725         defaultDenyDialog.setArguments(args);
726         defaultDenyDialog.setTargetFragment(this, 0);
727         defaultDenyDialog.show(requireFragmentManager().beginTransaction(),
728                 DefaultDenyDialog.class.getName());
729     }
730 
731     /**
732      * Once we user has confirmed that he/she wants to revoke a permission that was granted by
733      * default, actually revoke the permissions.
734      *
735      * @param changeTarget whether to change foreground, background, or both.
736      * @see #showDefaultDenyDialog(int)
737      */
onDenyAnyWay(@hangeTarget int changeTarget)738     private void onDenyAnyWay(@ChangeTarget int changeTarget) {
739         boolean hasDefaultPermissions = false;
740         if ((changeTarget & CHANGE_FOREGROUND) != 0) {
741             if (mGroup.areRuntimePermissionsGranted()) {
742                 SafetyNetLogger.logPermissionToggled(mGroup);
743             }
744 
745             mGroup.revokeRuntimePermissions(false);
746             hasDefaultPermissions = mGroup.hasGrantedByDefaultPermission();
747         }
748         if ((changeTarget & CHANGE_BACKGROUND) != 0) {
749             if (mGroup.getBackgroundPermissions() != null) {
750                 if (mGroup.getBackgroundPermissions().areRuntimePermissionsGranted()) {
751                     SafetyNetLogger.logPermissionToggled(mGroup.getBackgroundPermissions());
752                 }
753 
754                 mGroup.getBackgroundPermissions().revokeRuntimePermissions(false);
755                 hasDefaultPermissions |=
756                         mGroup.getBackgroundPermissions().hasGrantedByDefaultPermission();
757             }
758         }
759 
760         if (hasDefaultPermissions || !mGroup.doesSupportRuntimePermissions()) {
761             mHasConfirmedRevoke = true;
762         }
763         updateUi();
764     }
765 
766     /** Preference used to represent apps that can be picked as a default app. */
767     private static class SelectedPermissionPreference extends TwoStatePreference {
768 
SelectedPermissionPreference(Context context)769         SelectedPermissionPreference(Context context) {
770             super(context, null, TypedArrayUtils.getAttr(context, R.attr.preferenceStyle,
771                     android.R.attr.preferenceStyle));
772             setPersistent(false);
773             setLayoutResource(R.layout.car_radio_button_preference);
774             setWidgetLayoutResource(R.layout.radio_button_preference_widget);
775         }
776 
777         @Override
onBindViewHolder(PreferenceViewHolder holder)778         public void onBindViewHolder(PreferenceViewHolder holder) {
779             super.onBindViewHolder(holder);
780 
781             RadioButton radioButton = (RadioButton) holder.findViewById(R.id.radio_button);
782             radioButton.setChecked(isChecked());
783         }
784     }
785 
786     /**
787      * A dialog warning the user that they are about to deny a permission that was granted by
788      * default.
789      *
790      * @see #showDefaultDenyDialog(int)
791      */
792     public static class DefaultDenyDialog extends DialogFragment {
793         private static final String MSG = DefaultDenyDialog.class.getName() + ".arg.msg";
794         private static final String CHANGE_TARGET = DefaultDenyDialog.class.getName()
795                 + ".arg.changeTarget";
796 
797         @NonNull
798         @Override
onCreateDialog(Bundle savedInstanceState)799         public Dialog onCreateDialog(Bundle savedInstanceState) {
800             AutoAppPermissionFragment fragment = (AutoAppPermissionFragment) getTargetFragment();
801             return new AlertDialogBuilder(getContext())
802                     .setMessage(requireArguments().getInt(MSG))
803                     .setNegativeButton(R.string.cancel,
804                             (dialog, which) -> fragment.updateUi())
805                     .setPositiveButton(R.string.grant_dialog_button_deny_anyway,
806                             (dialog, which) ->
807                                     fragment.onDenyAnyWay(requireArguments().getInt(CHANGE_TARGET)))
808                     .create();
809         }
810     }
811 
812     /**
813      * A listener for permission changes.
814      */
815     private class PermissionChangeListener implements PackageManager.OnPermissionsChangedListener {
816         private final int mUid;
817 
PermissionChangeListener(int uid)818         PermissionChangeListener(int uid) {
819             mUid = uid;
820         }
821 
822         @Override
onPermissionsChanged(int uid)823         public void onPermissionsChanged(int uid) {
824             if (uid == mUid) {
825                 Log.w(LOG_TAG, "Permissions changed.");
826                 mGroup = getAppPermissionGroup();
827                 updateUi();
828             }
829         }
830     }
831 }
832