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 }