diff options
| author | 2023-03-22 20:57:49 +0000 | |
|---|---|---|
| committer | 2023-04-19 18:56:37 +0000 | |
| commit | 2bd65e09d8702807bd34d2b6f4b7ecbe0ab226af (patch) | |
| tree | 905084f068905a1a714719b1d41d1fd8ed96f2ea | |
| parent | 68e0f1c2bf3eb8783213264408ec0bd1c725eb7e (diff) | |
Creates a service in SysUI (WalletContextualLocationsService) that can send store locations
to AiAi.
Bug: 270394156
Change-Id: I5e67705cd38db55ecd627c7c2358a6a2815b9587
5 files changed, 237 insertions, 2 deletions
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 09c62d01cdaa..f4e5692fc841 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -389,6 +389,9 @@ <service android:name="SystemUIService" android:exported="true" /> + <service android:name=".wallet.controller.WalletContextualLocationsService" + android:exported="true" + /> <!-- Service for dumping extremely verbose content during a bug report --> <service android:name=".dump.SystemUIAuxiliaryDumpService" diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualLocationsService.kt b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualLocationsService.kt new file mode 100644 index 000000000000..1c17fc34a34a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualLocationsService.kt @@ -0,0 +1,93 @@ +package com.android.systemui.wallet.controller + +import android.content.Intent +import android.os.IBinder +import android.util.Log +import androidx.annotation.VisibleForTesting +import androidx.lifecycle.LifecycleService +import androidx.lifecycle.lifecycleScope +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * Serves as an intermediary between QuickAccessWalletService and ContextualCardManager (in PCC). + * When QuickAccessWalletService has a list of store locations, WalletContextualLocationsService + * will send them to ContextualCardManager. When the user enters a store location, this Service + * class will be notified, and WalletContextualSuggestionsController will be updated. + */ +class WalletContextualLocationsService +@Inject +constructor( + private val controller: WalletContextualSuggestionsController, + private val featureFlags: FeatureFlags, +) : LifecycleService() { + private var listener: IWalletCardsUpdatedListener? = null + private var scope: CoroutineScope = this.lifecycleScope + + @VisibleForTesting + constructor( + controller: WalletContextualSuggestionsController, + featureFlags: FeatureFlags, + scope: CoroutineScope, + ) : this(controller, featureFlags) { + this.scope = scope + } + + override fun onBind(intent: Intent): IBinder { + super.onBind(intent) + scope.launch { + controller.allWalletCards.collect { cards -> + val cardsSize = cards.size + Log.i(TAG, "Number of cards registered $cardsSize") + listener?.registerNewWalletCards(cards) + } + } + return binder + } + + override fun onDestroy() { + super.onDestroy() + listener = null + } + + @VisibleForTesting + fun addWalletCardsUpdatedListenerInternal(listener: IWalletCardsUpdatedListener) { + if (!featureFlags.isEnabled(Flags.ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS)) { + return + } + this.listener = listener // Currently, only one listener at a time is supported + // Sends WalletCard objects from QuickAccessWalletService to the listener + val cards = controller.allWalletCards.value + if (!cards.isEmpty()) { + val cardsSize = cards.size + Log.i(TAG, "Number of cards registered $cardsSize") + listener.registerNewWalletCards(cards) + } + } + + @VisibleForTesting + fun onWalletContextualLocationsStateUpdatedInternal(storeLocations: List<String>) { + if (!featureFlags.isEnabled(Flags.ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS)) { + return + } + Log.i(TAG, "Entered store $storeLocations") + controller.setSuggestionCardIds(storeLocations.toSet()) + } + + private val binder: IWalletContextualLocationsService.Stub + = object : IWalletContextualLocationsService.Stub() { + override fun addWalletCardsUpdatedListener(listener: IWalletCardsUpdatedListener) { + addWalletCardsUpdatedListenerInternal(listener) + } + override fun onWalletContextualLocationsStateUpdated(storeLocations: List<String>) { + onWalletContextualLocationsStateUpdatedInternal(storeLocations) + } + } + + companion object { + private const val TAG = "WalletContextualLocationsService" + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt index 518f5a774d7f..b3ad9b0c6a37 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt +++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt @@ -36,6 +36,7 @@ import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.emptyFlow @@ -57,7 +58,8 @@ constructor( ) { private val cardsReceivedCallbacks: MutableSet<(List<WalletCard>) -> Unit> = mutableSetOf() - private val allWalletCards: Flow<List<WalletCard>> = + /** All potential cards. */ + val allWalletCards: StateFlow<List<WalletCard>> = if (featureFlags.isEnabled(Flags.ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS)) { // TODO(b/237409756) determine if we should debounce this so we don't call the service // too frequently. Also check if the list actually changed before calling callbacks. @@ -107,12 +109,13 @@ constructor( emptyList() ) } else { - emptyFlow() + MutableStateFlow<List<WalletCard>>(emptyList()).asStateFlow() } private val _suggestionCardIds: MutableStateFlow<Set<String>> = MutableStateFlow(emptySet()) private val contextualSuggestionsCardIds: Flow<Set<String>> = _suggestionCardIds.asStateFlow() + /** Contextually-relevant cards. */ val contextualSuggestionCards: Flow<List<WalletCard>> = combine(allWalletCards, contextualSuggestionsCardIds) { cards, ids -> val ret = diff --git a/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java b/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java index 9429d8991090..efba3e5d9c34 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java +++ b/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java @@ -35,6 +35,8 @@ import dagger.multibindings.ClassKey; import dagger.multibindings.IntoMap; import dagger.multibindings.StringKey; +import android.app.Service; +import com.android.systemui.wallet.controller.WalletContextualLocationsService; /** * Module for injecting classes in Wallet. @@ -42,6 +44,12 @@ import dagger.multibindings.StringKey; @Module public abstract class WalletModule { + @Binds + @IntoMap + @ClassKey(WalletContextualLocationsService.class) + abstract Service bindWalletContextualLocationsService( + WalletContextualLocationsService service); + /** */ @Binds @IntoMap diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualLocationsServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualLocationsServiceTest.kt new file mode 100644 index 000000000000..af1d7881195e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualLocationsServiceTest.kt @@ -0,0 +1,128 @@ +package com.android.systemui.wallet.controller + +import android.app.PendingIntent +import android.content.Intent +import android.graphics.Bitmap +import android.graphics.drawable.Icon +import android.os.Looper +import android.service.quickaccesswallet.WalletCard +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.TestCoroutineScope +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.Mockito.anySet +import org.mockito.Mockito.doNothing +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@RunWith(JUnit4::class) +@SmallTest +@kotlinx.coroutines.ExperimentalCoroutinesApi +class WalletContextualLocationsServiceTest : SysuiTestCase() { + @Mock private lateinit var controller: WalletContextualSuggestionsController + private var featureFlags = FakeFeatureFlags() + private lateinit var underTest: WalletContextualLocationsService + private lateinit var testScope: TestScope + private var listenerRegisteredCount: Int = 0 + private val listener: IWalletCardsUpdatedListener.Stub = object : IWalletCardsUpdatedListener.Stub() { + override fun registerNewWalletCards(cards: List<WalletCard?>) { + listenerRegisteredCount++ + } + } + + @Before + @kotlinx.coroutines.ExperimentalCoroutinesApi + fun setUp() { + MockitoAnnotations.initMocks(this) + doReturn(fakeWalletCards).whenever(controller).allWalletCards + doNothing().whenever(controller).setSuggestionCardIds(anySet()) + + if (Looper.myLooper() == null) Looper.prepare() + + testScope = TestScope() + featureFlags.set(Flags.ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS, true) + listenerRegisteredCount = 0 + + underTest = WalletContextualLocationsService(controller, featureFlags, testScope.backgroundScope) + } + + @Test + @kotlinx.coroutines.ExperimentalCoroutinesApi + fun addListener() = testScope.runTest { + underTest.addWalletCardsUpdatedListenerInternal(listener) + assertThat(listenerRegisteredCount).isEqualTo(1) + } + + @Test + @kotlinx.coroutines.ExperimentalCoroutinesApi + fun addStoreLocations() = testScope.runTest { + underTest.onWalletContextualLocationsStateUpdatedInternal(ArrayList<String>()) + verify(controller, times(1)).setSuggestionCardIds(anySet()) + } + + @Test + @kotlinx.coroutines.ExperimentalCoroutinesApi + fun updateListenerAndLocationsState() = testScope.runTest { + // binds to the service and adds a listener + val underTestStub = getInterface + underTestStub.addWalletCardsUpdatedListener(listener) + assertThat(listenerRegisteredCount).isEqualTo(1) + + // sends a list of card IDs to the controller + underTestStub.onWalletContextualLocationsStateUpdated(ArrayList<String>()) + verify(controller, times(1)).setSuggestionCardIds(anySet()) + + // adds another listener + fakeWalletCards.update{ updatedFakeWalletCards } + runCurrent() + assertThat(listenerRegisteredCount).isEqualTo(2) + + // sends another list of card IDs to the controller + underTestStub.onWalletContextualLocationsStateUpdated(ArrayList<String>()) + verify(controller, times(2)).setSuggestionCardIds(anySet()) + } + + private val fakeWalletCards: MutableStateFlow<List<WalletCard>> + get() { + val intent = Intent(getContext(), WalletContextualLocationsService::class.java) + val pi: PendingIntent = PendingIntent.getActivity(getContext(), 0, intent, PendingIntent.FLAG_IMMUTABLE) + val icon: Icon = Icon.createWithBitmap(Bitmap.createBitmap(70, 50, Bitmap.Config.ARGB_8888)) + val walletCards: ArrayList<WalletCard> = ArrayList<WalletCard>() + walletCards.add(WalletCard.Builder("card1", icon, "card", pi).build()) + walletCards.add(WalletCard.Builder("card2", icon, "card", pi).build()) + return MutableStateFlow<List<WalletCard>>(walletCards) + } + + private val updatedFakeWalletCards: List<WalletCard> + get() { + val intent = Intent(getContext(), WalletContextualLocationsService::class.java) + val pi: PendingIntent = PendingIntent.getActivity(getContext(), 0, intent, PendingIntent.FLAG_IMMUTABLE) + val icon: Icon = Icon.createWithBitmap(Bitmap.createBitmap(70, 50, Bitmap.Config.ARGB_8888)) + val walletCards: ArrayList<WalletCard> = ArrayList<WalletCard>() + walletCards.add(WalletCard.Builder("card3", icon, "card", pi).build()) + return walletCards + } + + private val getInterface: IWalletContextualLocationsService + get() { + val intent = Intent() + return IWalletContextualLocationsService.Stub.asInterface(underTest.onBind(intent)) + } +}
\ No newline at end of file |