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 org.mockito.ArgumentMatchers.any;
20 import static org.mockito.ArgumentMatchers.anyString;
21 import static org.mockito.Mockito.never;
22 import static org.mockito.Mockito.times;
23 import static org.mockito.Mockito.verify;
24 import static org.mockito.Mockito.when;
25 
26 import android.content.ComponentName;
27 import android.content.Context;
28 import android.nfc.cardemulation.NfcFServiceInfo;
29 import android.os.ParcelFileDescriptor;
30 
31 import androidx.test.runner.AndroidJUnit4;
32 
33 import com.android.dx.mockito.inline.extended.ExtendedMockito;
34 import com.android.nfc.cardemulation.RegisteredT3tIdentifiersCache.T3tIdentifier;
35 
36 import java.io.PrintWriter;
37 import java.util.ArrayList;
38 import java.util.List;
39 import java.util.Locale;
40 
41 import org.junit.After;
42 import org.junit.Assert;
43 import org.junit.Before;
44 import org.junit.Test;
45 import org.junit.runner.RunWith;
46 import org.mockito.ArgumentCaptor;
47 import org.mockito.Captor;
48 import org.mockito.Mock;
49 import org.mockito.Mockito;
50 import org.mockito.MockitoAnnotations;
51 import org.mockito.MockitoSession;
52 import org.mockito.quality.Strictness;
53 
54 @RunWith(AndroidJUnit4.class)
55 public class RegisteredT3tIdentifiersCacheTest {
56 
57   private static final String NFCID2 = "nfcid2";
58   private static final String SYSTEM_CODE = "system code";
59   private static final String T3TPMM = "t3tpmm";
60   private static final String ANOTHER_SYSTEM_CODE = "another system code";
61   private static final int USER_ID = 1;
62   private static final String WALLET_HOLDER_PACKAGE_NAME = "com.android.test.walletroleholder";
63   private static final String ON_HOST_SERVICE_NAME
64       = "com.android.test.walletroleholder.OnHostApduService";
65   private static final String NON_PAYMENT_NFC_PACKAGE_NAME = "com.android.test.nonpaymentnfc";
66   private static final String NON_PAYMENT_SERVICE_NAME
67       = "com.android.test.nonpaymentnfc.NonPaymentApduService";
68   private static final ComponentName WALLET_HOLDER_SERVICE_COMPONENT =
69       new ComponentName(WALLET_HOLDER_PACKAGE_NAME, ON_HOST_SERVICE_NAME);
70   private static final ComponentName NON_PAYMENT_SERVICE_COMPONENT =
71       new ComponentName(NON_PAYMENT_NFC_PACKAGE_NAME, NON_PAYMENT_SERVICE_NAME);
72   @Mock
73   private Context mContext;
74   @Mock
75   private NfcFServiceInfo mNfcFServiceInfo;
76   @Mock
77   private PrintWriter mPw;
78   @Mock
79   private ParcelFileDescriptor mPfd;
80   @Mock
81   private SystemCodeRoutingManager mRoutingManager;
82   @Captor
83   ArgumentCaptor<List<T3tIdentifier>> identifiersCaptor;
84 
85   private RegisteredT3tIdentifiersCache cache;
86   private MockitoSession mStaticMockSession;
87 
88   @Before
setUp()89   public void setUp() throws Exception {
90     mStaticMockSession = ExtendedMockito.mockitoSession()
91         .mockStatic(ParcelFileDescriptor.class)
92         .strictness(Strictness.LENIENT)
93         .startMocking();
94     MockitoAnnotations.initMocks(this);
95     when(mNfcFServiceInfo.toString()).thenReturn("");
96     when(mNfcFServiceInfo.getSystemCode()).thenReturn(SYSTEM_CODE);
97     when(mNfcFServiceInfo.getNfcid2()).thenReturn(NFCID2);
98     when(mNfcFServiceInfo.getT3tPmm()).thenReturn(T3TPMM);
99     when(mNfcFServiceInfo.getComponent()).thenReturn(NON_PAYMENT_SERVICE_COMPONENT);
100     when(ParcelFileDescriptor.dup(any())).thenReturn(mPfd);
101   }
102 
103   @After
tearDown()104   public void tearDown() {
105     mStaticMockSession.finishMocking();
106   }
107 
108   @Test
testConstructor()109   public void testConstructor() {
110     cache = new RegisteredT3tIdentifiersCache(mContext);
111 
112     Assert.assertEquals(cache.mContext, mContext);
113     Assert.assertNotNull(cache.mRoutingManager);
114   }
115 
116   @Test
testT3tIdentifierEquals_ReturnsTrue()117   public void testT3tIdentifierEquals_ReturnsTrue() {
118     cache = new RegisteredT3tIdentifiersCache(mContext);
119     T3tIdentifier firstIdentifier = cache.new T3tIdentifier(SYSTEM_CODE, NFCID2, T3TPMM);
120     T3tIdentifier secondIdentifier
121         = cache.new T3tIdentifier(SYSTEM_CODE.toUpperCase(Locale.ROOT), NFCID2, "");
122 
123     boolean result = firstIdentifier.equals(secondIdentifier);
124 
125     Assert.assertTrue(result);
126   }
127 
128   @Test
testT3tIdentifierEquals_ReturnsFalse()129   public void testT3tIdentifierEquals_ReturnsFalse() {
130     cache = new RegisteredT3tIdentifiersCache(mContext);
131     T3tIdentifier firstIdentifier = cache.new T3tIdentifier(SYSTEM_CODE, NFCID2, T3TPMM);
132     T3tIdentifier secondIdentifier = cache.new T3tIdentifier(ANOTHER_SYSTEM_CODE, NFCID2, T3TPMM);
133 
134     boolean result = firstIdentifier.equals(secondIdentifier);
135 
136     Assert.assertFalse(result);
137   }
138 
139   @Test
testResolveNfcid2()140   public void testResolveNfcid2() {
141     cache = new RegisteredT3tIdentifiersCache(mContext);
142     cache.mForegroundT3tIdentifiersCache.put(NFCID2, mNfcFServiceInfo);
143 
144     NfcFServiceInfo result = cache.resolveNfcid2(NFCID2);
145 
146     Assert.assertEquals(result, mNfcFServiceInfo);
147   }
148 
149   @Test
testOnSecureNfcToggledWithNfcDisabled_ReturnsEarly()150   public void testOnSecureNfcToggledWithNfcDisabled_ReturnsEarly() {
151     cache = new RegisteredT3tIdentifiersCache(mContext, mRoutingManager);
152     cache.mNfcEnabled = false;
153 
154     cache.onSecureNfcToggled();
155 
156     verify(mRoutingManager, never()).configureRouting(any());
157   }
158 
159   @Test
testOnSecureNfcToggledWithNfcEnabled_ConfiguresRouting()160   public void testOnSecureNfcToggledWithNfcEnabled_ConfiguresRouting() {
161     cache = new RegisteredT3tIdentifiersCache(mContext, mRoutingManager);
162     cache.mNfcEnabled = true;
163     cache.mForegroundT3tIdentifiersCache.put(NFCID2, mNfcFServiceInfo);
164 
165     cache.onSecureNfcToggled();
166 
167     verify(mRoutingManager, times(2)).configureRouting(identifiersCaptor.capture());
168     List<T3tIdentifier> firstList = identifiersCaptor.getAllValues().get(0);
169     List<T3tIdentifier> secondList = identifiersCaptor.getAllValues().get(1);
170     Assert.assertEquals(1, firstList.size());
171     T3tIdentifier identifier = firstList.get(0);
172     Assert.assertEquals(SYSTEM_CODE, identifier.systemCode);
173     Assert.assertEquals(NFCID2, identifier.nfcid2);
174     Assert.assertEquals(T3TPMM, identifier.t3tPmm);
175     Assert.assertEquals(1, secondList.size());
176     Assert.assertEquals(secondList.get(0), identifier);
177   }
178 
179   @Test
testOnServicesUpdated()180   public void testOnServicesUpdated() {
181     cache = new RegisteredT3tIdentifiersCache(mContext);
182     ArrayList<NfcFServiceInfo> serviceList = getServiceList();
183 
184     cache.onServicesUpdated(USER_ID, serviceList);
185 
186     Assert.assertEquals(1, cache.mUserNfcFServiceInfo.size());
187     Assert.assertEquals(serviceList, cache.mUserNfcFServiceInfo.get(USER_ID));
188   }
189 
190   /**
191    * Tests the case where the component passed in is null and mEnabledForegroundService is also
192    * null. Ultimately, no update is performed.
193    */
194   @Test
testOnEnabledForegroundNfcFServiceChangedCase1()195   public void testOnEnabledForegroundNfcFServiceChangedCase1() {
196     cache = new RegisteredT3tIdentifiersCache(mContext, mRoutingManager);
197     cache.mEnabledForegroundService = null;
198     cache.mEnabledForegroundServiceUserId = USER_ID;
199     cache.mNfcEnabled = true;
200 
201     cache.onEnabledForegroundNfcFServiceChanged(USER_ID, /* component = */ null);
202 
203     Assert.assertEquals(USER_ID, cache.mEnabledForegroundServiceUserId);
204     Assert.assertNull(cache.mEnabledForegroundService);
205     verify(mRoutingManager, never()).configureRouting(any());
206   }
207 
208   /**
209    * Tests the case where the component passed in is null and mEnabledForegroundService is non-null.
210    * Ultimately, routing is configured.
211    */
212   @Test
testOnEnabledForegroundNfcFServiceChangedCase2()213   public void testOnEnabledForegroundNfcFServiceChangedCase2() {
214     cache = new RegisteredT3tIdentifiersCache(mContext, mRoutingManager);
215     cache.mEnabledForegroundService = WALLET_HOLDER_SERVICE_COMPONENT;
216     cache.mEnabledForegroundServiceUserId = USER_ID;
217     cache.mNfcEnabled = true;
218 
219     cache.onEnabledForegroundNfcFServiceChanged(USER_ID, /* component = */ null);
220 
221     Assert.assertNull(cache.mEnabledForegroundService);
222     Assert.assertEquals(-1, cache.mEnabledForegroundServiceUserId);
223     verify(mRoutingManager).configureRouting(identifiersCaptor.capture());
224     Assert.assertTrue(identifiersCaptor.getValue().isEmpty());
225   }
226 
227   /**
228    * Tests the case where the component passed in is non-null and mEnabledForegroundService is null.
229    * Ultimately, routing is configured.
230    */
231   @Test
testOnEnabledForegroundNfcFServiceChangedCase3()232   public void testOnEnabledForegroundNfcFServiceChangedCase3() {
233     cache = new RegisteredT3tIdentifiersCache(mContext, mRoutingManager);
234     cache.mEnabledForegroundService = null;
235     cache.mEnabledForegroundServiceUserId = -1;
236     cache.mUserNfcFServiceInfo.put(USER_ID, getServiceList());
237     cache.mNfcEnabled = true;
238 
239     cache.onEnabledForegroundNfcFServiceChanged(USER_ID, NON_PAYMENT_SERVICE_COMPONENT);
240 
241     Assert.assertEquals(NON_PAYMENT_SERVICE_COMPONENT, cache.mEnabledForegroundService);
242     Assert.assertEquals(USER_ID, cache.mEnabledForegroundServiceUserId);
243     verify(mRoutingManager).configureRouting(identifiersCaptor.capture());
244     Assert.assertEquals(1, identifiersCaptor.getValue().size());
245     T3tIdentifier identifier = identifiersCaptor.getValue().get(0);
246     Assert.assertEquals(SYSTEM_CODE, identifier.systemCode);
247     Assert.assertEquals(NFCID2, identifier.nfcid2);
248     Assert.assertEquals(T3TPMM, identifier.t3tPmm);
249   }
250 
251   /**
252    * Tests the case where the component passed in is non-null and mEnabledForegroundService is also
253    * non-null. Ultimately, no action is taken.
254    */
255   @Test
testOnEnabledForegroundNfcFServiceChangedCase4()256   public void testOnEnabledForegroundNfcFServiceChangedCase4() {
257     cache = new RegisteredT3tIdentifiersCache(mContext, mRoutingManager);
258     cache.mEnabledForegroundService = WALLET_HOLDER_SERVICE_COMPONENT;
259     cache.mEnabledForegroundServiceUserId = USER_ID;
260     cache.mNfcEnabled = true;
261 
262     cache.onEnabledForegroundNfcFServiceChanged(USER_ID, NON_PAYMENT_SERVICE_COMPONENT);
263 
264     Assert.assertEquals(WALLET_HOLDER_SERVICE_COMPONENT, cache.mEnabledForegroundService);
265     Assert.assertEquals(USER_ID, cache.mEnabledForegroundServiceUserId);
266     verify(mRoutingManager, never()).configureRouting(any());
267   }
268 
269   @Test
testOnNfcEnabled()270   public void testOnNfcEnabled() {
271     cache = new RegisteredT3tIdentifiersCache(mContext);
272 
273     cache.onNfcEnabled();
274 
275     Assert.assertTrue(cache.mNfcEnabled);
276   }
277 
278   @Test
testOnNfcDisabled()279   public void testOnNfcDisabled() {
280     cache = new RegisteredT3tIdentifiersCache(mContext, mRoutingManager);
281     cache.mNfcEnabled = true;
282     cache.mForegroundT3tIdentifiersCache.put(NFCID2, mNfcFServiceInfo);
283     cache.mEnabledForegroundService = WALLET_HOLDER_SERVICE_COMPONENT;
284     cache.mEnabledForegroundServiceUserId = USER_ID;
285 
286     cache.onNfcDisabled();
287 
288     Assert.assertFalse(cache.mNfcEnabled);
289     Assert.assertTrue(cache.mForegroundT3tIdentifiersCache.isEmpty());
290     Assert.assertNull(cache.mEnabledForegroundService);
291     Assert.assertEquals(-1, cache.mEnabledForegroundServiceUserId);
292     verify(mRoutingManager).onNfccRoutingTableCleared();
293   }
294 
295   @Test
testOnUserSwitched()296   public void testOnUserSwitched() {
297     cache = new RegisteredT3tIdentifiersCache(mContext, mRoutingManager);
298     cache.mNfcEnabled = true;
299     cache.mForegroundT3tIdentifiersCache.put(NFCID2, mNfcFServiceInfo);
300     cache.mEnabledForegroundService = WALLET_HOLDER_SERVICE_COMPONENT;
301     cache.mEnabledForegroundServiceUserId = USER_ID;
302 
303     cache.onUserSwitched();
304 
305     Assert.assertTrue(cache.mForegroundT3tIdentifiersCache.isEmpty());
306     Assert.assertNull(cache.mEnabledForegroundService);
307     Assert.assertEquals(-1, cache.mEnabledForegroundServiceUserId);
308     verify(mRoutingManager).configureRouting(identifiersCaptor.capture());
309     Assert.assertTrue(identifiersCaptor.getValue().isEmpty());
310   }
311 
312   @Test
testDump()313   public void testDump() throws Exception {
314     cache = new RegisteredT3tIdentifiersCache(mContext, mRoutingManager);
315     cache.mForegroundT3tIdentifiersCache.put(NFCID2, mNfcFServiceInfo);
316 
317     cache.dump(/* fd = */ null, mPw, /* args = */ null);
318 
319     verify(mPw, times(5)).println(anyString());
320     verify(mPfd).close();
321     verify(mNfcFServiceInfo).dump(any(), any(), any());
322     verify(mRoutingManager).dump(any(), any(), any());
323   }
324 
getServiceList()325   private ArrayList<NfcFServiceInfo> getServiceList() {
326     ArrayList<NfcFServiceInfo> list = new ArrayList<NfcFServiceInfo>();
327     list.add(mNfcFServiceInfo);
328     return list;
329   }
330 }