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 }