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.wallet.controller;
18 
19 import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE;
20 import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE;
21 
22 import android.app.PendingIntent;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.database.ContentObserver;
26 import android.os.UserHandle;
27 import android.provider.Settings;
28 import android.service.quickaccesswallet.GetWalletCardsRequest;
29 import android.service.quickaccesswallet.QuickAccessWalletClient;
30 import android.service.quickaccesswallet.QuickAccessWalletClientImpl;
31 import android.util.Log;
32 
33 import com.android.systemui.R;
34 import com.android.systemui.animation.ActivityLaunchAnimator;
35 import com.android.systemui.dagger.SysUISingleton;
36 import com.android.systemui.dagger.qualifiers.Background;
37 import com.android.systemui.dagger.qualifiers.Main;
38 import com.android.systemui.plugins.ActivityStarter;
39 import com.android.systemui.util.settings.SecureSettings;
40 import com.android.systemui.util.time.SystemClock;
41 import com.android.systemui.wallet.ui.WalletActivity;
42 
43 import java.util.concurrent.Executor;
44 import java.util.concurrent.TimeUnit;
45 
46 import javax.inject.Inject;
47 
48 /**
49  * Controller to handle communication between SystemUI and Quick Access Wallet Client.
50  */
51 @SysUISingleton
52 public class QuickAccessWalletController {
53 
54     /**
55      * Event for the wallet status change, e.g. the default payment app change and the wallet
56      * preference change.
57      */
58     public enum WalletChangeEvent {
59         DEFAULT_PAYMENT_APP_CHANGE,
60         WALLET_PREFERENCE_CHANGE,
61     }
62 
63     private static final String TAG = "QAWController";
64     private static final long RECREATION_TIME_WINDOW = TimeUnit.MINUTES.toMillis(10L);
65     private final Context mContext;
66     private final Executor mExecutor;
67     private final Executor mBgExecutor;
68     private final SecureSettings mSecureSettings;
69     private final SystemClock mClock;
70 
71     private QuickAccessWalletClient mQuickAccessWalletClient;
72     private ContentObserver mWalletPreferenceObserver;
73     private ContentObserver mDefaultPaymentAppObserver;
74     private int mWalletPreferenceChangeEvents = 0;
75     private int mDefaultPaymentAppChangeEvents = 0;
76     private boolean mWalletEnabled = false;
77     private long mQawClientCreatedTimeMillis;
78 
79     @Inject
QuickAccessWalletController( Context context, @Main Executor executor, @Background Executor bgExecutor, SecureSettings secureSettings, QuickAccessWalletClient quickAccessWalletClient, SystemClock clock)80     public QuickAccessWalletController(
81             Context context,
82             @Main Executor executor,
83             @Background Executor bgExecutor,
84             SecureSettings secureSettings,
85             QuickAccessWalletClient quickAccessWalletClient,
86             SystemClock clock) {
87         mContext = context;
88         mExecutor = executor;
89         mBgExecutor = bgExecutor;
90         mSecureSettings = secureSettings;
91         mQuickAccessWalletClient = quickAccessWalletClient;
92         mClock = clock;
93         mQawClientCreatedTimeMillis = mClock.elapsedRealtime();
94     }
95 
96     /**
97      * Returns true if the Quick Access Wallet service & feature is available.
98      */
isWalletEnabled()99     public boolean isWalletEnabled() {
100         return mWalletEnabled;
101     }
102 
103     /**
104      * Returns the current instance of {@link QuickAccessWalletClient} in the controller.
105      */
getWalletClient()106     public QuickAccessWalletClient getWalletClient() {
107         return mQuickAccessWalletClient;
108     }
109 
110     /**
111      * Setup the wallet change observers per {@link WalletChangeEvent}
112      *
113      * @param cardsRetriever a callback that retrieves the wallet cards
114      * @param events {@link WalletChangeEvent} need to be handled.
115      */
setupWalletChangeObservers( QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever, WalletChangeEvent... events)116     public void setupWalletChangeObservers(
117             QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever,
118             WalletChangeEvent... events) {
119         for (WalletChangeEvent event : events) {
120             if (event == WALLET_PREFERENCE_CHANGE) {
121                 setupWalletPreferenceObserver();
122             } else if (event == DEFAULT_PAYMENT_APP_CHANGE) {
123                 setupDefaultPaymentAppObserver(cardsRetriever);
124             }
125         }
126     }
127 
128     /**
129      * Unregister wallet change observers per {@link WalletChangeEvent} if needed.
130      */
unregisterWalletChangeObservers(WalletChangeEvent... events)131     public void unregisterWalletChangeObservers(WalletChangeEvent... events) {
132         for (WalletChangeEvent event : events) {
133             if (event == WALLET_PREFERENCE_CHANGE && mWalletPreferenceObserver != null) {
134                 mWalletPreferenceChangeEvents--;
135                 if (mWalletPreferenceChangeEvents == 0) {
136                     mSecureSettings.unregisterContentObserver(mWalletPreferenceObserver);
137                 }
138             } else if (event == DEFAULT_PAYMENT_APP_CHANGE && mDefaultPaymentAppObserver != null) {
139                 mDefaultPaymentAppChangeEvents--;
140                 if (mDefaultPaymentAppChangeEvents == 0) {
141                     mSecureSettings.unregisterContentObserver(mDefaultPaymentAppObserver);
142                 }
143             }
144         }
145     }
146 
147     /**
148      * Update the "show wallet" preference.
149      */
updateWalletPreference()150     public void updateWalletPreference() {
151         mWalletEnabled = mQuickAccessWalletClient.isWalletServiceAvailable()
152                 && mQuickAccessWalletClient.isWalletFeatureAvailable()
153                 && mQuickAccessWalletClient.isWalletFeatureAvailableWhenDeviceLocked();
154     }
155 
156     /**
157      * Query the wallet cards from {@link QuickAccessWalletClient}.
158      *
159      * @param cardsRetriever a callback to retrieve wallet cards.
160      * @param maxCards the maximum number of cards requested from the QuickAccessWallet
161      */
queryWalletCards( QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever, int maxCards)162     public void queryWalletCards(
163             QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever, int maxCards) {
164         if (mClock.elapsedRealtime() - mQawClientCreatedTimeMillis
165                 > RECREATION_TIME_WINDOW) {
166             Log.i(TAG, "Re-creating the QAW client to avoid stale.");
167             reCreateWalletClient();
168         }
169         if (!mQuickAccessWalletClient.isWalletFeatureAvailable()) {
170             Log.d(TAG, "QuickAccessWallet feature is not available.");
171             return;
172         }
173         int cardWidth =
174                 mContext.getResources().getDimensionPixelSize(R.dimen.wallet_tile_card_view_width);
175         int cardHeight =
176                 mContext.getResources().getDimensionPixelSize(R.dimen.wallet_tile_card_view_height);
177         int iconSizePx = mContext.getResources().getDimensionPixelSize(R.dimen.wallet_icon_size);
178         GetWalletCardsRequest request =
179                 new GetWalletCardsRequest(cardWidth, cardHeight, iconSizePx, maxCards);
180         mQuickAccessWalletClient.getWalletCards(mBgExecutor, request, cardsRetriever);
181     }
182 
183     /**
184      * Query the wallet cards from {@link QuickAccessWalletClient}.
185      *
186      * @param cardsRetriever a callback to retrieve wallet cards.
187      */
queryWalletCards( QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever)188     public void queryWalletCards(
189             QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever) {
190         queryWalletCards(cardsRetriever, /* maxCards= */ 1);
191     }
192 
193 
194     /**
195      * Re-create the {@link QuickAccessWalletClient} of the controller.
196      */
reCreateWalletClient()197     public void reCreateWalletClient() {
198         mQuickAccessWalletClient = QuickAccessWalletClient.create(mContext, mBgExecutor);
199         mQawClientCreatedTimeMillis = mClock.elapsedRealtime();
200     }
201 
202     /**
203      * Starts the QuickAccessWallet UI: either the app's designated UI, or the built-in Wallet UI.
204      *
205      * If the service has configured itself so that
206      * {@link QuickAccessWalletClient#useTargetActivityForQuickAccess()}
207      * is true, or the service isn't providing any cards, use the target activity. Otherwise, use
208      * the SysUi {@link WalletActivity}
209      *
210      * The Wallet target activity is defined as the {@link android.app.PendingIntent} returned by
211      * {@link QuickAccessWalletClient#getWalletPendingIntent} if that is not null. If that is null,
212      * then the {@link Intent} returned by {@link QuickAccessWalletClient#createWalletIntent()}. If
213      * that too is null, then fall back to {@link WalletActivity}.
214      *
215      * @param activityStarter an {@link ActivityStarter} to launch the Intent or PendingIntent.
216      * @param animationController an {@link ActivityLaunchAnimator.Controller} to provide a
217      *                            smooth animation for the activity launch.
218      * @param hasCard whether the service returns any cards.
219      */
startQuickAccessUiIntent(ActivityStarter activityStarter, ActivityLaunchAnimator.Controller animationController, boolean hasCard)220     public void startQuickAccessUiIntent(ActivityStarter activityStarter,
221             ActivityLaunchAnimator.Controller animationController,
222             boolean hasCard) {
223         mQuickAccessWalletClient.getWalletPendingIntent(mExecutor,
224                 walletPendingIntent -> {
225                     if (walletPendingIntent != null) {
226                         startQuickAccessViaPendingIntent(walletPendingIntent, activityStarter,
227                                 animationController);
228                         return;
229                     }
230                     Intent intent = null;
231                     if (!hasCard) {
232                         intent = mQuickAccessWalletClient.createWalletIntent();
233                     }
234                     if (intent == null) {
235                         intent = getSysUiWalletIntent();
236                     }
237                     startQuickAccessViaIntent(intent, hasCard, activityStarter,
238                             animationController);
239 
240                 });
241     }
242 
getSysUiWalletIntent()243     private Intent getSysUiWalletIntent() {
244         return new Intent(mContext, WalletActivity.class)
245                 .setAction(Intent.ACTION_VIEW);
246     }
247 
startQuickAccessViaIntent(Intent intent, boolean hasCard, ActivityStarter activityStarter, ActivityLaunchAnimator.Controller animationController)248     private void startQuickAccessViaIntent(Intent intent,
249             boolean hasCard,
250             ActivityStarter activityStarter,
251             ActivityLaunchAnimator.Controller animationController) {
252         if (hasCard) {
253             activityStarter.startActivity(intent, true /* dismissShade */,
254                     animationController, true /* showOverLockscreenWhenLocked */);
255         } else {
256             activityStarter.postStartActivityDismissingKeyguard(
257                     intent,
258                     /* delay= */ 0,
259                     animationController);
260         }
261     }
262 
startQuickAccessViaPendingIntent(PendingIntent pendingIntent, ActivityStarter activityStarter, ActivityLaunchAnimator.Controller animationController)263     private void startQuickAccessViaPendingIntent(PendingIntent pendingIntent,
264             ActivityStarter activityStarter,
265             ActivityLaunchAnimator.Controller animationController) {
266         activityStarter.postStartActivityDismissingKeyguard(
267                 pendingIntent,
268                 animationController);
269 
270     }
271 
272 
setupDefaultPaymentAppObserver( QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever)273     private void setupDefaultPaymentAppObserver(
274             QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever) {
275         if (mDefaultPaymentAppObserver == null) {
276             mDefaultPaymentAppObserver = new ContentObserver(null /* handler */) {
277                 @Override
278                 public void onChange(boolean selfChange) {
279                     mExecutor.execute(() -> {
280                         reCreateWalletClient();
281                         updateWalletPreference();
282                         queryWalletCards(cardsRetriever);
283                     });
284                 }
285             };
286 
287             mSecureSettings.registerContentObserverForUser(
288                     Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT,
289                     false /* notifyForDescendants */,
290                     mDefaultPaymentAppObserver,
291                     UserHandle.USER_ALL);
292         }
293         mDefaultPaymentAppChangeEvents++;
294     }
295 
setupWalletPreferenceObserver()296     private void setupWalletPreferenceObserver() {
297         if (mWalletPreferenceObserver == null) {
298             mWalletPreferenceObserver = new ContentObserver(null /* handler */) {
299                 @Override
300                 public void onChange(boolean selfChange) {
301                     mExecutor.execute(() -> {
302                         updateWalletPreference();
303                     });
304                 }
305             };
306 
307             mSecureSettings.registerContentObserverForUser(
308                     QuickAccessWalletClientImpl.SETTING_KEY,
309                     false /* notifyForDescendants */,
310                     mWalletPreferenceObserver,
311                     UserHandle.USER_ALL);
312         }
313         mWalletPreferenceChangeEvents++;
314     }
315 }
316