1 /* <lambda>null2 * Copyright (C) 2020 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.model 18 19 import android.Manifest 20 import android.app.AppOpsManager 21 import android.app.AppOpsManager.MODE_ALLOWED 22 import android.app.AppOpsManager.MODE_ERRORED 23 import android.app.AppOpsManager.OPSTR_MANAGE_EXTERNAL_STORAGE 24 import android.app.Application 25 import android.content.Intent 26 import android.os.Build 27 import android.os.Bundle 28 import android.os.UserHandle 29 import android.util.Log 30 import androidx.annotation.StringRes 31 import androidx.fragment.app.Fragment 32 import androidx.lifecycle.MutableLiveData 33 import androidx.lifecycle.ViewModel 34 import androidx.lifecycle.ViewModelProvider 35 import androidx.navigation.fragment.findNavController 36 import com.android.permissioncontroller.PermissionControllerStatsLog 37 import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED 38 import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_VIEWED 39 import com.android.permissioncontroller.R 40 import com.android.permissioncontroller.permission.data.FullStoragePermissionAppsLiveData 41 import com.android.permissioncontroller.permission.data.FullStoragePermissionAppsLiveData.FullStoragePackageState 42 import com.android.permissioncontroller.permission.data.LightAppPermGroupLiveData 43 import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData 44 import com.android.permissioncontroller.permission.data.get 45 import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup 46 import com.android.permissioncontroller.permission.model.livedatatypes.LightPermission 47 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType.ALLOW 48 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType.ALLOW_ALWAYS 49 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType.ALLOW_FOREGROUND 50 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType.ASK 51 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType.ASK_ONCE 52 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType.DENY 53 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType.DENY_FOREGROUND 54 import com.android.permissioncontroller.permission.utils.KotlinUtils 55 import com.android.permissioncontroller.permission.utils.LocationUtils 56 import com.android.permissioncontroller.permission.utils.SafetyNetLogger 57 import com.android.permissioncontroller.permission.utils.Utils 58 import com.android.permissioncontroller.permission.utils.Utils.ForegroundCapableType.ASSISTANT 59 import com.android.permissioncontroller.permission.utils.Utils.ForegroundCapableType.CARRIER_SERVICE 60 import com.android.permissioncontroller.permission.utils.Utils.ForegroundCapableType.SOUND_TRIGGER 61 import com.android.permissioncontroller.permission.utils.Utils.ForegroundCapableType.VOICE_INTERACTION 62 import com.android.permissioncontroller.permission.utils.navigateSafe 63 import com.android.settingslib.RestrictedLockUtils 64 import java.util.Random 65 import kotlin.collections.component1 66 import kotlin.collections.component2 67 68 /** 69 * ViewModel for the AppPermissionFragment. Determines button state and detail text strings, logs 70 * permission change information, and makes permission changes. 71 * 72 * @param app The current application 73 * @param packageName The name of the package this ViewModel represents 74 * @param permGroupName The name of the permission group this ViewModel represents 75 * @param user The user of the package 76 * @param sessionId A session ID used in logs to identify this particular session 77 * @param couldPackageHaveFgCapabilities Whether the package could access a foreground perm while in 78 * the background 79 */ 80 class AppPermissionViewModel( 81 private val app: Application, 82 private val packageName: String, 83 private val permGroupName: String, 84 private val user: UserHandle, 85 private val sessionId: Long, 86 private val foregroundCapableType: Utils.ForegroundCapableType 87 ) : ViewModel() { 88 89 companion object { 90 private val LOG_TAG = AppPermissionViewModel::class.java.simpleName 91 } 92 93 interface ConfirmDialogShowingFragment { 94 fun showConfirmDialog( 95 changeRequest: ChangeRequest, 96 @StringRes messageId: Int, 97 buttonPressed: Int, 98 oneTime: Boolean 99 ) 100 } 101 102 enum class ChangeRequest(val value: Int) { 103 GRANT_FOREGROUND(1), 104 REVOKE_FOREGROUND(2), 105 GRANT_BACKGROUND(4), 106 REVOKE_BACKGROUND(8), 107 GRANT_BOTH(GRANT_FOREGROUND.value or GRANT_BACKGROUND.value), 108 REVOKE_BOTH(REVOKE_FOREGROUND.value or REVOKE_BACKGROUND.value), 109 GRANT_FOREGROUND_ONLY(GRANT_FOREGROUND.value or REVOKE_BACKGROUND.value), 110 GRANT_All_FILE_ACCESS(16); 111 112 infix fun andValue(other: ChangeRequest): Int { 113 return value and other.value 114 } 115 } 116 117 enum class ButtonType(val type: Int) { 118 ALLOW(0), 119 ALLOW_ALWAYS(1), 120 ALLOW_FOREGROUND(2), 121 ASK_ONCE(3), 122 ASK(4), 123 DENY(5), 124 DENY_FOREGROUND(6); 125 } 126 127 private val isStorage = permGroupName == Manifest.permission_group.STORAGE 128 private var hasConfirmedRevoke = false 129 private var lightAppPermGroup: LightAppPermGroup? = null 130 131 /** 132 * A livedata which determines which detail string, if any, should be shown 133 */ 134 val detailResIdLiveData = MutableLiveData<Pair<Int, Int?>>() 135 /** 136 * A livedata which stores the device admin, if there is one 137 */ 138 val showAdminSupportLiveData = MutableLiveData<RestrictedLockUtils.EnforcedAdmin>() 139 140 /** 141 * A livedata which determines which detail string, if any, should be shown 142 */ 143 val fullStorageStateLiveData = object : SmartUpdateMediatorLiveData<FullStoragePackageState>() { 144 init { 145 if (isStorage) { 146 addSource(FullStoragePermissionAppsLiveData) { 147 updateIfActive() 148 } 149 } else { 150 value = null 151 } 152 } 153 override fun onUpdate() { 154 for (state in FullStoragePermissionAppsLiveData.value ?: return) { 155 if (state.packageName == packageName && state.user == user) { 156 value = state 157 return 158 } 159 } 160 value = null 161 return 162 } 163 } 164 165 data class ButtonState( 166 var isChecked: Boolean, 167 var isEnabled: Boolean, 168 var isShown: Boolean, 169 var customRequest: ChangeRequest? 170 ) { 171 constructor() : this(false, true, false, null) 172 } 173 174 /** 175 * A livedata which computes the state of the radio buttons 176 */ 177 val buttonStateLiveData = object 178 : SmartUpdateMediatorLiveData<@JvmSuppressWildcards Map<ButtonType, ButtonState>>() { 179 180 private val appPermGroupLiveData = LightAppPermGroupLiveData[packageName, permGroupName, 181 user] 182 183 init { 184 addSource(appPermGroupLiveData) { appPermGroup -> 185 lightAppPermGroup = appPermGroup 186 if (appPermGroupLiveData.isInitialized && appPermGroup == null) { 187 value = null 188 } else if (appPermGroup != null) { 189 if (isStorage && !fullStorageStateLiveData.isInitialized) { 190 return@addSource 191 } 192 if (value == null) { 193 logAppPermissionFragmentViewed() 194 } 195 updateIfActive() 196 } 197 } 198 199 if (isStorage) { 200 addSource(fullStorageStateLiveData) { 201 updateIfActive() 202 } 203 } 204 } 205 206 override fun onUpdate() { 207 val group = appPermGroupLiveData.value ?: return 208 209 val admin = RestrictedLockUtils.getProfileOrDeviceOwner(app, user) 210 211 val couldPackageHaveFgCapabilities = 212 foregroundCapableType != Utils.ForegroundCapableType.NONE 213 214 val allowedState = ButtonState() 215 val allowedAlwaysState = ButtonState() 216 val allowedForegroundState = ButtonState() 217 val askOneTimeState = ButtonState() 218 val askState = ButtonState() 219 val deniedState = ButtonState() 220 val deniedForegroundState = ButtonState() // when bg is fixed as granted and fg is flex 221 222 askState.isShown = Utils.supportsOneTimeGrant(permGroupName) && 223 !(group.foreground.isGranted && group.isOneTime) 224 deniedState.isShown = true 225 226 if (group.hasPermWithBackgroundMode) { 227 // Background / Foreground / Deny case 228 allowedForegroundState.isShown = true 229 if (group.hasBackgroundGroup) { 230 allowedAlwaysState.isShown = true 231 } 232 233 allowedAlwaysState.isChecked = group.background.isGranted && 234 group.foreground.isGranted 235 allowedForegroundState.isChecked = group.foreground.isGranted && 236 !group.background.isGranted && !group.isOneTime 237 askState.isChecked = !group.foreground.isGranted && group.isOneTime 238 askOneTimeState.isChecked = group.foreground.isGranted && group.isOneTime 239 askOneTimeState.isShown = askOneTimeState.isChecked 240 deniedState.isChecked = !group.foreground.isGranted && !group.isOneTime 241 var detailId = 0 242 if (applyFixToForegroundBackground(group, group.foreground.isSystemFixed, 243 group.background.isSystemFixed, allowedAlwaysState, 244 allowedForegroundState, askState, deniedState, 245 deniedForegroundState) || 246 applyFixToForegroundBackground(group, group.foreground.isPolicyFixed, 247 group.background.isPolicyFixed, allowedAlwaysState, 248 allowedForegroundState, askState, deniedState, 249 deniedForegroundState)) { 250 showAdminSupportLiveData.value = admin 251 detailId = getDetailResIdForFixedByPolicyPermissionGroup(group, 252 admin != null) 253 if (detailId != 0) { 254 detailResIdLiveData.value = detailId to null 255 } 256 } else if (Utils.areGroupPermissionsIndividuallyControlled(app, permGroupName)) { 257 val detailPair = getIndividualPermissionDetailResId(group) 258 detailId = detailPair.first 259 detailResIdLiveData.value = detailId to detailPair.second 260 } 261 if (couldPackageHaveFgCapabilities) { 262 // Correct the UI in case the app can access bg location with only fg perm 263 allowedAlwaysState.isShown = true 264 allowedAlwaysState.isChecked = 265 allowedAlwaysState.isChecked || allowedForegroundState.isChecked 266 // Should be enabled && is denied enabled for the user to be able to switch to. 267 allowedAlwaysState.isEnabled = 268 ((allowedAlwaysState.isEnabled && allowedAlwaysState.isShown) || 269 allowedForegroundState.isEnabled) && 270 ((deniedState.isEnabled && deniedState.isShown) || 271 (deniedForegroundState.isEnabled && 272 deniedForegroundState.isShown)) 273 allowedForegroundState.isChecked = false 274 allowedForegroundState.isEnabled = false 275 deniedState.isChecked = deniedState.isChecked || askState.isChecked 276 deniedForegroundState.isChecked = deniedState.isChecked 277 askState.isEnabled = false 278 279 if (detailId == 0) { 280 detailId = getForegroundCapableDetailResId(foregroundCapableType) 281 if (detailId != 0) { 282 detailResIdLiveData.value = detailId to null 283 } 284 } 285 } 286 } else { 287 // Allow / Deny case 288 allowedState.isShown = true 289 290 allowedState.isChecked = group.foreground.isGranted 291 askState.isChecked = !group.foreground.isGranted && group.isOneTime 292 askOneTimeState.isChecked = group.foreground.isGranted && group.isOneTime 293 askOneTimeState.isShown = askOneTimeState.isChecked 294 deniedState.isChecked = !group.foreground.isGranted && !group.isOneTime 295 296 var detailId = 0 297 if (group.foreground.isPolicyFixed || group.foreground.isSystemFixed) { 298 allowedState.isEnabled = false 299 askState.isEnabled = false 300 deniedState.isEnabled = false 301 showAdminSupportLiveData.value = admin 302 val detailId = getDetailResIdForFixedByPolicyPermissionGroup(group, 303 admin != null) 304 if (detailId != 0) { 305 detailResIdLiveData.value = detailId to null 306 } 307 } 308 if (isForegroundGroupSpecialCase(permGroupName)) { 309 allowedForegroundState.isShown = true 310 allowedState.isShown = false 311 allowedForegroundState.isChecked = allowedState.isChecked 312 allowedForegroundState.isEnabled = allowedState.isEnabled 313 if (couldPackageHaveFgCapabilities || (Utils.isEmergencyApp(app, packageName) && 314 isMicrophone(permGroupName))) { 315 allowedAlwaysState.isShown = true 316 allowedAlwaysState.isChecked = allowedForegroundState.isChecked 317 allowedAlwaysState.isEnabled = allowedForegroundState.isEnabled 318 allowedForegroundState.isChecked = false 319 allowedForegroundState.isEnabled = false 320 deniedState.isChecked = deniedState.isChecked || askState.isChecked 321 askState.isEnabled = false 322 323 if (detailId == 0) { 324 detailId = getForegroundCapableDetailResId(foregroundCapableType) 325 if (detailId != 0) { 326 detailResIdLiveData.value = detailId to null 327 } 328 } 329 } 330 } 331 } 332 if (group.packageInfo.targetSdkVersion < Build.VERSION_CODES.M) { 333 // Pre-M app's can't ask for runtime permissions 334 askState.isShown = false 335 deniedState.isChecked = askState.isChecked || deniedState.isChecked 336 deniedForegroundState.isChecked = askState.isChecked || 337 deniedForegroundState.isChecked 338 } 339 340 val storageState = fullStorageStateLiveData.value 341 if (isStorage && storageState?.isLegacy != true) { 342 val allowedAllFilesState = allowedAlwaysState 343 val allowedMediaOnlyState = allowedForegroundState 344 if (storageState != null) { 345 // Set up the tri state permission for storage 346 allowedAllFilesState.isEnabled = allowedState.isEnabled 347 allowedAllFilesState.isShown = true 348 if (storageState.isGranted) { 349 allowedAllFilesState.isChecked = true 350 deniedState.isChecked = false 351 } 352 } else { 353 allowedAllFilesState.isEnabled = false 354 allowedAllFilesState.isShown = false 355 } 356 allowedMediaOnlyState.isShown = true 357 allowedMediaOnlyState.isEnabled = allowedState.isEnabled 358 allowedMediaOnlyState.isChecked = allowedState.isChecked && 359 storageState?.isGranted != true 360 allowedState.isChecked = false 361 allowedState.isShown = false 362 } 363 364 value = mapOf(ALLOW to allowedState, ALLOW_ALWAYS to allowedAlwaysState, 365 ALLOW_FOREGROUND to allowedForegroundState, ASK_ONCE to askOneTimeState, 366 ASK to askState, DENY to deniedState, DENY_FOREGROUND to deniedForegroundState) 367 } 368 } 369 370 // TODO evanseverson: Actually change mic/camera to be a foreground only permission 371 private fun isForegroundGroupSpecialCase(permissionGroupName: String): Boolean { 372 return permissionGroupName.equals(Manifest.permission_group.CAMERA) || 373 permissionGroupName.equals(Manifest.permission_group.MICROPHONE) 374 } 375 private fun isMicrophone(permissionGroupName: String): Boolean { 376 return permissionGroupName.equals(Manifest.permission_group.MICROPHONE) 377 } 378 379 /** 380 * Modifies the radio buttons to reflect the current policy fixing state 381 * 382 * @return if anything was changed 383 */ 384 private fun applyFixToForegroundBackground( 385 group: LightAppPermGroup, 386 isForegroundFixed: Boolean, 387 isBackgroundFixed: Boolean, 388 allowedAlwaysState: ButtonState, 389 allowedForegroundState: ButtonState, 390 askState: ButtonState, 391 deniedState: ButtonState, 392 deniedForegroundState: ButtonState 393 ): Boolean { 394 if (isBackgroundFixed && isForegroundFixed) { 395 // Background and foreground are both policy fixed. Disable everything 396 allowedAlwaysState.isEnabled = false 397 allowedForegroundState.isEnabled = false 398 askState.isEnabled = false 399 deniedState.isEnabled = false 400 401 if (askState.isChecked) { 402 askState.isChecked = false 403 deniedState.isChecked = true 404 } 405 } else if (isBackgroundFixed && !isForegroundFixed) { 406 if (group.background.isGranted) { 407 // Background policy fixed as granted, foreground flexible. Granting 408 // foreground implies background comes with it in this case. 409 // Only allow user to grant background or deny (which only toggles fg) 410 allowedForegroundState.isEnabled = false 411 askState.isEnabled = false 412 deniedState.isShown = false 413 deniedForegroundState.isShown = true 414 deniedForegroundState.isChecked = deniedState.isChecked 415 416 if (askState.isChecked) { 417 askState.isChecked = false 418 deniedState.isChecked = true 419 } 420 } else { 421 // Background policy fixed as not granted, foreground flexible 422 allowedAlwaysState.isEnabled = false 423 } 424 } else if (!isBackgroundFixed && isForegroundFixed) { 425 if (group.foreground.isGranted) { 426 // Foreground is fixed as granted, background flexible. 427 // Allow switching between foreground and background. No denying 428 allowedForegroundState.isEnabled = allowedAlwaysState.isShown 429 askState.isEnabled = false 430 deniedState.isEnabled = false 431 } else { 432 // Foreground is fixed denied. Background irrelevant 433 allowedAlwaysState.isEnabled = false 434 allowedForegroundState.isEnabled = false 435 askState.isEnabled = false 436 deniedState.isEnabled = false 437 438 if (askState.isChecked) { 439 askState.isChecked = false 440 deniedState.isChecked = true 441 } 442 } 443 } else { 444 return false 445 } 446 return true 447 } 448 449 /** 450 * Navigate to either the App Permission Groups screen, or the Permission Apps Screen. 451 * @param fragment The current fragment 452 * @param action The action to be taken 453 * @param args The arguments to pass to the fragment 454 */ 455 fun showBottomLinkPage(fragment: Fragment, action: String, args: Bundle) { 456 var actionId = R.id.app_to_perm_groups 457 if (action == Intent.ACTION_MANAGE_PERMISSION_APPS) { 458 actionId = R.id.app_to_perm_apps 459 } 460 461 fragment.findNavController().navigateSafe(actionId, args) 462 } 463 464 /** 465 * Request to grant/revoke permissions group. 466 * 467 * Does <u>not</u> handle: 468 * 469 * * Individually granted permissions 470 * * Permission groups with background permissions 471 * 472 * <u>Does</u> handle: 473 * 474 * * Default grant permissions 475 * 476 * @param setOneTime Whether or not to set this permission as one time 477 * @param fragment The fragment calling this method 478 * @param defaultDeny The system which will show the default deny dialog. Usually the same as 479 * the fragment. 480 * @param changeRequest Which permission group (foreground/background/both) should be changed 481 * @param buttonClicked button which was pressed to initiate the change, one of 482 * AppPermissionFragmentActionReported.button_pressed constants 483 * 484 * @return The dialogue to show, if applicable, or if the request was processed. 485 */ 486 fun requestChange( 487 setOneTime: Boolean, 488 fragment: Fragment, 489 defaultDeny: ConfirmDialogShowingFragment, 490 changeRequest: ChangeRequest, 491 buttonClicked: Int 492 ) { 493 val context = fragment.context ?: return 494 val group = lightAppPermGroup ?: return 495 val wasForegroundGranted = group.foreground.isGranted 496 val wasBackgroundGranted = group.background.isGranted 497 498 if (LocationUtils.isLocationGroupAndProvider(context, permGroupName, packageName)) { 499 val packageLabel = KotlinUtils.getPackageLabel(app, packageName, user) 500 LocationUtils.showLocationDialog(context, packageLabel) 501 } 502 503 val shouldGrantForeground = !group.isForegroundFixed && 504 changeRequest andValue ChangeRequest.GRANT_FOREGROUND != 0 505 val shouldGrantBackground = !group.isBackgroundFixed && 506 changeRequest andValue ChangeRequest.GRANT_BACKGROUND != 0 507 val shouldRevokeForeground = !group.isForegroundFixed && 508 changeRequest andValue ChangeRequest.REVOKE_FOREGROUND != 0 509 val shouldRevokeBackground = !group.isBackgroundFixed && 510 changeRequest andValue ChangeRequest.REVOKE_BACKGROUND != 0 511 var showDefaultDenyDialog = false 512 var showGrantedByDefaultWarning = false 513 514 if (shouldRevokeForeground && wasForegroundGranted) { 515 showDefaultDenyDialog = (group.foreground.isGrantedByDefault || 516 !group.supportsRuntimePerms || 517 group.hasInstallToRuntimeSplit) 518 showGrantedByDefaultWarning = showGrantedByDefaultWarning || 519 group.foreground.isGrantedByDefault 520 } 521 522 if (shouldRevokeBackground && wasBackgroundGranted) { 523 showDefaultDenyDialog = showDefaultDenyDialog || 524 group.background.isGrantedByDefault || 525 !group.supportsRuntimePerms || 526 group.hasInstallToRuntimeSplit 527 showGrantedByDefaultWarning = showGrantedByDefaultWarning || 528 group.background.isGrantedByDefault 529 } 530 531 if (showDefaultDenyDialog && !hasConfirmedRevoke && showGrantedByDefaultWarning) { 532 defaultDeny.showConfirmDialog(changeRequest, R.string.system_warning, buttonClicked, 533 setOneTime) 534 return 535 } 536 537 if (showDefaultDenyDialog && !hasConfirmedRevoke) { 538 defaultDeny.showConfirmDialog(changeRequest, R.string.old_sdk_deny_warning, 539 buttonClicked, setOneTime) 540 return 541 } 542 543 var newGroup = group 544 val oldGroup = group 545 546 if (shouldRevokeBackground && group.hasBackgroundGroup && 547 (wasBackgroundGranted || group.background.isUserFixed || 548 group.isOneTime != setOneTime)) { 549 newGroup = KotlinUtils 550 .revokeBackgroundRuntimePermissions(app, newGroup) 551 552 // only log if we have actually denied permissions, not if we switch from 553 // "ask every time" to denied 554 if (wasBackgroundGranted) { 555 SafetyNetLogger.logPermissionToggled(newGroup, true) 556 } 557 } 558 559 if (shouldRevokeForeground && (wasForegroundGranted || group.isOneTime != setOneTime)) { 560 newGroup = KotlinUtils 561 .revokeForegroundRuntimePermissions(app, newGroup, false, setOneTime) 562 563 // only log if we have actually denied permissions, not if we switch from 564 // "ask every time" to denied 565 if (wasForegroundGranted) { 566 SafetyNetLogger.logPermissionToggled(newGroup) 567 } 568 } 569 570 if (shouldGrantForeground) { 571 newGroup = KotlinUtils.grantForegroundRuntimePermissions(app, newGroup) 572 573 if (!wasForegroundGranted) { 574 SafetyNetLogger.logPermissionToggled(newGroup) 575 } 576 } 577 578 if (shouldGrantBackground && group.hasBackgroundGroup) { 579 newGroup = KotlinUtils.grantBackgroundRuntimePermissions(app, newGroup) 580 581 if (!wasBackgroundGranted) { 582 SafetyNetLogger.logPermissionToggled(newGroup, true) 583 } 584 } 585 586 logPermissionChanges(oldGroup, newGroup, buttonClicked) 587 588 fullStorageStateLiveData.value?.let { 589 FullStoragePermissionAppsLiveData.recalculate() 590 } 591 } 592 593 /** 594 * Once the user has confirmed that he/she wants to revoke a permission that was granted by 595 * default, actually revoke the permissions. 596 * 597 * @param changeRequest whether to change foreground, background, or both. 598 * @param buttonPressed button pressed to initiate the change, one of 599 * AppPermissionFragmentActionReported.button_pressed constants 600 * @param oneTime whether the change should show that the permission was selected as one-time 601 * 602 */ 603 fun onDenyAnyWay(changeRequest: ChangeRequest, buttonPressed: Int, oneTime: Boolean) { 604 val group = lightAppPermGroup ?: return 605 val wasForegroundGranted = group.foreground.isGranted 606 val wasBackgroundGranted = group.background.isGranted 607 var hasDefaultPermissions = false 608 609 var newGroup = group 610 val oldGroup = group 611 612 if (changeRequest andValue ChangeRequest.REVOKE_BACKGROUND != 0 && 613 group.hasBackgroundGroup) { 614 newGroup = KotlinUtils.revokeBackgroundRuntimePermissions(app, newGroup, false, oneTime) 615 616 if (wasBackgroundGranted) { 617 SafetyNetLogger.logPermissionToggled(newGroup) 618 } 619 hasDefaultPermissions = hasDefaultPermissions || 620 group.background.isGrantedByDefault 621 } 622 623 if (changeRequest andValue ChangeRequest.REVOKE_FOREGROUND != 0) { 624 newGroup = KotlinUtils.revokeForegroundRuntimePermissions(app, newGroup, false, oneTime) 625 if (wasForegroundGranted) { 626 SafetyNetLogger.logPermissionToggled(newGroup) 627 } 628 hasDefaultPermissions = group.foreground.isGrantedByDefault 629 } 630 logPermissionChanges(oldGroup, newGroup, buttonPressed) 631 632 if (hasDefaultPermissions || !group.supportsRuntimePerms) { 633 hasConfirmedRevoke = true 634 } 635 636 fullStorageStateLiveData.value?.let { 637 FullStoragePermissionAppsLiveData.recalculate() 638 } 639 } 640 641 /** 642 * Set the All Files access for this app 643 * 644 * @param granted Whether to grant or revoke access 645 */ 646 fun setAllFilesAccess(granted: Boolean) { 647 val aom = app.getSystemService(AppOpsManager::class.java)!! 648 val uid = lightAppPermGroup?.packageInfo?.uid ?: return 649 val mode = if (granted) { 650 MODE_ALLOWED 651 } else { 652 MODE_ERRORED 653 } 654 val fullStorageGrant = fullStorageStateLiveData.value?.isGranted 655 if (fullStorageGrant != null && fullStorageGrant != granted) { 656 aom.setUidMode(OPSTR_MANAGE_EXTERNAL_STORAGE, uid, mode) 657 FullStoragePermissionAppsLiveData.recalculate() 658 } 659 } 660 661 /** 662 * Show the All App Permissions screen with the proper filter group, package name, and user. 663 * 664 * @param fragment The current fragment we wish to transition from 665 */ 666 fun showAllPermissions(fragment: Fragment, args: Bundle) { 667 fragment.findNavController().navigateSafe(R.id.app_to_all_perms, args) 668 } 669 670 private fun getIndividualPermissionDetailResId(group: LightAppPermGroup): Pair<Int, Int> { 671 return when (val numRevoked = 672 group.permissions.filter { !it.value.isGrantedIncludingAppOp }.size) { 673 0 -> R.string.permission_revoked_none to numRevoked 674 group.permissions.size -> R.string.permission_revoked_all to numRevoked 675 else -> R.string.permission_revoked_count to numRevoked 676 } 677 } 678 679 /** 680 * Get the detail string id of a permission group if it is at least partially fixed by policy. 681 */ 682 private fun getDetailResIdForFixedByPolicyPermissionGroup( 683 group: LightAppPermGroup, 684 hasAdmin: Boolean 685 ): Int { 686 val isForegroundPolicyDenied = group.foreground.isPolicyFixed && !group.foreground.isGranted 687 val isPolicyFullyFixedWithGrantedOrNoBkg = group.isPolicyFullyFixed && 688 (group.background.isGranted || !group.hasBackgroundGroup) 689 if (group.foreground.isSystemFixed || group.background.isSystemFixed) { 690 return R.string.permission_summary_enabled_system_fixed 691 } else if (hasAdmin) { 692 // Permission is fully controlled by policy and cannot be switched 693 if (isForegroundPolicyDenied) { 694 return R.string.disabled_by_admin 695 } else if (isPolicyFullyFixedWithGrantedOrNoBkg) { 696 return R.string.enabled_by_admin 697 } else if (group.isPolicyFullyFixed) { 698 return R.string.permission_summary_enabled_by_admin_foreground_only 699 } 700 701 // Part of the permission group can still be switched 702 if (group.background.isPolicyFixed && group.background.isGranted) { 703 return R.string.permission_summary_enabled_by_admin_background_only 704 } else if (group.background.isPolicyFixed) { 705 return R.string.permission_summary_disabled_by_admin_background_only 706 } else if (group.foreground.isPolicyFixed) { 707 return R.string.permission_summary_enabled_by_admin_foreground_only 708 } 709 } else { 710 // Permission is fully controlled by policy and cannot be switched 711 if ((isForegroundPolicyDenied) || isPolicyFullyFixedWithGrantedOrNoBkg) { 712 // Permission is fully controlled by policy and cannot be switched 713 // State will be displayed by switch, so no need to add text for that 714 return R.string.permission_summary_enforced_by_policy 715 } else if (group.isPolicyFullyFixed) { 716 return R.string.permission_summary_enabled_by_policy_foreground_only 717 } 718 719 // Part of the permission group can still be switched 720 if (group.background.isPolicyFixed && group.background.isGranted) { 721 return R.string.permission_summary_enabled_by_policy_background_only 722 } else if (group.background.isPolicyFixed) { 723 return R.string.permission_summary_disabled_by_policy_background_only 724 } else if (group.foreground.isPolicyFixed) { 725 return R.string.permission_summary_enabled_by_policy_foreground_only 726 } 727 } 728 return 0 729 } 730 731 private fun getForegroundCapableDetailResId(type: Utils.ForegroundCapableType): Int { 732 when (type) { 733 SOUND_TRIGGER -> return R.string.fg_capabilities_sound_trigger 734 ASSISTANT -> return R.string.fg_capabilities_assistant 735 VOICE_INTERACTION -> return R.string.fg_capabilities_voice_interaction 736 CARRIER_SERVICE -> return R.string.fg_capabilities_carrier 737 } 738 return 0 739 } 740 741 private fun logPermissionChanges( 742 oldGroup: LightAppPermGroup, 743 newGroup: LightAppPermGroup, 744 buttonPressed: Int 745 ) { 746 val changeId = Random().nextLong() 747 748 for ((permName, permission) in oldGroup.permissions) { 749 val newPermission = newGroup.permissions[permName] ?: continue 750 751 if (permission.isGrantedIncludingAppOp != newPermission.isGrantedIncludingAppOp || 752 permission.flags != newPermission.flags) { 753 logAppPermissionFragmentActionReported(changeId, newPermission, buttonPressed) 754 } 755 } 756 } 757 758 private fun logAppPermissionFragmentActionReported( 759 changeId: Long, 760 permission: LightPermission, 761 buttonPressed: Int 762 ) { 763 val uid = KotlinUtils.getPackageUid(app, packageName, user) ?: return 764 PermissionControllerStatsLog.write(APP_PERMISSION_FRAGMENT_ACTION_REPORTED, sessionId, 765 changeId, uid, packageName, permission.permInfo.name, 766 permission.isGrantedIncludingAppOp, permission.flags, buttonPressed) 767 Log.v(LOG_TAG, "Permission changed via UI with sessionId=$sessionId changeId=" + 768 "$changeId uid=$uid packageName=$packageName permission=" + permission.permInfo.name + 769 " isGranted=" + permission.isGrantedIncludingAppOp + " permissionFlags=" + 770 permission.flags + " buttonPressed=$buttonPressed") 771 } 772 773 /** 774 * Logs information about this AppPermissionGroup and view session 775 */ 776 fun logAppPermissionFragmentViewed() { 777 val uid = KotlinUtils.getPackageUid(app, packageName, user) ?: return 778 PermissionControllerStatsLog.write(APP_PERMISSION_FRAGMENT_VIEWED, sessionId, 779 uid, packageName, permGroupName) 780 Log.v(LOG_TAG, "AppPermission fragment viewed with sessionId=$sessionId uid=" + 781 "$uid packageName=$packageName" + 782 "permGroupName=$permGroupName") 783 } 784 } 785 786 /** 787 * Factory for an AppPermissionViewModel 788 * 789 * @param app The current application 790 * @param packageName The name of the package this ViewModel represents 791 * @param permGroupName The name of the permission group this ViewModel represents 792 * @param user The user of the package 793 * @param sessionId A session ID used in logs to identify this particular session 794 */ 795 class AppPermissionViewModelFactory( 796 private val app: Application, 797 private val packageName: String, 798 private val permGroupName: String, 799 private val user: UserHandle, 800 private val sessionId: Long, 801 private val foregroundCapableType: Utils.ForegroundCapableType 802 ) : ViewModelProvider.Factory { createnull803 override fun <T : ViewModel> create(modelClass: Class<T>): T { 804 @Suppress("UNCHECKED_CAST") 805 return AppPermissionViewModel(app, packageName, permGroupName, user, sessionId, 806 foregroundCapableType) as T 807 } 808 }