1 /* 2 * Copyright (C) 2024 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.nfc.cardemulation; 18 19 import static com.android.nfc.cardemulation.HostNfcFEmulationManager.STATE_IDLE; 20 import static com.android.nfc.cardemulation.HostNfcFEmulationManager.STATE_W4_SERVICE; 21 import static com.android.nfc.cardemulation.HostNfcFEmulationManager.STATE_XFER; 22 import static com.google.common.truth.Truth.assertThat; 23 import static org.mockito.ArgumentMatchers.any; 24 import static org.mockito.ArgumentMatchers.anyInt; 25 import static org.mockito.ArgumentMatchers.anyLong; 26 import static org.mockito.ArgumentMatchers.anyString; 27 import static org.mockito.Mockito.doNothing; 28 import static org.mockito.Mockito.times; 29 import static org.mockito.Mockito.verify; 30 import static org.mockito.Mockito.when; 31 32 import android.content.ComponentName; 33 import android.content.Context; 34 import android.content.Intent; 35 import android.content.ServiceConnection; 36 import android.nfc.cardemulation.HostNfcFService; 37 import android.nfc.cardemulation.NfcFServiceInfo; 38 import android.os.Bundle; 39 import android.os.IBinder; 40 import android.os.Looper; 41 import android.os.Message; 42 import android.os.Messenger; 43 import android.os.UserHandle; 44 import android.util.proto.ProtoOutputStream; 45 46 import androidx.test.runner.AndroidJUnit4; 47 48 import com.android.nfc.NfcService; 49 50 import java.io.PrintWriter; 51 52 import org.junit.Before; 53 import org.junit.Test; 54 import org.junit.runner.RunWith; 55 import org.mockito.Mock; 56 import org.mockito.Mockito; 57 import org.mockito.MockitoAnnotations; 58 import org.mockito.ArgumentCaptor; 59 import org.mockito.Captor; 60 61 @RunWith(AndroidJUnit4.class) 62 public class HostNfcFEmulationManagerTest { 63 64 private HostNfcFEmulationManager manager; 65 66 @Mock 67 private RegisteredT3tIdentifiersCache mockIdentifiersCache; 68 @Mock 69 private Messenger mockActiveService; 70 @Mock 71 private Context mockContext; 72 @Mock 73 private IBinder mockIBinder; 74 @Mock 75 private PrintWriter mockPrintWriter; 76 @Mock 77 private ProtoOutputStream mockProto; 78 @Mock 79 private NfcFServiceInfo mockResolvedService; 80 81 @Captor 82 ArgumentCaptor<Message> msgCaptor; 83 @Captor 84 ArgumentCaptor<Intent> intentCaptor; 85 @Captor 86 ArgumentCaptor<UserHandle> userHandleCaptor; 87 @Captor 88 ArgumentCaptor<ServiceConnection> serviceConnectionCaptor; 89 @Captor 90 ArgumentCaptor<Integer> flagsCaptor; 91 92 private static final ComponentName testService = new ComponentName( 93 "com.android.test.walletroleholder", 94 "com.android.test.walletroleholder.WalletRoleHolderApduService"); 95 private static final byte[] DATA = new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; 96 private static final int USER_ID = 2; 97 98 @Before setUp()99 public void setUp() { 100 MockitoAnnotations.initMocks(this); 101 when(mockIdentifiersCache.resolveNfcid2(anyString())).thenReturn(null); 102 when(mockActiveService.getBinder()).thenReturn(mockIBinder); 103 when(mockIdentifiersCache.resolveNfcid2(anyString())).thenReturn(mockResolvedService); 104 when(mockResolvedService.getUid()).thenReturn(USER_ID); 105 doNothing().when(mockPrintWriter).println(anyString()); 106 doNothing().when(mockProto).write(anyLong(), anyString()); 107 doNothing().when(mockProto).end(anyLong()); 108 } 109 110 @Test testConstructor()111 public void testConstructor() { 112 Looper.prepare(); 113 114 manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache); 115 116 assertThat(manager.mContext).isEqualTo(mockContext); 117 assertThat(manager.mLock).isNotNull(); 118 assertThat(manager.mEnabledFgServiceName).isNull(); 119 assertThat(manager.mT3tIdentifiersCache).isEqualTo(mockIdentifiersCache); 120 assertThat(manager.mState).isEqualTo(STATE_IDLE); 121 } 122 123 @Test testOnEnabledForegroundNfcFServiceChangedWithNonNullService()124 public void testOnEnabledForegroundNfcFServiceChangedWithNonNullService() throws Exception { 125 Looper.prepare(); 126 manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache); 127 128 manager.onEnabledForegroundNfcFServiceChanged(USER_ID, testService); 129 130 assertThat(manager.mEnabledFgServiceUserId).isEqualTo(USER_ID); 131 assertThat(manager.mEnabledFgServiceName).isEqualTo(testService); 132 } 133 134 @Test testOnEnabledForegroundNfcFServiceChangedWithNullService()135 public void testOnEnabledForegroundNfcFServiceChangedWithNullService() throws Exception { 136 Looper.prepare(); 137 manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache); 138 manager.mActiveService = mockActiveService; 139 manager.mServiceBound = true; 140 141 manager.onEnabledForegroundNfcFServiceChanged(USER_ID, /* service = */ null); 142 143 verify(mockActiveService).send(any(Message.class)); 144 verify(mockContext).unbindService(any(ServiceConnection.class)); 145 assertThat(manager.mEnabledFgServiceUserId).isEqualTo(USER_ID); 146 assertThat(manager.mEnabledFgServiceName).isNull(); 147 assertThat(manager.mServiceBound).isFalse(); 148 assertThat(manager.mService).isNull(); 149 assertThat(manager.mServiceName).isNull(); 150 assertThat(manager.mServiceUserId).isEqualTo(-1); 151 } 152 153 @Test testOnHostEmulationDataWithNullDataAndNoActiveService_ReturnsEarly()154 public void testOnHostEmulationDataWithNullDataAndNoActiveService_ReturnsEarly() 155 throws Exception { 156 Looper.prepare(); 157 manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache); 158 manager.mActiveServiceName = null; 159 160 manager.onHostEmulationData(/* data = */ null); 161 162 assertThat(manager.mState).isEqualTo(STATE_IDLE); 163 } 164 165 @Test testOnHostEmulationDataWithDisabledResolvedServiceFromActiveService_ReturnsEarly()166 public void testOnHostEmulationDataWithDisabledResolvedServiceFromActiveService_ReturnsEarly() 167 throws Exception { 168 Looper.prepare(); 169 manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache); 170 manager.mActiveServiceName = testService; 171 manager.mEnabledFgServiceName = null; 172 173 manager.onHostEmulationData(/* data = */ null); 174 175 assertThat(manager.mState).isEqualTo(STATE_IDLE); 176 } 177 178 @Test testOnHostEmulationDataWithEnabledResolvedServiceFromCache_BindToService()179 public void testOnHostEmulationDataWithEnabledResolvedServiceFromCache_BindToService() 180 throws Exception { 181 Looper.prepare(); 182 manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache); 183 manager.mActiveServiceName = testService; 184 manager.mEnabledFgServiceName = testService; 185 manager.mServiceBound = true; 186 manager.mServiceName = testService; 187 manager.mServiceUserId = 0; 188 manager.mService = mockActiveService; 189 190 manager.onHostEmulationData(DATA); 191 192 verify(mockActiveService).send(any(Message.class)); 193 assertThat(manager.mState).isEqualTo(STATE_XFER); 194 } 195 196 @Test testOnHostEmulationDataWithValidExistingService_BindToService()197 public void testOnHostEmulationDataWithValidExistingService_BindToService() throws Exception { 198 Looper.prepare(); 199 manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache); 200 manager.mActiveServiceName = testService; 201 manager.mEnabledFgServiceName = testService; 202 manager.mEnabledFgServiceUserId = USER_ID; 203 manager.mServiceBound = true; 204 manager.mServiceName = testService; 205 manager.mServiceUserId = USER_ID; 206 manager.mService = mockActiveService; 207 208 manager.onHostEmulationData(/* data = */ null); 209 210 verify(mockActiveService).send(any(Message.class)); 211 assertThat(manager.mState).isEqualTo(STATE_XFER); 212 } 213 214 @Test testOnHostEmulationDataWithInvalidExistingService_WaitingForNewService()215 public void testOnHostEmulationDataWithInvalidExistingService_WaitingForNewService() 216 throws Exception { 217 Looper.prepare(); 218 manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache); 219 manager.mActiveServiceName = testService; 220 manager.mEnabledFgServiceName = testService; 221 222 manager.onHostEmulationData(DATA); 223 224 assertThat(manager.mState).isEqualTo(STATE_W4_SERVICE); 225 assertThat(manager.mPendingPacket).isEqualTo(DATA); 226 } 227 228 @Test testOnHostEmulationDataWithXFERState_SendRegularPacketData()229 public void testOnHostEmulationDataWithXFERState_SendRegularPacketData() throws Exception { 230 Looper.prepare(); 231 manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache); 232 manager.mActiveServiceName = testService; 233 manager.mEnabledFgServiceName = testService; 234 manager.mState = STATE_XFER; 235 manager.mActiveService = mockActiveService; 236 237 manager.onHostEmulationData(DATA); 238 239 verify(mockActiveService).send(any(Message.class)); 240 } 241 242 @Test testOnHostEmulationDataWithW4ServiceState_DoNothing()243 public void testOnHostEmulationDataWithW4ServiceState_DoNothing() throws Exception { 244 Looper.prepare(); 245 manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache); 246 manager.mActiveServiceName = testService; 247 manager.mEnabledFgServiceName = testService; 248 manager.mState = STATE_W4_SERVICE; 249 250 manager.onHostEmulationData(DATA); 251 252 assertThat(manager.mState).isEqualTo(STATE_W4_SERVICE); 253 } 254 255 @Test testOnHostEmulationDeactivated()256 public void testOnHostEmulationDeactivated() throws Exception { 257 Looper.prepare(); 258 manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache); 259 manager.mActiveService = mockActiveService; 260 261 manager.onHostEmulationDeactivated(); 262 263 verify(mockActiveService).send(any(Message.class)); 264 assertThat(manager.mActiveService).isNull(); 265 assertThat(manager.mActiveServiceName).isNull(); 266 assertThat(manager.mState).isEqualTo(STATE_IDLE); 267 } 268 269 @Test testOnNfcDisabled()270 public void testOnNfcDisabled() throws Exception { 271 Looper.prepare(); 272 manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache); 273 manager.mActiveService = mockActiveService; 274 275 manager.onNfcDisabled(); 276 277 verify(mockActiveService).send(any(Message.class)); 278 assertThat(manager.mEnabledFgServiceName).isNull(); 279 assertThat(manager.mActiveService).isNull(); 280 assertThat(manager.mActiveServiceName).isNull(); 281 assertThat(manager.mState).isEqualTo(STATE_IDLE); 282 } 283 284 @Test testOnUserSwitched()285 public void testOnUserSwitched() throws Exception { 286 Looper.prepare(); 287 manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache); 288 manager.mActiveService = mockActiveService; 289 290 manager.onUserSwitched(); 291 292 verify(mockActiveService).send(any(Message.class)); 293 assertThat(manager.mEnabledFgServiceName).isNull(); 294 assertThat(manager.mActiveService).isNull(); 295 assertThat(manager.mActiveServiceName).isNull(); 296 assertThat(manager.mState).isEqualTo(STATE_IDLE); 297 } 298 299 @Test testSendDataToServiceLockedWithNewService()300 public void testSendDataToServiceLockedWithNewService() throws Exception { 301 Looper.prepare(); 302 manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache); 303 304 manager.sendDataToServiceLocked(mockActiveService, DATA); 305 306 verify(mockActiveService).send(msgCaptor.capture()); 307 Message msg = msgCaptor.getValue(); 308 assertThat(msg.getTarget()).isNull(); 309 assertThat(msg.what).isEqualTo(HostNfcFService.MSG_COMMAND_PACKET); 310 assertThat(msg.getData().getByteArray("data")).isEqualTo(DATA); 311 } 312 313 @Test testSendDataToServiceLockedWithExistingService()314 public void testSendDataToServiceLockedWithExistingService() throws Exception { 315 Looper.prepare(); 316 manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache); 317 manager.mActiveService = mockActiveService; 318 319 manager.sendDataToServiceLocked(mockActiveService, DATA); 320 321 verify(mockActiveService).send(msgCaptor.capture()); 322 Message msg = msgCaptor.getValue(); 323 assertThat(msg.getTarget()).isNull(); 324 assertThat(msg.what).isEqualTo(HostNfcFService.MSG_COMMAND_PACKET); 325 assertThat(msg.getData().getByteArray("data")).isEqualTo(DATA); 326 } 327 328 @Test testDeactivateToActiveServiceLockedWithNullActiveService()329 public void testDeactivateToActiveServiceLockedWithNullActiveService() throws Exception { 330 Looper.prepare(); 331 manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache); 332 333 manager.sendDeactivateToActiveServiceLocked(HostNfcFService.DEACTIVATION_LINK_LOSS); 334 335 verify(mockActiveService, times(0)).send(any(Message.class)); 336 } 337 338 @Test testDeactivateToActiveServiceLockedWithNonNullActiveService()339 public void testDeactivateToActiveServiceLockedWithNonNullActiveService() throws Exception { 340 Looper.prepare(); 341 manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache); 342 manager.mActiveService = mockActiveService; 343 344 manager.sendDeactivateToActiveServiceLocked(HostNfcFService.DEACTIVATION_LINK_LOSS); 345 346 verify(mockActiveService).send(msgCaptor.capture()); 347 Message msg = msgCaptor.getValue(); 348 assertThat(msg.getTarget()).isNull(); 349 assertThat(msg.what).isEqualTo(HostNfcFService.MSG_DEACTIVATED); 350 assertThat(msg.arg1).isEqualTo(HostNfcFService.DEACTIVATION_LINK_LOSS); 351 } 352 353 @Test testBindServiceWithNewService_SuccessfullyBindsToService()354 public void testBindServiceWithNewService_SuccessfullyBindsToService() throws Exception { 355 Looper.prepare(); 356 manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache); 357 when(mockContext.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class), anyInt(), 358 any(UserHandle.class))).thenReturn(true); 359 360 Messenger result = manager.bindServiceIfNeededLocked(USER_ID, testService); 361 362 verify(mockContext).bindServiceAsUser(intentCaptor.capture(), serviceConnectionCaptor.capture(), 363 flagsCaptor.capture(), userHandleCaptor.capture()); 364 Intent bindIntent = intentCaptor.getValue(); 365 assertThat(bindIntent.getAction()).isEqualTo(HostNfcFService.SERVICE_INTERFACE); 366 assertThat(bindIntent.getComponent()).isEqualTo(testService); 367 assertThat(serviceConnectionCaptor.getValue()).isNotNull(); 368 assertThat(flagsCaptor.getValue()).isEqualTo(Context.BIND_AUTO_CREATE); 369 assertThat(userHandleCaptor.getValue()).isEqualTo(UserHandle.of(USER_ID)); 370 assertThat(manager.mServiceUserId).isEqualTo(USER_ID); 371 assertThat(result).isNull(); 372 } 373 374 @Test testBindServiceWithNewService_FailsToBindToService()375 public void testBindServiceWithNewService_FailsToBindToService() throws Exception { 376 Looper.prepare(); 377 manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache); 378 when(mockContext.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class), anyInt(), 379 any(UserHandle.class))).thenReturn(false); 380 381 Messenger result = manager.bindServiceIfNeededLocked(USER_ID, testService); 382 383 assertThat(manager.mServiceUserId).isEqualTo(0); 384 assertThat(result).isNull(); 385 } 386 387 @Test testBindServiceWithExistingService_ServiceAlreadyBound()388 public void testBindServiceWithExistingService_ServiceAlreadyBound() throws Exception { 389 Looper.prepare(); 390 manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache); 391 manager.mService = mockActiveService; 392 manager.mServiceBound = true; 393 manager.mServiceName = testService; 394 manager.mServiceUserId = USER_ID; 395 396 Messenger result = manager.bindServiceIfNeededLocked(USER_ID, testService); 397 398 assertThat(result).isEqualTo(mockActiveService); 399 } 400 401 @Test testUnbindService()402 public void testUnbindService() throws Exception { 403 Looper.prepare(); 404 manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache); 405 manager.mServiceBound = true; 406 407 manager.unbindServiceIfNeededLocked(); 408 409 verify(mockContext).unbindService(any(ServiceConnection.class)); 410 assertThat(manager.mServiceBound).isFalse(); 411 assertThat(manager.mService).isNull(); 412 assertThat(manager.mServiceName).isNull(); 413 assertThat(manager.mServiceUserId).isEqualTo(-1); 414 } 415 416 @Test testFindNfcid2WithNullData()417 public void testFindNfcid2WithNullData() throws Exception { 418 Looper.prepare(); 419 manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache); 420 421 String result = manager.findNfcid2(/* data = */ null); 422 423 assertThat(result).isNull(); 424 } 425 426 @Test testFindNfcid2WithNonNullData()427 public void testFindNfcid2WithNonNullData() throws Exception { 428 Looper.prepare(); 429 manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache); 430 431 String result = manager.findNfcid2(DATA); 432 433 assertThat(result).isEqualTo("0203040506070809"); 434 } 435 436 @Test testServiceConnectionOnConnected()437 public void testServiceConnectionOnConnected() throws Exception { 438 Looper.prepare(); 439 manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache); 440 manager.mPendingPacket = DATA; 441 manager.bindServiceIfNeededLocked(USER_ID, testService); 442 verify(mockContext).bindServiceAsUser(any(Intent.class), serviceConnectionCaptor.capture(), 443 anyInt(), any(UserHandle.class)); 444 ServiceConnection connection = serviceConnectionCaptor.getValue(); 445 446 connection.onServiceConnected(testService, mockIBinder); 447 448 assertThat(manager.mService).isNotNull(); 449 assertThat(manager.mServiceBound).isTrue(); 450 assertThat(manager.mServiceName).isEqualTo(testService); 451 assertThat(manager.mState).isEqualTo(STATE_XFER); 452 assertThat(manager.mPendingPacket).isNull(); 453 } 454 455 @Test testServiceConnectionOnDisconnected()456 public void testServiceConnectionOnDisconnected() throws Exception { 457 Looper.prepare(); 458 manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache); 459 manager.bindServiceIfNeededLocked(USER_ID, testService); 460 verify(mockContext).bindServiceAsUser(any(Intent.class), serviceConnectionCaptor.capture(), 461 anyInt(), any(UserHandle.class)); 462 ServiceConnection connection = serviceConnectionCaptor.getValue(); 463 464 connection.onServiceDisconnected(testService); 465 466 assertThat(manager.mService).isNull(); 467 assertThat(manager.mServiceBound).isFalse(); 468 assertThat(manager.mServiceName).isNull(); 469 } 470 471 @Test testMessageHandler()472 public void testMessageHandler() throws Exception { 473 Looper.prepare(); 474 manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache); 475 manager.mActiveService = mockActiveService; 476 Message msg = new Message(); 477 msg.replyTo = mockActiveService; 478 msg.what = HostNfcFService.MSG_RESPONSE_PACKET; 479 Bundle dataBundle = new Bundle(); 480 dataBundle.putByteArray("data", new byte[]{}); 481 msg.setData(dataBundle); 482 HostNfcFEmulationManager.MessageHandler messageHandler = manager.new MessageHandler(); 483 484 messageHandler.handleMessage(msg); 485 } 486 487 @Test testBytesToString()488 public void testBytesToString() throws Exception { 489 Looper.prepare(); 490 manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache); 491 492 String result = manager.bytesToString(DATA, /* offset = */ 0, DATA.length); 493 494 assertThat(result).isEqualTo("000102030405060708090A0B"); 495 } 496 497 @Test testDumpWithBoundService()498 public void testDumpWithBoundService() throws Exception { 499 Looper.prepare(); 500 manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache); 501 manager.mServiceBound = true; 502 503 manager.dump(/* fd = */ null, mockPrintWriter, /* args = */ null); 504 505 verify(mockPrintWriter, times(2)).println(anyString()); 506 } 507 508 @Test testDumpWithoutBoundService()509 public void testDumpWithoutBoundService() throws Exception { 510 Looper.prepare(); 511 manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache); 512 513 manager.dump(/* fd = */ null, mockPrintWriter, /* args = */ null); 514 515 verify(mockPrintWriter).println(anyString()); 516 } 517 518 @Test testDumpDebugWithBoundService()519 public void testDumpDebugWithBoundService() throws Exception { 520 Looper.prepare(); 521 manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache); 522 manager.mServiceBound = true; 523 manager.mServiceName = testService; 524 525 manager.dumpDebug(mockProto); 526 527 verify(mockProto, times(2)).write(anyLong(), anyString()); 528 verify(mockProto, times(1)).end(anyLong()); 529 } 530 531 @Test testDumpDebugWithoutBoundService()532 public void testDumpDebugWithoutBoundService() throws Exception { 533 Looper.prepare(); 534 manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache); 535 536 manager.dumpDebug(mockProto); 537 538 verify(mockProto, times(0)).write(anyLong(), anyString()); 539 verify(mockProto, times(0)).end(anyLong()); 540 } 541 }