Make AccessCheckingService a SystemService.
And connect it with the existing parts of the system, so that we can remove all the stub classes created for compilation. The new implementations of the app op and permission service interface are now published via LocalServices and available in services-core. The external states are now actually available, including the package states being provided by PackageManagerLocal. The callbacks are temporarily being delivered by the new permission service because only it has the blocking callbacks at the right timing during package changes. A new intialize() function is added for the new app op and permission services. It is called when the core system services (including ours) have read the on-disk state and are ready, so that our services can now retrieve their dependent services and start working. Any call to the new services before this call can't have meaningful results and should be no-op'ed in some way. Bug: 182523293 Test: presubmit Change-Id: I9c4ec4b7d56ec9d97c4b097cacc7680fa0a08690
This commit is contained in:
@@ -16,11 +16,23 @@
|
||||
|
||||
package com.android.server.permission.access
|
||||
|
||||
import android.content.Context
|
||||
import com.android.internal.annotations.Keep
|
||||
import com.android.server.permission.access.external.PackageState
|
||||
import com.android.server.LocalManagerRegistry
|
||||
import com.android.server.LocalServices
|
||||
import com.android.server.SystemService
|
||||
import com.android.server.appop.AppOpsCheckingServiceInterface
|
||||
import com.android.server.permission.access.appop.AppOpService
|
||||
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
|
||||
import com.android.server.permission.access.permission.PermissionService
|
||||
import com.android.server.pm.PackageManagerLocal
|
||||
import com.android.server.pm.UserManagerService
|
||||
import com.android.server.pm.permission.PermissionManagerServiceInterface
|
||||
import com.android.server.pm.permission.PermissionManagerServiceInternal
|
||||
import com.android.server.pm.pkg.PackageState
|
||||
|
||||
@Keep
|
||||
class AccessCheckingService {
|
||||
class AccessCheckingService(context: Context) : SystemService(context) {
|
||||
@Volatile
|
||||
private lateinit var state: AccessState
|
||||
private val stateLock = Any()
|
||||
@@ -29,14 +41,35 @@ class AccessCheckingService {
|
||||
|
||||
private val persistence = AccessPersistence(policy)
|
||||
|
||||
fun init() {
|
||||
private lateinit var appOpService: AppOpService
|
||||
private lateinit var permissionService: PermissionService
|
||||
|
||||
private lateinit var packageManagerLocal: PackageManagerLocal
|
||||
private lateinit var userManagerService: UserManagerService
|
||||
|
||||
override fun onStart() {
|
||||
appOpService = AppOpService(this)
|
||||
permissionService = PermissionService(this)
|
||||
|
||||
LocalServices.addService(AppOpsCheckingServiceInterface::class.java, appOpService)
|
||||
LocalServices.addService(PermissionManagerServiceInterface::class.java, permissionService)
|
||||
}
|
||||
|
||||
fun initialize() {
|
||||
packageManagerLocal =
|
||||
LocalManagerRegistry.getManagerOrThrow(PackageManagerLocal::class.java)
|
||||
userManagerService = UserManagerService.getInstance()
|
||||
|
||||
val userIds = IntSet(userManagerService.userIdsIncludingPreCreated)
|
||||
val packageStates = packageManagerLocal.packageStates
|
||||
|
||||
val state = AccessState()
|
||||
state.systemState.userIds.apply {
|
||||
// TODO: Get and add all user IDs.
|
||||
// TODO: Maybe get and add all packages?
|
||||
}
|
||||
policy.initialize(state, userIds, packageStates)
|
||||
persistence.read(state)
|
||||
this.state = state
|
||||
|
||||
appOpService.initialize()
|
||||
permissionService.initialize()
|
||||
}
|
||||
|
||||
fun getDecision(subject: AccessUri, `object`: AccessUri): Int =
|
||||
@@ -50,30 +83,60 @@ class AccessCheckingService {
|
||||
}
|
||||
}
|
||||
|
||||
fun onUserAdded(userId: Int) {
|
||||
internal fun onUserAdded(userId: Int) {
|
||||
mutateState {
|
||||
with(policy) { onUserAdded(userId) }
|
||||
}
|
||||
}
|
||||
|
||||
fun onUserRemoved(userId: Int) {
|
||||
internal fun onUserRemoved(userId: Int) {
|
||||
mutateState {
|
||||
with(policy) { onUserRemoved(userId) }
|
||||
}
|
||||
}
|
||||
|
||||
fun onPackageAdded(packageState: PackageState) {
|
||||
internal fun onStorageVolumeMounted(volumeUuid: String?, isSystemUpdated: Boolean) {
|
||||
val packageStates = packageManagerLocal.packageStates
|
||||
mutateState {
|
||||
with(policy) { onPackageAdded(packageState) }
|
||||
with(policy) { onStorageVolumeMounted(packageStates, volumeUuid, isSystemUpdated) }
|
||||
}
|
||||
}
|
||||
|
||||
fun onPackageRemoved(packageState: PackageState) {
|
||||
internal fun onPackageAdded(packageName: String) {
|
||||
val packageStates = packageManagerLocal.packageStates
|
||||
mutateState {
|
||||
with(policy) { onPackageRemoved(packageState) }
|
||||
with(policy) { onPackageAdded(packageStates, packageName) }
|
||||
}
|
||||
}
|
||||
|
||||
internal fun onPackageRemoved(packageName: String, appId: Int) {
|
||||
val packageStates = packageManagerLocal.packageStates
|
||||
mutateState {
|
||||
with(policy) { onPackageRemoved(packageStates, packageName, appId) }
|
||||
}
|
||||
}
|
||||
|
||||
internal fun onPackageInstalled(
|
||||
packageName: String,
|
||||
params: PermissionManagerServiceInternal.PackageInstalledParams,
|
||||
userId: Int
|
||||
) {
|
||||
val packageStates = packageManagerLocal.packageStates
|
||||
mutateState {
|
||||
with(policy) { onPackageInstalled(packageStates, packageName, params, userId) }
|
||||
}
|
||||
}
|
||||
|
||||
internal fun onPackageUninstalled(packageName: String, appId: Int, userId: Int) {
|
||||
val packageStates = packageManagerLocal.packageStates
|
||||
mutateState {
|
||||
with(policy) { onPackageUninstalled(packageStates, packageName, appId, userId) }
|
||||
}
|
||||
}
|
||||
|
||||
private val PackageManagerLocal.packageStates: Map<String, PackageState>
|
||||
get() = withUnfilteredSnapshot().use { it.packageStates }
|
||||
|
||||
internal inline fun <T> getState(action: GetStateScope.() -> T): T =
|
||||
GetStateScope(state).action()
|
||||
|
||||
|
||||
@@ -22,11 +22,12 @@ import com.android.modules.utils.BinaryXmlSerializer
|
||||
import com.android.server.permission.access.appop.PackageAppOpPolicy
|
||||
import com.android.server.permission.access.appop.UidAppOpPolicy
|
||||
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
|
||||
import com.android.server.permission.access.external.PackageState
|
||||
import com.android.server.permission.access.permission.UidPermissionPolicy
|
||||
import com.android.server.permission.access.util.forEachTag
|
||||
import com.android.server.permission.access.util.tag
|
||||
import com.android.server.permission.access.util.tagName
|
||||
import com.android.server.pm.permission.PermissionManagerServiceInternal
|
||||
import com.android.server.pm.pkg.PackageState
|
||||
|
||||
class AccessPolicy private constructor(
|
||||
private val schemePolicies: IndexedMap<String, IndexedMap<String, SchemePolicy>>
|
||||
@@ -53,6 +54,17 @@ class AccessPolicy private constructor(
|
||||
with(getSchemePolicy(subject, `object`)) { setDecision(subject, `object`, decision) }
|
||||
}
|
||||
|
||||
fun initialize(state: AccessState, userIds: IntSet, packageStates: Map<String, PackageState>) {
|
||||
state.systemState.apply {
|
||||
this.userIds += userIds
|
||||
this.packageStates = packageStates
|
||||
packageStates.forEach { (_, packageState) ->
|
||||
appIds.getOrPut(packageState.appId) { IndexedListSet() }
|
||||
.add(packageState.packageName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun MutateStateScope.onUserAdded(userId: Int) {
|
||||
newState.systemState.userIds += userId
|
||||
newState.userStates[userId] = UserState()
|
||||
@@ -69,18 +81,34 @@ class AccessPolicy private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun MutateStateScope.onPackageAdded(packageState: PackageState) {
|
||||
var isAppIdAdded = false
|
||||
newState.systemState.apply {
|
||||
packageStates[packageState.packageName] = packageState
|
||||
appIds.getOrPut(packageState.appId) {
|
||||
isAppIdAdded = true
|
||||
IndexedListSet()
|
||||
}.add(packageState.packageName)
|
||||
fun MutateStateScope.onStorageVolumeMounted(
|
||||
packageStates: Map<String, PackageState>,
|
||||
volumeUuid: String?,
|
||||
isSystemUpdated: Boolean
|
||||
) {
|
||||
newState.systemState.packageStates = packageStates
|
||||
forEachSchemePolicy {
|
||||
with(it) { onStorageVolumeMounted(volumeUuid, isSystemUpdated) }
|
||||
}
|
||||
}
|
||||
|
||||
fun MutateStateScope.onPackageAdded(
|
||||
packageStates: Map<String, PackageState>,
|
||||
packageName: String
|
||||
) {
|
||||
newState.systemState.packageStates = packageStates
|
||||
var isAppIdAdded = false
|
||||
val packageState = packageStates[packageName]
|
||||
// TODO(zhanghai): Remove check before submission.
|
||||
checkNotNull(packageState)
|
||||
val appId = packageState.appId
|
||||
newState.systemState.appIds.getOrPut(appId) {
|
||||
isAppIdAdded = true
|
||||
IndexedListSet()
|
||||
}.add(packageName)
|
||||
if (isAppIdAdded) {
|
||||
forEachSchemePolicy {
|
||||
with(it) { onAppIdAdded(packageState.appId) }
|
||||
with(it) { onAppIdAdded(appId) }
|
||||
}
|
||||
}
|
||||
forEachSchemePolicy {
|
||||
@@ -88,30 +116,61 @@ class AccessPolicy private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun MutateStateScope.onPackageRemoved(packageState: PackageState) {
|
||||
fun MutateStateScope.onPackageRemoved(
|
||||
packageStates: Map<String, PackageState>,
|
||||
packageName: String,
|
||||
appId: Int
|
||||
) {
|
||||
newState.systemState.packageStates = packageStates
|
||||
var isAppIdRemoved = false
|
||||
newState.systemState.apply {
|
||||
packageStates -= packageState.packageName
|
||||
appIds.apply appIds@{
|
||||
this[packageState.appId]?.apply {
|
||||
this -= packageState.packageName
|
||||
if (isEmpty()) {
|
||||
this@appIds -= packageState.appId
|
||||
isAppIdRemoved = true
|
||||
}
|
||||
// TODO(zhanghai): Remove check before submission.
|
||||
check(packageName !in packageStates)
|
||||
newState.systemState.appIds.apply appIds@{
|
||||
this[appId]?.apply {
|
||||
this -= packageName
|
||||
if (isEmpty()) {
|
||||
this@appIds -= appId
|
||||
isAppIdRemoved = true
|
||||
}
|
||||
}
|
||||
}
|
||||
forEachSchemePolicy {
|
||||
with(it) { onPackageRemoved(packageState) }
|
||||
with(it) { onPackageRemoved(packageName, appId) }
|
||||
}
|
||||
if (isAppIdRemoved) {
|
||||
forEachSchemePolicy {
|
||||
with(it) { onAppIdRemoved(packageState.appId) }
|
||||
with(it) { onAppIdRemoved(appId) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun MutateStateScope.onPackageInstalled(
|
||||
packageStates: Map<String, PackageState>,
|
||||
packageName: String,
|
||||
params: PermissionManagerServiceInternal.PackageInstalledParams,
|
||||
userId: Int
|
||||
) {
|
||||
newState.systemState.packageStates = packageStates
|
||||
val packageState = packageStates[packageName]
|
||||
// TODO(zhanghai): Remove check before submission.
|
||||
checkNotNull(packageState)
|
||||
forEachSchemePolicy {
|
||||
with(it) { onPackageInstalled(packageState, params, userId) }
|
||||
}
|
||||
}
|
||||
|
||||
fun MutateStateScope.onPackageUninstalled(
|
||||
packageStates: Map<String, PackageState>,
|
||||
packageName: String,
|
||||
appId: Int,
|
||||
userId: Int
|
||||
) {
|
||||
newState.systemState.packageStates = packageStates
|
||||
forEachSchemePolicy {
|
||||
with(it) { onPackageUninstalled(packageName, appId, userId) }
|
||||
}
|
||||
}
|
||||
|
||||
fun BinaryXmlPullParser.parseSystemState(systemState: SystemState) {
|
||||
forEachTag {
|
||||
when (tagName) {
|
||||
@@ -230,9 +289,22 @@ abstract class SchemePolicy {
|
||||
|
||||
open fun MutateStateScope.onAppIdRemoved(appId: Int) {}
|
||||
|
||||
open fun MutateStateScope.onStorageVolumeMounted(
|
||||
volumeUuid: String?,
|
||||
isSystemUpdated: Boolean
|
||||
) {}
|
||||
|
||||
open fun MutateStateScope.onPackageAdded(packageState: PackageState) {}
|
||||
|
||||
open fun MutateStateScope.onPackageRemoved(packageState: PackageState) {}
|
||||
open fun MutateStateScope.onPackageRemoved(packageName: String, appId: Int) {}
|
||||
|
||||
open fun MutateStateScope.onPackageInstalled(
|
||||
packageState: PackageState,
|
||||
params: PermissionManagerServiceInternal.PackageInstalledParams,
|
||||
userId: Int
|
||||
) {}
|
||||
|
||||
open fun MutateStateScope.onPackageUninstalled(packageName: String, appId: Int, userId: Int) {}
|
||||
|
||||
open fun BinaryXmlPullParser.parseSystemState(systemState: SystemState) {}
|
||||
|
||||
|
||||
@@ -18,8 +18,8 @@ package com.android.server.permission.access
|
||||
|
||||
import android.content.pm.PermissionGroupInfo
|
||||
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
|
||||
import com.android.server.permission.access.data.Permission
|
||||
import com.android.server.permission.access.external.PackageState
|
||||
import com.android.server.permission.access.permission.Permission
|
||||
import com.android.server.pm.pkg.PackageState
|
||||
|
||||
class AccessState private constructor(
|
||||
val systemState: SystemState,
|
||||
@@ -32,8 +32,8 @@ class AccessState private constructor(
|
||||
|
||||
class SystemState private constructor(
|
||||
val userIds: IntSet,
|
||||
val packageStates: IndexedMap<String, PackageState>,
|
||||
val disabledSystemPackageStates: IndexedMap<String, PackageState>,
|
||||
var packageStates: Map<String, PackageState>,
|
||||
var disabledSystemPackageStates: Map<String, PackageState>,
|
||||
val appIds: IntMap<IndexedListSet<String>>,
|
||||
// A map of KnownPackagesInt to a set of known package names
|
||||
val knownPackages: IntMap<IndexedListSet<String>>,
|
||||
@@ -59,7 +59,7 @@ class SystemState private constructor(
|
||||
val permissions: IndexedMap<String, Permission>
|
||||
) : WritableState() {
|
||||
constructor() : this(
|
||||
IntSet(), IndexedMap(), IndexedMap(), IntMap(), IntMap(), IntMap(), IndexedMap(),
|
||||
IntSet(), emptyMap(), emptyMap(), IntMap(), IntMap(), IntMap(), IndexedMap(),
|
||||
IndexedListSet(), IndexedMap(), IndexedMap(), IndexedMap(), IndexedMap(), IndexedMap(),
|
||||
IndexedMap(), IndexedMap(), IndexedMap(), IndexedMap(), IndexedMap(), IndexedMap(),
|
||||
IndexedMap(), IndexedMap(), IndexedMap()
|
||||
@@ -68,8 +68,8 @@ class SystemState private constructor(
|
||||
fun copy(): SystemState =
|
||||
SystemState(
|
||||
userIds.copy(),
|
||||
packageStates.copy { it },
|
||||
disabledSystemPackageStates.copy { it },
|
||||
packageStates,
|
||||
disabledSystemPackageStates,
|
||||
appIds.copy { it.copy() },
|
||||
knownPackages.copy { it.copy() },
|
||||
deviceAndProfileOwners.copy { it },
|
||||
|
||||
@@ -16,8 +16,7 @@
|
||||
|
||||
package com.android.server.permission.access
|
||||
|
||||
import com.android.server.permission.access.external.UserHandle
|
||||
import com.android.server.permission.access.external.UserHandleCompat
|
||||
import android.os.UserHandle
|
||||
|
||||
sealed class AccessUri(
|
||||
val scheme: String
|
||||
@@ -70,7 +69,7 @@ data class UidUri(
|
||||
val uid: Int
|
||||
) : AccessUri(SCHEME) {
|
||||
val userId: Int
|
||||
get() = UserHandleCompat.getUserId(uid)
|
||||
get() = UserHandle.getUserId(uid)
|
||||
|
||||
val appId: Int
|
||||
get() = UserHandle.getAppId(uid)
|
||||
|
||||
@@ -24,9 +24,13 @@ import com.android.server.appop.OnOpModeChangedListener
|
||||
import com.android.server.permission.access.AccessCheckingService
|
||||
import java.io.PrintWriter
|
||||
|
||||
class AppOpsCheckingServiceCompatImpl(
|
||||
private val accessCheckingService: AccessCheckingService
|
||||
class AppOpService(
|
||||
private val service: AccessCheckingService
|
||||
) : AppOpsCheckingServiceInterface {
|
||||
fun initialize() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun getNonDefaultUidModes(uid: Int): SparseIntArray {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
@@ -139,6 +143,6 @@ class AppOpsCheckingServiceCompatImpl(
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOG_TAG = AppOpsCheckingServiceCompatImpl::class.java.simpleName
|
||||
private val LOG_TAG = AppOpService::class.java.simpleName
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,6 @@ import com.android.server.permission.access.MutateStateScope
|
||||
import com.android.server.permission.access.PackageUri
|
||||
import com.android.server.permission.access.UserState
|
||||
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
|
||||
import com.android.server.permission.access.external.PackageState
|
||||
|
||||
class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) {
|
||||
override val subjectScheme: String
|
||||
@@ -48,9 +47,9 @@ class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) {
|
||||
newState.userStates[subject.userId]?.packageAppOpModes?.remove(subject.packageName)
|
||||
}
|
||||
|
||||
override fun MutateStateScope.onPackageRemoved(packageState: PackageState) {
|
||||
override fun MutateStateScope.onPackageRemoved(packageName: String, appId: Int) {
|
||||
newState.userStates.forEachIndexed { _, _, userState ->
|
||||
userState.packageAppOpModes -= packageState.packageName
|
||||
userState.packageAppOpModes -= packageName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,8 @@ class IntSet private constructor(
|
||||
fun copy(): IntSet = IntSet(array.clone())
|
||||
}
|
||||
|
||||
fun IntSet(values: IntArray): IntSet = IntSet().apply{ this += values }
|
||||
|
||||
inline fun IntSet.allIndexed(predicate: (Int, Int) -> Boolean): Boolean {
|
||||
for (index in 0 until size) {
|
||||
if (!predicate(index, elementAt(index))) {
|
||||
@@ -103,6 +105,14 @@ inline operator fun IntSet.plusAssign(element: Int) {
|
||||
add(element)
|
||||
}
|
||||
|
||||
operator fun IntSet.plusAssign(set: IntSet) {
|
||||
set.forEachIndexed { _, it -> this += it }
|
||||
}
|
||||
|
||||
operator fun IntSet.plusAssign(array: IntArray) {
|
||||
array.forEach { this += it }
|
||||
}
|
||||
|
||||
inline fun IntSet.removeAllIndexed(predicate: (Int, Int) -> Boolean) {
|
||||
for (index in lastIndex downTo 0) {
|
||||
if (predicate(index, elementAt(index))) {
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.server.permission.access.data
|
||||
|
||||
import com.android.server.permission.access.external.AndroidPackage
|
||||
|
||||
class Package(
|
||||
private val androidPackage: AndroidPackage
|
||||
) {
|
||||
val name: String
|
||||
get() = androidPackage.packageName
|
||||
|
||||
val adoptPermissions: List<String>
|
||||
get() = androidPackage.adoptPermissions
|
||||
|
||||
val appId: Int
|
||||
get() = androidPackage.appId
|
||||
|
||||
val requestedPermissions: List<String>
|
||||
get() = androidPackage.requestedPermissions
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
throw NotImplementedError()
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
throw NotImplementedError()
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* 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.server.permission.access.external
|
||||
|
||||
class CompatibilityPermissionInfo {
|
||||
companion object {
|
||||
val COMPAT_PERMS = arrayOf(CompatibilityPermissionInfo())
|
||||
}
|
||||
|
||||
val name: String
|
||||
get() = throw NotImplementedError()
|
||||
|
||||
val sdkVersion: Int
|
||||
get() = throw NotImplementedError()
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
* 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.server.permission.access.external
|
||||
|
||||
class KnownPackages {
|
||||
companion object {
|
||||
const val PACKAGE_SYSTEM = 0
|
||||
const val PACKAGE_SETUP_WIZARD = 1
|
||||
const val PACKAGE_INSTALLER = 2
|
||||
const val PACKAGE_VERIFIER = 4
|
||||
const val PACKAGE_SYSTEM_TEXT_CLASSIFIER = 6
|
||||
const val PACKAGE_PERMISSION_CONTROLLER = 7
|
||||
const val PACKAGE_CONFIGURATOR = 10
|
||||
const val PACKAGE_INCIDENT_REPORT_APPROVER = 11
|
||||
const val PACKAGE_APP_PREDICTOR = 12
|
||||
const val PACKAGE_COMPANION = 15
|
||||
const val PACKAGE_RETAIL_DEMO = 16
|
||||
const val PACKAGE_RECENTS = 17
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.server.permission.access.external
|
||||
|
||||
import android.content.pm.PermissionGroupInfo
|
||||
import android.content.pm.PermissionInfo
|
||||
|
||||
object PackageInfoUtils {
|
||||
fun generatePermissionInfo(parsedPermission: ParsedPermission, flags: Long): PermissionInfo {
|
||||
throw NotImplementedError()
|
||||
}
|
||||
|
||||
fun generatePermissionGroupInfo(
|
||||
parsedPermissionGroup: ParsedPermissionGroup,
|
||||
flags: Long
|
||||
): PermissionGroupInfo {
|
||||
throw NotImplementedError()
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.server.permission.access.external
|
||||
|
||||
import android.util.SparseArray
|
||||
|
||||
interface PackageState {
|
||||
val androidPackage: AndroidPackage?
|
||||
val appId: Int
|
||||
val isSystem: Boolean
|
||||
val isUpdatedSystemApp: Boolean
|
||||
val packageName: String
|
||||
val userStates: SparseArray<PackageUserState>
|
||||
val hasSharedUser: Boolean
|
||||
val sharedUserAppId: Int
|
||||
val signingDetails: SigningDetails
|
||||
}
|
||||
|
||||
interface AndroidPackage {
|
||||
val packageName: String
|
||||
val apexModuleName: String?
|
||||
val appId: Int
|
||||
val isPrivileged: Boolean
|
||||
val isOem: Boolean
|
||||
val isVendor: Boolean
|
||||
val isProduct: Boolean
|
||||
val isSystemExt: Boolean
|
||||
val targetSdkVersion: Int
|
||||
val adoptPermissions: List<String>
|
||||
val permissions: List<ParsedPermission>
|
||||
val permissionGroups: List<ParsedPermissionGroup>
|
||||
val requestedPermissions: List<String>
|
||||
val implicitPermissions: List<String>
|
||||
}
|
||||
|
||||
interface ParsedPermission {
|
||||
val name: String
|
||||
val isTree: Boolean
|
||||
val packageName: String
|
||||
val isSignature: Boolean
|
||||
val protectionLevel: Int
|
||||
}
|
||||
|
||||
interface ParsedPermissionGroup {
|
||||
val name: String
|
||||
val packageName: String
|
||||
}
|
||||
|
||||
interface PackageUserState {
|
||||
val isInstantApp: Boolean
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* 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.server.permission.access.external
|
||||
|
||||
class RoSystemProperties {
|
||||
companion object {
|
||||
const val CONTROL_PRIVAPP_PERMISSIONS_DISABLE = false
|
||||
const val CONTROL_PRIVAPP_PERMISSIONS_ENFORCE = false
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* 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.server.permission.access.external
|
||||
|
||||
object SigningDetails {
|
||||
fun hasCommonSignerWithCapability(otherDetails: SigningDetails, flags: Int): Boolean {
|
||||
throw NotImplementedError()
|
||||
}
|
||||
|
||||
fun hasAncestorOrSelf(oldDetails: SigningDetails): Boolean {
|
||||
throw NotImplementedError()
|
||||
}
|
||||
|
||||
fun checkCapability(oldDetails: SigningDetails, flags: Int): Boolean {
|
||||
throw NotImplementedError()
|
||||
}
|
||||
|
||||
fun hasAncestorOrSelfWithDigest(certDigests: Set<String>): Boolean {
|
||||
throw NotImplementedError()
|
||||
}
|
||||
|
||||
class CertCapabilities {
|
||||
companion object {
|
||||
/** grant SIGNATURE permissions to pkgs with this cert */
|
||||
var PERMISSION = 4
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.server.permission.access.external
|
||||
|
||||
interface UserHandle {
|
||||
companion object {
|
||||
fun getAppId(uid: Int): Int {
|
||||
throw NotImplementedError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object UserHandleCompat {
|
||||
fun getUserId(uid: Int): Int {
|
||||
throw NotImplementedError()
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.permission.access.data
|
||||
package com.android.server.permission.access.permission
|
||||
|
||||
import android.content.pm.PermissionInfo
|
||||
import com.android.server.permission.access.util.hasBits
|
||||
@@ -33,8 +33,9 @@ import com.android.server.pm.permission.PermissionManagerServiceInterface
|
||||
import com.android.server.permission.access.AccessCheckingService
|
||||
import com.android.server.permission.access.PermissionUri
|
||||
import com.android.server.permission.access.UidUri
|
||||
import com.android.server.permission.access.data.Permission
|
||||
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
|
||||
import com.android.server.permission.access.util.hasBits
|
||||
import com.android.server.pm.UserManagerService
|
||||
import com.android.server.pm.permission.LegacyPermission
|
||||
import com.android.server.pm.permission.LegacyPermissionSettings
|
||||
import com.android.server.pm.permission.LegacyPermissionState
|
||||
@@ -46,17 +47,24 @@ import java.io.PrintWriter
|
||||
/**
|
||||
* Modern implementation of [PermissionManagerServiceInterface].
|
||||
*/
|
||||
class ModernPermissionManagerServiceImpl(
|
||||
class PermissionService(
|
||||
private val service: AccessCheckingService
|
||||
) : PermissionManagerServiceInterface {
|
||||
private val policy =
|
||||
service.getSchemePolicy(UidUri.SCHEME, PermissionUri.SCHEME) as UidPermissionPolicy
|
||||
|
||||
private val packageManagerInternal =
|
||||
LocalServices.getService(PackageManagerInternal::class.java)
|
||||
private lateinit var packageManagerInternal: PackageManagerInternal
|
||||
private lateinit var packageManagerLocal: PackageManagerLocal
|
||||
private lateinit var userManagerService: UserManagerService
|
||||
|
||||
private val packageManagerLocal =
|
||||
LocalManagerRegistry.getManagerOrThrow(PackageManagerLocal::class.java)
|
||||
private val mountedStorageVolumes = IndexedSet<String?>()
|
||||
|
||||
fun initialize() {
|
||||
packageManagerInternal = LocalServices.getService(PackageManagerInternal::class.java)
|
||||
packageManagerLocal =
|
||||
LocalManagerRegistry.getManagerOrThrow(PackageManagerLocal::class.java)
|
||||
userManagerService = UserManagerService.getInstance()
|
||||
}
|
||||
|
||||
override fun getAllPermissionGroups(flags: Int): List<PermissionGroupInfo> {
|
||||
TODO("Not yet implemented")
|
||||
@@ -351,6 +359,8 @@ class ModernPermissionManagerServiceImpl(
|
||||
}
|
||||
|
||||
override fun readLegacyPermissionsTEMP(legacyPermissionSettings: LegacyPermissionSettings) {
|
||||
// Package settings has been read when this method is called.
|
||||
service.initialize()
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
@@ -375,15 +385,18 @@ class ModernPermissionManagerServiceImpl(
|
||||
}
|
||||
|
||||
override fun onUserCreated(userId: Int) {
|
||||
TODO("Not yet implemented")
|
||||
service.onUserAdded(userId)
|
||||
}
|
||||
|
||||
override fun onUserRemoved(userId: Int) {
|
||||
TODO("Not yet implemented")
|
||||
service.onUserRemoved(userId)
|
||||
}
|
||||
|
||||
override fun onStorageVolumeMounted(volumeUuid: String, fingerprintChanged: Boolean) {
|
||||
TODO("Not yet implemented")
|
||||
service.onStorageVolumeMounted(volumeUuid, fingerprintChanged)
|
||||
synchronized(mountedStorageVolumes) {
|
||||
mountedStorageVolumes += volumeUuid
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPackageAdded(
|
||||
@@ -391,7 +404,18 @@ class ModernPermissionManagerServiceImpl(
|
||||
isInstantApp: Boolean,
|
||||
oldPackage: AndroidPackage?
|
||||
) {
|
||||
TODO("Not yet implemented")
|
||||
synchronized(mountedStorageVolumes) {
|
||||
if (androidPackage.volumeUuid !in mountedStorageVolumes) {
|
||||
// Wait for the storage volume to be mounted and batch the state mutation there.
|
||||
return
|
||||
}
|
||||
}
|
||||
service.onPackageAdded(androidPackage.packageName)
|
||||
}
|
||||
|
||||
override fun onPackageRemoved(androidPackage: AndroidPackage) {
|
||||
// This may not be a full removal so ignored - we'll figure out full removal in
|
||||
// onPackageUninstalled().
|
||||
}
|
||||
|
||||
override fun onPackageInstalled(
|
||||
@@ -400,21 +424,37 @@ class ModernPermissionManagerServiceImpl(
|
||||
params: PermissionManagerServiceInternal.PackageInstalledParams,
|
||||
userId: Int
|
||||
) {
|
||||
TODO("Not yet implemented")
|
||||
synchronized(mountedStorageVolumes) {
|
||||
if (androidPackage.volumeUuid !in mountedStorageVolumes) {
|
||||
// Wait for the storage volume to be mounted and batch the state mutation there.
|
||||
return
|
||||
}
|
||||
}
|
||||
val userIds = if (userId == UserHandle.USER_ALL) {
|
||||
userManagerService.userIdsIncludingPreCreated
|
||||
} else {
|
||||
intArrayOf(userId)
|
||||
}
|
||||
userIds.forEach { service.onPackageInstalled(androidPackage.packageName, params, it) }
|
||||
}
|
||||
|
||||
override fun onPackageUninstalled(
|
||||
packageName: String,
|
||||
appId: Int,
|
||||
androidPackage: AndroidPackage?,
|
||||
sharedUserPkgs: MutableList<AndroidPackage>,
|
||||
sharedUserPkgs: List<AndroidPackage>,
|
||||
userId: Int
|
||||
) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun onPackageRemoved(androidPackage: AndroidPackage) {
|
||||
TODO("Not yet implemented")
|
||||
val userIds = if (userId == UserHandle.USER_ALL) {
|
||||
userManagerService.userIdsIncludingPreCreated
|
||||
} else {
|
||||
intArrayOf(userId)
|
||||
}
|
||||
userIds.forEach { service.onPackageUninstalled(packageName, appId, it) }
|
||||
val packageState = packageManagerInternal.packageStates[packageName]
|
||||
if (packageState == null) {
|
||||
service.onPackageRemoved(packageName, appId)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -22,7 +22,6 @@ import com.android.modules.utils.BinaryXmlPullParser
|
||||
import com.android.modules.utils.BinaryXmlSerializer
|
||||
import com.android.server.permission.access.SystemState
|
||||
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
|
||||
import com.android.server.permission.access.data.Permission
|
||||
import com.android.server.permission.access.util.attribute
|
||||
import com.android.server.permission.access.util.attributeInt
|
||||
import com.android.server.permission.access.util.attributeIntHex
|
||||
|
||||
@@ -20,9 +20,11 @@ import android.Manifest
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.PermissionGroupInfo
|
||||
import android.content.pm.PermissionInfo
|
||||
import android.content.pm.SigningDetails
|
||||
import android.os.Build
|
||||
import android.os.UserHandle
|
||||
import android.util.Log
|
||||
import com.android.internal.os.RoSystemProperties
|
||||
import com.android.modules.utils.BinaryXmlPullParser
|
||||
import com.android.modules.utils.BinaryXmlSerializer
|
||||
import com.android.server.permission.access.AccessState
|
||||
@@ -35,16 +37,13 @@ import com.android.server.permission.access.SystemState
|
||||
import com.android.server.permission.access.UidUri
|
||||
import com.android.server.permission.access.UserState
|
||||
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
|
||||
import com.android.server.permission.access.data.Permission
|
||||
import com.android.server.permission.access.external.AndroidPackage
|
||||
import com.android.server.permission.access.external.CompatibilityPermissionInfo
|
||||
import com.android.server.permission.access.external.KnownPackages
|
||||
import com.android.server.permission.access.external.PackageInfoUtils
|
||||
import com.android.server.permission.access.external.PackageState
|
||||
import com.android.server.permission.access.external.RoSystemProperties
|
||||
import com.android.server.permission.access.external.SigningDetails
|
||||
import com.android.server.permission.access.util.hasAnyBit
|
||||
import com.android.server.permission.access.util.hasBits
|
||||
import com.android.server.pm.KnownPackages
|
||||
import com.android.server.pm.parsing.PackageInfoUtils
|
||||
import com.android.server.pm.permission.CompatibilityPermissionInfo
|
||||
import com.android.server.pm.pkg.AndroidPackage
|
||||
import com.android.server.pm.pkg.PackageState
|
||||
|
||||
class UidPermissionPolicy : SchemePolicy() {
|
||||
private val persistence = UidPermissionPersistence()
|
||||
@@ -75,7 +74,7 @@ class UidPermissionPolicy : SchemePolicy() {
|
||||
}
|
||||
|
||||
override fun MutateStateScope.onUserAdded(userId: Int) {
|
||||
newState.systemState.packageStates.forEachValueIndexed { _, packageState ->
|
||||
newState.systemState.packageStates.forEach { (_, packageState) ->
|
||||
evaluateAllPermissionStatesForPackageAndUser(packageState, null, userId)
|
||||
grantImplicitPermissions(packageState, userId)
|
||||
}
|
||||
@@ -179,7 +178,7 @@ class UidPermissionPolicy : SchemePolicy() {
|
||||
packageState.androidPackage!!.permissionGroups.forEachIndexed { _, parsedPermissionGroup ->
|
||||
val newPermissionGroup = PackageInfoUtils.generatePermissionGroupInfo(
|
||||
parsedPermissionGroup, PackageManager.GET_META_DATA.toLong()
|
||||
)
|
||||
)!!
|
||||
// TODO: Clear permission state on group take-over?
|
||||
val permissionGroupName = newPermissionGroup.name
|
||||
val oldPermissionGroup = newState.systemState.permissionGroups[permissionGroupName]
|
||||
@@ -211,7 +210,7 @@ class UidPermissionPolicy : SchemePolicy() {
|
||||
// }
|
||||
val newPermissionInfo = PackageInfoUtils.generatePermissionInfo(
|
||||
parsedPermission, PackageManager.GET_META_DATA.toLong()
|
||||
)
|
||||
)!!
|
||||
// TODO: newPermissionInfo.flags |= PermissionInfo.FLAG_INSTALLED
|
||||
val permissionName = newPermissionInfo.name
|
||||
val oldPermission = if (parsedPermission.isTree) {
|
||||
@@ -573,23 +572,24 @@ class UidPermissionPolicy : SchemePolicy() {
|
||||
packageState: PackageState,
|
||||
permission: Permission
|
||||
): Boolean {
|
||||
// check if the package is allow to use this signature permission. A package is allowed to
|
||||
// use a signature permission if:
|
||||
// - it has the same set of signing certificates as the source package
|
||||
// - or its signing certificate was rotated from the source package's certificate
|
||||
// - or its signing certificate is a previous signing certificate of the defining
|
||||
// package, and the defining package still trusts the old certificate for permissions
|
||||
// - or it shares a common signing certificate in its lineage with the defining package,
|
||||
// and the defining package still trusts the old certificate for permissions
|
||||
// - or it shares the above relationships with the system package
|
||||
// Check if the package is allowed to use this signature permission. A package is allowed
|
||||
// to use a signature permission if:
|
||||
// - it has the same set of signing certificates as the source package
|
||||
// - or its signing certificate was rotated from the source package's certificate
|
||||
// - or its signing certificate is a previous signing certificate of the defining
|
||||
// package, and the defining package still trusts the old certificate for permissions
|
||||
// - or it shares a common signing certificate in its lineage with the defining package,
|
||||
// and the defining package still trusts the old certificate for permissions
|
||||
// - or it shares the above relationships with the system package
|
||||
val packageSigningDetails = packageState.androidPackage!!.signingDetails
|
||||
val sourceSigningDetails = newState.systemState
|
||||
.packageStates[permission.packageName]?.signingDetails
|
||||
.packageStates[permission.packageName]?.androidPackage?.signingDetails
|
||||
val platformSigningDetails = newState.systemState
|
||||
.packageStates[PLATFORM_PACKAGE_NAME]!!.signingDetails
|
||||
return sourceSigningDetails?.hasCommonSignerWithCapability(packageState.signingDetails,
|
||||
.packageStates[PLATFORM_PACKAGE_NAME]!!.androidPackage!!.signingDetails
|
||||
return sourceSigningDetails?.hasCommonSignerWithCapability(packageSigningDetails,
|
||||
SigningDetails.CertCapabilities.PERMISSION) == true ||
|
||||
packageState.signingDetails.hasAncestorOrSelf(platformSigningDetails) ||
|
||||
platformSigningDetails.checkCapability(packageState.signingDetails,
|
||||
packageSigningDetails.hasAncestorOrSelf(platformSigningDetails) ||
|
||||
platformSigningDetails.checkCapability(packageSigningDetails,
|
||||
SigningDetails.CertCapabilities.PERMISSION)
|
||||
}
|
||||
|
||||
@@ -629,7 +629,10 @@ class UidPermissionPolicy : SchemePolicy() {
|
||||
androidPackage: AndroidPackage,
|
||||
permissionName: String
|
||||
): Boolean {
|
||||
val apexModuleName = androidPackage.apexModuleName
|
||||
// TODO(b/261913353): STOPSHIP: Add AndroidPackage.apexModuleName. The below is only for
|
||||
// passing compilation but won't actually work.
|
||||
//val apexModuleName = androidPackage.apexModuleName
|
||||
val apexModuleName = androidPackage.packageName
|
||||
val systemState = newState.systemState
|
||||
val packageName = androidPackage.packageName
|
||||
val permissionNames = when {
|
||||
@@ -657,7 +660,10 @@ class UidPermissionPolicy : SchemePolicy() {
|
||||
): Boolean {
|
||||
// Different from the previous implementation, which may incorrectly use the APEX package
|
||||
// name, we now use the APEX module name to be consistent with the allowlist.
|
||||
val apexModuleName = androidPackage.apexModuleName
|
||||
// TODO(b/261913353): STOPSHIP: Add AndroidPackage.apexModuleName. The below is only for
|
||||
// passing compilation but won't actually work.
|
||||
//val apexModuleName = androidPackage.apexModuleName
|
||||
val apexModuleName = androidPackage.packageName
|
||||
val systemState = newState.systemState
|
||||
val packageName = androidPackage.packageName
|
||||
val permissionNames = when {
|
||||
@@ -741,7 +747,7 @@ class UidPermissionPolicy : SchemePolicy() {
|
||||
return true
|
||||
}
|
||||
if (permission.isKnownSigner &&
|
||||
packageState.signingDetails.hasAncestorOrSelfWithDigest(permission.knownCerts)) {
|
||||
androidPackage.signingDetails.hasAncestorOrSelfWithDigest(permission.knownCerts)) {
|
||||
// If the permission is to be granted to a known signer then check if any of this
|
||||
// app's signing certificates are in the trusted certificate digest Set.
|
||||
return true
|
||||
@@ -840,7 +846,7 @@ class UidPermissionPolicy : SchemePolicy() {
|
||||
return uid == ownerUid
|
||||
}
|
||||
|
||||
override fun MutateStateScope.onPackageRemoved(packageState: PackageState) {
|
||||
override fun MutateStateScope.onPackageRemoved(packageName: String, appId: Int) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user