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. All rights reserved.
18  */
19 /*
20  * Contributed by: Giesecke & Devrient GmbH.
21  */
22 
23 package android.se.omapi;
24 
25 import android.annotation.BroadcastBehavior;
26 import android.annotation.NonNull;
27 import android.annotation.SdkConstant;
28 import android.annotation.SdkConstant.SdkConstantType;
29 import android.content.ComponentName;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.ServiceConnection;
33 import android.os.IBinder;
34 import android.os.RemoteException;
35 import android.util.Log;
36 
37 import java.util.HashMap;
38 import java.util.concurrent.Executor;
39 
40 /**
41  * The SEService realises the communication to available Secure Elements on the
42  * device. This is the entry point of this API. It is used to connect to the
43  * infrastructure and get access to a list of Secure Element Readers.
44  *
45  * @see <a href="http://simalliance.org">SIMalliance Open Mobile API  v3.0</a>
46  */
47 public final class SEService {
48 
49     /**
50      * Error code used with ServiceSpecificException.
51      * Thrown if there was an error communicating with the Secure Element.
52      *
53      * @hide
54      */
55     public static final int IO_ERROR = 1;
56 
57     /**
58      * Error code used with ServiceSpecificException.
59      * Thrown if AID cannot be selected or is not available when opening
60      * a logical channel.
61      *
62      * @hide
63      */
64     public static final int NO_SUCH_ELEMENT_ERROR = 2;
65 
66     /**
67      * Interface to send call-backs to the application when the service is connected.
68      */
69     public interface OnConnectedListener {
70         /**
71          * Called by the framework when the service is connected.
72          */
onConnected()73         void onConnected();
74     }
75 
76     /**
77      * Broadcast Action: Intent to notify if the secure element state is changed.
78      */
79     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
80     @BroadcastBehavior(registeredOnly = true, protectedBroadcast = true)
81     public static final String ACTION_SECURE_ELEMENT_STATE_CHANGED =
82             "android.se.omapi.action.SECURE_ELEMENT_STATE_CHANGED";
83 
84     /**
85      * Mandatory extra containing the reader name of the state changed secure element.
86      *
87      * @see Reader#getName()
88      */
89     public static final String EXTRA_READER_NAME = "android.se.omapi.extra.READER_NAME";
90 
91     /**
92      * Mandatory extra containing the connected state of the state changed secure element.
93      *
94      * True if the secure element is connected correctly, false otherwise.
95      */
96     public static final String EXTRA_READER_STATE = "android.se.omapi.extra.READER_STATE";
97 
98     /**
99      * Listener object that allows the notification of the caller if this
100      * SEService could be bound to the backend.
101      */
102     private class SEListener extends ISecureElementListener.Stub {
103         public OnConnectedListener mListener = null;
104         public Executor mExecutor = null;
105 
106         @Override
asBinder()107         public IBinder asBinder() {
108             return this;
109         }
110 
onConnected()111         public void onConnected() {
112             if (mListener != null && mExecutor != null) {
113                 mExecutor.execute(new Runnable() {
114                     @Override
115                     public void run() {
116                         mListener.onConnected();
117                     }
118                 });
119             }
120         }
121     }
122     private SEListener mSEListener = new SEListener();
123 
124     private static final String TAG = "OMAPI.SEService";
125 
126     private static final String UICC_TERMINAL = "SIM";
127 
128     private final Object mLock = new Object();
129 
130     /** The client context (e.g. activity). */
131     private final Context mContext;
132 
133     /** The backend system. */
134     private volatile ISecureElementService mSecureElementService;
135 
136     /**
137      * Class for interacting with the main interface of the backend.
138      */
139     private ServiceConnection mConnection;
140 
141     /**
142      * Collection of available readers
143      */
144     private final HashMap<String, Reader> mReaders = new HashMap<String, Reader>();
145 
146     /**
147      * Establishes a new connection that can be used to connect to all the
148      * Secure Elements available in the system. The connection process can be
149      * quite long, so it happens in an asynchronous way. It is usable only if
150      * the specified listener is called or if isConnected() returns
151      * <code>true</code>. <br>
152      * The call-back object passed as a parameter will have its
153      * onConnected() method called when the connection actually happen.
154      *
155      * @param context
156      *            the context of the calling application. Cannot be
157      *            <code>null</code>.
158      * @param listener
159      *            a OnConnectedListener object.
160      * @param executor
161      *            an Executor which will be used when invoking the callback.
162      */
SEService(@onNull Context context, @NonNull Executor executor, @NonNull OnConnectedListener listener)163     public SEService(@NonNull Context context, @NonNull Executor executor,
164             @NonNull OnConnectedListener listener) {
165 
166         if (context == null || listener == null || executor == null) {
167             throw new NullPointerException("Arguments must not be null");
168         }
169 
170         mContext = context;
171         mSEListener.mListener = listener;
172         mSEListener.mExecutor = executor;
173 
174         mConnection = new ServiceConnection() {
175 
176             public synchronized void onServiceConnected(
177                     ComponentName className, IBinder service) {
178 
179                 mSecureElementService = ISecureElementService.Stub.asInterface(service);
180                 if (mSEListener != null) {
181                     mSEListener.onConnected();
182                 }
183                 Log.i(TAG, "Service onServiceConnected");
184             }
185 
186             public void onServiceDisconnected(ComponentName className) {
187                 mSecureElementService = null;
188                 Log.i(TAG, "Service onServiceDisconnected");
189             }
190         };
191 
192         Intent intent = new Intent(ISecureElementService.class.getName());
193         intent.setClassName("com.android.se",
194                             "com.android.se.SecureElementService");
195         boolean bindingSuccessful =
196                 mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
197         if (bindingSuccessful) {
198             Log.i(TAG, "bindService successful");
199         }
200     }
201 
202     /**
203      * Tells whether or not the service is connected.
204      *
205      * @return <code>true</code> if the service is connected.
206      */
isConnected()207     public boolean isConnected() {
208         return mSecureElementService != null;
209     }
210 
211     /**
212      * Returns an array of available Secure Element readers.
213      * There must be no duplicated objects in the returned list.
214      * All available readers shall be listed even if no card is inserted.
215      *
216      * @return An array of Readers. If there are no readers the returned array
217      * is of length 0.
218      */
getReaders()219     public @NonNull Reader[] getReaders() {
220         loadReaders();
221 
222         return mReaders.values().toArray(new Reader[0]);
223     }
224 
225     /**
226       * Obtain a UICC Reader instance with specific slot number from the SecureElementService
227       *
228       * @param slotNumber The index of the uicc slot. The index starts from 1.
229       * @throws IllegalArgumentException if the reader object corresponding to the uiccSlotNumber
230       *         is not exist.
231       * @return A Reader object for this uicc slot.
232       */
getUiccReader(int slotNumber)233      public @NonNull Reader getUiccReader(int slotNumber) {
234          if (slotNumber < 1) {
235              throw new IllegalArgumentException("slotNumber should be larger than 0");
236          }
237          loadReaders();
238 
239          String readerName = UICC_TERMINAL + slotNumber;
240          Reader reader = mReaders.get(readerName);
241 
242          if (reader == null) {
243             throw new IllegalArgumentException("Reader:" + readerName + " doesn't exist");
244          }
245 
246          return reader;
247     }
248 
249     /**
250      * Releases all Secure Elements resources allocated by this SEService
251      * (including any binding to an underlying service).
252      * As a result isConnected() will return false after shutdown() was called.
253      * After this method call, the SEService object is not connected.
254      * This method should be called when connection to the Secure Element is not needed
255      * or in the termination method of the calling application
256      * (or part of this application) which is bound to this SEService.
257      */
shutdown()258     public void shutdown() {
259         synchronized (mLock) {
260             if (mSecureElementService != null) {
261                 for (Reader reader : mReaders.values()) {
262                     try {
263                         reader.closeSessions();
264                     } catch (Exception ignore) { }
265                 }
266             }
267             try {
268                 mContext.unbindService(mConnection);
269             } catch (IllegalArgumentException e) {
270                 // Do nothing and fail silently since an error here indicates
271                 // that binding never succeeded in the first place.
272             }
273             mSecureElementService = null;
274         }
275     }
276 
277     /**
278      * Returns the version of the OpenMobile API specification this
279      * implementation is based on.
280      *
281      * @return String containing the OpenMobile API version (e.g. "3.0").
282      */
getVersion()283     public @NonNull String getVersion() {
284         return "3.3";
285     }
286 
getListener()287     @NonNull ISecureElementListener getListener() {
288         return mSEListener;
289     }
290 
291     /**
292      * Obtain a Reader instance from the SecureElementService
293      */
getReader(String name)294     private @NonNull ISecureElementReader getReader(String name) {
295         try {
296             return mSecureElementService.getReader(name);
297         } catch (RemoteException e) {
298             throw new IllegalStateException(e.getMessage());
299         }
300     }
301 
302     /**
303      * Load available Secure Element Readers
304      */
loadReaders()305     private void loadReaders() {
306         if (mSecureElementService == null) {
307             throw new IllegalStateException("service not connected to system");
308         }
309         String[] readerNames;
310         try {
311             readerNames = mSecureElementService.getReaders();
312         } catch (RemoteException e) {
313             throw e.rethrowAsRuntimeException();
314         }
315 
316         for (String readerName : readerNames) {
317             if (mReaders.get(readerName) == null) {
318                 try {
319                     mReaders.put(readerName, new Reader(this, readerName,
320                             getReader(readerName)));
321                 } catch (Exception e) {
322                     Log.e(TAG, "Error adding Reader: " + readerName, e);
323                 }
324             }
325         }
326     }
327 }
328