Support non-active tokens in QuickAccessWallet.
1. Individual card states (active, pendingActivation, suspended) have their own header icon and label. When scrolling between cards that have different states, the icon and label will fade in/out. 2. The pendingActivation cards will have a verification button underneath the card view; the button text is parsed from cards label. 3. Add an overflow menu for wallet view, it will navigate to Lock Screen Settings page. (TODO in next CL) 4. Update the way we render header icon: because gmscore now provide new version of icons, the programmatical tweaks no longer needed on the SysUI side. 5. Add Falsing prevention for the locked state wallet view: cardImage/actionButton/appButton 6. Other UI polish: change the Wallet Tile secondary label to card last4 digits, etc. demo: https://drive.google.com/file/d/1syNnPFdIGh24sHWU2So-wsPxZTIg7xI9/view?usp=sharing&resourcekey=0-Iw3ZotiOJlE8jPImK7QQhQ Test: manual Test: atest Bug: 186496392 Bug: 150332483 Fix: 184057200 Fix: 184905963 Change-Id: I84f487154104ffe1636c98ef1538bf99102c0442
This commit is contained in:
@@ -1,23 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2019 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.
|
||||
-->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<size
|
||||
android:height="56dp"
|
||||
android:width="56dp" />
|
||||
<solid android:color="@android:color/transparent" />
|
||||
<stroke android:width="2dp" android:color="@color/GM2_grey_300" />
|
||||
</shape>
|
||||
@@ -19,6 +19,13 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipChildren="false">
|
||||
<Toolbar
|
||||
android:id="@+id/action_bar"
|
||||
style="?android:attr/actionBarStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:color/transparent"
|
||||
android:navigationContentDescription="@null" />
|
||||
<LinearLayout
|
||||
android:id="@+id/card_carousel_container"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2019 The Android Open Source Project
|
||||
<!--
|
||||
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.
|
||||
@@ -13,11 +14,11 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<size
|
||||
android:height="56dp"
|
||||
android:width="56dp" />
|
||||
<solid android:color="@android:color/transparent" />
|
||||
<stroke android:width="2dp" android:color="#AECBFA" />
|
||||
</shape>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/wallet_lockscreen_settings"
|
||||
android:enabled="true"
|
||||
android:showAsAction="never"
|
||||
android:title="@string/wallet_lockscreen_settings_label"
|
||||
android:visible="true" />
|
||||
</menu>
|
||||
@@ -1477,6 +1477,7 @@
|
||||
|
||||
<!-- Wallet activity screen specs -->
|
||||
<dimen name="wallet_icon_size">36sp</dimen>
|
||||
<dimen name="wallet_view_header_icon_size">56dp</dimen>
|
||||
<dimen name="card_margin">16dp</dimen>
|
||||
<dimen name="card_carousel_dot_offset">24dp</dimen>
|
||||
<dimen name="card_carousel_dot_unselected_radius">2dp</dimen>
|
||||
|
||||
@@ -1656,14 +1656,14 @@
|
||||
<string name="wallet_app_button_label">Show all</string>
|
||||
<!-- Label of the button underneath the card carousel prompting user unlock device. [CHAR LIMIT=NONE] -->
|
||||
<string name="wallet_action_button_label_unlock">Unlock to pay</string>
|
||||
<!-- Secondary label of the quick access wallet tile if active. [CHAR LIMIT=32] -->
|
||||
<string name="wallet_secondary_label_active">Ready</string>
|
||||
<!-- Secondary label of the quick access wallet tile if no card. [CHAR LIMIT=NONE] -->
|
||||
<string name="wallet_secondary_label_no_card">Set up payment</string>
|
||||
<string name="wallet_secondary_label_no_card">Not set up</string>
|
||||
<!-- Secondary label of the quick access wallet tile if device locked. [CHAR LIMIT=NONE] -->
|
||||
<string name="wallet_secondary_label_device_locked">Unlock to use</string>
|
||||
<!-- Message shown when an unknown failure occurred when fetching cards. [CHAR LIMIT=NONE] -->
|
||||
<string name="wallet_error_generic">There was a problem getting your cards, please try again later</string>
|
||||
<!-- Overflow menu item label to open the lockscreen settings in wallet activity. [CHAR LIMIT=NONE] -->
|
||||
<string name="wallet_lockscreen_settings_label">Lock screen settings</string>
|
||||
|
||||
<!-- Name of the work status bar icon. -->
|
||||
<string name="status_bar_work">Work profile</string>
|
||||
|
||||
@@ -75,7 +75,7 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
|
||||
private final FeatureFlags mFeatureFlags;
|
||||
|
||||
@VisibleForTesting Drawable mCardViewDrawable;
|
||||
private boolean mHasCard;
|
||||
private WalletCard mSelectedCard;
|
||||
|
||||
@Inject
|
||||
public QuickAccessWalletTile(
|
||||
@@ -125,7 +125,7 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
|
||||
view == null ? null : ActivityLaunchAnimator.Controller.fromView(view);
|
||||
|
||||
mUiHandler.post(() -> {
|
||||
if (mHasCard) {
|
||||
if (mSelectedCard != null) {
|
||||
Intent intent = new Intent(mContext, WalletActivity.class)
|
||||
.setAction(Intent.ACTION_VIEW)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
@@ -157,15 +157,14 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
|
||||
state.icon = ResourceIcon.get(R.drawable.ic_wallet_lockscreen);
|
||||
boolean isDeviceLocked = !mKeyguardStateController.isUnlocked();
|
||||
if (mQuickAccessWalletClient.isWalletServiceAvailable()) {
|
||||
if (mHasCard) {
|
||||
if (mSelectedCard != null) {
|
||||
if (isDeviceLocked) {
|
||||
state.state = Tile.STATE_INACTIVE;
|
||||
state.secondaryLabel =
|
||||
mContext.getString(R.string.wallet_secondary_label_device_locked);
|
||||
} else {
|
||||
state.state = Tile.STATE_ACTIVE;
|
||||
state.secondaryLabel =
|
||||
mContext.getString(R.string.wallet_secondary_label_active);
|
||||
state.secondaryLabel = mSelectedCard.getContentDescription();
|
||||
}
|
||||
} else {
|
||||
state.state = Tile.STATE_INACTIVE;
|
||||
@@ -208,7 +207,7 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
|
||||
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= */ 2);
|
||||
new GetWalletCardsRequest(cardWidth, cardHeight, iconSizePx, /* maxCards= */ 1);
|
||||
mQuickAccessWalletClient.getWalletCards(mExecutor, request, mCardRetriever);
|
||||
}
|
||||
|
||||
@@ -222,7 +221,7 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
|
||||
if (cards.isEmpty()) {
|
||||
Log.d(TAG, "No wallet cards exist.");
|
||||
mCardViewDrawable = null;
|
||||
mHasCard = false;
|
||||
mSelectedCard = null;
|
||||
refreshState();
|
||||
return;
|
||||
}
|
||||
@@ -231,8 +230,8 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
|
||||
Log.d(TAG, "Selected card index out of bounds.");
|
||||
return;
|
||||
}
|
||||
mCardViewDrawable = cards.get(selectedIndex).getCardImage().loadDrawable(mContext);
|
||||
mHasCard = true;
|
||||
mSelectedCard = cards.get(selectedIndex);
|
||||
mCardViewDrawable = mSelectedCard.getCardImage().loadDrawable(mContext);
|
||||
refreshState();
|
||||
}
|
||||
|
||||
@@ -240,7 +239,7 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
|
||||
public void onWalletCardRetrievalError(@NonNull GetWalletCardsError error) {
|
||||
Log.w(TAG, "Error retrieve wallet cards");
|
||||
mCardViewDrawable = null;
|
||||
mHasCard = false;
|
||||
mSelectedCard = null;
|
||||
refreshState();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,13 +16,14 @@
|
||||
|
||||
package com.android.systemui.wallet.ui;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.service.quickaccesswallet.QuickAccessWalletClient;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Toolbar;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
@@ -30,6 +31,7 @@ import com.android.systemui.R;
|
||||
import com.android.systemui.dagger.qualifiers.Background;
|
||||
import com.android.systemui.dagger.qualifiers.Main;
|
||||
import com.android.systemui.plugins.ActivityStarter;
|
||||
import com.android.systemui.plugins.FalsingManager;
|
||||
import com.android.systemui.settings.UserTracker;
|
||||
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
|
||||
import com.android.systemui.statusbar.policy.KeyguardStateController;
|
||||
@@ -50,6 +52,7 @@ public class WalletActivity extends LifecycleActivity {
|
||||
private final ActivityStarter mActivityStarter;
|
||||
private final Executor mExecutor;
|
||||
private final Handler mHandler;
|
||||
private final FalsingManager mFalsingManager;
|
||||
private final UserTracker mUserTracker;
|
||||
private WalletScreenController mWalletScreenController;
|
||||
|
||||
@@ -61,6 +64,7 @@ public class WalletActivity extends LifecycleActivity {
|
||||
ActivityStarter activityStarter,
|
||||
@Background Executor executor,
|
||||
@Main Handler handler,
|
||||
FalsingManager falsingManager,
|
||||
UserTracker userTracker) {
|
||||
mQuickAccessWalletClient = quickAccessWalletClient;
|
||||
mKeyguardStateController = keyguardStateController;
|
||||
@@ -68,19 +72,23 @@ public class WalletActivity extends LifecycleActivity {
|
||||
mActivityStarter = activityStarter;
|
||||
mExecutor = executor;
|
||||
mHandler = handler;
|
||||
mFalsingManager = falsingManager;
|
||||
mUserTracker = userTracker;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
getWindow().requestFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
setContentView(R.layout.quick_access_wallet);
|
||||
|
||||
getWindow().getDecorView().setBackgroundColor(getColor(R.color.wallet_white));
|
||||
Toolbar toolbar = findViewById(R.id.action_bar);
|
||||
if (toolbar != null) {
|
||||
setActionBar(toolbar);
|
||||
}
|
||||
setTitle("");
|
||||
getActionBar().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
|
||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getActionBar().setHomeAsUpIndicator(R.drawable.ic_close);
|
||||
getActionBar().setHomeActionContentDescription(R.string.accessibility_desc_close);
|
||||
@@ -93,15 +101,28 @@ public class WalletActivity extends LifecycleActivity {
|
||||
mExecutor,
|
||||
mHandler,
|
||||
mUserTracker,
|
||||
mFalsingManager,
|
||||
mKeyguardStateController);
|
||||
|
||||
walletView.getAppButton().setOnClickListener(
|
||||
v -> mActivityStarter.startActivity(
|
||||
mQuickAccessWalletClient.createWalletIntent(), true));
|
||||
v -> {
|
||||
if (!mKeyguardStateController.isUnlocked()
|
||||
&& mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
|
||||
return;
|
||||
}
|
||||
mActivityStarter.startActivity(
|
||||
mQuickAccessWalletClient.createWalletIntent(), true);
|
||||
finish();
|
||||
});
|
||||
// Click the action button to re-render the screen when the device is unlocked.
|
||||
if (!mKeyguardStateController.isUnlocked()) {
|
||||
walletView.getActionButton().setOnClickListener(
|
||||
v -> mKeyguardDismissUtil.executeWhenUnlocked(() -> false, false));
|
||||
v -> {
|
||||
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
|
||||
return;
|
||||
}
|
||||
mKeyguardDismissUtil.executeWhenUnlocked(() -> false, false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,12 +138,21 @@ public class WalletActivity extends LifecycleActivity {
|
||||
mWalletScreenController.queryWalletCards();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.wallet_activity_options_menu, menu);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
int itemId = item.getItemId();
|
||||
if (itemId == android.R.id.home) {
|
||||
finish();
|
||||
return true;
|
||||
} else if (itemId == R.id.wallet_lockscreen_settings) {
|
||||
// TODO(b/186496392): Navigate to Lock Screen Settings page when the item is clicked.
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@@ -205,8 +205,8 @@ public class WalletCardCarousel extends RecyclerView {
|
||||
boolean setData(List<WalletCardViewInfo> data, int selectedIndex) {
|
||||
boolean wasEmpty = mWalletCardCarouselAdapter.getItemCount() == 0;
|
||||
mWalletCardCarouselAdapter.setData(data);
|
||||
scrollToPosition(selectedIndex);
|
||||
if (wasEmpty) {
|
||||
scrollToPosition(selectedIndex);
|
||||
mNumCardsToAnimate = numCardsOnScreen(data.size(), selectedIndex);
|
||||
mCardAnimationStartPosition = Math.max(selectedIndex - 1, 0);
|
||||
}
|
||||
@@ -427,15 +427,6 @@ public class WalletCardCarousel extends RecyclerView {
|
||||
mSelectionListener.onCardClicked(cardViewInfo);
|
||||
}
|
||||
});
|
||||
if (mNumCardsToAnimate > 0 && (position - mCardAnimationStartPosition < 2)) {
|
||||
mNumCardsToAnimate--;
|
||||
int startDelay = (position - mCardAnimationStartPosition) * CARD_ANIM_ALPHA_DELAY
|
||||
+ mExtraAnimationDelay;
|
||||
viewHolder.itemView.setAlpha(0f);
|
||||
viewHolder.itemView.animate().alpha(1f)
|
||||
.setStartDelay(Math.max(0, startDelay))
|
||||
.setDuration(CARD_ANIM_ALPHA_DURATION).start();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.systemui.wallet.ui;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -47,4 +48,10 @@ interface WalletCardViewInfo {
|
||||
*/
|
||||
@NonNull
|
||||
CharSequence getLabel();
|
||||
|
||||
/**
|
||||
* Pending intent upon the card is clicked.
|
||||
*/
|
||||
@NonNull
|
||||
PendingIntent getPendingIntent();
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.systemui.wallet.ui;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
@@ -41,6 +42,7 @@ import androidx.annotation.NonNull;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.plugins.ActivityStarter;
|
||||
import com.android.systemui.plugins.FalsingManager;
|
||||
import com.android.systemui.settings.UserTracker;
|
||||
import com.android.systemui.statusbar.policy.KeyguardStateController;
|
||||
|
||||
@@ -57,7 +59,6 @@ public class WalletScreenController implements
|
||||
KeyguardStateController.Callback {
|
||||
|
||||
private static final String TAG = "WalletScreenCtrl";
|
||||
private static final String PREFS_HAS_CARDS = "has_cards";
|
||||
private static final String PREFS_WALLET_VIEW_HEIGHT = "wallet_view_height";
|
||||
private static final int MAX_CARDS = 10;
|
||||
private static final long SELECTION_DELAY_MILLIS = TimeUnit.SECONDS.toMillis(30);
|
||||
@@ -72,6 +73,7 @@ public class WalletScreenController implements
|
||||
private final SharedPreferences mPrefs;
|
||||
private final WalletView mWalletView;
|
||||
private final WalletCardCarousel mCardCarousel;
|
||||
private final FalsingManager mFalsingManager;
|
||||
|
||||
@VisibleForTesting String mSelectedCardId;
|
||||
@VisibleForTesting boolean mIsDismissed;
|
||||
@@ -85,12 +87,14 @@ public class WalletScreenController implements
|
||||
Executor executor,
|
||||
Handler handler,
|
||||
UserTracker userTracker,
|
||||
FalsingManager falsingManager,
|
||||
KeyguardStateController keyguardStateController) {
|
||||
mContext = context;
|
||||
mWalletClient = walletClient;
|
||||
mActivityStarter = activityStarter;
|
||||
mExecutor = executor;
|
||||
mHandler = handler;
|
||||
mFalsingManager = falsingManager;
|
||||
mKeyguardStateController = keyguardStateController;
|
||||
mPrefs = userTracker.getUserContext().getSharedPreferences(TAG, Context.MODE_PRIVATE);
|
||||
mWalletView = walletView;
|
||||
@@ -102,12 +106,6 @@ public class WalletScreenController implements
|
||||
if (mCardCarousel != null) {
|
||||
mCardCarousel.setSelectionListener(this);
|
||||
}
|
||||
|
||||
if (!mPrefs.getBoolean(PREFS_HAS_CARDS, false)) {
|
||||
// The empty state view is shown preemptively when cards were not returned last time
|
||||
// to decrease perceived latency.
|
||||
showEmptyStateView();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -136,8 +134,6 @@ public class WalletScreenController implements
|
||||
mWalletView.showCardCarousel(
|
||||
data, response.getSelectedIndex(), !mKeyguardStateController.isUnlocked());
|
||||
}
|
||||
// The empty state view will not be shown preemptively next time if cards were returned
|
||||
mPrefs.edit().putBoolean(PREFS_HAS_CARDS, !data.isEmpty()).apply();
|
||||
removeMinHeightAndRecordHeightOnLayout();
|
||||
});
|
||||
}
|
||||
@@ -167,7 +163,6 @@ public class WalletScreenController implements
|
||||
}
|
||||
switch (event.getEventType()) {
|
||||
case WalletServiceEvent.TYPE_NFC_PAYMENT_STARTED:
|
||||
onDismissed();
|
||||
break;
|
||||
case WalletServiceEvent.TYPE_WALLET_CARDS_UPDATED:
|
||||
queryWalletCards();
|
||||
@@ -207,14 +202,17 @@ public class WalletScreenController implements
|
||||
|
||||
@Override
|
||||
public void onCardClicked(@NonNull WalletCardViewInfo cardInfo) {
|
||||
if (!mKeyguardStateController.isUnlocked()
|
||||
&& mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
|
||||
return;
|
||||
}
|
||||
if (!(cardInfo instanceof QAWalletCardViewInfo)
|
||||
|| ((QAWalletCardViewInfo) cardInfo).mWalletCard == null
|
||||
|| ((QAWalletCardViewInfo) cardInfo).mWalletCard.getPendingIntent() == null) {
|
||||
return;
|
||||
}
|
||||
mActivityStarter.startActivity(
|
||||
((QAWalletCardViewInfo) cardInfo).mWalletCard.getPendingIntent().getIntent(),
|
||||
true);
|
||||
((QAWalletCardViewInfo) cardInfo).mWalletCard.getPendingIntent().getIntent(), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -235,7 +233,8 @@ public class WalletScreenController implements
|
||||
|
||||
mWalletView.show();
|
||||
mWalletView.hideErrorMessage();
|
||||
int iconSizePx = mContext.getResources().getDimensionPixelSize(R.dimen.wallet_icon_size);
|
||||
int iconSizePx =
|
||||
mContext.getResources().getDimensionPixelSize(R.dimen.wallet_view_header_icon_size);
|
||||
GetWalletCardsRequest request =
|
||||
new GetWalletCardsRequest(cardWidthPx, cardHeightPx, iconSizePx, MAX_CARDS);
|
||||
mWalletClient.getWalletCards(mExecutor, request, this);
|
||||
@@ -248,6 +247,7 @@ public class WalletScreenController implements
|
||||
mIsDismissed = true;
|
||||
mSelectedCardId = null;
|
||||
mHandler.removeCallbacks(mSelectionRunnable);
|
||||
mFalsingManager.cleanup();
|
||||
mWalletClient.notifyWalletDismissed();
|
||||
mWalletClient.removeWalletServiceEventListener(this);
|
||||
mWalletView.animateDismissal();
|
||||
@@ -342,5 +342,10 @@ public class WalletScreenController implements
|
||||
public CharSequence getLabel() {
|
||||
return mWalletCard.getCardLabel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PendingIntent getPendingIntent() {
|
||||
return mWalletCard.getPendingIntent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,13 +22,12 @@ import static com.android.systemui.wallet.ui.WalletCardCarousel.CARD_ANIM_ALPHA_
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.AnimationUtils;
|
||||
@@ -46,10 +45,10 @@ import java.util.List;
|
||||
/** Layout for the wallet screen. */
|
||||
public class WalletView extends FrameLayout implements WalletCardCarousel.OnCardScrollListener {
|
||||
|
||||
private static final String TAG = "WalletView";
|
||||
private static final int CAROUSEL_IN_ANIMATION_DURATION = 300;
|
||||
private static final int CAROUSEL_OUT_ANIMATION_DURATION = 200;
|
||||
private static final int CARD_LABEL_ANIM_DELAY = 133;
|
||||
private static final int CONTACTLESS_ICON_SIZE = 90;
|
||||
|
||||
private final WalletCardCarousel mCardCarousel;
|
||||
private final ImageView mIcon;
|
||||
@@ -65,6 +64,8 @@ public class WalletView extends FrameLayout implements WalletCardCarousel.OnCard
|
||||
private final TextView mErrorView;
|
||||
private final ViewGroup mEmptyStateView;
|
||||
private CharSequence mCenterCardText;
|
||||
private Drawable mCenterCardIcon;
|
||||
private boolean mIsDeviceLocked = false;
|
||||
|
||||
public WalletView(Context context) {
|
||||
this(context, null);
|
||||
@@ -104,16 +105,21 @@ public class WalletView extends FrameLayout implements WalletCardCarousel.OnCard
|
||||
@Override
|
||||
public void onCardScroll(WalletCardViewInfo centerCard, WalletCardViewInfo nextCard,
|
||||
float percentDistanceFromCenter) {
|
||||
CharSequence centerCardText = centerCard.getLabel();
|
||||
CharSequence centerCardText = getLabelText(centerCard);
|
||||
Drawable centerCardIcon = centerCard.getIcon();
|
||||
if (!TextUtils.equals(mCenterCardText, centerCardText)) {
|
||||
mCenterCardText = centerCardText;
|
||||
mCenterCardIcon = centerCardIcon;
|
||||
mCardLabel.setText(centerCardText);
|
||||
mIcon.setImageDrawable(centerCardIcon);
|
||||
}
|
||||
if (TextUtils.equals(centerCardText, nextCard.getLabel())) {
|
||||
renderActionButton(centerCard, mIsDeviceLocked);
|
||||
if (TextUtils.equals(centerCardText, getLabelText(nextCard))) {
|
||||
mCardLabel.setAlpha(1f);
|
||||
} else {
|
||||
mCardLabel.setAlpha(percentDistanceFromCenter);
|
||||
mIcon.setAlpha(percentDistanceFromCenter);
|
||||
mActionButton.setAlpha(percentDistanceFromCenter);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,10 +134,11 @@ public class WalletView extends FrameLayout implements WalletCardCarousel.OnCard
|
||||
*/
|
||||
void showCardCarousel(
|
||||
List<WalletCardViewInfo> data, int selectedIndex, boolean isDeviceLocked) {
|
||||
mIsDeviceLocked = isDeviceLocked;
|
||||
boolean shouldAnimate = mCardCarousel.setData(data, selectedIndex);
|
||||
mCardCarouselContainer.setVisibility(VISIBLE);
|
||||
mErrorView.setVisibility(GONE);
|
||||
renderHeaderIconAndActionButton(data.get(0), isDeviceLocked);
|
||||
renderHeaderIconAndActionButton(data.get(selectedIndex), isDeviceLocked);
|
||||
if (shouldAnimate) {
|
||||
// If the empty state is visible, animate it away and delay the card carousel animation
|
||||
int emptyStateAnimDelay = 0;
|
||||
@@ -239,39 +246,45 @@ public class WalletView extends FrameLayout implements WalletCardCarousel.OnCard
|
||||
return mCardCarouselContainer;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
TextView getCardLabel() {
|
||||
return mCardLabel;
|
||||
}
|
||||
|
||||
private void renderHeaderIconAndActionButton(WalletCardViewInfo walletCard, boolean isLocked) {
|
||||
Drawable icon = resizeDrawable(getResources(), walletCard.getIcon());
|
||||
renderHeaderIcon(icon, isLocked);
|
||||
if (isLocked) {
|
||||
mIcon.setImageDrawable(walletCard.getIcon());
|
||||
mIcon.setVisibility(VISIBLE);
|
||||
renderActionButton(walletCard, isLocked);
|
||||
}
|
||||
|
||||
private void renderActionButton(WalletCardViewInfo walletCard, boolean isDeviceLocked) {
|
||||
CharSequence actionButtonText = getActionButtonText(walletCard);
|
||||
if (isDeviceLocked) {
|
||||
mActionButton.setVisibility(VISIBLE);
|
||||
mActionButton.setText(R.string.wallet_action_button_label_unlock);
|
||||
} else if (actionButtonText != null) {
|
||||
mActionButton.setText(actionButtonText);
|
||||
mActionButton.setVisibility(VISIBLE);
|
||||
mActionButton.setOnClickListener(v -> {
|
||||
try {
|
||||
walletCard.getPendingIntent().send();
|
||||
} catch (PendingIntent.CanceledException e) {
|
||||
Log.w(TAG, "Error sending pending intent for wallet card");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
mActionButton.setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void renderHeaderIcon(@Nullable Drawable icon, boolean isLocked) {
|
||||
if (icon == null) {
|
||||
mIcon.setVisibility(INVISIBLE);
|
||||
return;
|
||||
}
|
||||
icon.setTint(mContext.getColor(isLocked ? R.color.GM2_grey_800 : R.color.GM2_blue_600));
|
||||
mIcon.setImageDrawable(icon);
|
||||
mIcon.setVisibility(VISIBLE);
|
||||
mIcon.setBackground(
|
||||
mContext.getDrawable(
|
||||
isLocked
|
||||
? R.drawable.circle_wallet_secondary_56dp
|
||||
: R.drawable.circle_wallet_primary_56dp));
|
||||
private static CharSequence getLabelText(WalletCardViewInfo card) {
|
||||
String[] rawLabel = card.getLabel().toString().split("\\n");
|
||||
return rawLabel.length == 2 ? rawLabel[0] : card.getLabel();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Drawable resizeDrawable(Resources resources, @Nullable Drawable drawable) {
|
||||
if (drawable == null) {
|
||||
return null;
|
||||
}
|
||||
Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
|
||||
return new BitmapDrawable(resources, Bitmap.createScaledBitmap(
|
||||
bitmap, CONTACTLESS_ICON_SIZE, CONTACTLESS_ICON_SIZE, true));
|
||||
private static CharSequence getActionButtonText(WalletCardViewInfo card) {
|
||||
String[] rawLabel = card.getLabel().toString().split("\\n");
|
||||
return rawLabel.length == 2 ? rawLabel[1] : null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,7 +284,7 @@ public class QuickAccessWalletTileTest extends SysuiTestCase {
|
||||
|
||||
assertEquals(Tile.STATE_ACTIVE, state.state);
|
||||
assertEquals(
|
||||
mContext.getString(R.string.wallet_secondary_label_active),
|
||||
"•••• 1234",
|
||||
state.secondaryLabel);
|
||||
assertNotNull(state.stateDescription);
|
||||
assertNotNull(state.sideViewDrawable);
|
||||
@@ -332,7 +332,7 @@ public class QuickAccessWalletTileTest extends SysuiTestCase {
|
||||
assertEquals(
|
||||
mContext.getResources().getDimensionPixelSize(R.dimen.wallet_tile_card_view_height),
|
||||
request.getCardHeightPx());
|
||||
assertEquals(2, request.getMaxCards());
|
||||
assertEquals(1, request.getMaxCards());
|
||||
assertThat(mCallbackCaptor.getValue()).isInstanceOf(
|
||||
QuickAccessWalletClient.OnWalletCardsRetrievedCallback.class);
|
||||
}
|
||||
@@ -423,6 +423,6 @@ public class QuickAccessWalletTileTest extends SysuiTestCase {
|
||||
private WalletCard createWalletCard(Context context) {
|
||||
PendingIntent pendingIntent =
|
||||
PendingIntent.getActivity(context, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE);
|
||||
return new WalletCard.Builder(CARD_ID, CARD_IMAGE, "description", pendingIntent).build();
|
||||
return new WalletCard.Builder(CARD_ID, CARD_IMAGE, "•••• 1234", pendingIntent).build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,12 +20,13 @@ import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
@@ -50,6 +51,7 @@ import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.plugins.ActivityStarter;
|
||||
import com.android.systemui.plugins.FalsingManager;
|
||||
import com.android.systemui.settings.UserTracker;
|
||||
import com.android.systemui.statusbar.policy.KeyguardStateController;
|
||||
|
||||
@@ -72,7 +74,8 @@ public class WalletScreenControllerTest extends SysuiTestCase {
|
||||
|
||||
private static final int MAX_CARDS = 10;
|
||||
private static final int CARD_CAROUSEL_WIDTH = 10;
|
||||
private static final String CARD_ID = "card_id";
|
||||
private static final String CARD_ID_1 = "card_id_1";
|
||||
private static final String CARD_ID_2 = "card_id_2";
|
||||
private static final CharSequence SHORTCUT_SHORT_LABEL = "View all";
|
||||
private static final CharSequence SHORTCUT_LONG_LABEL = "Add a payment method";
|
||||
private static final CharSequence SERVICE_LABEL = "Wallet app";
|
||||
@@ -89,6 +92,8 @@ public class WalletScreenControllerTest extends SysuiTestCase {
|
||||
@Mock
|
||||
UserTracker mUserTracker;
|
||||
@Mock
|
||||
FalsingManager mFalsingManager;
|
||||
@Mock
|
||||
KeyguardStateController mKeyguardStateController;
|
||||
@Captor
|
||||
ArgumentCaptor<Intent> mIntentCaptor;
|
||||
@@ -122,11 +127,12 @@ public class WalletScreenControllerTest extends SysuiTestCase {
|
||||
MoreExecutors.directExecutor(),
|
||||
new Handler(mTestableLooper.getLooper()),
|
||||
mUserTracker,
|
||||
mFalsingManager,
|
||||
mKeyguardStateController);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryCards_hasCards_showCarousel() {
|
||||
public void queryCards_hasCards_showCarousel_activeCard() {
|
||||
GetWalletCardsResponse response =
|
||||
new GetWalletCardsResponse(
|
||||
Collections.singletonList(createWalletCard(mContext)), 0);
|
||||
@@ -145,6 +151,33 @@ public class WalletScreenControllerTest extends SysuiTestCase {
|
||||
mTestableLooper.processAllMessages();
|
||||
|
||||
assertEquals(VISIBLE, mWalletView.getCardCarouselContainer().getVisibility());
|
||||
assertEquals("Hold to reader", mWalletView.getCardLabel().getText().toString());
|
||||
assertEquals(GONE, mWalletView.getErrorView().getVisibility());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryCards_hasCards_showCarousel_pendingActivationCard_parseLabel() {
|
||||
GetWalletCardsResponse response =
|
||||
new GetWalletCardsResponse(
|
||||
Collections.singletonList(createNonActiveWalletCard(mContext)), 0);
|
||||
|
||||
mController.queryWalletCards();
|
||||
mTestableLooper.processAllMessages();
|
||||
|
||||
verify(mWalletClient).getWalletCards(any(), any(), mCallbackCaptor.capture());
|
||||
|
||||
QuickAccessWalletClient.OnWalletCardsRetrievedCallback callback =
|
||||
mCallbackCaptor.getValue();
|
||||
|
||||
assertEquals(mController, callback);
|
||||
|
||||
callback.onWalletCardsRetrieved(response);
|
||||
mTestableLooper.processAllMessages();
|
||||
|
||||
assertEquals(VISIBLE, mWalletView.getCardCarouselContainer().getVisibility());
|
||||
assertEquals("Not set up", mWalletView.getCardLabel().getText().toString());
|
||||
assertEquals("Verify now", mWalletView.getActionButton().getText().toString());
|
||||
assertEquals(VISIBLE, mWalletView.getActionButton().getVisibility());
|
||||
assertEquals(GONE, mWalletView.getErrorView().getVisibility());
|
||||
}
|
||||
|
||||
@@ -185,7 +218,7 @@ public class WalletScreenControllerTest extends SysuiTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onWalletServiceEvent_nfcPaymentStart_dismiss() {
|
||||
public void onWalletServiceEvent_nfcPaymentStart_doNothing() {
|
||||
WalletServiceEvent event =
|
||||
new WalletServiceEvent(WalletServiceEvent.TYPE_NFC_PAYMENT_STARTED);
|
||||
|
||||
@@ -193,8 +226,8 @@ public class WalletScreenControllerTest extends SysuiTestCase {
|
||||
mTestableLooper.processAllMessages();
|
||||
|
||||
assertNull(mController.mSelectedCardId);
|
||||
assertTrue(mController.mIsDismissed);
|
||||
verify(mWalletClient).notifyWalletDismissed();
|
||||
assertFalse(mController.mIsDismissed);
|
||||
verifyZeroInteractions(mWalletClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -228,14 +261,14 @@ public class WalletScreenControllerTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void onCardSelected() {
|
||||
mController.onCardSelected(createCardViewInfo());
|
||||
mController.onCardSelected(createCardViewInfo(createWalletCard(mContext)));
|
||||
|
||||
assertEquals(CARD_ID, mController.mSelectedCardId);
|
||||
assertEquals(CARD_ID_1, mController.mSelectedCardId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onCardClicked_startIntent() {
|
||||
WalletCardViewInfo walletCardViewInfo = createCardViewInfo();
|
||||
WalletCardViewInfo walletCardViewInfo = createCardViewInfo(createWalletCard(mContext));
|
||||
|
||||
mController.onCardClicked(walletCardViewInfo);
|
||||
|
||||
@@ -278,18 +311,30 @@ public class WalletScreenControllerTest extends SysuiTestCase {
|
||||
assertEquals(GONE, mWalletView.getVisibility());
|
||||
}
|
||||
|
||||
private WalletCard createNonActiveWalletCard(Context context) {
|
||||
PendingIntent pendingIntent =
|
||||
PendingIntent.getActivity(context, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE);
|
||||
return new WalletCard.Builder(CARD_ID_2, createIcon(), "•••• 5678", pendingIntent)
|
||||
.setCardIcon(createIcon())
|
||||
.setCardLabel("Not set up\nVerify now")
|
||||
.build();
|
||||
}
|
||||
|
||||
private WalletCard createWalletCard(Context context) {
|
||||
PendingIntent pendingIntent =
|
||||
PendingIntent.getActivity(context, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE);
|
||||
return new WalletCard.Builder(CARD_ID, createIcon(), "description", pendingIntent).build();
|
||||
return new WalletCard.Builder(CARD_ID_1, createIcon(), "•••• 1234", pendingIntent)
|
||||
.setCardIcon(createIcon())
|
||||
.setCardLabel("Hold to reader")
|
||||
.build();
|
||||
}
|
||||
|
||||
private static Icon createIcon() {
|
||||
return Icon.createWithBitmap(Bitmap.createBitmap(70, 44, Bitmap.Config.ARGB_8888));
|
||||
}
|
||||
|
||||
private WalletCardViewInfo createCardViewInfo() {
|
||||
private WalletCardViewInfo createCardViewInfo(WalletCard walletCard) {
|
||||
return new WalletScreenController.QAWalletCardViewInfo(
|
||||
mContext, createWalletCard(mContext));
|
||||
mContext, walletCard);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user