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