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_IGNORED 23 import android.app.AppOpsManager.OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED 24 import android.apphibernation.AppHibernationManager 25 import android.content.Context 26 import android.os.Bundle 27 import android.os.UserHandle 28 import android.util.Log 29 import androidx.fragment.app.Fragment 30 import androidx.lifecycle.ViewModel 31 import androidx.lifecycle.ViewModelProvider 32 import androidx.navigation.fragment.findNavController 33 import com.android.modules.utils.build.SdkLevel 34 import com.android.permissioncontroller.PermissionControllerApplication 35 import com.android.permissioncontroller.PermissionControllerStatsLog 36 import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_GROUPS_FRAGMENT_AUTO_REVOKE_ACTION 37 import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_GROUPS_FRAGMENT_AUTO_REVOKE_ACTION__ACTION__SWITCH_DISABLED 38 import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_GROUPS_FRAGMENT_AUTO_REVOKE_ACTION__ACTION__SWITCH_ENABLED 39 import com.android.permissioncontroller.R 40 import com.android.permissioncontroller.hibernation.isHibernationEnabled 41 import com.android.permissioncontroller.permission.data.AppPermGroupUiInfoLiveData 42 import com.android.permissioncontroller.permission.data.FullStoragePermissionAppsLiveData 43 import com.android.permissioncontroller.permission.data.HibernationSettingStateLiveData 44 import com.android.permissioncontroller.permission.data.LightPackageInfoLiveData 45 import com.android.permissioncontroller.permission.data.PackagePermissionsLiveData 46 import com.android.permissioncontroller.permission.data.PackagePermissionsLiveData.Companion.NON_RUNTIME_NORMAL_PERMS 47 import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData 48 import com.android.permissioncontroller.permission.data.get 49 import com.android.permissioncontroller.permission.data.v35.PackagePermissionsExternalDeviceLiveData 50 import com.android.permissioncontroller.permission.model.livedatatypes.AppPermGroupUiInfo.PermGrantState 51 import com.android.permissioncontroller.permission.model.v31.AppPermissionUsage 52 import com.android.permissioncontroller.permission.ui.Category 53 import com.android.permissioncontroller.permission.utils.IPC 54 import com.android.permissioncontroller.permission.utils.KotlinUtils 55 import com.android.permissioncontroller.permission.utils.PermissionMapping 56 import com.android.permissioncontroller.permission.utils.Utils 57 import com.android.permissioncontroller.permission.utils.Utils.AppPermsLastAccessType 58 import com.android.permissioncontroller.permission.utils.navigateSafe 59 import com.android.permissioncontroller.permission.utils.v35.MultiDeviceUtils 60 import java.time.Instant 61 import java.util.concurrent.TimeUnit 62 import kotlin.math.max 63 import kotlinx.coroutines.GlobalScope 64 import kotlinx.coroutines.launch 65 66 /** 67 * ViewModel for the AppPermissionGroupsFragment. Has a liveData with the UI information for all 68 * permission groups that this package requests runtime permissions from 69 * 70 * @param packageName The name of the package this viewModel is representing 71 * @param user The user of the package this viewModel is representing 72 */ 73 class AppPermissionGroupsViewModel( 74 private val packageName: String, 75 private val user: UserHandle, 76 private val sessionId: Long 77 ) : ViewModel() { 78 79 companion object { 80 const val AGGREGATE_DATA_FILTER_BEGIN_DAYS_1 = 1 81 const val AGGREGATE_DATA_FILTER_BEGIN_DAYS_7 = 7 82 val LOG_TAG: String = AppPermissionGroupsViewModel::class.java.simpleName 83 } 84 85 val app = PermissionControllerApplication.get()!! 86 87 enum class PermSubtitle(val value: Int) { 88 NONE(0), 89 MEDIA_ONLY(1), 90 ALL_FILES(2), 91 FOREGROUND_ONLY(3), 92 BACKGROUND(4), 93 } 94 95 data class GroupUiInfo( 96 val groupName: String, 97 val isSystem: Boolean = false, 98 val subtitle: PermSubtitle, 99 val persistentDeviceId: String, 100 ) { 101 constructor( 102 groupName: String, 103 isSystem: Boolean 104 ) : this( 105 groupName, 106 isSystem, 107 PermSubtitle.NONE, 108 MultiDeviceUtils.getDefaultDevicePersistentDeviceId() 109 ) 110 111 constructor( 112 groupName: String, 113 isSystem: Boolean, 114 subtitle: PermSubtitle, 115 ) : this( 116 groupName, 117 isSystem, 118 subtitle, 119 MultiDeviceUtils.getDefaultDevicePersistentDeviceId() 120 ) 121 } 122 123 // Auto-revoke and hibernation share the same settings 124 val autoRevokeLiveData = HibernationSettingStateLiveData[packageName, user] 125 126 private val packagePermsLiveData = PackagePermissionsLiveData[packageName, user] 127 private val appPermGroupUiInfoLiveDatas = mutableMapOf<String, AppPermGroupUiInfoLiveData>() 128 private val fullStoragePermsLiveData = FullStoragePermissionAppsLiveData 129 private val packagePermsExternalDeviceLiveData = 130 PackagePermissionsExternalDeviceLiveData[packageName, user] 131 132 /** 133 * LiveData whose data is a map of grant category (either allowed or denied) to a list of 134 * permission group names that match the key, and two booleans representing if this is a system 135 * group, and a subtitle resource ID, if applicable. 136 */ 137 val packagePermGroupsLiveData = 138 object : 139 SmartUpdateMediatorLiveData<@JvmSuppressWildcards Map<Category, List<GroupUiInfo>>>() { 140 141 init { 142 addSource(packagePermsLiveData) { update() } 143 addSource(fullStoragePermsLiveData) { update() } 144 addSource(autoRevokeLiveData) { 145 removeSource(autoRevokeLiveData) 146 update() 147 } 148 addSource(packagePermsExternalDeviceLiveData) { update() } 149 update() 150 } 151 152 override fun onUpdate() { 153 val groups = 154 packagePermsLiveData.value?.keys?.filter { it != NON_RUNTIME_NORMAL_PERMS } 155 if (groups == null && packagePermsLiveData.isInitialized) { 156 value = null 157 return 158 } else if ( 159 groups == null || 160 (Manifest.permission_group.STORAGE in groups && 161 !fullStoragePermsLiveData.isInitialized) || 162 !autoRevokeLiveData.isInitialized 163 ) { 164 return 165 } 166 167 val getLiveData = { groupName: String -> 168 AppPermGroupUiInfoLiveData[packageName, groupName, user] 169 } 170 setSourcesToDifference(groups, appPermGroupUiInfoLiveDatas, getLiveData) 171 172 if (!appPermGroupUiInfoLiveDatas.all { it.value.isInitialized }) { 173 return 174 } 175 176 val groupGrantStates = mutableMapOf<Category, MutableList<GroupUiInfo>>() 177 groupGrantStates[Category.ALLOWED] = mutableListOf() 178 groupGrantStates[Category.ASK] = mutableListOf() 179 groupGrantStates[Category.DENIED] = mutableListOf() 180 181 val fullStorageState = 182 fullStoragePermsLiveData.value?.find { pkg -> 183 pkg.packageName == packageName && pkg.user == user 184 } 185 186 for (groupName in groups) { 187 val isSystem = 188 PermissionMapping.getPlatformPermissionGroups().contains(groupName) 189 appPermGroupUiInfoLiveDatas[groupName]?.value?.let { uiInfo -> 190 if (SdkLevel.isAtLeastT() && !uiInfo.shouldShow) { 191 return@let 192 } 193 if ( 194 groupName == Manifest.permission_group.STORAGE && 195 (fullStorageState?.isGranted == true && !fullStorageState.isLegacy) 196 ) { 197 groupGrantStates[Category.ALLOWED]!!.add( 198 GroupUiInfo(groupName, isSystem, PermSubtitle.ALL_FILES) 199 ) 200 return@let 201 } 202 when (uiInfo.permGrantState) { 203 PermGrantState.PERMS_ALLOWED -> { 204 val subtitle = 205 if (groupName == Manifest.permission_group.STORAGE) { 206 if (SdkLevel.isAtLeastT()) { 207 PermSubtitle.NONE 208 } else { 209 if (fullStorageState?.isLegacy == true) { 210 PermSubtitle.ALL_FILES 211 } else { 212 PermSubtitle.MEDIA_ONLY 213 } 214 } 215 } else { 216 PermSubtitle.NONE 217 } 218 groupGrantStates[Category.ALLOWED]!!.add( 219 GroupUiInfo(groupName, isSystem, subtitle) 220 ) 221 } 222 PermGrantState.PERMS_ALLOWED_ALWAYS -> 223 groupGrantStates[Category.ALLOWED]!!.add( 224 GroupUiInfo(groupName, isSystem, PermSubtitle.BACKGROUND) 225 ) 226 PermGrantState.PERMS_ALLOWED_FOREGROUND_ONLY -> 227 groupGrantStates[Category.ALLOWED]!!.add( 228 GroupUiInfo(groupName, isSystem, PermSubtitle.FOREGROUND_ONLY) 229 ) 230 PermGrantState.PERMS_DENIED -> 231 groupGrantStates[Category.DENIED]!!.add( 232 GroupUiInfo(groupName, isSystem) 233 ) 234 PermGrantState.PERMS_ASK -> 235 groupGrantStates[Category.ASK]!!.add( 236 GroupUiInfo(groupName, isSystem) 237 ) 238 } 239 } 240 } 241 242 packagePermsExternalDeviceLiveData.value?.forEach { externalDeviceGrantInfo -> 243 val groupName = externalDeviceGrantInfo.groupName 244 val isSystem = 245 PermissionMapping.getPlatformPermissionGroups().contains(groupName) 246 val persistentDeviceId = externalDeviceGrantInfo.persistentDeviceId 247 when (externalDeviceGrantInfo.permGrantState) { 248 PermGrantState.PERMS_ALLOWED -> { 249 groupGrantStates[Category.ALLOWED]!!.add( 250 GroupUiInfo( 251 groupName, 252 isSystem, 253 PermSubtitle.NONE, 254 persistentDeviceId 255 ) 256 ) 257 } 258 PermGrantState.PERMS_ALLOWED_ALWAYS -> 259 groupGrantStates[Category.ALLOWED]!!.add( 260 GroupUiInfo( 261 groupName, 262 isSystem, 263 PermSubtitle.BACKGROUND, 264 persistentDeviceId 265 ) 266 ) 267 PermGrantState.PERMS_ALLOWED_FOREGROUND_ONLY -> 268 groupGrantStates[Category.ALLOWED]!!.add( 269 GroupUiInfo( 270 groupName, 271 isSystem, 272 PermSubtitle.FOREGROUND_ONLY, 273 persistentDeviceId 274 ) 275 ) 276 PermGrantState.PERMS_DENIED -> 277 groupGrantStates[Category.DENIED]!!.add( 278 GroupUiInfo( 279 groupName, 280 isSystem, 281 PermSubtitle.NONE, 282 persistentDeviceId 283 ) 284 ) 285 PermGrantState.PERMS_ASK -> 286 groupGrantStates[Category.ASK]!!.add( 287 GroupUiInfo( 288 groupName, 289 isSystem, 290 PermSubtitle.NONE, 291 persistentDeviceId 292 ) 293 ) 294 } 295 } 296 297 value = groupGrantStates 298 } 299 } 300 301 // TODO 206455664: remove once issue is identified 302 fun logLiveDataState() { 303 Log.i( 304 LOG_TAG, 305 "Overall liveData isStale: ${packagePermGroupsLiveData.isStale}, " + 306 "isInitialized: ${packagePermGroupsLiveData.isInitialized}, " + 307 "value: ${packagePermGroupsLiveData.value}" 308 ) 309 Log.i( 310 LOG_TAG, 311 "AutoRevoke liveData isStale: ${autoRevokeLiveData.isStale}, " + 312 "isInitialized: ${autoRevokeLiveData.isInitialized}, " + 313 "value: ${autoRevokeLiveData.value}" 314 ) 315 Log.i( 316 LOG_TAG, 317 "PackagePerms liveData isStale: ${packagePermsLiveData.isStale}, " + 318 "isInitialized: ${packagePermsLiveData.isInitialized}, " + 319 "value: ${packagePermsLiveData.value}" 320 ) 321 Log.i( 322 LOG_TAG, 323 "FullStorage liveData isStale: ${fullStoragePermsLiveData.isStale}, " + 324 "isInitialized: ${fullStoragePermsLiveData.isInitialized}, " + 325 "value size: ${fullStoragePermsLiveData.value?.size}" 326 ) 327 for ((group, liveData) in appPermGroupUiInfoLiveDatas) { 328 Log.i( 329 LOG_TAG, 330 "$group ui liveData isStale: ${liveData.isStale}, " + 331 "isInitialized: ${liveData.isInitialized}, " + 332 "value size: ${liveData.value}" 333 ) 334 } 335 } 336 337 fun setAutoRevoke(enabled: Boolean) { 338 GlobalScope.launch(IPC) { 339 val aom = app.getSystemService(AppOpsManager::class.java)!! 340 val lightPackageInfo = LightPackageInfoLiveData[packageName, user].getInitializedValue() 341 342 if (lightPackageInfo != null) { 343 Log.i( 344 LOG_TAG, 345 "sessionId $sessionId setting auto revoke enabled to $enabled for" + 346 "$packageName $user" 347 ) 348 val tag = 349 if (enabled) { 350 APP_PERMISSION_GROUPS_FRAGMENT_AUTO_REVOKE_ACTION__ACTION__SWITCH_ENABLED 351 } else { 352 APP_PERMISSION_GROUPS_FRAGMENT_AUTO_REVOKE_ACTION__ACTION__SWITCH_DISABLED 353 } 354 PermissionControllerStatsLog.write( 355 APP_PERMISSION_GROUPS_FRAGMENT_AUTO_REVOKE_ACTION, 356 sessionId, 357 lightPackageInfo.uid, 358 packageName, 359 tag 360 ) 361 362 val mode = 363 if (enabled) { 364 MODE_ALLOWED 365 } else { 366 MODE_IGNORED 367 } 368 aom.setUidMode(OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, lightPackageInfo.uid, mode) 369 if (isHibernationEnabled() && SdkLevel.isAtLeastSv2() && !enabled) { 370 // Only unhibernate on S_V2+ to have consistent toggle behavior w/ Settings 371 val ahm = app.getSystemService(AppHibernationManager::class.java)!! 372 ahm.setHibernatingForUser(packageName, false) 373 ahm.setHibernatingGlobally(packageName, false) 374 } 375 } 376 } 377 } 378 379 fun showExtraPerms(fragment: Fragment, args: Bundle) { 380 fragment.findNavController().navigateSafe(R.id.perm_groups_to_custom, args) 381 } 382 383 fun showAllPermissions(fragment: Fragment, args: Bundle) { 384 fragment.findNavController().navigateSafe(R.id.perm_groups_to_all_perms, args) 385 } 386 387 // This method should be consolidated with 388 // PermissionAppsViewModel#extractGroupUsageLastAccessTime 389 fun extractGroupUsageLastAccessTime( 390 accessTime: MutableMap<String, Long>, 391 appPermissionUsages: List<AppPermissionUsage>, 392 packageName: String 393 ) { 394 if (!SdkLevel.isAtLeastS()) { 395 return 396 } 397 398 val aggregateDataFilterBeginDays = 399 if (KotlinUtils.is7DayToggleEnabled()) AGGREGATE_DATA_FILTER_BEGIN_DAYS_7 400 else AGGREGATE_DATA_FILTER_BEGIN_DAYS_1 401 402 accessTime.clear() 403 val filterTimeBeginMillis = 404 max( 405 System.currentTimeMillis() - 406 TimeUnit.DAYS.toMillis(aggregateDataFilterBeginDays.toLong()), 407 Instant.EPOCH.toEpochMilli() 408 ) 409 val numApps: Int = appPermissionUsages.size 410 for (appIndex in 0 until numApps) { 411 val appUsage: AppPermissionUsage = appPermissionUsages[appIndex] 412 if (appUsage.packageName != packageName) { 413 continue 414 } 415 val appGroups = appUsage.groupUsages 416 val numGroups = appGroups.size 417 for (groupIndex in 0 until numGroups) { 418 val groupUsage = appGroups[groupIndex] 419 var lastAccessTime = groupUsage.lastAccessTime 420 val groupName = groupUsage.group.name 421 if (lastAccessTime == 0L || lastAccessTime < filterTimeBeginMillis) { 422 continue 423 } 424 425 // We might have another AppPermissionUsage entry that's of the same packageName 426 // but with a different uid. In that case, we want to grab the max lastAccessTime 427 // as the last usage to show. 428 lastAccessTime = 429 Math.max( 430 accessTime.getOrDefault(groupName, Instant.EPOCH.toEpochMilli()), 431 lastAccessTime 432 ) 433 accessTime[groupName] = lastAccessTime 434 } 435 } 436 } 437 438 fun getPreferenceSummary( 439 groupInfo: GroupUiInfo, 440 context: Context, 441 lastAccessTime: Long? 442 ): String { 443 val summaryTimestamp = 444 Utils.getPermissionLastAccessSummaryTimestamp( 445 lastAccessTime, 446 context, 447 groupInfo.groupName 448 ) 449 @AppPermsLastAccessType val lastAccessType: Int = summaryTimestamp.second 450 451 return when (groupInfo.subtitle) { 452 PermSubtitle.BACKGROUND -> 453 when (lastAccessType) { 454 Utils.LAST_24H_CONTENT_PROVIDER -> 455 context.getString(R.string.app_perms_content_provider_24h_background) 456 Utils.LAST_7D_CONTENT_PROVIDER -> 457 context.getString(R.string.app_perms_content_provider_7d_background) 458 Utils.LAST_24H_SENSOR_TODAY -> 459 context.getString( 460 R.string.app_perms_24h_access_background, 461 summaryTimestamp.first 462 ) 463 Utils.LAST_24H_SENSOR_YESTERDAY -> 464 context.getString( 465 R.string.app_perms_24h_access_yest_background, 466 summaryTimestamp.first 467 ) 468 Utils.LAST_7D_SENSOR -> 469 context.getString( 470 R.string.app_perms_7d_access_background, 471 summaryTimestamp.third, 472 summaryTimestamp.first 473 ) 474 Utils.NOT_IN_LAST_7D -> 475 context.getString(R.string.permission_subtitle_background) 476 else -> context.getString(R.string.permission_subtitle_background) 477 } 478 PermSubtitle.MEDIA_ONLY -> 479 when (lastAccessType) { 480 Utils.LAST_24H_CONTENT_PROVIDER -> 481 context.getString(R.string.app_perms_content_provider_24h_media_only) 482 Utils.LAST_7D_CONTENT_PROVIDER -> 483 context.getString(R.string.app_perms_content_provider_7d_media_only) 484 Utils.LAST_24H_SENSOR_TODAY -> 485 context.getString( 486 R.string.app_perms_24h_access_media_only, 487 summaryTimestamp.first 488 ) 489 Utils.LAST_24H_SENSOR_YESTERDAY -> 490 context.getString( 491 R.string.app_perms_24h_access_yest_media_only, 492 summaryTimestamp.first 493 ) 494 Utils.LAST_7D_SENSOR -> 495 context.getString( 496 R.string.app_perms_7d_access_media_only, 497 summaryTimestamp.third, 498 summaryTimestamp.first 499 ) 500 Utils.NOT_IN_LAST_7D -> 501 context.getString(R.string.permission_subtitle_media_only) 502 else -> context.getString(R.string.permission_subtitle_media_only) 503 } 504 PermSubtitle.ALL_FILES -> 505 when (lastAccessType) { 506 Utils.LAST_24H_CONTENT_PROVIDER -> 507 context.getString(R.string.app_perms_content_provider_24h_all_files) 508 Utils.LAST_7D_CONTENT_PROVIDER -> 509 context.getString(R.string.app_perms_content_provider_7d_all_files) 510 Utils.LAST_24H_SENSOR_TODAY -> 511 context.getString( 512 R.string.app_perms_24h_access_all_files, 513 summaryTimestamp.first 514 ) 515 Utils.LAST_24H_SENSOR_YESTERDAY -> 516 context.getString( 517 R.string.app_perms_24h_access_yest_all_files, 518 summaryTimestamp.first 519 ) 520 Utils.LAST_7D_SENSOR -> 521 context.getString( 522 R.string.app_perms_7d_access_all_files, 523 summaryTimestamp.third, 524 summaryTimestamp.first 525 ) 526 Utils.NOT_IN_LAST_7D -> 527 context.getString(R.string.permission_subtitle_all_files) 528 else -> context.getString(R.string.permission_subtitle_all_files) 529 } 530 else -> 531 // PermSubtitle.FOREGROUND_ONLY should fall into this as well 532 when (lastAccessType) { 533 Utils.LAST_24H_CONTENT_PROVIDER -> 534 context.getString(R.string.app_perms_content_provider_24h) 535 Utils.LAST_7D_CONTENT_PROVIDER -> 536 context.getString(R.string.app_perms_content_provider_7d) 537 Utils.LAST_24H_SENSOR_TODAY -> 538 context.getString(R.string.app_perms_24h_access, summaryTimestamp.first) 539 Utils.LAST_24H_SENSOR_YESTERDAY -> 540 context.getString( 541 R.string.app_perms_24h_access_yest, 542 summaryTimestamp.first 543 ) 544 Utils.LAST_7D_SENSOR -> 545 context.getString( 546 R.string.app_perms_7d_access, 547 summaryTimestamp.third, 548 summaryTimestamp.first 549 ) 550 Utils.NOT_IN_LAST_7D -> "" 551 else -> "" 552 } 553 } 554 } 555 } 556 557 /** 558 * Factory for an AppPermissionGroupsViewModel 559 * 560 * @param packageName The name of the package this viewModel is representing 561 * @param user The user of the package this viewModel is representing 562 */ 563 class AppPermissionGroupsViewModelFactory( 564 private val packageName: String, 565 private val user: UserHandle, 566 private val sessionId: Long 567 ) : ViewModelProvider.Factory { 568 createnull569 override fun <T : ViewModel> create(modelClass: Class<T>): T { 570 @Suppress("UNCHECKED_CAST") 571 return AppPermissionGroupsViewModel(packageName, user, sessionId) as T 572 } 573 } 574