Universal Broadcast Receiver for SystemUI
Implements a UBR for SystemUI. All classes subscribing to Context should
subscribe here instead to reduce number of IPC into SystemUI.
Test: atest
Bug: 134566046
Change-Id: I6be24f62bd9b9b3a49a4efdf712e2e73c0d8d0ac
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 0ee9bff..59270a0 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -34,6 +34,7 @@
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.systemui.appops.AppOpsController;
import com.android.systemui.assist.AssistManager;
+import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dock.DockManager;
@@ -200,6 +201,7 @@
@Inject Lazy<ActivityStarter> mActivityStarter;
@Inject Lazy<ActivityStarterDelegate> mActivityStarterDelegate;
+ @Inject Lazy<BroadcastDispatcher> mBroadcastDispatcher;
@Inject Lazy<AsyncSensorManager> mAsyncSensorManager;
@Inject Lazy<BluetoothController> mBluetoothController;
@Inject Lazy<LocationController> mLocationController;
@@ -317,6 +319,7 @@
mProviders.put(MAIN_HANDLER, mMainHandler::get);
mProviders.put(ActivityStarter.class, mActivityStarter::get);
mProviders.put(ActivityStarterDelegate.class, mActivityStarterDelegate::get);
+ mProviders.put(BroadcastDispatcher.class, mBroadcastDispatcher::get);
mProviders.put(AsyncSensorManager.class, mAsyncSensorManager::get);
@@ -496,6 +499,7 @@
// Make sure that the DumpController gets added to mDependencies, as they are only added
// with Dependency#get.
getDependency(DumpController.class);
+ getDependency(BroadcastDispatcher.class);
// If an arg is specified, try to dump the dependency
String controller = args != null && args.length > 1
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
new file mode 100644
index 0000000..f0e8c16
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
@@ -0,0 +1,156 @@
+/*
+ * 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.systemui.broadcast
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.IntentFilter
+import android.os.Handler
+import android.os.Looper
+import android.os.Message
+import android.os.UserHandle
+import android.util.Log
+import android.util.SparseArray
+import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.Dependency.BG_LOOPER_NAME
+import com.android.systemui.Dependency.MAIN_HANDLER_NAME
+import com.android.systemui.Dumpable
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import javax.inject.Inject
+import javax.inject.Named
+import javax.inject.Singleton
+
+data class ReceiverData(
+ val receiver: BroadcastReceiver,
+ val filter: IntentFilter,
+ val handler: Handler,
+ val user: UserHandle
+)
+
+private const val MSG_ADD_RECEIVER = 0
+private const val MSG_REMOVE_RECEIVER = 1
+private const val MSG_REMOVE_RECEIVER_FOR_USER = 2
+private const val TAG = "BroadcastDispatcher"
+private const val DEBUG = false
+
+/**
+ * SystemUI master Broadcast Dispatcher.
+ *
+ * This class allows [BroadcastReceiver] to register and centralizes registrations to [Context]
+ * from SystemUI. That way the number of calls to [BroadcastReceiver.onReceive] can be reduced for
+ * a given broadcast.
+ *
+ * Use only for IntentFilters with actions and optionally categories. It does not support,
+ * permissions, schemes or data types. Cannot be used for getting sticky broadcasts.
+ */
+@Singleton
+open class BroadcastDispatcher @Inject constructor (
+ private val context: Context,
+ @Named(MAIN_HANDLER_NAME) private val mainHandler: Handler,
+ @Named(BG_LOOPER_NAME) private val bgLooper: Looper
+) : Dumpable {
+
+ // Only modify in BG thread
+ private val receiversByUser = SparseArray<UserBroadcastDispatcher>(20)
+
+ /**
+ * Register a receiver for broadcast with the dispatcher
+ *
+ * @param receiver A receiver to dispatch the [Intent]
+ * @param filter A filter to determine what broadcasts should be dispatched to this receiver.
+ * It will only take into account actions and categories for filtering.
+ * @param handler A handler to dispatch [BroadcastReceiver.onReceive]. By default, it is the
+ * main handler.
+ * @param user A user handle to determine which broadcast should be dispatched to this receiver.
+ * By default, it is the current user.
+ */
+ @JvmOverloads
+ fun registerReceiver(
+ receiver: BroadcastReceiver,
+ filter: IntentFilter,
+ handler: Handler = mainHandler,
+ user: UserHandle = context.user
+ ) {
+ this.handler.obtainMessage(MSG_ADD_RECEIVER, ReceiverData(receiver, filter, handler, user))
+ .sendToTarget()
+ }
+
+ /**
+ * Unregister receiver for all users.
+ * <br>
+ * This will remove every registration of [receiver], not those done just with [UserHandle.ALL].
+ *
+ * @param receiver The receiver to unregister. It will be unregistered for all users.
+ */
+ fun unregisterReceiver(receiver: BroadcastReceiver) {
+ handler.obtainMessage(MSG_REMOVE_RECEIVER, receiver).sendToTarget()
+ }
+
+ /**
+ * Unregister receiver for a particular user.
+ *
+ * @param receiver The receiver to unregister. It will be unregistered for all users.
+ * @param user The user associated to the registered [receiver]. It can be [UserHandle.ALL].
+ */
+ fun unregisterReceiverForUser(receiver: BroadcastReceiver, user: UserHandle) {
+ handler.obtainMessage(MSG_REMOVE_RECEIVER_FOR_USER, user.identifier, 0, receiver)
+ .sendToTarget()
+ }
+
+ @VisibleForTesting
+ protected open fun createUBRForUser(userId: Int) =
+ UserBroadcastDispatcher(context, userId, mainHandler, bgLooper)
+
+ override fun dump(fd: FileDescriptor?, pw: PrintWriter?, args: Array<out String>?) {
+ pw?.println("Broadcast dispatcher:")
+ for (index in 0 until receiversByUser.size()) {
+ pw?.println(" User ${receiversByUser.keyAt(index)}")
+ receiversByUser.valueAt(index).dump(fd, pw, args)
+ }
+ }
+
+ private val handler = object : Handler(bgLooper) {
+ override fun handleMessage(msg: Message) {
+ when (msg.what) {
+ MSG_ADD_RECEIVER -> {
+ val data = msg.obj as ReceiverData
+ val userId = data.user.identifier
+ if (userId < UserHandle.USER_ALL) {
+ if (DEBUG) Log.w(TAG, "Register receiver for invalid user: $userId")
+ return
+ }
+ val uBR = receiversByUser.get(userId, createUBRForUser(userId))
+ receiversByUser.put(userId, uBR)
+ uBR.registerReceiver(data)
+ }
+
+ MSG_REMOVE_RECEIVER -> {
+ for (it in 0 until receiversByUser.size()) {
+ receiversByUser.valueAt(it).unregisterReceiver(msg.obj as BroadcastReceiver)
+ }
+ }
+
+ MSG_REMOVE_RECEIVER_FOR_USER -> {
+ receiversByUser.get(msg.arg1)?.unregisterReceiver(msg.obj as BroadcastReceiver)
+ }
+
+ else -> super.handleMessage(msg)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
new file mode 100644
index 0000000..d44b63e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
@@ -0,0 +1,179 @@
+/*
+ * 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.systemui.broadcast
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.Handler
+import android.os.Looper
+import android.os.Message
+import android.os.UserHandle
+import android.util.ArrayMap
+import android.util.ArraySet
+import android.util.Log
+import com.android.systemui.Dumpable
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import java.util.concurrent.atomic.AtomicBoolean
+
+private const val MSG_REGISTER_RECEIVER = 0
+private const val MSG_UNREGISTER_RECEIVER = 1
+private const val TAG = "UniversalReceiver"
+private const val DEBUG = false
+
+/**
+ * Broadcast dispatcher for a given user registration [userId].
+ *
+ * Created by [BroadcastDispatcher] as needed by users. The value of [userId] can be
+ * [UserHandle.USER_ALL].
+ */
+class UserBroadcastDispatcher(
+ private val context: Context,
+ private val userId: Int,
+ private val mainHandler: Handler,
+ private val bgLooper: Looper
+) : BroadcastReceiver(), Dumpable {
+
+ private val bgHandler = object : Handler(bgLooper) {
+ override fun handleMessage(msg: Message) {
+ when (msg.what) {
+ MSG_REGISTER_RECEIVER -> handleRegisterReceiver(msg.obj as ReceiverData)
+ MSG_UNREGISTER_RECEIVER -> handleUnregisterReceiver(msg.obj as BroadcastReceiver)
+ else -> Unit
+ }
+ }
+ }
+
+ private val registered = AtomicBoolean(false)
+
+ internal fun isRegistered() = registered.get()
+
+ private val registerReceiver = Runnable {
+ val categories = mutableSetOf<String>()
+ receiverToReceiverData.values.flatten().forEach {
+ it.filter.categoriesIterator()?.asSequence()?.let {
+ categories.addAll(it)
+ }
+ }
+ val intentFilter = IntentFilter().apply {
+ actionsToReceivers.keys.forEach { addAction(it) }
+ categories.forEach { addCategory(it) }
+ }
+
+ if (registered.get()) {
+ context.unregisterReceiver(this)
+ registered.set(false)
+ }
+ // Short interval without receiver, this can be problematic
+ if (intentFilter.countActions() > 0 && !registered.get()) {
+ context.registerReceiverAsUser(
+ this,
+ UserHandle.of(userId),
+ intentFilter,
+ null,
+ bgHandler)
+ registered.set(true)
+ }
+ }
+
+ // Only modify in BG thread
+ private val actionsToReceivers = ArrayMap<String, MutableSet<ReceiverData>>()
+ private val receiverToReceiverData = ArrayMap<BroadcastReceiver, MutableSet<ReceiverData>>()
+
+ override fun onReceive(context: Context, intent: Intent) {
+ bgHandler.post(HandleBroadcastRunnable(actionsToReceivers, context, intent))
+ }
+
+ /**
+ * Register a [ReceiverData] for this user.
+ */
+ fun registerReceiver(receiverData: ReceiverData) {
+ bgHandler.obtainMessage(MSG_REGISTER_RECEIVER, receiverData).sendToTarget()
+ }
+
+ /**
+ * Unregister a given [BroadcastReceiver] for this user.
+ */
+ fun unregisterReceiver(receiver: BroadcastReceiver) {
+ bgHandler.obtainMessage(MSG_UNREGISTER_RECEIVER, receiver).sendToTarget()
+ }
+
+ private fun handleRegisterReceiver(receiverData: ReceiverData) {
+ if (DEBUG) Log.w(TAG, "Register receiver: ${receiverData.receiver}")
+ receiverToReceiverData.getOrPut(receiverData.receiver, { ArraySet() }).add(receiverData)
+ var changed = false
+ // Index the BroadcastReceiver by all its actions, that way it's easier to dispatch given
+ // a received intent.
+ receiverData.filter.actionsIterator().forEach {
+ actionsToReceivers.getOrPut(it) {
+ changed = true
+ ArraySet()
+ }.add(receiverData)
+ }
+ if (changed) {
+ mainHandler.post(registerReceiver)
+ }
+ }
+
+ private fun handleUnregisterReceiver(receiver: BroadcastReceiver) {
+ if (DEBUG) Log.w(TAG, "Unregister receiver: $receiver")
+ val actions = receiverToReceiverData.getOrElse(receiver) { return }
+ .flatMap { it.filter.actionsIterator().asSequence().asIterable() }.toSet()
+ receiverToReceiverData.get(receiver)?.clear()
+ var changed = false
+ actions.forEach { action ->
+ actionsToReceivers.get(action)?.removeIf { it.receiver == receiver }
+ if (actionsToReceivers.get(action)?.isEmpty() ?: false) {
+ changed = true
+ actionsToReceivers.remove(action)
+ }
+ }
+ if (changed) {
+ mainHandler.post(registerReceiver)
+ }
+ }
+
+ override fun dump(fd: FileDescriptor?, pw: PrintWriter?, args: Array<out String>?) {
+ pw?.println(" Registered=${registered.get()}")
+ actionsToReceivers.forEach { (action, list) ->
+ pw?.println(" $action:")
+ list.forEach { pw?.println(" ${it.receiver}") }
+ }
+ }
+
+ private class HandleBroadcastRunnable(
+ val actionsToReceivers: Map<String, Set<ReceiverData>>,
+ val context: Context,
+ val intent: Intent
+ ) : Runnable {
+ override fun run() {
+ if (DEBUG) Log.w(TAG, "Dispatching $intent")
+ actionsToReceivers.get(intent.action)
+ ?.filter {
+ it.filter.hasAction(intent.action) &&
+ it.filter.matchCategories(intent.categories) == null }
+ ?.forEach {
+ it.handler.post {
+ if (DEBUG) Log.w(TAG, "Dispatching to ${it.receiver}")
+ it.receiver.onReceive(context, intent)
+ }
+ }
+ }
+ }
+}