• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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  * Copyright (c) 2015-2017, The Linux Foundation.
18  */
19 
20 /*
21  * Contributed by: Giesecke & Devrient GmbH.
22  */
23 
24 package com.android.se;
25 
26 import android.content.Context;
27 import android.content.pm.PackageManager;
28 import android.hardware.secure_element.V1_0.ISecureElement;
29 import android.hardware.secure_element.V1_0.ISecureElementHalCallback;
30 import android.hardware.secure_element.V1_0.LogicalChannelResponse;
31 import android.hardware.secure_element.V1_0.SecureElementStatus;
32 import android.os.Build;
33 import android.os.Handler;
34 import android.os.HwBinder;
35 import android.os.Message;
36 import android.os.RemoteException;
37 import android.os.ServiceSpecificException;
38 import android.se.omapi.ISecureElementListener;
39 import android.se.omapi.ISecureElementReader;
40 import android.se.omapi.ISecureElementSession;
41 import android.se.omapi.SEService;
42 import android.util.Log;
43 
44 import com.android.se.SecureElementService.SecureElementSession;
45 import com.android.se.internal.ByteArrayConverter;
46 import com.android.se.security.AccessControlEnforcer;
47 import com.android.se.security.ChannelAccess;
48 
49 import java.io.IOException;
50 import java.io.PrintWriter;
51 import java.util.ArrayList;
52 import java.util.Collection;
53 import java.util.HashMap;
54 import java.util.Map;
55 import java.util.MissingResourceException;
56 import java.util.NoSuchElementException;
57 
58 /**
59  * Each Terminal represents a Secure Element.
60  * Communicates to the SE via SecureElement HAL.
61  */
62 public class Terminal {
63 
64     private final String mTag;
65     private final Map<Integer, Channel> mChannels = new HashMap<Integer, Channel>();
66     private final Object mLock = new Object();
67     private final String mName;
68     public boolean mIsConnected = false;
69     private Context mContext;
70     private boolean mDefaultApplicationSelectedOnBasicChannel = true;
71 
72     private static final boolean DEBUG = Build.IS_DEBUGGABLE;
73     private static final int GET_SERVICE_DELAY_MILLIS = 4 * 1000;
74     private static final int EVENT_GET_HAL = 1;
75 
76     private ISecureElement mSEHal;
77 
78     /** For each Terminal there will be one AccessController object. */
79     private AccessControlEnforcer mAccessControlEnforcer;
80 
81     private ISecureElementHalCallback.Stub mHalCallback = new ISecureElementHalCallback.Stub() {
82         @Override
83         public void onStateChange(boolean state) {
84             synchronized (mLock) {
85                 Log.i(mTag, "OnStateChange:" + state);
86                 mIsConnected = state;
87                 if (!state) {
88                     if (mAccessControlEnforcer != null) {
89                         mAccessControlEnforcer.reset();
90                     }
91                 } else {
92                     // If any logical channel in use is in the channel list, it should be closed
93                     // because the access control enfocer allowed to open it by checking the access
94                     // rules retrieved before. Now we are going to retrieve the rules again and
95                     // the new rules can be different from the previous ones.
96                     closeChannels();
97                     try {
98                         initializeAccessControl();
99                     } catch (Exception e) {
100                         // ignore
101                     }
102                     mDefaultApplicationSelectedOnBasicChannel = true;
103                 }
104             }
105         }
106     };
107 
108     class SecureElementDeathRecipient implements HwBinder.DeathRecipient {
109         @Override
serviceDied(long cookie)110         public void serviceDied(long cookie) {
111             Log.e(mTag, mName + " died");
112             synchronized (mLock) {
113                 mIsConnected = false;
114                 if (mAccessControlEnforcer != null) {
115                     mAccessControlEnforcer.reset();
116                 }
117             }
118             mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_GET_HAL, 0),
119                     GET_SERVICE_DELAY_MILLIS);
120         }
121     }
122 
123     private HwBinder.DeathRecipient mDeathRecipient = new SecureElementDeathRecipient();
124 
125     private Handler mHandler = new Handler() {
126         @Override
127         public void handleMessage(Message message) {
128             switch (message.what) {
129                 case EVENT_GET_HAL:
130                     try {
131                         initialize();
132                     } catch (Exception e) {
133                         Log.e(mTag, mName + " could not be initialized again");
134                         sendMessageDelayed(obtainMessage(EVENT_GET_HAL, 0),
135                                 GET_SERVICE_DELAY_MILLIS);
136                     }
137                     break;
138                 default:
139                     break;
140             }
141         }
142     };
143 
Terminal(String name, Context context)144     public Terminal(String name, Context context) {
145         mContext = context;
146         mName = name;
147         mTag = "SecureElement-Terminal-" + getName();
148     }
149 
150     /**
151      * Initializes the terminal
152      *
153      * @throws NoSuchElementException if there is no HAL implementation for the specified SE name
154      * @throws RemoteException if there is a failure communicating with the remote
155      */
initialize()156     public void initialize() throws NoSuchElementException, RemoteException {
157         synchronized (mLock) {
158             mSEHal = ISecureElement.getService(mName);
159             if (mSEHal == null) {
160                 throw new NoSuchElementException("No HAL is provided for " + mName);
161             }
162             mSEHal.init(mHalCallback);
163             mSEHal.linkToDeath(mDeathRecipient, 0);
164         }
165         Log.i(mTag, mName + " was initialized");
166     }
167 
byteArrayToArrayList(byte[] array)168     private ArrayList<Byte> byteArrayToArrayList(byte[] array) {
169         ArrayList<Byte> list = new ArrayList<Byte>();
170         if (array == null) {
171             return list;
172         }
173 
174         for (Byte b : array) {
175             list.add(b);
176         }
177         return list;
178     }
179 
arrayListToByteArray(ArrayList<Byte> list)180     private byte[] arrayListToByteArray(ArrayList<Byte> list) {
181         Byte[] byteArray = list.toArray(new Byte[list.size()]);
182         int i = 0;
183         byte[] result = new byte[list.size()];
184         for (Byte b : byteArray) {
185             result[i++] = b.byteValue();
186         }
187         return result;
188     }
189 
190     /**
191      * Closes the given channel
192      */
closeChannel(Channel channel)193     public void closeChannel(Channel channel) {
194         if (channel == null) {
195             return;
196         }
197         if (mIsConnected) {
198             try {
199                 byte status = mSEHal.closeChannel((byte) channel.getChannelNumber());
200                 /* For Basic Channels, errors are expected.
201                  * Underlying implementations use this call as an indication when there
202                  * aren't any users actively using the channel, and the chip can go
203                  * into low power state.
204                  */
205                 if (!channel.isBasicChannel() && status != SecureElementStatus.SUCCESS) {
206                     Log.e(mTag, "Error closing channel " + channel.getChannelNumber());
207                 }
208             } catch (RemoteException e) {
209                 Log.e(mTag, "Exception in closeChannel() " + e);
210             }
211         }
212         synchronized (mLock) {
213             mChannels.remove(channel.getChannelNumber(), channel);
214             if (mChannels.get(channel.getChannelNumber()) != null) {
215                 Log.e(mTag, "Removing channel failed");
216             }
217         }
218     }
219 
220     /**
221      * Cleans up all the channels in use.
222      */
closeChannels()223     public synchronized void closeChannels() {
224         Collection<Channel> col = mChannels.values();
225         Channel[] channelList = col.toArray(new Channel[col.size()]);
226         for (Channel channel : channelList) {
227             channel.close();
228         }
229     }
230 
231     /**
232      * Closes the terminal.
233      */
close()234     public void close() {
235         synchronized (mLock) {
236             if (mSEHal != null) {
237                 try {
238                     mSEHal.unlinkToDeath(mDeathRecipient);
239                 } catch (RemoteException e) {
240                     // ignore
241                 }
242             }
243         }
244     }
245 
getName()246     public String getName() {
247         return mName;
248     }
249 
250     /**
251      * Returns the ATR of the Secure Element, or null if not available.
252      */
getAtr()253     public byte[] getAtr() {
254         if (!mIsConnected) {
255             return null;
256         }
257 
258         try {
259             ArrayList<Byte> responseList = mSEHal.getAtr();
260             if (responseList.isEmpty()) {
261                 return null;
262             }
263             return arrayListToByteArray(responseList);
264         } catch (RemoteException e) {
265             Log.e(mTag, "Exception in getAtr()" + e);
266             return null;
267         }
268     }
269 
270     /**
271      * Selects the default application on the basic channel.
272      *
273      * If there is an exception selecting the default application, select
274      * is performed with the default access control aid.
275      */
selectDefaultApplication()276     public void selectDefaultApplication() {
277         try {
278             select(null);
279         } catch (NoSuchElementException e) {
280             if (getAccessControlEnforcer() != null) {
281                 try {
282                     select(mAccessControlEnforcer.getDefaultAccessControlAid());
283                 } catch (Exception ignore) {
284                 }
285             }
286         } catch (Exception ignore) {
287         }
288     }
289 
select(byte[] aid)290     private void select(byte[] aid) throws IOException {
291         int commandSize = (aid == null ? 0 : aid.length) + 5;
292         byte[] selectCommand = new byte[commandSize];
293         selectCommand[0] = 0x00;
294         selectCommand[1] = (byte) 0xA4;
295         selectCommand[2] = 0x04;
296         selectCommand[3] = 0x00;
297         if (aid != null && aid.length != 0) {
298             selectCommand[4] = (byte) aid.length;
299             System.arraycopy(aid, 0, selectCommand, 5, aid.length);
300         } else {
301             selectCommand[4] = 0x00;
302         }
303         byte[] selectResponse = transmit(selectCommand);
304         if (selectResponse.length < 2) {
305             selectResponse = null;
306             throw new NoSuchElementException("Response length is too small");
307         }
308         int sw1 = selectResponse[selectResponse.length - 2] & 0xFF;
309         int sw2 = selectResponse[selectResponse.length - 1] & 0xFF;
310         if (sw1 != 0x90 || sw2 != 0x00) {
311             selectResponse = null;
312             throw new NoSuchElementException("Status word is incorrect");
313         }
314     }
315 
316     /**
317      * Opens a Basic Channel with the given AID and P2 paramters
318      */
openBasicChannel(SecureElementSession session, byte[] aid, byte p2, ISecureElementListener listener, String packageName, int pid)319     public Channel openBasicChannel(SecureElementSession session, byte[] aid, byte p2,
320             ISecureElementListener listener, String packageName, int pid) throws IOException,
321             NoSuchElementException {
322         if (aid != null && aid.length == 0) {
323             aid = null;
324         } else if (aid != null && (aid.length < 5 || aid.length > 16)) {
325             throw new IllegalArgumentException("AID out of range");
326         } else if (!mIsConnected) {
327             throw new IOException("Secure Element is not connected");
328         }
329 
330         Log.w(mTag, "Enable access control on basic channel for " + packageName);
331         ChannelAccess channelAccess;
332         try {
333             channelAccess = setUpChannelAccess(aid, packageName, pid);
334         } catch (MissingResourceException e) {
335             return null;
336         }
337 
338         synchronized (mLock) {
339             if (mChannels.get(0) != null) {
340                 Log.e(mTag, "basic channel in use");
341                 return null;
342             }
343             if (aid == null && !mDefaultApplicationSelectedOnBasicChannel) {
344                 Log.e(mTag, "default application is not selected");
345                 return null;
346             }
347 
348             ArrayList<byte[]> responseList = new ArrayList<byte[]>();
349             byte[] status = new byte[1];
350 
351             try {
352                 mSEHal.openBasicChannel(byteArrayToArrayList(aid), p2,
353                         new ISecureElement.openBasicChannelCallback() {
354                             @Override
355                             public void onValues(ArrayList<Byte> responseObject, byte halStatus) {
356                                 status[0] = halStatus;
357                                 responseList.add(arrayListToByteArray(responseObject));
358                                 return;
359                             }
360                         });
361             } catch (RemoteException e) {
362                 throw new IOException(e.getMessage());
363             }
364 
365             byte[] selectResponse = responseList.get(0);
366             if (status[0] == SecureElementStatus.CHANNEL_NOT_AVAILABLE) {
367                 return null;
368             } else if (status[0] == SecureElementStatus.UNSUPPORTED_OPERATION) {
369                 throw new UnsupportedOperationException("OpenBasicChannel() failed");
370             } else if (status[0] == SecureElementStatus.IOERROR) {
371                 throw new IOException("OpenBasicChannel() failed");
372             } else if (status[0] == SecureElementStatus.NO_SUCH_ELEMENT_ERROR) {
373                 throw new NoSuchElementException("OpenBasicChannel() failed");
374             }
375 
376             Channel basicChannel = new Channel(session, this, 0, selectResponse, aid,
377                     listener);
378             basicChannel.setChannelAccess(channelAccess);
379 
380             if (aid != null) {
381                 mDefaultApplicationSelectedOnBasicChannel = false;
382             }
383             mChannels.put(0, basicChannel);
384             return basicChannel;
385         }
386     }
387 
388     /**
389      * Opens a logical Channel without Channel Access initialization.
390      */
openLogicalChannelWithoutChannelAccess(byte[] aid)391     public Channel openLogicalChannelWithoutChannelAccess(byte[] aid) throws IOException,
392             NoSuchElementException {
393         return openLogicalChannel(null, aid, (byte) 0x00, null, null, 0);
394     }
395 
396     /**
397      * Opens a logical Channel with AID.
398      */
openLogicalChannel(SecureElementSession session, byte[] aid, byte p2, ISecureElementListener listener, String packageName, int pid)399     public Channel openLogicalChannel(SecureElementSession session, byte[] aid, byte p2,
400             ISecureElementListener listener, String packageName, int pid) throws IOException,
401             NoSuchElementException {
402         if (aid != null && aid.length == 0) {
403             aid = null;
404         } else if (aid != null && (aid.length < 5 || aid.length > 16)) {
405             throw new IllegalArgumentException("AID out of range");
406         } else if (!mIsConnected) {
407             throw new IOException("Secure Element is not connected");
408         }
409 
410         ChannelAccess channelAccess = null;
411         if (packageName != null) {
412             Log.w(mTag, "Enable access control on logical channel for " + packageName);
413             try {
414                 channelAccess = setUpChannelAccess(aid, packageName, pid);
415             } catch (MissingResourceException e) {
416                 return null;
417             }
418         }
419 
420         synchronized (mLock) {
421             LogicalChannelResponse[] responseArray = new LogicalChannelResponse[1];
422             byte[] status = new byte[1];
423 
424             try {
425                 mSEHal.openLogicalChannel(byteArrayToArrayList(aid), p2,
426                         new ISecureElement.openLogicalChannelCallback() {
427                             @Override
428                             public void onValues(LogicalChannelResponse response, byte halStatus) {
429                                 status[0] = halStatus;
430                                 responseArray[0] = response;
431                                 return;
432                             }
433                         });
434             } catch (RemoteException e) {
435                 throw new IOException(e.getMessage());
436             }
437 
438             if (status[0] == SecureElementStatus.CHANNEL_NOT_AVAILABLE) {
439                 return null;
440             } else if (status[0] == SecureElementStatus.UNSUPPORTED_OPERATION) {
441                 throw new UnsupportedOperationException("OpenLogicalChannel() failed");
442             } else if (status[0] == SecureElementStatus.IOERROR) {
443                 throw new IOException("OpenLogicalChannel() failed");
444             } else if (status[0] == SecureElementStatus.NO_SUCH_ELEMENT_ERROR) {
445                 throw new NoSuchElementException("OpenLogicalChannel() failed");
446             }
447             if (responseArray[0].channelNumber <= 0 || status[0] != SecureElementStatus.SUCCESS) {
448                 return null;
449             }
450             int channelNumber = responseArray[0].channelNumber;
451             byte[] selectResponse = arrayListToByteArray(responseArray[0].selectResponse);
452             Channel logicalChannel = new Channel(session, this, channelNumber,
453                     selectResponse, aid, listener);
454             logicalChannel.setChannelAccess(channelAccess);
455 
456             mChannels.put(channelNumber, logicalChannel);
457             return logicalChannel;
458         }
459     }
460 
461     /**
462      * Returns true if the given AID can be selected on the Terminal
463      */
isAidSelectable(byte[] aid)464     public boolean isAidSelectable(byte[] aid) {
465         if (aid == null) {
466             throw new NullPointerException("aid must not be null");
467         } else if (!mIsConnected) {
468             Log.e(mTag, "Secure Element is not connected");
469             return false;
470         }
471 
472         synchronized (mLock) {
473             LogicalChannelResponse[] responseArray = new LogicalChannelResponse[1];
474             byte[] status = new byte[1];
475             try {
476                 mSEHal.openLogicalChannel(byteArrayToArrayList(aid), (byte) 0x00,
477                         new ISecureElement.openLogicalChannelCallback() {
478                             @Override
479                             public void onValues(LogicalChannelResponse response, byte halStatus) {
480                                 status[0] = halStatus;
481                                 responseArray[0] = response;
482                                 return;
483                             }
484                         });
485                 if (status[0] == SecureElementStatus.SUCCESS) {
486                     mSEHal.closeChannel(responseArray[0].channelNumber);
487                     return true;
488                 }
489                 return false;
490             } catch (RemoteException e) {
491                 Log.e(mTag, "Error in isAidSelectable() returning false" + e);
492                 return false;
493             }
494         }
495     }
496 
497     /**
498      * Transmits the specified command and returns the response.
499      *
500      * @param cmd the command APDU to be transmitted.
501      * @return the response received.
502      */
transmit(byte[] cmd)503     public byte[] transmit(byte[] cmd) throws IOException {
504         if (!mIsConnected) {
505             Log.e(mTag, "Secure Element is not connected");
506             throw new IOException("Secure Element is not connected");
507         }
508 
509         byte[] rsp = transmitInternal(cmd);
510         int sw1 = rsp[rsp.length - 2] & 0xFF;
511         int sw2 = rsp[rsp.length - 1] & 0xFF;
512 
513         if (sw1 == 0x6C) {
514             cmd[cmd.length - 1] = rsp[rsp.length - 1];
515             rsp = transmitInternal(cmd);
516         } else if (sw1 == 0x61) {
517             do {
518                 byte[] getResponseCmd = new byte[]{
519                         cmd[0], (byte) 0xC0, 0x00, 0x00, (byte) sw2
520                 };
521                 byte[] tmp = transmitInternal(getResponseCmd);
522                 byte[] aux = rsp;
523                 rsp = new byte[aux.length + tmp.length - 2];
524                 System.arraycopy(aux, 0, rsp, 0, aux.length - 2);
525                 System.arraycopy(tmp, 0, rsp, aux.length - 2, tmp.length);
526                 sw1 = rsp[rsp.length - 2] & 0xFF;
527                 sw2 = rsp[rsp.length - 1] & 0xFF;
528             } while (sw1 == 0x61);
529         }
530         return rsp;
531     }
532 
transmitInternal(byte[] cmd)533     private byte[] transmitInternal(byte[] cmd) throws IOException {
534         ArrayList<Byte> response;
535         try {
536             response = mSEHal.transmit(byteArrayToArrayList(cmd));
537         } catch (RemoteException e) {
538             throw new IOException(e.getMessage());
539         }
540         if (response.isEmpty()) {
541             throw new IOException("Error in transmit()");
542         }
543         byte[] rsp = arrayListToByteArray(response);
544         if (DEBUG) {
545             Log.i(mTag, "Sent : " + ByteArrayConverter.byteArrayToHexString(cmd));
546             Log.i(mTag, "Received : " + ByteArrayConverter.byteArrayToHexString(rsp));
547         }
548         return rsp;
549     }
550 
551     /**
552      * Checks if the application is authorized to receive the transaction event.
553      */
isNfcEventAllowed(PackageManager packageManager, byte[] aid, String[] packageNames)554     public boolean[] isNfcEventAllowed(PackageManager packageManager, byte[] aid,
555             String[] packageNames) {
556         boolean checkRefreshTag = true;
557         if (mAccessControlEnforcer == null) {
558             try {
559                 initializeAccessControl();
560                 // Just finished to initialize the access control enforcer.
561                 // It is too much to check the refresh tag in this case.
562                 checkRefreshTag = false;
563             } catch (Exception e) {
564                 Log.i(mTag, "isNfcEventAllowed Exception: " + e.getMessage());
565                 return null;
566             }
567         }
568         mAccessControlEnforcer.setPackageManager(packageManager);
569 
570         synchronized (mLock) {
571             try {
572                 return mAccessControlEnforcer.isNfcEventAllowed(aid, packageNames,
573                         checkRefreshTag);
574             } catch (Exception e) {
575                 Log.i(mTag, "isNfcEventAllowed Exception: " + e.getMessage());
576                 return null;
577             }
578         }
579     }
580 
581     /**
582      * Returns true if the Secure Element is present
583      */
isSecureElementPresent()584     public boolean isSecureElementPresent() {
585         try {
586             return mSEHal.isCardPresent();
587         } catch (RemoteException e) {
588             Log.e(mTag, "Error in isSecureElementPresent() " + e);
589             return false;
590         }
591     }
592 
593     /**
594      * Initialize the Access Control and set up the channel access.
595      */
setUpChannelAccess(byte[] aid, String packageName, int pid)596     private ChannelAccess setUpChannelAccess(byte[] aid, String packageName, int pid)
597             throws IOException, MissingResourceException {
598         boolean checkRefreshTag = true;
599         if (mAccessControlEnforcer == null) {
600             initializeAccessControl();
601             // Just finished to initialize the access control enforcer.
602             // It is too much to check the refresh tag in this case.
603             checkRefreshTag = false;
604         }
605         mAccessControlEnforcer.setPackageManager(mContext.getPackageManager());
606 
607         synchronized (mLock) {
608             try {
609                 ChannelAccess channelAccess =
610                         mAccessControlEnforcer.setUpChannelAccess(aid, packageName,
611                                 checkRefreshTag);
612                 channelAccess.setCallingPid(pid);
613                 return channelAccess;
614             } catch (IOException | MissingResourceException e) {
615                 throw e;
616             } catch (Exception e) {
617                 throw new SecurityException("Exception in setUpChannelAccess()" + e);
618             }
619         }
620     }
621 
622     /**
623      * Initializes the Access Control for this Terminal
624      */
initializeAccessControl()625     private synchronized void initializeAccessControl() throws IOException,
626             MissingResourceException {
627         synchronized (mLock) {
628             if (mAccessControlEnforcer == null) {
629                 mAccessControlEnforcer = new AccessControlEnforcer(this);
630             }
631             try {
632                 mAccessControlEnforcer.initialize();
633             } catch (IOException | MissingResourceException e) {
634                 // Retrieving access rules failed because of an IO error happened between
635                 // the terminal and the secure element or the lack of a logical channel available.
636                 // It might be a temporary failure, so the terminal shall attempt to cache
637                 // the access rules again later.
638                 mAccessControlEnforcer = null;
639                 throw e;
640             }
641         }
642     }
643 
getAccessControlEnforcer()644     public AccessControlEnforcer getAccessControlEnforcer() {
645         return mAccessControlEnforcer;
646     }
647 
648     /** Dump data for debug purpose . */
dump(PrintWriter writer)649     public void dump(PrintWriter writer) {
650         writer.println("SECURE ELEMENT SERVICE TERMINAL: " + mName);
651         writer.println();
652 
653         writer.println("mIsConnected:" + mIsConnected);
654         writer.println();
655 
656         /* Dump the list of currunlty openned channels */
657         writer.println("List of open channels:");
658 
659         for (Channel channel : mChannels.values()) {
660             writer.println("channel " + channel.getChannelNumber() + ": ");
661             writer.println("package: " + channel.getChannelAccess().getPackageName());
662             writer.println("pid: " + channel.getChannelAccess().getCallingPid());
663             writer.println("aid selected: " + channel.hasSelectedAid());
664             writer.println("basic channel: " + channel.isBasicChannel());
665             writer.println();
666         }
667         writer.println();
668 
669         /* Dump ACE data */
670         if (mAccessControlEnforcer != null) {
671             mAccessControlEnforcer.dump(writer);
672         }
673     }
674 
675     // Implementation of the SecureElement Reader interface according to OMAPI.
676     final class SecureElementReader extends ISecureElementReader.Stub {
677 
678         private final SecureElementService mService;
679         private final ArrayList<SecureElementSession> mSessions =
680                 new ArrayList<SecureElementSession>();
681 
SecureElementReader(SecureElementService service)682         SecureElementReader(SecureElementService service) {
683             mService = service;
684         }
685 
getAtr()686         public byte[] getAtr() {
687             return Terminal.this.getAtr();
688         }
689 
690         @Override
isSecureElementPresent()691         public boolean isSecureElementPresent() throws RemoteException {
692             return Terminal.this.isSecureElementPresent();
693         }
694 
695         @Override
closeSessions()696         public void closeSessions() {
697             synchronized (mLock) {
698                 while (mSessions.size() > 0) {
699                     try {
700                         mSessions.get(0).close();
701                     } catch (Exception ignore) {
702                     }
703                 }
704                 mSessions.clear();
705             }
706         }
707 
removeSession(SecureElementSession session)708         public void removeSession(SecureElementSession session) {
709             if (session == null) {
710                 throw new NullPointerException("session is null");
711             }
712             mSessions.remove(session);
713             synchronized (mLock) {
714                 if (mSessions.size() == 0) {
715                     mDefaultApplicationSelectedOnBasicChannel = true;
716                 }
717             }
718         }
719 
720         @Override
openSession()721         public ISecureElementSession openSession() throws RemoteException {
722             if (!isSecureElementPresent()) {
723                 throw new ServiceSpecificException(SEService.IO_ERROR,
724                         "Secure Element is not present.");
725             }
726 
727             synchronized (mLock) {
728                 SecureElementSession session = mService.new SecureElementSession(this);
729                 mSessions.add(session);
730                 return session;
731             }
732         }
733 
getTerminal()734         Terminal getTerminal() {
735             return Terminal.this;
736         }
737     }
738 }
739