Merge "Open Mobile API for frameworks/base"

This commit is contained in:
Ruchi Kandoi
2018-01-25 23:30:50 +00:00
committed by Gerrit Code Review
11 changed files with 1313 additions and 0 deletions

View File

@@ -200,6 +200,11 @@ java_library {
"core/java/android/nfc/INfcUnlockHandler.aidl",
"core/java/android/nfc/INfcDta.aidl",
"core/java/android/nfc/ITagRemovedCallback.aidl",
"core/java/android/se/omapi/ISecureElementService.aidl",
"core/java/android/se/omapi/ISecureElementListener.aidl",
"core/java/android/se/omapi/ISecureElementChannel.aidl",
"core/java/android/se/omapi/ISecureElementReader.aidl",
"core/java/android/se/omapi/ISecureElementSession.aidl",
"core/java/android/os/IBatteryPropertiesListener.aidl",
"core/java/android/os/IBatteryPropertiesRegistrar.aidl",
"core/java/android/os/ICancellationSignal.aidl",

View File

@@ -37013,6 +37013,59 @@ package android.sax {
}
package android.se.omapi {
public class Channel {
method public void close();
method public byte[] getSelectResponse();
method public android.se.omapi.Session getSession();
method public boolean isBasicChannel();
method public boolean isClosed();
method public boolean selectNext() throws java.io.IOException;
method public byte[] transmit(byte[]) throws java.io.IOException;
}
public abstract interface ISecureElementListener implements android.os.IInterface {
method public abstract void serviceConnected() throws android.os.RemoteException;
}
public static abstract class ISecureElementListener.Stub extends android.os.Binder implements android.se.omapi.ISecureElementListener {
ctor public ISecureElementListener.Stub();
method public android.os.IBinder asBinder();
method public static android.se.omapi.ISecureElementListener asInterface(android.os.IBinder);
method public boolean onTransact(int, android.os.Parcel, android.os.Parcel, int) throws android.os.RemoteException;
}
public class Reader {
method public void closeSessions();
method public java.lang.String getName();
method public android.se.omapi.SEService getSEService();
method public boolean isSecureElementPresent();
method public android.se.omapi.Session openSession() throws java.io.IOException;
}
public class SEService {
ctor public SEService(android.content.Context, android.se.omapi.ISecureElementListener);
method public android.se.omapi.Reader[] getReaders();
method public java.lang.String getVersion();
method public boolean isConnected();
method public void shutdown();
}
public class Session {
method public void close();
method public void closeChannels();
method public byte[] getATR();
method public android.se.omapi.Reader getReader();
method public boolean isClosed();
method public android.se.omapi.Channel openBasicChannel(byte[], byte) throws java.io.IOException;
method public android.se.omapi.Channel openBasicChannel(byte[]) throws java.io.IOException;
method public android.se.omapi.Channel openLogicalChannel(byte[], byte) throws java.io.IOException;
method public android.se.omapi.Channel openLogicalChannel(byte[]) throws java.io.IOException;
}
}
package android.security {
public final class KeyChain {

View File

@@ -0,0 +1,251 @@
/*
* 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.
*/
/*
* Contributed by: Giesecke & Devrient GmbH.
*/
package android.se.omapi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.RemoteException;
import android.util.Log;
import java.io.IOException;
/**
* Instances of this class represent an ISO/IEC 7816-4 channel opened to a
* Secure Element. It can be either a logical channel or the basic channel. They
* can be used to send APDUs to the secure element. Channels are opened by
* calling the Session.openBasicChannel(byte[]) or
* Session.openLogicalChannel(byte[]) methods.
*
* @see <a href="http://globalplatform.org">GlobalPlatform Open Mobile API</a>
*/
public class Channel {
private static final String TAG = "OMAPI.Channel";
private Session mSession;
private final ISecureElementChannel mChannel;
private final SEService mService;
private final Object mLock = new Object();
Channel(SEService service, Session session, ISecureElementChannel channel) {
if (service == null || session == null || channel == null) {
throw new IllegalArgumentException("Parameters cannot be null");
}
mService = service;
mSession = session;
mChannel = channel;
}
/**
* Closes this channel to the Secure Element. If the method is called when
* the channel is already closed, this method will be ignored. The close()
* method shall wait for completion of any pending transmit(byte[] command)
* before closing the channel.
*/
public void close() {
if (!isClosed()) {
synchronized (mLock) {
try {
mChannel.close();
} catch (Exception e) {
Log.e(TAG, "Error closing channel", e);
}
}
}
}
/**
* Tells if this channel is closed.
*
* @return <code>true</code> if the channel is closed or in case of an error.
* <code>false</code> otherwise.
*/
public boolean isClosed() {
if (!mService.isConnected()) {
Log.e(TAG, "service not connected to system");
return true;
}
try {
return mChannel.isClosed();
} catch (RemoteException e) {
Log.e(TAG, "Exception in isClosed()");
return true;
}
}
/**
* Returns a boolean telling if this channel is the basic channel.
*
* @return <code>true</code> if this channel is a basic channel. <code>false</code> if
* this channel is a logical channel.
*/
public boolean isBasicChannel() {
if (!mService.isConnected()) {
throw new IllegalStateException("service not connected to system");
}
try {
return mChannel.isBasicChannel();
} catch (RemoteException e) {
throw new IllegalStateException(e.getMessage());
}
}
/**
* Transmit an APDU command (as per ISO/IEC 7816-4) to the Secure Element. The
* underlying layers generate as many TPDUs as necessary to transport this APDU. The
* API shall ensure that all available data returned from Secure Element, including
* concatenated responses, are retrieved and made available to the calling application. If a
* warning status code is received the API wont check for further response data but will
* return all data received so far and the warning status code.<br>
* The transport part is invisible from the application. The generated response is the
* response of the APDU which means that all protocols related responses are handled
* inside the API or the underlying implementation.<br>
* The transmit method shall support extended length APDU commands independently of
* the coding within the ATR.<br>
* For status word '61 XX' the API or underlying implementation shall issue a GET
* RESPONSE command as specified by ISO 7816-4 standard with LE=XX; for the status
* word '6C XX', the API or underlying implementation shall reissue the input command
* with LE=XX. For other status words, the API (or underlying implementation) shall return
* the complete response including data and status word to the device application. The API
* (or underlying implementation) shall not handle internally the received status words. The
* channel shall not be closed even if the Secure Element answered with an error code.
* The system ensures the synchronization between all the concurrent calls to this method,
* and that only one APDU will be sent at a time, irrespective of the number of TPDUs that
* might be required to transport it to the SE. The entire APDU communication to this SE is
* locked to the APDU.<br>
* The channel information in the class byte in the APDU will be ignored. The system will
* add any required information to ensure the APDU is transported on this channel.
* The only restrictions on the set of commands that can be sent is defined below, the API
* implementation shall be able to send all other commands: <br>
* <ul>
* <li>MANAGE_CHANNEL commands are not allowed.</li>
* <li>SELECT by DF Name (p1=04) are not allowed.</li>
* <li>CLA bytes with channel numbers are de-masked.</li>
* </ul>
*
* @param command the APDU command to be transmitted, as a byte array.
*
* @return the response received, as a byte array. The returned byte array contains the data
* bytes in the following order:
* [&lt;first data byte&gt;, ..., &lt;last data byte&gt;, &lt;sw1&gt;, &lt;sw2&gt;]
*
* @throws IOException if there is a communication problem to the reader or the Secure Element.
* @throws IllegalStateException if the channel is used after being closed.
* @throws IllegalArgumentException if the command byte array is less than 4 bytes long.
* @throws IllegalArgumentException if Lc byte is inconsistent with length of the byte array.
* @throws IllegalArgumentException if CLA byte is invalid according to [2] (0xff).
* @throws IllegalArgumentException if INS byte is invalid according to [2] (0x6x or 0x9x).
* @throws SecurityException if the command is filtered by the security policy.
* @throws NullPointerException if command is NULL.
*/
public @NonNull byte[] transmit(byte[] command) throws IOException {
if (!mService.isConnected()) {
throw new IllegalStateException("service not connected to system");
}
synchronized (mLock) {
try {
byte[] response = mChannel.transmit(command);
if (response == null) {
throw new IOException("Error in communicating with Secure Element");
}
return response;
} catch (RemoteException e) {
throw new IOException(e.getMessage());
}
}
}
/**
* Get the session that has opened this channel.
*
* @return the session object this channel is bound to.
*/
public @NonNull Session getSession() {
return mSession;
}
/**
* Returns the data as received from the application select command inclusively the status word
* received at applet selection.
* The returned byte array contains the data bytes in the following order:
* [&lt;first data byte&gt;, ..., &lt;last data byte&gt;, &lt;sw1&gt;, &lt;sw2&gt;]
* @return The data as returned by the application select command inclusively the status word.
* Only the status word if the application select command has no returned data.
* Returns null if an application select command has not been performed or the selection
* response can not be retrieved by the reader implementation.
*/
public @Nullable byte[] getSelectResponse() {
if (!mService.isConnected()) {
throw new IllegalStateException("service not connected to system");
}
byte[] response;
try {
response = mChannel.getSelectResponse();
} catch (RemoteException e) {
throw new IllegalStateException(e.getMessage());
}
if (response != null && response.length == 0) {
response = null;
}
return response;
}
/**
* Performs a selection of the next Applet on this channel that matches to the partial AID
* specified in the openBasicChannel(byte[] aid) or openLogicalChannel(byte[] aid) method.
* This mechanism can be used by a device application to iterate through all Applets
* matching to the same partial AID.
* If selectNext() returns true a new Applet was successfully selected on this channel.
* If no further Applet exists with matches to the partial AID this method returns false
* and the already selected Applet stays selected. <br>
*
* Since the API cannot distinguish between a partial and full AID the API shall rely on the
* response of the Secure Element for the return value of this method. <br>
* The implementation of the underlying SELECT command within this method shall use
* the same values as the corresponding openBasicChannel(byte[] aid) or
* openLogicalChannel(byte[] aid) command with the option: <br>
* P2='02' (Next occurrence) <br>
* The select response stored in the Channel object shall be updated with the APDU
* response of the SELECT command.
* @return <code>true</code> if new Applet was selected on this channel.
<code>false</code> he already selected Applet stays selected on this channel.
*
* @throws IOException if there is a communication problem to the reader or the Secure Element.
* @throws IllegalStateException if the channel is used after being closed.
* @throws UnsupportedOperationException if this operation is not supported by the card.
*/
public boolean selectNext() throws IOException {
if (!mService.isConnected()) {
throw new IllegalStateException("service not connected to system");
}
try {
synchronized (mLock) {
return mChannel.selectNext();
}
} catch (RemoteException e) {
throw new IOException(e.getMessage());
}
}
}

View File

@@ -0,0 +1,80 @@
/*
* 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.
*/
/*
* Contributed by: Giesecke & Devrient GmbH.
*/
package android.se.omapi;
import android.se.omapi.ISecureElementSession;
/** @hide */
interface ISecureElementChannel {
/**
* Closes the specified connection and frees internal resources.
* A logical channel will be closed.
*/
void close();
/**
* Tells if this channel is closed.
*
* @return <code>true</code> if the channel is closed,
* <code>false</code> otherwise.
*/
boolean isClosed();
/**
* Returns a boolean telling if this channel is the basic channel.
*
* @return <code>true</code> if this channel is a basic channel.
* <code>false</code> if this channel is a logical channel.
*/
boolean isBasicChannel();
/**
* Returns the data as received from the application select command
* inclusively the status word. The returned byte array contains the data
* bytes in the following order:
* [<first data byte>, ..., <last data byte>, <sw1>, <sw2>]
*/
byte[] getSelectResponse();
/**
* Transmits the specified command APDU and returns the response APDU.
* MANAGE channel commands are not supported.
* Selection of applets is not supported in logical channels.
*/
byte[] transmit(in byte[] command);
/**
* Performs a selection of the next Applet on this channel that matches to
* the partial AID specified in the openBasicChannel(byte[] aid) or
* openLogicalChannel(byte[] aid) method. This mechanism can be used by a
* device application to iterate through all Applets matching to the same
* partial AID.
* If selectNext() returns true a new Applet was successfully selected on
* this channel.
* If no further Applet exists with matches to the partial AID this method
* returns false and the already selected Applet stays selected.
*
* @return <code>true</code> if new Applet was successfully selected.
* <code>false</code> if no further Applet exists which matches the
* partial AID.
*/
boolean selectNext();
}

View File

@@ -0,0 +1,30 @@
/*
* 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.
*/
/*
* Contributed by: Giesecke & Devrient GmbH.
*/
package android.se.omapi;
/**
* Interface to receive call-backs when the service is connected.
*/
interface ISecureElementListener {
/**
* Called by the framework when the service is connected.
*/
void serviceConnected();
}

View File

@@ -0,0 +1,51 @@
/*
* 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.
*/
/*
* Contributed by: Giesecke & Devrient GmbH.
*/
package android.se.omapi;
import android.se.omapi.ISecureElementSession;
/** @hide */
interface ISecureElementReader {
/**
* Returns true if a card is present in the specified reader.
* Returns false if a card is not present in the specified reader.
*/
boolean isSecureElementPresent();
/**
* Connects to a secure element in this reader. <br>
* This method prepares (initialises) the Secure Element for communication
* before the Session object is returned (e.g. powers the Secure Element by
* ICC ON if its not already on). There might be multiple sessions opened at
* the same time on the same reader. The system ensures the interleaving of
* APDUs between the respective sessions.
*
* @return a Session object to be used to create Channels.
*/
ISecureElementSession openSession();
/**
* Close all the sessions opened on this reader. All the channels opened by
* all these sessions will be closed.
*/
void closeSessions();
}

View File

@@ -0,0 +1,50 @@
/*
* 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.
*/
/*
* Contributed by: Giesecke & Devrient GmbH.
*/
package android.se.omapi;
import android.se.omapi.ISecureElementReader;
/**
* SecureElement service interface.
* @hide
*/
interface ISecureElementService {
/**
* Returns the friendly names of available Secure Element readers.
*/
String[] getReaders();
/**
* Returns SecureElement Service reader object to the given name.
*/
ISecureElementReader getReader(String reader);
/**
* Checks if the application defined by the package name is allowed to
* receive NFC transaction events for the defined AID.
*/
boolean[] isNFCEventAllowed(String reader, in byte[] aid,
in String[] packageNames);
}

View File

@@ -0,0 +1,73 @@
/*
* 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.
*/
/*
* Contributed by: Giesecke & Devrient GmbH.
*/
package android.se.omapi;
import android.se.omapi.ISecureElementChannel;
import android.se.omapi.ISecureElementReader;
import android.se.omapi.ISecureElementListener;
/** @hide */
interface ISecureElementSession {
/**
* Returns the ATR of the connected card or null if the ATR is not available
*/
byte[] getAtr();
/**
* Close the connection with the Secure Element. This will close any
* channels opened by this application with this Secure Element.
*/
void close();
/**
* Close any channel opened on this session.
*/
void closeChannels();
/**
* Tells if this session is closed.
*
* @return <code>true</code> if the session is closed, false otherwise.
*/
boolean isClosed();
/**
* Opens a connection using the basic channel of the card in the
* specified reader and returns a channel handle. Selects the specified
* applet if aid != null.
* Logical channels cannot be opened with this connection.
* Use interface method openLogicalChannel() to open a logical channel.
*/
ISecureElementChannel openBasicChannel(in byte[] aid, in byte p2,
ISecureElementListener listener);
/**
* Opens a connection using the next free logical channel of the card in the
* specified reader. Selects the specified applet.
* Selection of other applets with this connection is not supported.
*/
ISecureElementChannel openLogicalChannel(in byte[] aid, in byte p2,
ISecureElementListener listener);
}

View File

@@ -0,0 +1,151 @@
/*
* 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.
*/
/*
* Contributed by: Giesecke & Devrient GmbH.
*/
package android.se.omapi;
import android.annotation.NonNull;
import android.os.RemoteException;
import android.util.Log;
import java.io.IOException;
/**
* Instances of this class represent Secure Element Readers supported to this
* device. These Readers can be physical devices or virtual devices. They can be
* removable or not. They can contain Secure Element that can or cannot be
* removed.
*
* @see <a href="http://globalplatform.org">GlobalPlatform Open Mobile API</a>
*/
public class Reader {
private static final String TAG = "OMAPI.Reader";
private final String mName;
private final SEService mService;
private ISecureElementReader mReader;
private final Object mLock = new Object();
Reader(SEService service, String name, ISecureElementReader reader) throws
IOException {
if (reader == null || service == null || name == null) {
throw new IllegalArgumentException("Parameters cannot be null");
}
mName = name;
mService = service;
mReader = reader;
}
/**
* Return the name of this reader.
* <ul>
* <li>If this reader is a SIM reader, then its name must be "SIM[Slot]".</li>
* <li>If the reader is a SD or micro SD reader, then its name must be "SD[Slot]"</li>
* <li>If the reader is a embedded SE reader, then its name must be "eSE[Slot]"</li>
* </ul>
* Slot is a decimal number without leading zeros. The Numbering must start with 1
* (e.g. SIM1, SIM2, ... or SD1, SD2, ... or eSE1, eSE2, ...).
* The slot number “1” for a reader is optional
* (SIM and SIM1 are both valid for the first SIM-reader,
* but if there are two readers then the second reader must be named SIM2).
* This applies also for other SD or SE readers.
*
* @return the reader name, as a String.
*/
public @NonNull String getName() {
return mName;
}
/**
* Connects to a Secure Element in this reader. <br>
* This method prepares (initialises) the Secure Element for communication
* before the Session object is returned (e.g. powers the Secure Element by
* ICC ON if its not already on). There might be multiple sessions opened at
* the same time on the same reader. The system ensures the interleaving of
* APDUs between the respective sessions.
*
* @throws IOException if something went wrong with the communicating to the
* Secure Element or the reader.
* @return a Session object to be used to create Channels.
*/
public @NonNull Session openSession() throws IOException {
if (!mService.isConnected()) {
throw new IllegalStateException("service is not connected");
}
synchronized (mLock) {
ISecureElementSession session;
try {
session = mReader.openSession();
} catch (RemoteException e) {
throw new IOException(e.getMessage());
}
if (session == null) {
throw new IOException("service session is null.");
}
return new Session(mService, session, this);
}
}
/**
* Check if a Secure Element is present in this reader.
*
* @throws IllegalStateException if the service is not connected
* @return <code>true</code> if the SE is present, <code>false</code> otherwise.
*/
public boolean isSecureElementPresent() {
if (!mService.isConnected()) {
throw new IllegalStateException("service is not connected");
}
try {
return mReader.isSecureElementPresent();
} catch (RemoteException e) {
throw new IllegalStateException("Error in isSecureElementPresent()");
}
}
/**
* Return the Secure Element service this reader is bound to.
*
* @return the SEService object.
*/
public @NonNull SEService getSEService() {
return mService;
}
/**
* Close all the sessions opened on this reader.
* All the channels opened by all these sessions will be closed.
*/
public void closeSessions() {
if (!mService.isConnected()) {
Log.e(TAG, "service is not connected");
return;
}
synchronized (mLock) {
try {
mReader.closeSessions();
} catch (RemoteException ignore) { }
}
}
}

View File

@@ -0,0 +1,222 @@
/*
* 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.NonNull;
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;
/**
* 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 class SEService {
private static final String TAG = "OMAPI.SEService";
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>();
/**
* Listener object that allows the notification of the caller if this
* SEService could be bound to the backend.
*/
private ISecureElementListener mSEListener;
/**
* 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
* serviceConnected() method called when the connection actually happen.
*
* @param context
* the context of the calling application. Cannot be
* <code>null</code>.
* @param listener
* a ISecureElementListener object. Can be <code>null</code>.
*/
public SEService(Context context, ISecureElementListener listener) {
if (context == null) {
throw new NullPointerException("context must not be null");
}
mContext = context;
mSEListener = listener;
mConnection = new ServiceConnection() {
public synchronized void onServiceConnected(
ComponentName className, IBinder service) {
mSecureElementService = ISecureElementService.Stub.asInterface(service);
if (mSEListener != null) {
try {
mSEListener.serviceConnected();
} catch (RemoteException ignore) { }
}
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 the list 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 The readers list, as an array of Readers. If there are no
* readers the returned array is of length 0.
*/
public @NonNull Reader[] getReaders() {
if (mSecureElementService == null) {
throw new IllegalStateException("service not connected to system");
}
String[] readerNames;
try {
readerNames = mSecureElementService.getReaders();
} catch (RemoteException e) {
throw new RuntimeException(e);
}
Reader[] readers = new Reader[readerNames.length];
int i = 0;
for (String readerName : readerNames) {
if (mReaders.get(readerName) == null) {
try {
mReaders.put(readerName, new Reader(this, readerName,
getReader(readerName)));
readers[i++] = mReaders.get(readerName);
} catch (Exception e) {
Log.e(TAG, "Error adding Reader: " + readerName, e);
}
} else {
readers[i++] = mReaders.get(readerName);
}
}
return readers;
}
/**
* 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.
* It is recommended to call this method 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 String getVersion() {
return "3.2";
}
@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());
}
}
}

View File

@@ -0,0 +1,347 @@
/*
* 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) 2017, The Linux Foundation.
*/
/*
* Contributed by: Giesecke & Devrient GmbH.
*/
package android.se.omapi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.RemoteException;
import android.util.Log;
import java.io.IOException;
import java.util.NoSuchElementException;
/**
* Instances of this class represent a connection session to one of the Secure
* Elements available on the device. These objects can be used to get a
* communication channel with an Applet in the Secure Element.
* This channel can be the basic channel or a logical channel.
*
* @see <a href="http://simalliance.org">SIMalliance Open Mobile API v3.0</a>
*/
public class Session {
private final Object mLock = new Object();
private final SEService mService;
private final Reader mReader;
private final ISecureElementSession mSession;
private static final String TAG = "OMAPI.Session";
Session(SEService service, ISecureElementSession session, Reader reader) {
if (service == null || reader == null || session == null) {
throw new IllegalArgumentException("Parameters cannot be null");
}
mService = service;
mReader = reader;
mSession = session;
}
/**
* Get the reader that provides this session.
*
* @return The Reader object.
*/
public @NonNull Reader getReader() {
return mReader;
}
/**
* Get the Answer to Reset of this Secure Element. <br>
* The returned byte array can be null if the ATR for this Secure Element is
* not available.
*
* @throws IllegalStateException if there was an error connecting to SE or
* if the service was not connected.
* @return the ATR as a byte array or null.
*/
public @Nullable byte[] getATR() {
if (!mService.isConnected()) {
throw new IllegalStateException("service not connected to system");
}
try {
return mSession.getAtr();
} catch (RemoteException e) {
throw new IllegalStateException(e.getMessage());
}
}
/**
* Close the connection with the Secure Element. This will close any
* channels opened by this application with this Secure Element.
*/
public void close() {
if (!mService.isConnected()) {
Log.e(TAG, "service not connected to system");
return;
}
synchronized (mLock) {
try {
mSession.close();
} catch (RemoteException e) {
Log.e(TAG, "Error closing session", e);
}
}
}
/**
* Tells if this session is closed.
*
* @return <code>true</code> if the session is closed, false otherwise.
*/
public boolean isClosed() {
try {
return mSession.isClosed();
} catch (RemoteException e) {
// If there was an error here, then the session is considered close
return true;
}
}
/**
* Close any channel opened on this session.
*/
public void closeChannels() {
if (!mService.isConnected()) {
Log.e(TAG, "service not connected to system");
return;
}
synchronized (mLock) {
try {
mSession.closeChannels();
} catch (RemoteException e) {
Log.e(TAG, "Error closing channels", e);
}
}
}
/**
* Get an access to the basic channel, as defined in the ISO/IEC 7816-4 specification (the
* one that has number 0). The obtained object is an instance of the Channel class.
* If the AID is null, it means no Applet is to be selected on this channel and the default
* Applet is used. If the AID is defined then the corresponding Applet is selected.
* Once this channel has been opened by a device application, it is considered as "locked"
* by this device application, and other calls to this method will return null, until the
* channel is closed. Some Secure Elements (like the UICC) might always keep the basic channel
* locked (i.e. return null to applications), to prevent access to the basic channel, while
* some other might return a channel object implementing some kind of filtering on the
* commands, restricting the set of accepted command to a smaller set.
* It is recommended for the UICC to reject the opening of the basic channel to a specific
* applet, by always answering null to such a request.
* For other Secure Elements, the recommendation is to accept opening the basic channel
* on the default applet until another applet is selected on the basic channel. As there is no
* other way than a reset to select again the default applet, the implementation of the
* transport API should guarantee that the openBasicChannel(null) command will return
* null until a reset occurs.
* With previous release (V2.05) it was not possible to set P2 value, this value was always
* set to '00'.Except for specific needs it is recommended to keep P2 to '00'. It is
* recommended that the device allows all values for P2, however only the following values
* are mandatory: '00', '04', '08', '0C'(as defined in [2])
* The implementation of the underlying SELECT command within this method shall be
* based on ISO 7816-4 with following options:
* <ul>
* <li>CLA = '00'</li>
* <li>INS = 'A4'</li>
* <li>P1 = '04' (Select by DF name/application identifier)</li>
* </ul>
*
* The select response data can be retrieved with byte[] getSelectResponse().
* The API shall handle received status word as follow. If the status word indicates that the
* Secure Element was able to open a channel (e.g. status word '90 00' or status words
* referencing a warning in ISO-7816-4: '62 XX' or '63 XX') the API shall keep the
* channel opened and the next getSelectResponse() shall return the received status
* word.
* Other received status codes indicating that the Secure Element was able not to open a
* channel shall be considered as an error and the corresponding channel shall not be
* opened.
* The function without P2 as parameter is provided for backwards compatibility and will
* fall back to a select command with P2='00'.
*
* @param aid the AID of the Applet to be selected on this channel, as a
* byte array, or null if no Applet is to be selected.
* @param p2 the P2 parameter of the SELECT APDU executed on this channel.
* @throws IOException if there is a communication problem to the reader or
* the Secure Element.
* @throws IllegalStateException if the Secure Element session is used after
* being closed.
* @throws IllegalArgumentException if the aid's length is not within 5 to
* 16 (inclusive).
* @throws SecurityException if the calling application cannot be granted
* access to this AID or the default Applet on this
* session.
* @throws NoSuchElementException if the AID on the Secure Element is not available or cannot be
* selected.
* @throws UnsupportedOperationException if the given P2 parameter is not
* supported by the device
* @return an instance of Channel if available or null.
*/
public @Nullable Channel openBasicChannel(byte[] aid, byte p2) throws IOException {
if (!mService.isConnected()) {
throw new IllegalStateException("service not connected to system");
}
synchronized (mLock) {
try {
ISecureElementChannel channel = mSession.openBasicChannel(aid, p2,
mReader.getSEService().getListener());
if (channel == null) {
return null;
}
return new Channel(mService, this, channel);
} catch (RemoteException e) {
throw new IOException(e.getMessage());
}
}
}
/**
* This method is provided to ease the development of mobile application and for compliancy
* with existing applications.
* This method is equivalent to openBasicChannel(aid, P2=0x00)
*
* @param aid the AID of the Applet to be selected on this channel, as a
* byte array, or null if no Applet is to be selected.
* @throws IOException if there is a communication problem to the reader or
* the Secure Element.
* @throws IllegalStateException if the Secure Element session is used after
* being closed.
* @throws IllegalArgumentException if the aid's length is not within 5 to
* 16 (inclusive).
* @throws SecurityException if the calling application cannot be granted
* access to this AID or the default Applet on this
* session.
* @throws NoSuchElementException if the AID on the Secure Element is not available or cannot be
* selected.
* @throws UnsupportedOperationException if the given P2 parameter is not
* supported by the device
* @return an instance of Channel if available or null.
*/
public @Nullable Channel openBasicChannel(byte[] aid) throws IOException {
return openBasicChannel(aid, (byte) 0x00);
}
/**
* Open a logical channel with the Secure Element, selecting the Applet represented by
* the given AID. If the AID is null, which means no Applet is to be selected on this
* channel, the default Applet is used. It's up to the Secure Element to choose which
* logical channel will be used.
* With previous release (V2.05) it was not possible to set P2 value, this value was always
* set to '00'.Except for specific needs it is recommended to keep P2 to '00'. It is
* recommended that the device allows all values for P2, however only the following values
* are mandatory: '00', '04', '08', '0C'(as defined in [2])
* The implementation of the underlying SELECT command within this method shall be
* based on ISO 7816-4 with following options:
*
* <ul>
* <li>CLA = '01' to '03', '40 to 4F'</li>
* <li>INS = 'A4'</li>
* <li>P1 = '04' (Select by DF name/application identifier)</li>
* </ul>
*
* The select response data can be retrieved with byte[] getSelectResponse().
* The API shall handle received status word as follow. If the status word indicates that the
* Secure Element was able to open a channel (e.g. status word '90 00' or status words
* referencing a warning in ISO-7816-4: '62 XX' or '63 XX') the API shall keep the
* channel opened and the next getSelectResponse() shall return the received status
* word.
* Other received status codes indicating that the Secure Element was able not to open a
* channel shall be considered as an error and the corresponding channel shall not be
* opened.
* In case of UICC it is recommended for the API to reject the opening of the logical
* channel without a specific AID, by always answering null to such a request.
* The function without P2 as parameter is provided for backwards compatibility and will
* fall back to a select command with P2=00.
*
* @param aid the AID of the Applet to be selected on this channel, as a
* byte array.
* @param p2 the P2 parameter of the SELECT APDU executed on this channel.
* @throws IOException if there is a communication problem to the reader or
* the Secure Element.
* @throws IllegalStateException if the Secure Element is used after being
* closed.
* @throws IllegalArgumentException if the aid's length is not within 5 to
* 16 (inclusive).
* @throws SecurityException if the calling application cannot be granted
* access to this AID or the default Applet on this
* session.
* @throws NoSuchElementException if the AID on the Secure Element is not
* available or cannot be selected or a logical channel is already
* open to a non-multiselectable Applet.
* @throws UnsupportedOperationException if the given P2 parameter is not
* supported by the device.
* @return an instance of Channel. Null if the Secure Element is unable to
* provide a new logical channel.
*/
public @Nullable Channel openLogicalChannel(byte[] aid, byte p2) throws IOException {
if ((mReader.getName().startsWith("SIM")) && (aid == null)) {
Log.e(TAG, "NULL AID not supported on " + mReader.getName());
return null;
}
if (!mService.isConnected()) {
throw new IllegalStateException("service not connected to system");
}
synchronized (mLock) {
try {
ISecureElementChannel channel = mSession.openLogicalChannel(
aid,
p2,
mReader.getSEService().getListener());
if (channel == null) {
return null;
}
return new Channel(mService, this, channel);
} catch (RemoteException e) {
throw new IOException(e.getMessage());
}
}
}
/**
* This method is provided to ease the development of mobile application and for compliancy
* with existing applications.
* This method is equivalent to openLogicalChannel(aid, P2=0x00)
*
* @param aid the AID of the Applet to be selected on this channel, as a
* byte array.
* @throws IOException if there is a communication problem to the reader or
* the Secure Element.
* @throws IllegalStateException if the Secure Element is used after being
* closed.
* @throws IllegalArgumentException if the aid's length is not within 5 to
* 16 (inclusive).
* @throws SecurityException if the calling application cannot be granted
* access to this AID or the default Applet on this
* session.
* @throws NoSuchElementException if the AID on the Secure Element is not
* available or cannot be selected or a logical channel is already
* open to a non-multiselectable Applet.
* @throws UnsupportedOperationException if the given P2 parameter is not
* supported by the device.
* @return an instance of Channel. Null if the Secure Element is unable to
* provide a new logical channel.
*/
public @Nullable Channel openLogicalChannel(byte[] aid) throws IOException {
return openLogicalChannel(aid, (byte) 0x00);
}
}