Merge "Universal Broadcast Receiver for SystemUI"
This commit is contained in:
committed by
Android (Google) Code Review
commit
2fa208d98a
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user