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 */ queryWalletCards( QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever)161 public void queryWalletCards( 162 QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever) { 163 if (mClock.elapsedRealtime() - mQawClientCreatedTimeMillis 164 > RECREATION_TIME_WINDOW) { 165 Log.i(TAG, "Re-creating the QAW client to avoid stale."); 166 reCreateWalletClient(); 167 } 168 if (!mQuickAccessWalletClient.isWalletFeatureAvailable()) { 169 Log.d(TAG, "QuickAccessWallet feature is not available."); 170 return; 171 } 172 int cardWidth = 173 mContext.getResources().getDimensionPixelSize(R.dimen.wallet_tile_card_view_width); 174 int cardHeight = 175 mContext.getResources().getDimensionPixelSize(R.dimen.wallet_tile_card_view_height); 176 int iconSizePx = mContext.getResources().getDimensionPixelSize(R.dimen.wallet_icon_size); 177 GetWalletCardsRequest request = 178 new GetWalletCardsRequest(cardWidth, cardHeight, iconSizePx, /* maxCards= */ 1); 179 mQuickAccessWalletClient.getWalletCards(mBgExecutor, request, cardsRetriever); 180 } 181 182 /** 183 * Re-create the {@link QuickAccessWalletClient} of the controller. 184 */ reCreateWalletClient()185 public void reCreateWalletClient() { 186 mQuickAccessWalletClient = QuickAccessWalletClient.create(mContext, mBgExecutor); 187 mQawClientCreatedTimeMillis = mClock.elapsedRealtime(); 188 } 189 190 /** 191 * Starts the QuickAccessWallet UI: either the app's designated UI, or the built-in Wallet UI. 192 * 193 * If the service has configured itself so that 194 * {@link QuickAccessWalletClient#useTargetActivityForQuickAccess()} 195 * is true, or the service isn't providing any cards, use the target activity. Otherwise, use 196 * the SysUi {@link WalletActivity} 197 * 198 * The Wallet target activity is defined as the {@link android.app.PendingIntent} returned by 199 * {@link QuickAccessWalletClient#getWalletPendingIntent} if that is not null. If that is null, 200 * then the {@link Intent} returned by {@link QuickAccessWalletClient#createWalletIntent()}. If 201 * that too is null, then fall back to {@link WalletActivity}. 202 * 203 * @param activityStarter an {@link ActivityStarter} to launch the Intent or PendingIntent. 204 * @param animationController an {@link ActivityLaunchAnimator.Controller} to provide a 205 * smooth animation for the activity launch. 206 * @param hasCard whether the service returns any cards. 207 */ startQuickAccessUiIntent(ActivityStarter activityStarter, ActivityLaunchAnimator.Controller animationController, boolean hasCard)208 public void startQuickAccessUiIntent(ActivityStarter activityStarter, 209 ActivityLaunchAnimator.Controller animationController, 210 boolean hasCard) { 211 mQuickAccessWalletClient.getWalletPendingIntent(mExecutor, 212 walletPendingIntent -> { 213 if (walletPendingIntent != null) { 214 startQuickAccessViaPendingIntent(walletPendingIntent, activityStarter, 215 animationController); 216 return; 217 } 218 Intent intent = null; 219 if (!hasCard) { 220 intent = mQuickAccessWalletClient.createWalletIntent(); 221 } 222 if (intent == null) { 223 intent = getSysUiWalletIntent(); 224 } 225 startQuickAccessViaIntent(intent, hasCard, activityStarter, 226 animationController); 227 228 }); 229 } 230 getSysUiWalletIntent()231 private Intent getSysUiWalletIntent() { 232 return new Intent(mContext, WalletActivity.class) 233 .setAction(Intent.ACTION_VIEW); 234 } 235 startQuickAccessViaIntent(Intent intent, boolean hasCard, ActivityStarter activityStarter, ActivityLaunchAnimator.Controller animationController)236 private void startQuickAccessViaIntent(Intent intent, 237 boolean hasCard, 238 ActivityStarter activityStarter, 239 ActivityLaunchAnimator.Controller animationController) { 240 if (hasCard) { 241 activityStarter.startActivity(intent, true /* dismissShade */, 242 animationController, true /* showOverLockscreenWhenLocked */); 243 } else { 244 activityStarter.postStartActivityDismissingKeyguard( 245 intent, 246 /* delay= */ 0, 247 animationController); 248 } 249 } 250 startQuickAccessViaPendingIntent(PendingIntent pendingIntent, ActivityStarter activityStarter, ActivityLaunchAnimator.Controller animationController)251 private void startQuickAccessViaPendingIntent(PendingIntent pendingIntent, 252 ActivityStarter activityStarter, 253 ActivityLaunchAnimator.Controller animationController) { 254 activityStarter.postStartActivityDismissingKeyguard( 255 pendingIntent, 256 animationController); 257 258 } 259 260 setupDefaultPaymentAppObserver( QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever)261 private void setupDefaultPaymentAppObserver( 262 QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever) { 263 if (mDefaultPaymentAppObserver == null) { 264 mDefaultPaymentAppObserver = new ContentObserver(null /* handler */) { 265 @Override 266 public void onChange(boolean selfChange) { 267 mExecutor.execute(() -> { 268 reCreateWalletClient(); 269 updateWalletPreference(); 270 queryWalletCards(cardsRetriever); 271 }); 272 } 273 }; 274 275 mSecureSettings.registerContentObserverForUser( 276 Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT, 277 false /* notifyForDescendants */, 278 mDefaultPaymentAppObserver, 279 UserHandle.USER_ALL); 280 } 281 mDefaultPaymentAppChangeEvents++; 282 } 283 setupWalletPreferenceObserver()284 private void setupWalletPreferenceObserver() { 285 if (mWalletPreferenceObserver == null) { 286 mWalletPreferenceObserver = new ContentObserver(null /* handler */) { 287 @Override 288 public void onChange(boolean selfChange) { 289 mExecutor.execute(() -> { 290 updateWalletPreference(); 291 }); 292 } 293 }; 294 295 mSecureSettings.registerContentObserverForUser( 296 QuickAccessWalletClientImpl.SETTING_KEY, 297 false /* notifyForDescendants */, 298 mWalletPreferenceObserver, 299 UserHandle.USER_ALL); 300 } 301 mWalletPreferenceChangeEvents++; 302 } 303 } 304