1/*
2 * Copyright (C) 2011 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
17package com.android.nfc_extras;
18
19import android.annotation.SdkConstant;
20import android.annotation.SdkConstant.SdkConstantType;
21import android.os.Binder;
22import android.os.Bundle;
23import android.os.RemoteException;
24
25import java.io.IOException;
26
27public class NfcExecutionEnvironment {
28    private final NfcAdapterExtras mExtras;
29    private final Binder mToken;
30
31    // Exception types that can be thrown by NfcService
32    // 1:1 mapped to EE_ERROR_ types in NfcService
33    private static final int EE_ERROR_IO = -1;
34    private static final int EE_ERROR_ALREADY_OPEN = -2;
35    private static final int EE_ERROR_INIT = -3;
36    private static final int EE_ERROR_LISTEN_MODE = -4;
37    private static final int EE_ERROR_EXT_FIELD = -5;
38    private static final int EE_ERROR_NFC_DISABLED = -6;
39
40    /**
41     * Broadcast Action: An ISO-DEP AID was selected.
42     *
43     * <p>This happens as the result of a 'SELECT AID' command from an
44     * external NFC reader/writer.
45     *
46     * <p>Always contains the extra field {@link #EXTRA_AID}
47     *
48     * <p class="note">
49     * Requires the {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} permission
50     * to receive.
51     */
52    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
53    public static final String ACTION_AID_SELECTED =
54        "com.android.nfc_extras.action.AID_SELECTED";
55
56    /**
57     * Mandatory byte array extra field in {@link #ACTION_AID_SELECTED}.
58     *
59     * <p>Contains the AID selected.
60     * @hide
61     */
62    public static final String EXTRA_AID = "com.android.nfc_extras.extra.AID";
63
64    /**
65     * Broadcast action: A filtered APDU was received.
66     *
67     * <p>This happens when an APDU of interest was matched by the Nfc adapter,
68     * for instance as the result of matching an externally-configured filter.
69     *
70     * <p>The filter configuration mechanism is not currently defined.
71     *
72     * <p>Always contains the extra field {@link EXTRA_APDU_BYTES}.
73     *
74     * @hide
75     */
76    public static final String ACTION_APDU_RECEIVED =
77        "com.android.nfc_extras.action.APDU_RECEIVED";
78
79    /**
80     * Mandatory byte array extra field in {@link #ACTION_APDU_RECEIVED}.
81     *
82     * <p>Contains the bytes of the received APDU.
83     *
84     * @hide
85     */
86    public static final String EXTRA_APDU_BYTES =
87        "com.android.nfc_extras.extra.APDU_BYTES";
88
89    /**
90     * Broadcast action: An EMV card removal event was detected.
91     *
92     * @hide
93     */
94    public static final String ACTION_EMV_CARD_REMOVAL =
95        "com.android.nfc_extras.action.EMV_CARD_REMOVAL";
96
97    /**
98     * Broadcast action: An adapter implementing MIFARE Classic via card
99     * emulation detected that a block has been accessed.
100     *
101     * <p>This may only be issued for the first block that the reader
102     * authenticates to.
103     *
104     * <p>May contain the extra field {@link #EXTRA_MIFARE_BLOCK}.
105     *
106     * @hide
107     */
108    public static final String ACTION_MIFARE_ACCESS_DETECTED =
109        "com.android.nfc_extras.action.MIFARE_ACCESS_DETECTED";
110
111    /**
112     * Optional integer extra field in {@link #ACTION_MIFARE_ACCESS_DETECTED}.
113     *
114     * <p>Provides the block number being accessed.  If not set, the block
115     * number being accessed is unknown.
116     *
117     * @hide
118     */
119    public static final String EXTRA_MIFARE_BLOCK =
120        "com.android.nfc_extras.extra.MIFARE_BLOCK";
121
122    NfcExecutionEnvironment(NfcAdapterExtras extras) {
123        mExtras = extras;
124        mToken = new Binder();
125    }
126
127    /**
128     * Open the NFC Execution Environment on its contact interface.
129     *
130     * <p>Opening a channel to the the secure element may fail
131     * for a number of reasons:
132     * <ul>
133     * <li>NFC must be enabled for the connection to the SE to be opened.
134     * If it is disabled at the time of this call, an {@link EeNfcDisabledException}
135     * is thrown.
136     *
137     * <li>Only one process may open the secure element at a time. Additionally,
138     * this method is not reentrant. If the secure element is already opened,
139     * either by this process or by a different process, an {@link EeAlreadyOpenException}
140     * is thrown.
141     *
142     * <li>If the connection to the secure element could not be initialized,
143     * an {@link EeInitializationException} is thrown.
144     *
145     * <li>If the secure element or the NFC controller is activated in listen
146     * mode - that is, it is talking over the contactless interface - an
147     * {@link EeListenModeException} is thrown.
148     *
149     * <li>If the NFC controller is in a field powered by a remote device,
150     * such as a payment terminal, an {@link EeExternalFieldException} is
151     * thrown.
152     * </ul>
153     * <p>All other NFC functionality is disabled while the NFC-EE is open
154     * on its contact interface, so make sure to call {@link #close} once complete.
155     *
156     * <p class="note">
157     * Requires the {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} permission.
158     *
159     * @throws EeAlreadyOpenException if the NFC-EE is already open
160     * @throws EeNfcDisabledException if NFC is disabled
161     * @throws EeInitializationException if the Secure Element could not be initialized
162     * @throws EeListenModeException if the NFCC or Secure Element is activated in listen mode
163     * @throws EeExternalFieldException if the NFCC is in the presence of a remote-powered field
164     * @throws EeIoException if an unknown error occurs
165     */
166    public void open() throws EeIOException {
167        try {
168            Bundle b = mExtras.getService().open(mExtras.mPackageName, mToken);
169            throwBundle(b);
170        } catch (RemoteException e) {
171            mExtras.attemptDeadServiceRecovery(e);
172            throw new EeIOException("NFC Service was dead, try again");
173        }
174    }
175
176    /**
177     * Close the NFC Execution Environment on its contact interface.
178     *
179     * <p class="note">
180     * Requires the {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} permission.
181     *
182     * @throws IOException if the NFC-EE is already open, or some other error occurs
183     */
184    public void close() throws IOException {
185        try {
186            throwBundle(mExtras.getService().close(mExtras.mPackageName, mToken));
187        } catch (RemoteException e) {
188            mExtras.attemptDeadServiceRecovery(e);
189            throw new IOException("NFC Service was dead");
190        }
191    }
192
193    /**
194     * Send raw commands to the NFC-EE and receive the response.
195     *
196     * <p class="note">
197     * Requires the {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} permission.
198     *
199     * @throws IOException if the NFC-EE is not open, or some other error occurs
200     */
201    public byte[] transceive(byte[] in) throws IOException {
202        Bundle b;
203        try {
204            b = mExtras.getService().transceive(mExtras.mPackageName, in);
205        } catch (RemoteException e) {
206            mExtras.attemptDeadServiceRecovery(e);
207            throw new IOException("NFC Service was dead, need to re-open");
208        }
209        throwBundle(b);
210        return b.getByteArray("out");
211    }
212
213    private static void throwBundle(Bundle b) throws EeIOException {
214        switch (b.getInt("e")) {
215            case EE_ERROR_NFC_DISABLED:
216                throw new EeNfcDisabledException(b.getString("m"));
217            case EE_ERROR_IO:
218                throw new EeIOException(b.getString("m"));
219            case EE_ERROR_INIT:
220                throw new EeInitializationException(b.getString("m"));
221            case EE_ERROR_EXT_FIELD:
222                throw new EeExternalFieldException(b.getString("m"));
223            case EE_ERROR_LISTEN_MODE:
224                throw new EeListenModeException(b.getString("m"));
225            case EE_ERROR_ALREADY_OPEN:
226                throw new EeAlreadyOpenException(b.getString("m"));
227        }
228    }
229}
230