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