1 /* 2 * 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.permissioncontroller.PermissionControllerApplication 34 import com.android.permissioncontroller.PermissionControllerStatsLog 35 import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_GROUPS_FRAGMENT_AUTO_REVOKE_ACTION 36 import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_GROUPS_FRAGMENT_AUTO_REVOKE_ACTION__ACTION__SWITCH_DISABLED 37 import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_GROUPS_FRAGMENT_AUTO_REVOKE_ACTION__ACTION__SWITCH_ENABLED 38 import com.android.permissioncontroller.R 39 import com.android.permissioncontroller.hibernation.isHibernationEnabled 40 import com.android.permissioncontroller.permission.data.AppPermGroupUiInfoLiveData 41 import com.android.permissioncontroller.permission.data.FullStoragePermissionAppsLiveData 42 import com.android.permissioncontroller.permission.data.HibernationSettingStateLiveData 43 import com.android.permissioncontroller.permission.data.LightPackageInfoLiveData 44 import com.android.permissioncontroller.permission.data.PackagePermissionsLiveData 45 import com.android.permissioncontroller.permission.data.PackagePermissionsLiveData.Companion.NON_RUNTIME_NORMAL_PERMS 46 import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData 47 import com.android.permissioncontroller.permission.data.get 48 import com.android.permissioncontroller.permission.model.AppPermissionUsage 49 import com.android.permissioncontroller.permission.model.livedatatypes.AppPermGroupUiInfo.PermGrantState 50 import com.android.permissioncontroller.permission.ui.Category 51 import com.android.permissioncontroller.permission.utils.IPC 52 import com.android.permissioncontroller.permission.utils.Utils 53 import com.android.permissioncontroller.permission.utils.Utils.AppPermsLastAccessType 54 import com.android.permissioncontroller.permission.utils.navigateSafe 55 import kotlinx.coroutines.GlobalScope 56 import kotlinx.coroutines.launch 57 import java.time.Instant 58 import java.util.concurrent.TimeUnit 59 import kotlin.math.max 60 61 /** 62 * ViewModel for the AppPermissionGroupsFragment. Has a liveData with the UI information for all 63 * permission groups that this package requests runtime permissions from 64 * 65 * @param packageName The name of the package this viewModel is representing 66 * @param user The user of the package this viewModel is representing 67 */ 68 class AppPermissionGroupsViewModel( 69 private val packageName: String, 70 private val user: UserHandle, 71 private val sessionId: Long 72 ) : ViewModel() { 73 74 companion object { 75 const val AGGREGATE_DATA_FILTER_BEGIN_DAYS = 1 76 val LOG_TAG: String = AppPermissionGroupsViewModel::class.java.simpleName 77 } 78 79 val app = PermissionControllerApplication.get()!! 80 81 enum class PermSubtitle(val value: Int) { 82 NONE(0), 83 MEDIA_ONLY(1), 84 ALL_FILES(2), 85 FOREGROUND_ONLY(3), 86 BACKGROUND(4), 87 } 88 89 data class GroupUiInfo( 90 val groupName: String, 91 val isSystem: Boolean = false, 92 val subtitle: PermSubtitle 93 ) { 94 constructor(groupName: String, isSystem: Boolean) : 95 this(groupName, isSystem, PermSubtitle.NONE) 96 } 97 98 // Auto-revoke and hibernation share the same settings 99 val autoRevokeLiveData = HibernationSettingStateLiveData[packageName, user] 100 101 /** 102 * LiveData whose data is a map of grant category (either allowed or denied) to a list 103 * of permission group names that match the key, and two booleans representing if this is a 104 * system group, and a subtitle resource ID, if applicable. 105 */ 106 val packagePermGroupsLiveData = object : SmartUpdateMediatorLiveData<@JvmSuppressWildcards 107 Map<Category, List<GroupUiInfo>>>() { 108 109 private val packagePermsLiveData = 110 PackagePermissionsLiveData[packageName, user] 111 private val appPermGroupUiInfoLiveDatas = mutableMapOf<String, AppPermGroupUiInfoLiveData>() 112 private val fullStoragePermsLiveData = FullStoragePermissionAppsLiveData 113 114 init { 115 addSource(packagePermsLiveData) { 116 update() 117 } 118 addSource(fullStoragePermsLiveData) { 119 update() 120 } 121 addSource(autoRevokeLiveData) { 122 removeSource(autoRevokeLiveData) 123 update() 124 } 125 update() 126 } 127 128 override fun onUpdate() { 129 val groups = packagePermsLiveData.value?.keys?.filter { it != NON_RUNTIME_NORMAL_PERMS } 130 if (groups == null && packagePermsLiveData.isInitialized) { 131 value = null 132 return 133 } else if (groups == null || (Manifest.permission_group.STORAGE in groups && 134 !fullStoragePermsLiveData.isInitialized) || !autoRevokeLiveData.isInitialized) { 135 return 136 } 137 138 val getLiveData = { groupName: String -> 139 AppPermGroupUiInfoLiveData[packageName, groupName, user] 140 } 141 setSourcesToDifference(groups, appPermGroupUiInfoLiveDatas, getLiveData) 142 143 if (!appPermGroupUiInfoLiveDatas.all { it.value.isInitialized }) { 144 return 145 } 146 147 val groupGrantStates = mutableMapOf<Category, 148 MutableList<GroupUiInfo>>() 149 groupGrantStates[Category.ALLOWED] = mutableListOf() 150 groupGrantStates[Category.ASK] = mutableListOf() 151 groupGrantStates[Category.DENIED] = mutableListOf() 152 153 val fullStorageState = fullStoragePermsLiveData.value?.find { pkg -> 154 pkg.packageName == packageName && pkg.user == user 155 } 156 157 for (groupName in groups) { 158 val isSystem = Utils.getPlatformPermissionGroups().contains(groupName) 159 appPermGroupUiInfoLiveDatas[groupName]?.value?.let { uiInfo -> 160 if (groupName == Manifest.permission_group.STORAGE && 161 (fullStorageState?.isGranted == true && !fullStorageState.isLegacy)) { 162 groupGrantStates[Category.ALLOWED]!!.add( 163 GroupUiInfo(groupName, isSystem, PermSubtitle.ALL_FILES)) 164 return@let 165 } 166 when (uiInfo.permGrantState) { 167 PermGrantState.PERMS_ALLOWED -> { 168 val subtitle = if (groupName == Manifest.permission_group.STORAGE) { 169 if (fullStorageState?.isLegacy == true) { 170 PermSubtitle.ALL_FILES 171 } else { 172 PermSubtitle.MEDIA_ONLY 173 } 174 } else { 175 PermSubtitle.NONE 176 } 177 groupGrantStates[Category.ALLOWED]!!.add( 178 GroupUiInfo(groupName, isSystem, subtitle)) 179 } 180 PermGrantState.PERMS_ALLOWED_ALWAYS -> groupGrantStates[ 181 Category.ALLOWED]!!.add(GroupUiInfo(groupName, isSystem, 182 PermSubtitle.BACKGROUND)) 183 PermGrantState.PERMS_ALLOWED_FOREGROUND_ONLY -> groupGrantStates[ 184 Category.ALLOWED]!!.add(GroupUiInfo(groupName, isSystem, 185 PermSubtitle.FOREGROUND_ONLY)) 186 PermGrantState.PERMS_DENIED -> groupGrantStates[Category.DENIED]!!.add( 187 GroupUiInfo(groupName, isSystem)) 188 PermGrantState.PERMS_ASK -> groupGrantStates[Category.ASK]!!.add( 189 GroupUiInfo(groupName, isSystem)) 190 } 191 } 192 } 193 194 value = groupGrantStates 195 } 196 } 197 198 fun setAutoRevoke(enabled: Boolean) { 199 GlobalScope.launch(IPC) { 200 val aom = app.getSystemService(AppOpsManager::class.java)!! 201 val uid = LightPackageInfoLiveData[packageName, user].getInitializedValue()?.uid 202 203 if (uid != null) { 204 Log.i(LOG_TAG, "sessionId $sessionId setting auto revoke enabled to $enabled for" + 205 "$packageName $user") 206 val tag = if (enabled) { 207 APP_PERMISSION_GROUPS_FRAGMENT_AUTO_REVOKE_ACTION__ACTION__SWITCH_ENABLED 208 } else { 209 APP_PERMISSION_GROUPS_FRAGMENT_AUTO_REVOKE_ACTION__ACTION__SWITCH_DISABLED 210 } 211 PermissionControllerStatsLog.write( 212 APP_PERMISSION_GROUPS_FRAGMENT_AUTO_REVOKE_ACTION, sessionId, uid, packageName, 213 tag) 214 215 val mode = if (enabled) { 216 MODE_ALLOWED 217 } else { 218 MODE_IGNORED 219 } 220 aom.setUidMode(OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, uid, mode) 221 if (isHibernationEnabled() && !enabled) { 222 val ahm = app.getSystemService(AppHibernationManager::class.java)!! 223 ahm.setHibernatingForUser(packageName, false) 224 ahm.setHibernatingGlobally(packageName, false) 225 } 226 } 227 } 228 } 229 230 fun showExtraPerms(fragment: Fragment, args: Bundle) { 231 fragment.findNavController().navigateSafe(R.id.perm_groups_to_custom, args) 232 } 233 234 fun showAllPermissions(fragment: Fragment, args: Bundle) { 235 fragment.findNavController().navigateSafe(R.id.perm_groups_to_all_perms, args) 236 } 237 238 // This method should be consolidated with 239 // PermissionAppsViewModel#extractGroupUsageLastAccessTime 240 fun extractGroupUsageLastAccessTime( 241 accessTime: MutableMap<String, Long>, 242 appPermissionUsages: List<AppPermissionUsage>, 243 packageName: String 244 ) { 245 accessTime.clear() 246 val filterTimeBeginMillis = max(System.currentTimeMillis() - 247 TimeUnit.DAYS.toMillis(AGGREGATE_DATA_FILTER_BEGIN_DAYS.toLong()), 248 Instant.EPOCH.toEpochMilli()) 249 val numApps: Int = appPermissionUsages.size 250 for (appIndex in 0 until numApps) { 251 val appUsage: AppPermissionUsage = appPermissionUsages[appIndex] 252 if (appUsage.packageName != packageName) { 253 continue 254 } 255 val appGroups = appUsage.groupUsages 256 val numGroups = appGroups.size 257 for (groupIndex in 0 until numGroups) { 258 val groupUsage = appGroups[groupIndex] 259 var lastAccessTime = groupUsage.lastAccessTime 260 val groupName = groupUsage.group.name 261 if (lastAccessTime == 0L || lastAccessTime < filterTimeBeginMillis) { 262 continue 263 } 264 265 // We might have another AppPermissionUsage entry that's of the same packageName 266 // but with a different uid. In that case, we want to grab the max lastAccessTime 267 // as the last usage to show. 268 lastAccessTime = Math.max( 269 accessTime.getOrDefault(groupName, Instant.EPOCH.toEpochMilli()), 270 lastAccessTime) 271 accessTime[groupName] = lastAccessTime 272 } 273 } 274 } 275 276 fun getPreferenceSummary(groupInfo: GroupUiInfo, context: Context, lastAccessTime: Long?): 277 String { 278 val summaryTimestamp = Utils 279 .getPermissionLastAccessSummaryTimestamp( 280 lastAccessTime, context, groupInfo.groupName) 281 @AppPermsLastAccessType val lastAccessType: Int = summaryTimestamp.second 282 283 return when (groupInfo.subtitle) { 284 PermSubtitle.BACKGROUND -> 285 when (lastAccessType) { 286 Utils.LAST_24H_CONTENT_PROVIDER -> context.getString( 287 R.string.app_perms_content_provider_background) 288 Utils.LAST_24H_SENSOR_TODAY -> context.getString( 289 R.string.app_perms_24h_access_background, 290 summaryTimestamp.first) 291 Utils.LAST_24H_SENSOR_YESTERDAY -> context.getString( 292 R.string.app_perms_24h_access_yest_background, 293 summaryTimestamp.first) 294 Utils.NOT_IN_LAST_24H -> context.getString( 295 R.string.permission_subtitle_background) 296 else -> context.getString( 297 R.string.permission_subtitle_background) 298 } 299 PermSubtitle.MEDIA_ONLY -> 300 when (lastAccessType) { 301 Utils.LAST_24H_CONTENT_PROVIDER -> context.getString( 302 R.string.app_perms_content_provider_media_only) 303 Utils.LAST_24H_SENSOR_TODAY -> context.getString( 304 R.string.app_perms_24h_access_media_only, 305 summaryTimestamp.first) 306 Utils.LAST_24H_SENSOR_YESTERDAY -> context.getString( 307 R.string.app_perms_24h_access_yest_media_only, 308 summaryTimestamp.first) 309 Utils.NOT_IN_LAST_24H -> context.getString( 310 R.string.permission_subtitle_media_only) 311 else -> context.getString(R.string.permission_subtitle_media_only) 312 } 313 PermSubtitle.ALL_FILES -> 314 when (lastAccessType) { 315 Utils.LAST_24H_CONTENT_PROVIDER -> context.getString( 316 R.string.app_perms_content_provider_all_files) 317 Utils.LAST_24H_SENSOR_TODAY -> context.getString( 318 R.string.app_perms_24h_access_all_files, 319 summaryTimestamp.first) 320 Utils.LAST_24H_SENSOR_YESTERDAY -> context.getString( 321 R.string.app_perms_24h_access_yest_all_files, 322 summaryTimestamp.first) 323 Utils.NOT_IN_LAST_24H -> context.getString( 324 R.string.permission_subtitle_all_files) 325 else -> context.getString(R.string.permission_subtitle_all_files) 326 } 327 PermSubtitle.FOREGROUND_ONLY -> 328 when (lastAccessType) { 329 Utils.LAST_24H_CONTENT_PROVIDER -> context.getString( 330 R.string.app_perms_content_provider) 331 Utils.LAST_24H_SENSOR_TODAY -> context.getString( 332 R.string.app_perms_24h_access, 333 summaryTimestamp.first) 334 Utils.LAST_24H_SENSOR_YESTERDAY -> context.getString( 335 R.string.app_perms_24h_access_yest, 336 summaryTimestamp.first) 337 Utils.NOT_IN_LAST_24H -> "" 338 else -> "" 339 } 340 else -> 341 when (lastAccessType) { 342 Utils.LAST_24H_CONTENT_PROVIDER -> context.getString( 343 R.string.app_perms_content_provider) 344 Utils.LAST_24H_SENSOR_TODAY -> context.getString( 345 R.string.app_perms_24h_access, 346 summaryTimestamp.first) 347 Utils.LAST_24H_SENSOR_YESTERDAY -> context.getString( 348 R.string.app_perms_24h_access_yest, 349 summaryTimestamp.first) 350 Utils.NOT_IN_LAST_24H -> "" 351 else -> "" 352 } 353 } 354 } 355 } 356 357 /** 358 * Factory for an AppPermissionGroupsViewModel 359 * 360 * @param packageName The name of the package this viewModel is representing 361 * @param user The user of the package this viewModel is representing 362 */ 363 class AppPermissionGroupsViewModelFactory( 364 private val packageName: String, 365 private val user: UserHandle, 366 private val sessionId: Long 367 ) : ViewModelProvider.Factory { 368 369 override fun <T : ViewModel> create(modelClass: Class<T>): T { 370 @Suppress("UNCHECKED_CAST") 371 return AppPermissionGroupsViewModel(packageName, user, sessionId) as T 372 } 373 } 374