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