1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 * 16 */ 17 18 package com.android.systemui.keyguard.data.quickaffordance 19 20 import android.content.Context 21 import android.graphics.drawable.Drawable 22 import android.service.quickaccesswallet.GetWalletCardsError 23 import android.service.quickaccesswallet.GetWalletCardsResponse 24 import android.service.quickaccesswallet.QuickAccessWalletClient 25 import android.service.quickaccesswallet.WalletCard 26 import android.util.Log 27 import com.android.systemui.R 28 import com.android.systemui.animation.Expandable 29 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging 30 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow 31 import com.android.systemui.common.shared.model.ContentDescription 32 import com.android.systemui.common.shared.model.Icon 33 import com.android.systemui.dagger.SysUISingleton 34 import com.android.systemui.dagger.qualifiers.Application 35 import com.android.systemui.plugins.ActivityStarter 36 import com.android.systemui.wallet.controller.QuickAccessWalletController 37 import com.android.systemui.wallet.util.getPaymentCards 38 import javax.inject.Inject 39 import kotlinx.coroutines.channels.awaitClose 40 import kotlinx.coroutines.flow.Flow 41 import kotlinx.coroutines.suspendCancellableCoroutine 42 43 /** Quick access wallet quick affordance data source. */ 44 @SysUISingleton 45 class QuickAccessWalletKeyguardQuickAffordanceConfig 46 @Inject 47 constructor( 48 @Application private val context: Context, 49 private val walletController: QuickAccessWalletController, 50 private val activityStarter: ActivityStarter, 51 ) : KeyguardQuickAffordanceConfig { 52 53 override val key: String = BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET 54 55 override fun pickerName(): String = context.getString(R.string.accessibility_wallet_button) 56 57 override val pickerIconResourceId = R.drawable.ic_wallet_lockscreen 58 59 override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> = 60 conflatedCallbackFlow { 61 val callback = 62 object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback { 63 override fun onWalletCardsRetrieved(response: GetWalletCardsResponse) { 64 val hasCards = getPaymentCards(response.walletCards)?.isNotEmpty() == true 65 trySendWithFailureLogging( 66 state( 67 isFeatureEnabled = isWalletAvailable(), 68 hasCard = hasCards, 69 tileIcon = walletController.walletClient.tileIcon, 70 ), 71 TAG, 72 ) 73 } 74 75 override fun onWalletCardRetrievalError(error: GetWalletCardsError) { 76 Log.e(TAG, "Wallet card retrieval error, message: \"${error?.message}\"") 77 trySendWithFailureLogging( 78 KeyguardQuickAffordanceConfig.LockScreenState.Hidden, 79 TAG, 80 ) 81 } 82 } 83 84 walletController.setupWalletChangeObservers( 85 callback, 86 QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE, 87 QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE 88 ) 89 walletController.updateWalletPreference() 90 walletController.queryWalletCards(callback) 91 92 awaitClose { 93 walletController.unregisterWalletChangeObservers( 94 QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE, 95 QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE 96 ) 97 } 98 } 99 100 override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState { 101 return when { 102 !walletController.walletClient.isWalletServiceAvailable -> 103 KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice 104 !isWalletAvailable() -> 105 KeyguardQuickAffordanceConfig.PickerScreenState.Disabled( 106 explanation = 107 context.getString( 108 R.string.wallet_quick_affordance_unavailable_install_the_app 109 ), 110 ) 111 queryCards().isEmpty() -> 112 KeyguardQuickAffordanceConfig.PickerScreenState.Disabled( 113 explanation = 114 context.getString( 115 R.string.wallet_quick_affordance_unavailable_configure_the_app 116 ), 117 ) 118 else -> KeyguardQuickAffordanceConfig.PickerScreenState.Default() 119 } 120 } 121 122 override fun onTriggered( 123 expandable: Expandable?, 124 ): KeyguardQuickAffordanceConfig.OnTriggeredResult { 125 walletController.startQuickAccessUiIntent( 126 activityStarter, 127 expandable?.activityLaunchController(), 128 /* hasCard= */ true, 129 ) 130 return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled 131 } 132 133 private suspend fun queryCards(): List<WalletCard> { 134 return suspendCancellableCoroutine { continuation -> 135 val callback = 136 object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback { 137 override fun onWalletCardsRetrieved(response: GetWalletCardsResponse) { 138 continuation.resumeWith( 139 Result.success(getPaymentCards(response.walletCards) ?: emptyList()) 140 ) 141 } 142 143 override fun onWalletCardRetrievalError(error: GetWalletCardsError) { 144 continuation.resumeWith(Result.success(emptyList())) 145 } 146 } 147 walletController.queryWalletCards(callback) 148 } 149 } 150 151 private fun isWalletAvailable() = 152 with(walletController.walletClient) { isWalletServiceAvailable && isWalletFeatureAvailable } 153 154 private fun state( 155 isFeatureEnabled: Boolean, 156 hasCard: Boolean, 157 tileIcon: Drawable?, 158 ): KeyguardQuickAffordanceConfig.LockScreenState { 159 return if (isFeatureEnabled && hasCard && tileIcon != null) { 160 KeyguardQuickAffordanceConfig.LockScreenState.Visible( 161 icon = 162 Icon.Loaded( 163 drawable = tileIcon, 164 contentDescription = 165 ContentDescription.Resource( 166 res = R.string.accessibility_wallet_button, 167 ), 168 ), 169 ) 170 } else { 171 KeyguardQuickAffordanceConfig.LockScreenState.Hidden 172 } 173 } 174 175 companion object { 176 private const val TAG = "QuickAccessWalletKeyguardQuickAffordanceConfig" 177 } 178 } 179