1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.qs.tiles; 18 19 import static android.content.pm.PackageManager.FEATURE_NFC_HOST_CARD_EMULATION; 20 import static android.provider.Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT; 21 22 import static com.google.common.truth.Truth.assertThat; 23 24 import static junit.framework.TestCase.assertEquals; 25 import static junit.framework.TestCase.assertFalse; 26 import static junit.framework.TestCase.assertNotNull; 27 import static junit.framework.TestCase.assertNull; 28 import static junit.framework.TestCase.assertTrue; 29 30 import static org.mockito.ArgumentMatchers.any; 31 import static org.mockito.ArgumentMatchers.eq; 32 import static org.mockito.Mockito.doNothing; 33 import static org.mockito.Mockito.spy; 34 import static org.mockito.Mockito.times; 35 import static org.mockito.Mockito.verify; 36 import static org.mockito.Mockito.verifyZeroInteractions; 37 import static org.mockito.Mockito.when; 38 39 import android.app.PendingIntent; 40 import android.content.ComponentName; 41 import android.content.Context; 42 import android.content.Intent; 43 import android.content.pm.PackageManager; 44 import android.graphics.Bitmap; 45 import android.graphics.drawable.Drawable; 46 import android.graphics.drawable.Icon; 47 import android.os.Handler; 48 import android.os.UserHandle; 49 import android.service.quickaccesswallet.GetWalletCardsError; 50 import android.service.quickaccesswallet.GetWalletCardsResponse; 51 import android.service.quickaccesswallet.QuickAccessWalletClient; 52 import android.service.quickaccesswallet.QuickAccessWalletService; 53 import android.service.quickaccesswallet.WalletCard; 54 import android.service.quicksettings.Tile; 55 import android.testing.TestableLooper; 56 57 import androidx.test.ext.junit.runners.AndroidJUnit4; 58 import androidx.test.filters.SmallTest; 59 60 import com.android.internal.logging.MetricsLogger; 61 import com.android.systemui.SysuiTestCase; 62 import com.android.systemui.classifier.FalsingManagerFake; 63 import com.android.systemui.plugins.ActivityStarter; 64 import com.android.systemui.plugins.qs.QSTile; 65 import com.android.systemui.plugins.statusbar.StatusBarStateController; 66 import com.android.systemui.qs.QSHost; 67 import com.android.systemui.qs.QsEventLogger; 68 import com.android.systemui.qs.logging.QSLogger; 69 import com.android.systemui.qs.tileimpl.QSTileImpl; 70 import com.android.systemui.res.R; 71 import com.android.systemui.statusbar.policy.KeyguardStateController; 72 import com.android.systemui.util.settings.SecureSettings; 73 import com.android.systemui.wallet.controller.QuickAccessWalletController; 74 75 import org.junit.After; 76 import org.junit.Before; 77 import org.junit.Test; 78 import org.junit.runner.RunWith; 79 import org.mockito.ArgumentCaptor; 80 import org.mockito.Captor; 81 import org.mockito.Mock; 82 import org.mockito.MockitoAnnotations; 83 84 import java.util.Collections; 85 86 @RunWith(AndroidJUnit4.class) 87 @TestableLooper.RunWithLooper(setAsMainLooper = true) 88 @SmallTest 89 public class QuickAccessWalletTileTest extends SysuiTestCase { 90 91 private static final String CARD_ID = "card_id"; 92 private static final String LABEL = "QAW"; 93 private static final String CARD_DESCRIPTION = "•••• 1234"; 94 private static final Icon CARD_IMAGE = 95 Icon.createWithBitmap(Bitmap.createBitmap(70, 50, Bitmap.Config.ARGB_8888)); 96 private static final int PRIMARY_USER_ID = 0; 97 private static final int SECONDARY_USER_ID = 10; 98 99 private final Drawable mTileIcon = mContext.getDrawable(R.drawable.ic_qs_wallet); 100 private final Intent mWalletIntent = new Intent(QuickAccessWalletService.ACTION_VIEW_WALLET) 101 .setComponent(new ComponentName(mContext.getPackageName(), "WalletActivity")); 102 103 @Mock 104 private QSHost mHost; 105 @Mock 106 private MetricsLogger mMetricsLogger; 107 @Mock 108 private StatusBarStateController mStatusBarStateController; 109 @Mock 110 private ActivityStarter mActivityStarter; 111 @Mock 112 private QSLogger mQSLogger; 113 @Mock 114 private QsEventLogger mUiEventLogger; 115 @Mock 116 private QuickAccessWalletClient mQuickAccessWalletClient; 117 @Mock 118 private KeyguardStateController mKeyguardStateController; 119 @Mock 120 private PackageManager mPackageManager; 121 @Mock 122 private SecureSettings mSecureSettings; 123 @Mock 124 private QuickAccessWalletController mController; 125 @Mock 126 private Icon mCardImage; 127 @Captor 128 ArgumentCaptor<QuickAccessWalletClient.OnWalletCardsRetrievedCallback> mCallbackCaptor; 129 130 private Context mSpiedContext; 131 private TestableLooper mTestableLooper; 132 private QuickAccessWalletTile mTile; 133 134 @Before setUp()135 public void setUp() throws Exception { 136 MockitoAnnotations.initMocks(this); 137 138 mTestableLooper = TestableLooper.get(this); 139 mSpiedContext = spy(mContext); 140 141 doNothing().when(mSpiedContext).startActivity(any(Intent.class)); 142 when(mHost.getContext()).thenReturn(mSpiedContext); 143 when(mQuickAccessWalletClient.getServiceLabel()).thenReturn(LABEL); 144 when(mQuickAccessWalletClient.getTileIcon()).thenReturn(mTileIcon); 145 when(mQuickAccessWalletClient.isWalletFeatureAvailable()).thenReturn(true); 146 when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(true); 147 when(mQuickAccessWalletClient.isWalletFeatureAvailableWhenDeviceLocked()).thenReturn(true); 148 when(mController.getWalletClient()).thenReturn(mQuickAccessWalletClient); 149 when(mCardImage.getType()).thenReturn(Icon.TYPE_URI); 150 when(mCardImage.loadDrawableAsUser(any(), eq(SECONDARY_USER_ID))).thenReturn(null); 151 152 mTile = new QuickAccessWalletTile( 153 mHost, 154 mUiEventLogger, 155 mTestableLooper.getLooper(), 156 new Handler(mTestableLooper.getLooper()), 157 new FalsingManagerFake(), 158 mMetricsLogger, 159 mStatusBarStateController, 160 mActivityStarter, 161 mQSLogger, 162 mKeyguardStateController, 163 mPackageManager, 164 mSecureSettings, 165 mController); 166 167 mTile.initialize(); 168 mTestableLooper.processAllMessages(); 169 } 170 171 @After tearDown()172 public void tearDown() { 173 mTile.destroy(); 174 mTestableLooper.processAllMessages(); 175 } 176 177 @Test testNewTile()178 public void testNewTile() { 179 assertFalse(mTile.newTileState().handlesLongClick); 180 } 181 182 @Test testWalletServiceUnavailable_recreateWalletClient()183 public void testWalletServiceUnavailable_recreateWalletClient() { 184 when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(false); 185 186 mTile.handleSetListening(true); 187 188 verify(mController, times(1)).reCreateWalletClient(); 189 } 190 191 @Test testWalletFeatureUnavailable_recreateWalletClient()192 public void testWalletFeatureUnavailable_recreateWalletClient() { 193 when(mQuickAccessWalletClient.isWalletFeatureAvailable()).thenReturn(false); 194 195 mTile.handleSetListening(true); 196 197 verify(mController, times(1)).reCreateWalletClient(); 198 } 199 200 @Test testIsAvailable_qawFeatureAvailableWalletUnavailable()201 public void testIsAvailable_qawFeatureAvailableWalletUnavailable() { 202 when(mController.isWalletRoleAvailable()).thenReturn(false); 203 when(mPackageManager.hasSystemFeature(FEATURE_NFC_HOST_CARD_EMULATION)).thenReturn(true); 204 when(mPackageManager.hasSystemFeature("org.chromium.arc")).thenReturn(false); 205 when(mSecureSettings.getStringForUser(NFC_PAYMENT_DEFAULT_COMPONENT, 206 UserHandle.USER_CURRENT)).thenReturn("Component"); 207 208 assertTrue(mTile.isAvailable()); 209 } 210 211 @Test testIsAvailable_nfcUnavailableWalletAvailable()212 public void testIsAvailable_nfcUnavailableWalletAvailable() { 213 when(mController.isWalletRoleAvailable()).thenReturn(true); 214 when(mHost.getUserId()).thenReturn(PRIMARY_USER_ID); 215 when(mPackageManager.hasSystemFeature(FEATURE_NFC_HOST_CARD_EMULATION)).thenReturn(false); 216 when(mPackageManager.hasSystemFeature("org.chromium.arc")).thenReturn(false); 217 218 assertTrue(mTile.isAvailable()); 219 } 220 221 @Test testHandleClick_startQuickAccessUiIntent_noCard()222 public void testHandleClick_startQuickAccessUiIntent_noCard() { 223 setUpWalletCard(/* hasCard= */ false); 224 225 mTile.handleClick(/* view= */ null); 226 mTestableLooper.processAllMessages(); 227 228 verify(mController).startQuickAccessUiIntent( 229 eq(mActivityStarter), 230 eq(null), 231 /* hasCard= */ eq(false)); 232 } 233 234 @Test testHandleClick_startQuickAccessUiIntent_hasCard()235 public void testHandleClick_startQuickAccessUiIntent_hasCard() { 236 setUpWalletCard(/* hasCard= */ true); 237 238 mTile.handleClick(null /* view */); 239 mTestableLooper.processAllMessages(); 240 241 verify(mController).startQuickAccessUiIntent( 242 eq(mActivityStarter), 243 eq(null), 244 /* hasCard= */ eq(true)); 245 } 246 247 @Test testHandleUpdateState_updateLabelAndIcon()248 public void testHandleUpdateState_updateLabelAndIcon() { 249 QSTile.State state = new QSTile.State(); 250 251 mTile.handleUpdateState(state, null); 252 253 assertEquals(LABEL, state.label.toString()); 254 assertTrue(state.label.toString().contentEquals(state.contentDescription)); 255 assertEquals(mTileIcon, state.icon.getDrawable(mContext)); 256 } 257 258 @Test testHandleUpdateState_updateLabelAndIcon_noIconFromApi()259 public void testHandleUpdateState_updateLabelAndIcon_noIconFromApi() { 260 when(mQuickAccessWalletClient.getTileIcon()).thenReturn(null); 261 QSTile.State state = new QSTile.State(); 262 QSTile.Icon icon = QSTileImpl.ResourceIcon.get(R.drawable.ic_wallet_lockscreen); 263 264 mTile.handleUpdateState(state, null); 265 266 assertEquals(LABEL, state.label.toString()); 267 assertTrue(state.label.toString().contentEquals(state.contentDescription)); 268 assertEquals(icon, state.icon); 269 } 270 271 @Test testGetTileLabel_serviceLabelExists()272 public void testGetTileLabel_serviceLabelExists() { 273 assertEquals(LABEL, mTile.getTileLabel().toString()); 274 } 275 276 @Test testGetTileLabel_serviceLabelDoesNotExist()277 public void testGetTileLabel_serviceLabelDoesNotExist() { 278 when(mQuickAccessWalletClient.getServiceLabel()).thenReturn(null); 279 assertEquals(mContext.getString(R.string.wallet_title), mTile.getTileLabel().toString()); 280 } 281 282 @Test testHandleUpdateState_walletIsUpdating()283 public void testHandleUpdateState_walletIsUpdating() { 284 when(mKeyguardStateController.isUnlocked()).thenReturn(true); 285 QSTile.State state = new QSTile.State(); 286 GetWalletCardsResponse response = 287 new GetWalletCardsResponse( 288 Collections.singletonList(createWalletCard(mContext)), 0); 289 290 mTile.handleSetListening(true); 291 292 verify(mController).queryWalletCards(mCallbackCaptor.capture()); 293 294 // Wallet cards fetching on its way; wallet updating. 295 mTile.handleUpdateState(state, null); 296 297 assertEquals(Tile.STATE_INACTIVE, state.state); 298 assertEquals( 299 mContext.getString(R.string.wallet_secondary_label_updating), state.secondaryLabel); 300 assertNotNull(state.stateDescription); 301 assertNull(state.sideViewCustomDrawable); 302 303 // Wallet cards fetching completed. 304 mCallbackCaptor.getValue().onWalletCardsRetrieved(response); 305 mTestableLooper.processAllMessages(); 306 307 mTile.handleUpdateState(state, null); 308 309 assertEquals(Tile.STATE_ACTIVE, state.state); 310 assertEquals(CARD_DESCRIPTION, state.secondaryLabel); 311 assertNotNull(state.stateDescription); 312 assertNotNull(state.sideViewCustomDrawable); 313 } 314 315 @Test testHandleUpdateState_hasCard_deviceLocked_tileInactive()316 public void testHandleUpdateState_hasCard_deviceLocked_tileInactive() { 317 when(mKeyguardStateController.isUnlocked()).thenReturn(false); 318 QSTile.State state = new QSTile.State(); 319 setUpWalletCard(/* hasCard= */ true); 320 321 mTile.handleUpdateState(state, null); 322 323 assertEquals(Tile.STATE_INACTIVE, state.state); 324 assertEquals(CARD_DESCRIPTION, state.secondaryLabel); 325 assertNotNull(state.stateDescription); 326 assertNotNull(state.sideViewCustomDrawable); 327 } 328 329 @Test testHandleUpdateState_hasCard_deviceUnlocked_tileActive()330 public void testHandleUpdateState_hasCard_deviceUnlocked_tileActive() { 331 when(mKeyguardStateController.isUnlocked()).thenReturn(true); 332 QSTile.State state = new QSTile.State(); 333 setUpWalletCard(/* hasCard= */ true); 334 335 mTile.handleUpdateState(state, null); 336 337 assertEquals(Tile.STATE_ACTIVE, state.state); 338 assertEquals(CARD_DESCRIPTION, state.secondaryLabel); 339 assertNotNull(state.stateDescription); 340 assertNotNull(state.sideViewCustomDrawable); 341 } 342 343 344 @Test testHandleUpdateState_noCard_tileInactive()345 public void testHandleUpdateState_noCard_tileInactive() { 346 QSTile.State state = new QSTile.State(); 347 setUpWalletCard(/* hasCard= */ false); 348 349 mTile.handleUpdateState(state, null); 350 351 assertEquals(Tile.STATE_INACTIVE, state.state); 352 assertEquals( 353 mContext.getString(R.string.wallet_secondary_label_no_card), 354 state.secondaryLabel); 355 assertNotNull(state.stateDescription); 356 assertNull(state.sideViewCustomDrawable); 357 } 358 359 @Test testHandleUpdateState_qawServiceUnavailable_tileUnavailable()360 public void testHandleUpdateState_qawServiceUnavailable_tileUnavailable() { 361 when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(false); 362 QSTile.State state = new QSTile.State(); 363 364 mTile.handleUpdateState(state, null); 365 366 assertEquals(Tile.STATE_UNAVAILABLE, state.state); 367 assertNull(state.stateDescription); 368 assertNull(state.sideViewCustomDrawable); 369 } 370 371 @Test testHandleUpdateState_qawFeatureUnavailable_tileUnavailable()372 public void testHandleUpdateState_qawFeatureUnavailable_tileUnavailable() { 373 when(mQuickAccessWalletClient.isWalletFeatureAvailable()).thenReturn(false); 374 QSTile.State state = new QSTile.State(); 375 376 mTile.handleUpdateState(state, null); 377 378 assertEquals(Tile.STATE_UNAVAILABLE, state.state); 379 assertNull(state.stateDescription); 380 assertNull(state.sideViewCustomDrawable); 381 } 382 383 @Test testHandleSetListening_queryCards()384 public void testHandleSetListening_queryCards() { 385 mTile.handleSetListening(true); 386 387 verify(mController).queryWalletCards(mCallbackCaptor.capture()); 388 389 assertThat(mCallbackCaptor.getValue()).isInstanceOf( 390 QuickAccessWalletClient.OnWalletCardsRetrievedCallback.class); 391 } 392 393 @Test testQueryCards_hasCards_updateSideViewDrawable()394 public void testQueryCards_hasCards_updateSideViewDrawable() { 395 when(mKeyguardStateController.isUnlocked()).thenReturn(true); 396 setUpWalletCard(/* hasCard= */ true); 397 398 assertNotNull(mTile.getState().sideViewCustomDrawable); 399 } 400 401 @Test testQueryCards_notCurrentUser_hasCards_noSideViewDrawable()402 public void testQueryCards_notCurrentUser_hasCards_noSideViewDrawable() { 403 when(mKeyguardStateController.isUnlocked()).thenReturn(true); 404 405 PendingIntent pendingIntent = 406 PendingIntent.getActivity(mContext, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE); 407 WalletCard walletCard = 408 new WalletCard.Builder( 409 CARD_ID, mCardImage, CARD_DESCRIPTION, pendingIntent).build(); 410 GetWalletCardsResponse response = 411 new GetWalletCardsResponse(Collections.singletonList(walletCard), 0); 412 413 mTile.handleSetListening(true); 414 415 verify(mController).queryWalletCards(mCallbackCaptor.capture()); 416 417 mCallbackCaptor.getValue().onWalletCardsRetrieved(response); 418 mTestableLooper.processAllMessages(); 419 420 assertNull(mTile.getState().sideViewCustomDrawable); 421 } 422 423 @Test testQueryCards_cardDataPayment_updateSideViewDrawable()424 public void testQueryCards_cardDataPayment_updateSideViewDrawable() { 425 when(mKeyguardStateController.isUnlocked()).thenReturn(true); 426 setUpWalletCardWithType(/* hasCard =*/ true, WalletCard.CARD_TYPE_PAYMENT); 427 428 assertNotNull(mTile.getState().sideViewCustomDrawable); 429 } 430 431 @Test testQueryCards_cardDataNonPayment_updateSideViewDrawable()432 public void testQueryCards_cardDataNonPayment_updateSideViewDrawable() { 433 when(mKeyguardStateController.isUnlocked()).thenReturn(true); 434 setUpWalletCardWithType(/* hasCard =*/ true, WalletCard.CARD_TYPE_NON_PAYMENT); 435 436 assertNull(mTile.getState().sideViewCustomDrawable); 437 } 438 439 @Test testQueryCards_noCards_notUpdateSideViewDrawable()440 public void testQueryCards_noCards_notUpdateSideViewDrawable() { 441 setUpWalletCard(/* hasCard= */ false); 442 443 assertNull(mTile.getState().sideViewCustomDrawable); 444 } 445 446 @Test testQueryCards_error_notUpdateSideViewDrawable()447 public void testQueryCards_error_notUpdateSideViewDrawable() { 448 String errorMessage = "getWalletCardsError"; 449 GetWalletCardsError error = new GetWalletCardsError(CARD_IMAGE, errorMessage); 450 451 mTile.handleSetListening(true); 452 453 verify(mController).queryWalletCards(mCallbackCaptor.capture()); 454 455 mCallbackCaptor.getValue().onWalletCardRetrievalError(error); 456 mTestableLooper.processAllMessages(); 457 458 assertNull(mTile.getState().sideViewCustomDrawable); 459 } 460 461 @Test testHandleSetListening_notListening_notQueryCards()462 public void testHandleSetListening_notListening_notQueryCards() { 463 mTile.handleSetListening(false); 464 465 verifyZeroInteractions(mQuickAccessWalletClient); 466 } 467 createWalletCardWithType(Context context, int cardType)468 private WalletCard createWalletCardWithType(Context context, int cardType) { 469 PendingIntent pendingIntent = 470 PendingIntent.getActivity(context, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE); 471 return new WalletCard.Builder(CARD_ID, cardType, CARD_IMAGE, CARD_DESCRIPTION, 472 pendingIntent).build(); 473 } 474 setUpWalletCardWithType(boolean hasCard, int cardType)475 private void setUpWalletCardWithType(boolean hasCard, int cardType) { 476 GetWalletCardsResponse response = 477 new GetWalletCardsResponse( 478 hasCard 479 ? Collections.singletonList( 480 createWalletCardWithType(mContext, cardType)) 481 : Collections.EMPTY_LIST, 0); 482 483 mTile.handleSetListening(true); 484 485 verify(mController).queryWalletCards(mCallbackCaptor.capture()); 486 487 mCallbackCaptor.getValue().onWalletCardsRetrieved(response); 488 mTestableLooper.processAllMessages(); 489 } 490 setUpWalletCard(boolean hasCard)491 private void setUpWalletCard(boolean hasCard) { 492 GetWalletCardsResponse response = 493 new GetWalletCardsResponse( 494 hasCard 495 ? Collections.singletonList(createWalletCard(mContext)) 496 : Collections.EMPTY_LIST, 0); 497 498 mTile.handleSetListening(true); 499 500 verify(mController).queryWalletCards(mCallbackCaptor.capture()); 501 502 mCallbackCaptor.getValue().onWalletCardsRetrieved(response); 503 mTestableLooper.processAllMessages(); 504 } 505 createWalletCard(Context context)506 private WalletCard createWalletCard(Context context) { 507 PendingIntent pendingIntent = 508 PendingIntent.getActivity(context, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE); 509 return new WalletCard.Builder(CARD_ID, CARD_IMAGE, CARD_DESCRIPTION, pendingIntent).build(); 510 } 511 } 512