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.modules.utils.build.SdkLevel 25 import com.android.permissioncontroller.PermissionControllerApplication 26 import com.android.permissioncontroller.permission.data.v34.LightInstallSourceInfoLiveData 27 import com.android.permissioncontroller.permission.data.v34.SafetyLabelInfoLiveData 28 import kotlinx.coroutines.Dispatchers.Main 29 import kotlinx.coroutines.GlobalScope 30 import kotlinx.coroutines.launch 31 32 /** 33 * Listens for package additions, replacements, and removals, and notifies listeners. 34 */ 35 object PackageBroadcastReceiver : BroadcastReceiver() { 36 37 private val app: Application = PermissionControllerApplication.get() 38 private val intentFilter = IntentFilter(Intent.ACTION_PACKAGE_ADDED).apply { 39 addAction(Intent.ACTION_PACKAGE_REMOVED) 40 addAction(Intent.ACTION_PACKAGE_REPLACED) 41 addAction(Intent.ACTION_PACKAGE_CHANGED) 42 addDataScheme("package") 43 } 44 45 /** 46 * Map<packageName, callbacks listenening to package> 47 */ 48 private val changeCallbacks = mutableMapOf<String, MutableSet<PackageBroadcastListener>>() 49 /** 50 * A list of listener IDs, which listen to all package additions, changes, and removals. 51 */ 52 private val allCallbacks = mutableSetOf<PackageBroadcastListener>() 53 54 /** 55 * Add a callback which will be notified when the specified packaged is changed or removed. 56 */ 57 fun addChangeCallback(packageName: String, listener: PackageBroadcastListener) { 58 GlobalScope.launch(Main.immediate) { 59 val wasEmpty = hasNoListeners() 60 61 changeCallbacks.getOrPut(packageName, { mutableSetOf() }).add(listener) 62 63 if (wasEmpty) { 64 app.applicationContext.registerReceiverForAllUsers(this@PackageBroadcastReceiver, 65 intentFilter, null, null) 66 } 67 } 68 } 69 70 /** 71 * Add a callback which will be notified any time a package is added, removed, or changed. 72 * 73 * @param listener the listener to be added 74 * @return returns the integer ID assigned to the 75 */ 76 fun addAllCallback(listener: PackageBroadcastListener) { 77 GlobalScope.launch(Main.immediate) { 78 val wasEmpty = hasNoListeners() 79 80 allCallbacks.add(listener) 81 82 if (wasEmpty) { 83 app.applicationContext.registerReceiverForAllUsers(this@PackageBroadcastReceiver, 84 intentFilter, null, null) 85 } 86 } 87 } 88 89 /** 90 * Removes a package add/remove/change callback. 91 * 92 * @param listener the listener we wish to remove 93 */ 94 fun removeAllCallback(listener: PackageBroadcastListener) { 95 GlobalScope.launch(Main.immediate) { 96 val wasEmpty = hasNoListeners() 97 98 if (allCallbacks.remove(listener) && hasNoListeners() && !wasEmpty) { 99 app.applicationContext.unregisterReceiver(this@PackageBroadcastReceiver) 100 } 101 } 102 } 103 104 /** 105 * Removes a change callback. 106 * 107 * @param packageName the package the listener is listening for 108 * @param listener the listener we wish to remove 109 */ 110 fun removeChangeCallback(packageName: String?, listener: PackageBroadcastListener) { 111 GlobalScope.launch(Main.immediate) { 112 val wasEmpty = hasNoListeners() 113 114 changeCallbacks[packageName]?.let { callbackSet -> 115 callbackSet.remove(listener) 116 if (callbackSet.isEmpty()) { 117 changeCallbacks.remove(packageName) 118 } 119 if (hasNoListeners() && !wasEmpty) { 120 app.applicationContext.unregisterReceiver(this@PackageBroadcastReceiver) 121 } 122 } 123 } 124 } 125 126 private fun getNumListeners(): Int { 127 var numListeners = allCallbacks.size 128 for ((_, changeCallbackSet) in changeCallbacks) { 129 numListeners += changeCallbackSet.size 130 } 131 return numListeners 132 } 133 134 private fun hasNoListeners(): Boolean { 135 return getNumListeners() == 0 136 } 137 138 /** 139 * Upon receiving a broadcast, rout it to the proper callbacks. 140 * 141 * @param context the context of the broadcast 142 * @param intent data about the broadcast which was sent 143 */ 144 override fun onReceive(context: Context, intent: Intent) { 145 val packageName = intent.data?.schemeSpecificPart ?: return 146 147 for (callback in allCallbacks.toList()) { 148 callback.onPackageUpdate(packageName) 149 } 150 151 if (intent.action != Intent.ACTION_PACKAGE_ADDED) { 152 changeCallbacks[packageName]?.toList()?.let { callbacks -> 153 for (callback in callbacks) { 154 callback.onPackageUpdate(packageName) 155 } 156 } 157 } 158 159 if (intent.action == Intent.ACTION_PACKAGE_REMOVED) { 160 // Invalidate all livedatas associated with this package 161 LightPackageInfoLiveData.invalidateAllForPackage(packageName) 162 PermStateLiveData.invalidateAllForPackage(packageName) 163 PackagePermissionsLiveData.invalidateAllForPackage(packageName) 164 HibernationSettingStateLiveData.invalidateAllForPackage(packageName) 165 LightAppPermGroupLiveData.invalidateAllForPackage(packageName) 166 AppPermGroupUiInfoLiveData.invalidateAllForPackage(packageName) 167 if (SdkLevel.isAtLeastU()) { 168 SafetyLabelInfoLiveData.invalidateAllForPackage(packageName) 169 LightInstallSourceInfoLiveData.invalidateAllForPackage(packageName) 170 } 171 } 172 } 173 174 /** 175 * A listener interface for objects desiring to be notified of package broadcasts. 176 */ 177 interface PackageBroadcastListener { 178 /** 179 * To be called when a specific package has been changed, or when any package has been 180 * installed. 181 * 182 * @param packageName the name of the package which was updated 183 */ 184 fun onPackageUpdate(packageName: String) 185 } 186 } 187