Revert "Revert "Added Untested repositories and LiveDatas""
This reverts commit 1bacae42d74cbecad28d6cdda1ade6613c670864.
Reason for revert: Fix issues with initial commit
Change-Id: I77a57aff9a8174f49f506f71810b6d0a4b6ffb05
diff --git a/PermissionController/PREUPLOAD.cfg b/PermissionController/PREUPLOAD.cfg
index 94e4640..ee31c50 100644
--- a/PermissionController/PREUPLOAD.cfg
+++ b/PermissionController/PREUPLOAD.cfg
@@ -4,4 +4,5 @@
[Hook Scripts]
checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
+ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py -f ${PREUPLOAD_FILES}
strings_lint_hook = ${REPO_ROOT}/frameworks/base/tools/stringslint/stringslint_sha.sh ${PREUPLOAD_COMMIT}
diff --git a/PermissionController/src/com/android/packageinstaller/permission/data/AppOpModeChangeListenerMultiplexer.kt b/PermissionController/src/com/android/packageinstaller/permission/data/AppOpModeChangeListenerMultiplexer.kt
new file mode 100644
index 0000000..186e43d
--- /dev/null
+++ b/PermissionController/src/com/android/packageinstaller/permission/data/AppOpModeChangeListenerMultiplexer.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2019 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.packageinstaller.permission.data
+
+import android.app.AppOpsManager
+import android.app.Application
+
+/**
+ * Listens to a particular App Op for all packages. Allows other classes to register a callback,
+ * to be notified when the app op mode is updated for a specific package.
+ *
+ * @param app: The application this multiplexer is being instantiated in
+ * @param opName: The name of the op this multiplexer will watch
+ */
+class AppOpModeChangeListenerMultiplexer(val app: Application, val opName: String)
+ : AppOpsManager.OnOpChangedListener,
+ DataRepository.InactiveTimekeeper {
+ private val appOpsManager =
+ app.applicationContext.getSystemService(AppOpsManager::class.java)
+ /**
+ * Maps a String package name to a list of listeners
+ */
+ private val callbacks = mutableMapOf<String, MutableList<OnAppOpModeChangeListener>>()
+
+ override var timeWentInactive: Long? = 0
+
+ /**
+ * Adds a listener for this op for a specific package.
+ *
+ * @param packageName the name of the package this listener wishes to be notified for
+ * @param listener the callback that will be notified
+ */
+ fun addListener(packageName: String, listener: OnAppOpModeChangeListener) {
+ val wasEmpty = callbacks.isEmpty()
+
+ callbacks.getOrPut(packageName, ::mutableListOf).add(listener)
+
+ if (wasEmpty) {
+ appOpsManager.startWatchingMode(opName, null, this)
+ }
+ }
+
+ /**
+ * Removes a listener for this specific op.
+ *
+ * @param packageName the package the listener to be removed is listening on
+ * @param listener the listener to be removed
+ */
+ fun removeListener(packageName: String, listener: OnAppOpModeChangeListener) {
+ if (callbacks[packageName]?.remove(listener) != true) {
+ return
+ }
+
+ if (callbacks[packageName]!!.isEmpty()) {
+ callbacks.remove(packageName)
+ }
+
+ if (callbacks.isEmpty()) {
+ appOpsManager.stopWatchingMode(this)
+ }
+ }
+
+ /**
+ * Callback called when the op is changed in the system.
+ *
+ * @param op the name of the op whose mode was changed (should always be the op we listened to)
+ * @param packageName the package for which the op mode was changed
+ */
+ override fun onOpChanged(op: String, packageName: String) {
+ app.applicationContext.mainExecutor.execute {
+ callbacks[packageName]?.forEach { callback ->
+ callback.onChanged(op, packageName)
+ }
+ }
+ }
+
+ override fun hasObservers(): Boolean {
+ return callbacks.isNotEmpty()
+ }
+
+ /**
+ * An interface for other classes to receive callbacks from this class
+ */
+ interface OnAppOpModeChangeListener {
+ fun onChanged(op: String, packageName: String)
+ }
+}
\ No newline at end of file
diff --git a/PermissionController/src/com/android/packageinstaller/permission/data/AppOpRepository.kt b/PermissionController/src/com/android/packageinstaller/permission/data/AppOpRepository.kt
new file mode 100644
index 0000000..1076473
--- /dev/null
+++ b/PermissionController/src/com/android/packageinstaller/permission/data/AppOpRepository.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 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.packageinstaller.permission.data
+
+import android.app.Application
+
+/**
+ * Repository for AppOpModeChangeListenerMultiplexers.
+ * Key value is a String app op name, value is its corresponding Listener.
+ */
+object AppOpRepository : DataRepository<String, AppOpModeChangeListenerMultiplexer>() {
+
+ /**
+ * Gets the AppOpChangeListenerMultiplexer associated with the given op, creating and
+ * caching it if needed.
+ *
+ * @param app: The application this is being called from
+ * @param op: The op we wish to get the listener for
+ *
+ * @return The cached or newly created AppOpModeChangeListenerMultiplexer for the given op
+ */
+ fun getAppOpChangeListener(app: Application, op: String):
+ AppOpModeChangeListenerMultiplexer {
+ return getDataObject(app, op)
+ }
+
+ override fun newValue(app: Application, op: String): AppOpModeChangeListenerMultiplexer {
+ return AppOpModeChangeListenerMultiplexer(app, op)
+ }
+}
\ No newline at end of file
diff --git a/PermissionController/src/com/android/packageinstaller/permission/data/AppOpsLiveData.kt b/PermissionController/src/com/android/packageinstaller/permission/data/AppOpsLiveData.kt
new file mode 100644
index 0000000..880edbf
--- /dev/null
+++ b/PermissionController/src/com/android/packageinstaller/permission/data/AppOpsLiveData.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2019 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.packageinstaller.permission.data
+
+import android.app.AppOpsManager
+import android.app.Application
+import android.os.UserHandle
+import androidx.lifecycle.MediatorLiveData
+
+/**
+ * Live Data representing the modes of all app ops used by a particular packagename and permission
+ * group.
+ *
+ * @param app: The application this LiveData is being instantiated in
+ * @param packageName: The name of the package this LiveData will watch for mode changes for
+ * @param permissionGroupName: The name of the permission group whose app ops this LiveData
+ * will watch
+ * @param user: The user whose App Ops will be observed
+ */
+class AppOpsLiveData(
+ private val app: Application,
+ private val packageName: String,
+ private val permissionGroupName: String,
+ user: UserHandle
+) : MediatorLiveData<Map<String, Int>>(),
+ AppOpModeChangeListenerMultiplexer.OnAppOpModeChangeListener {
+
+ private val context = app.applicationContext
+
+ /**
+ * Note- user parameter must be the same as the currently running user in order for the correct
+ * uid to be resolved.
+ */
+ private val uid = context.packageManager.getPackageUid(packageName, 0)
+
+ /**
+ * Maps an String op name to its current mode (Int).
+ */
+ private val ops = mutableMapOf<String, Int>()
+ private val appOpsManager =
+ context.getSystemService(AppOpsManager::class.java)
+ private val groupLiveData =
+ PermissionGroupRepository.getPermissionGroupLiveData(app, permissionGroupName, user)
+
+ init {
+ populateOps()
+ addSource(groupLiveData) {
+ populateOps()
+ }
+ }
+
+ override fun onChanged(op: String, packageName: String) {
+ if (packageName == this.packageName) {
+ val opMode = appOpsManager.unsafeCheckOpNoThrow(op, uid, packageName)
+ if (opMode != ops[op]) {
+ ops[op] = opMode
+ value = ops
+ }
+ }
+ }
+
+ /**
+ * Get updates for all ops in the given package name and permission group,
+ * then updates the map, and notifies observers.
+ */
+ private fun populateOps() {
+ removeListeners()
+ ops.clear()
+
+ val permissionInfos = groupLiveData.value?.permissionInfos
+ if (permissionInfos == null) {
+ this.value = null
+ return
+ }
+ for ((_, permissionInfo) in permissionInfos) {
+ val opName = AppOpsManager.permissionToOp(permissionInfo.name)
+ val opMode = appOpsManager.unsafeCheckOpNoThrow(opName, uid, packageName)
+ ops[opName] = opMode
+ }
+
+ addListeners()
+ value = ops
+ }
+
+ private fun addListeners() {
+ for (key in ops.keys) {
+ AppOpRepository.getAppOpChangeListener(app, key).addListener(packageName, this)
+ }
+ }
+
+ private fun removeListeners() {
+ for (key in ops.keys) {
+ AppOpRepository.getAppOpChangeListener(app, key).removeListener(packageName, this)
+ }
+ }
+
+ override fun onActive() {
+ super.onActive()
+
+ addListeners()
+ }
+
+ override fun onInactive() {
+ super.onInactive()
+
+ removeListeners()
+ }
+}
\ No newline at end of file
diff --git a/PermissionController/src/com/android/packageinstaller/permission/data/AppPermissionGroupLiveData.kt b/PermissionController/src/com/android/packageinstaller/permission/data/AppPermissionGroupLiveData.kt
new file mode 100644
index 0000000..ba9d4b1
--- /dev/null
+++ b/PermissionController/src/com/android/packageinstaller/permission/data/AppPermissionGroupLiveData.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2019 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.packageinstaller.permission.data
+
+import android.app.Application
+import android.os.Process
+import android.os.UserHandle
+import androidx.lifecycle.MediatorLiveData
+import com.android.packageinstaller.permission.model.AppPermissionGroup
+
+/**
+ * Live Data for AppPermissionGroup
+ *
+ * @param app: The application this LiveData is being instantiated in
+ * @param packageName: The name of the package whose permission group will be watched
+ * @param permissionGroupName: The name of the permission group whose app ops this LiveData
+ * will watch
+ * @param user: The user whose permission group will be observed
+ */
+class AppPermissionGroupLiveData(
+ private val app: Application,
+ private val packageName: String,
+ private val permissionGroupName: String,
+ private val user: UserHandle
+) : MediatorLiveData<AppPermissionGroup>(),
+ PermissionListenerMultiplexer.PermissionChangeCallback,
+ DataRepository.InactiveTimekeeper {
+
+ private val context = app.applicationContext
+ private val appOpsLiveData = AppOpsLiveData(app, packageName, permissionGroupName, user)
+ private val packageInfoLiveData =
+ PackageInfoRepository.getPackageInfoLiveData(app, packageName, user)
+ private val groupLiveData =
+ PermissionGroupRepository.getPermissionGroupLiveData(app, permissionGroupName, user)
+
+ private var uid = context.packageManager.getPackageUid(packageName, 0)
+
+ override var timeWentInactive: Long? = null
+
+ init {
+ /**
+ * Since the AppPermissionGroup only keeps a reference to the AppOpManager, it is not
+ * immediately affected by app op changes. Regenerate the AppPermissionGroup
+ */
+ addSource(appOpsLiveData) {
+ generateNewPermissionGroup()
+ }
+
+ addSource(groupLiveData) {
+ generateNewPermissionGroup()
+ }
+
+ addSource(packageInfoLiveData) {
+ generateNewPermissionGroup()
+ if (Process.myUserHandle() == user) {
+ uid = context.packageManager.getPackageUid(packageName, 0)
+ }
+ }
+ generateNewPermissionGroup()
+ }
+
+ /**
+ * Create a new AppPermissionGroup.
+ */
+ private fun generateNewPermissionGroup() {
+ val packageInfo = packageInfoLiveData.value
+ val groupInfo = groupLiveData.value?.groupInfo
+ val permissionInfos = groupLiveData.value?.permissionInfos?.values?.toList()
+
+ if (packageInfo == null || groupInfo == null || permissionInfos == null) {
+ value = null
+ } else {
+ value = AppPermissionGroup.create(context, packageInfo, groupInfo,
+ permissionInfos, false)
+ }
+ }
+
+ override fun onInactive() {
+ super.onInactive()
+
+ AppPermissionGroupRepository.permissionListenerMultiplexer?.removeCallback(uid, this)
+ timeWentInactive = System.nanoTime()
+ }
+
+ override fun onActive() {
+ super.onActive()
+
+ AppPermissionGroupRepository.permissionListenerMultiplexer?.addCallback(uid, this)
+ }
+
+ override fun onPermissionChange() {
+ generateNewPermissionGroup()
+ }
+}
\ No newline at end of file
diff --git a/PermissionController/src/com/android/packageinstaller/permission/data/AppPermissionGroupRepository.kt b/PermissionController/src/com/android/packageinstaller/permission/data/AppPermissionGroupRepository.kt
new file mode 100644
index 0000000..d2e0143
--- /dev/null
+++ b/PermissionController/src/com/android/packageinstaller/permission/data/AppPermissionGroupRepository.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2019 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.packageinstaller.permission.data
+
+import android.app.Application
+import android.os.UserHandle
+
+/**
+ * Repository for AppPermissionGroupLiveDatas.
+ * Key value is a (packageName, permissionGroupName, user) triple, value is an app permission group
+ * LiveData.
+ */
+object AppPermissionGroupRepository
+ : DataRepository<Triple<String, String, UserHandle>, AppPermissionGroupLiveData>() {
+
+ /**
+ * Used by the AppPermissionGroupLiveData objects. Must be instantiated if used before
+ * getAppPermissionGroupLiveData is called for the first time.
+ */
+ var permissionListenerMultiplexer: PermissionListenerMultiplexer? = null
+
+ /**
+ * Gets the AppPermissionGroupLiveData associated with the provided group name and user,
+ * creating it if need be.
+ *
+ * @param app: The application this is being called from
+ * @param packageName: The name of the package desired
+ * @param permissionGroup: The name of the permission group desired
+ * @param user: The UserHandle of the user for whom we want the app permission group
+ *
+ * @return The cached or newly created AppPermissionGroupLiveData
+ */
+ fun getAppPermissionGroupLiveData(
+ app: Application,
+ packageName: String,
+ permissionGroup: String,
+ user: UserHandle
+ ): AppPermissionGroupLiveData {
+ if (permissionListenerMultiplexer == null) {
+ permissionListenerMultiplexer =
+ PermissionListenerMultiplexer(app)
+ }
+ return getDataObject(app, Triple(packageName, permissionGroup, user))
+ }
+
+ override fun newValue(
+ app: Application,
+ key: Triple<String, String, UserHandle>
+ ): AppPermissionGroupLiveData {
+ return AppPermissionGroupLiveData(app, key.first, key.second, key.third)
+ }
+}
\ No newline at end of file
diff --git a/PermissionController/src/com/android/packageinstaller/permission/data/DataRepository.kt b/PermissionController/src/com/android/packageinstaller/permission/data/DataRepository.kt
new file mode 100644
index 0000000..0cd5628
--- /dev/null
+++ b/PermissionController/src/com/android/packageinstaller/permission/data/DataRepository.kt
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2019 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.packageinstaller.permission.data
+
+import android.app.Application
+import android.content.ComponentCallbacks2
+import android.content.res.Configuration
+import java.util.concurrent.TimeUnit
+
+/**
+ * A generalize data repository, which carries a component callback which trims its data in response
+ * to memory pressure
+ */
+abstract class DataRepository<K, V : DataRepository.InactiveTimekeeper> : ComponentCallbacks2 {
+
+ /**
+ * Deadlines for removal based on memory pressure. Live Data objects which have been inactive
+ * for longer than the deadline will be removed.
+ */
+ private val TIME_THRESHOLD_LAX_NANOS: Long = TimeUnit.NANOSECONDS.convert(5, TimeUnit.MINUTES)
+ private val TIME_THRESHOLD_TIGHT_NANOS: Long = TimeUnit.NANOSECONDS.convert(1, TimeUnit.MINUTES)
+ private val TIME_THRESHOLD_ALL_NANOS: Long = 0
+
+ private val data = mutableMapOf<K, V>()
+
+ /**
+ * Whether or not this data repository has been registered as a component callback yet
+ */
+ private var registered = false
+
+ /**
+ * Get a value from this repository, creating it if needed
+ *
+ * @param app: The application this is being called from
+ * @param key: The key associated with the desired Value
+ *
+ * @return The cached or newly created Value for the given Key
+ */
+ protected fun getDataObject(app: Application, key: K): V {
+ if (!registered) {
+ app.registerComponentCallbacks(this)
+ registered = true
+ }
+
+ return data.getOrPut(key) { newValue(app, key) }
+ }
+
+ /**
+ * Generate a new value type from the given data
+ *
+ * @param app: The application this is being called from
+ * @param key: Information about this value object, used to instantiate it
+ *
+ * @return The generated Value
+ */
+ protected abstract fun newValue(app: Application, key: K): V
+
+ /**
+ * Remove LiveData objects with no observer based on the severity of the memory pressure.
+ *
+ * @param level The severity of the current memory pressure
+ */
+ override fun onTrimMemory(level: Int) {
+
+ trimInactiveData(threshold = when (level) {
+ ComponentCallbacks2.TRIM_MEMORY_BACKGROUND -> TIME_THRESHOLD_LAX_NANOS
+ ComponentCallbacks2.TRIM_MEMORY_MODERATE -> TIME_THRESHOLD_TIGHT_NANOS
+ ComponentCallbacks2.TRIM_MEMORY_COMPLETE -> TIME_THRESHOLD_ALL_NANOS
+ ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE -> TIME_THRESHOLD_LAX_NANOS
+ ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW -> TIME_THRESHOLD_TIGHT_NANOS
+ ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL -> TIME_THRESHOLD_ALL_NANOS
+ else -> return
+ })
+ }
+
+ override fun onLowMemory() {
+ onTrimMemory(ComponentCallbacks2.TRIM_MEMORY_COMPLETE)
+ }
+
+ override fun onConfigurationChanged(newConfig: Configuration) {
+ // Do nothing, but required to override by interface
+ }
+
+ private fun trimInactiveData(threshold: Long) {
+ data.entries.removeAll { (_, value) ->
+ value.timeInactive?.let { it >= threshold } ?: false
+ }
+ }
+
+ /**
+ * Interface which describes an object which can track how long it has been inactive, and if
+ * it has any observers.
+ */
+ interface InactiveTimekeeper {
+
+ /**
+ * Long value representing the time this object went inactive, which is read only on the
+ * main thread, so does not cause race conditions.
+ */
+ var timeWentInactive: Long?
+
+ /**
+ * Calculates the time since this object went inactive.
+ *
+ * @return The time since this object went inactive, or null if it is not inactive
+ */
+ val timeInactive: Long?
+ get() {
+ if (hasObservers()) {
+ return null
+ }
+ val time = timeWentInactive ?: return null
+ return System.nanoTime() - time
+ }
+
+ /**
+ * Whether or not this object has any objects observing it (observers need not be active).
+ *
+ * @return Whether or not this object has any Observers
+ */
+ fun hasObservers(): Boolean
+ }
+}
\ No newline at end of file
diff --git a/PermissionController/src/com/android/packageinstaller/permission/data/PackageBroadcastReceiver.kt b/PermissionController/src/com/android/packageinstaller/permission/data/PackageBroadcastReceiver.kt
new file mode 100644
index 0000000..6d4ede1
--- /dev/null
+++ b/PermissionController/src/com/android/packageinstaller/permission/data/PackageBroadcastReceiver.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2019 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.packageinstaller.permission.data
+
+import android.app.Application
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.util.ArrayMap
+
+/**
+ * Listens for package additions, replacements, and removals, and notifies listeners.
+ *
+ * @param app: The application this receiver is being instantiated in
+ */
+class PackageBroadcastReceiver(val app: Application) : BroadcastReceiver() {
+
+ private val intentFilter = IntentFilter(Intent.ACTION_PACKAGE_ADDED).apply {
+ addAction(Intent.ACTION_PACKAGE_REMOVED)
+ addAction(Intent.ACTION_PACKAGE_REPLACED)
+ }
+
+ /**
+ * A map of callbacks for a specific package change.
+ */
+ private val changeCallbacks: ArrayMap<String, MutableList<PackageBroadcastListener>> =
+ ArrayMap()
+ /**
+ * A list of listener IDs, which listen to all package additions, changes, and removals.
+ */
+ private val allCallbacks = mutableListOf<PackageBroadcastListener>()
+
+ /**
+ * Add a callback which will be notified when the specified packaged is changed or removed.
+ */
+ fun addChangeCallback(packageName: String, listener: PackageBroadcastListener) {
+ val wasEmpty = hasNoListeners()
+
+ changeCallbacks.getOrPut(packageName, { mutableListOf() }).add(listener)
+
+ if (wasEmpty) {
+ app.registerReceiver(this, intentFilter)
+ }
+ }
+
+ /**
+ * Add a callback which will be notified any time a package is added, removed, or changed.
+ *
+ * @param listener the listener to be added
+ * @return returns the integer ID assigned to the
+ */
+ fun addAllCallback(listener: PackageBroadcastListener) {
+ val wasEmpty = hasNoListeners()
+
+ allCallbacks.add(listener)
+
+ if (wasEmpty) {
+ app.registerReceiver(this, intentFilter)
+ }
+ }
+
+ /**
+ * Removes a package add/remove/change callback.
+ *
+ * @param listener the listener we wish to remove
+ */
+ fun removeAllCallback(listener: PackageBroadcastListener) {
+ if (!allCallbacks.remove(listener)) {
+ return
+ }
+ if (hasNoListeners()) {
+ app.unregisterReceiver(this)
+ }
+ }
+
+ /**
+ * Removes a change callback.
+ *
+ * @param packageName the package the listener is listening for
+ * @param listener the listener we wish to remove
+ */
+ fun removeChangeCallback(packageName: String?, listener: PackageBroadcastListener) {
+ if (changeCallbacks.contains(packageName)) {
+ if (!changeCallbacks[packageName]!!.remove(listener)) {
+ return
+ }
+ if (changeCallbacks[packageName]!!.isEmpty()) {
+ changeCallbacks.remove(packageName)
+ }
+ if (hasNoListeners()) {
+ app.unregisterReceiver(this)
+ }
+ }
+ }
+
+ fun getNumListeners(): Int {
+ var numListeners = allCallbacks.size
+ for ((_, changeCallbackList) in changeCallbacks) {
+ numListeners += changeCallbackList.size
+ }
+ return numListeners
+ }
+
+ fun hasNoListeners(): Boolean {
+ return getNumListeners() == 0
+ }
+
+ /**
+ * Upon receiving a broadcast, rout it to the proper callbacks.
+ *
+ * @param context: the context of the broadcast
+ * @param intent: data about the broadcast which was sent
+ */
+ override fun onReceive(context: Context, intent: Intent) {
+ val packageName = intent.dataString!!
+
+ for (callback in allCallbacks) {
+ callback.onPackageUpdate(packageName)
+ }
+
+ if (intent.action != Intent.ACTION_PACKAGE_ADDED) {
+ changeCallbacks[packageName]?.forEach { callback ->
+ callback.onPackageUpdate(packageName)
+ }
+ }
+ }
+
+ /**
+ * A listener interface for objects desiring to be notified of package broadcasts.
+ */
+ interface PackageBroadcastListener {
+ /**
+ * To be called when a specific package has been changed, or when any package has been
+ * installed.
+ *
+ * @param packageName the name of the package which was updated
+ */
+ fun onPackageUpdate(packageName: String)
+ }
+}
\ No newline at end of file
diff --git a/PermissionController/src/com/android/packageinstaller/permission/data/PackageInfoLiveData.kt b/PermissionController/src/com/android/packageinstaller/permission/data/PackageInfoLiveData.kt
new file mode 100644
index 0000000..5e0e2e2
--- /dev/null
+++ b/PermissionController/src/com/android/packageinstaller/permission/data/PackageInfoLiveData.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2019 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.packageinstaller.permission.data
+
+import android.app.Application
+import android.content.Context
+import android.content.pm.PackageInfo
+import android.content.pm.PackageManager
+import android.os.UserHandle
+import androidx.lifecycle.LiveData
+
+/**
+ * LiveData for PackageInfo.
+ *
+ * @param app: The application this LiveData will watch
+ * @param packageName: The name of the package this LiveData will watch for mode changes for
+ * @param user: The user for whom the packageInfo will be defined
+ */
+class PackageInfoLiveData(
+ private val app: Application,
+ private val packageName: String,
+ private val user: UserHandle
+) : LiveData<PackageInfo>(),
+ PackageBroadcastReceiver.PackageBroadcastListener,
+ DataRepository.InactiveTimekeeper {
+
+ val context: Context = app.applicationContext
+
+ override var timeWentInactive: Long? = null
+
+ /**
+ * Callback from the PackageBroadcastReceiver. Either deletes or generates package data.
+ *
+ * @param packageName the name of the package which was updated. Ignored in this method.
+ */
+ override fun onPackageUpdate(packageName: String) {
+ generatePackageData()
+ }
+
+ override fun getValue(): PackageInfo? {
+ return super.getValue() ?: run {
+ generatePackageData()
+ return super.getValue()
+ }
+ }
+
+ /**
+ * Generates a PackageInfo for our given package.
+ */
+ fun generatePackageData() {
+ try {
+ this.value = context.packageManager.getPackageInfo(packageName,
+ PackageManager.GET_PERMISSIONS)
+ } catch (e: PackageManager.NameNotFoundException) {
+ this.value = null
+ }
+ }
+
+ override fun onActive() {
+ super.onActive()
+
+ PackageInfoRepository.getPackageBroadcastReceiver(app)
+ .addChangeCallback(packageName, this)
+ }
+
+ override fun onInactive() {
+ super.onInactive()
+
+ timeWentInactive = System.nanoTime()
+ PackageInfoRepository.getPackageBroadcastReceiver(app)
+ .removeChangeCallback(packageName, this)
+ }
+}
\ No newline at end of file
diff --git a/PermissionController/src/com/android/packageinstaller/permission/data/PackageInfoRepository.kt b/PermissionController/src/com/android/packageinstaller/permission/data/PackageInfoRepository.kt
new file mode 100644
index 0000000..e3511ee
--- /dev/null
+++ b/PermissionController/src/com/android/packageinstaller/permission/data/PackageInfoRepository.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2019 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.packageinstaller.permission.data
+
+import android.app.Application
+import android.os.UserHandle
+
+/**
+ * Repository for PackageInfo LiveDatas, and a PackageBroadcastReceiver.
+ * Key value is a String package name and UserHandle, value is a PackageInfoLiveData.
+ */
+object PackageInfoRepository : DataRepository<Pair<String, UserHandle>, PackageInfoLiveData>() {
+
+ private var broadcastReceiver: PackageBroadcastReceiver? = null
+
+ /**
+ * Gets the PackageBroadcastReceiver, instantiating it if need be.
+ *
+ * @param app: The application this is being called from
+ *
+ * @return The cached or newly created PackageBroadcastReceiver
+ */
+ fun getPackageBroadcastReceiver(app: Application): PackageBroadcastReceiver {
+ if (broadcastReceiver == null) {
+ broadcastReceiver = PackageBroadcastReceiver(app)
+ }
+ return broadcastReceiver!!
+ }
+
+ /**
+ * Gets the PackageInfoLiveData associated with the provided package name and user,
+ * creating it if need be.
+ *
+ * @param app: The application this is being called from
+ * @param packageName: The name of the package desired
+ * @param user: The UserHandle for whom we want the package
+ *
+ * @return The cached or newly created PackageInfoLiveData
+ */
+ fun getPackageInfoLiveData(app: Application, packageName: String, user: UserHandle):
+ PackageInfoLiveData {
+ return getDataObject(app, packageName to user)
+ }
+
+ override fun newValue(app: Application, key: Pair<String, UserHandle>): PackageInfoLiveData {
+ return PackageInfoLiveData(app, key.first, key.second)
+ }
+}
\ No newline at end of file
diff --git a/PermissionController/src/com/android/packageinstaller/permission/data/PermissionGroupLiveData.kt b/PermissionController/src/com/android/packageinstaller/permission/data/PermissionGroupLiveData.kt
new file mode 100644
index 0000000..f3ba861
--- /dev/null
+++ b/PermissionController/src/com/android/packageinstaller/permission/data/PermissionGroupLiveData.kt
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2019 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.packageinstaller.permission.data
+
+import android.app.Application
+import android.content.pm.PackageInfo
+import android.content.pm.PackageItemInfo
+import android.content.pm.PackageManager
+import android.content.pm.PermissionInfo
+import android.os.UserHandle
+import androidx.lifecycle.MediatorLiveData
+import com.android.packageinstaller.permission.data.PackageInfoRepository.getPackageInfoLiveData
+import com.android.packageinstaller.permission.data.PackageInfoRepository.getPackageBroadcastReceiver
+import com.android.packageinstaller.permission.utils.Utils
+
+/**
+ * LiveData for a Permission Group. Contains GroupInfo and a list of PermissionInfos
+ *
+ * @param app: The application this LiveData is being instantiated in
+ * @param groupName: The name of the permission group this LiveData represents
+ * @param user: The user for which this permission group is defined
+ */
+class PermissionGroupLiveData(
+ private val app: Application,
+ private val groupName: String,
+ private val user: UserHandle
+) : MediatorLiveData<PermissionGroupLiveData.PermissionGroup>(),
+ PackageBroadcastReceiver.PackageBroadcastListener,
+ DataRepository.InactiveTimekeeper {
+
+ private val context = app.applicationContext!!
+ /**
+ * Maps a String permission name to its corresponding PermissionInfo
+ */
+ private val permissionInfos = mutableMapOf<String, PermissionInfo>()
+ /**
+ * Maps a String packageName to its corresponding PackageInfoLiveData
+ */
+ private val liveDatas = mutableMapOf<String, PackageInfoLiveData>()
+
+ private lateinit var groupInfo: PackageItemInfo
+ override var timeWentInactive: Long? = null
+
+ override fun getValue(): PermissionGroup? {
+ return super.getValue() ?: run {
+ initializeGroup()
+ return super.getValue()
+ }
+ }
+
+ /**
+ * Called when a package is installed, changed, or removed. If we aren't already watching the
+ * package, gets its packageInfo, and passes it to onPackageChanged.
+ *
+ * @param packageName the package which was added
+ */
+ override fun onPackageUpdate(packageName: String) {
+ if (!liveDatas.contains(packageName)) {
+ try {
+ val packageInfo = context.packageManager.getPackageInfo(packageName,
+ PackageManager.GET_PERMISSIONS)
+ onPackageChanged(packageInfo)
+ } catch (e: PackageManager.NameNotFoundException) {
+ /*
+ * If we can't find the package, and aren't already listening, we don't need to care
+ * about it
+ */
+ }
+ }
+ }
+
+ /**
+ * Responds to package changes, either addition, replacement, or removal. Removes all
+ * permissions that were defined by the affected package, and then re-adds any currently
+ * defined.
+ *
+ * @param packageInfo the packageInfo of the package which was changed
+ */
+ private fun onPackageChanged(packageInfo: PackageInfo) {
+ if (groupInfo.packageName == packageInfo.packageName) {
+ groupInfo = Utils.getGroupInfo(groupInfo.name, context) ?: run {
+ removeAllDataAndSetValueNull()
+ return
+ }
+ }
+ removePackagePermissions(packageInfo.packageName)
+
+ var hasPackagePermissions = false
+ for (newPermission in packageInfo.permissions) {
+ if (Utils.getGroupOfPermission(newPermission) == groupInfo.name) {
+ permissionInfos[newPermission.name] = newPermission
+ hasPackagePermissions = true
+ }
+ }
+
+ if (hasPackagePermissions) {
+ val liveData = liveDatas[packageInfo.packageName]
+ ?: getPackageInfoLiveData(app, packageInfo.packageName, user)
+ addPackageLiveData(packageInfo.packageName, liveData)
+ }
+
+ /**
+ * If this isn't the package defining the permission group, and it doesn't define any
+ * permissions in the group, and we currently listen to the package, stop listening
+ */
+ if (groupInfo.packageName != packageInfo.packageName && !hasPackagePermissions &&
+ liveDatas.contains(packageInfo.packageName)) {
+ removeSource(liveDatas[packageInfo.packageName]!!)
+ liveDatas.remove(packageInfo.packageName)
+ }
+
+ this.value = PermissionGroup(groupInfo, permissionInfos)
+ }
+
+ /**
+ * Remove a package. If the package was the one defining this group, remove all sources and
+ * clear all data. Otherwise, remove all permissions defined by the removed package, then
+ * update observers.
+ *
+ * @param packageName the package which was removed
+ */
+ private fun onPackageRemoved(packageName: String) {
+ if (groupInfo.packageName == packageName) {
+ /**
+ * If the package defining this permission group is removed, stop listening to any
+ * packages, clear any held data, and set value to null
+ */
+ removeAllDataAndSetValueNull()
+ return
+ }
+
+ liveDatas[packageName]?.let {
+ removeSource(it)
+ liveDatas.remove(packageName)
+ }
+
+ removePackagePermissions(packageName)
+ this.value = PermissionGroup(groupInfo, permissionInfos)
+ }
+
+ /**
+ * Remove all permissions defined by a particular package
+ *
+ * @param packageName the package whose permissions we wish to remove
+ */
+ private fun removePackagePermissions(packageName: String) {
+ permissionInfos.entries.removeAll { (_, permInfo) ->
+ permInfo.packageName == packageName
+ }
+ }
+
+ /**
+ * Adds a PackageInfoLiveData as a source, if we don't already have it.
+ *
+ * @param packageName the name of the package the PackageInfoLiveData watches
+ * @param data the PackageInfoLiveData to be inserted
+ */
+ private fun addPackageLiveData(packageName: String, data: PackageInfoLiveData) {
+ if (!liveDatas.contains(packageName)) {
+ liveDatas[packageName] = data
+ addSource(data) {
+ if (it != null) {
+ onPackageChanged(it)
+ } else {
+ onPackageRemoved(packageName)
+ }
+ }
+ }
+ }
+
+ /**
+ * Initializes this permission group from scratch. Resets the groupInfo, PermissionInfos, and
+ * PackageInfoLiveInfos, then re-adds them.
+ */
+ private fun initializeGroup() {
+ permissionInfos.clear()
+ for ((_, liveData) in liveDatas) {
+ removeSource(liveData)
+ }
+ liveDatas.clear()
+
+ groupInfo = Utils.getGroupInfo(groupName, context) ?: run {
+ value = null
+ return
+ }
+
+ val permInfos = try {
+ Utils.getPermissionInfosForGroup(context.packageManager, groupName)
+ } catch (e: PackageManager.NameNotFoundException) {
+ value = null
+ return
+ }
+
+ for (permInfo in permInfos) {
+ permissionInfos[permInfo.name] = permInfo
+ }
+ value = PermissionGroup(groupInfo, permissionInfos)
+
+ addPackageLiveData(groupInfo.packageName,
+ getPackageInfoLiveData(app, groupInfo.packageName, user))
+
+ for ((_, permissionInfo) in permissionInfos) {
+ val pkgName = permissionInfo.packageName
+ if (!liveDatas.contains(pkgName)) {
+ addPackageLiveData(pkgName, getPackageInfoLiveData(app, pkgName, user))
+ }
+ }
+ }
+
+ private fun removeAllDataAndSetValueNull() {
+ for ((_, liveData) in liveDatas) {
+ removeSource(liveData)
+ }
+ liveDatas.clear()
+ permissionInfos.clear()
+ value = null
+ }
+
+ override fun onInactive() {
+ super.onInactive()
+
+ timeWentInactive = System.nanoTime()
+ getPackageBroadcastReceiver(app).removeAllCallback(this)
+ }
+
+ override fun onActive() {
+ super.onActive()
+
+ initializeGroup()
+ getPackageBroadcastReceiver(app).addAllCallback(this)
+ }
+
+ /**
+ * A permission Group, represented as a PackageItemInfo groupInfo, and a map of permission name
+ * to PermissionInfo objects.
+ *
+ * @param permissionInfos: the Permissions in this group
+ * @param groupInfo: information about the permission group
+ */
+ data class PermissionGroup(
+ var groupInfo: PackageItemInfo,
+ val permissionInfos: MutableMap<String, PermissionInfo>
+
+ )
+}
diff --git a/PermissionController/src/com/android/packageinstaller/permission/data/PermissionGroupRepository.kt b/PermissionController/src/com/android/packageinstaller/permission/data/PermissionGroupRepository.kt
new file mode 100644
index 0000000..901687f
--- /dev/null
+++ b/PermissionController/src/com/android/packageinstaller/permission/data/PermissionGroupRepository.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2019 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.packageinstaller.permission.data
+
+import android.app.Application
+import android.os.UserHandle
+
+/**
+ * Repository for PermissionGroupLiveDatas.
+ * Maps a pair of String Permission Group Name and UserHandle to a PermissionGroupLiveData.
+ */
+object PermissionGroupRepository
+ : DataRepository<Pair<String, UserHandle>, PermissionGroupLiveData>() {
+
+ /**
+ * Gets the PermissionGroupLiveData associated with the provided group name and user,
+ * creating it if need be.
+ *
+ * @param app: The application this is being called from
+ * @param groupName: The name of the permission group desired
+ * @param user: The UserHandle of the user for whom we want the permission group
+ *
+ * @return The cached or newly created PermissionGroupListener
+ */
+ fun getPermissionGroupLiveData(app: Application, groupName: String, user: UserHandle):
+ PermissionGroupLiveData {
+ return getDataObject(app, groupName to user)
+ }
+
+ override fun newValue(
+ app: Application,
+ key: Pair<String, UserHandle>
+ ): PermissionGroupLiveData {
+ return PermissionGroupLiveData(app, key.first, key.second)
+ }
+}
\ No newline at end of file
diff --git a/PermissionController/src/com/android/packageinstaller/permission/data/PermissionListenerMultiplexer.kt b/PermissionController/src/com/android/packageinstaller/permission/data/PermissionListenerMultiplexer.kt
new file mode 100644
index 0000000..8870e35
--- /dev/null
+++ b/PermissionController/src/com/android/packageinstaller/permission/data/PermissionListenerMultiplexer.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2019 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.packageinstaller.permission.data
+
+import android.app.Application
+import android.content.pm.PackageManager
+
+/**
+ * Serves as a single shared Permission Change Listener for all AppPermissionGroupLiveDatas.
+ *
+ * @param app: The application this multiplexer is being instantiated from
+ */
+class PermissionListenerMultiplexer(private val app: Application)
+ : PackageManager.OnPermissionsChangedListener {
+ /**
+ * Maps an Int uid to a list of PermissionChangeCallbacks that wish to be informed when
+ * permissions are updated for that UID.
+ */
+ private val callbacks = mutableMapOf<Int, MutableList<PermissionChangeCallback>>()
+ private val pm = app.applicationContext.packageManager
+
+ override fun onPermissionsChanged(uid: Int) {
+ callbacks[uid]?.forEach { callback ->
+ callback.onPermissionChange()
+ }
+ }
+
+ fun addCallback(uid: Int, callback: PermissionChangeCallback) {
+ val wasEmpty = callbacks.isEmpty()
+
+ callbacks.getOrPut(uid, { mutableListOf() }).add(callback)
+
+ if (wasEmpty) {
+ pm.addOnPermissionsChangeListener(this)
+ }
+ }
+
+ fun removeCallback(uid: Int, callback: PermissionChangeCallback) {
+ if (!callbacks.contains(uid)) {
+ return
+ }
+
+ if (!callbacks[uid]!!.remove(callback)) {
+ return
+ }
+
+ if (callbacks[uid]!!.isEmpty()) {
+ callbacks.remove(uid)
+ }
+
+ if (callbacks.isEmpty()) {
+ pm.removeOnPermissionsChangeListener(this)
+ }
+ }
+
+ interface PermissionChangeCallback {
+ fun onPermissionChange()
+ }
+}
\ No newline at end of file
diff --git a/PermissionController/src/com/android/packageinstaller/permission/ui/handheld/AppPermissionFragment.java b/PermissionController/src/com/android/packageinstaller/permission/ui/handheld/AppPermissionFragment.java
index 9199692..a548f31 100644
--- a/PermissionController/src/com/android/packageinstaller/permission/ui/handheld/AppPermissionFragment.java
+++ b/PermissionController/src/com/android/packageinstaller/permission/ui/handheld/AppPermissionFragment.java
@@ -32,11 +32,6 @@
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageItemInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.PermissionInfo;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.UserHandle;
@@ -56,6 +51,7 @@
import androidx.core.widget.NestedScrollView;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
+import androidx.lifecycle.ViewModelProviders;
import com.android.packageinstaller.PermissionControllerStatsLog;
import com.android.packageinstaller.permission.model.AppPermissionGroup;
@@ -63,7 +59,6 @@
import com.android.packageinstaller.permission.model.PermissionUsages;
import com.android.packageinstaller.permission.ui.AppPermissionActivity;
import com.android.packageinstaller.permission.utils.LocationUtils;
-import com.android.packageinstaller.permission.utils.PackageRemovalMonitor;
import com.android.packageinstaller.permission.utils.SafetyNetLogger;
import com.android.packageinstaller.permission.utils.Utils;
import com.android.permissioncontroller.R;
@@ -92,6 +87,7 @@
static final int CHANGE_BOTH = CHANGE_FOREGROUND | CHANGE_BACKGROUND;
private @NonNull AppPermissionGroup mGroup;
+ private @NonNull AppPermissionViewModel mViewModel;
private @NonNull RadioGroup mRadioGroup;
private @NonNull RadioButton mAlwaysButton;
@@ -105,18 +101,6 @@
private boolean mHasConfirmedRevoke;
/**
- * Listens for changes to the permission of the app the permission is currently getting
- * granted to. {@code null} when unregistered.
- */
- private @Nullable PackageManager.OnPermissionsChangedListener mPermissionChangeListener;
-
- /**
- * Listens for changes to the app the permission is currently getting granted to. {@code null}
- * when unregistered.
- */
- private @Nullable PackageRemovalMonitor mPackageRemovalMonitor;
-
- /**
* @return A new fragment
*/
public static @NonNull AppPermissionFragment newInstance(@NonNull String packageName,
@@ -148,44 +132,30 @@
mHasConfirmedRevoke = false;
- createAppPermissionGroup();
-
- if (mGroup != null) {
- getActivity().setTitle(
- getPreferenceManager().getContext().getString(R.string.app_permission_title,
- mGroup.getFullLabel()));
- logAppPermissionFragmentViewed();
- }
- }
-
- private void createAppPermissionGroup() {
- Activity activity = getActivity();
- Context context = getPreferenceManager().getContext();
-
String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
String groupName = getArguments().getString(Intent.EXTRA_PERMISSION_GROUP_NAME);
if (groupName == null) {
groupName = getArguments().getString(Intent.EXTRA_PERMISSION_NAME);
}
- PackageItemInfo groupInfo = Utils.getGroupInfo(groupName, context);
- List<PermissionInfo> groupPermInfos = Utils.getGroupPermissionInfos(groupName, context);
- if (groupInfo == null || groupPermInfos == null) {
- Log.i(LOG_TAG, "Illegal group: " + groupName);
- activity.setResult(Activity.RESULT_CANCELED);
- activity.finish();
- return;
- }
UserHandle userHandle = getArguments().getParcelable(Intent.EXTRA_USER);
- mGroup = AppPermissionGroup.create(context,
- getPackageInfo(activity, packageName, userHandle),
- groupInfo, groupPermInfos, false);
- if (mGroup == null || !Utils.shouldShowPermission(context, mGroup)) {
- Log.i(LOG_TAG, "Illegal group: " + (mGroup == null ? "null" : mGroup.getName()));
- activity.setResult(Activity.RESULT_CANCELED);
- activity.finish();
+ AppPermissionViewModelFactory factory = new AppPermissionViewModelFactory(
+ getActivity().getApplication(), packageName, groupName, userHandle);
+ mViewModel = ViewModelProviders.of(this, factory)
+ .get(AppPermissionViewModel.class);
+
+ AppPermissionGroup group = mViewModel.getLiveData().getValue();
+ if (group == null) {
+ Log.i(LOG_TAG, "Illegal group: " + groupName);
+ getActivity().setResult(Activity.RESULT_CANCELED);
+ getActivity().finish();
return;
}
+ mGroup = group;
+ getActivity().setTitle(
+ getPreferenceManager().getContext().getString(R.string.app_permission_title,
+ mGroup.getFullLabel()));
+ logAppPermissionFragmentViewed();
}
@Override
@@ -246,6 +216,19 @@
mNestedScrollView = root.requireViewById(R.id.nested_scroll_view);
+ mViewModel.getLiveData().observe(this, appGroup -> {
+ AppPermissionGroup group = mViewModel.getLiveData().getValue();
+ if (group == null) {
+ Log.i(LOG_TAG, "AppPermissionGroup package or group invalidated for "
+ + mGroup.getName());
+ getActivity().setResult(Activity.RESULT_CANCELED);
+ getActivity().finish();
+ } else {
+ mGroup = group;
+ updateButtons();
+ }
+ });
+
return root;
}
@@ -340,56 +323,12 @@
public void onStart() {
super.onStart();
- if (mGroup == null) {
- return;
- }
-
- String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
- UserHandle userHandle = getArguments().getParcelable(Intent.EXTRA_USER);
- Activity activity = getActivity();
-
- // Get notified when permissions change.
- try {
- mPermissionChangeListener = new PermissionChangeListener(
- mGroup.getApp().applicationInfo.uid);
- } catch (NameNotFoundException e) {
- activity.setResult(Activity.RESULT_CANCELED);
- activity.finish();
- return;
- }
- PackageManager pm = activity.getPackageManager();
- pm.addOnPermissionsChangeListener(mPermissionChangeListener);
-
- // Get notified when the package is removed.
- mPackageRemovalMonitor = new PackageRemovalMonitor(getContext(), packageName) {
- @Override
- public void onPackageRemoved() {
- Log.w(LOG_TAG, packageName + " was uninstalled");
- activity.setResult(Activity.RESULT_CANCELED);
- activity.finish();
- }
- };
- mPackageRemovalMonitor.register();
-
- // Check if the package was removed while this activity was not started.
- try {
- activity.createPackageContextAsUser(
- packageName, 0, userHandle).getPackageManager().getPackageInfo(packageName, 0);
- } catch (NameNotFoundException e) {
- Log.w(LOG_TAG, packageName + " was uninstalled while this activity was stopped", e);
- activity.setResult(Activity.RESULT_CANCELED);
- activity.finish();
- }
-
ActionBar ab = getActivity().getActionBar();
if (ab != null) {
ab.setElevation(0);
}
- ActionBarShadowController.attachToView(activity, getLifecycle(), mNestedScrollView);
- // Re-create the permission group in case permissions have changed and update the UI.
- createAppPermissionGroup();
- updateButtons();
+ ActionBarShadowController.attachToView(getActivity(), getLifecycle(), mNestedScrollView);
}
void logAppPermissionFragmentViewed() {
@@ -402,22 +341,6 @@
}
@Override
- public void onStop() {
- super.onStop();
-
- if (mPackageRemovalMonitor != null) {
- mPackageRemovalMonitor.unregister();
- mPackageRemovalMonitor = null;
- }
-
- if (mPermissionChangeListener != null) {
- getActivity().getPackageManager().removeOnPermissionsChangeListener(
- mPermissionChangeListener);
- mPermissionChangeListener = null;
- }
- }
-
- @Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
getActivity().finish();
@@ -443,7 +366,7 @@
return permissionSnapshot;
}
- permissions = mGroup.getPermissions();
+ permissions = permissionGroup.getPermissions();
numPermissions = permissions.size();
for (int i = 0; i < numPermissions; i++) {
@@ -636,20 +559,6 @@
mWidgetFrame.setVisibility(View.VISIBLE);
}
- private static @Nullable PackageInfo getPackageInfo(@NonNull Activity activity,
- @NonNull String packageName, @NonNull UserHandle userHandle) {
- try {
- return activity.createPackageContextAsUser(packageName, 0,
- userHandle).getPackageManager().getPackageInfo(packageName,
- PackageManager.GET_PERMISSIONS);
- } catch (PackageManager.NameNotFoundException e) {
- Log.i(LOG_TAG, "No package: " + activity.getCallingPackage(), e);
- activity.setResult(Activity.RESULT_CANCELED);
- activity.finish();
- return null;
- }
- }
-
/**
* Are any permissions of this group fixed by the system, i.e. not changeable by the user.
*
@@ -817,6 +726,7 @@
/**
* Show the given string as informative text below the radio buttons.
+ *
* @param strId the resourceId of the string to display.
*/
private void setDetail(int strId) {
@@ -870,8 +780,6 @@
LocationUtils.showLocationDialog(getContext(),
Utils.getAppLabel(mGroup.getApp().applicationInfo, getContext()));
- // The request was denied, so update the buttons.
- updateButtons();
return false;
}
@@ -915,7 +823,6 @@
if (showDefaultDenyDialog && !mHasConfirmedRevoke) {
showDefaultDenyDialog(changeTarget);
- updateButtons();
return false;
} else {
ArrayList<PermissionState> stateBefore = createPermissionSnapshot();
@@ -941,8 +848,6 @@
}
}
- updateButtons();
-
return true;
}
@@ -1049,28 +954,9 @@
}
}
- /**
- * A listener for permission changes.
- */
- private class PermissionChangeListener implements PackageManager.OnPermissionsChangedListener {
- private final int mUid;
-
- PermissionChangeListener(int uid) throws NameNotFoundException {
- mUid = uid;
- }
-
- @Override
- public void onPermissionsChanged(int uid) {
- if (uid == mUid) {
- Log.w(LOG_TAG, "Permissions changed.");
- createAppPermissionGroup();
- updateButtons();
- }
- }
- }
-
private static class PermissionState {
- @NonNull public final String permissionName;
+ @NonNull
+ public final String permissionName;
public final boolean permissionGranted;
PermissionState(@NonNull String permissionName, boolean permissionGranted) {
diff --git a/PermissionController/src/com/android/packageinstaller/permission/ui/handheld/AppPermissionViewModel.kt b/PermissionController/src/com/android/packageinstaller/permission/ui/handheld/AppPermissionViewModel.kt
new file mode 100644
index 0000000..a62a112
--- /dev/null
+++ b/PermissionController/src/com/android/packageinstaller/permission/ui/handheld/AppPermissionViewModel.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2019 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.packageinstaller.permission.ui.handheld
+
+import android.app.Application
+import android.os.UserHandle
+import androidx.lifecycle.ViewModel
+import com.android.packageinstaller.permission.data.AppPermissionGroupRepository
+
+class AppPermissionViewModel(
+ app: Application,
+ packageName: String,
+ permissionGroupName: String,
+ user: UserHandle
+) : ViewModel() {
+ val liveData =
+ AppPermissionGroupRepository.getAppPermissionGroupLiveData(app,
+ packageName, permissionGroupName, user)
+}
\ No newline at end of file
diff --git a/PermissionController/src/com/android/packageinstaller/permission/ui/handheld/AppPermissionViewModelFactory.kt b/PermissionController/src/com/android/packageinstaller/permission/ui/handheld/AppPermissionViewModelFactory.kt
new file mode 100644
index 0000000..feb7b95
--- /dev/null
+++ b/PermissionController/src/com/android/packageinstaller/permission/ui/handheld/AppPermissionViewModelFactory.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2019 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.packageinstaller.permission.ui.handheld
+
+import android.app.Application
+import android.os.UserHandle
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+
+class AppPermissionViewModelFactory(
+ private val app: Application,
+ private val packageName: String,
+ private val permissionGroupName: String,
+ private val user: UserHandle
+) : ViewModelProvider.Factory {
+ override fun <T : ViewModel> create(modelClass: Class<T>): T {
+ @Suppress("UNCHECKED_CAST")
+ return AppPermissionViewModel(app, packageName, permissionGroupName, user) as T
+ }
+}
\ No newline at end of file