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