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