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