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