Add card image to the QuickAccessWalletTile.
We fetch the wallet cards on handleUpdateState() of the Tile, if the card image exists, it will be stored in the QS State and then pass to the QSTileViewHorizontal so the card image will be presented on the tile. The car image won't show on the tile when the user is edittng the tiles. Test: manually tested on device locked - https://hsv.googleplex.com/5756150307880960 unlocked - https://hsv.googleplex.com/5767433589620736 Test: atest Bug: b/182963608 Change-Id: I45bab06528a7154419ae73f9e576cf385bf0f7c1
This commit is contained in:
@@ -143,6 +143,7 @@ public interface QSTile {
|
||||
public SlashState slash;
|
||||
public boolean handlesLongClick = true;
|
||||
public boolean showRippleEffect = true;
|
||||
public Drawable sideViewDrawable;
|
||||
|
||||
public boolean copyTo(State other) {
|
||||
if (other == null) throw new IllegalArgumentException();
|
||||
@@ -163,7 +164,8 @@ public interface QSTile {
|
||||
|| !Objects.equals(other.dualTarget, dualTarget)
|
||||
|| !Objects.equals(other.slash, slash)
|
||||
|| !Objects.equals(other.handlesLongClick, handlesLongClick)
|
||||
|| !Objects.equals(other.showRippleEffect, showRippleEffect);
|
||||
|| !Objects.equals(other.showRippleEffect, showRippleEffect)
|
||||
|| !Objects.equals(other.sideViewDrawable, sideViewDrawable);
|
||||
other.icon = icon;
|
||||
other.iconSupplier = iconSupplier;
|
||||
other.label = label;
|
||||
@@ -179,6 +181,7 @@ public interface QSTile {
|
||||
other.slash = slash != null ? slash.copy() : null;
|
||||
other.handlesLongClick = handlesLongClick;
|
||||
other.showRippleEffect = showRippleEffect;
|
||||
other.sideViewDrawable = sideViewDrawable;
|
||||
return changed;
|
||||
}
|
||||
|
||||
@@ -204,6 +207,7 @@ public interface QSTile {
|
||||
sb.append(",isTransient=").append(isTransient);
|
||||
sb.append(",state=").append(state);
|
||||
sb.append(",slash=\"").append(slash).append("\"");
|
||||
sb.append(",sideViewDrawable").append(sideViewDrawable);
|
||||
return sb.append(']');
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,9 @@
|
||||
-->
|
||||
<com.android.systemui.qs.tileimpl.ButtonRelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:paddingTop="12dp">
|
||||
|
||||
@@ -1437,4 +1437,6 @@
|
||||
<dimen name="min_wallet_empty_height">208dp</dimen>
|
||||
<dimen name="wallet_card_border_width">1dp</dimen>
|
||||
<dimen name="wallet_empty_state_corner_radius">24dp</dimen>
|
||||
<dimen name="wallet_tile_card_view_height">32dp</dimen>
|
||||
<dimen name="wallet_tile_card_view_width">50dp</dimen>
|
||||
</resources>
|
||||
|
||||
@@ -24,6 +24,8 @@ import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.RippleDrawable
|
||||
import android.service.quicksettings.Tile.STATE_ACTIVE
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.RelativeLayout
|
||||
import com.android.systemui.R
|
||||
@@ -41,6 +43,7 @@ open class QSTileViewHorizontal(
|
||||
private var paintColor = Color.WHITE
|
||||
private var paintAnimator: ValueAnimator? = null
|
||||
private var labelAnimator: ValueAnimator? = null
|
||||
private var mSideView: ImageView = ImageView(mContext)
|
||||
override var heightOverride: Int = HeightOverrideable.NO_OVERRIDE
|
||||
|
||||
init {
|
||||
@@ -56,6 +59,14 @@ open class QSTileViewHorizontal(
|
||||
val iconSize = context.resources.getDimensionPixelSize(R.dimen.qs_icon_size)
|
||||
addView(mIcon, 0, LayoutParams(iconSize, iconSize))
|
||||
|
||||
mSideView.visibility = View.GONE
|
||||
addView(
|
||||
mSideView,
|
||||
-1,
|
||||
LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply {
|
||||
gravity = Gravity.CENTER_VERTICAL
|
||||
})
|
||||
|
||||
mColorLabelActive = ColorStateList.valueOf(getColorForState(getContext(), STATE_ACTIVE))
|
||||
changeLabelColor(getLabelColor(mState)) // Matches the default state of the tile
|
||||
}
|
||||
@@ -128,6 +139,7 @@ open class QSTileViewHorizontal(
|
||||
}
|
||||
paintColor = newColor
|
||||
}
|
||||
loadSideViewDrawableIfNecessary(state)
|
||||
}
|
||||
|
||||
private fun animateBackground(newBackgroundColor: Int) {
|
||||
@@ -180,5 +192,21 @@ open class QSTileViewHorizontal(
|
||||
labelAnimator?.cancel()?.also { labelAnimator = null }
|
||||
}
|
||||
|
||||
private fun loadSideViewDrawableIfNecessary(state: QSTile.State) {
|
||||
if (state.sideViewDrawable != null) {
|
||||
(mSideView.layoutParams as MarginLayoutParams).apply {
|
||||
marginStart =
|
||||
context.resources.getDimensionPixelSize(R.dimen.qs_label_container_margin)
|
||||
}
|
||||
mSideView.setImageDrawable(state.sideViewDrawable)
|
||||
mSideView.visibility = View.VISIBLE
|
||||
mSideView.adjustViewBounds = true
|
||||
mSideView.scaleType = ImageView.ScaleType.FIT_CENTER
|
||||
} else {
|
||||
mSideView.setImageDrawable(null)
|
||||
mSideView.visibility = GONE
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleExpand(dualTarget: Boolean) {}
|
||||
}
|
||||
@@ -20,11 +20,20 @@ import static android.provider.Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT;
|
||||
|
||||
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;
|
||||
import android.service.quicksettings.Tile;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.logging.MetricsLogger;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.dagger.qualifiers.Background;
|
||||
@@ -40,20 +49,29 @@ import com.android.systemui.statusbar.FeatureFlags;
|
||||
import com.android.systemui.statusbar.policy.KeyguardStateController;
|
||||
import com.android.systemui.util.settings.SecureSettings;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/** Quick settings tile: Quick access wallet **/
|
||||
public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
|
||||
|
||||
private static final String TAG = "QuickAccessWalletTile";
|
||||
private static final String FEATURE_CHROME_OS = "org.chromium.arc";
|
||||
|
||||
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 FeatureFlags mFeatureFlags;
|
||||
|
||||
@VisibleForTesting Drawable mCardViewDrawable;
|
||||
|
||||
@Inject
|
||||
public QuickAccessWalletTile(
|
||||
QSHost host,
|
||||
@@ -68,6 +86,7 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
|
||||
KeyguardStateController keyguardStateController,
|
||||
PackageManager packageManager,
|
||||
SecureSettings secureSettings,
|
||||
@Background Executor executor,
|
||||
FeatureFlags featureFlags) {
|
||||
super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
|
||||
statusBarStateController, activityStarter, qsLogger);
|
||||
@@ -75,6 +94,7 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
|
||||
mKeyguardStateController = keyguardStateController;
|
||||
mPackageManager = packageManager;
|
||||
mSecureSettings = secureSettings;
|
||||
mExecutor = executor;
|
||||
mFeatureFlags = featureFlags;
|
||||
}
|
||||
|
||||
@@ -86,6 +106,14 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleSetListening(boolean listening) {
|
||||
super.handleSetListening(listening);
|
||||
if (listening) {
|
||||
queryWalletCards();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleClick() {
|
||||
mActivityStarter.postStartActivityDismissingKeyguard(
|
||||
@@ -108,6 +136,7 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
|
||||
} else {
|
||||
state.state = Tile.STATE_UNAVAILABLE;
|
||||
}
|
||||
state.sideViewDrawable = mCardViewDrawable;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -133,4 +162,40 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
|
||||
CharSequence qawLabel = mQuickAccessWalletClient.getServiceLabel();
|
||||
return qawLabel == null ? mLabel : qawLabel;
|
||||
}
|
||||
|
||||
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= */ 2);
|
||||
mQuickAccessWalletClient.getWalletCards(mExecutor, request, mCardRetriever);
|
||||
}
|
||||
|
||||
private class WalletCardRetriever implements
|
||||
QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
|
||||
|
||||
@Override
|
||||
public void onWalletCardsRetrieved(@NonNull GetWalletCardsResponse response) {
|
||||
Log.i(TAG, "Successfully retrieved wallet cards.");
|
||||
List<WalletCard> cards = response.getWalletCards();
|
||||
if (cards.isEmpty()) {
|
||||
Log.d(TAG, "No wallet cards exist.");
|
||||
mCardViewDrawable = null;
|
||||
refreshState();
|
||||
return;
|
||||
}
|
||||
mCardViewDrawable = cards.get(0).getCardImage().loadDrawable(mContext);
|
||||
refreshState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWalletCardRetrievalError(@NonNull GetWalletCardsError error) {
|
||||
Log.w(TAG, "Error retrieve wallet cards");
|
||||
mCardViewDrawable = null;
|
||||
refreshState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,21 +19,36 @@ package com.android.systemui.qs.tiles;
|
||||
import static android.content.pm.PackageManager.FEATURE_NFC_HOST_CARD_EMULATION;
|
||||
import static android.provider.Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static junit.framework.TestCase.assertEquals;
|
||||
import static junit.framework.TestCase.assertFalse;
|
||||
import static junit.framework.TestCase.assertNotNull;
|
||||
import static junit.framework.TestCase.assertNull;
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
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;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Handler;
|
||||
import android.service.quickaccesswallet.GetWalletCardsError;
|
||||
import android.service.quickaccesswallet.GetWalletCardsRequest;
|
||||
import android.service.quickaccesswallet.GetWalletCardsResponse;
|
||||
import android.service.quickaccesswallet.QuickAccessWalletClient;
|
||||
import android.service.quickaccesswallet.QuickAccessWalletService;
|
||||
import android.service.quickaccesswallet.WalletCard;
|
||||
import android.service.quicksettings.Tile;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
@@ -56,17 +71,30 @@ import com.android.systemui.statusbar.FeatureFlags;
|
||||
import com.android.systemui.statusbar.policy.KeyguardStateController;
|
||||
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;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@TestableLooper.RunWithLooper(setAsMainLooper = true)
|
||||
@SmallTest
|
||||
public class QuickAccessWalletTileTest extends SysuiTestCase {
|
||||
|
||||
private static final String CARD_ID = "card_id";
|
||||
private static final Icon CARD_IMAGE =
|
||||
Icon.createWithBitmap(Bitmap.createBitmap(70, 50, Bitmap.Config.ARGB_8888));
|
||||
|
||||
private final Intent mWalletIntent = new Intent(QuickAccessWalletService.ACTION_VIEW_WALLET)
|
||||
.setComponent(new ComponentName(mContext.getPackageName(), "WalletActivity"));
|
||||
|
||||
@Mock
|
||||
private QSTileHost mHost;
|
||||
@Mock
|
||||
@@ -88,6 +116,10 @@ public class QuickAccessWalletTileTest extends SysuiTestCase {
|
||||
private SecureSettings mSecureSettings;
|
||||
@Mock
|
||||
private FeatureFlags mFeatureFlags;
|
||||
@Captor
|
||||
ArgumentCaptor<GetWalletCardsRequest> mRequestCaptor;
|
||||
@Captor
|
||||
ArgumentCaptor<QuickAccessWalletClient.OnWalletCardsRetrievedCallback> mCallbackCaptor;
|
||||
|
||||
private TestableLooper mTestableLooper;
|
||||
private QuickAccessWalletTile mTile;
|
||||
@@ -115,6 +147,7 @@ public class QuickAccessWalletTileTest extends SysuiTestCase {
|
||||
mKeyguardStateController,
|
||||
mPackageManager,
|
||||
mSecureSettings,
|
||||
MoreExecutors.directExecutor(),
|
||||
mFeatureFlags);
|
||||
}
|
||||
|
||||
@@ -204,4 +237,112 @@ public class QuickAccessWalletTileTest extends SysuiTestCase {
|
||||
|
||||
assertEquals(Tile.STATE_UNAVAILABLE, state.state);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleSetListening_queryCards() {
|
||||
mTile.handleSetListening(true);
|
||||
|
||||
verify(mQuickAccessWalletClient)
|
||||
.getWalletCards(any(), mRequestCaptor.capture(), 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(2, request.getMaxCards());
|
||||
assertThat(mCallbackCaptor.getValue()).isInstanceOf(
|
||||
QuickAccessWalletClient.OnWalletCardsRetrievedCallback.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleSetListening_queryCards_hasCards_updateSideViewDrawable() {
|
||||
GetWalletCardsResponse response =
|
||||
new GetWalletCardsResponse(
|
||||
Collections.singletonList(createWalletCard(mContext)), 0);
|
||||
|
||||
mTile.handleSetListening(true);
|
||||
|
||||
verify(mQuickAccessWalletClient).getWalletCards(any(), any(), mCallbackCaptor.capture());
|
||||
|
||||
mCallbackCaptor.getValue().onWalletCardsRetrieved(response);
|
||||
mTestableLooper.processAllMessages();
|
||||
|
||||
assertNotNull(mTile.getState().sideViewDrawable);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testState_queryCards_hasCards_then_noCards() {
|
||||
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().sideViewDrawable);
|
||||
|
||||
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().sideViewDrawable);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleSetListening_queryCards_noCards_notUpdateSideViewDrawable() {
|
||||
QSTile.State state = new QSTile.State();
|
||||
GetWalletCardsResponse response = new GetWalletCardsResponse(Collections.EMPTY_LIST, 0);
|
||||
|
||||
mTile.handleSetListening(true);
|
||||
|
||||
verify(mQuickAccessWalletClient).getWalletCards(any(), any(), mCallbackCaptor.capture());
|
||||
|
||||
mCallbackCaptor.getValue().onWalletCardsRetrieved(response);
|
||||
mTestableLooper.processAllMessages();
|
||||
|
||||
assertNull(mTile.getState().sideViewDrawable);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleSetListening_queryCards_error_notUpdateSideViewDrawable() {
|
||||
String errorMessage = "getWalletCardsError";
|
||||
GetWalletCardsError error = new GetWalletCardsError(CARD_IMAGE, errorMessage);
|
||||
|
||||
mTile.handleSetListening(true);
|
||||
|
||||
verify(mQuickAccessWalletClient).getWalletCards(any(), any(), mCallbackCaptor.capture());
|
||||
|
||||
mCallbackCaptor.getValue().onWalletCardRetrievalError(error);
|
||||
mTestableLooper.processAllMessages();
|
||||
|
||||
assertNull(mTile.getState().sideViewDrawable);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleSetListening_notListening_notQueryCards() {
|
||||
mTile.handleSetListening(false);
|
||||
|
||||
verifyZeroInteractions(mQuickAccessWalletClient);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user