Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f5b6e033 authored by Brad Lassey's avatar Brad Lassey Committed by Android Build Coastguard Worker
Browse files

For the wallet card & icon, only allow the drawable be loaded from bitmap.

Test: manual
Bug: 366403307
Flag: EXEMPT bugfix
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:eb5a9b8a829dace87d592c0d1c0b29f2dfc49fc0)
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:fa3a2e143a7992d600e4235e0f9c5c2471742258)
Merged-In: I0033f6ba7ce7f9b9d592e7ce44e63ba804ceb348
Change-Id: I0033f6ba7ce7f9b9d592e7ce44e63ba804ceb348
parent e035a551
Loading
Loading
Loading
Loading
+545 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.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.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
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.Drawable;
import android.graphics.drawable.Icon;
import android.os.Handler;
import android.os.UserHandle;
import android.service.quickaccesswallet.GetWalletCardsError;
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.TestableLooper;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;

import com.android.internal.logging.MetricsLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.QsEventLogger;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.wallet.controller.QuickAccessWalletController;

import org.junit.After;
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(AndroidJUnit4.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
public class QuickAccessWalletTileTest extends SysuiTestCase {

    private static final String CARD_ID = "card_id";
    private static final String LABEL = "QAW";
    private static final String CARD_DESCRIPTION = "•••• 1234";
    private static final Icon CARD_IMAGE =
            Icon.createWithBitmap(Bitmap.createBitmap(70, 50, Bitmap.Config.ARGB_8888));
    private static final Icon INVALID_CARD_IMAGE =
            Icon.createWithContentUri("content://media/external/images/media");
    private static final int PRIMARY_USER_ID = 0;
    private static final int SECONDARY_USER_ID = 10;

    private final Drawable mTileIcon = mContext.getDrawable(R.drawable.ic_qs_wallet);
    private final Intent mWalletIntent = new Intent(QuickAccessWalletService.ACTION_VIEW_WALLET)
            .setComponent(new ComponentName(mContext.getPackageName(), "WalletActivity"));

    @Mock
    private QSHost mHost;
    @Mock
    private MetricsLogger mMetricsLogger;
    @Mock
    private StatusBarStateController mStatusBarStateController;
    @Mock
    private ActivityStarter mActivityStarter;
    @Mock
    private QSLogger mQSLogger;
    @Mock
    private QsEventLogger mUiEventLogger;
    @Mock
    private QuickAccessWalletClient mQuickAccessWalletClient;
    @Mock
    private KeyguardStateController mKeyguardStateController;
    @Mock
    private PackageManager mPackageManager;
    @Mock
    private SecureSettings mSecureSettings;
    @Mock
    private QuickAccessWalletController mController;
    @Mock
    private Icon mCardImage;
    @Captor
    ArgumentCaptor<QuickAccessWalletClient.OnWalletCardsRetrievedCallback> mCallbackCaptor;

    private Context mSpiedContext;
    private TestableLooper mTestableLooper;
    private QuickAccessWalletTile mTile;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);

        mTestableLooper = TestableLooper.get(this);
        mSpiedContext = spy(mContext);

        doNothing().when(mSpiedContext).startActivity(any(Intent.class));
        when(mHost.getContext()).thenReturn(mSpiedContext);
        when(mQuickAccessWalletClient.getServiceLabel()).thenReturn(LABEL);
        when(mQuickAccessWalletClient.getTileIcon()).thenReturn(mTileIcon);
        when(mQuickAccessWalletClient.isWalletFeatureAvailable()).thenReturn(true);
        when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(true);
        when(mQuickAccessWalletClient.isWalletFeatureAvailableWhenDeviceLocked()).thenReturn(true);
        when(mController.getWalletClient()).thenReturn(mQuickAccessWalletClient);
        when(mCardImage.getType()).thenReturn(Icon.TYPE_URI);
        when(mCardImage.loadDrawableAsUser(any(), eq(SECONDARY_USER_ID))).thenReturn(null);

        mTile = new QuickAccessWalletTile(
                mHost,
                mUiEventLogger,
                mTestableLooper.getLooper(),
                new Handler(mTestableLooper.getLooper()),
                new FalsingManagerFake(),
                mMetricsLogger,
                mStatusBarStateController,
                mActivityStarter,
                mQSLogger,
                mKeyguardStateController,
                mPackageManager,
                mSecureSettings,
                mController);

        mTile.initialize();
        mTestableLooper.processAllMessages();
    }

    @After
    public void tearDown() {
        mTile.destroy();
        mTestableLooper.processAllMessages();
    }

    @Test
    public void testNewTile() {
        assertFalse(mTile.newTileState().handlesLongClick);
    }

    @Test
    public void testWalletServiceUnavailable_recreateWalletClient() {
        when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(false);

        mTile.handleSetListening(true);

        verify(mController, times(1)).reCreateWalletClient();
    }

    @Test
    public void testWalletFeatureUnavailable_recreateWalletClient() {
        when(mQuickAccessWalletClient.isWalletFeatureAvailable()).thenReturn(false);

        mTile.handleSetListening(true);

        verify(mController, times(1)).reCreateWalletClient();
    }

    @Test
    public void testIsAvailable_qawFeatureAvailableWalletUnavailable() {
        when(mController.isWalletRoleAvailable()).thenReturn(false);
        when(mPackageManager.hasSystemFeature(FEATURE_NFC_HOST_CARD_EMULATION)).thenReturn(true);
        when(mPackageManager.hasSystemFeature("org.chromium.arc")).thenReturn(false);
        when(mSecureSettings.getStringForUser(NFC_PAYMENT_DEFAULT_COMPONENT,
                UserHandle.USER_CURRENT)).thenReturn("Component");

        assertTrue(mTile.isAvailable());
    }

    @Test
    public void testIsAvailable_nfcUnavailableWalletAvailable() {
        when(mController.isWalletRoleAvailable()).thenReturn(true);
        when(mHost.getUserId()).thenReturn(PRIMARY_USER_ID);
        when(mPackageManager.hasSystemFeature(FEATURE_NFC_HOST_CARD_EMULATION)).thenReturn(false);
        when(mPackageManager.hasSystemFeature("org.chromium.arc")).thenReturn(false);

        assertTrue(mTile.isAvailable());
    }

    @Test
    public void testHandleClick_startQuickAccessUiIntent_noCard() {
        setUpWalletCard(/* hasCard= */ false);

        mTile.handleClick(/* view= */ null);
        mTestableLooper.processAllMessages();

        verify(mController).startQuickAccessUiIntent(
                eq(mActivityStarter),
                eq(null),
                /* hasCard= */ eq(false));
    }

    @Test
    public void testHandleClick_startQuickAccessUiIntent_hasCard() {
        setUpWalletCard(/* hasCard= */ true);

        mTile.handleClick(null /* view */);
        mTestableLooper.processAllMessages();

        verify(mController).startQuickAccessUiIntent(
                eq(mActivityStarter),
                eq(null),
                /* hasCard= */ eq(true));
    }

    @Test
    public void testHandleUpdateState_updateLabelAndIcon() {
        QSTile.State state = new QSTile.State();

        mTile.handleUpdateState(state, null);

        assertEquals(LABEL, state.label.toString());
        assertTrue(state.label.toString().contentEquals(state.contentDescription));
        assertEquals(mTileIcon, state.icon.getDrawable(mContext));
    }

    @Test
    public void testHandleUpdateState_updateLabelAndIcon_noIconFromApi() {
        when(mQuickAccessWalletClient.getTileIcon()).thenReturn(null);
        QSTile.State state = new QSTile.State();
        QSTile.Icon icon = QSTileImpl.ResourceIcon.get(R.drawable.ic_wallet_lockscreen);

        mTile.handleUpdateState(state, null);

        assertEquals(LABEL, state.label.toString());
        assertTrue(state.label.toString().contentEquals(state.contentDescription));
        assertEquals(icon, state.icon);
    }

    @Test
    public void testGetTileLabel_serviceLabelExists() {
        assertEquals(LABEL, mTile.getTileLabel().toString());
    }

    @Test
    public void testGetTileLabel_serviceLabelDoesNotExist() {
        when(mQuickAccessWalletClient.getServiceLabel()).thenReturn(null);
        assertEquals(mContext.getString(R.string.wallet_title), mTile.getTileLabel().toString());
    }

    @Test
    public void testHandleUpdateState_walletIsUpdating() {
        when(mKeyguardStateController.isUnlocked()).thenReturn(true);
        QSTile.State state = new QSTile.State();
        GetWalletCardsResponse response =
                new GetWalletCardsResponse(
                        Collections.singletonList(createWalletCard(mContext)), 0);

        mTile.handleSetListening(true);

        verify(mController).queryWalletCards(mCallbackCaptor.capture());

        // Wallet cards fetching on its way; wallet updating.
        mTile.handleUpdateState(state, null);

        assertEquals(Tile.STATE_INACTIVE, state.state);
        assertEquals(
                mContext.getString(R.string.wallet_secondary_label_updating), state.secondaryLabel);
        assertNotNull(state.stateDescription);
        assertNull(state.sideViewCustomDrawable);

        // Wallet cards fetching completed.
        mCallbackCaptor.getValue().onWalletCardsRetrieved(response);
        mTestableLooper.processAllMessages();

        mTile.handleUpdateState(state, null);

        assertEquals(Tile.STATE_ACTIVE, state.state);
        assertEquals(CARD_DESCRIPTION, state.secondaryLabel);
        assertNotNull(state.stateDescription);
        assertNotNull(state.sideViewCustomDrawable);
    }

    @Test
    public void testHandleUpdateState_hasCard_deviceLocked_tileInactive() {
        when(mKeyguardStateController.isUnlocked()).thenReturn(false);
        QSTile.State state = new QSTile.State();
        setUpWalletCard(/* hasCard= */ true);

        mTile.handleUpdateState(state, null);

        assertEquals(Tile.STATE_INACTIVE, state.state);
        assertEquals(CARD_DESCRIPTION, state.secondaryLabel);
        assertNotNull(state.stateDescription);
        assertNotNull(state.sideViewCustomDrawable);
    }

    @Test
    public void testHandleUpdateState_hasCard_deviceUnlocked_tileActive() {
        when(mKeyguardStateController.isUnlocked()).thenReturn(true);
        QSTile.State state = new QSTile.State();
        setUpWalletCard(/* hasCard= */ true);

        mTile.handleUpdateState(state, null);

        assertEquals(Tile.STATE_ACTIVE, state.state);
        assertEquals(CARD_DESCRIPTION, state.secondaryLabel);
        assertNotNull(state.stateDescription);
        assertNotNull(state.sideViewCustomDrawable);
    }


    @Test
    public void testHandleUpdateState_noCard_tileInactive() {
        QSTile.State state = new QSTile.State();
        setUpWalletCard(/* hasCard= */ false);

        mTile.handleUpdateState(state, null);

        assertEquals(Tile.STATE_INACTIVE, state.state);
        assertEquals(
                mContext.getString(R.string.wallet_secondary_label_no_card),
                state.secondaryLabel);
        assertNotNull(state.stateDescription);
        assertNull(state.sideViewCustomDrawable);
    }

    @Test
    public void testHandleUpdateState_qawServiceUnavailable_tileUnavailable() {
        when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(false);
        QSTile.State state = new QSTile.State();

        mTile.handleUpdateState(state, null);

        assertEquals(Tile.STATE_UNAVAILABLE, state.state);
        assertNull(state.stateDescription);
        assertNull(state.sideViewCustomDrawable);
    }

    @Test
    public void testHandleUpdateState_qawFeatureUnavailable_tileUnavailable() {
        when(mQuickAccessWalletClient.isWalletFeatureAvailable()).thenReturn(false);
        QSTile.State state = new QSTile.State();

        mTile.handleUpdateState(state, null);

        assertEquals(Tile.STATE_UNAVAILABLE, state.state);
        assertNull(state.stateDescription);
        assertNull(state.sideViewCustomDrawable);
    }

    @Test
    public void testHandleSetListening_queryCards() {
        mTile.handleSetListening(true);

        verify(mController).queryWalletCards(mCallbackCaptor.capture());

        assertThat(mCallbackCaptor.getValue()).isInstanceOf(
                QuickAccessWalletClient.OnWalletCardsRetrievedCallback.class);
    }

    @Test
    public void testQueryCards_hasCards_updateSideViewDrawable() {
        when(mKeyguardStateController.isUnlocked()).thenReturn(true);
        setUpWalletCard(/* hasCard= */ true);

        assertNotNull(mTile.getState().sideViewCustomDrawable);
    }

    @Test
    public void testQueryCards_notCurrentUser_hasCards_noSideViewDrawable() {
        when(mKeyguardStateController.isUnlocked()).thenReturn(true);

        PendingIntent pendingIntent =
                PendingIntent.getActivity(mContext, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE);
        WalletCard walletCard =
                new WalletCard.Builder(
                        CARD_ID, mCardImage, CARD_DESCRIPTION, pendingIntent).build();
        GetWalletCardsResponse response =
                new GetWalletCardsResponse(Collections.singletonList(walletCard), 0);

        mTile.handleSetListening(true);

        verify(mController).queryWalletCards(mCallbackCaptor.capture());

        mCallbackCaptor.getValue().onWalletCardsRetrieved(response);
        mTestableLooper.processAllMessages();

        assertNull(mTile.getState().sideViewCustomDrawable);
    }

    @Test
    public void testQueryCards_cardDataPayment_updateSideViewDrawable() {
        when(mKeyguardStateController.isUnlocked()).thenReturn(true);
        setUpWalletCardWithType(/* hasCard =*/ true, WalletCard.CARD_TYPE_PAYMENT);

        assertNotNull(mTile.getState().sideViewCustomDrawable);
    }

    @Test
    public void testQueryCards_cardDataNonPayment_updateSideViewDrawable() {
        when(mKeyguardStateController.isUnlocked()).thenReturn(true);
        setUpWalletCardWithType(/* hasCard =*/ true, WalletCard.CARD_TYPE_NON_PAYMENT);

        assertNull(mTile.getState().sideViewCustomDrawable);
    }

    @Test
    public void testQueryCards_noCards_notUpdateSideViewDrawable() {
        setUpWalletCard(/* hasCard= */ false);

        assertNull(mTile.getState().sideViewCustomDrawable);
    }

    @Test
    public void testQueryCards_invalidDrawable_noSideViewDrawable() {
        when(mKeyguardStateController.isUnlocked()).thenReturn(true);
        setUpInvalidWalletCard(/* hasCard= */ true);

        assertNull(mTile.getState().sideViewCustomDrawable);
    }

    @Test
    public void testQueryCards_error_notUpdateSideViewDrawable() {
        String errorMessage = "getWalletCardsError";
        GetWalletCardsError error = new GetWalletCardsError(CARD_IMAGE, errorMessage);

        mTile.handleSetListening(true);

        verify(mController).queryWalletCards(mCallbackCaptor.capture());

        mCallbackCaptor.getValue().onWalletCardRetrievalError(error);
        mTestableLooper.processAllMessages();

        assertNull(mTile.getState().sideViewCustomDrawable);
    }

    @Test
    public void testHandleSetListening_notListening_notQueryCards() {
        mTile.handleSetListening(false);

        verifyNoMoreInteractions(mQuickAccessWalletClient);
    }

    private WalletCard createWalletCardWithType(Context context, int cardType) {
        PendingIntent pendingIntent =
                PendingIntent.getActivity(context, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE);
        return new WalletCard.Builder(CARD_ID, cardType, CARD_IMAGE, CARD_DESCRIPTION,
                pendingIntent).build();
    }

    private void setUpWalletCardWithType(boolean hasCard, int cardType) {
        GetWalletCardsResponse response =
                new GetWalletCardsResponse(
                        hasCard
                                ? Collections.singletonList(
                                createWalletCardWithType(mContext, cardType))
                                : Collections.EMPTY_LIST, 0);

        mTile.handleSetListening(true);

        verify(mController).queryWalletCards(mCallbackCaptor.capture());

        mCallbackCaptor.getValue().onWalletCardsRetrieved(response);
        mTestableLooper.processAllMessages();
    }

    private void setUpWalletCard(boolean hasCard) {
        GetWalletCardsResponse response =
                new GetWalletCardsResponse(
                        hasCard
                                ? Collections.singletonList(createWalletCard(mContext))
                                : Collections.EMPTY_LIST, 0);

        mTile.handleSetListening(true);

        verify(mController).queryWalletCards(mCallbackCaptor.capture());

        mCallbackCaptor.getValue().onWalletCardsRetrieved(response);
        mTestableLooper.processAllMessages();
    }

    private void setUpInvalidWalletCard(boolean hasCard) {
        GetWalletCardsResponse response =
                new GetWalletCardsResponse(
                        hasCard
                                ? Collections.singletonList(createInvalidWalletCard(mContext))
                                : Collections.EMPTY_LIST, 0);

        mTile.handleSetListening(true);

        verify(mController).queryWalletCards(mCallbackCaptor.capture());

        mCallbackCaptor.getValue().onWalletCardsRetrieved(response);
        mTestableLooper.processAllMessages();
    }

    private WalletCard createWalletCard(Context context) {
        PendingIntent pendingIntent =
                PendingIntent.getActivity(context, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE);
        return new WalletCard.Builder(CARD_ID, CARD_IMAGE, CARD_DESCRIPTION, pendingIntent).build();
    }

    private WalletCard createInvalidWalletCard(Context context) {
        PendingIntent pendingIntent =
                PendingIntent.getActivity(context, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE);
        return new WalletCard.Builder(
                CARD_ID, INVALID_CARD_IMAGE, CARD_DESCRIPTION, pendingIntent).build();
    }


}
+5 −5
Original line number Diff line number Diff line
@@ -247,15 +247,15 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
            }
            mSelectedCard = cards.get(selectedIndex);
            switch (mSelectedCard.getCardImage().getType()) {
                case TYPE_BITMAP:
                case TYPE_ADAPTIVE_BITMAP:
                    mCardViewDrawable = mSelectedCard.getCardImage().loadDrawable(mContext);
                    break;
                case TYPE_URI:
                case TYPE_URI_ADAPTIVE_BITMAP:
                    mCardViewDrawable = null;
                    break;
                case TYPE_RESOURCE:
                case TYPE_BITMAP:
                case TYPE_ADAPTIVE_BITMAP:
                case TYPE_DATA:
                    mCardViewDrawable = mSelectedCard.getCardImage().loadDrawable(mContext);
                    mCardViewDrawable = null;
                    break;
                default:
                    Log.e(TAG, "Unknown icon type: " + mSelectedCard.getCardImage().getType());
+10 −4
Original line number Diff line number Diff line
@@ -319,13 +319,19 @@ public class WalletScreenController implements
        QAWalletCardViewInfo(Context context, WalletCard walletCard) {
            mWalletCard = walletCard;
            Icon cardImageIcon = mWalletCard.getCardImage();
            if (cardImageIcon.getType() == Icon.TYPE_URI) {
                mCardDrawable = null;
            } else {
            if (cardImageIcon.getType() == Icon.TYPE_BITMAP
                    || cardImageIcon.getType() == Icon.TYPE_ADAPTIVE_BITMAP) {
                mCardDrawable = mWalletCard.getCardImage().loadDrawable(context);
            } else {
                mCardDrawable = null;
            }
            Icon icon = mWalletCard.getCardIcon();
            mIconDrawable = icon == null ? null : icon.loadDrawable(context);
            if (icon != null && (icon.getType() == Icon.TYPE_BITMAP
                    || icon.getType() == Icon.TYPE_ADAPTIVE_BITMAP)) {
                mIconDrawable = icon.loadDrawable(context);
            } else {
                mIconDrawable = null;
            }
        }

        @Override
+5 −0
Original line number Diff line number Diff line
@@ -275,6 +275,11 @@ public class WalletView extends FrameLayout implements WalletCardCarousel.OnCard
        return mCardLabel;
    }

    @VisibleForTesting
    ImageView getIcon() {
        return mIcon;
    }

    @Nullable
    private static Drawable getHeaderIcon(Context context, WalletCardViewInfo walletCard) {
        Drawable icon = walletCard.getIcon();
+48 −0

File changed.

Preview size limit exceeded, changes collapsed.