From 3b4f91e9aff6a4aa110dd876a486aff73391debb Mon Sep 17 00:00:00 2001 From: Silin Huang Date: Thu, 20 May 2021 15:59:05 -0700 Subject: [PATCH] Recreate QuickAccessWallet for Wallet Tile and Lockscreen Icon when the default payment app has changed. Also to avoid a wallet client that doesn't have ServiceInfo is living too long, don't make it final and re-create the wallet client if it has a null service info. Fix: 187972400 Test: atest Test: manual, see demo- the default payment app is GPay, check Tile and Lockscreen Icon, then change the default payment app and check again. https://drive.google.com/file/d/10-I339VPuxJRGXJT-XmwmnZs4MHaH3gm/view?usp=sharing&resourcekey=0-wDtRXNNr_Tg9ptxk1Tpxsw Change-Id: Ie9a05795bff447424b299132fa60ae2efb2092be --- .../qs/tiles/QuickAccessWalletTile.java | 51 +++-- .../phone/KeyguardBottomAreaView.java | 83 ++----- .../NotificationPanelViewController.java | 10 +- .../QuickAccessWalletController.java | 207 ++++++++++++++++++ .../systemui/wallet/ui/WalletActivity.java | 16 +- .../qs/tiles/QuickAccessWalletTileTest.java | 62 ++---- .../phone/NotificationPanelViewTest.java | 8 +- .../QuickAccessWalletControllerTest.java | 158 +++++++++++++ 8 files changed, 454 insertions(+), 141 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java create mode 100644 packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java index e467925551013..64aec5e0b32b5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java @@ -18,13 +18,14 @@ package com.android.systemui.qs.tiles; import static android.provider.Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT; +import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE; + import android.content.Intent; import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.Looper; import android.service.quickaccesswallet.GetWalletCardsError; -import android.service.quickaccesswallet.GetWalletCardsRequest; import android.service.quickaccesswallet.GetWalletCardsResponse; import android.service.quickaccesswallet.QuickAccessWalletClient; import android.service.quickaccesswallet.WalletCard; @@ -51,6 +52,7 @@ import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.settings.SecureSettings; +import com.android.systemui.wallet.controller.QuickAccessWalletController; import com.android.systemui.wallet.ui.WalletActivity; import java.util.List; @@ -66,16 +68,15 @@ public class QuickAccessWalletTile extends QSTileImpl { private final CharSequence mLabel = mContext.getString(R.string.wallet_title); private final WalletCardRetriever mCardRetriever = new WalletCardRetriever(); - // TODO(b/180959290): Re-create the QAW Client when the default NFC payment app changes. - private final QuickAccessWalletClient mQuickAccessWalletClient; private final KeyguardStateController mKeyguardStateController; private final PackageManager mPackageManager; private final SecureSettings mSecureSettings; private final Executor mExecutor; + private final QuickAccessWalletController mController; private final FeatureFlags mFeatureFlags; - @VisibleForTesting Drawable mCardViewDrawable; private WalletCard mSelectedCard; + @VisibleForTesting Drawable mCardViewDrawable; @Inject public QuickAccessWalletTile( @@ -87,15 +88,15 @@ public class QuickAccessWalletTile extends QSTileImpl { StatusBarStateController statusBarStateController, ActivityStarter activityStarter, QSLogger qsLogger, - QuickAccessWalletClient quickAccessWalletClient, KeyguardStateController keyguardStateController, PackageManager packageManager, SecureSettings secureSettings, - @Background Executor executor, + @Main Executor executor, + QuickAccessWalletController quickAccessWalletController, FeatureFlags featureFlags) { super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); - mQuickAccessWalletClient = quickAccessWalletClient; + mController = quickAccessWalletController; mKeyguardStateController = keyguardStateController; mPackageManager = packageManager; mSecureSettings = secureSettings; @@ -115,7 +116,11 @@ public class QuickAccessWalletTile extends QSTileImpl { protected void handleSetListening(boolean listening) { super.handleSetListening(listening); if (listening) { - queryWalletCards(); + mController.setupWalletChangeObservers(mCardRetriever, DEFAULT_PAYMENT_APP_CHANGE); + if (!mController.getWalletClient().isWalletServiceAvailable()) { + mController.reCreateWalletClient(); + } + mController.queryWalletCards(mCardRetriever); } } @@ -139,12 +144,13 @@ public class QuickAccessWalletTile extends QSTileImpl { mContext.startActivity(intent); } } else { - if (mQuickAccessWalletClient.createWalletIntent() == null) { + if (mController.getWalletClient().createWalletIntent() == null) { Log.w(TAG, "Could not get intent of the wallet app."); return; } mActivityStarter.postStartActivityDismissingKeyguard( - mQuickAccessWalletClient.createWalletIntent(), /* delay= */ 0, + mController.getWalletClient().createWalletIntent(), + /* delay= */ 0, animationController); } }); @@ -152,30 +158,34 @@ public class QuickAccessWalletTile extends QSTileImpl { @Override protected void handleUpdateState(State state, Object arg) { - CharSequence label = mQuickAccessWalletClient.getServiceLabel(); + CharSequence label = mController.getWalletClient().getServiceLabel(); state.label = label == null ? mLabel : label; state.contentDescription = state.label; state.icon = ResourceIcon.get(R.drawable.ic_wallet_lockscreen); boolean isDeviceLocked = !mKeyguardStateController.isUnlocked(); - if (mQuickAccessWalletClient.isWalletServiceAvailable()) { + if (mController.getWalletClient().isWalletServiceAvailable()) { if (mSelectedCard != null) { if (isDeviceLocked) { state.state = Tile.STATE_INACTIVE; state.secondaryLabel = mContext.getString(R.string.wallet_secondary_label_device_locked); + state.sideViewCustomDrawable = null; } else { state.state = Tile.STATE_ACTIVE; state.secondaryLabel = mSelectedCard.getContentDescription(); + state.sideViewCustomDrawable = mCardViewDrawable; } } else { state.state = Tile.STATE_INACTIVE; state.secondaryLabel = mContext.getString(R.string.wallet_secondary_label_no_card); + state.sideViewCustomDrawable = null; } state.stateDescription = state.secondaryLabel; } else { state.state = Tile.STATE_UNAVAILABLE; + state.secondaryLabel = null; + state.sideViewCustomDrawable = null; } - state.sideViewCustomDrawable = isDeviceLocked ? null : mCardViewDrawable; } @Override @@ -198,19 +208,14 @@ public class QuickAccessWalletTile extends QSTileImpl { @Override public CharSequence getTileLabel() { - CharSequence label = mQuickAccessWalletClient.getServiceLabel(); + CharSequence label = mController.getWalletClient().getServiceLabel(); return label == null ? mLabel : label; } - private void queryWalletCards() { - int cardWidth = - mContext.getResources().getDimensionPixelSize(R.dimen.wallet_tile_card_view_width); - int cardHeight = - mContext.getResources().getDimensionPixelSize(R.dimen.wallet_tile_card_view_height); - int iconSizePx = mContext.getResources().getDimensionPixelSize(R.dimen.wallet_icon_size); - GetWalletCardsRequest request = - new GetWalletCardsRequest(cardWidth, cardHeight, iconSizePx, /* maxCards= */ 1); - mQuickAccessWalletClient.getWalletCards(mExecutor, request, mCardRetriever); + @Override + protected void handleDestroy() { + super.handleDestroy(); + mController.unregisterWalletChangeObservers(DEFAULT_PAYMENT_APP_CHANGE); } private class WalletCardRetriever implements diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index 7f919b5f5cf5e..4d8e7de376064 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -24,6 +24,8 @@ import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_BUTT import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_UNLOCK; import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_RIGHT_BUTTON; import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_RIGHT_UNLOCK; +import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE; +import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE; import android.app.ActivityManager; import android.app.ActivityOptions; @@ -39,7 +41,6 @@ import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Configuration; -import android.database.ContentObserver; import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.os.Bundle; @@ -49,13 +50,10 @@ import android.os.Messenger; import android.os.RemoteException; import android.os.UserHandle; import android.provider.MediaStore; -import android.provider.Settings; import android.service.media.CameraPrewarmService; import android.service.quickaccesswallet.GetWalletCardsError; -import android.service.quickaccesswallet.GetWalletCardsRequest; import android.service.quickaccesswallet.GetWalletCardsResponse; import android.service.quickaccesswallet.QuickAccessWalletClient; -import android.service.quickaccesswallet.QuickAccessWalletClientImpl; import android.telecom.TelecomManager; import android.text.TextUtils; import android.util.AttributeSet; @@ -71,7 +69,6 @@ import android.widget.ImageView; import android.widget.TextView; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.LockPatternUtils; @@ -97,11 +94,9 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.PreviewInflater; import com.android.systemui.tuner.LockscreenFragment.LockButtonFactory; import com.android.systemui.tuner.TunerService; -import com.android.systemui.util.settings.SecureSettings; +import com.android.systemui.wallet.controller.QuickAccessWalletController; import com.android.systemui.wallet.ui.WalletActivity; -import java.util.concurrent.Executor; - /** * Implementation for the bottom area of the Keyguard, including camera/phone affordance and status * text. @@ -137,10 +132,9 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL private KeyguardAffordanceView mLeftAffordanceView; private ImageView mWalletButton; - private boolean mWalletEnabled = false; private boolean mHasCard = false; private WalletCardRetriever mCardRetriever = new WalletCardRetriever(); - private QuickAccessWalletClient mQuickAccessWalletClient; + private QuickAccessWalletController mQuickAccessWalletController; private ViewGroup mIndicationArea; private TextView mIndicationText; @@ -159,7 +153,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL private StatusBar mStatusBar; private KeyguardAffordanceHelper mAffordanceHelper; private FalsingManager mFalsingManager; - @Nullable private Executor mUiExecutor; private boolean mUserSetupComplete; private boolean mPrewarmBound; private Messenger mPrewarmMessenger; @@ -193,8 +186,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL private int mBurnInYOffset; private ActivityIntentHelper mActivityIntentHelper; private KeyguardUpdateMonitor mKeyguardUpdateMonitor; - private ContentObserver mWalletPreferenceObserver; - private SecureSettings mSecureSettings; public KeyguardBottomAreaView(Context context) { this(context, null); @@ -332,8 +323,9 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL getContext().unregisterReceiver(mDevicePolicyReceiver); mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback); - if (mWalletPreferenceObserver != null) { - mSecureSettings.unregisterContentObserver(mWalletPreferenceObserver); + if (mQuickAccessWalletController != null) { + mQuickAccessWalletController.unregisterWalletChangeObservers( + WALLET_PREFERENCE_CHANGE, DEFAULT_PAYMENT_APP_CHANGE); } } @@ -456,7 +448,10 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } private void updateWalletVisibility() { - if (mDozing || !mWalletEnabled || !mHasCard) { + if (mDozing + || mQuickAccessWalletController == null + || !mQuickAccessWalletController.isWalletEnabled() + || !mHasCard) { mWalletButton.setVisibility(GONE); mIndicationArea.setPadding(0, 0, 0, 0); } else { @@ -690,7 +685,9 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL @Override public void onKeyguardShowingChanged() { if (mKeyguardStateController.isShowing()) { - queryWalletCards(); + if (mQuickAccessWalletController != null) { + mQuickAccessWalletController.queryWalletCards(mCardRetriever); + } } } @@ -935,50 +932,17 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL /** * Initialize the wallet feature, only enabling if the feature is enabled within the platform. */ - public void initWallet(QuickAccessWalletClient client, Executor uiExecutor, - SecureSettings secureSettings) { - mQuickAccessWalletClient = client; - mSecureSettings = secureSettings; - setupWalletPreferenceObserver(); - updateWalletPreference(); - - mUiExecutor = uiExecutor; - queryWalletCards(); + public void initWallet( + QuickAccessWalletController controller) { + mQuickAccessWalletController = controller; + mQuickAccessWalletController.setupWalletChangeObservers( + mCardRetriever, WALLET_PREFERENCE_CHANGE, DEFAULT_PAYMENT_APP_CHANGE); + mQuickAccessWalletController.updateWalletPreference(); + mQuickAccessWalletController.queryWalletCards(mCardRetriever); updateWalletVisibility(); } - private void setupWalletPreferenceObserver() { - if (mWalletPreferenceObserver == null) { - mWalletPreferenceObserver = new ContentObserver(null /* handler */) { - @Override - public void onChange(boolean selfChange) { - mUiExecutor.execute(() -> updateWalletPreference()); - } - }; - - mSecureSettings.registerContentObserver( - Settings.Secure.getUriFor(QuickAccessWalletClientImpl.SETTING_KEY), - false /* notifyForDescendants */, - mWalletPreferenceObserver); - } - } - - private void updateWalletPreference() { - mWalletEnabled = mQuickAccessWalletClient.isWalletFeatureAvailable() - && mQuickAccessWalletClient.isWalletFeatureAvailableWhenDeviceLocked(); - } - - private void queryWalletCards() { - if (!mWalletEnabled || mUiExecutor == null) { - return; - } - GetWalletCardsRequest request = - new GetWalletCardsRequest(1 /* cardWidth */, 1 /* cardHeight */, - 1 /* iconSizePx */, 1 /* maxCards */); - mQuickAccessWalletClient.getWalletCards(mUiExecutor, request, mCardRetriever); - } - private void onWalletClick(View v) { // More coming here; need to inform the user about how to proceed if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { @@ -991,12 +955,13 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); mContext.startActivity(intent); } else { - if (mQuickAccessWalletClient.createWalletIntent() == null) { + if (mQuickAccessWalletController.getWalletClient().createWalletIntent() == null) { Log.w(TAG, "Could not get intent of the wallet app."); return; } mActivityStarter.postStartActivityDismissingKeyguard( - mQuickAccessWalletClient.createWalletIntent(), /* delay= */ 0); + mQuickAccessWalletController.getWalletClient().createWalletIntent(), + /* delay= */ 0); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 74071439b0d7b..e731d3b0d0204 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -54,7 +54,6 @@ import android.os.PowerManager; import android.os.SystemClock; import android.os.UserManager; import android.os.VibrationEffect; -import android.service.quickaccesswallet.QuickAccessWalletClient; import android.util.Log; import android.util.MathUtils; import android.view.DisplayCutout; @@ -153,6 +152,7 @@ import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.util.Utils; import com.android.systemui.util.settings.SecureSettings; +import com.android.systemui.wallet.controller.QuickAccessWalletController; import com.android.wm.shell.animation.FlingAnimationUtils; import java.io.FileDescriptor; @@ -310,6 +310,7 @@ public class NotificationPanelViewController extends PanelViewController { private final FeatureFlags mFeatureFlags; private final ScrimController mScrimController; private final PrivacyDotViewController mPrivacyDotViewController; + private final QuickAccessWalletController mQuickAccessWalletController; // Maximum # notifications to show on Keyguard; extras will be collapsed in an overflow card. // If there are exactly 1 + mMaxKeyguardNotifications, then still shows all notifications @@ -583,7 +584,6 @@ public class NotificationPanelViewController extends PanelViewController { private int mScreenCornerRadius; private int mNotificationScrimPadding; - private final QuickAccessWalletClient mQuickAccessWalletClient; private final Executor mUiExecutor; private final SecureSettings mSecureSettings; @@ -664,11 +664,11 @@ public class NotificationPanelViewController extends PanelViewController { AmbientState ambientState, LockIconViewController lockIconViewController, FeatureFlags featureFlags, - QuickAccessWalletClient quickAccessWalletClient, KeyguardMediaController keyguardMediaController, PrivacyDotViewController privacyDotViewController, TapAgainViewController tapAgainViewController, FragmentService fragmentService, + QuickAccessWalletController quickAccessWalletController, @Main Executor uiExecutor, SecureSettings secureSettings) { super(view, falsingManager, dozeLog, keyguardStateController, @@ -679,6 +679,7 @@ public class NotificationPanelViewController extends PanelViewController { mVibratorHelper = vibratorHelper; mKeyguardMediaController = keyguardMediaController; mPrivacyDotViewController = privacyDotViewController; + mQuickAccessWalletController = quickAccessWalletController; mMetricsLogger = metricsLogger; mActivityManager = activityManager; mConfigurationController = configurationController; @@ -721,7 +722,6 @@ public class NotificationPanelViewController extends PanelViewController { mScrimController.setClipsQsScrim(!mShouldUseSplitNotificationShade); mUserManager = userManager; mMediaDataManager = mediaDataManager; - mQuickAccessWalletClient = quickAccessWalletClient; mTapAgainViewController = tapAgainViewController; mUiExecutor = uiExecutor; mSecureSettings = secureSettings; @@ -1122,7 +1122,7 @@ public class NotificationPanelViewController extends PanelViewController { mKeyguardBottomArea.setFalsingManager(mFalsingManager); if (mFeatureFlags.isQuickAccessWalletEnabled()) { - mKeyguardBottomArea.initWallet(mQuickAccessWalletClient, mUiExecutor, mSecureSettings); + mKeyguardBottomArea.initWallet(mQuickAccessWalletController); } } diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java new file mode 100644 index 0000000000000..9d0cc6a00ec0e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.wallet.controller; + +import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE; +import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE; + +import android.content.Context; +import android.database.ContentObserver; +import android.provider.Settings; +import android.service.quickaccesswallet.GetWalletCardsRequest; +import android.service.quickaccesswallet.QuickAccessWalletClient; +import android.service.quickaccesswallet.QuickAccessWalletClientImpl; +import android.util.Log; + +import com.android.systemui.R; +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.util.settings.SecureSettings; + +import java.util.concurrent.Executor; + +import javax.inject.Inject; + +/** + * Controller to handle communication between SystemUI and Quick Access Wallet Client. + */ +@SysUISingleton +public class QuickAccessWalletController { + + /** + * Event for the wallet status change, e.g. the default payment app change and the wallet + * preference change. + */ + public enum WalletChangeEvent { + DEFAULT_PAYMENT_APP_CHANGE, + WALLET_PREFERENCE_CHANGE, + } + + private static final String TAG = "QAWController"; + private final Context mContext; + private final Executor mExecutor; + private final SecureSettings mSecureSettings; + + private QuickAccessWalletClient mQuickAccessWalletClient; + private ContentObserver mWalletPreferenceObserver; + private ContentObserver mDefaultPaymentAppObserver; + private int mWalletPreferenceChangeEvents = 0; + private int mDefaultPaymentAppChangeEvents = 0; + private boolean mWalletEnabled = false; + + @Inject + public QuickAccessWalletController( + Context context, + @Main Executor executor, + SecureSettings secureSettings, + QuickAccessWalletClient quickAccessWalletClient) { + mContext = context; + mExecutor = executor; + mSecureSettings = secureSettings; + mQuickAccessWalletClient = quickAccessWalletClient; + } + + /** + * Returns true if the Quick Access Wallet service & feature is available. + */ + public boolean isWalletEnabled() { + return mWalletEnabled; + } + + /** + * Returns the current instance of {@link QuickAccessWalletClient} in the controller. + */ + public QuickAccessWalletClient getWalletClient() { + return mQuickAccessWalletClient; + } + + /** + * Setup the wallet change observers per {@link WalletChangeEvent} + * + * @param cardsRetriever a callback that retrieves the wallet cards + * @param events {@link WalletChangeEvent} need to be handled. + */ + public void setupWalletChangeObservers( + QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever, + WalletChangeEvent... events) { + for (WalletChangeEvent event : events) { + if (event == WALLET_PREFERENCE_CHANGE) { + setupWalletPreferenceObserver(); + } else if (event == DEFAULT_PAYMENT_APP_CHANGE) { + setupDefaultPaymentAppObserver(cardsRetriever); + } + } + } + + /** + * Unregister wallet change observers per {@link WalletChangeEvent} if needed. + * + */ + public void unregisterWalletChangeObservers(WalletChangeEvent... events) { + for (WalletChangeEvent event : events) { + if (event == WALLET_PREFERENCE_CHANGE && mWalletPreferenceObserver != null) { + mWalletPreferenceChangeEvents--; + if (mWalletPreferenceChangeEvents == 0) { + mSecureSettings.unregisterContentObserver(mWalletPreferenceObserver); + } + } else if (event == DEFAULT_PAYMENT_APP_CHANGE && mDefaultPaymentAppObserver != null) { + mDefaultPaymentAppChangeEvents--; + if (mDefaultPaymentAppChangeEvents == 0) { + mSecureSettings.unregisterContentObserver(mDefaultPaymentAppObserver); + } + } + } + } + + /** + * Update the "show wallet" preference. + */ + public void updateWalletPreference() { + mWalletEnabled = mQuickAccessWalletClient.isWalletServiceAvailable() + && mQuickAccessWalletClient.isWalletFeatureAvailable() + && mQuickAccessWalletClient.isWalletFeatureAvailableWhenDeviceLocked(); + } + + /** + * Query the wallet cards from {@link QuickAccessWalletClient}. + * + * @param cardsRetriever a callback to retrieve wallet cards. + */ + public void queryWalletCards( + QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever) { + if (!mWalletEnabled) { + Log.w(TAG, "QuickAccessWallet is unavailable, unable to query cards."); + return; + } + int cardWidth = + mContext.getResources().getDimensionPixelSize(R.dimen.wallet_tile_card_view_width); + int cardHeight = + mContext.getResources().getDimensionPixelSize(R.dimen.wallet_tile_card_view_height); + int iconSizePx = mContext.getResources().getDimensionPixelSize(R.dimen.wallet_icon_size); + GetWalletCardsRequest request = + new GetWalletCardsRequest(cardWidth, cardHeight, iconSizePx, /* maxCards= */ 1); + mQuickAccessWalletClient.getWalletCards(mExecutor, request, cardsRetriever); + } + + /** + * Re-create the {@link QuickAccessWalletClient} of the controller. + */ + public void reCreateWalletClient() { + mQuickAccessWalletClient = QuickAccessWalletClient.create(mContext); + } + + private void setupDefaultPaymentAppObserver( + QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever) { + if (mDefaultPaymentAppObserver == null) { + mDefaultPaymentAppObserver = new ContentObserver(null /* handler */) { + @Override + public void onChange(boolean selfChange) { + mExecutor.execute(() -> { + reCreateWalletClient(); + updateWalletPreference(); + queryWalletCards(cardsRetriever); + }); + } + }; + + mSecureSettings.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT), + false /* notifyForDescendants */, + mDefaultPaymentAppObserver); + } + mDefaultPaymentAppChangeEvents++; + } + + private void setupWalletPreferenceObserver() { + if (mWalletPreferenceObserver == null) { + mWalletPreferenceObserver = new ContentObserver(null /* handler */) { + @Override + public void onChange(boolean selfChange) { + mExecutor.execute(() -> { + updateWalletPreference(); + }); + } + }; + + mSecureSettings.registerContentObserver( + Settings.Secure.getUriFor(QuickAccessWalletClientImpl.SETTING_KEY), + false /* notifyForDescendants */, + mWalletPreferenceObserver); + } + mWalletPreferenceChangeEvents++; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java index 83aa01f8d3931..c6123e77076d7 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java +++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java @@ -24,6 +24,7 @@ import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Handler; import android.service.quickaccesswallet.QuickAccessWalletClient; +import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.Window; @@ -52,7 +53,7 @@ import javax.inject.Inject; */ public class WalletActivity extends LifecycleActivity { - private final QuickAccessWalletClient mQuickAccessWalletClient; + private static final String TAG = "WalletActivity"; private final KeyguardStateController mKeyguardStateController; private final KeyguardDismissUtil mKeyguardDismissUtil; private final ActivityStarter mActivityStarter; @@ -65,7 +66,6 @@ public class WalletActivity extends LifecycleActivity { @Inject public WalletActivity( - QuickAccessWalletClient quickAccessWalletClient, KeyguardStateController keyguardStateController, KeyguardDismissUtil keyguardDismissUtil, ActivityStarter activityStarter, @@ -74,7 +74,6 @@ public class WalletActivity extends LifecycleActivity { FalsingManager falsingManager, UserTracker userTracker, StatusBarKeyguardViewManager keyguardViewManager) { - mQuickAccessWalletClient = quickAccessWalletClient; mKeyguardStateController = keyguardStateController; mKeyguardDismissUtil = keyguardDismissUtil; mActivityStarter = activityStarter; @@ -103,10 +102,11 @@ public class WalletActivity extends LifecycleActivity { getActionBar().setHomeActionContentDescription(R.string.accessibility_desc_close); WalletView walletView = requireViewById(R.id.wallet_view); + QuickAccessWalletClient walletClient = QuickAccessWalletClient.create(this); mWalletScreenController = new WalletScreenController( this, walletView, - mQuickAccessWalletClient, + walletClient, mActivityStarter, mExecutor, mHandler, @@ -116,6 +116,10 @@ public class WalletActivity extends LifecycleActivity { walletView.getAppButton().setOnClickListener( v -> { + if (walletClient.createWalletIntent() == null) { + Log.w(TAG, "Unable to create wallet app intent."); + return; + } if (!mKeyguardStateController.isUnlocked() && mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { return; @@ -123,12 +127,12 @@ public class WalletActivity extends LifecycleActivity { if (mKeyguardStateController.isUnlocked()) { mActivityStarter.startActivity( - mQuickAccessWalletClient.createWalletIntent(), true); + walletClient.createWalletIntent(), true); finish(); } else { mKeyguardDismissUtil.executeWhenUnlocked(() -> { mActivityStarter.startActivity( - mQuickAccessWalletClient.createWalletIntent(), true); + walletClient.createWalletIntent(), true); finish(); return false; }, false, true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java index 7533cf1310de9..b09afab3d242f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java @@ -73,6 +73,7 @@ import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.settings.SecureSettings; +import com.android.systemui.wallet.controller.QuickAccessWalletController; import com.google.common.util.concurrent.MoreExecutors; @@ -119,6 +120,8 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { @Mock private SecureSettings mSecureSettings; @Mock + private QuickAccessWalletController mController; + @Mock private FeatureFlags mFeatureFlags; @Captor ArgumentCaptor mIntentCaptor; @@ -145,6 +148,8 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { when(mQuickAccessWalletClient.getServiceLabel()).thenReturn(LABEL); when(mQuickAccessWalletClient.isWalletFeatureAvailable()).thenReturn(true); when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(true); + when(mQuickAccessWalletClient.isWalletFeatureAvailableWhenDeviceLocked()).thenReturn(true); + when(mController.getWalletClient()).thenReturn(mQuickAccessWalletClient); mTile = new QuickAccessWalletTile( mHost, @@ -155,11 +160,11 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { mStatusBarStateController, mActivityStarter, mQSLogger, - mQuickAccessWalletClient, mKeyguardStateController, mPackageManager, mSecureSettings, MoreExecutors.directExecutor(), + mController, mFeatureFlags); } @@ -174,6 +179,15 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { assertFalse(mTile.isAvailable()); } + @Test + public void testWalletServiceUnavailable_recreateWalletClient() { + when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(false); + + mTile.handleSetListening(true); + + verify(mController, times(1)).reCreateWalletClient(); + } + @Test public void testIsAvailable_qawFeatureAvailable() { when(mPackageManager.hasSystemFeature(FEATURE_NFC_HOST_CARD_EMULATION)).thenReturn(true); @@ -330,17 +344,8 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { public void testHandleSetListening_queryCards() { mTile.handleSetListening(true); - verify(mQuickAccessWalletClient) - .getWalletCards(any(), mRequestCaptor.capture(), mCallbackCaptor.capture()); + verify(mController).queryWalletCards(mCallbackCaptor.capture()); - GetWalletCardsRequest request = mRequestCaptor.getValue(); - assertEquals( - mContext.getResources().getDimensionPixelSize(R.dimen.wallet_tile_card_view_width), - request.getCardWidthPx()); - assertEquals( - mContext.getResources().getDimensionPixelSize(R.dimen.wallet_tile_card_view_height), - request.getCardHeightPx()); - assertEquals(1, request.getMaxCards()); assertThat(mCallbackCaptor.getValue()).isInstanceOf( QuickAccessWalletClient.OnWalletCardsRetrievedCallback.class); } @@ -353,37 +358,6 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { assertNotNull(mTile.getState().sideViewCustomDrawable); } - @Test - public void testState_queryCards_hasCards_then_noCards() { - when(mKeyguardStateController.isUnlocked()).thenReturn(true); - GetWalletCardsResponse responseWithCards = - new GetWalletCardsResponse( - Collections.singletonList(createWalletCard(mContext)), 0); - GetWalletCardsResponse responseWithoutCards = - new GetWalletCardsResponse(Collections.EMPTY_LIST, 0); - - mTile.handleSetListening(true); - - verify(mQuickAccessWalletClient).getWalletCards(any(), any(), mCallbackCaptor.capture()); - - // query wallet cards, has cards - mCallbackCaptor.getValue().onWalletCardsRetrieved(responseWithCards); - mTestableLooper.processAllMessages(); - - assertNotNull(mTile.getState().sideViewCustomDrawable); - - mTile.handleSetListening(true); - - verify(mQuickAccessWalletClient, times(2)) - .getWalletCards(any(), any(), mCallbackCaptor.capture()); - - // query wallet cards, has no cards - mCallbackCaptor.getValue().onWalletCardsRetrieved(responseWithoutCards); - mTestableLooper.processAllMessages(); - - assertNull(mTile.getState().sideViewCustomDrawable); - } - @Test public void testQueryCards_noCards_notUpdateSideViewDrawable() { setUpWalletCard(/* hasCard= */ false); @@ -398,7 +372,7 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { mTile.handleSetListening(true); - verify(mQuickAccessWalletClient).getWalletCards(any(), any(), mCallbackCaptor.capture()); + verify(mController).queryWalletCards(mCallbackCaptor.capture()); mCallbackCaptor.getValue().onWalletCardRetrievalError(error); mTestableLooper.processAllMessages(); @@ -422,7 +396,7 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { mTile.handleSetListening(true); - verify(mQuickAccessWalletClient).getWalletCards(any(), any(), mCallbackCaptor.capture()); + verify(mController).queryWalletCards(mCallbackCaptor.capture()); mCallbackCaptor.getValue().onWalletCardsRetrieved(response); mTestableLooper.processAllMessages(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java index 83a1872a0a451..ee8d1209a5cb3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java @@ -42,7 +42,6 @@ import android.content.res.Resources; import android.hardware.biometrics.BiometricSourceType; import android.os.PowerManager; import android.os.UserManager; -import android.service.quickaccesswallet.QuickAccessWalletClient; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.DisplayMetrics; @@ -112,6 +111,7 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.time.FakeSystemClock; +import com.android.systemui.wallet.controller.QuickAccessWalletController; import com.android.wm.shell.animation.FlingAnimationUtils; import org.junit.Before; @@ -252,8 +252,6 @@ public class NotificationPanelViewTest extends SysuiTestCase { @Mock private LockIconViewController mLockIconViewController; @Mock - private QuickAccessWalletClient mQuickAccessWalletClient; - @Mock private KeyguardMediaController mKeyguardMediaController; @Mock private PrivacyDotViewController mPrivacyDotViewController; @@ -267,6 +265,8 @@ public class NotificationPanelViewTest extends SysuiTestCase { private FragmentService mFragmentService; @Mock private FragmentHostManager mFragmentHostManager; + @Mock + private QuickAccessWalletController mQuickAccessWalletController; private SysuiStatusBarStateController mStatusBarStateController; private NotificationPanelViewController mNotificationPanelViewController; @@ -380,11 +380,11 @@ public class NotificationPanelViewTest extends SysuiTestCase { mAmbientState, mLockIconViewController, mFeatureFlags, - mQuickAccessWalletClient, mKeyguardMediaController, mPrivacyDotViewController, mTapAgainViewController, mFragmentService, + mQuickAccessWalletController, new FakeExecutor(new FakeSystemClock()), mSecureSettings); mNotificationPanelViewController.initDependencies( diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java new file mode 100644 index 0000000000000..33666bc5b4627 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.wallet.controller; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.service.quickaccesswallet.GetWalletCardsRequest; +import android.service.quickaccesswallet.QuickAccessWalletClient; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.util.settings.SecureSettings; + +import com.google.common.util.concurrent.MoreExecutors; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +@SmallTest +public class QuickAccessWalletControllerTest extends SysuiTestCase { + + @Mock + private QuickAccessWalletClient mQuickAccessWalletClient; + @Mock + private SecureSettings mSecureSettings; + @Mock + private QuickAccessWalletClient.OnWalletCardsRetrievedCallback mCardsRetriever; + @Captor + private ArgumentCaptor mRequestCaptor; + + private QuickAccessWalletController mController; + private TestableLooper mTestableLooper; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mTestableLooper = TestableLooper.get(this); + when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(true); + when(mQuickAccessWalletClient.isWalletFeatureAvailable()).thenReturn(true); + when(mQuickAccessWalletClient.isWalletFeatureAvailableWhenDeviceLocked()).thenReturn(true); + + mController = new QuickAccessWalletController( + mContext, + MoreExecutors.directExecutor(), + mSecureSettings, + mQuickAccessWalletClient); + } + + @Test + public void walletEnabled() { + mController.updateWalletPreference(); + + assertTrue(mController.isWalletEnabled()); + } + + @Test + public void walletServiceUnavailable_walletNotEnabled() { + when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(false); + + mController.updateWalletPreference(); + + assertFalse(mController.isWalletEnabled()); + } + + @Test + public void walletFeatureUnavailable_walletNotEnabled() { + when(mQuickAccessWalletClient.isWalletFeatureAvailable()).thenReturn(false); + + mController.updateWalletPreference(); + + assertFalse(mController.isWalletEnabled()); + } + + @Test + public void walletFeatureWhenLockedUnavailable_walletNotEnabled() { + when(mQuickAccessWalletClient.isWalletFeatureAvailableWhenDeviceLocked()).thenReturn(false); + + mController.updateWalletPreference(); + + assertFalse(mController.isWalletEnabled()); + } + + @Test + public void getWalletClient_NoRecreation_sameClient() { + assertSame(mQuickAccessWalletClient, mController.getWalletClient()); + } + + @Test + public void getWalletClient_reCreateClient_notSameClient() { + mController.reCreateWalletClient(); + + assertNotSame(mQuickAccessWalletClient, mController.getWalletClient()); + } + + @Test + public void queryWalletCards_walletNotEnabled_notQuery() { + when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(false); + + mController.queryWalletCards(mCardsRetriever); + + verify(mQuickAccessWalletClient, never()).getWalletCards(any(), any(), any()); + } + + @Test + public void queryWalletCards_walletEnabled_queryCards() { + mController.updateWalletPreference(); + mController.queryWalletCards(mCardsRetriever); + + verify(mQuickAccessWalletClient) + .getWalletCards( + eq(MoreExecutors.directExecutor()), + mRequestCaptor.capture(), + eq(mCardsRetriever)); + + GetWalletCardsRequest request = mRequestCaptor.getValue(); + assertEquals(1, mRequestCaptor.getValue().getMaxCards()); + assertEquals( + mContext.getResources().getDimensionPixelSize(R.dimen.wallet_tile_card_view_width), + request.getCardWidthPx()); + assertEquals( + mContext.getResources().getDimensionPixelSize(R.dimen.wallet_tile_card_view_height), + request.getCardHeightPx()); + } +}