338 lines
11 KiB
Java
338 lines
11 KiB
Java
![]() |
/*
|
||
|
* 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);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|