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         @Override
getInterfaceHash()123         public String getInterfaceHash() {
124             return ISecureElementListener.HASH;
125         }
126 
127         @Override
getInterfaceVersion()128         public int getInterfaceVersion() {
129             return ISecureElementListener.VERSION;
130         }
131     }
132     private SEListener mSEListener = new SEListener();
133 
134     private static final String TAG = "OMAPI.SEService";
135 
136     private static final String UICC_TERMINAL = "SIM";
137 
138     private final Object mLock = new Object();
139 
140     /** The client context (e.g. activity). */
141     private final Context mContext;
142 
143     /** The backend system. */
144     private volatile ISecureElementService mSecureElementService;
145 
146     /**
147      * Class for interacting with the main interface of the backend.
148      */
149     private ServiceConnection mConnection;
150 
151     /**
152      * Collection of available readers
153      */
154     private final HashMap<String, Reader> mReaders = new HashMap<String, Reader>();
155 
156     /**
157      * Establishes a new connection that can be used to connect to all the
158      * Secure Elements available in the system. The connection process can be
159      * quite long, so it happens in an asynchronous way. It is usable only if
160      * the specified listener is called or if isConnected() returns
161      * <code>true</code>. <br>
162      * The call-back object passed as a parameter will have its
163      * onConnected() method called when the connection actually happen.
164      *
165      * @param context
166      *            the context of the calling application. Cannot be
167      *            <code>null</code>.
168      * @param listener
169      *            a OnConnectedListener object.
170      * @param executor
171      *            an Executor which will be used when invoking the callback.
172      */
SEService(@onNull Context context, @NonNull Executor executor, @NonNull OnConnectedListener listener)173     public SEService(@NonNull Context context, @NonNull Executor executor,
174             @NonNull OnConnectedListener listener) {
175 
176         if (context == null || listener == null || executor == null) {
177             throw new NullPointerException("Arguments must not be null");
178         }
179 
180         mContext = context;
181         mSEListener.mListener = listener;
182         mSEListener.mExecutor = executor;
183 
184         mConnection = new ServiceConnection() {
185 
186             public synchronized void onServiceConnected(
187                     ComponentName className, IBinder service) {
188 
189                 mSecureElementService = ISecureElementService.Stub.asInterface(service);
190                 if (mSEListener != null) {
191                     mSEListener.onConnected();
192                 }
193                 Log.i(TAG, "Service onServiceConnected");
194             }
195 
196             public void onServiceDisconnected(ComponentName className) {
197                 mSecureElementService = null;
198                 Log.i(TAG, "Service onServiceDisconnected");
199             }
200         };
201 
202         Intent intent = new Intent(ISecureElementService.class.getName());
203         intent.setClassName("com.android.se",
204                             "com.android.se.SecureElementService");
205         boolean bindingSuccessful =
206                 mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
207         if (bindingSuccessful) {
208             Log.i(TAG, "bindService successful");
209         }
210     }
211 
212     /**
213      * Tells whether or not the service is connected.
214      *
215      * @return <code>true</code> if the service is connected.
216      */
isConnected()217     public boolean isConnected() {
218         return mSecureElementService != null;
219     }
220 
221     /**
222      * Returns an array of available Secure Element readers.
223      * There must be no duplicated objects in the returned list.
224      * All available readers shall be listed even if no card is inserted.
225      *
226      * @return An array of Readers. If there are no readers the returned array
227      * is of length 0.
228      */
getReaders()229     public @NonNull Reader[] getReaders() {
230         loadReaders();
231 
232         return mReaders.values().toArray(new Reader[0]);
233     }
234 
235     /**
236       * Obtain a UICC Reader instance with specific slot number from the SecureElementService
237       *
238       * @param slotNumber The index of the uicc slot. The index starts from 1.
239       * @throws IllegalArgumentException if the reader object corresponding to the uiccSlotNumber
240       *         is not exist.
241       * @return A Reader object for this uicc slot.
242       */
getUiccReader(int slotNumber)243     public @NonNull Reader getUiccReader(int slotNumber) {
244         if (slotNumber < 1) {
245             throw new IllegalArgumentException("slotNumber should be larger than 0");
246         }
247         loadReaders();
248 
249         String readerName = UICC_TERMINAL + slotNumber;
250         Reader reader = mReaders.get(readerName);
251 
252         if (reader == null) {
253             throw new IllegalArgumentException("Reader:" + readerName + " doesn't exist");
254         }
255 
256         return reader;
257     }
258 
259     /**
260      * Releases all Secure Elements resources allocated by this SEService
261      * (including any binding to an underlying service).
262      * As a result isConnected() will return false after shutdown() was called.
263      * After this method call, the SEService object is not connected.
264      * This method should be called when connection to the Secure Element is not needed
265      * or in the termination method of the calling application
266      * (or part of this application) which is bound to this SEService.
267      */
shutdown()268     public void shutdown() {
269         synchronized (mLock) {
270             if (mSecureElementService != null) {
271                 for (Reader reader : mReaders.values()) {
272                     try {
273                         reader.closeSessions();
274                     } catch (Exception ignore) { }
275                 }
276             }
277             try {
278                 mContext.unbindService(mConnection);
279             } catch (IllegalArgumentException e) {
280                 // Do nothing and fail silently since an error here indicates
281                 // that binding never succeeded in the first place.
282             }
283             mSecureElementService = null;
284         }
285     }
286 
287     /**
288      * Returns the version of the OpenMobile API specification this
289      * implementation is based on.
290      *
291      * @return String containing the OpenMobile API version (e.g. "3.0").
292      */
getVersion()293     public @NonNull String getVersion() {
294         return "3.3";
295     }
296 
getListener()297     @NonNull ISecureElementListener getListener() {
298         return mSEListener;
299     }
300 
301     /**
302      * Obtain a Reader instance from the SecureElementService
303      */
getReader(String name)304     private @NonNull ISecureElementReader getReader(String name) {
305         try {
306             return mSecureElementService.getReader(name);
307         } catch (RemoteException e) {
308             throw new IllegalStateException(e.getMessage());
309         }
310     }
311 
312     /**
313      * Load available Secure Element Readers
314      */
loadReaders()315     private void loadReaders() {
316         if (mSecureElementService == null) {
317             throw new IllegalStateException("service not connected to system");
318         }
319         String[] readerNames;
320         try {
321             readerNames = mSecureElementService.getReaders();
322         } catch (RemoteException e) {
323             throw e.rethrowAsRuntimeException();
324         }
325 
326         for (String readerName : readerNames) {
327             if (mReaders.get(readerName) == null) {
328                 try {
329                     mReaders.put(readerName, new Reader(this, readerName,
330                             getReader(readerName)));
331                 } catch (Exception e) {
332                     Log.e(TAG, "Error adding Reader: " + readerName, e);
333                 }
334             }
335         }
336     }
337 }
338