• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.DEFAULT_WALLET_APP_CHANGE;
21 import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE;
22 
23 import android.annotation.WorkerThread;
24 import android.app.PendingIntent;
25 import android.app.role.OnRoleHoldersChangedListener;
26 import android.app.role.RoleManager;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.database.ContentObserver;
30 import android.os.UserHandle;
31 import android.provider.Settings;
32 import android.service.quickaccesswallet.GetWalletCardsRequest;
33 import android.service.quickaccesswallet.QuickAccessWalletClient;
34 import android.service.quickaccesswallet.QuickAccessWalletClientImpl;
35 import android.service.quickaccesswallet.WalletCard;
36 import android.util.Log;
37 
38 import com.android.systemui.animation.ActivityTransitionAnimator;
39 import com.android.systemui.dagger.SysUISingleton;
40 import com.android.systemui.dagger.qualifiers.Background;
41 import com.android.systemui.dagger.qualifiers.Main;
42 import com.android.systemui.plugins.ActivityStarter;
43 import com.android.systemui.res.R;
44 import com.android.systemui.util.settings.SecureSettings;
45 import com.android.systemui.util.time.SystemClock;
46 import com.android.systemui.wallet.ui.WalletActivity;
47 
48 import java.util.concurrent.Executor;
49 import java.util.concurrent.TimeUnit;
50 
51 import javax.inject.Inject;
52 
53 /**
54  * Controller to handle communication between SystemUI and Quick Access Wallet Client.
55  */
56 @SysUISingleton
57 public class QuickAccessWalletController {
58 
59     /**
60      * Event for the wallet status change, e.g. the default payment app change and the wallet
61      * preference change.
62      */
63     public enum WalletChangeEvent {
64         DEFAULT_PAYMENT_APP_CHANGE,
65         DEFAULT_WALLET_APP_CHANGE,
66         WALLET_PREFERENCE_CHANGE,
67     }
68 
69     private static final String TAG = "QAWController";
70     private static final long RECREATION_TIME_WINDOW = TimeUnit.MINUTES.toMillis(10L);
71     private final Context mContext;
72     private final Executor mExecutor;
73     private final Executor mBgExecutor;
74     private final SecureSettings mSecureSettings;
75     private final SystemClock mClock;
76 
77     private QuickAccessWalletClient mQuickAccessWalletClient;
78     private ContentObserver mWalletPreferenceObserver;
79     private RoleManager mRoleManager;
80     private OnRoleHoldersChangedListener mDefaultWalletAppObserver;
81     private ContentObserver mDefaultPaymentAppObserver;
82     private int mWalletPreferenceChangeEvents = 0;
83     private int mDefaultPaymentAppChangeEvents = 0;
84     private int mDefaultWalletAppChangeEvents = 0;
85     private boolean mWalletEnabled = false;
86     private long mQawClientCreatedTimeMillis;
87 
88     @Inject
QuickAccessWalletController( Context context, @Main Executor executor, @Background Executor bgExecutor, SecureSettings secureSettings, QuickAccessWalletClient quickAccessWalletClient, SystemClock clock, RoleManager roleManager)89     public QuickAccessWalletController(
90             Context context,
91             @Main Executor executor,
92             @Background Executor bgExecutor,
93             SecureSettings secureSettings,
94             QuickAccessWalletClient quickAccessWalletClient,
95             SystemClock clock,
96             RoleManager roleManager) {
97         mContext = context;
98         mExecutor = executor;
99         mBgExecutor = bgExecutor;
100         mSecureSettings = secureSettings;
101         mRoleManager = roleManager;
102         mQuickAccessWalletClient = quickAccessWalletClient;
103         mClock = clock;
104         mQawClientCreatedTimeMillis = mClock.elapsedRealtime();
105     }
106 
isWalletRoleAvailable()107     public boolean isWalletRoleAvailable() {
108         return mRoleManager.isRoleAvailable(RoleManager.ROLE_WALLET);
109     }
110 
111     /**
112      * Returns true if the Quick Access Wallet service & feature is available.
113      */
isWalletEnabled()114     public boolean isWalletEnabled() {
115         return mWalletEnabled;
116     }
117 
118     /**
119      * Returns the current instance of {@link QuickAccessWalletClient} in the controller.
120      */
getWalletClient()121     public QuickAccessWalletClient getWalletClient() {
122         return mQuickAccessWalletClient;
123     }
124 
125     /**
126      * Setup the wallet change observers per {@link WalletChangeEvent}
127      *
128      * @param cardsRetriever a callback that retrieves the wallet cards
129      * @param events {@link WalletChangeEvent} need to be handled.
130      */
setupWalletChangeObservers( QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever, WalletChangeEvent... events)131     public void setupWalletChangeObservers(
132             QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever,
133             WalletChangeEvent... events) {
134         for (WalletChangeEvent event : events) {
135             if (event == WALLET_PREFERENCE_CHANGE) {
136                 setupWalletPreferenceObserver();
137             } else if (event == DEFAULT_PAYMENT_APP_CHANGE) {
138                 setupDefaultPaymentAppObserver(cardsRetriever);
139             } else if (event == DEFAULT_WALLET_APP_CHANGE) {
140                 setupDefaultWalletAppObserver(cardsRetriever);
141             }
142         }
143     }
144 
145     /**
146      * Unregister wallet change observers per {@link WalletChangeEvent} if needed.
147      */
unregisterWalletChangeObservers(WalletChangeEvent... events)148     public void unregisterWalletChangeObservers(WalletChangeEvent... events) {
149         for (WalletChangeEvent event : events) {
150             if (event == WALLET_PREFERENCE_CHANGE && mWalletPreferenceObserver != null) {
151                 mWalletPreferenceChangeEvents--;
152                 if (mWalletPreferenceChangeEvents == 0) {
153                     mSecureSettings.unregisterContentObserverSync(mWalletPreferenceObserver);
154                 }
155             } else if (event == DEFAULT_PAYMENT_APP_CHANGE && mDefaultPaymentAppObserver != null) {
156                 mDefaultPaymentAppChangeEvents--;
157                 if (mDefaultPaymentAppChangeEvents == 0) {
158                     mSecureSettings.unregisterContentObserverSync(mDefaultPaymentAppObserver);
159                 }
160             } else if (event == DEFAULT_WALLET_APP_CHANGE && mDefaultWalletAppObserver != null) {
161                 mDefaultWalletAppChangeEvents--;
162                 if (mDefaultWalletAppChangeEvents == 0) {
163                     mRoleManager.removeOnRoleHoldersChangedListenerAsUser(mDefaultWalletAppObserver,
164                             UserHandle.ALL);
165                 }
166             }
167         }
168     }
169 
170     /**
171      * Update the "show wallet" preference.
172      * This should not be called on the main thread.
173      */
174     @WorkerThread
updateWalletPreference()175     public void updateWalletPreference() {
176         mWalletEnabled = mQuickAccessWalletClient.isWalletServiceAvailable()
177                 && mQuickAccessWalletClient.isWalletFeatureAvailable()
178                 && mQuickAccessWalletClient.isWalletFeatureAvailableWhenDeviceLocked();
179     }
180 
181     /**
182      * Query the wallet cards from {@link QuickAccessWalletClient}.
183      * This should not be called on the main thread.
184      *
185      * @param cardsRetriever a callback to retrieve wallet cards.
186      * @param maxCards the maximum number of cards requested from the QuickAccessWallet
187      */
188     @WorkerThread
queryWalletCards( QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever, int maxCards)189     public void queryWalletCards(
190             QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever, int maxCards) {
191         if (mClock.elapsedRealtime() - mQawClientCreatedTimeMillis
192                 > RECREATION_TIME_WINDOW) {
193             Log.i(TAG, "Re-creating the QAW client to avoid stale.");
194             reCreateWalletClient();
195         }
196         if (!mQuickAccessWalletClient.isWalletFeatureAvailable()) {
197             Log.d(TAG, "QuickAccessWallet feature is not available.");
198             return;
199         }
200         int cardWidth =
201                 mContext.getResources().getDimensionPixelSize(R.dimen.wallet_tile_card_view_width);
202         int cardHeight =
203                 mContext.getResources().getDimensionPixelSize(R.dimen.wallet_tile_card_view_height);
204         int iconSizePx = mContext.getResources().getDimensionPixelSize(R.dimen.wallet_icon_size);
205         GetWalletCardsRequest request =
206                 new GetWalletCardsRequest(cardWidth, cardHeight, iconSizePx, maxCards);
207         mQuickAccessWalletClient.getWalletCards(mBgExecutor, request, cardsRetriever);
208     }
209 
210     /**
211      * Query the wallet cards from {@link QuickAccessWalletClient}.
212      * This should not be called on the main thread.
213      *
214      * @param cardsRetriever a callback to retrieve wallet cards.
215      */
216     @WorkerThread
queryWalletCards( QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever)217     public void queryWalletCards(
218             QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever) {
219         queryWalletCards(cardsRetriever, /* maxCards= */ 1);
220     }
221 
222 
223     /**
224      * Re-create the {@link QuickAccessWalletClient} of the controller.
225      */
reCreateWalletClient()226     public void reCreateWalletClient() {
227         mQuickAccessWalletClient = QuickAccessWalletClient.create(mContext, mBgExecutor);
228         mQawClientCreatedTimeMillis = mClock.elapsedRealtime();
229     }
230 
231     /**
232      * Starts the QuickAccessWallet Gesture UI (the app is launched by a hardware gesture).
233      *
234      *
235      * The Wallet target activity is defined as the {@link android.app.PendingIntent} returned by
236      * {@link QuickAccessWalletClient#getGestureTargetActivityPendingIntent} if that is not null.
237      * If that is null, then the method {@link QuickAccessWalletController#startQuickAccessUiIntent}
238      * as defined below is called, which starts the QuickAccessWallet UI.
239      *
240      * @param activityStarter an {@link ActivityStarter} to launch the Intent or PendingIntent.
241      * @param animationController an {@link ActivityTransitionAnimator.Controller} to provide a
242      *                            smooth animation for the activity launch.
243      */
startGestureUiIntent(ActivityStarter activityStarter, ActivityTransitionAnimator.Controller animationController)244     public void startGestureUiIntent(ActivityStarter activityStarter,
245             ActivityTransitionAnimator.Controller animationController){
246         mQuickAccessWalletClient.getGestureTargetActivityPendingIntent(
247                 mExecutor,
248                 gesturePendingIntent -> {
249                     if (gesturePendingIntent != null) {
250                         activityStarter.startPendingIntentMaybeDismissingKeyguard(
251                                 gesturePendingIntent, null, null);
252                         return;
253                     }
254 
255                     startQuickAccessUiIntent(activityStarter, animationController, true);
256                 }
257         );
258     }
259 
260     /**
261      * Starts the QuickAccessWallet UI: either the app's designated UI, or the built-in Wallet UI.
262      *
263      * If the service has configured itself so that
264      * {@link QuickAccessWalletClient#useTargetActivityForQuickAccess()}
265      * is true, or the service isn't providing any cards, use the target activity. Otherwise, use
266      * the SysUi {@link WalletActivity}
267      *
268      * The Wallet target activity is defined as the {@link android.app.PendingIntent} returned by
269      * {@link QuickAccessWalletClient#getWalletPendingIntent} if that is not null. If that is null,
270      * then the {@link Intent} returned by {@link QuickAccessWalletClient#createWalletIntent()}. If
271      * that too is null, then fall back to {@link WalletActivity}.
272      *
273      * @param activityStarter an {@link ActivityStarter} to launch the Intent or PendingIntent.
274      * @param animationController an {@link ActivityTransitionAnimator.Controller} to provide a
275      *                            smooth animation for the activity launch.
276      * @param hasCard whether the service returns any cards.
277      */
startQuickAccessUiIntent(ActivityStarter activityStarter, ActivityTransitionAnimator.Controller animationController, boolean hasCard)278     public void startQuickAccessUiIntent(ActivityStarter activityStarter,
279             ActivityTransitionAnimator.Controller animationController,
280             boolean hasCard) {
281         mQuickAccessWalletClient.getWalletPendingIntent(mExecutor,
282                 walletPendingIntent -> {
283                     if (walletPendingIntent != null) {
284                         startQuickAccessViaPendingIntent(walletPendingIntent, activityStarter,
285                                 animationController);
286                         return;
287                     }
288                     Intent intent = null;
289                     if (!hasCard) {
290                         intent = mQuickAccessWalletClient.createWalletIntent();
291                     }
292                     if (intent == null) {
293                         intent = getSysUiWalletIntent();
294                     }
295                     startQuickAccessViaIntent(intent, hasCard, activityStarter,
296                             animationController, mQuickAccessWalletClient.getUser());
297 
298                 });
299     }
300 
301     /**
302      * Starts the {@link android.app.PendingIntent} for a {@link WalletCard}.
303      *
304      * This should be used to open a selected card from the QuickAccessWallet UI or
305      * the settings tile.
306      *
307      * @param activityStarter an {@link ActivityStarter} to launch the Intent or PendingIntent.
308      * @param animationController an {@link ActivityTransitionAnimator.Controller} to provide a
309      *                            smooth animation for the activity launch.
310      */
startWalletCardPendingIntent(WalletCard card, ActivityStarter activityStarter, ActivityTransitionAnimator.Controller animationController)311     public void startWalletCardPendingIntent(WalletCard card,
312             ActivityStarter activityStarter,
313             ActivityTransitionAnimator.Controller animationController) {
314         activityStarter.postStartActivityDismissingKeyguard(
315                 card.getPendingIntent(), animationController);
316     }
317 
getSysUiWalletIntent()318     private Intent getSysUiWalletIntent() {
319         return new Intent(mContext, WalletActivity.class)
320                 .setAction(Intent.ACTION_VIEW);
321     }
322 
startQuickAccessViaIntent(Intent intent, boolean hasCard, ActivityStarter activityStarter, ActivityTransitionAnimator.Controller animationController, UserHandle user)323     private void startQuickAccessViaIntent(Intent intent,
324             boolean hasCard,
325             ActivityStarter activityStarter,
326             ActivityTransitionAnimator.Controller animationController,
327             UserHandle user) {
328         if (hasCard) {
329             activityStarter.startActivity(intent, true /* dismissShade */,
330                     animationController, true /* showOverLockscreenWhenLocked */);
331         } else {
332             activityStarter.postStartActivityDismissingKeyguard(
333                     intent,
334                     /* delay= */ 0,
335                     animationController,
336                     null,
337                     user);
338         }
339     }
340 
startQuickAccessViaPendingIntent(PendingIntent pendingIntent, ActivityStarter activityStarter, ActivityTransitionAnimator.Controller animationController)341     private void startQuickAccessViaPendingIntent(PendingIntent pendingIntent,
342             ActivityStarter activityStarter,
343             ActivityTransitionAnimator.Controller animationController) {
344         activityStarter.postStartActivityDismissingKeyguard(
345                 pendingIntent,
346                 animationController);
347 
348     }
349 
350 
setupDefaultPaymentAppObserver( QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever)351     private void setupDefaultPaymentAppObserver(
352             QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever) {
353         if (mDefaultPaymentAppObserver == null) {
354             mDefaultPaymentAppObserver = new ContentObserver(null /* handler */) {
355                 @Override
356                 public void onChange(boolean selfChange) {
357                     mExecutor.execute(() -> {
358                         reCreateWalletClient();
359                         updateWalletPreference();
360                         queryWalletCards(cardsRetriever);
361                     });
362                 }
363             };
364 
365             mSecureSettings.registerContentObserverForUserSync(
366                     Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT,
367                     false /* notifyForDescendants */,
368                     mDefaultPaymentAppObserver,
369                     UserHandle.USER_ALL);
370         }
371         mDefaultPaymentAppChangeEvents++;
372     }
373 
setupDefaultWalletAppObserver( QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever)374     private void setupDefaultWalletAppObserver(
375             QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever) {
376         if (mDefaultWalletAppObserver == null) {
377             mDefaultWalletAppObserver = (roleName, user) -> {
378                 if (!roleName.equals(RoleManager.ROLE_WALLET)) {
379                     return;
380                 }
381                 mExecutor.execute(() -> {
382                     reCreateWalletClient();
383                     updateWalletPreference();
384                     queryWalletCards(cardsRetriever);
385                 });
386             };
387             mRoleManager.addOnRoleHoldersChangedListenerAsUser(mExecutor,
388                     mDefaultWalletAppObserver, UserHandle.ALL);
389         }
390         mDefaultWalletAppChangeEvents++;
391     }
392 
setupWalletPreferenceObserver()393     private void setupWalletPreferenceObserver() {
394         if (mWalletPreferenceObserver == null) {
395             mWalletPreferenceObserver = new ContentObserver(null /* handler */) {
396                 @Override
397                 public void onChange(boolean selfChange) {
398                     mExecutor.execute(() -> {
399                         updateWalletPreference();
400                     });
401                 }
402             };
403 
404             mSecureSettings.registerContentObserverForUserSync(
405                     QuickAccessWalletClientImpl.SETTING_KEY,
406                     false /* notifyForDescendants */,
407                     mWalletPreferenceObserver,
408                     UserHandle.USER_ALL);
409         }
410         mWalletPreferenceChangeEvents++;
411     }
412 }
413