blob: 405d93d2f8da7171fb85b4ad74ebe5cb0b897320 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.permissioncontroller.permission.ui.model
import android.app.AppOpsManager
import android.app.AppOpsManager.MODE_ALLOWED
import android.app.AppOpsManager.MODE_IGNORED
import android.app.AppOpsManager.OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED
import android.Manifest
import android.app.role.RoleManager
import android.os.Bundle
import android.os.UserHandle
import android.provider.Settings
import android.util.Log
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import com.android.permissioncontroller.Constants.ASSISTANT_RECORD_AUDIO_IS_USER_SENSITIVE_KEY
import com.android.permissioncontroller.PermissionControllerApplication
import com.android.permissioncontroller.PermissionControllerStatsLog
import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_GROUPS_FRAGMENT_AUTO_REVOKE_ACTION
import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_GROUPS_FRAGMENT_AUTO_REVOKE_ACTION__ACTION__SWITCH_DISABLED
import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_GROUPS_FRAGMENT_AUTO_REVOKE_ACTION__ACTION__SWITCH_ENABLED
import com.android.permissioncontroller.R
import com.android.permissioncontroller.permission.data.AppPermGroupUiInfoLiveData
import com.android.permissioncontroller.permission.data.AutoRevokeStateLiveData
import com.android.permissioncontroller.permission.data.FullStoragePermissionAppsLiveData
import com.android.permissioncontroller.permission.data.LightPackageInfoLiveData
import com.android.permissioncontroller.permission.data.PackagePermissionsLiveData
import com.android.permissioncontroller.permission.data.PackagePermissionsLiveData.Companion.NON_RUNTIME_NORMAL_PERMS
import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData
import com.android.permissioncontroller.permission.data.get
import com.android.permissioncontroller.permission.debug.shouldShowCameraMicIndicators
import com.android.permissioncontroller.permission.model.livedatatypes.AppPermGroupUiInfo.PermGrantState
import com.android.permissioncontroller.permission.ui.Category
import com.android.permissioncontroller.permission.utils.IPC
import com.android.permissioncontroller.permission.utils.KotlinUtils
import com.android.permissioncontroller.permission.utils.Utils
import com.android.permissioncontroller.permission.utils.navigateSafe
import com.android.permissioncontroller.permission.utils.updateUserSensitiveForUid
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
/**
* ViewModel for the AppPermissionGroupsFragment. Has a liveData with the UI information for all
* permission groups that this package requests runtime permissions from
*
* @param packageName The name of the package this viewModel is representing
* @param user The user of the package this viewModel is representing
*/
class AppPermissionGroupsViewModel(
private val packageName: String,
private val user: UserHandle,
private val sessionId: Long
) : ViewModel() {
companion object {
val LOG_TAG: String = AppPermissionGroupsViewModel::class.java.simpleName
}
val app = PermissionControllerApplication.get()!!
enum class PermSubtitle(val value: Int) {
NONE(0),
MEDIA_ONLY(1),
ALL_FILES(2),
FOREGROUND_ONLY(3),
}
data class GroupUiInfo(
val groupName: String,
val isSystem: Boolean = false,
val subtitle: PermSubtitle
) {
constructor(groupName: String, isSystem: Boolean) :
this(groupName, isSystem, PermSubtitle.NONE)
}
val autoRevokeLiveData = AutoRevokeStateLiveData[packageName, user]
/**
* LiveData whose data is a map of grant category (either allowed or denied) to a list
* of permission group names that match the key, and two booleans representing if this is a
* system group, and a subtitle resource ID, if applicable.
*/
val packagePermGroupsLiveData = object : SmartUpdateMediatorLiveData<@JvmSuppressWildcards
Map<Category, List<GroupUiInfo>>>() {
private val packagePermsLiveData =
PackagePermissionsLiveData[packageName, user]
private val appPermGroupUiInfoLiveDatas = mutableMapOf<String, AppPermGroupUiInfoLiveData>()
private val fullStoragePermsLiveData = FullStoragePermissionAppsLiveData
init {
addSource(packagePermsLiveData) {
updateIfActive()
}
addSource(fullStoragePermsLiveData) {
updateIfActive()
}
addSource(autoRevokeLiveData) {
removeSource(autoRevokeLiveData)
updateIfActive()
}
updateIfActive()
}
override fun onUpdate() {
val groups = packagePermsLiveData.value?.keys?.filter { it != NON_RUNTIME_NORMAL_PERMS }
if (groups == null && packagePermsLiveData.isInitialized) {
value = null
return
} else if (groups == null || (Manifest.permission_group.STORAGE in groups &&
!fullStoragePermsLiveData.isInitialized) || !autoRevokeLiveData.isInitialized) {
return
}
val hasFullStorage = fullStoragePermsLiveData.value?.any { pkg ->
pkg.packageName == packageName && pkg.user == user && pkg.isGranted
} ?: false
val getLiveData = { groupName: String ->
AppPermGroupUiInfoLiveData[packageName, groupName, user]
}
setSourcesToDifference(groups, appPermGroupUiInfoLiveDatas, getLiveData)
if (!appPermGroupUiInfoLiveDatas.all { it.value.isInitialized }) {
return
}
val groupGrantStates = mutableMapOf<Category,
MutableList<GroupUiInfo>>()
groupGrantStates[Category.ALLOWED] = mutableListOf()
groupGrantStates[Category.ASK] = mutableListOf()
groupGrantStates[Category.DENIED] = mutableListOf()
for (groupName in groups) {
val isSystem = Utils.getPlatformPermissionGroups().contains(groupName)
appPermGroupUiInfoLiveDatas[groupName]?.value?.let { uiInfo ->
when (uiInfo.permGrantState) {
PermGrantState.PERMS_ALLOWED -> {
var subtitle = PermSubtitle.NONE
if (groupName == Manifest.permission_group.STORAGE) {
subtitle = if (hasFullStorage) {
PermSubtitle.ALL_FILES
} else {
PermSubtitle.MEDIA_ONLY
}
}
groupGrantStates[Category.ALLOWED]!!.add(
GroupUiInfo(groupName, isSystem, subtitle))
}
PermGrantState.PERMS_ALLOWED_ALWAYS -> groupGrantStates[
Category.ALLOWED]!!.add(GroupUiInfo(groupName, isSystem))
PermGrantState.PERMS_ALLOWED_FOREGROUND_ONLY -> groupGrantStates[
Category.ALLOWED]!!.add(GroupUiInfo(groupName, isSystem,
PermSubtitle.FOREGROUND_ONLY))
PermGrantState.PERMS_DENIED -> groupGrantStates[Category.DENIED]!!.add(
GroupUiInfo(groupName, isSystem))
PermGrantState.PERMS_ASK -> groupGrantStates[Category.ASK]!!.add(
GroupUiInfo(groupName, isSystem))
}
}
}
value = groupGrantStates
}
}
fun shouldShowAssistantMicSwitch(): Boolean {
if (!shouldShowCameraMicIndicators()) {
return false
}
val rolePkgs = Utils.getUserContext(app, user).getSystemService(RoleManager::class.java)!!
.getRoleHolders(RoleManager.ROLE_ASSISTANT)
return packageName in rolePkgs
}
fun setAutoRevoke(enabled: Boolean) {
GlobalScope.launch(IPC) {
val aom = app.getSystemService(AppOpsManager::class.java)!!
val uid = LightPackageInfoLiveData[packageName, user].getInitializedValue()?.uid
if (uid != null) {
Log.i(LOG_TAG, "sessionId $sessionId setting auto revoke enabled to $enabled for" +
"$packageName $user")
val tag = if (enabled) {
APP_PERMISSION_GROUPS_FRAGMENT_AUTO_REVOKE_ACTION__ACTION__SWITCH_ENABLED
} else {
APP_PERMISSION_GROUPS_FRAGMENT_AUTO_REVOKE_ACTION__ACTION__SWITCH_DISABLED
}
PermissionControllerStatsLog.write(
APP_PERMISSION_GROUPS_FRAGMENT_AUTO_REVOKE_ACTION, sessionId, uid, packageName,
tag)
val mode = if (enabled) {
MODE_ALLOWED
} else {
MODE_IGNORED
}
aom.setUidMode(OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, uid, mode)
}
}
}
fun setShowAssistantMicUsage(enabled: Boolean) {
val value = if (enabled) {
1
} else {
0
}
Settings.Secure.putInt(app.contentResolver, ASSISTANT_RECORD_AUDIO_IS_USER_SENSITIVE_KEY,
value)
updateUserSensitiveForUid(KotlinUtils.getPackageUid(app, packageName, user) ?: return)
}
fun showExtraPerms(fragment: Fragment, args: Bundle) {
fragment.findNavController().navigateSafe(R.id.perm_groups_to_custom, args)
}
fun showAllPermissions(fragment: Fragment, args: Bundle) {
fragment.findNavController().navigateSafe(R.id.perm_groups_to_all_perms, args)
}
}
/**
* Factory for an AppPermissionGroupsViewModel
*
* @param packageName The name of the package this viewModel is representing
* @param user The user of the package this viewModel is representing
*/
class AppPermissionGroupsViewModelFactory(
private val packageName: String,
private val user: UserHandle,
private val sessionId: Long
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
return AppPermissionGroupsViewModel(packageName, user, sessionId) as T
}
}