Create TogglePermissionAppInfoPage

For example, this could be used for "Install unknown apps".

Bug: 235727273
Test: Manual with Test App
Change-Id: I86227da2d5a909d07fc2526c4dce0dff7ce34509
This commit is contained in:
Chaohui Wang
2022-08-15 23:06:58 +08:00
parent bcdc02173e
commit d607f08abe
7 changed files with 325 additions and 0 deletions

View File

@@ -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<Boolean>
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<Boolean>() {
override fun onActive() {
postValue(getMode() == MODE_ALLOWED)
}
override fun onInactive() {
}
}
}

View File

@@ -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
}

View File

@@ -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"

View File

@@ -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)
}

View File

@@ -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)
}
}

View File

@@ -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<out AppRecord>,
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 <T : AppRecord> createSwitchModel(
listModel: TogglePermissionAppListModel<T>,
packageName: String,
userId: Int,
): TogglePermissionSwitchModel<T> {
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<T : AppRecord>(
context: Context,
private val listModel: TogglePermissionAppListModel<T>,
private val record: T,
isAllowed: State<Boolean?>,
) : 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)
}
}

View File

@@ -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<T : AppRecord> {
val pageTitleResId: Int
val switchTitleResId: Int
val footerResId: Int
fun transformItem(app: ApplicationInfo): T
@Composable
fun isAllowed(record: T): State<Boolean?>
fun isChangeable(record: T): Boolean
fun setAllowed(record: T, newAllowed: Boolean)
}
interface TogglePermissionAppListModelFactory {
fun createModel(
permission: String,
context: Context,
): TogglePermissionAppListModel<out AppRecord>
}