Merge "Universal Broadcast Receiver for SystemUI"

This commit is contained in:
Fabian Kozynski
2019-08-14 20:11:32 +00:00
committed by Android (Google) Code Review
5 changed files with 711 additions and 0 deletions

View File

@@ -34,6 +34,7 @@ import com.android.keyguard.clock.ClockManager;
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 @@ public class Dependency extends SystemUI {
@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 @@ public class Dependency extends SystemUI {
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 @@ public class Dependency extends SystemUI {
// 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

View File

@@ -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)
}
}
}
}

View File

@@ -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)
}
}
}
}
}

View File

@@ -0,0 +1,142 @@
/*
* 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.UserHandle
import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import com.android.systemui.SysuiTestCase
import junit.framework.Assert.assertSame
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
@SmallTest
class BroadcastDispatcherTest : SysuiTestCase() {
companion object {
val user0 = UserHandle.of(0)
val user1 = UserHandle.of(1)
fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
}
@Mock
private lateinit var mockContext: Context
@Mock
private lateinit var mockUBRUser0: UserBroadcastDispatcher
@Mock
private lateinit var mockUBRUser1: UserBroadcastDispatcher
@Mock
private lateinit var broadcastReceiver: BroadcastReceiver
@Mock
private lateinit var broadcastReceiverOther: BroadcastReceiver
@Mock
private lateinit var intentFilter: IntentFilter
@Mock
private lateinit var intentFilterOther: IntentFilter
@Mock
private lateinit var mockHandler: Handler
@Captor
private lateinit var argumentCaptor: ArgumentCaptor<ReceiverData>
private lateinit var testableLooper: TestableLooper
private lateinit var broadcastDispatcher: BroadcastDispatcher
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
testableLooper = TestableLooper.get(this)
broadcastDispatcher = TestBroadcastDispatcher(
mockContext,
Handler(testableLooper.looper),
testableLooper.looper,
mapOf(0 to mockUBRUser0, 1 to mockUBRUser1))
}
@Test
fun testAddingReceiverToCorrectUBR() {
broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user0)
broadcastDispatcher.registerReceiver(
broadcastReceiverOther, intentFilterOther, mockHandler, user1)
testableLooper.processAllMessages()
verify(mockUBRUser0).registerReceiver(capture(argumentCaptor))
assertSame(broadcastReceiver, argumentCaptor.value.receiver)
assertSame(intentFilter, argumentCaptor.value.filter)
verify(mockUBRUser1).registerReceiver(capture(argumentCaptor))
assertSame(broadcastReceiverOther, argumentCaptor.value.receiver)
assertSame(intentFilterOther, argumentCaptor.value.filter)
}
@Test
fun testRemovingReceiversRemovesFromAllUBR() {
broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user0)
broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user1)
broadcastDispatcher.unregisterReceiver(broadcastReceiver)
testableLooper.processAllMessages()
verify(mockUBRUser0).unregisterReceiver(broadcastReceiver)
verify(mockUBRUser1).unregisterReceiver(broadcastReceiver)
}
@Test
fun testRemoveReceiverFromUser() {
broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user0)
broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user1)
broadcastDispatcher.unregisterReceiverForUser(broadcastReceiver, user0)
testableLooper.processAllMessages()
verify(mockUBRUser0).unregisterReceiver(broadcastReceiver)
verify(mockUBRUser1, never()).unregisterReceiver(broadcastReceiver)
}
private class TestBroadcastDispatcher(
context: Context,
mainHandler: Handler,
bgLooper: Looper,
var mockUBRMap: Map<Int, UserBroadcastDispatcher>
) : BroadcastDispatcher(context, mainHandler, bgLooper) {
override fun createUBRForUser(userId: Int): UserBroadcastDispatcher {
return mockUBRMap.getOrDefault(userId, mock(UserBroadcastDispatcher::class.java))
}
}
}

View File

@@ -0,0 +1,230 @@
/*
* 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.UserHandle
import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import com.android.systemui.SysuiTestCase
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.eq
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.anyString
import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
@SmallTest
class UserBroadcastDispatcherTest : SysuiTestCase() {
companion object {
private const val ACTION_1 = "com.android.systemui.tests.ACTION_1"
private const val ACTION_2 = "com.android.systemui.tests.ACTION_2"
private const val CATEGORY_1 = "com.android.systemui.tests.CATEGORY_1"
private const val CATEGORY_2 = "com.android.systemui.tests.CATEGORY_2"
private const val USER_ID = 0
private val USER_HANDLE = UserHandle.of(USER_ID)
fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
}
@Mock
private lateinit var broadcastReceiver: BroadcastReceiver
@Mock
private lateinit var broadcastReceiverOther: BroadcastReceiver
@Mock
private lateinit var mockContext: Context
@Mock
private lateinit var mockHandler: Handler
@Captor
private lateinit var argumentCaptor: ArgumentCaptor<IntentFilter>
private lateinit var testableLooper: TestableLooper
private lateinit var universalBroadcastReceiver: UserBroadcastDispatcher
private lateinit var intentFilter: IntentFilter
private lateinit var intentFilterOther: IntentFilter
private lateinit var handler: Handler
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
testableLooper = TestableLooper.get(this)
handler = Handler(testableLooper.looper)
universalBroadcastReceiver = UserBroadcastDispatcher(
mockContext, USER_ID, handler, testableLooper.looper)
}
@Test
fun testNotRegisteredOnStart() {
testableLooper.processAllMessages()
verify(mockContext, never()).registerReceiver(any(), any())
verify(mockContext, never()).registerReceiver(any(), any(), anyInt())
verify(mockContext, never()).registerReceiver(any(), any(), anyString(), any())
verify(mockContext, never()).registerReceiver(any(), any(), anyString(), any(), anyInt())
verify(mockContext, never()).registerReceiverAsUser(any(), any(), any(), anyString(), any())
}
@Test
fun testSingleReceiverRegistered() {
intentFilter = IntentFilter(ACTION_1)
universalBroadcastReceiver.registerReceiver(
ReceiverData(broadcastReceiver, intentFilter, mockHandler, USER_HANDLE))
testableLooper.processAllMessages()
assertTrue(universalBroadcastReceiver.isRegistered())
verify(mockContext).registerReceiverAsUser(
any(),
eq(USER_HANDLE),
capture(argumentCaptor),
any(),
any())
assertEquals(1, argumentCaptor.value.countActions())
assertTrue(argumentCaptor.value.hasAction(ACTION_1))
assertEquals(0, argumentCaptor.value.countCategories())
}
@Test
fun testSingleReceiverUnregistered() {
intentFilter = IntentFilter(ACTION_1)
universalBroadcastReceiver.registerReceiver(
ReceiverData(broadcastReceiver, intentFilter, mockHandler, USER_HANDLE))
testableLooper.processAllMessages()
reset(mockContext)
assertTrue(universalBroadcastReceiver.isRegistered())
universalBroadcastReceiver.unregisterReceiver(broadcastReceiver)
testableLooper.processAllMessages()
verify(mockContext, atLeastOnce()).unregisterReceiver(any())
verify(mockContext, never()).registerReceiverAsUser(any(), any(), any(), any(), any())
assertFalse(universalBroadcastReceiver.isRegistered())
}
@Test
fun testFilterHasAllActionsAndCategories_twoReceivers() {
intentFilter = IntentFilter(ACTION_1)
intentFilterOther = IntentFilter(ACTION_2).apply {
addCategory(CATEGORY_1)
addCategory(CATEGORY_2)
}
universalBroadcastReceiver.registerReceiver(
ReceiverData(broadcastReceiver, intentFilter, mockHandler, USER_HANDLE))
universalBroadcastReceiver.registerReceiver(
ReceiverData(broadcastReceiverOther, intentFilterOther, mockHandler, USER_HANDLE))
testableLooper.processAllMessages()
assertTrue(universalBroadcastReceiver.isRegistered())
verify(mockContext, times(2)).registerReceiverAsUser(
any(),
eq(USER_HANDLE),
capture(argumentCaptor),
any(),
any())
val lastFilter = argumentCaptor.value
assertTrue(lastFilter.hasAction(ACTION_1))
assertTrue(lastFilter.hasAction(ACTION_2))
assertTrue(lastFilter.hasCategory(CATEGORY_1))
assertTrue(lastFilter.hasCategory(CATEGORY_1))
}
@Test
fun testDispatchToCorrectReceiver() {
intentFilter = IntentFilter(ACTION_1)
intentFilterOther = IntentFilter(ACTION_2)
universalBroadcastReceiver.registerReceiver(
ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE))
universalBroadcastReceiver.registerReceiver(
ReceiverData(broadcastReceiverOther, intentFilterOther, handler, USER_HANDLE))
val intent = Intent(ACTION_2)
universalBroadcastReceiver.onReceive(mockContext, intent)
testableLooper.processAllMessages()
verify(broadcastReceiver, never()).onReceive(any(), any())
verify(broadcastReceiverOther).onReceive(mockContext, intent)
}
@Test
fun testDispatchToCorrectReceiver_differentFiltersSameReceiver() {
intentFilter = IntentFilter(ACTION_1)
intentFilterOther = IntentFilter(ACTION_2)
universalBroadcastReceiver.registerReceiver(
ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE))
universalBroadcastReceiver.registerReceiver(
ReceiverData(broadcastReceiver, intentFilterOther, handler, USER_HANDLE))
val intent = Intent(ACTION_2)
universalBroadcastReceiver.onReceive(mockContext, intent)
testableLooper.processAllMessages()
verify(broadcastReceiver).onReceive(mockContext, intent)
}
@Test
fun testDispatchIntentWithoutCategories() {
intentFilter = IntentFilter(ACTION_1)
intentFilter.addCategory(CATEGORY_1)
intentFilterOther = IntentFilter(ACTION_1)
intentFilterOther.addCategory(CATEGORY_2)
universalBroadcastReceiver.registerReceiver(
ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE))
universalBroadcastReceiver.registerReceiver(
ReceiverData(broadcastReceiverOther, intentFilterOther, handler, USER_HANDLE))
val intent = Intent(ACTION_1)
universalBroadcastReceiver.onReceive(mockContext, intent)
testableLooper.processAllMessages()
verify(broadcastReceiver).onReceive(mockContext, intent)
verify(broadcastReceiverOther).onReceive(mockContext, intent)
}
}