1 /* <lambda>null2 * Copyright (C) 2023 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.viewmodel.v31 18 19 import android.app.Application 20 import android.content.Context 21 import android.os.Build 22 import android.os.Bundle 23 import androidx.annotation.RequiresApi 24 import androidx.annotation.VisibleForTesting 25 import androidx.lifecycle.AbstractSavedStateViewModelFactory 26 import androidx.lifecycle.AndroidViewModel 27 import androidx.lifecycle.SavedStateHandle 28 import androidx.lifecycle.ViewModel 29 import androidx.lifecycle.asLiveData 30 import androidx.lifecycle.viewModelScope 31 import androidx.savedstate.SavedStateRegistryOwner 32 import com.android.permissioncontroller.permission.data.repository.v31.PermissionRepository 33 import com.android.permissioncontroller.permission.domain.model.v31.PermissionGroupUsageModel 34 import com.android.permissioncontroller.permission.domain.usecase.v31.GetPermissionGroupUsageUseCase 35 import com.android.permissioncontroller.permission.utils.KotlinUtils 36 import java.time.Instant 37 import java.util.concurrent.TimeUnit 38 import kotlin.concurrent.Volatile 39 import kotlin.math.max 40 import kotlinx.coroutines.CoroutineDispatcher 41 import kotlinx.coroutines.CoroutineScope 42 import kotlinx.coroutines.Dispatchers 43 import kotlinx.coroutines.flow.Flow 44 import kotlinx.coroutines.flow.SharingStarted 45 import kotlinx.coroutines.flow.StateFlow 46 import kotlinx.coroutines.flow.flowOn 47 import kotlinx.coroutines.flow.map 48 import kotlinx.coroutines.flow.stateIn 49 import kotlinx.coroutines.runBlocking 50 51 /** Privacy dashboard's new implementation. */ 52 class PermissionUsageViewModel( 53 val app: Application, 54 private val permissionRepository: PermissionRepository, 55 private val getPermissionUsageUseCase: GetPermissionGroupUsageUseCase, 56 scope: CoroutineScope? = null, 57 private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default, 58 // Inject the parameter to prevent READ_DEVICE_CONFIG permission error on T- platforms. 59 private val is7DayToggleEnabled: Boolean = KotlinUtils.is7DayToggleEnabled(), 60 ) : AndroidViewModel(app) { 61 private var showSystemApps = false 62 private var show7DaysData = false 63 private val coroutineScope = scope ?: viewModelScope 64 65 // Cache permission usages to calculate ui state for "show system" and "show 7 days" toggle. 66 @Volatile private var permissionGroupUsages = emptyList<PermissionGroupUsageModel>() 67 68 private val permissionUsagesUiStateFlow: StateFlow<PermissionUsagesUiState> by lazy { 69 getPermissionUsageUseCase() 70 .map { permGroupUsages -> 71 permissionGroupUsages = permGroupUsages 72 buildPermissionUsagesUiState(permGroupUsages) 73 } 74 .flowOn(defaultDispatcher) 75 .stateIn( 76 coroutineScope, 77 SharingStarted.WhileSubscribed(5000), 78 PermissionUsagesUiState.Loading 79 ) 80 } 81 82 val permissionUsagesUiLiveData = 83 permissionUsagesUiStateFlow.asLiveData(context = coroutineScope.coroutineContext) 84 85 @VisibleForTesting 86 fun getPermissionUsagesUiDataFlow(): Flow<PermissionUsagesUiState> { 87 return permissionUsagesUiStateFlow 88 } 89 90 /** Get start time based on whether to show 24 hours or 7 days data. */ 91 private fun getStartTime(show7DaysData: Boolean): Long { 92 val curTime = System.currentTimeMillis() 93 val showPermissionUsagesDuration = 94 if (is7DayToggleEnabled && show7DaysData) { 95 TIME_7_DAYS_DURATION 96 } else { 97 TIME_24_HOURS_DURATION 98 } 99 return max(curTime - showPermissionUsagesDuration, Instant.EPOCH.toEpochMilli()) 100 } 101 102 /** Builds a [PermissionUsagesUiState] containing all data necessary to render the UI. */ 103 private fun buildPermissionUsagesUiState( 104 permissionGroupOps: List<PermissionGroupUsageModel> 105 ): PermissionUsagesUiState { 106 val startTime = getStartTime(show7DaysData) 107 val dashboardPermissionGroups = 108 permissionRepository.getPermissionGroupsForPrivacyDashboard() 109 val permissionUsageCountMap = HashMap<String, Int>(dashboardPermissionGroups.size) 110 for (permissionGroup in dashboardPermissionGroups) { 111 permissionUsageCountMap[permissionGroup] = 0 112 } 113 114 val permGroupOps = permissionGroupOps.filter { it.lastAccessTimestampMillis > startTime } 115 permGroupOps 116 .filter { showSystemApps || it.isUserSensitive } 117 .forEach { 118 permissionUsageCountMap[it.permissionGroup] = 119 permissionUsageCountMap.getOrDefault(it.permissionGroup, 0) + 1 120 } 121 return PermissionUsagesUiState.Success( 122 permGroupOps.any { !it.isUserSensitive }, 123 permissionUsageCountMap 124 ) 125 } 126 127 fun getShowSystemApps(): Boolean { 128 return showSystemApps 129 } 130 131 fun getShow7DaysData(): Boolean { 132 return show7DaysData 133 } 134 135 fun updateShowSystem(showSystem: Boolean): PermissionUsagesUiState { 136 showSystemApps = showSystem 137 return buildPermissionUsagesUiState(permissionGroupUsages) 138 } 139 140 fun updateShow7Days(show7Days: Boolean): PermissionUsagesUiState { 141 show7DaysData = show7Days 142 return buildPermissionUsagesUiState(permissionGroupUsages) 143 } 144 145 private val permissionGroupLabels = mutableMapOf<String, String>() 146 147 fun getPermissionGroupLabel(context: Context, permissionGroup: String): String { 148 return runBlocking(coroutineScope.coroutineContext + Dispatchers.Default) { 149 permissionGroupLabels.getOrDefault( 150 permissionGroup, 151 permissionRepository.getPermissionGroupLabel(context, permissionGroup).toString() 152 ) 153 } 154 } 155 156 /** Companion class for [PermissionUsageViewModel]. */ 157 companion object { 158 private val TIME_7_DAYS_DURATION = TimeUnit.DAYS.toMillis(7) 159 private val TIME_24_HOURS_DURATION = TimeUnit.DAYS.toMillis(1) 160 } 161 } 162 163 /** Data class to hold all the information required to configure the UI. */ 164 sealed class PermissionUsagesUiState { 165 data object Loading : PermissionUsagesUiState() 166 data class Success( 167 val shouldShowSystemToggle: Boolean, 168 val permissionGroupUsageCount: Map<String, Int> 169 ) : PermissionUsagesUiState() 170 } 171 172 /** Factory for [PermissionUsageViewModel]. */ 173 @RequiresApi(Build.VERSION_CODES.S) 174 class PermissionUsageViewModelFactory( 175 private val app: Application, 176 owner: SavedStateRegistryOwner, 177 defaultArgs: Bundle 178 ) : AbstractSavedStateViewModelFactory(owner, defaultArgs) { 179 @Suppress("UNCHECKED_CAST") createnull180 override fun <T : ViewModel> create( 181 key: String, 182 modelClass: Class<T>, 183 handle: SavedStateHandle 184 ): T { 185 val permissionRepository = PermissionRepository.getInstance(app) 186 val permissionUsageUseCase = GetPermissionGroupUsageUseCase.create(app) 187 return PermissionUsageViewModel(app, permissionRepository, permissionUsageUseCase) as T 188 } 189 } 190