Merge "Add card image to the QuickAccessWalletTile." into sc-dev

This commit is contained in:
TreeHugger Robot
2021-04-06 01:52:23 +00:00
committed by Android (Google) Code Review
6 changed files with 243 additions and 2 deletions

View File

@@ -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(']');
}

View File

@@ -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">

View File

@@ -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>

View File

@@ -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) {}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}