Merge "Support non-active tokens in QuickAccessWallet." into sc-dev
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>
|
||||
@@ -1483,6 +1483,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