Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9cd45257 authored by Chaohui Wang's avatar Chaohui Wang
Browse files

Fix DisposableBroadcastReceiverAsUser

In Composable, we should use LaunchedEffect + collect(), instead of
collectLatestWithLifecycle(), to ensure re-enter safe.
Lifecycle is managed by LaunchedEffect.

Fix: 382637350
Flag: EXEMPT bug fix
Test: manual - on All apps
Test: unit test
Change-Id: Ic60192e58269776ffb07ae6123b261e2ddb09865
parent f40bd9f5
Loading
Loading
Loading
Loading
+5 −4
Original line number Diff line number Diff line
@@ -21,9 +21,8 @@ import android.content.Intent
import android.content.IntentFilter
import android.os.UserHandle
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
import com.android.settingslib.spaprivileged.framework.common.broadcastReceiverAsUserFlow

/**
@@ -35,6 +34,8 @@ fun DisposableBroadcastReceiverAsUser(
    userHandle: UserHandle,
    onReceive: (Intent) -> Unit,
) {
    LocalContext.current.broadcastReceiverAsUserFlow(intentFilter, userHandle)
        .collectLatestWithLifecycle(LocalLifecycleOwner.current, action = onReceive)
    val context = LocalContext.current
    LaunchedEffect(Unit) {
        context.broadcastReceiverAsUserFlow(intentFilter, userHandle).collect(onReceive)
    }
}
+46 −26
Original line number Diff line number Diff line
@@ -22,10 +22,11 @@ import android.content.Intent
import android.content.IntentFilter
import android.os.UserHandle
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.testing.TestLifecycleOwner
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
@@ -38,12 +39,12 @@ import org.mockito.kotlin.mock

@RunWith(AndroidJUnit4::class)
class DisposableBroadcastReceiverAsUserTest {
    @get:Rule
    val composeTestRule = createComposeRule()
    @get:Rule val composeTestRule = createComposeRule()

    private var registeredBroadcastReceiver: BroadcastReceiver? = null

    private val context = mock<Context> {
    private val context =
        mock<Context> {
            on {
                registerReceiverAsUser(
                    any(),
@@ -53,19 +54,24 @@ class DisposableBroadcastReceiverAsUserTest {
                    isNull(),
                    eq(Context.RECEIVER_NOT_EXPORTED),
                )
        } doAnswer {
            } doAnswer
                {
                    registeredBroadcastReceiver = it.arguments[0] as BroadcastReceiver
                    null
                }

            on { unregisterReceiver(any()) } doAnswer
                {
                    if (registeredBroadcastReceiver === it.arguments[0]) {
                        registeredBroadcastReceiver = null
                    }
                }
        }

    @Test
    fun broadcastReceiver_registered() {
        composeTestRule.setContent {
            CompositionLocalProvider(
                LocalContext provides context,
                LocalLifecycleOwner provides TestLifecycleOwner(),
            ) {
            CompositionLocalProvider(LocalContext provides context) {
                DisposableBroadcastReceiverAsUser(INTENT_FILTER, USER_HANDLE) {}
            }
        }
@@ -77,10 +83,7 @@ class DisposableBroadcastReceiverAsUserTest {
    fun broadcastReceiver_isCalledOnReceive() {
        var onReceiveIsCalled = false
        composeTestRule.setContent {
            CompositionLocalProvider(
                LocalContext provides context,
                LocalLifecycleOwner provides TestLifecycleOwner(),
            ) {
            CompositionLocalProvider(LocalContext provides context) {
                DisposableBroadcastReceiverAsUser(INTENT_FILTER, USER_HANDLE) {
                    onReceiveIsCalled = true
                }
@@ -92,6 +95,23 @@ class DisposableBroadcastReceiverAsUserTest {
        composeTestRule.waitUntil { onReceiveIsCalled }
    }

    @Test
    fun broadcastReceiver_unregistered() {
        var isBroadcastReceiverRegistered by mutableStateOf(true)
        composeTestRule.setContent {
            if (isBroadcastReceiverRegistered) {
                CompositionLocalProvider(LocalContext provides context) {
                    DisposableBroadcastReceiverAsUser(INTENT_FILTER, USER_HANDLE) {}
                }
            }
        }
        composeTestRule.waitUntil { registeredBroadcastReceiver != null }

        isBroadcastReceiverRegistered = false

        composeTestRule.waitUntil { registeredBroadcastReceiver == null }
    }

    private companion object {
        val USER_HANDLE: UserHandle = UserHandle.of(0)