Creates a service in SysUI (WalletContextualLocationsService) that can send store locations
to AiAi. Bug: 270394156 Change-Id: I5e67705cd38db55ecd627c7c2358a6a2815b9587
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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 =
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user