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