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