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