1 /* 2 * Copyright (C) 2021 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 package com.android.systemui.qs.tiles; 18 19 import static android.graphics.drawable.Icon.TYPE_URI; 20 import static android.provider.Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT; 21 22 import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE; 23 import static com.android.systemui.wallet.util.WalletCardUtilsKt.getPaymentCards; 24 25 import android.content.Intent; 26 import android.content.pm.PackageManager; 27 import android.graphics.drawable.Drawable; 28 import android.os.Handler; 29 import android.os.Looper; 30 import android.os.UserHandle; 31 import android.service.quickaccesswallet.GetWalletCardsError; 32 import android.service.quickaccesswallet.GetWalletCardsResponse; 33 import android.service.quickaccesswallet.QuickAccessWalletClient; 34 import android.service.quickaccesswallet.WalletCard; 35 import android.service.quicksettings.Tile; 36 import android.util.Log; 37 import android.view.View; 38 39 import androidx.annotation.NonNull; 40 import androidx.annotation.Nullable; 41 42 import com.android.internal.annotations.VisibleForTesting; 43 import com.android.internal.jank.InteractionJankMonitor; 44 import com.android.internal.logging.MetricsLogger; 45 import com.android.systemui.R; 46 import com.android.systemui.animation.ActivityLaunchAnimator; 47 import com.android.systemui.dagger.qualifiers.Background; 48 import com.android.systemui.dagger.qualifiers.Main; 49 import com.android.systemui.plugins.ActivityStarter; 50 import com.android.systemui.plugins.FalsingManager; 51 import com.android.systemui.plugins.qs.QSTile; 52 import com.android.systemui.plugins.statusbar.StatusBarStateController; 53 import com.android.systemui.qs.QSHost; 54 import com.android.systemui.qs.QsEventLogger; 55 import com.android.systemui.qs.logging.QSLogger; 56 import com.android.systemui.qs.tileimpl.QSTileImpl; 57 import com.android.systemui.statusbar.policy.KeyguardStateController; 58 import com.android.systemui.util.settings.SecureSettings; 59 import com.android.systemui.wallet.controller.QuickAccessWalletController; 60 61 import java.util.List; 62 63 import javax.inject.Inject; 64 65 /** Quick settings tile: Quick access wallet **/ 66 public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> { 67 68 public static final String TILE_SPEC = "wallet"; 69 70 private static final String TAG = "QuickAccessWalletTile"; 71 private static final String FEATURE_CHROME_OS = "org.chromium.arc"; 72 73 private final CharSequence mLabel = mContext.getString(R.string.wallet_title); 74 private final WalletCardRetriever mCardRetriever = new WalletCardRetriever(); 75 private final KeyguardStateController mKeyguardStateController; 76 private final PackageManager mPackageManager; 77 private final SecureSettings mSecureSettings; 78 private final QuickAccessWalletController mController; 79 80 @Nullable 81 private WalletCard mSelectedCard; 82 private boolean mIsWalletUpdating = true; 83 @Nullable 84 @VisibleForTesting Drawable mCardViewDrawable; 85 86 @Inject QuickAccessWalletTile( QSHost host, QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, QSLogger qsLogger, KeyguardStateController keyguardStateController, PackageManager packageManager, SecureSettings secureSettings, QuickAccessWalletController quickAccessWalletController)87 public QuickAccessWalletTile( 88 QSHost host, 89 QsEventLogger uiEventLogger, 90 @Background Looper backgroundLooper, 91 @Main Handler mainHandler, 92 FalsingManager falsingManager, 93 MetricsLogger metricsLogger, 94 StatusBarStateController statusBarStateController, 95 ActivityStarter activityStarter, 96 QSLogger qsLogger, 97 KeyguardStateController keyguardStateController, 98 PackageManager packageManager, 99 SecureSettings secureSettings, 100 QuickAccessWalletController quickAccessWalletController) { 101 super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, 102 statusBarStateController, activityStarter, qsLogger); 103 mController = quickAccessWalletController; 104 mKeyguardStateController = keyguardStateController; 105 mPackageManager = packageManager; 106 mSecureSettings = secureSettings; 107 } 108 109 110 @Override newTileState()111 public State newTileState() { 112 State state = new State(); 113 state.handlesLongClick = false; 114 return state; 115 } 116 117 @Override handleSetListening(boolean listening)118 protected void handleSetListening(boolean listening) { 119 super.handleSetListening(listening); 120 if (listening) { 121 mController.setupWalletChangeObservers(mCardRetriever, DEFAULT_PAYMENT_APP_CHANGE); 122 if (!mController.getWalletClient().isWalletServiceAvailable() 123 || !mController.getWalletClient().isWalletFeatureAvailable()) { 124 Log.i(TAG, "QAW service is unavailable, recreating the wallet client."); 125 mController.reCreateWalletClient(); 126 } 127 mController.queryWalletCards(mCardRetriever); 128 } 129 } 130 131 @Override handleClick(@ullable View view)132 protected void handleClick(@Nullable View view) { 133 ActivityLaunchAnimator.Controller animationController = 134 view == null ? null : ActivityLaunchAnimator.Controller.fromView(view, 135 InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE); 136 137 mUiHandler.post( 138 () -> mController.startQuickAccessUiIntent( 139 mActivityStarter, animationController, mSelectedCard != null)); 140 } 141 142 @Override handleUpdateState(State state, Object arg)143 protected void handleUpdateState(State state, Object arg) { 144 CharSequence label = mController.getWalletClient().getServiceLabel(); 145 state.label = label == null ? mLabel : label; 146 state.contentDescription = state.label; 147 Drawable tileIcon = mController.getWalletClient().getTileIcon(); 148 state.icon = 149 tileIcon == null 150 ? ResourceIcon.get(R.drawable.ic_wallet_lockscreen) 151 : new DrawableIcon(tileIcon); 152 boolean isDeviceLocked = !mKeyguardStateController.isUnlocked(); 153 if (mController.getWalletClient().isWalletServiceAvailable() 154 && mController.getWalletClient().isWalletFeatureAvailable()) { 155 if (mSelectedCard != null) { 156 state.state = isDeviceLocked ? Tile.STATE_INACTIVE : Tile.STATE_ACTIVE; 157 state.secondaryLabel = mSelectedCard.getContentDescription(); 158 state.sideViewCustomDrawable = mCardViewDrawable; 159 } else { 160 state.state = Tile.STATE_INACTIVE; 161 state.secondaryLabel = 162 mContext.getString( 163 mIsWalletUpdating 164 ? R.string.wallet_secondary_label_updating 165 : R.string.wallet_secondary_label_no_card); 166 state.sideViewCustomDrawable = null; 167 } 168 state.stateDescription = state.secondaryLabel; 169 } else { 170 state.state = Tile.STATE_UNAVAILABLE; 171 state.secondaryLabel = null; 172 state.sideViewCustomDrawable = null; 173 } 174 } 175 176 @Override getMetricsCategory()177 public int getMetricsCategory() { 178 return 0; 179 } 180 181 @Override isAvailable()182 public boolean isAvailable() { 183 return mPackageManager.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION) 184 && !mPackageManager.hasSystemFeature(FEATURE_CHROME_OS) 185 && mSecureSettings.getStringForUser(NFC_PAYMENT_DEFAULT_COMPONENT, 186 UserHandle.USER_CURRENT) != null; 187 } 188 189 @Nullable 190 @Override getLongClickIntent()191 public Intent getLongClickIntent() { 192 return null; 193 } 194 195 @Override getTileLabel()196 public CharSequence getTileLabel() { 197 CharSequence label = mController.getWalletClient().getServiceLabel(); 198 return label == null ? mLabel : label; 199 } 200 201 @Override handleDestroy()202 protected void handleDestroy() { 203 super.handleDestroy(); 204 mController.unregisterWalletChangeObservers(DEFAULT_PAYMENT_APP_CHANGE); 205 } 206 207 private class WalletCardRetriever implements 208 QuickAccessWalletClient.OnWalletCardsRetrievedCallback { 209 210 @Override onWalletCardsRetrieved(@onNull GetWalletCardsResponse response)211 public void onWalletCardsRetrieved(@NonNull GetWalletCardsResponse response) { 212 Log.i(TAG, "Successfully retrieved wallet cards."); 213 mIsWalletUpdating = false; 214 List<WalletCard> cards = getPaymentCards(response.getWalletCards()); 215 if (cards.isEmpty()) { 216 Log.d(TAG, "No wallet cards exist."); 217 mCardViewDrawable = null; 218 mSelectedCard = null; 219 refreshState(); 220 return; 221 } 222 int selectedIndex = response.getSelectedIndex(); 223 if (selectedIndex >= cards.size()) { 224 Log.w(TAG, "Error retrieving cards: Invalid selected card index."); 225 mSelectedCard = null; 226 mCardViewDrawable = null; 227 return; 228 } 229 mSelectedCard = cards.get(selectedIndex); 230 android.graphics.drawable.Icon cardImageIcon = mSelectedCard.getCardImage(); 231 if (cardImageIcon.getType() == TYPE_URI) { 232 mCardViewDrawable = null; 233 } else { 234 mCardViewDrawable = mSelectedCard.getCardImage().loadDrawable(mContext); 235 } 236 refreshState(); 237 } 238 239 @Override onWalletCardRetrievalError(@onNull GetWalletCardsError error)240 public void onWalletCardRetrievalError(@NonNull GetWalletCardsError error) { 241 mIsWalletUpdating = false; 242 mCardViewDrawable = null; 243 mSelectedCard = null; 244 refreshState(); 245 } 246 } 247 } 248