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