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