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