Add BroadcastDispatcher.broadcastFlow

Bug: 242040009
Test: atest BroadcastDispatcherTest
Change-Id: I688d686950239abab207d13d50cfb896303531d1
This commit is contained in:
Jordan Demeulenaere
2022-08-19 16:49:25 +02:00
parent d7d2de53a1
commit c9a5847b55
2 changed files with 94 additions and 2 deletions

View File

@@ -31,6 +31,8 @@ import android.util.SparseArray
import com.android.internal.annotations.VisibleForTesting import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.Dumpable import com.android.systemui.Dumpable
import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager import com.android.systemui.dump.DumpManager
@@ -38,6 +40,8 @@ import com.android.systemui.settings.UserTracker
import java.io.PrintWriter import java.io.PrintWriter
import java.util.concurrent.Executor import java.util.concurrent.Executor
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
data class ReceiverData( data class ReceiverData(
val receiver: BroadcastReceiver, val receiver: BroadcastReceiver,
@@ -153,6 +157,55 @@ open class BroadcastDispatcher @Inject constructor(
.sendToTarget() .sendToTarget()
} }
/**
* Returns a [Flow] that, when collected, emits a new value whenever a broadcast matching
* [filter] is received. The value will be computed from the intent and the registered receiver
* using [map].
*
* @see registerReceiver
*/
@JvmOverloads
fun <T> broadcastFlow(
filter: IntentFilter,
user: UserHandle? = null,
@Context.RegisterReceiverFlags flags: Int = Context.RECEIVER_EXPORTED,
permission: String? = null,
map: (Intent, BroadcastReceiver) -> T,
): Flow<T> = conflatedCallbackFlow {
val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
trySendWithFailureLogging(map(intent, this), TAG)
}
}
registerReceiver(
receiver,
filter,
bgExecutor,
user,
flags,
permission,
)
awaitClose {
unregisterReceiver(receiver)
}
}
/**
* Returns a [Flow] that, when collected, emits `Unit` whenever a broadcast matching [filter] is
* received.
*
* @see registerReceiver
*/
@JvmOverloads
fun broadcastFlow(
filter: IntentFilter,
user: UserHandle? = null,
@Context.RegisterReceiverFlags flags: Int = Context.RECEIVER_EXPORTED,
permission: String? = null,
): Flow<Unit> = broadcastFlow(filter, user, flags, permission) { _, _ -> Unit }
private fun checkFilter(filter: IntentFilter) { private fun checkFilter(filter: IntentFilter) {
val sb = StringBuilder() val sb = StringBuilder()
if (filter.countActions() == 0) sb.append("Filter must contain at least one action. ") if (filter.countActions() == 0) sb.append("Filter must contain at least one action. ")

View File

@@ -18,6 +18,7 @@ package com.android.systemui.broadcast
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
@@ -32,22 +33,27 @@ import com.android.systemui.dump.DumpManager
import com.android.systemui.settings.UserTracker import com.android.systemui.settings.UserTracker
import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.time.FakeSystemClock import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.Executor
import junit.framework.Assert.assertSame import junit.framework.Assert.assertSame
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runBlockingTest
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor import org.mockito.ArgumentCaptor
import org.mockito.Captor import org.mockito.Captor
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.anyInt import org.mockito.Mockito.anyInt
import org.mockito.Mockito.inOrder import org.mockito.Mockito.inOrder
import org.mockito.Mockito.mock import org.mockito.Mockito.mock
import org.mockito.Mockito.never import org.mockito.Mockito.never
import org.mockito.Mockito.verify import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
import java.util.concurrent.Executor
@RunWith(AndroidTestingRunner::class) @RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper @TestableLooper.RunWithLooper
@@ -381,6 +387,39 @@ class BroadcastDispatcherTest : SysuiTestCase() {
.clearPendingRemoval(broadcastReceiver, user1.identifier) .clearPendingRemoval(broadcastReceiver, user1.identifier)
} }
@Test
fun testBroadcastFlow() = runBlockingTest {
val flow = broadcastDispatcher.broadcastFlow(intentFilter, user1) { intent, receiver ->
intent to receiver
}
// Collect the values into collectedValues.
val collectedValues = mutableListOf<Pair<Intent, BroadcastReceiver>>()
val job = launch {
flow.collect { collectedValues.add(it) }
}
testableLooper.processAllMessages()
verify(mockUBRUser1).registerReceiver(capture(argumentCaptor), eq(DEFAULT_FLAG))
val receiver = argumentCaptor.value.receiver
// Simulate fake broadcasted intents.
val fakeIntents = listOf<Intent>(mock(), mock(), mock())
fakeIntents.forEach { receiver.onReceive(mockContext, it) }
// The intents should have been collected.
advanceUntilIdle()
val expectedValues = fakeIntents.map { it to receiver }
assertThat(collectedValues).containsExactlyElementsIn(expectedValues)
// Stop the collection.
job.cancel()
testableLooper.processAllMessages()
verify(mockUBRUser1).unregisterReceiver(receiver)
}
private fun setUserMock(mockContext: Context, user: UserHandle) { private fun setUserMock(mockContext: Context, user: UserHandle) {
`when`(mockContext.user).thenReturn(user) `when`(mockContext.user).thenReturn(user)
`when`(mockContext.userId).thenReturn(user.identifier) `when`(mockContext.userId).thenReturn(user.identifier)