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