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.Application
21 import android.content.Intent
22 import android.os.Bundle
23 import android.os.UserHandle
24 import androidx.fragment.app.Fragment
25 import androidx.lifecycle.AbstractSavedStateViewModelFactory
26 import androidx.lifecycle.MediatorLiveData
27 import androidx.lifecycle.SavedStateHandle
28 import androidx.lifecycle.ViewModel
29 import androidx.navigation.fragment.findNavController
30 import androidx.savedstate.SavedStateRegistryOwner
31 import com.android.permissioncontroller.R
32 import com.android.permissioncontroller.permission.data.AllPackageInfosLiveData
33 import com.android.permissioncontroller.permission.data.FullStoragePermissionAppsLiveData
34 import com.android.permissioncontroller.permission.data.FullStoragePermissionAppsLiveData.FullStoragePackageState
35 import com.android.permissioncontroller.permission.data.SinglePermGroupPackagesUiInfoLiveData
36 import com.android.permissioncontroller.permission.model.livedatatypes.AppPermGroupUiInfo.PermGrantState
37 import com.android.permissioncontroller.permission.ui.Category
38 import com.android.permissioncontroller.permission.ui.LocationProviderInterceptDialog
39 import com.android.permissioncontroller.permission.ui.model.PermissionAppsViewModel.Companion.CREATION_LOGGED_KEY
40 import com.android.permissioncontroller.permission.ui.model.PermissionAppsViewModel.Companion.HAS_SYSTEM_APPS_KEY
41 import com.android.permissioncontroller.permission.ui.model.PermissionAppsViewModel.Companion.SHOULD_SHOW_SYSTEM_KEY
42 import com.android.permissioncontroller.permission.ui.model.PermissionAppsViewModel.Companion.SHOW_ALWAYS_ALLOWED
43 import com.android.permissioncontroller.permission.utils.LocationUtils
44 import com.android.permissioncontroller.permission.utils.navigateSafe
45 
46 /**
47  * ViewModel for the PermissionAppsFragment. Has a liveData with all of the UI info for each
48  * package which requests permissions in this permission group, a liveData which tracks whether or
49  * not to show system apps, and a liveData tracking whether there are any system apps which request
50  * permissions in this group.
51  *
52  * @param app The current application
53  * @param groupName The name of the permission group this viewModel is representing
54  */
55 class PermissionAppsViewModel(
56     private val state: SavedStateHandle,
57     private val app: Application,
58     private val groupName: String
59 ) : ViewModel() {
60 
61     companion object {
62         internal const val SHOULD_SHOW_SYSTEM_KEY = "showSystem"
63         internal const val HAS_SYSTEM_APPS_KEY = "hasSystem"
64         internal const val SHOW_ALWAYS_ALLOWED = "showAlways"
65         internal const val CREATION_LOGGED_KEY = "creationLogged"
66     }
67 
68     val shouldShowSystemLiveData = state.getLiveData(SHOULD_SHOW_SYSTEM_KEY, false)
69     val hasSystemAppsLiveData = state.getLiveData(HAS_SYSTEM_APPS_KEY, true)
70     val showAllowAlwaysStringLiveData = state.getLiveData(SHOW_ALWAYS_ALLOWED, false)
71     val categorizedAppsLiveData = CategorizedAppsLiveData(groupName)
72 
73     fun updateShowSystem(showSystem: Boolean) {
74         if (showSystem != state.get(SHOULD_SHOW_SYSTEM_KEY)) {
75             state.set(SHOULD_SHOW_SYSTEM_KEY, showSystem)
76         }
77     }
78 
79     var creationLogged
80         get() = state.get(CREATION_LOGGED_KEY) ?: false
81         set(value) = state.set(CREATION_LOGGED_KEY, value)
82 
83     inner class CategorizedAppsLiveData(groupName: String)
84         : MediatorLiveData<@kotlin.jvm.JvmSuppressWildcards
85     Map<Category, List<Pair<String, UserHandle>>>>() {
86         private val packagesUiInfoLiveData = SinglePermGroupPackagesUiInfoLiveData[groupName]
87 
88         init {
89             var fullStorageLiveData: FullStoragePermissionAppsLiveData? = null
90 
91             // If this is the Storage group, observe a FullStoragePermissionAppsLiveData, update
92             // the packagesWithFullFileAccess list, and call update to populate the subtitles.
93             if (groupName == Manifest.permission_group.STORAGE) {
94                 fullStorageLiveData = FullStoragePermissionAppsLiveData
95                 addSource(FullStoragePermissionAppsLiveData) { fullAccessPackages ->
96                     if (fullAccessPackages != packagesWithFullFileAccess) {
97                         packagesWithFullFileAccess = fullAccessPackages.filter { it.isGranted }
98                         if (packagesUiInfoLiveData.isInitialized) {
99                             update()
100                         }
101                     }
102                 }
103             }
104 
105             addSource(packagesUiInfoLiveData) {
106                 if (fullStorageLiveData == null || fullStorageLiveData.isInitialized)
107                     update()
108             }
109             addSource(shouldShowSystemLiveData) {
110                 if (fullStorageLiveData == null || fullStorageLiveData.isInitialized)
111                     update()
112             }
113 
114             if ((fullStorageLiveData == null || fullStorageLiveData.isInitialized) &&
115                 packagesUiInfoLiveData.isInitialized) {
116                 packagesWithFullFileAccess = fullStorageLiveData?.value?.filter { it.isGranted }
117                     ?: emptyList()
118                 update()
119             }
120         }
121 
122         fun update() {
123             val categoryMap = mutableMapOf<Category, MutableList<Pair<String, UserHandle>>>()
124             val showSystem: Boolean = state.get(SHOULD_SHOW_SYSTEM_KEY) ?: false
125 
126             categoryMap[Category.ALLOWED] = mutableListOf()
127             categoryMap[Category.ALLOWED_FOREGROUND] = mutableListOf()
128             categoryMap[Category.ASK] = mutableListOf()
129             categoryMap[Category.DENIED] = mutableListOf()
130 
131             val packageMap = packagesUiInfoLiveData.value ?: run {
132                 if (packagesUiInfoLiveData.isInitialized) {
133                     value = categoryMap
134                 }
135                 return
136             }
137 
138             val hasSystem = packageMap.any { it.value.isSystem && it.value.shouldShow }
139             if (hasSystem != state.get(HAS_SYSTEM_APPS_KEY)) {
140                 state.set(HAS_SYSTEM_APPS_KEY, hasSystem)
141             }
142 
143             var showAlwaysAllowedString = false
144 
145             for ((packageUserPair, uiInfo) in packageMap) {
146                 if (!uiInfo.shouldShow) {
147                     continue
148                 }
149 
150                 if (uiInfo.isSystem && !showSystem) {
151                     continue
152                 }
153 
154                 if (uiInfo.permGrantState == PermGrantState.PERMS_ALLOWED_ALWAYS ||
155                     uiInfo.permGrantState == PermGrantState.PERMS_ALLOWED_FOREGROUND_ONLY) {
156                     showAlwaysAllowedString = true
157                 }
158 
159                 var category = when (uiInfo.permGrantState) {
160                     PermGrantState.PERMS_ALLOWED -> Category.ALLOWED
161                     PermGrantState.PERMS_ALLOWED_FOREGROUND_ONLY -> Category.ALLOWED_FOREGROUND
162                     PermGrantState.PERMS_ALLOWED_ALWAYS -> Category.ALLOWED
163                     PermGrantState.PERMS_DENIED -> Category.DENIED
164                     PermGrantState.PERMS_ASK -> Category.ASK
165                 }
166 
167                 if (groupName == Manifest.permission_group.STORAGE &&
168                     packagesWithFullFileAccess.any { !it.isLegacy && it.isGranted &&
169                         it.packageName to it.user == packageUserPair }) {
170                     category = Category.ALLOWED
171                 }
172                 categoryMap[category]!!.add(packageUserPair)
173             }
174             showAllowAlwaysStringLiveData.value = showAlwaysAllowedString
175             value = categoryMap
176         }
177     }
178 
179     /**
180      * If this is the storage permission group, some apps have full access to storage, while
181      * others just have access to media files. This list contains the packages with full access.
182      * To listen for changes, create and observe a FullStoragePermissionAppsLiveData
183      */
184     private var packagesWithFullFileAccess = listOf<FullStoragePackageState>()
185 
186     /**
187      * Whether or not to show the "Files and Media" subtitle label for a package, vs. the normal
188      * "Media". Requires packagesWithFullFileAccess to be updated in order to work. To do this,
189      * create and observe a FullStoragePermissionAppsLiveData.
190      *
191      * @param packageName The name of the package we want to check
192      * @param user The name of the user whose package we want to check
193      *
194      * @return true if the package and user has full file access
195      */
196     fun packageHasFullStorage(packageName: String, user: UserHandle): Boolean {
197         return packagesWithFullFileAccess.any {
198             it.packageName == packageName && it.user == user }
199     }
200 
201     /**
202      * Whether or not packages have been loaded from the system.
203      * To update, need to observe the allPackageInfosLiveData.
204      *
205      * @return Whether or not all packages have been loaded
206      */
207     fun arePackagesLoaded(): Boolean {
208         return AllPackageInfosLiveData.isInitialized
209     }
210 
211     /**
212      * Navigate to an AppPermissionFragment, unless this is a special location package
213      *
214      * @param fragment The fragment attached to this ViewModel
215      * @param packageName The package name we want to navigate to
216      * @param user The user we want to navigate to the package of
217      * @param args The arguments to pass onto the fragment
218      */
219     fun navigateToAppPermission(
220         fragment: Fragment,
221         packageName: String,
222         user: UserHandle,
223         args: Bundle
224     ) {
225         val activity = fragment.activity!!
226         if (LocationUtils.isLocationGroupAndProvider(
227                 activity, groupName, packageName)) {
228             val intent = Intent(activity, LocationProviderInterceptDialog::class.java)
229             intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
230             activity.startActivityAsUser(intent, user)
231             return
232         }
233 
234         if (LocationUtils.isLocationGroupAndControllerExtraPackage(
235                 activity, groupName, packageName)) {
236             // Redirect to location controller extra package settings.
237             LocationUtils.startLocationControllerExtraPackageSettings(activity, user)
238             return
239         }
240 
241         fragment.findNavController().navigateSafe(R.id.perm_apps_to_app, args)
242     }
243 }
244 
245 /**
246  * Factory for a PermissionAppsViewModel
247  *
248  * @param app The current application of the fragment
249  * @param groupName The name of the permission group this viewModel is representing
250  * @param owner The owner of this saved state
251  * @param defaultArgs The default args to pass
252  */
253 class PermissionAppsViewModelFactory(
254     private val app: Application,
255     private val groupName: String,
256     owner: SavedStateRegistryOwner,
257     defaultArgs: Bundle
258 ) : AbstractSavedStateViewModelFactory(owner, defaultArgs) {
259 
createnull260     override fun <T : ViewModel?> create(p0: String, p1: Class<T>, state: SavedStateHandle): T {
261         state.set(SHOULD_SHOW_SYSTEM_KEY, state.get<Boolean>(SHOULD_SHOW_SYSTEM_KEY) ?: false)
262         state.set(HAS_SYSTEM_APPS_KEY, state.get<Boolean>(HAS_SYSTEM_APPS_KEY) ?: true)
263         state.set(SHOW_ALWAYS_ALLOWED, state.get<Boolean>(SHOW_ALWAYS_ALLOWED) ?: false)
264         state.set(CREATION_LOGGED_KEY, state.get<Boolean>(CREATION_LOGGED_KEY) ?: false)
265         @Suppress("UNCHECKED_CAST")
266         return PermissionAppsViewModel(state, app, groupName) as T
267     }
268 }