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