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