1 /*
2  * Copyright (C) 2022 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 package com.android.nfc.cardemulation;
17 
18 import static com.android.nfc.cardemulation.HostEmulationManager.STATE_W4_SELECT;
19 import static com.android.nfc.cardemulation.HostEmulationManager.STATE_W4_SERVICE;
20 
21 import static org.junit.Assert.assertEquals;
22 import static org.junit.Assert.assertNotNull;
23 import static org.junit.Assert.assertTrue;
24 import static org.mockito.ArgumentMatchers.anyString;
25 import static org.mockito.Mockito.mock;
26 import static org.mockito.Mockito.when;
27 
28 import android.annotation.NonNull;
29 import android.annotation.RequiresPermission;
30 import android.bluetooth.BluetoothProtoEnums;
31 import android.content.ComponentName;
32 import android.content.Context;
33 import android.content.ContextWrapper;
34 import android.content.Intent;
35 import android.content.ServiceConnection;
36 import android.content.pm.ApplicationInfo;
37 import android.content.pm.PackageManager;
38 import android.nfc.cardemulation.ApduServiceInfo;
39 import android.nfc.cardemulation.CardEmulation;
40 import android.nfc.cardemulation.PollingFrame;
41 import android.os.Binder;
42 import android.os.Handler;
43 import android.os.IBinder;
44 import android.os.Looper;
45 import android.os.UserHandle;
46 import android.os.test.TestLooper;
47 import android.platform.test.annotations.RequiresFlagsDisabled;
48 import android.platform.test.flag.junit.CheckFlagsRule;
49 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
50 import android.util.Log;
51 import android.util.Pair;
52 
53 import androidx.test.ext.junit.runners.AndroidJUnit4;
54 import androidx.test.platform.app.InstrumentationRegistry;
55 
56 import com.android.dx.mockito.inline.extended.ExtendedMockito;
57 import com.android.nfc.flags.Flags;
58 import com.android.nfc.NfcService;
59 import com.android.nfc.cardemulation.RegisteredAidCache.AidResolveInfo;
60 import com.android.nfc.NfcStatsLog;
61 
62 import java.util.ArrayList;
63 import java.util.List;
64 
65 import org.junit.After;
66 import org.junit.Assert;
67 import org.junit.Before;
68 import org.junit.Rule;
69 import org.junit.Test;
70 import org.junit.runner.RunWith;
71 import org.mockito.Mock;
72 import org.mockito.Mockito;
73 import org.mockito.MockitoSession;
74 import org.mockito.quality.Strictness;
75 
76 @RunWith(AndroidJUnit4.class)
77 public final class NfcCardEmulationOccurredTest {
78 
79     private static final String TAG = NfcCardEmulationOccurredTest.class.getSimpleName();
80     private boolean mNfcSupported;
81 
82     private MockitoSession mStaticMockSession;
83     private HostEmulationManager mHostEmulation;
84     private RegisteredAidCache mockAidCache;
85     private Context mockContext;
86     private PackageManager packageManager;
87     private final TestLooper mTestLooper = new TestLooper();
88 
89     private static final int UID_1 = 111;
90 
91     @Rule
92     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
93 
94     @Before
setUp()95     public void setUp() {
96         mStaticMockSession = ExtendedMockito.mockitoSession()
97                 .mockStatic(NfcStatsLog.class)
98                 .mockStatic(Flags.class)
99                 .mockStatic(NfcService.class)
100                 .strictness(Strictness.LENIENT)
101                 .startMocking();
102 
103         Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
104         packageManager = context.getPackageManager();
105         if (!packageManager.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
106             mNfcSupported = false;
107             return;
108         }
109         mNfcSupported = true;
110 
111         initMockContext(context);
112 
113         mockAidCache = Mockito.mock(RegisteredAidCache.class);
114         ApduServiceInfo apduServiceInfo = Mockito.mock(ApduServiceInfo.class);
115         when(apduServiceInfo.requiresUnlock()).thenReturn(false);
116         when(apduServiceInfo.requiresScreenOn()).thenReturn(false);
117         when(apduServiceInfo.isOnHost()).thenReturn(true);
118         when(apduServiceInfo.getComponent()).thenReturn(new ComponentName("packageName", "name"));
119         when(apduServiceInfo.getUid()).thenReturn(UID_1);
120         AidResolveInfo aidResolveInfo = mockAidCache.new AidResolveInfo();
121         aidResolveInfo.defaultService = apduServiceInfo;
122         aidResolveInfo.category = CardEmulation.CATEGORY_OTHER;
123         aidResolveInfo.services = new ArrayList<ApduServiceInfo>();
124         aidResolveInfo.services.add(apduServiceInfo);
125         when(mockAidCache.resolveAid(anyString())).thenReturn(aidResolveInfo);
126         when(mockAidCache.getPreferredPaymentService()).thenReturn(new Pair<>(null, null));
127         when(NfcService.getInstance()).thenReturn(mock(NfcService.class));
128         when(Flags.statsdCeEventsFlag()).thenReturn(false);
129 
130         InstrumentationRegistry.getInstrumentation().runOnMainSync(
131                 () -> mHostEmulation = new HostEmulationManager(
132                         mockContext, mTestLooper.getLooper(), mockAidCache));
133         assertNotNull(mHostEmulation);
134         mHostEmulation.onHostEmulationActivated();
135     }
136 
initMockContext(Context context)137     private void initMockContext(Context context) {
138         mockContext = new ContextWrapper(context) {
139             @Override
140             public void sendBroadcastAsUser(Intent intent, UserHandle user) {
141                 Log.i(TAG, "[Mock] sendBroadcastAsUser");
142             }
143 
144             @Override
145             public PackageManager getPackageManager() {
146                 Log.i(TAG, "[Mock] getPackageManager");
147                 return packageManager;
148             }
149 
150             public boolean bindServiceAsUser(
151                     @NonNull @RequiresPermission Intent service,
152                     @NonNull ServiceConnection conn, int flags,
153                     @NonNull UserHandle user) {
154                 Log.i(TAG, "[Mock] bindServiceAsUser");
155                 return true;
156             }
157 
158             public void unbindService(@NonNull ServiceConnection conn){
159                 Log.i(TAG, "[Mock] unbindService");
160             }
161         };
162     }
163 
164     @After
tearDown()165     public void tearDown() {
166         mHostEmulation.onHostEmulationDeactivated();
167         mStaticMockSession.finishMocking();
168     }
169 
170     @RequiresFlagsDisabled(Flags.FLAG_STATSD_CE_EVENTS_FLAG)
171     @Test
testHCEOther()172     public void testHCEOther() {
173         if (!mNfcSupported) return;
174 
175         byte[] aidBytes = new byte[] {
176                 0x00, (byte)0xA4, 0x04, 0x00,  // command
177                 0x08,  // data length
178                 (byte)0xA0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00,
179                 0x00,  // card manager AID
180                 0x00  // trailer
181         };
182         mHostEmulation.onHostEmulationData(aidBytes);
183         ExtendedMockito.verify(() -> NfcStatsLog.write(
184                 NfcStatsLog.NFC_CARDEMULATION_OCCURRED,
185                 NfcStatsLog.NFC_CARDEMULATION_OCCURRED__CATEGORY__HCE_OTHER,
186                 "HCE",
187                 UID_1));
188     }
189 
190     @Test
testOnHostEmulationActivated()191     public void testOnHostEmulationActivated() {
192         if (!mNfcSupported) return;
193 
194         mHostEmulation.onHostEmulationActivated();
195         int value = mHostEmulation.getState();
196         Assert.assertEquals(value, STATE_W4_SELECT);
197     }
198 
199     @Test
testOnPollingLoopDetected()200     public void testOnPollingLoopDetected() {
201         if (!mNfcSupported) return;
202 
203         PollingFrame pollingFrame = mock(PollingFrame.class);
204         ArrayList<PollingFrame> pollingFrames = new ArrayList<PollingFrame>();
205         pollingFrames.add(pollingFrame);
206         ComponentName componentName = mock(ComponentName.class);
207         when(componentName.getPackageName()).thenReturn("com.android.nfc");
208         when(mockAidCache.getPreferredService())
209                 .thenReturn(new Pair<>(0, componentName));
210         mHostEmulation.onPollingLoopDetected(pollingFrames);
211         PollingFrame resultPollingFrame = mHostEmulation.mPendingPollingLoopFrames.get(0);
212         Assert.assertEquals(pollingFrame, resultPollingFrame);
213     }
214 
215     @Test
testOnPollingLoopDetectedServiceBound()216     public void testOnPollingLoopDetectedServiceBound() {
217         if (!mNfcSupported) return;
218 
219         PollingFrame pollingLoopTypeOnFrame = mock(PollingFrame.class);
220         ArrayList<PollingFrame> pollingLoopTypeOnFrames = new ArrayList<PollingFrame>();
221         pollingLoopTypeOnFrames.add(pollingLoopTypeOnFrame);
222         PollingFrame pollingLoopTypeOffFrame = mock(PollingFrame.class);
223         ArrayList<PollingFrame> pollingLoopTypeOffFrames = new ArrayList<PollingFrame>();
224         pollingLoopTypeOffFrames.add(pollingLoopTypeOffFrame);
225         when(pollingLoopTypeOnFrame.getType())
226                 .thenReturn(PollingFrame.POLLING_LOOP_TYPE_ON);
227         when(pollingLoopTypeOffFrame.getType())
228                 .thenReturn(PollingFrame.POLLING_LOOP_TYPE_OFF);
229         ComponentName componentName = mock(ComponentName.class);
230         when(componentName.getPackageName()).thenReturn("com.android.nfc");
231         when(mockAidCache.getPreferredService())
232                 .thenReturn(new Pair<>(0, componentName));
233         IBinder iBinder = new Binder();
234         ServiceConnection serviceConnection = mHostEmulation.getServiceConnection();
235         serviceConnection.onServiceConnected(componentName, iBinder);
236         mHostEmulation.onPollingLoopDetected(pollingLoopTypeOnFrames);
237         mHostEmulation.onPollingLoopDetected(pollingLoopTypeOnFrames);
238         mHostEmulation.onPollingLoopDetected(pollingLoopTypeOffFrames);
239         mHostEmulation.onPollingLoopDetected(pollingLoopTypeOffFrames);
240         IBinder mActiveService = mHostEmulation.getMessenger();
241         Assert.assertNotNull(mActiveService);
242         Assert.assertEquals(iBinder, mActiveService);
243     }
244 
245     @Test
testOnPollingLoopDetectedSTATE_XFER()246     public void testOnPollingLoopDetectedSTATE_XFER() {
247         if (!mNfcSupported) return;
248 
249         ComponentName componentName = mock(ComponentName.class);
250         when(componentName.getPackageName()).thenReturn("com.android.nfc");
251         IBinder iBinder = new Binder();
252         ServiceConnection serviceConnection = mHostEmulation.getServiceConnection();
253         serviceConnection.onServiceConnected(componentName, iBinder);
254         int state = mHostEmulation.getState();
255         Log.d(TAG, "testOnPollingLoopDetectedSTATE_XFER() - state = "+state);
256 
257         byte[] aidBytes = new byte[] {
258                 0x00, (byte)0xA4, 0x04, 0x00,  // command
259                 0x08,  // data length
260                 (byte)0xA0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00,
261                 0x00,  // card manager AID
262                 0x00  // trailer
263         };
264         mHostEmulation.onHostEmulationData(aidBytes);
265         state = mHostEmulation.getState();
266         assertEquals(state, STATE_W4_SERVICE);
267     }
268 
269     @Test
testOnOffHostAidSelected()270     public void testOnOffHostAidSelected() {
271         if (!mNfcSupported) return;
272 
273         mHostEmulation.onOffHostAidSelected();
274         int state = mHostEmulation.getState();
275         assertEquals(state, STATE_W4_SELECT);
276     }
277 
278     @Test
testOnPreferredPaymentServiceChanged()279     public void testOnPreferredPaymentServiceChanged() {
280         if (!mNfcSupported) return;
281 
282         ComponentName componentName = mock(ComponentName.class);
283         when(componentName.getPackageName()).thenReturn("com.android.nfc");
284         int userId = 0;
285         mHostEmulation.onPreferredPaymentServiceChanged(userId, componentName);
286         mTestLooper.dispatchAll();
287         ComponentName serviceName = mHostEmulation.getServiceName();
288         assertNotNull(serviceName);
289         assertEquals(componentName, serviceName);
290     }
291 
292     @Test
testOnPreferredForegroundServiceChanged()293     public void testOnPreferredForegroundServiceChanged() {
294         if (!mNfcSupported) return;
295 
296         ComponentName componentName = mock(ComponentName.class);
297         when(componentName.getPackageName()).thenReturn("com.android.nfc");
298         int userId = 0;
299         mHostEmulation.onPreferredForegroundServiceChanged(userId, componentName);
300         Boolean isServiceBounded = mHostEmulation.isServiceBounded();
301         assertNotNull(isServiceBounded);
302         assertTrue(isServiceBounded);
303     }
304 }