1 /* 2 * Copyright (C) 2018 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.internal.telephony.uicc.euicc.apdu; 18 19 import static com.android.internal.telephony.CommandException.Error.RADIO_NOT_AVAILABLE; 20 import static org.junit.Assert.assertEquals; 21 import static org.junit.Assert.assertNull; 22 import static org.junit.Assert.assertThrows; 23 import static org.junit.Assert.assertTrue; 24 import static org.mockito.ArgumentMatchers.any; 25 import static org.mockito.ArgumentMatchers.anyBoolean; 26 import static org.mockito.ArgumentMatchers.anyInt; 27 import static org.mockito.ArgumentMatchers.eq; 28 import static org.mockito.Mockito.inOrder; 29 import static org.mockito.Mockito.doThrow; 30 import static org.mockito.Mockito.mock; 31 import static org.mockito.Mockito.never; 32 import static org.mockito.Mockito.reset; 33 import static org.mockito.Mockito.times; 34 import static org.mockito.Mockito.verify; 35 36 import android.os.Handler; 37 import android.os.Looper; 38 import android.platform.test.flag.junit.SetFlagsRule; 39 import android.preference.PreferenceManager; 40 import android.telephony.IccOpenLogicalChannelResponse; 41 import android.testing.AndroidTestingRunner; 42 import android.testing.TestableLooper; 43 44 import androidx.test.InstrumentationRegistry; 45 46 import com.android.internal.telephony.CommandException; 47 import com.android.internal.telephony.CommandsInterface; 48 import com.android.internal.telephony.euicc.EuiccSession; 49 import com.android.internal.telephony.flags.Flags; 50 import com.android.internal.telephony.uicc.IccIoResult; 51 import com.android.internal.telephony.uicc.IccUtils; 52 53 import org.junit.After; 54 import org.junit.Before; 55 import org.junit.Rule; 56 import org.junit.Test; 57 import org.junit.runner.RunWith; 58 import org.mockito.InOrder; 59 import org.mockito.Mockito; 60 61 @RunWith(AndroidTestingRunner.class) 62 @TestableLooper.RunWithLooper 63 public class ApduSenderTest { 64 @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); 65 66 private static class ResponseCaptor extends ApduSenderResultCallback { 67 public byte[] response; 68 public Throwable exception; 69 public int stopApduIndex = -1; 70 71 @Override shouldContinueOnIntermediateResult(IccIoResult result)72 public boolean shouldContinueOnIntermediateResult(IccIoResult result) { 73 if (stopApduIndex < 0) { 74 return true; 75 } 76 if (stopApduIndex == 0) { 77 return false; 78 } 79 stopApduIndex--; 80 return true; 81 } 82 83 @Override onResult(byte[] bytes)84 public void onResult(byte[] bytes) { 85 response = bytes; 86 } 87 88 @Override onException(Throwable e)89 public void onException(Throwable e) { 90 exception = e; 91 } 92 } 93 94 private static final int PHONE_ID = 0; 95 private static final String SESSION_ID = "TEST"; 96 // keep in sync with ApduSender.mChannelKey 97 private static final String SHARED_PREFS_KEY_CHANNEL_ID = "esim-channel_0"; 98 // keep in sync with ApduSender.mChannelResponseKey 99 private static final String SHARED_PREFS_KEY_CHANNEL_RESPONSE = "esim-res-id_0"; 100 101 // Mocked classes 102 private CommandsInterface mMockCi; 103 104 private TestableLooper mLooper; 105 private Handler mHandler; 106 private ResponseCaptor mResponseCaptor; 107 private byte[] mSelectResponse; 108 private ApduSender mSender; 109 110 @Before setUp()111 public void setUp() { 112 mSetFlagsRule.enableFlags(Flags.FLAG_OPTIMIZATION_APDU_SENDER); 113 114 mMockCi = mock(CommandsInterface.class); 115 mLooper = TestableLooper.get(this); 116 mHandler = new Handler(mLooper.getLooper()); 117 mResponseCaptor = new ResponseCaptor(); 118 mSelectResponse = null; 119 120 mSender = new ApduSender(InstrumentationRegistry.getContext(), PHONE_ID, 121 mMockCi, ApduSender.ISD_R_AID, false /* supportExtendedApdu */); 122 } 123 124 @After tearDown()125 public void tearDown() { 126 mHandler.removeCallbacksAndMessages(null); 127 mHandler = null; 128 mLooper = null; 129 mResponseCaptor = null; 130 mSelectResponse = null; 131 mSender = null; 132 133 EuiccSession.get().endSession(SESSION_ID); 134 clearSharedPreferences(); 135 } 136 137 @Test testWrongAid_throwsIllegalArgumentException()138 public void testWrongAid_throwsIllegalArgumentException() { 139 String wrongAid = "-1"; 140 141 assertThrows(IllegalArgumentException.class, () -> { 142 new ApduSender(InstrumentationRegistry.getContext(), 0 /* phoneId= */, 143 mMockCi, wrongAid, false /* supportExtendedApdu */); 144 }); 145 } 146 147 @Test testSendEmptyCommands()148 public void testSendEmptyCommands() throws InterruptedException { 149 int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "A1A1A19000"); 150 LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null); 151 152 mSender.send((selectResponse, requestBuilder) -> mSelectResponse = selectResponse, 153 mResponseCaptor, mHandler); 154 mLooper.processAllMessages(); 155 156 assertEquals("A1A1A19000", IccUtils.bytesToHexString(mSelectResponse)); 157 assertNull(mResponseCaptor.response); 158 assertNull(mResponseCaptor.exception); 159 verify(mMockCi).iccOpenLogicalChannel(eq(ApduSender.ISD_R_AID), anyInt(), any()); 160 verify(mMockCi).iccCloseLogicalChannel(eq(channel), eq(true /*isEs10*/), any()); 161 } 162 163 @Test testOpenChannelErrorStatus()164 public void testOpenChannelErrorStatus() throws InterruptedException { 165 LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, 166 new CommandException(CommandException.Error.NO_SUCH_ELEMENT)); 167 168 mSender.send((selectResponse, requestBuilder) -> mSelectResponse = new byte[0], 169 mResponseCaptor, mHandler); 170 mLooper.processAllMessages(); 171 172 assertNull("Request provider should not be called when failed to open channel.", 173 mSelectResponse); 174 assertTrue(mResponseCaptor.exception instanceof ApduException); 175 verify(mMockCi).iccOpenLogicalChannel(eq(ApduSender.ISD_R_AID), anyInt(), any()); 176 } 177 178 @Test testSend()179 public void testSend() throws InterruptedException { 180 int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000"); 181 LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, "A1A1A19000"); 182 LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null); 183 184 mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu( 185 10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler); 186 mLooper.processAllMessages(); 187 188 assertEquals("A1A1A1", IccUtils.bytesToHexString(mResponseCaptor.response)); 189 InOrder inOrder = inOrder(mMockCi); 190 inOrder.verify(mMockCi).iccOpenLogicalChannel(eq(ApduSender.ISD_R_AID), anyInt(), any()); 191 inOrder.verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), 192 eq(1), eq(2), eq(3), eq(0), eq("a"), anyBoolean(), any()); 193 inOrder.verify(mMockCi).iccCloseLogicalChannel(eq(channel), eq(true /*isEs10*/), any()); 194 } 195 196 @Test testSendMultiApdus()197 public void testSendMultiApdus() throws InterruptedException { 198 int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000"); 199 LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, "A19000", "A29000", 200 "A39000", "A49000"); 201 LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null); 202 203 mSender.send((selectResponse, requestBuilder) -> { 204 requestBuilder.addApdu(10, 1, 2, 3, 0, "a"); 205 requestBuilder.addApdu(10, 1, 2, 3, "ab"); 206 requestBuilder.addApdu(10, 1, 2, 3); 207 requestBuilder.addStoreData("abcd"); 208 }, mResponseCaptor, mHandler); 209 mLooper.processAllMessages(); 210 211 assertEquals("A4", IccUtils.bytesToHexString(mResponseCaptor.response)); 212 InOrder inOrder = inOrder(mMockCi); 213 inOrder.verify(mMockCi).iccOpenLogicalChannel(eq(ApduSender.ISD_R_AID), anyInt(), any()); 214 inOrder.verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), 215 eq(1), eq(2), eq(3), eq(0), eq("a"), anyBoolean(), any()); 216 inOrder.verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), 217 eq(1), eq(2), eq(3), eq(1), eq("ab"), anyBoolean(), any()); 218 inOrder.verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), 219 eq(1), eq(2), eq(3), eq(0), eq(""), anyBoolean(), any()); 220 inOrder.verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(0x81), 221 eq(0xE2), eq(0x91), eq(0), eq(2), eq("abcd"), anyBoolean(), any()); 222 inOrder.verify(mMockCi).iccCloseLogicalChannel(eq(channel), eq(true /*isEs10*/), any()); 223 } 224 225 @Test testSendMultiApdusStopEarly()226 public void testSendMultiApdusStopEarly() throws InterruptedException { 227 int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000"); 228 LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, "A19000", "A29000", 229 "A39000", "A49000"); 230 LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null); 231 mResponseCaptor.stopApduIndex = 2; 232 233 mSender.send((selectResponse, requestBuilder) -> { 234 requestBuilder.addApdu(10, 1, 2, 3, 0, "a"); 235 requestBuilder.addApdu(10, 1, 2, 3, "ab"); 236 requestBuilder.addApdu(10, 1, 2, 3); 237 requestBuilder.addStoreData("abcd"); 238 }, mResponseCaptor, mHandler); 239 mLooper.processAllMessages(); 240 241 assertEquals("A3", IccUtils.bytesToHexString(mResponseCaptor.response)); 242 verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2), 243 eq(3), eq(0), eq("a"), anyBoolean(), any()); 244 verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2), 245 eq(3), eq(1), eq("ab"), anyBoolean(), any()); 246 verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2), 247 eq(3), eq(0), eq(""), anyBoolean(), any()); 248 } 249 250 @Test testSendLongResponse()251 public void testSendLongResponse() throws InterruptedException { 252 int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000"); 253 LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, "A1A1A16104", 254 "B2B2B2B26102", "C3C39000"); 255 LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null); 256 257 mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu( 258 10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler); 259 mLooper.processAllMessages(); 260 261 assertEquals("A1A1A1B2B2B2B2C3C3", IccUtils.bytesToHexString(mResponseCaptor.response)); 262 verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2), 263 eq(3), eq(0), eq("a"), anyBoolean(), any()); 264 verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel), eq(0xC0), eq(0), 265 eq(0), eq(4), eq(""), anyBoolean(), any()); 266 verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel), eq(0xC0), eq(0), 267 eq(0), eq(2), eq(""), anyBoolean(), any()); 268 } 269 270 @Test testSendStoreDataLongDataLongResponse()271 public void testSendStoreDataLongDataLongResponse() throws InterruptedException { 272 int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000"); 273 LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, "A19000", "9000", "9000", 274 "B22B6103", "B2222B9000", "C39000"); 275 LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null); 276 277 // Each segment has 0xFF (the limit of a single command) bytes. 278 String s1 = new String(new char[0xFF]).replace("\0", "AA"); 279 String s2 = new String(new char[0xFF]).replace("\0", "BB"); 280 String s3 = new String(new char[16]).replace("\0", "CC"); 281 String longData = s1 + s2 + s3; 282 mSender.send((selectResponse, requestBuilder) -> { 283 requestBuilder.addApdu(10, 1, 2, 3, 0, "a"); 284 requestBuilder.addApdu(10, 1, 2, 3, 0, "b"); 285 requestBuilder.addStoreData(longData); 286 }, mResponseCaptor, mHandler); 287 mLooper.processAllMessages(); 288 289 assertEquals("C3", IccUtils.bytesToHexString(mResponseCaptor.response)); 290 verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2), 291 eq(3), eq(0), eq("a"), anyBoolean(), any()); 292 verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2), 293 eq(3), eq(0), eq("b"), anyBoolean(), any()); 294 verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(0x81), eq(0xE2), eq(0x11), 295 eq(0), eq(0xFF), eq(s1), anyBoolean(), any()); 296 verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(0x81), eq(0xE2), eq(0x11), 297 eq(1), eq(0xFF), eq(s2), anyBoolean(), any()); 298 verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(0x81), eq(0xE2), eq(0x91), 299 eq(2), eq(16), eq(s3), anyBoolean(), any()); 300 } 301 302 @Test testSendStoreDataLongDataMod0()303 public void testSendStoreDataLongDataMod0() throws InterruptedException { 304 int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000"); 305 LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, "9000", "B2222B9000"); 306 LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null); 307 308 // Each segment has 0xFF (the limit of a single command) bytes. 309 String s1 = new String(new char[0xFF]).replace("\0", "AA"); 310 String s2 = new String(new char[0xFF]).replace("\0", "BB"); 311 String longData = s1 + s2; 312 mSender.send((selectResponse, requestBuilder) -> { 313 requestBuilder.addStoreData(longData); 314 }, mResponseCaptor, mHandler); 315 mLooper.processAllMessages(); 316 317 assertEquals("B2222B", IccUtils.bytesToHexString(mResponseCaptor.response)); 318 verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(0x81), eq(0xE2), eq(0x11), 319 eq(0), eq(0xFF), eq(s1), anyBoolean(), any()); 320 verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(0x81), eq(0xE2), eq(0x91), 321 eq(1), eq(0xFF), eq(s2), anyBoolean(), any()); 322 } 323 324 @Test testSendStoreDataLen0()325 public void testSendStoreDataLen0() throws InterruptedException { 326 int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000"); 327 LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, "B2222B9000"); 328 LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null); 329 330 mSender.send((selectResponse, requestBuilder) -> { 331 requestBuilder.addStoreData(""); 332 }, mResponseCaptor, mHandler); 333 mLooper.processAllMessages(); 334 335 assertEquals("B2222B", IccUtils.bytesToHexString(mResponseCaptor.response)); 336 verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(0x81), eq(0xE2), eq(0x91), 337 eq(0), eq(0), eq(""), anyBoolean(), any()); 338 } 339 340 @Test testSendErrorResponseInMiddle()341 public void testSendErrorResponseInMiddle() throws InterruptedException { 342 int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000"); 343 LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, "A19000", "9000", 344 "B22B6103", "6985"); 345 LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null); 346 347 // Each segment has 0xFF (the limit of a single command) bytes. 348 String s1 = new String(new char[0xFF]).replace("\0", "AA"); 349 String s2 = new String(new char[0xFF]).replace("\0", "BB"); 350 String s3 = new String(new char[16]).replace("\0", "CC"); 351 String longData = s1 + s2 + s3; 352 mSender.send((selectResponse, requestBuilder) -> { 353 requestBuilder.addApdu(10, 1, 2, 3, 0, "a"); 354 requestBuilder.addStoreData(longData); 355 }, mResponseCaptor, mHandler); 356 mLooper.processAllMessages(); 357 358 assertEquals(0x6985, ((ApduException) mResponseCaptor.exception).getApduStatus()); 359 verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2), 360 eq(3), eq(0), eq("a"), anyBoolean(), any()); 361 verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(0x81), eq(0xE2), eq(0x11), 362 eq(0), eq(0xFF), eq(s1), anyBoolean(), any()); 363 verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(0x81), eq(0xE2), eq(0x11), 364 eq(1), eq(0xFF), eq(s2), anyBoolean(), any()); 365 verify(mMockCi, never()).iccTransmitApduLogicalChannel(eq(channel), eq(0x81), eq(0xE2), 366 eq(0x91), eq(2), eq(16), eq(s3), anyBoolean(), any()); 367 } 368 369 @Test testChannelAlreadyOpened()370 public void testChannelAlreadyOpened() throws InterruptedException { 371 int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000"); 372 LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null); 373 374 ResponseCaptor outerResponseCaptor = new ResponseCaptor(); 375 mSender.send( 376 (selectResponse, requestBuilder) -> mSender.send( 377 (selectResponseOther, requestBuilderOther) -> 378 mSelectResponse = selectResponseOther, 379 mResponseCaptor, mHandler), 380 outerResponseCaptor, mHandler); 381 mLooper.processAllMessages(); 382 383 assertNull("Should not open channel when another one is already opened.", mSelectResponse); 384 assertTrue(mResponseCaptor.exception instanceof ApduException); 385 verify(mMockCi, times(1)).iccOpenLogicalChannel(eq(ApduSender.ISD_R_AID), anyInt(), any()); 386 } 387 388 @Test testConstructor_doNotCloseOpenChannelInSharedPreference()389 public void testConstructor_doNotCloseOpenChannelInSharedPreference() 390 throws InterruptedException { 391 // Open a channel and not close it, by making CI.iccTransmitApduLogicalChannel throw. 392 int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000"); 393 doThrow(new RuntimeException()).when(mMockCi).iccTransmitApduLogicalChannel( 394 eq(channel), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(), 395 anyBoolean(), any()); 396 mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu( 397 10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler); 398 mLooper.processAllMessages(); 399 // Stub close channel 400 reset(mMockCi); 401 LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null); 402 403 // Call constructor 404 mSender = new ApduSender(InstrumentationRegistry.getContext(), PHONE_ID, 405 mMockCi, ApduSender.ISD_R_AID, false /* supportExtendedApdu */); 406 mLooper.processAllMessages(); 407 408 // The constructor should have closed channel 409 verify(mMockCi, times(0)).iccCloseLogicalChannel(eq(channel), eq(true /*isEs10*/), any()); 410 assertEquals(1, getChannelIdFromSharedPreferences()); 411 } 412 413 @Test testSend_OpenChannelFailedNoSuchElement_useChannelInSharedPreference()414 public void testSend_OpenChannelFailedNoSuchElement_useChannelInSharedPreference() { 415 // Open a channel but not close, by making CI.iccTransmitApduLogicalChannel throw. 416 int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000"); 417 doThrow(new RuntimeException()).when(mMockCi).iccTransmitApduLogicalChannel( 418 eq(channel), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(), 419 anyBoolean(), any()); 420 mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu( 421 10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler); 422 mLooper.processAllMessages(); 423 reset(mMockCi); 424 // Constructor fails to close channel 425 LogicalChannelMocker.mockCloseLogicalChannel( 426 mMockCi, channel, new CommandException(RADIO_NOT_AVAILABLE)); 427 mSender = new ApduSender(InstrumentationRegistry.getContext(), PHONE_ID, 428 mMockCi, ApduSender.ISD_R_AID, false /* supportExtendedApdu */); 429 mLooper.processAllMessages(); 430 reset(mMockCi); 431 // Stub open channel failure NO_SUCH_ELEMENT 432 LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, 433 new CommandException(CommandException.Error.NO_SUCH_ELEMENT)); 434 LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, "A1A1A19000"); 435 LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null); 436 437 mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu( 438 10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler); 439 mLooper.processAllMessages(); 440 441 // open channel would fail, and send/close would succeed because of 442 // previous open response saved in sharedPref 443 InOrder inOrder = inOrder(mMockCi); 444 inOrder.verify(mMockCi).iccOpenLogicalChannel(eq(ApduSender.ISD_R_AID), anyInt(), any()); 445 inOrder.verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), 446 eq(channel | 10), eq(1), eq(2), eq(3), eq(0), eq("a"), anyBoolean(), any()); 447 inOrder.verify(mMockCi).iccCloseLogicalChannel(eq(channel), eq(true /*isEs10*/), any()); 448 inOrder.verifyNoMoreInteractions(); 449 } 450 451 @Test testSend_euiccSession_shouldNotCloseChannel()452 public void testSend_euiccSession_shouldNotCloseChannel() 453 throws InterruptedException { 454 int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000"); 455 LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, "A1A1A19000"); 456 LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null); 457 EuiccSession.get().startSession(SESSION_ID); 458 459 mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu( 460 10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler); 461 mLooper.processAllMessages(); 462 463 assertEquals("A1A1A1", IccUtils.bytesToHexString(mResponseCaptor.response)); 464 InOrder inOrder = inOrder(mMockCi); 465 inOrder.verify(mMockCi).iccOpenLogicalChannel(eq(ApduSender.ISD_R_AID), anyInt(), any()); 466 inOrder.verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), 467 eq(1), eq(2), eq(3), eq(0), eq("a"), anyBoolean(), any()); 468 // No iccCloseLogicalChannel 469 inOrder.verifyNoMoreInteractions(); 470 } 471 472 @Test testSendTwice_euiccSession_shouldOpenChannelOnceNotCloseChannel()473 public void testSendTwice_euiccSession_shouldOpenChannelOnceNotCloseChannel() 474 throws InterruptedException { 475 int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000"); 476 LogicalChannelMocker.mockSendToLogicalChannel( 477 mMockCi, channel, "A1A1A19000", "A1A1A19000"); 478 LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null); 479 EuiccSession.get().startSession(SESSION_ID); 480 481 mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu( 482 10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler); 483 mLooper.processAllMessages(); 484 mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu( 485 10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler); 486 mLooper.processAllMessages(); 487 488 assertEquals("A1A1A1", IccUtils.bytesToHexString(mResponseCaptor.response)); 489 InOrder inOrder = inOrder(mMockCi); 490 // iccOpenLogicalChannel once 491 inOrder.verify(mMockCi).iccOpenLogicalChannel(eq(ApduSender.ISD_R_AID), anyInt(), any()); 492 // iccTransmitApduLogicalChannel twice 493 inOrder.verify(mMockCi, times(2)).iccTransmitApduLogicalChannel(eq(channel), 494 eq(channel | 10), eq(1), eq(2), eq(3), eq(0), eq("a"), anyBoolean(), any()); 495 // No iccCloseLogicalChannel 496 inOrder.verifyNoMoreInteractions(); 497 } 498 499 @Test testSendTwice_thenEndSession()500 public void testSendTwice_thenEndSession() throws InterruptedException { 501 int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000"); 502 LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, 503 "A1A1A19000", "A1A1A19000"); 504 LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null); 505 EuiccSession.get().startSession(SESSION_ID); 506 507 mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu( 508 10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler); 509 mLooper.processAllMessages(); 510 mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu( 511 10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler); 512 mLooper.processAllMessages(); 513 EuiccSession.get().endSession(SESSION_ID); 514 mLooper.processAllMessages(); 515 516 assertEquals("A1A1A1", IccUtils.bytesToHexString(mResponseCaptor.response)); 517 InOrder inOrder = inOrder(mMockCi); 518 // iccOpenLogicalChannel once 519 inOrder.verify(mMockCi).iccOpenLogicalChannel(eq(ApduSender.ISD_R_AID), anyInt(), any()); 520 // iccTransmitApduLogicalChannel twice 521 inOrder.verify(mMockCi, times(2)).iccTransmitApduLogicalChannel(eq(channel), 522 eq(channel | 10), eq(1), eq(2), eq(3), eq(0), eq("a"), anyBoolean(), any()); 523 // iccCloseLogicalChannel once 524 inOrder.verify(mMockCi).iccCloseLogicalChannel(eq(channel), eq(true /*isEs10*/), any()); 525 } 526 getChannelIdFromSharedPreferences()527 private int getChannelIdFromSharedPreferences() { 528 return PreferenceManager.getDefaultSharedPreferences(InstrumentationRegistry.getContext()) 529 .getInt(SHARED_PREFS_KEY_CHANNEL_ID, -1); 530 } 531 clearSharedPreferences()532 private void clearSharedPreferences() { 533 PreferenceManager.getDefaultSharedPreferences(InstrumentationRegistry.getContext()) 534 .edit() 535 .remove(SHARED_PREFS_KEY_CHANNEL_ID) 536 .remove(SHARED_PREFS_KEY_CHANNEL_RESPONSE) 537 .apply(); 538 } 539 } 540