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

Commit 0eb4962e authored by Brad Hinegardner's avatar Brad Hinegardner Committed by Android (Google) Code Review
Browse files

Merge "Wallet quick affordance binder calls on main thread" into main

parents 9f4ce7d6 5fe33e59
Loading
Loading
Loading
Loading
+41 −53
Original line number Diff line number Diff line
@@ -29,17 +29,16 @@ import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.wallet.controller.QuickAccessWalletController
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runBlockingTest
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -48,7 +47,6 @@ import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
@@ -56,26 +54,32 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
    @Mock private lateinit var walletController: QuickAccessWalletController
    @Mock private lateinit var activityStarter: ActivityStarter

    private lateinit var testDispatcher: TestDispatcher
    private lateinit var testScope: TestScope

    private lateinit var underTest: QuickAccessWalletKeyguardQuickAffordanceConfig

    @Before
    fun setUp() {
        MockitoAnnotations.initMocks(this)

        testDispatcher = StandardTestDispatcher()
        testScope = TestScope(testDispatcher)

        underTest =
            QuickAccessWalletKeyguardQuickAffordanceConfig(
                context,
                testDispatcher,
                walletController,
                activityStarter,
            )
    }

    @Test
    fun affordance_keyguardShowing_hasWalletCard_visibleModel() = runBlockingTest {
    fun affordance_keyguardShowing_hasWalletCard_visibleModel() = testScope.runTest {
        setUpState()
        var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null

        val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
        val latest by collectLastValue(underTest.lockScreenState)

        val visibleModel = latest as KeyguardQuickAffordanceConfig.LockScreenState.Visible
        assertThat(visibleModel.icon)
@@ -88,28 +92,22 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
                        ),
                )
            )
        job.cancel()
    }

    @Test
    fun affordance_keyguardShowing_hasNonPaymentCard_modelIsNone() =
        runTest(UnconfinedTestDispatcher()) {
    fun affordance_keyguardShowing_hasNonPaymentCard_modelIsNone() = testScope.runTest {
            setUpState(cardType = WalletCard.CARD_TYPE_NON_PAYMENT)
            var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null

            val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
            val latest by collectLastValue(underTest.lockScreenState)

            assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
            job.cancel()
        }

    @Test
    fun affordance_keyguardShowing_hasPaymentCard_visibleModel() =
        runTest(UnconfinedTestDispatcher()) {
    fun affordance_keyguardShowing_hasPaymentCard_visibleModel() = testScope.runTest {
        setUpState(cardType = WalletCard.CARD_TYPE_PAYMENT)
            var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null

            val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
        val latest by collectLastValue(underTest.lockScreenState)

        val visibleModel = latest as KeyguardQuickAffordanceConfig.LockScreenState.Visible
        assertThat(visibleModel.icon)
@@ -122,43 +120,33 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
                        ),
                )
            )
            job.cancel()
    }

    @Test
    fun affordance_walletFeatureNotEnabled_modelIsNone() = runBlockingTest {
    fun affordance_walletFeatureNotEnabled_modelIsNone() = testScope.runTest {
        setUpState(isWalletFeatureAvailable = false)
        var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null

        val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
        val latest by collectLastValue(underTest.lockScreenState)

        assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)

        job.cancel()
    }

    @Test
    fun affordance_queryNotSuccessful_modelIsNone() = runBlockingTest {
    fun affordance_queryNotSuccessful_modelIsNone() = testScope.runTest {
        setUpState(isWalletQuerySuccessful = false)
        var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null

        val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
        val latest by collectLastValue(underTest.lockScreenState)

        assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)

        job.cancel()
    }

    @Test
    fun affordance_noSelectedCard_modelIsNone() = runBlockingTest {
    fun affordance_noSelectedCard_modelIsNone() = testScope.runTest {
        setUpState(hasSelectedCard = false)
        var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null

        val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
        val latest by collectLastValue(underTest.lockScreenState)

        assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)

        job.cancel()
    }

    @Test
@@ -179,7 +167,7 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
    }

    @Test
    fun getPickerScreenState_default() = runTest {
    fun getPickerScreenState_default() = testScope.runTest {
        setUpState()

        assertThat(underTest.getPickerScreenState())
@@ -187,7 +175,7 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
    }

    @Test
    fun getPickerScreenState_unavailable() = runTest {
    fun getPickerScreenState_unavailable() = testScope.runTest {
        setUpState(
            isWalletServiceAvailable = false,
        )
@@ -197,7 +185,7 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
    }

    @Test
    fun getPickerScreenState_disabledWhenTheFeatureIsNotEnabled() = runTest {
    fun getPickerScreenState_disabledWhenTheFeatureIsNotEnabled() = testScope.runTest {
        setUpState(
            isWalletFeatureAvailable = false,
        )
@@ -207,7 +195,7 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
    }

    @Test
    fun getPickerScreenState_disabledWhenThereIsNoCard() = runTest {
    fun getPickerScreenState_disabledWhenThereIsNoCard() = testScope.runTest {
        setUpState(
            hasSelectedCard = false,
        )
+52 −23
Original line number Diff line number Diff line
@@ -32,13 +32,19 @@ import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.wallet.controller.QuickAccessWalletController
import com.android.systemui.wallet.util.getPaymentCards
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext

/** Quick access wallet quick affordance data source. */
@SysUISingleton
@@ -46,6 +52,7 @@ class QuickAccessWalletKeyguardQuickAffordanceConfig
@Inject
constructor(
    @Application private val context: Context,
    @Background private val backgroundDispatcher: CoroutineDispatcher,
    private val walletController: QuickAccessWalletController,
    private val activityStarter: ActivityStarter,
) : KeyguardQuickAffordanceConfig {
@@ -56,6 +63,7 @@ constructor(

    override val pickerIconResourceId = R.drawable.ic_wallet_lockscreen

    @OptIn(ExperimentalCoroutinesApi::class)
    override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
        conflatedCallbackFlow {
            val callback =
@@ -63,11 +71,7 @@ constructor(
                    override fun onWalletCardsRetrieved(response: GetWalletCardsResponse) {
                        val hasCards = getPaymentCards(response.walletCards)?.isNotEmpty() == true
                        trySendWithFailureLogging(
                            state(
                                isFeatureEnabled = isWalletAvailable(),
                                hasCard = hasCards,
                                tileIcon = walletController.walletClient.tileIcon,
                            ),
                            hasCards,
                            TAG,
                        )
                    }
@@ -75,7 +79,7 @@ constructor(
                    override fun onWalletCardRetrievalError(error: GetWalletCardsError) {
                        Log.e(TAG, "Wallet card retrieval error, message: \"${error?.message}\"")
                        trySendWithFailureLogging(
                            KeyguardQuickAffordanceConfig.LockScreenState.Hidden,
                            null,
                            TAG,
                        )
                    }
@@ -86,8 +90,12 @@ constructor(
                QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
                QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
            )

            withContext(backgroundDispatcher) {
                // Both must be called on background thread
                walletController.updateWalletPreference()
                walletController.queryWalletCards(callback)
            }

            awaitClose {
                walletController.unregisterWalletChangeObservers(
@@ -95,6 +103,19 @@ constructor(
                    QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
                )
            }
        }.flatMapLatest { hasCards ->
            // If hasCards is null, this indicates an error occurred upon card retrieval
            val state =
                if (hasCards == null) {
                    KeyguardQuickAffordanceConfig.LockScreenState.Hidden
                } else {
                    state(
                        isWalletAvailable(),
                        hasCards,
                        walletController.walletClient.tileIcon,
                    )
                }
            flowOf(state)
        }

    override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
@@ -131,12 +152,13 @@ constructor(
    }

    private suspend fun queryCards(): List<WalletCard> {
        return suspendCancellableCoroutine { continuation ->
        return withContext(backgroundDispatcher) {
            suspendCancellableCoroutine { continuation ->
                val callback =
                    object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
                        override fun onWalletCardsRetrieved(response: GetWalletCardsResponse) {
                            continuation.resumeWith(
                            Result.success(getPaymentCards(response.walletCards) ?: emptyList())
                                Result.success(getPaymentCards(response.walletCards))
                            )
                        }

@@ -144,12 +166,19 @@ constructor(
                            continuation.resumeWith(Result.success(emptyList()))
                        }
                    }
                // Must be called on background thread
                walletController.queryWalletCards(callback)
            }
        }
    }

    private fun isWalletAvailable() =
        with(walletController.walletClient) { isWalletServiceAvailable && isWalletFeatureAvailable }
    private suspend fun isWalletAvailable() =
        withContext(backgroundDispatcher) {
            with(walletController.walletClient) {
                // Must be called on background thread
                isWalletServiceAvailable && isWalletFeatureAvailable
            }
        }

    private fun state(
        isFeatureEnabled: Boolean,
+7 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.systemui.wallet.controller;
import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE;
import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE;

import android.annotation.WorkerThread;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
@@ -146,7 +147,9 @@ public class QuickAccessWalletController {

    /**
     * Update the "show wallet" preference.
     * This should not be called on the main thread.
     */
    @WorkerThread
    public void updateWalletPreference() {
        mWalletEnabled = mQuickAccessWalletClient.isWalletServiceAvailable()
                && mQuickAccessWalletClient.isWalletFeatureAvailable()
@@ -155,10 +158,12 @@ public class QuickAccessWalletController {

    /**
     * Query the wallet cards from {@link QuickAccessWalletClient}.
     * This should not be called on the main thread.
     *
     * @param cardsRetriever a callback to retrieve wallet cards.
     * @param maxCards the maximum number of cards requested from the QuickAccessWallet
     */
    @WorkerThread
    public void queryWalletCards(
            QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever, int maxCards) {
        if (mClock.elapsedRealtime() - mQawClientCreatedTimeMillis
@@ -182,9 +187,11 @@ public class QuickAccessWalletController {

    /**
     * Query the wallet cards from {@link QuickAccessWalletClient}.
     * This should not be called on the main thread.
     *
     * @param cardsRetriever a callback to retrieve wallet cards.
     */
    @WorkerThread
    public void queryWalletCards(
            QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever) {
        queryWalletCards(cardsRetriever, /* maxCards= */ 1);