• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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