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