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