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

Commit dc95a62e authored by Tao Wu's avatar Tao Wu
Browse files

growth: Refactor GrowthInteractor broadcast logic and send to current user.

The broadcast for device entry is now sent using `sendBroadcastAsUser` with `UserHandle.CURRENT`. The delayed broadcast mechanism has been refactored to use `transformLatest` for improved cancellation handling when the device entry state changes.

Bug: 437915291
Flag: com.android.systemui.enable_desktop_growth

Change-Id: Ia5520605ce4a068f8362c3531ea38ab983556d0d
parent 1b4c0173
Loading
Loading
Loading
Loading
+31 −7
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.growth.domain.interactor

import android.content.Intent
import android.os.UserHandle
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
@@ -66,6 +67,7 @@ class GrowthInteractorTest : SysuiTestCase() {
    private lateinit var underTest: GrowthInteractor

    @Captor private lateinit var intentArgumentCaptor: ArgumentCaptor<Intent>
    @Captor private lateinit var userHandleArgumentCaptor: ArgumentCaptor<UserHandle>

    @Before
    fun setUp() {
@@ -83,7 +85,12 @@ class GrowthInteractorTest : SysuiTestCase() {
            advanceTimeBy(GROWTH_BROADCAST_DELAY.plus(DURATION_50_MILLIS))
            runCurrent()

            verify(broadcastSender, times(1)).sendBroadcast(capture(intentArgumentCaptor))
            verify(broadcastSender, times(1))
                .sendBroadcastAsUser(
                    capture(intentArgumentCaptor),
                    capture(userHandleArgumentCaptor)
                )
            assertThat(userHandleArgumentCaptor.value).isEqualTo(UserHandle.CURRENT)
            assertThat(intentArgumentCaptor.value.action)
                .isEqualTo(GrowthInteractor.ACTION_DEVICE_ENTERED_DIRECTLY)
            assertThat(intentArgumentCaptor.value.`package`).isNull()
@@ -103,7 +110,12 @@ class GrowthInteractorTest : SysuiTestCase() {
            advanceTimeBy(GROWTH_BROADCAST_DELAY.plus(DURATION_50_MILLIS))
            runCurrent()

            verify(broadcastSender, times(1)).sendBroadcast(capture(intentArgumentCaptor))
            verify(broadcastSender, times(1))
                .sendBroadcastAsUser(
                    capture(intentArgumentCaptor),
                    capture(userHandleArgumentCaptor)
                )
            assertThat(userHandleArgumentCaptor.value).isEqualTo(UserHandle.CURRENT)
            assertThat(intentArgumentCaptor.value.action)
                .isEqualTo(GrowthInteractor.ACTION_DEVICE_ENTERED_DIRECTLY)
            assertThat(intentArgumentCaptor.value.`package`).isNull()
@@ -120,7 +132,12 @@ class GrowthInteractorTest : SysuiTestCase() {
            advanceTimeBy(GROWTH_BROADCAST_DELAY.plus(DURATION_50_MILLIS))
            runCurrent()

            verify(broadcastSender, times(1)).sendBroadcast(capture(intentArgumentCaptor))
            verify(broadcastSender, times(1))
                .sendBroadcastAsUser(
                    capture(intentArgumentCaptor),
                    capture(userHandleArgumentCaptor)
                )
            assertThat(userHandleArgumentCaptor.value).isEqualTo(UserHandle.CURRENT)
            assertThat(intentArgumentCaptor.value.action)
                .isEqualTo(GrowthInteractor.ACTION_DEVICE_ENTERED_DIRECTLY)
            assertThat(intentArgumentCaptor.value.`package`).isNull()
@@ -137,7 +154,12 @@ class GrowthInteractorTest : SysuiTestCase() {
            advanceTimeBy(GROWTH_BROADCAST_DELAY.plus(DURATION_50_MILLIS))
            runCurrent()

            verify(broadcastSender, times(1)).sendBroadcast(capture(intentArgumentCaptor))
            verify(broadcastSender, times(1))
                .sendBroadcastAsUser(
                    capture(intentArgumentCaptor),
                    capture(userHandleArgumentCaptor)
                )
            assertThat(userHandleArgumentCaptor.value).isEqualTo(UserHandle.CURRENT)
            assertThat(intentArgumentCaptor.value.action)
                .isEqualTo(GrowthInteractor.ACTION_DEVICE_ENTERED_DIRECTLY)
            assertThat(intentArgumentCaptor.value.`package`).isNull()
@@ -148,13 +170,15 @@ class GrowthInteractorTest : SysuiTestCase() {
    fun onDeviceNotEnteredDirectly_doNotSendBroadcast_onLockscreen() =
        kosmos.runTest {
            overrideResources(GROWTH_APP_PACKAGE_NAME, GROWTH_RECEIVER_CLASS_NAME)
            underTest = kosmos.growthInteractor
            underTest.activateIn(kosmos.testScope)
            setDeviceEntered()
            advanceTimeBy(GROWTH_BROADCAST_DELAY.plus(DURATION_50_MILLIS))
            clearInvocations(broadcastSender)

            setScene(Scenes.Lockscreen)
            advanceTimeBy(GROWTH_BROADCAST_DELAY.plus(DURATION_50_MILLIS))
            verify(broadcastSender, never()).sendBroadcast(any(), any())
            verify(broadcastSender, never()).sendBroadcastAsUser(any(), any())
        }

    @Test
@@ -166,12 +190,12 @@ class GrowthInteractorTest : SysuiTestCase() {
            setDeviceEntered()
            advanceTimeBy(DURATION_50_MILLIS)
            runCurrent()
            verify(broadcastSender, never()).sendBroadcast(any(), any())
            verify(broadcastSender, never()).sendBroadcastAsUser(any(), any())

            setScene(Scenes.Lockscreen)
            advanceTimeBy(GROWTH_BROADCAST_DELAY.plus(DURATION_50_MILLIS))
            runCurrent()
            verify(broadcastSender, never()).sendBroadcast(any(), any())
            verify(broadcastSender, never()).sendBroadcastAsUser(any(), any())
        }

    private fun overrideResources(packageName: String, receiverClassName: String) {
+25 −20
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.systemui.growth.domain.interactor
import android.content.ComponentName
import android.content.Intent
import android.content.res.Resources
import android.os.UserHandle
import androidx.annotation.VisibleForTesting
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.dagger.SysUISingleton
@@ -28,11 +29,9 @@ import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.res.R
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.transformLatest

/** Interactor to communicate with the growth app. */
@SysUISingleton
@@ -49,33 +48,39 @@ constructor(
    private val growthBroadcastDelayMillis =
        resources.getInteger(R.integer.config_growthBroadcastDelayMillis)

    private var sendBroadcastJob: Job? = null

    @OptIn(ExperimentalCoroutinesApi::class)
    override suspend fun onActivated(): Nothing {
        deviceEntryInteractor.get().isDeviceEnteredDirectly.collect {
            // Cancel any existing job before launching a new one.
            sendBroadcastJob?.cancel()
            sendBroadcastJob = null

            if (it) {
                // Launch the delayed task.
                sendBroadcastJob = coroutineScope {
                    launch {
        deviceEntryInteractor
            // When the device is entered directly (`isEntered` is true), wait for a delay and then
            // emit. `transformLatest` will cancel the delay if `isDeviceEnteredDirectly` emits a
            // new value (e.g. `false` when the device is locked) before the delay is finished,
            // thus cancelling the broadcast.
            .get()
            .isDeviceEnteredDirectly
            .transformLatest { isEntered ->
                if (isEntered) {
                    delay(growthBroadcastDelayMillis.toLong())
                        sendBroadcast()
                    }
                    emit(Unit)
                }
            }
            .collect {
                sendBroadcast()
            }
        // The underlying flow should never complete, so this line should not be reachable.
        throw IllegalStateException("isDeviceEnteredDirectly flow completed unexpectedly")
    }

    /**
     * Sends a broadcast to the growth app for the current user to notify that the device has been
     * entered directly. The broadcast is explicit if a package and receiver class are configured.
     */
    private suspend fun sendBroadcast() {
        // Broadcast the device entered event.
        val intent = Intent().apply { setAction(ACTION_DEVICE_ENTERED_DIRECTLY) }
        if (growthAppPackageName.isNotEmpty() && growthReceiverClassName.isNotEmpty()) {
            intent.setComponent(ComponentName(growthAppPackageName, growthReceiverClassName))
        }
        broadcastSender.sendBroadcast(intent)
        broadcastSender.sendBroadcastAsUser(intent, UserHandle.CURRENT)
    }

    companion object {