1 /*
<lambda>null2  * Copyright (C) 2024 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.wallpaper.picker.broadcast
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 android.os.Handler
25 import android.os.HandlerThread
26 import android.os.Looper
27 import android.os.Process
28 import androidx.test.core.app.ApplicationProvider
29 import androidx.test.filters.SmallTest
30 import com.android.wallpaper.picker.di.modules.SharedAppModule.Companion.BroadcastRunning
31 import com.google.common.truth.Truth.assertThat
32 import java.util.concurrent.Executor
33 import kotlinx.coroutines.ExperimentalCoroutinesApi
34 import kotlinx.coroutines.launch
35 import kotlinx.coroutines.test.advanceUntilIdle
36 import kotlinx.coroutines.test.runTest
37 import org.junit.Before
38 import org.junit.Test
39 import org.junit.runner.RunWith
40 import org.robolectric.RobolectricTestRunner
41 import org.robolectric.Shadows
42 
43 @OptIn(ExperimentalCoroutinesApi::class)
44 @SmallTest
45 @RunWith(RobolectricTestRunner::class)
46 class BroadcastDispatcherTest {
47 
48     private lateinit var mContext: Context
49 
50     private lateinit var broadcastReceiver: BroadcastReceiver
51     private lateinit var intentFilter: IntentFilter
52     private lateinit var broadcastDispatcher: BroadcastDispatcher
53 
54     private lateinit var mainExecutor: Executor
55 
56     @Before
57     fun setUp() {
58         mContext = ApplicationProvider.getApplicationContext()
59         val backgroundRunningLooper = provideBroadcastRunningLooper()
60         mainExecutor = provideBroadcastRunningExecutor(backgroundRunningLooper)
61         broadcastReceiver =
62             object : BroadcastReceiver() {
63                 override fun onReceive(context: Context?, intent: Intent?) {}
64             }
65         broadcastDispatcher = BroadcastDispatcher(mContext, backgroundRunningLooper)
66     }
67 
68     @Test
69     fun testBroadcastFlow_emitsValues() = runTest {
70         intentFilter = IntentFilter("ACTION_TEST")
71         val testIntent = Intent("ACTION_TEST")
72 
73         val flow =
74             broadcastDispatcher.broadcastFlow(intentFilter) { intent, receiver ->
75                 intent to receiver
76             }
77         val collectedValues = mutableListOf<Pair<Intent, BroadcastReceiver>>()
78         val job = launch { flow.collect { collectedValues.add(it) } }
79 
80         // Waits for the flow collection coroutine to start and collect any immediate emissions
81         advanceUntilIdle()
82 
83         val shadowApplication =
84             Shadows.shadowOf(ApplicationProvider.getApplicationContext() as Application)
85         val receivers = shadowApplication.registeredReceivers
86         val capturedReceiver = receivers.find { it.broadcastReceiver is BroadcastReceiver }
87         assertThat(capturedReceiver).isNotNull()
88         capturedReceiver?.let { collectedValues.add(testIntent to it.broadcastReceiver) }
89 
90         // Processes any additional tasks that may have been scheduled as a result of
91         // adding to collectedValues
92         advanceUntilIdle()
93 
94         val expectedValues = listOf(testIntent to capturedReceiver?.broadcastReceiver)
95         assertThat(expectedValues).isEqualTo(collectedValues)
96         job.cancel()
97     }
98 
99     @Test
100     fun testRegisterReceiver() {
101         intentFilter = IntentFilter(Intent.ACTION_BOOT_COMPLETED)
102 
103         broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter)
104 
105         val shadowApplication =
106             Shadows.shadowOf(ApplicationProvider.getApplicationContext() as Application)
107         val receivers = shadowApplication.registeredReceivers
108         val isRegistered = receivers.any { it.broadcastReceiver == broadcastReceiver }
109         assertThat(isRegistered).isEqualTo(true)
110     }
111 
112     @Test
113     fun testUnregisterReceiver() {
114         intentFilter = IntentFilter(Intent.ACTION_BOOT_COMPLETED)
115 
116         broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter)
117         broadcastDispatcher.unregisterReceiver(broadcastReceiver)
118 
119         val shadowApplication =
120             Shadows.shadowOf(ApplicationProvider.getApplicationContext() as Application)
121         val receivers = shadowApplication.registeredReceivers
122         val isUnregistered = receivers.none { it.broadcastReceiver == broadcastReceiver }
123         assertThat(isUnregistered).isEqualTo(true)
124     }
125 
126     @Test(expected = IllegalArgumentException::class)
127     fun testFilterMustNotContainDataType() {
128         val testFilter = IntentFilter(TEST_ACTION).apply { addDataType(TEST_TYPE) }
129 
130         broadcastDispatcher.registerReceiver(broadcastReceiver, testFilter)
131     }
132 
133     @Test(expected = IllegalArgumentException::class)
134     fun testFilterMustNotSetPriority() {
135         val testFilter =
136             IntentFilter(TEST_ACTION).apply { priority = IntentFilter.SYSTEM_HIGH_PRIORITY }
137 
138         broadcastDispatcher.registerReceiver(broadcastReceiver, testFilter)
139     }
140 
141     private fun provideBroadcastRunningLooper(): Looper {
142         return HandlerThread("BroadcastRunning", Process.THREAD_PRIORITY_BACKGROUND)
143             .apply {
144                 start()
145                 looper.setSlowLogThresholdMs(
146                     BROADCAST_SLOW_DISPATCH_THRESHOLD,
147                     BROADCAST_SLOW_DELIVERY_THRESHOLD,
148                 )
149             }
150             .looper
151     }
152 
153     private fun provideBroadcastRunningExecutor(@BroadcastRunning looper: Looper?): Executor {
154         val handler = Handler(looper ?: Looper.getMainLooper())
155         return Executor { command -> handler.post(command) }
156     }
157 
158     companion object {
159         private const val BROADCAST_SLOW_DISPATCH_THRESHOLD = 1000L
160         private const val BROADCAST_SLOW_DELIVERY_THRESHOLD = 1000L
161         const val TEST_ACTION = "TEST_ACTION"
162         const val TEST_TYPE = "test/type"
163     }
164 }
165