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