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