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