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