diff options
5 files changed, 143 insertions, 16 deletions
diff --git a/packages/SettingsLib/Spa/spa/Android.bp b/packages/SettingsLib/Spa/spa/Android.bp index a4928e6608ab..6871f2154bd6 100644 --- a/packages/SettingsLib/Spa/spa/Android.bp +++ b/packages/SettingsLib/Spa/spa/Android.bp @@ -27,6 +27,7 @@ android_library { "androidx.compose.material3_material3", "androidx.compose.material_material-icons-extended", "androidx.compose.runtime_runtime", + "androidx.compose.runtime_runtime-livedata", "androidx.compose.ui_ui-tooling-preview", "androidx.navigation_navigation-compose", "com.google.android.material_material", diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt index 8876f66bcafe..93ba4f7824b7 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt @@ -24,6 +24,7 @@ import android.content.Context import android.content.pm.ApplicationInfo import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Transformations class AppOpsController( context: Context, @@ -32,21 +33,23 @@ class AppOpsController( ) { private val appOpsManager = checkNotNull(context.getSystemService(AppOpsManager::class.java)) + val mode: LiveData<Int> + get() = _mode val isAllowed: LiveData<Boolean> - get() = _isAllowed + get() = Transformations.map(_mode) { it == MODE_ALLOWED } fun setAllowed(allowed: Boolean) { val mode = if (allowed) MODE_ALLOWED else MODE_ERRORED appOpsManager.setMode(op, app.uid, app.packageName, mode) - _isAllowed.postValue(allowed) + _mode.postValue(mode) } @Mode fun getMode(): Int = appOpsManager.checkOpNoThrow(op, app.uid, app.packageName) - private val _isAllowed = object : MutableLiveData<Boolean>() { + private val _mode = object : MutableLiveData<Int>() { override fun onActive() { - postValue(getMode() == MODE_ALLOWED) + postValue(getMode()) } override fun onInactive() { diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt index e521edd6b845..ba8af5400e18 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt @@ -16,31 +16,52 @@ package com.android.settingslib.spaprivileged.model.app +import android.app.AppGlobals import android.content.pm.ApplicationInfo import android.content.pm.PackageInfo +import android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED import android.content.pm.PackageManager import android.util.Log +import com.android.settingslib.spa.framework.util.asyncFilter private const val TAG = "PackageManagers" object PackageManagers { - fun getPackageInfoAsUser(packageName: String, userId: Int): PackageInfo = - PackageManager.getPackageInfoAsUserCached(packageName, 0, userId) + private val iPackageManager by lazy { AppGlobals.getPackageManager() } + + fun getPackageInfoAsUser(packageName: String, userId: Int): PackageInfo? = + getPackageInfoAsUser(packageName, 0, userId) fun getApplicationInfoAsUser(packageName: String, userId: Int): ApplicationInfo = PackageManager.getApplicationInfoAsUserCached(packageName, 0, userId) - fun hasRequestPermission(app: ApplicationInfo, permission: String): Boolean { - val packageInfo = try { - PackageManager.getPackageInfoAsUserCached( - app.packageName, PackageManager.GET_PERMISSIONS.toLong(), app.userId - ) - } catch (e: PackageManager.NameNotFoundException) { - Log.w(TAG, "getPackageInfoAsUserCached() failed", e) - return false - } + fun ApplicationInfo.hasRequestPermission(permission: String): Boolean { + val packageInfo = getPackageInfoAsUser(packageName, PackageManager.GET_PERMISSIONS, userId) return packageInfo?.requestedPermissions?.let { permission in it } ?: false } + + fun ApplicationInfo.hasGrantPermission(permission: String): Boolean { + val packageInfo = getPackageInfoAsUser(packageName, PackageManager.GET_PERMISSIONS, userId) + ?: return false + val index = packageInfo.requestedPermissions.indexOf(permission) + return index >= 0 && + packageInfo.requestedPermissionsFlags[index].hasFlag(REQUESTED_PERMISSION_GRANTED) + } + + suspend fun getAppOpPermissionPackages(userId: Int, permission: String): Set<String> = + iPackageManager.getAppOpPermissionPackages(permission, userId).asIterable().asyncFilter { + iPackageManager.isPackageAvailable(it, userId) + }.toSet() + + private fun getPackageInfoAsUser(packageName: String, flags: Int, userId: Int): PackageInfo? = + try { + PackageManager.getPackageInfoAsUserCached(packageName, flags.toLong(), userId) + } catch (e: PackageManager.NameNotFoundException) { + Log.w(TAG, "getPackageInfoAsUserCached() failed", e) + null + } + + private fun Int.hasFlag(flag: Int) = (this and flag) > 0 } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt index 99deb707a351..f51d2dbfdeef 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt @@ -49,7 +49,8 @@ fun AppInfo(packageName: String, userId: Int) { ), horizontalAlignment = Alignment.CenterHorizontally, ) { - val packageInfo = remember { PackageManagers.getPackageInfoAsUser(packageName, userId) } + val packageInfo = + remember { PackageManagers.getPackageInfoAsUser(packageName, userId) } ?: return Box(modifier = Modifier.padding(SettingsDimension.itemPaddingAround)) { AppIcon(app = packageInfo.applicationInfo, size = SettingsDimension.appIconInfoSize) } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt new file mode 100644 index 000000000000..c6f41d3bd3e2 --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2022 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.settingslib.spaprivileged.template.app + +import android.app.AppOpsManager.MODE_ALLOWED +import android.app.AppOpsManager.MODE_DEFAULT +import android.content.Context +import android.content.pm.ApplicationInfo +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.remember +import com.android.settingslib.spaprivileged.model.app.AppOpsController +import com.android.settingslib.spaprivileged.model.app.AppRecord +import com.android.settingslib.spaprivileged.model.app.PackageManagers +import com.android.settingslib.spaprivileged.model.app.PackageManagers.hasGrantPermission +import com.android.settingslib.spaprivileged.model.app.PackageManagers.hasRequestPermission +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map + +data class AppOpPermissionRecord( + override val app: ApplicationInfo, + val hasRequestPermission: Boolean, + var appOpsController: AppOpsController, +) : AppRecord + +abstract class AppOpPermissionListModel(private val context: Context) : + TogglePermissionAppListModel<AppOpPermissionRecord> { + + abstract val appOp: Int + abstract val permission: String + + private val notChangeablePackages = + setOf("android", "com.android.systemui", context.packageName) + + override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) = + userIdFlow.map { userId -> + PackageManagers.getAppOpPermissionPackages(userId, permission) + }.combine(appListFlow) { packageNames, appList -> + appList.map { app -> + AppOpPermissionRecord( + app = app, + hasRequestPermission = app.packageName in packageNames, + appOpsController = AppOpsController(context = context, app = app, op = appOp), + ) + } + } + + override fun transformItem(app: ApplicationInfo) = AppOpPermissionRecord( + app = app, + hasRequestPermission = app.hasRequestPermission(permission), + appOpsController = AppOpsController(context = context, app = app, op = appOp), + ) + + override fun filter(userIdFlow: Flow<Int>, recordListFlow: Flow<List<AppOpPermissionRecord>>) = + recordListFlow.map { recordList -> + recordList.filter { it.hasRequestPermission } + } + + /** + * Defining the default behavior as permissible as long as the package requested this permission + * (This means pre-M gets approval during install time; M apps gets approval during runtime). + */ + @Composable + override fun isAllowed(record: AppOpPermissionRecord): State<Boolean?> { + val mode = record.appOpsController.mode.observeAsState() + return remember { + derivedStateOf { + when (mode.value) { + null -> null + MODE_ALLOWED -> true + MODE_DEFAULT -> record.app.hasGrantPermission(permission) + else -> false + } + } + } + } + + override fun isChangeable(record: AppOpPermissionRecord) = + record.hasRequestPermission && record.app.packageName !in notChangeablePackages + + override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) { + record.appOpsController.setAllowed(newAllowed) + } +} |