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