1 /* <lambda>null2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.permissioncontroller.permission.data 18 19 import android.app.Application 20 import android.content.BroadcastReceiver 21 import android.content.Context 22 import android.content.Intent 23 import android.content.IntentFilter 24 import com.android.permissioncontroller.PermissionControllerApplication 25 import kotlinx.coroutines.Dispatchers.Main 26 import kotlinx.coroutines.GlobalScope 27 import kotlinx.coroutines.launch 28 29 /** 30 * Listens for package additions, replacements, and removals, and notifies listeners. 31 */ 32 object PackageBroadcastReceiver : BroadcastReceiver() { 33 34 private val app: Application = PermissionControllerApplication.get() 35 private val intentFilter = IntentFilter(Intent.ACTION_PACKAGE_ADDED).apply { 36 addAction(Intent.ACTION_PACKAGE_REMOVED) 37 addAction(Intent.ACTION_PACKAGE_REPLACED) 38 addAction(Intent.ACTION_PACKAGE_CHANGED) 39 addDataScheme("package") 40 } 41 42 /** 43 * Map<packageName, callbacks listenening to package> 44 */ 45 private val changeCallbacks = mutableMapOf<String, MutableSet<PackageBroadcastListener>>() 46 /** 47 * A list of listener IDs, which listen to all package additions, changes, and removals. 48 */ 49 private val allCallbacks = mutableSetOf<PackageBroadcastListener>() 50 51 /** 52 * Add a callback which will be notified when the specified packaged is changed or removed. 53 */ 54 fun addChangeCallback(packageName: String, listener: PackageBroadcastListener) { 55 GlobalScope.launch(Main.immediate) { 56 val wasEmpty = hasNoListeners() 57 58 changeCallbacks.getOrPut(packageName, { mutableSetOf() }).add(listener) 59 60 if (wasEmpty) { 61 app.applicationContext.registerReceiverForAllUsers(this@PackageBroadcastReceiver, 62 intentFilter, null, null) 63 } 64 } 65 } 66 67 /** 68 * Add a callback which will be notified any time a package is added, removed, or changed. 69 * 70 * @param listener the listener to be added 71 * @return returns the integer ID assigned to the 72 */ 73 fun addAllCallback(listener: PackageBroadcastListener) { 74 GlobalScope.launch(Main.immediate) { 75 val wasEmpty = hasNoListeners() 76 77 allCallbacks.add(listener) 78 79 if (wasEmpty) { 80 app.applicationContext.registerReceiverForAllUsers(this@PackageBroadcastReceiver, 81 intentFilter, null, null) 82 } 83 } 84 } 85 86 /** 87 * Removes a package add/remove/change callback. 88 * 89 * @param listener the listener we wish to remove 90 */ 91 fun removeAllCallback(listener: PackageBroadcastListener) { 92 GlobalScope.launch(Main.immediate) { 93 val wasEmpty = hasNoListeners() 94 95 if (allCallbacks.remove(listener) && hasNoListeners() && !wasEmpty) { 96 app.applicationContext.unregisterReceiver(this@PackageBroadcastReceiver) 97 } 98 } 99 } 100 101 /** 102 * Removes a change callback. 103 * 104 * @param packageName the package the listener is listening for 105 * @param listener the listener we wish to remove 106 */ 107 fun removeChangeCallback(packageName: String?, listener: PackageBroadcastListener) { 108 GlobalScope.launch(Main.immediate) { 109 val wasEmpty = hasNoListeners() 110 111 changeCallbacks[packageName]?.let { callbackSet -> 112 callbackSet.remove(listener) 113 if (callbackSet.isEmpty()) { 114 changeCallbacks.remove(packageName) 115 } 116 if (hasNoListeners() && !wasEmpty) { 117 app.applicationContext.unregisterReceiver(this@PackageBroadcastReceiver) 118 } 119 } 120 } 121 } 122 123 private fun getNumListeners(): Int { 124 var numListeners = allCallbacks.size 125 for ((_, changeCallbackSet) in changeCallbacks) { 126 numListeners += changeCallbackSet.size 127 } 128 return numListeners 129 } 130 131 private fun hasNoListeners(): Boolean { 132 return getNumListeners() == 0 133 } 134 135 /** 136 * Upon receiving a broadcast, rout it to the proper callbacks. 137 * 138 * @param context the context of the broadcast 139 * @param intent data about the broadcast which was sent 140 */ 141 override fun onReceive(context: Context, intent: Intent) { 142 val packageName = intent.data?.schemeSpecificPart ?: return 143 144 for (callback in allCallbacks.toList()) { 145 callback.onPackageUpdate(packageName) 146 } 147 148 if (intent.action != Intent.ACTION_PACKAGE_ADDED) { 149 changeCallbacks[packageName]?.toList()?.let { callbacks -> 150 for (callback in callbacks) { 151 callback.onPackageUpdate(packageName) 152 } 153 } 154 } 155 156 if (intent.action == Intent.ACTION_PACKAGE_REMOVED) { 157 // Invalidate all livedatas associated with this package 158 LightPackageInfoLiveData.invalidateAllForPackage(packageName) 159 PermStateLiveData.invalidateAllForPackage(packageName) 160 PackagePermissionsLiveData.invalidateAllForPackage(packageName) 161 HibernationSettingStateLiveData.invalidateAllForPackage(packageName) 162 LightAppPermGroupLiveData.invalidateAllForPackage(packageName) 163 AppPermGroupUiInfoLiveData.invalidateAllForPackage(packageName) 164 } 165 } 166 167 /** 168 * A listener interface for objects desiring to be notified of package broadcasts. 169 */ 170 interface PackageBroadcastListener { 171 /** 172 * To be called when a specific package has been changed, or when any package has been 173 * installed. 174 * 175 * @param packageName the name of the package which was updated 176 */ 177 fun onPackageUpdate(packageName: String) 178 } 179 }