blob: d646e23d230ad52a4853e6775143f299d2804dcc [file] [log] [blame]
Ruchi Kandoia1f94012017-12-08 15:07:03 -08001/*
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
Jack Yu3fd7f892019-07-23 17:40:44 +080025import android.app.ActivityThread;
Ruchi Kandoia1f94012017-12-08 15:07:03 -080026import android.annotation.NonNull;
27import android.content.ComponentName;
28import android.content.Context;
29import android.content.Intent;
30import android.content.ServiceConnection;
Jack Yu3fd7f892019-07-23 17:40:44 +080031import android.content.pm.IPackageManager;
32import android.content.pm.PackageManager;
Ruchi Kandoia1f94012017-12-08 15:07:03 -080033import android.os.IBinder;
34import android.os.RemoteException;
35import android.util.Log;
36
37import java.util.HashMap;
Ruchi Kandoicf8bc652018-04-02 13:31:47 -070038import java.util.concurrent.Executor;
Ruchi Kandoia1f94012017-12-08 15:07:03 -080039
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 Kandoif0082402018-03-27 10:03:34 -070047public final class SEService {
Ruchi Kandoia1f94012017-12-08 15:07:03 -080048
Ruchi Kandoi816a0532018-02-01 16:15:25 -080049 /**
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 Kandoi0b0b1832018-02-22 12:47:47 -080066 /**
67 * Interface to send call-backs to the application when the service is connected.
68 */
Ruchi Kandoif0082402018-03-27 10:03:34 -070069 public interface OnConnectedListener {
Ruchi Kandoid785fc42018-03-22 11:06:36 -070070 /**
71 * Called by the framework when the service is connected.
72 */
Ruchi Kandoicf8bc652018-04-02 13:31:47 -070073 void onConnected();
Ruchi Kandoid785fc42018-03-22 11:06:36 -070074 }
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 Kandoif0082402018-03-27 10:03:34 -070081 public OnConnectedListener mListener = null;
Ruchi Kandoicf8bc652018-04-02 13:31:47 -070082 public Executor mExecutor = null;
Ruchi Kandoid785fc42018-03-22 11:06:36 -070083
Ruchi Kandoi0b0b1832018-02-22 12:47:47 -080084 @Override
85 public IBinder asBinder() {
86 return this;
87 }
88
Ruchi Kandoicf8bc652018-04-02 13:31:47 -070089 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 Kandoid785fc42018-03-22 11:06:36 -070097 }
98 }
Ruchi Kandoi0b0b1832018-02-22 12:47:47 -080099 }
Ruchi Kandoid785fc42018-03-22 11:06:36 -0700100 private SEListener mSEListener = new SEListener();
Ruchi Kandoi0b0b1832018-02-22 12:47:47 -0800101
Ruchi Kandoia1f94012017-12-08 15:07:03 -0800102 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 Kandoia1f94012017-12-08 15:07:03 -0800123 * 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 Kandoicf8bc652018-04-02 13:31:47 -0700129 * onConnected() method called when the connection actually happen.
Ruchi Kandoia1f94012017-12-08 15:07:03 -0800130 *
131 * @param context
132 * the context of the calling application. Cannot be
133 * <code>null</code>.
134 * @param listener
Ruchi Kandoif0082402018-03-27 10:03:34 -0700135 * a OnConnectedListener object.
Ruchi Kandoicf8bc652018-04-02 13:31:47 -0700136 * @param executor
137 * an Executor which will be used when invoking the callback.
Ruchi Kandoia1f94012017-12-08 15:07:03 -0800138 */
Ruchi Kandoicf8bc652018-04-02 13:31:47 -0700139 public SEService(@NonNull Context context, @NonNull Executor executor,
140 @NonNull OnConnectedListener listener) {
Ruchi Kandoia1f94012017-12-08 15:07:03 -0800141
Ruchi Kandoicf8bc652018-04-02 13:31:47 -0700142 if (context == null || listener == null || executor == null) {
143 throw new NullPointerException("Arguments must not be null");
Ruchi Kandoia1f94012017-12-08 15:07:03 -0800144 }
145
Jack Yu3fd7f892019-07-23 17:40:44 +0800146 if (!hasOMAPIReaders()) {
147 throw new UnsupportedOperationException("Device does not support any OMAPI reader");
148 }
149
Ruchi Kandoia1f94012017-12-08 15:07:03 -0800150 mContext = context;
Ruchi Kandoid785fc42018-03-22 11:06:36 -0700151 mSEListener.mListener = listener;
Ruchi Kandoicf8bc652018-04-02 13:31:47 -0700152 mSEListener.mExecutor = executor;
Ruchi Kandoia1f94012017-12-08 15:07:03 -0800153
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 Kandoicf8bc652018-04-02 13:31:47 -0700161 mSEListener.onConnected();
Ruchi Kandoia1f94012017-12-08 15:07:03 -0800162 }
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 Kandoif0082402018-03-27 10:03:34 -0700192 * Returns an array of available Secure Element readers.
Ruchi Kandoia1f94012017-12-08 15:07:03 -0800193 * 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 Kandoif0082402018-03-27 10:03:34 -0700196 * @return An array of Readers. If there are no readers the returned array
197 * is of length 0.
Ruchi Kandoia1f94012017-12-08 15:07:03 -0800198 */
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 Kandoif0082402018-03-27 10:03:34 -0700233 * 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 Kandoia1f94012017-12-08 15:07:03 -0800235 * (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 Kandoid785fc42018-03-22 11:06:36 -0700262 public @NonNull String getVersion() {
Ruchi Kandoic46670d2018-04-26 01:45:14 -0700263 return "3.3";
Ruchi Kandoia1f94012017-12-08 15:07:03 -0800264 }
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 Yu3fd7f892019-07-23 17:40:44 +0800280
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 Kandoia1f94012017-12-08 15:07:03 -0800299}