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.app.AppOpsManager
20 import android.app.AppOpsManager.MODE_ALLOWED
21 import android.app.AppOpsManager.MODE_IGNORED
22 import android.app.AppOpsManager.OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED
23 import android.Manifest
24 import android.os.Bundle
25 import android.os.UserHandle
26 import android.util.Log
27 import androidx.fragment.app.Fragment
28 import androidx.lifecycle.ViewModel
29 import androidx.lifecycle.ViewModelProvider
30 import androidx.navigation.fragment.findNavController
31 import com.android.permissioncontroller.PermissionControllerApplication
32 import com.android.permissioncontroller.PermissionControllerStatsLog
33 import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_GROUPS_FRAGMENT_AUTO_REVOKE_ACTION
34 import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_GROUPS_FRAGMENT_AUTO_REVOKE_ACTION__ACTION__SWITCH_DISABLED
35 import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_GROUPS_FRAGMENT_AUTO_REVOKE_ACTION__ACTION__SWITCH_ENABLED
36 import com.android.permissioncontroller.R
37 import com.android.permissioncontroller.permission.data.AppPermGroupUiInfoLiveData
38 import com.android.permissioncontroller.permission.data.AutoRevokeStateLiveData
39 import com.android.permissioncontroller.permission.data.FullStoragePermissionAppsLiveData
40 import com.android.permissioncontroller.permission.data.LightPackageInfoLiveData
41 import com.android.permissioncontroller.permission.data.PackagePermissionsLiveData
42 import com.android.permissioncontroller.permission.data.PackagePermissionsLiveData.Companion.NON_RUNTIME_NORMAL_PERMS
43 import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData
44 import com.android.permissioncontroller.permission.data.get
45 import com.android.permissioncontroller.permission.model.livedatatypes.AppPermGroupUiInfo.PermGrantState
46 import com.android.permissioncontroller.permission.ui.Category
47 import com.android.permissioncontroller.permission.utils.IPC
48 import com.android.permissioncontroller.permission.utils.Utils
49 import com.android.permissioncontroller.permission.utils.navigateSafe
50 import kotlinx.coroutines.GlobalScope
51 import kotlinx.coroutines.launch
52 
53 /**
54  * ViewModel for the AppPermissionGroupsFragment. Has a liveData with the UI information for all
55  * permission groups that this package requests runtime permissions from
56  *
57  * @param packageName The name of the package this viewModel is representing
58  * @param user The user of the package this viewModel is representing
59  */
60 class AppPermissionGroupsViewModel(
61     private val packageName: String,
62     private val user: UserHandle,
63     private val sessionId: Long
64 ) : ViewModel() {
65 
66     companion object {
67         val LOG_TAG: String = AppPermissionGroupsViewModel::class.java.simpleName
68     }
69 
70     enum class PermSubtitle(val value: Int) {
71         NONE(0),
72         MEDIA_ONLY(1),
73         ALL_FILES(2),
74         FOREGROUND_ONLY(3),
75     }
76 
77     data class GroupUiInfo(
78         val groupName: String,
79         val isSystem: Boolean = false,
80         val subtitle: PermSubtitle
81     ) {
82         constructor(groupName: String, isSystem: Boolean) :
83             this(groupName, isSystem, PermSubtitle.NONE)
84     }
85 
86     val autoRevokeLiveData = AutoRevokeStateLiveData[packageName, user]
87 
88     /**
89      * LiveData whose data is a map of grant category (either allowed or denied) to a list
90      * of permission group names that match the key, and two booleans representing if this is a
91      * system group, and a subtitle resource ID, if applicable.
92      */
93     val packagePermGroupsLiveData = object : SmartUpdateMediatorLiveData<@JvmSuppressWildcards
94     Map<Category, List<GroupUiInfo>>>() {
95 
96         private val packagePermsLiveData =
97             PackagePermissionsLiveData[packageName, user]
98         private val appPermGroupUiInfoLiveDatas = mutableMapOf<String, AppPermGroupUiInfoLiveData>()
99         private val fullStoragePermsLiveData = FullStoragePermissionAppsLiveData
100 
101         init {
102             addSource(packagePermsLiveData) {
103                 updateIfActive()
104             }
105             addSource(fullStoragePermsLiveData) {
106                 updateIfActive()
107             }
108             addSource(autoRevokeLiveData) {
109                 removeSource(autoRevokeLiveData)
110                 updateIfActive()
111             }
112             updateIfActive()
113         }
114 
115         override fun onUpdate() {
116             val groups = packagePermsLiveData.value?.keys?.filter { it != NON_RUNTIME_NORMAL_PERMS }
117             if (groups == null && packagePermsLiveData.isInitialized) {
118                 value = null
119                 return
120             } else if (groups == null || (Manifest.permission_group.STORAGE in groups &&
121                     !fullStoragePermsLiveData.isInitialized) || !autoRevokeLiveData.isInitialized) {
122                 return
123             }
124 
125             val hasFullStorage = fullStoragePermsLiveData.value?.any { pkg ->
126                 pkg.packageName == packageName && pkg.user == user && pkg.isGranted
127             } ?: false
128 
129             val getLiveData = { groupName: String ->
130                 AppPermGroupUiInfoLiveData[packageName, groupName, user]
131             }
132             setSourcesToDifference(groups, appPermGroupUiInfoLiveDatas, getLiveData)
133 
134             if (!appPermGroupUiInfoLiveDatas.all { it.value.isInitialized }) {
135                 return
136             }
137 
138             val groupGrantStates = mutableMapOf<Category,
139                 MutableList<GroupUiInfo>>()
140             groupGrantStates[Category.ALLOWED] = mutableListOf()
141             groupGrantStates[Category.ASK] = mutableListOf()
142             groupGrantStates[Category.DENIED] = mutableListOf()
143 
144             for (groupName in groups) {
145                 val isSystem = Utils.getPlatformPermissionGroups().contains(groupName)
146                 appPermGroupUiInfoLiveDatas[groupName]?.value?.let { uiInfo ->
147                     when (uiInfo.permGrantState) {
148                         PermGrantState.PERMS_ALLOWED -> {
149                             var subtitle = PermSubtitle.NONE
150                             if (groupName == Manifest.permission_group.STORAGE) {
151                                 subtitle = if (hasFullStorage) {
152                                     PermSubtitle.ALL_FILES
153                                 } else {
154                                     PermSubtitle.MEDIA_ONLY
155                                 }
156                             }
157                             groupGrantStates[Category.ALLOWED]!!.add(
158                                 GroupUiInfo(groupName, isSystem, subtitle))
159                         }
160                         PermGrantState.PERMS_ALLOWED_ALWAYS -> groupGrantStates[
161                             Category.ALLOWED]!!.add(GroupUiInfo(groupName, isSystem))
162                         PermGrantState.PERMS_ALLOWED_FOREGROUND_ONLY -> groupGrantStates[
163                             Category.ALLOWED]!!.add(GroupUiInfo(groupName, isSystem,
164                             PermSubtitle.FOREGROUND_ONLY))
165                         PermGrantState.PERMS_DENIED -> groupGrantStates[Category.DENIED]!!.add(
166                             GroupUiInfo(groupName, isSystem))
167                         PermGrantState.PERMS_ASK -> groupGrantStates[Category.ASK]!!.add(
168                             GroupUiInfo(groupName, isSystem))
169                     }
170                 }
171             }
172 
173             value = groupGrantStates
174         }
175     }
176 
177     fun setAutoRevoke(enabled: Boolean) {
178         GlobalScope.launch(IPC) {
179             val aom = PermissionControllerApplication.get()
180                 .getSystemService(AppOpsManager::class.java)!!
181             val uid = LightPackageInfoLiveData[packageName, user].getInitializedValue()?.uid
182 
183             if (uid != null) {
184                 Log.i(LOG_TAG, "sessionId $sessionId setting auto revoke enabled to $enabled for" +
185                     "$packageName $user")
186                 val tag = if (enabled) {
187                     APP_PERMISSION_GROUPS_FRAGMENT_AUTO_REVOKE_ACTION__ACTION__SWITCH_ENABLED
188                 } else {
189                     APP_PERMISSION_GROUPS_FRAGMENT_AUTO_REVOKE_ACTION__ACTION__SWITCH_DISABLED
190                 }
191                 PermissionControllerStatsLog.write(
192                     APP_PERMISSION_GROUPS_FRAGMENT_AUTO_REVOKE_ACTION, sessionId, uid, packageName,
193                     tag)
194 
195                 val mode = if (enabled) {
196                     MODE_ALLOWED
197                 } else {
198                     MODE_IGNORED
199                 }
200                 aom.setUidMode(OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, uid, mode)
201             }
202         }
203     }
204 
205     fun showExtraPerms(fragment: Fragment, args: Bundle) {
206         fragment.findNavController().navigateSafe(R.id.perm_groups_to_custom, args)
207     }
208 
209     fun showAllPermissions(fragment: Fragment, args: Bundle) {
210         fragment.findNavController().navigateSafe(R.id.perm_groups_to_all_perms, args)
211     }
212 }
213 
214 /**
215  * Factory for an AppPermissionGroupsViewModel
216  *
217  * @param packageName The name of the package this viewModel is representing
218  * @param user The user of the package this viewModel is representing
219  */
220 class AppPermissionGroupsViewModelFactory(
221     private val packageName: String,
222     private val user: UserHandle,
223     private val sessionId: Long
224 ) : ViewModelProvider.Factory {
225 
createnull226     override fun <T : ViewModel> create(modelClass: Class<T>): T {
227         @Suppress("UNCHECKED_CAST")
228         return AppPermissionGroupsViewModel(packageName, user, sessionId) as T
229     }
230 }
231