| /* | |
| * Copyright (C) 2017 The Android Open Source Project | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| /* | |
| * Copyright (c) 2015-2017, The Linux Foundation. All rights reserved. | |
| */ | |
| /* | |
| * Contributed by: Giesecke & Devrient GmbH. | |
| */ | |
| package android.se.omapi; | |
| import android.annotation.BroadcastBehavior; | |
| import android.annotation.NonNull; | |
| import android.annotation.SdkConstant; | |
| import android.annotation.SdkConstant.SdkConstantType; | |
| import android.content.ComponentName; | |
| import android.content.Context; | |
| import android.content.Intent; | |
| import android.content.ServiceConnection; | |
| import android.os.IBinder; | |
| import android.os.RemoteException; | |
| import android.util.Log; | |
| import java.util.HashMap; | |
| import java.util.concurrent.Executor; | |
| /** | |
| * The SEService realises the communication to available Secure Elements on the | |
| * device. This is the entry point of this API. It is used to connect to the | |
| * infrastructure and get access to a list of Secure Element Readers. | |
| * | |
| * @see <a href="http://simalliance.org">SIMalliance Open Mobile API v3.0</a> | |
| */ | |
| public final class SEService { | |
| /** | |
| * Error code used with ServiceSpecificException. | |
| * Thrown if there was an error communicating with the Secure Element. | |
| * | |
| * @hide | |
| */ | |
| public static final int IO_ERROR = 1; | |
| /** | |
| * Error code used with ServiceSpecificException. | |
| * Thrown if AID cannot be selected or is not available when opening | |
| * a logical channel. | |
| * | |
| * @hide | |
| */ | |
| public static final int NO_SUCH_ELEMENT_ERROR = 2; | |
| /** | |
| * Interface to send call-backs to the application when the service is connected. | |
| */ | |
| public interface OnConnectedListener { | |
| /** | |
| * Called by the framework when the service is connected. | |
| */ | |
| void onConnected(); | |
| } | |
| /** | |
| * Broadcast Action: Intent to notify if the secure element state is changed. | |
| */ | |
| @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) | |
| @BroadcastBehavior(registeredOnly = true, protectedBroadcast = true) | |
| public static final String ACTION_SECURE_ELEMENT_STATE_CHANGED = | |
| "android.se.omapi.action.SECURE_ELEMENT_STATE_CHANGED"; | |
| /** | |
| * Mandatory extra containing the reader name of the state changed secure element. | |
| * | |
| * @see Reader#getName() | |
| */ | |
| public static final String EXTRA_READER_NAME = "android.se.omapi.extra.READER_NAME"; | |
| /** | |
| * Mandatory extra containing the connected state of the state changed secure element. | |
| * | |
| * True if the secure element is connected correctly, false otherwise. | |
| */ | |
| public static final String EXTRA_READER_STATE = "android.se.omapi.extra.READER_STATE"; | |
| /** | |
| * Listener object that allows the notification of the caller if this | |
| * SEService could be bound to the backend. | |
| */ | |
| private class SEListener extends ISecureElementListener.Stub { | |
| public OnConnectedListener mListener = null; | |
| public Executor mExecutor = null; | |
| @Override | |
| public IBinder asBinder() { | |
| return this; | |
| } | |
| public void onConnected() { | |
| if (mListener != null && mExecutor != null) { | |
| mExecutor.execute(new Runnable() { | |
| @Override | |
| public void run() { | |
| mListener.onConnected(); | |
| } | |
| }); | |
| } | |
| } | |
| @Override | |
| public String getInterfaceHash() { | |
| return ISecureElementListener.HASH; | |
| } | |
| @Override | |
| public int getInterfaceVersion() { | |
| return ISecureElementListener.VERSION; | |
| } | |
| } | |
| private SEListener mSEListener = new SEListener(); | |
| private static final String TAG = "OMAPI.SEService"; | |
| private static final String UICC_TERMINAL = "SIM"; | |
| private final Object mLock = new Object(); | |
| /** The client context (e.g. activity). */ | |
| private final Context mContext; | |
| /** The backend system. */ | |
| private volatile ISecureElementService mSecureElementService; | |
| /** | |
| * Class for interacting with the main interface of the backend. | |
| */ | |
| private ServiceConnection mConnection; | |
| /** | |
| * Collection of available readers | |
| */ | |
| private final HashMap<String, Reader> mReaders = new HashMap<String, Reader>(); | |
| /** | |
| * Establishes a new connection that can be used to connect to all the | |
| * Secure Elements available in the system. The connection process can be | |
| * quite long, so it happens in an asynchronous way. It is usable only if | |
| * the specified listener is called or if isConnected() returns | |
| * <code>true</code>. <br> | |
| * The call-back object passed as a parameter will have its | |
| * onConnected() method called when the connection actually happen. | |
| * | |
| * @param context | |
| * the context of the calling application. Cannot be | |
| * <code>null</code>. | |
| * @param listener | |
| * a OnConnectedListener object. | |
| * @param executor | |
| * an Executor which will be used when invoking the callback. | |
| */ | |
| public SEService(@NonNull Context context, @NonNull Executor executor, | |
| @NonNull OnConnectedListener listener) { | |
| if (context == null || listener == null || executor == null) { | |
| throw new NullPointerException("Arguments must not be null"); | |
| } | |
| mContext = context; | |
| mSEListener.mListener = listener; | |
| mSEListener.mExecutor = executor; | |
| mConnection = new ServiceConnection() { | |
| public synchronized void onServiceConnected( | |
| ComponentName className, IBinder service) { | |
| mSecureElementService = ISecureElementService.Stub.asInterface(service); | |
| if (mSEListener != null) { | |
| mSEListener.onConnected(); | |
| } | |
| Log.i(TAG, "Service onServiceConnected"); | |
| } | |
| public void onServiceDisconnected(ComponentName className) { | |
| mSecureElementService = null; | |
| Log.i(TAG, "Service onServiceDisconnected"); | |
| } | |
| }; | |
| Intent intent = new Intent(ISecureElementService.class.getName()); | |
| intent.setClassName("com.android.se", | |
| "com.android.se.SecureElementService"); | |
| boolean bindingSuccessful = | |
| mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE); | |
| if (bindingSuccessful) { | |
| Log.i(TAG, "bindService successful"); | |
| } | |
| } | |
| /** | |
| * Tells whether or not the service is connected. | |
| * | |
| * @return <code>true</code> if the service is connected. | |
| */ | |
| public boolean isConnected() { | |
| return mSecureElementService != null; | |
| } | |
| /** | |
| * Returns an array of available Secure Element readers. | |
| * There must be no duplicated objects in the returned list. | |
| * All available readers shall be listed even if no card is inserted. | |
| * | |
| * @return An array of Readers. If there are no readers the returned array | |
| * is of length 0. | |
| */ | |
| public @NonNull Reader[] getReaders() { | |
| loadReaders(); | |
| return mReaders.values().toArray(new Reader[0]); | |
| } | |
| /** | |
| * Obtain a UICC Reader instance with specific slot number from the SecureElementService | |
| * | |
| * @param slotNumber The index of the uicc slot. The index starts from 1. | |
| * @throws IllegalArgumentException if the reader object corresponding to the uiccSlotNumber | |
| * is not exist. | |
| * @return A Reader object for this uicc slot. | |
| */ | |
| public @NonNull Reader getUiccReader(int slotNumber) { | |
| if (slotNumber < 1) { | |
| throw new IllegalArgumentException("slotNumber should be larger than 0"); | |
| } | |
| loadReaders(); | |
| String readerName = UICC_TERMINAL + slotNumber; | |
| Reader reader = mReaders.get(readerName); | |
| if (reader == null) { | |
| throw new IllegalArgumentException("Reader:" + readerName + " doesn't exist"); | |
| } | |
| return reader; | |
| } | |
| /** | |
| * Releases all Secure Elements resources allocated by this SEService | |
| * (including any binding to an underlying service). | |
| * As a result isConnected() will return false after shutdown() was called. | |
| * After this method call, the SEService object is not connected. | |
| * This method should be called when connection to the Secure Element is not needed | |
| * or in the termination method of the calling application | |
| * (or part of this application) which is bound to this SEService. | |
| */ | |
| public void shutdown() { | |
| synchronized (mLock) { | |
| if (mSecureElementService != null) { | |
| for (Reader reader : mReaders.values()) { | |
| try { | |
| reader.closeSessions(); | |
| } catch (Exception ignore) { } | |
| } | |
| } | |
| try { | |
| mContext.unbindService(mConnection); | |
| } catch (IllegalArgumentException e) { | |
| // Do nothing and fail silently since an error here indicates | |
| // that binding never succeeded in the first place. | |
| } | |
| mSecureElementService = null; | |
| } | |
| } | |
| /** | |
| * Returns the version of the OpenMobile API specification this | |
| * implementation is based on. | |
| * | |
| * @return String containing the OpenMobile API version (e.g. "3.0"). | |
| */ | |
| public @NonNull String getVersion() { | |
| return "3.3"; | |
| } | |
| @NonNull ISecureElementListener getListener() { | |
| return mSEListener; | |
| } | |
| /** | |
| * Obtain a Reader instance from the SecureElementService | |
| */ | |
| private @NonNull ISecureElementReader getReader(String name) { | |
| try { | |
| return mSecureElementService.getReader(name); | |
| } catch (RemoteException e) { | |
| throw new IllegalStateException(e.getMessage()); | |
| } | |
| } | |
| /** | |
| * Load available Secure Element Readers | |
| */ | |
| private void loadReaders() { | |
| if (mSecureElementService == null) { | |
| throw new IllegalStateException("service not connected to system"); | |
| } | |
| String[] readerNames; | |
| try { | |
| readerNames = mSecureElementService.getReaders(); | |
| } catch (RemoteException e) { | |
| throw e.rethrowAsRuntimeException(); | |
| } | |
| for (String readerName : readerNames) { | |
| if (mReaders.get(readerName) == null) { | |
| try { | |
| mReaders.put(readerName, new Reader(this, readerName, | |
| getReader(readerName))); | |
| } catch (Exception e) { | |
| Log.e(TAG, "Error adding Reader: " + readerName, e); | |
| } | |
| } | |
| } | |
| } | |
| } |