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 java.io.IOException;
20
21import android.annotation.SdkConstant;
22import android.annotation.SdkConstant.SdkConstantType;
23import android.content.Context;
24import android.nfc.INfcAdapterExtras;
25import android.nfc.NfcAdapter;
26import android.os.Binder;
27import android.os.Bundle;
28import android.os.IBinder;
29import android.os.RemoteException;
30
31public class NfcExecutionEnvironment {
32    private final NfcAdapterExtras mExtras;
33
34    /**
35     * Broadcast Action: An ISO-DEP AID was selected.
36     *
37     * <p>This happens as the result of a 'SELECT AID' command from an
38     * external NFC reader/writer.
39     *
40     * <p>Always contains the extra field {@link #EXTRA_AID}
41     *
42     * <p class="note">
43     * Requires the {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} permission
44     * to receive.
45     */
46    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
47    public static final String ACTION_AID_SELECTED =
48        "com.android.nfc_extras.action.AID_SELECTED";
49
50    /**
51     * Mandatory byte array extra field in {@link #ACTION_AID_SELECTED}.
52     *
53     * <p>Contains the AID selected.
54     * @hide
55     */
56    public static final String EXTRA_AID = "com.android.nfc_extras.extra.AID";
57
58    /**
59     * Broadcast action: A filtered APDU was received.
60     *
61     * <p>This happens when an APDU of interest was matched by the Nfc adapter,
62     * for instance as the result of matching an externally-configured filter.
63     *
64     * <p>The filter configuration mechanism is not currently defined.
65     *
66     * <p>Always contains the extra field {@link EXTRA_APDU_BYTES}.
67     *
68     * @hide
69     */
70    public static final String ACTION_APDU_RECEIVED =
71        "com.android.nfc_extras.action.APDU_RECEIVED";
72
73    /**
74     * Mandatory byte array extra field in {@link #ACTION_APDU_RECEIVED}.
75     *
76     * <p>Contains the bytes of the received APDU.
77     *
78     * @hide
79     */
80    public static final String EXTRA_APDU_BYTES =
81        "com.android.nfc_extras.extra.APDU_BYTES";
82
83    /**
84     * Broadcast action: An EMV card removal event was detected.
85     *
86     * @hide
87     */
88    public static final String ACTION_EMV_CARD_REMOVAL =
89        "com.android.nfc_extras.action.EMV_CARD_REMOVAL";
90
91    /**
92     * Broadcast action: An adapter implementing MIFARE Classic via card
93     * emulation detected that a block has been accessed.
94     *
95     * <p>This may only be issued for the first block that the reader
96     * authenticates to.
97     *
98     * <p>May contain the extra field {@link #EXTRA_MIFARE_BLOCK}.
99     *
100     * @hide
101     */
102    public static final String ACTION_MIFARE_ACCESS_DETECTED =
103        "com.android.nfc_extras.action.MIFARE_ACCESS_DETECTED";
104
105    /**
106     * Optional integer extra field in {@link #ACTION_MIFARE_ACCESS_DETECTED}.
107     *
108     * <p>Provides the block number being accessed.  If not set, the block
109     * number being accessed is unknown.
110     *
111     * @hide
112     */
113    public static final String EXTRA_MIFARE_BLOCK =
114        "com.android.nfc_extras.extra.MIFARE_BLOCK";
115
116    NfcExecutionEnvironment(NfcAdapterExtras extras) {
117        mExtras = extras;
118    }
119
120    /**
121     * Open the NFC Execution Environment on its contact interface.
122     *
123     * <p>Only one process may open the secure element at a time. If it is
124     * already open, an {@link IOException} is thrown.
125     *
126     * <p>All other NFC functionality is disabled while the NFC-EE is open
127     * on its contact interface, so make sure to call {@link #close} once complete.
128     *
129     * <p class="note">
130     * Requires the {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} permission.
131     *
132     * @throws IOException if the NFC-EE is already open, or some other error occurs
133     */
134    public void open() throws IOException {
135        try {
136            Bundle b = mExtras.getService().open(new Binder());
137            throwBundle(b);
138        } catch (RemoteException e) {
139            mExtras.attemptDeadServiceRecovery(e);
140            throw new IOException("NFC Service was dead, try again");
141        }
142    }
143
144    /**
145     * Close the NFC Execution Environment on its contact interface.
146     *
147     * <p class="note">
148     * Requires the {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} permission.
149     *
150     * @throws IOException if the NFC-EE is already open, or some other error occurs
151     */
152    public void close() throws IOException {
153        try {
154            throwBundle(mExtras.getService().close());
155        } catch (RemoteException e) {
156            mExtras.attemptDeadServiceRecovery(e);
157            throw new IOException("NFC Service was dead");
158        }
159    }
160
161    /**
162     * Send raw commands to the NFC-EE and receive the response.
163     *
164     * <p class="note">
165     * Requires the {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} permission.
166     *
167     * @throws IOException if the NFC-EE is not open, or some other error occurs
168     */
169    public byte[] transceive(byte[] in) throws IOException {
170        Bundle b;
171        try {
172            b = mExtras.getService().transceive(in);
173        } catch (RemoteException e) {
174            mExtras.attemptDeadServiceRecovery(e);
175            throw new IOException("NFC Service was dead, need to re-open");
176        }
177        throwBundle(b);
178        return b.getByteArray("out");
179    }
180
181    private static void throwBundle(Bundle b) throws IOException {
182        if (b.getInt("e") == -1) {
183            throw new IOException(b.getString("m"));
184        }
185    }
186}
187