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