1 /* <lambda>null2 * 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.animation.Expandable 28 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging 29 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow 30 import com.android.systemui.common.shared.model.ContentDescription 31 import com.android.systemui.common.shared.model.Icon 32 import com.android.systemui.dagger.SysUISingleton 33 import com.android.systemui.dagger.qualifiers.Application 34 import com.android.systemui.dagger.qualifiers.Background 35 import com.android.systemui.plugins.ActivityStarter 36 import com.android.systemui.res.R 37 import com.android.systemui.wallet.controller.QuickAccessWalletController 38 import com.android.systemui.wallet.util.getPaymentCards 39 import javax.inject.Inject 40 import kotlinx.coroutines.CoroutineDispatcher 41 import kotlinx.coroutines.ExperimentalCoroutinesApi 42 import kotlinx.coroutines.channels.awaitClose 43 import kotlinx.coroutines.flow.Flow 44 import kotlinx.coroutines.flow.flatMapLatest 45 import kotlinx.coroutines.flow.flowOf 46 import kotlinx.coroutines.suspendCancellableCoroutine 47 import kotlinx.coroutines.withContext 48 49 /** Quick access wallet quick affordance data source. */ 50 @SysUISingleton 51 class QuickAccessWalletKeyguardQuickAffordanceConfig 52 @Inject 53 constructor( 54 @Application private val context: Context, 55 @Background private val backgroundDispatcher: CoroutineDispatcher, 56 private val walletController: QuickAccessWalletController, 57 private val activityStarter: ActivityStarter, 58 ) : KeyguardQuickAffordanceConfig { 59 60 override val key: String = BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET 61 62 override fun pickerName(): String = context.getString(R.string.accessibility_wallet_button) 63 64 override val pickerIconResourceId = R.drawable.ic_wallet_lockscreen 65 66 @OptIn(ExperimentalCoroutinesApi::class) 67 override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> = 68 conflatedCallbackFlow { 69 val callback = 70 object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback { 71 override fun onWalletCardsRetrieved(response: GetWalletCardsResponse) { 72 val hasCards = 73 getPaymentCards(response.walletCards)?.isNotEmpty() == true 74 trySendWithFailureLogging( 75 hasCards, 76 TAG, 77 ) 78 } 79 80 override fun onWalletCardRetrievalError(error: GetWalletCardsError) { 81 Log.e( 82 TAG, 83 "Wallet card retrieval error, message: \"${error?.message}\"" 84 ) 85 trySendWithFailureLogging( 86 null, 87 TAG, 88 ) 89 } 90 } 91 92 walletController.setupWalletChangeObservers( 93 callback, 94 QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE, 95 QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE, 96 QuickAccessWalletController.WalletChangeEvent.DEFAULT_WALLET_APP_CHANGE 97 ) 98 99 withContext(backgroundDispatcher) { 100 // Both must be called on background thread 101 walletController.updateWalletPreference() 102 walletController.queryWalletCards(callback) 103 } 104 105 awaitClose { 106 walletController.unregisterWalletChangeObservers( 107 QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE, 108 QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE, 109 QuickAccessWalletController.WalletChangeEvent.DEFAULT_WALLET_APP_CHANGE 110 ) 111 } 112 } 113 .flatMapLatest { hasCards -> 114 // If hasCards is null, this indicates an error occurred upon card retrieval 115 val state = 116 if (hasCards == null) { 117 KeyguardQuickAffordanceConfig.LockScreenState.Hidden 118 } else { 119 state( 120 isWalletAvailable(), 121 hasCards, 122 walletController.walletClient.tileIcon, 123 ) 124 } 125 flowOf(state) 126 } 127 128 override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState { 129 return when { 130 !walletController.walletClient.isWalletServiceAvailable -> 131 KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice 132 !isWalletAvailable() -> 133 KeyguardQuickAffordanceConfig.PickerScreenState.Disabled( 134 explanation = 135 context.getString( 136 R.string.wallet_quick_affordance_unavailable_install_the_app 137 ), 138 ) 139 queryCards().isEmpty() -> 140 KeyguardQuickAffordanceConfig.PickerScreenState.Disabled( 141 explanation = 142 context.getString( 143 R.string.wallet_quick_affordance_unavailable_configure_the_app 144 ), 145 ) 146 else -> KeyguardQuickAffordanceConfig.PickerScreenState.Default() 147 } 148 } 149 150 override fun onTriggered( 151 expandable: Expandable?, 152 ): KeyguardQuickAffordanceConfig.OnTriggeredResult { 153 walletController.startQuickAccessUiIntent( 154 activityStarter, 155 expandable?.activityTransitionController(), 156 /* hasCard= */ true, 157 ) 158 return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled 159 } 160 161 private suspend fun queryCards(): List<WalletCard> { 162 return withContext(backgroundDispatcher) { 163 suspendCancellableCoroutine { continuation -> 164 val callback = 165 object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback { 166 override fun onWalletCardsRetrieved(response: GetWalletCardsResponse) { 167 continuation.resumeWith( 168 Result.success(getPaymentCards(response.walletCards)) 169 ) 170 } 171 172 override fun onWalletCardRetrievalError(error: GetWalletCardsError) { 173 continuation.resumeWith(Result.success(emptyList())) 174 } 175 } 176 // Must be called on background thread 177 walletController.queryWalletCards(callback) 178 } 179 } 180 } 181 182 private suspend fun isWalletAvailable() = 183 withContext(backgroundDispatcher) { 184 with(walletController.walletClient) { 185 // Must be called on background thread 186 isWalletServiceAvailable && isWalletFeatureAvailable 187 } 188 } 189 190 private fun state( 191 isFeatureEnabled: Boolean, 192 hasCard: Boolean, 193 tileIcon: Drawable?, 194 ): KeyguardQuickAffordanceConfig.LockScreenState { 195 return if (isFeatureEnabled && hasCard && tileIcon != null) { 196 KeyguardQuickAffordanceConfig.LockScreenState.Visible( 197 icon = 198 Icon.Loaded( 199 drawable = tileIcon, 200 contentDescription = 201 ContentDescription.Resource( 202 res = R.string.accessibility_wallet_button, 203 ), 204 ), 205 ) 206 } else { 207 KeyguardQuickAffordanceConfig.LockScreenState.Hidden 208 } 209 } 210 211 companion object { 212 private const val TAG = "QuickAccessWalletKeyguardQuickAffordanceConfig" 213 } 214 } 215