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 23package android.se.omapi; 24 25import android.annotation.NonNull; 26import android.content.ComponentName; 27import android.content.Context; 28import android.content.Intent; 29import android.content.ServiceConnection; 30import android.os.IBinder; 31import android.os.RemoteException; 32import android.util.Log; 33 34import java.util.HashMap; 35import 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 */ 44public 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