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