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