Ruchi Kandoi | a1f9401 | 2017-12-08 15:07:03 -0800 | [diff] [blame] | 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 | |
Jack Yu | 3fd7f89 | 2019-07-23 17:40:44 +0800 | [diff] [blame] | 25 | import android.app.ActivityThread; |
Ruchi Kandoi | a1f9401 | 2017-12-08 15:07:03 -0800 | [diff] [blame] | 26 | import android.annotation.NonNull; |
| 27 | import android.content.ComponentName; |
| 28 | import android.content.Context; |
| 29 | import android.content.Intent; |
| 30 | import android.content.ServiceConnection; |
Jack Yu | 3fd7f89 | 2019-07-23 17:40:44 +0800 | [diff] [blame] | 31 | import android.content.pm.IPackageManager; |
| 32 | import android.content.pm.PackageManager; |
Ruchi Kandoi | a1f9401 | 2017-12-08 15:07:03 -0800 | [diff] [blame] | 33 | import android.os.IBinder; |
| 34 | import android.os.RemoteException; |
| 35 | import android.util.Log; |
| 36 | |
| 37 | import java.util.HashMap; |
Ruchi Kandoi | cf8bc65 | 2018-04-02 13:31:47 -0700 | [diff] [blame] | 38 | import java.util.concurrent.Executor; |
Ruchi Kandoi | a1f9401 | 2017-12-08 15:07:03 -0800 | [diff] [blame] | 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 | */ |
Ruchi Kandoi | f008240 | 2018-03-27 10:03:34 -0700 | [diff] [blame] | 47 | public final class SEService { |
Ruchi Kandoi | a1f9401 | 2017-12-08 15:07:03 -0800 | [diff] [blame] | 48 | |
Ruchi Kandoi | 816a053 | 2018-02-01 16:15:25 -0800 | [diff] [blame] | 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 | |
Ruchi Kandoi | 0b0b183 | 2018-02-22 12:47:47 -0800 | [diff] [blame] | 66 | /** |
| 67 | * Interface to send call-backs to the application when the service is connected. |
| 68 | */ |
Ruchi Kandoi | f008240 | 2018-03-27 10:03:34 -0700 | [diff] [blame] | 69 | public interface OnConnectedListener { |
Ruchi Kandoi | d785fc4 | 2018-03-22 11:06:36 -0700 | [diff] [blame] | 70 | /** |
| 71 | * Called by the framework when the service is connected. |
| 72 | */ |
Ruchi Kandoi | cf8bc65 | 2018-04-02 13:31:47 -0700 | [diff] [blame] | 73 | void onConnected(); |
Ruchi Kandoi | d785fc4 | 2018-03-22 11:06:36 -0700 | [diff] [blame] | 74 | } |
| 75 | |
| 76 | /** |
| 77 | * Listener object that allows the notification of the caller if this |
| 78 | * SEService could be bound to the backend. |
| 79 | */ |
| 80 | private class SEListener extends ISecureElementListener.Stub { |
Ruchi Kandoi | f008240 | 2018-03-27 10:03:34 -0700 | [diff] [blame] | 81 | public OnConnectedListener mListener = null; |
Ruchi Kandoi | cf8bc65 | 2018-04-02 13:31:47 -0700 | [diff] [blame] | 82 | public Executor mExecutor = null; |
Ruchi Kandoi | d785fc4 | 2018-03-22 11:06:36 -0700 | [diff] [blame] | 83 | |
Ruchi Kandoi | 0b0b183 | 2018-02-22 12:47:47 -0800 | [diff] [blame] | 84 | @Override |
| 85 | public IBinder asBinder() { |
| 86 | return this; |
| 87 | } |
| 88 | |
Ruchi Kandoi | cf8bc65 | 2018-04-02 13:31:47 -0700 | [diff] [blame] | 89 | public void onConnected() { |
| 90 | if (mListener != null && mExecutor != null) { |
| 91 | mExecutor.execute(new Runnable() { |
| 92 | @Override |
| 93 | public void run() { |
| 94 | mListener.onConnected(); |
| 95 | } |
| 96 | }); |
Ruchi Kandoi | d785fc4 | 2018-03-22 11:06:36 -0700 | [diff] [blame] | 97 | } |
| 98 | } |
Ruchi Kandoi | 0b0b183 | 2018-02-22 12:47:47 -0800 | [diff] [blame] | 99 | } |
Ruchi Kandoi | d785fc4 | 2018-03-22 11:06:36 -0700 | [diff] [blame] | 100 | private SEListener mSEListener = new SEListener(); |
Ruchi Kandoi | 0b0b183 | 2018-02-22 12:47:47 -0800 | [diff] [blame] | 101 | |
Ruchi Kandoi | a1f9401 | 2017-12-08 15:07:03 -0800 | [diff] [blame] | 102 | private static final String TAG = "OMAPI.SEService"; |
| 103 | |
| 104 | private final Object mLock = new Object(); |
| 105 | |
| 106 | /** The client context (e.g. activity). */ |
| 107 | private final Context mContext; |
| 108 | |
| 109 | /** The backend system. */ |
| 110 | private volatile ISecureElementService mSecureElementService; |
| 111 | |
| 112 | /** |
| 113 | * Class for interacting with the main interface of the backend. |
| 114 | */ |
| 115 | private ServiceConnection mConnection; |
| 116 | |
| 117 | /** |
| 118 | * Collection of available readers |
| 119 | */ |
| 120 | private final HashMap<String, Reader> mReaders = new HashMap<String, Reader>(); |
| 121 | |
| 122 | /** |
Ruchi Kandoi | a1f9401 | 2017-12-08 15:07:03 -0800 | [diff] [blame] | 123 | * Establishes a new connection that can be used to connect to all the |
| 124 | * Secure Elements available in the system. The connection process can be |
| 125 | * quite long, so it happens in an asynchronous way. It is usable only if |
| 126 | * the specified listener is called or if isConnected() returns |
| 127 | * <code>true</code>. <br> |
| 128 | * The call-back object passed as a parameter will have its |
Ruchi Kandoi | cf8bc65 | 2018-04-02 13:31:47 -0700 | [diff] [blame] | 129 | * onConnected() method called when the connection actually happen. |
Ruchi Kandoi | a1f9401 | 2017-12-08 15:07:03 -0800 | [diff] [blame] | 130 | * |
| 131 | * @param context |
| 132 | * the context of the calling application. Cannot be |
| 133 | * <code>null</code>. |
| 134 | * @param listener |
Ruchi Kandoi | f008240 | 2018-03-27 10:03:34 -0700 | [diff] [blame] | 135 | * a OnConnectedListener object. |
Ruchi Kandoi | cf8bc65 | 2018-04-02 13:31:47 -0700 | [diff] [blame] | 136 | * @param executor |
| 137 | * an Executor which will be used when invoking the callback. |
Ruchi Kandoi | a1f9401 | 2017-12-08 15:07:03 -0800 | [diff] [blame] | 138 | */ |
Ruchi Kandoi | cf8bc65 | 2018-04-02 13:31:47 -0700 | [diff] [blame] | 139 | public SEService(@NonNull Context context, @NonNull Executor executor, |
| 140 | @NonNull OnConnectedListener listener) { |
Ruchi Kandoi | a1f9401 | 2017-12-08 15:07:03 -0800 | [diff] [blame] | 141 | |
Ruchi Kandoi | cf8bc65 | 2018-04-02 13:31:47 -0700 | [diff] [blame] | 142 | if (context == null || listener == null || executor == null) { |
| 143 | throw new NullPointerException("Arguments must not be null"); |
Ruchi Kandoi | a1f9401 | 2017-12-08 15:07:03 -0800 | [diff] [blame] | 144 | } |
| 145 | |
Jack Yu | 3fd7f89 | 2019-07-23 17:40:44 +0800 | [diff] [blame] | 146 | if (!hasOMAPIReaders()) { |
| 147 | throw new UnsupportedOperationException("Device does not support any OMAPI reader"); |
| 148 | } |
| 149 | |
Ruchi Kandoi | a1f9401 | 2017-12-08 15:07:03 -0800 | [diff] [blame] | 150 | mContext = context; |
Ruchi Kandoi | d785fc4 | 2018-03-22 11:06:36 -0700 | [diff] [blame] | 151 | mSEListener.mListener = listener; |
Ruchi Kandoi | cf8bc65 | 2018-04-02 13:31:47 -0700 | [diff] [blame] | 152 | mSEListener.mExecutor = executor; |
Ruchi Kandoi | a1f9401 | 2017-12-08 15:07:03 -0800 | [diff] [blame] | 153 | |
| 154 | mConnection = new ServiceConnection() { |
| 155 | |
| 156 | public synchronized void onServiceConnected( |
| 157 | ComponentName className, IBinder service) { |
| 158 | |
| 159 | mSecureElementService = ISecureElementService.Stub.asInterface(service); |
| 160 | if (mSEListener != null) { |
Ruchi Kandoi | cf8bc65 | 2018-04-02 13:31:47 -0700 | [diff] [blame] | 161 | mSEListener.onConnected(); |
Ruchi Kandoi | a1f9401 | 2017-12-08 15:07:03 -0800 | [diff] [blame] | 162 | } |
| 163 | Log.i(TAG, "Service onServiceConnected"); |
| 164 | } |
| 165 | |
| 166 | public void onServiceDisconnected(ComponentName className) { |
| 167 | mSecureElementService = null; |
| 168 | Log.i(TAG, "Service onServiceDisconnected"); |
| 169 | } |
| 170 | }; |
| 171 | |
| 172 | Intent intent = new Intent(ISecureElementService.class.getName()); |
| 173 | intent.setClassName("com.android.se", |
| 174 | "com.android.se.SecureElementService"); |
| 175 | boolean bindingSuccessful = |
| 176 | mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE); |
| 177 | if (bindingSuccessful) { |
| 178 | Log.i(TAG, "bindService successful"); |
| 179 | } |
| 180 | } |
| 181 | |
| 182 | /** |
| 183 | * Tells whether or not the service is connected. |
| 184 | * |
| 185 | * @return <code>true</code> if the service is connected. |
| 186 | */ |
| 187 | public boolean isConnected() { |
| 188 | return mSecureElementService != null; |
| 189 | } |
| 190 | |
| 191 | /** |
Ruchi Kandoi | f008240 | 2018-03-27 10:03:34 -0700 | [diff] [blame] | 192 | * Returns an array of available Secure Element readers. |
Ruchi Kandoi | a1f9401 | 2017-12-08 15:07:03 -0800 | [diff] [blame] | 193 | * There must be no duplicated objects in the returned list. |
| 194 | * All available readers shall be listed even if no card is inserted. |
| 195 | * |
Ruchi Kandoi | f008240 | 2018-03-27 10:03:34 -0700 | [diff] [blame] | 196 | * @return An array of Readers. If there are no readers the returned array |
| 197 | * is of length 0. |
Ruchi Kandoi | a1f9401 | 2017-12-08 15:07:03 -0800 | [diff] [blame] | 198 | */ |
| 199 | public @NonNull Reader[] getReaders() { |
| 200 | if (mSecureElementService == null) { |
| 201 | throw new IllegalStateException("service not connected to system"); |
| 202 | } |
| 203 | String[] readerNames; |
| 204 | try { |
| 205 | readerNames = mSecureElementService.getReaders(); |
| 206 | } catch (RemoteException e) { |
| 207 | throw new RuntimeException(e); |
| 208 | } |
| 209 | |
| 210 | Reader[] readers = new Reader[readerNames.length]; |
| 211 | int i = 0; |
| 212 | for (String readerName : readerNames) { |
| 213 | if (mReaders.get(readerName) == null) { |
| 214 | try { |
| 215 | mReaders.put(readerName, new Reader(this, readerName, |
| 216 | getReader(readerName))); |
| 217 | readers[i++] = mReaders.get(readerName); |
| 218 | } catch (Exception e) { |
| 219 | Log.e(TAG, "Error adding Reader: " + readerName, e); |
| 220 | } |
| 221 | } else { |
| 222 | readers[i++] = mReaders.get(readerName); |
| 223 | } |
| 224 | } |
| 225 | return readers; |
| 226 | } |
| 227 | |
| 228 | /** |
| 229 | * Releases all Secure Elements resources allocated by this SEService |
| 230 | * (including any binding to an underlying service). |
| 231 | * As a result isConnected() will return false after shutdown() was called. |
| 232 | * After this method call, the SEService object is not connected. |
Ruchi Kandoi | f008240 | 2018-03-27 10:03:34 -0700 | [diff] [blame] | 233 | * This method should be called when connection to the Secure Element is not needed |
| 234 | * or in the termination method of the calling application |
Ruchi Kandoi | a1f9401 | 2017-12-08 15:07:03 -0800 | [diff] [blame] | 235 | * (or part of this application) which is bound to this SEService. |
| 236 | */ |
| 237 | public void shutdown() { |
| 238 | synchronized (mLock) { |
| 239 | if (mSecureElementService != null) { |
| 240 | for (Reader reader : mReaders.values()) { |
| 241 | try { |
| 242 | reader.closeSessions(); |
| 243 | } catch (Exception ignore) { } |
| 244 | } |
| 245 | } |
| 246 | try { |
| 247 | mContext.unbindService(mConnection); |
| 248 | } catch (IllegalArgumentException e) { |
| 249 | // Do nothing and fail silently since an error here indicates |
| 250 | // that binding never succeeded in the first place. |
| 251 | } |
| 252 | mSecureElementService = null; |
| 253 | } |
| 254 | } |
| 255 | |
| 256 | /** |
| 257 | * Returns the version of the OpenMobile API specification this |
| 258 | * implementation is based on. |
| 259 | * |
| 260 | * @return String containing the OpenMobile API version (e.g. "3.0"). |
| 261 | */ |
Ruchi Kandoi | d785fc4 | 2018-03-22 11:06:36 -0700 | [diff] [blame] | 262 | public @NonNull String getVersion() { |
Ruchi Kandoi | c46670d | 2018-04-26 01:45:14 -0700 | [diff] [blame] | 263 | return "3.3"; |
Ruchi Kandoi | a1f9401 | 2017-12-08 15:07:03 -0800 | [diff] [blame] | 264 | } |
| 265 | |
| 266 | @NonNull ISecureElementListener getListener() { |
| 267 | return mSEListener; |
| 268 | } |
| 269 | |
| 270 | /** |
| 271 | * Obtain a Reader instance from the SecureElementService |
| 272 | */ |
| 273 | private @NonNull ISecureElementReader getReader(String name) { |
| 274 | try { |
| 275 | return mSecureElementService.getReader(name); |
| 276 | } catch (RemoteException e) { |
| 277 | throw new IllegalStateException(e.getMessage()); |
| 278 | } |
| 279 | } |
Jack Yu | 3fd7f89 | 2019-07-23 17:40:44 +0800 | [diff] [blame] | 280 | |
| 281 | /** |
| 282 | * Helper to check if this device support any OMAPI readers |
| 283 | */ |
| 284 | private static boolean hasOMAPIReaders() { |
| 285 | IPackageManager pm = ActivityThread.getPackageManager(); |
| 286 | if (pm == null) { |
| 287 | Log.e(TAG, "Cannot get package manager, assuming OMAPI readers supported"); |
| 288 | return true; |
| 289 | } |
| 290 | try { |
| 291 | return pm.hasSystemFeature(PackageManager.FEATURE_SE_OMAPI_UICC, 0) |
| 292 | || pm.hasSystemFeature(PackageManager.FEATURE_SE_OMAPI_ESE, 0) |
| 293 | || pm.hasSystemFeature(PackageManager.FEATURE_SE_OMAPI_SD, 0); |
| 294 | } catch (RemoteException e) { |
| 295 | Log.e(TAG, "Package manager query failed, assuming OMAPI readers supported", e); |
| 296 | return true; |
| 297 | } |
| 298 | } |
Ruchi Kandoi | a1f9401 | 2017-12-08 15:07:03 -0800 | [diff] [blame] | 299 | } |