diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppOpsController.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppOpsController.kt new file mode 100644 index 0000000000000..3808f64386ef4 --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppOpsController.kt @@ -0,0 +1,55 @@ +/* + * 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.framework.app + +import android.app.AppOpsManager +import android.app.AppOpsManager.MODE_ALLOWED +import android.app.AppOpsManager.MODE_ERRORED +import android.app.AppOpsManager.Mode +import android.content.Context +import android.content.pm.ApplicationInfo +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData + +class AppOpsController( + context: Context, + private val app: ApplicationInfo, + private val op: Int, +) { + private val appOpsManager = checkNotNull(context.getSystemService(AppOpsManager::class.java)) + + val isAllowed: LiveData + get() = _isAllowed + + 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 + private fun getMode(): Int = appOpsManager.checkOpNoThrow(op, app.uid, app.packageName) + + private val _isAllowed = object : MutableLiveData() { + override fun onActive() { + postValue(getMode() == MODE_ALLOWED) + } + + override fun onInactive() { + } + } +} diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt new file mode 100644 index 0000000000000..8dde897cb23d1 --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt @@ -0,0 +1,23 @@ +/* + * 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.framework.app + +import android.content.pm.ApplicationInfo + +interface AppRecord { + val app: ApplicationInfo +} diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/Apps.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/Apps.kt new file mode 100644 index 0000000000000..f6755459c1b79 --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/Apps.kt @@ -0,0 +1,25 @@ +/* + * 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.framework.app + +import android.content.pm.ApplicationInfo +import android.os.UserHandle + +val ApplicationInfo.userId: Int + get() = UserHandle.getUserId(uid) + +fun ApplicationInfo.toRoute() = "$packageName/$userId" diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/PackageManagers.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/PackageManagers.kt index 5a3e66619c396..66b05da03b3f0 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/PackageManagers.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/PackageManagers.kt @@ -16,10 +16,14 @@ package com.android.settingslib.spaprivileged.framework.app +import android.content.pm.ApplicationInfo import android.content.pm.PackageInfo import android.content.pm.PackageManager object PackageManagers { fun getPackageInfoAsUser(packageName: String, userId: Int): PackageInfo = PackageManager.getPackageInfoAsUserCached(packageName, 0, userId) + + fun getApplicationInfoAsUser(packageName: String, userId: Int): ApplicationInfo = + PackageManager.getApplicationInfoAsUserCached(packageName, 0, userId) } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt new file mode 100644 index 0000000000000..06d7547be3097 --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt @@ -0,0 +1,53 @@ +/* + * 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 androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.widget.ui.Footer + +@Composable +fun AppInfoPage( + title: String, + packageName: String, + userId: Int, + footerText: String, + content: @Composable () -> Unit, +) { + // TODO: Replace with SettingsScaffold + Column(Modifier.verticalScroll(rememberScrollState())) { + Text( + text = title, + modifier = Modifier.padding(SettingsDimension.itemPadding), + color = MaterialTheme.colorScheme.onSurface, + style = MaterialTheme.typography.headlineMedium, + ) + + AppInfo(packageName, userId) + + content() + + Footer(footerText) + } +} diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt new file mode 100644 index 0000000000000..57e9e9ac1b9bc --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt @@ -0,0 +1,121 @@ +/* + * 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.content.Context +import android.os.Bundle +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.navigation.NavType +import androidx.navigation.navArgument +import com.android.settingslib.spa.framework.api.SettingsPageProvider +import com.android.settingslib.spa.framework.compose.rememberContext +import com.android.settingslib.spa.widget.preference.SwitchPreference +import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel +import com.android.settingslib.spaprivileged.framework.app.AppRecord +import com.android.settingslib.spaprivileged.framework.app.PackageManagers +import kotlinx.coroutines.Dispatchers + +private const val PERMISSION = "permission" +private const val PACKAGE_NAME = "packageName" +private const val USER_ID = "userId" + +open class TogglePermissionAppInfoPageProvider( + private val factory: TogglePermissionAppListModelFactory, +) : SettingsPageProvider { + override val name = "TogglePermissionAppInfoPage" + + override val arguments = listOf( + navArgument(PERMISSION) { type = NavType.StringType }, + navArgument(PACKAGE_NAME) { type = NavType.StringType }, + navArgument(USER_ID) { type = NavType.IntType }, + ) + + @Composable + override fun Page(arguments: Bundle?) { + checkNotNull(arguments) + val permission = checkNotNull(arguments.getString(PERMISSION)) + val packageName = checkNotNull(arguments.getString(PACKAGE_NAME)) + val userId = arguments.getInt(USER_ID) + val listModel = rememberContext { context -> factory.createModel(permission, context) } + TogglePermissionAppInfoPage(listModel, packageName, userId) + } + + fun getRoute(permissionType: String, packageName: String, userId: Int): String = + "$name/$permissionType/$packageName/$userId" +} + +@Composable +private fun TogglePermissionAppInfoPage( + listModel: TogglePermissionAppListModel, + packageName: String, + userId: Int, +) { + AppInfoPage( + title = stringResource(listModel.pageTitleResId), + packageName = packageName, + userId = userId, + footerText = stringResource(listModel.footerResId), + ) { + val model = createSwitchModel(listModel, packageName, userId) + LaunchedEffect(model, Dispatchers.Default) { + model.initState() + } + SwitchPreference(model) + } +} + +@Composable +private fun createSwitchModel( + listModel: TogglePermissionAppListModel, + packageName: String, + userId: Int, +): TogglePermissionSwitchModel { + val record = remember { + val app = PackageManagers.getApplicationInfoAsUser(packageName, userId) + listModel.transformItem(app) + } + val context = LocalContext.current + val isAllowed = listModel.isAllowed(record) + return remember { + TogglePermissionSwitchModel(context, listModel, record, isAllowed) + } +} + +private class TogglePermissionSwitchModel( + context: Context, + private val listModel: TogglePermissionAppListModel, + private val record: T, + isAllowed: State, +) : SwitchPreferenceModel { + override val title: String = context.getString(listModel.switchTitleResId) + override val checked = isAllowed + override val changeable = mutableStateOf(true) + + fun initState() { + changeable.value = listModel.isChangeable(record) + } + + override val onCheckedChange: (Boolean) -> Unit = { newChecked -> + listModel.setAllowed(record, newChecked) + } +} diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListModel.kt new file mode 100644 index 0000000000000..88ad9daa85b3e --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListModel.kt @@ -0,0 +1,44 @@ +/* + * 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.content.Context +import android.content.pm.ApplicationInfo +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import com.android.settingslib.spaprivileged.framework.app.AppRecord + +interface TogglePermissionAppListModel { + val pageTitleResId: Int + val switchTitleResId: Int + val footerResId: Int + + fun transformItem(app: ApplicationInfo): T + + @Composable + fun isAllowed(record: T): State + + fun isChangeable(record: T): Boolean + fun setAllowed(record: T, newAllowed: Boolean) +} + +interface TogglePermissionAppListModelFactory { + fun createModel( + permission: String, + context: Context, + ): TogglePermissionAppListModel +}