NfcAdapter.java revision 4934d3361edadb761d9c65fe8c2bf5b8d01e8121
1/*
2 * Copyright (C) 2010 The Android Open Source Project Licensed under the Apache
3 * License, Version 2.0 (the "License"); you may not use this file except in
4 * compliance with the License. You may obtain a copy of the License at
5 * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
6 * or agreed to in writing, software distributed under the License is
7 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
8 * KIND, either express or implied. See the License for the specific language
9 * governing permissions and limitations under the License.
10 */
11
12package android.nfc;
13
14import java.lang.UnsupportedOperationException;
15
16import android.annotation.SdkConstant;
17import android.annotation.SdkConstant.SdkConstantType;
18import android.app.ActivityThread;
19import android.content.Context;
20import android.content.pm.IPackageManager;
21import android.content.pm.PackageManager;
22import android.nfc.INfcAdapter;
23import android.os.IBinder;
24import android.os.RemoteException;
25import android.os.ServiceManager;
26import android.util.Log;
27
28/**
29 * Represents the device's local NFC adapter.
30 * <p>
31 * Use the static {@link #getDefaultAdapter} method to get the default NFC
32 * Adapter for this Android device. Most Android devices will have only one NFC
33 * Adapter, and {@link #getDefaultAdapter} returns the singleton object.
34 */
35public final class NfcAdapter {
36    /**
37     * Intent to start an activity when a tag is discovered.
38     */
39    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
40    public static final String ACTION_TAG_DISCOVERED = "android.nfc.action.TAG_DISCOVERED";
41
42    /**
43     * Mandatory Tag extra for the ACTION_TAG intents.
44     * @hide
45     */
46    public static final String EXTRA_TAG = "android.nfc.extra.TAG";
47
48    /**
49     * Optional NdefMessage[] extra for the ACTION_TAG intents.
50     */
51    public static final String EXTRA_NDEF_MESSAGES = "android.nfc.extra.NDEF_MESSAGES";
52
53    /**
54     * Optional byte[] extra for the tag identifier.
55     */
56    public static final String EXTRA_ID = "android.nfc.extra.ID";
57
58    /**
59     * Broadcast Action: a transaction with a secure element has been detected.
60     * <p>
61     * Always contains the extra field
62     * {@link android.nfc.NfcAdapter#EXTRA_AID}
63     * @hide
64     */
65    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
66    public static final String ACTION_TRANSACTION_DETECTED =
67            "android.nfc.action.TRANSACTION_DETECTED";
68
69    /**
70     * Broadcast Action: an adapter's state changed between enabled and disabled.
71     *
72     * The new value is stored in the extra EXTRA_NEW_BOOLEAN_STATE and just contains
73     * whether it's enabled or disabled, not including any information about whether it's
74     * actively enabling or disabling.
75     *
76     * @hide
77     */
78    public static final String ACTION_ADAPTER_STATE_CHANGE =
79            "android.nfc.action.ADAPTER_STATE_CHANGE";
80
81    /**
82     * The Intent extra for ACTION_ADAPTER_STATE_CHANGE, saying what the new state is.
83     *
84     * @hide
85     */
86    public static final String EXTRA_NEW_BOOLEAN_STATE = "android.nfc.isEnabled";
87
88    /**
89     * Mandatory byte array extra field in
90     * {@link android.nfc.NfcAdapter#ACTION_TRANSACTION_DETECTED}.
91     * <p>
92     * Contains the AID of the applet involved in the transaction.
93     * @hide
94     */
95    public static final String EXTRA_AID = "android.nfc.extra.AID";
96
97    /**
98     * LLCP link status: The LLCP link is activated.
99     * @hide
100     */
101    public static final int LLCP_LINK_STATE_ACTIVATED = 0;
102
103    /**
104     * LLCP link status: The LLCP link is deactivated.
105     * @hide
106     */
107    public static final int LLCP_LINK_STATE_DEACTIVATED = 1;
108
109    /**
110     * Broadcast Action: the LLCP link state changed.
111     * <p>
112     * Always contains the extra field
113     * {@link android.nfc.NfcAdapter#EXTRA_LLCP_LINK_STATE_CHANGED}.
114     * @hide
115     */
116    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
117    public static final String ACTION_LLCP_LINK_STATE_CHANGED =
118            "android.nfc.action.LLCP_LINK_STATE_CHANGED";
119
120    /**
121     * Used as int extra field in
122     * {@link android.nfc.NfcAdapter#ACTION_LLCP_LINK_STATE_CHANGED}.
123     * <p>
124     * It contains the new state of the LLCP link.
125     * @hide
126     */
127    public static final String EXTRA_LLCP_LINK_STATE_CHANGED = "android.nfc.extra.LLCP_LINK_STATE";
128
129    /**
130     * Tag Reader Discovery mode
131     * @hide
132     */
133    private static final int DISCOVERY_MODE_TAG_READER = 0;
134
135    /**
136     * NFC-IP1 Peer-to-Peer mode Enables the manager to act as a peer in an
137     * NFC-IP1 communication. Implementations should not assume that the
138     * controller will end up behaving as an NFC-IP1 target or initiator and
139     * should handle both cases, depending on the type of the remote peer type.
140     * @hide
141     */
142    private static final int DISCOVERY_MODE_NFCIP1 = 1;
143
144    /**
145     * Card Emulation mode Enables the manager to act as an NFC tag. Provided
146     * that a Secure Element (an UICC for instance) is connected to the NFC
147     * controller through its SWP interface, it can be exposed to the outside
148     * NFC world and be addressed by external readers the same way they would
149     * with a tag.
150     * <p>
151     * Which Secure Element is exposed is implementation-dependent.
152     *
153     * @hide
154     */
155    private static final int DISCOVERY_MODE_CARD_EMULATION = 2;
156
157    private static final String TAG = "NFC";
158
159    // Both guarded by NfcAdapter.class:
160    private static boolean sIsInitialized = false;
161    private static NfcAdapter sAdapter;
162
163    // Final after construction, except for attemptDeadServiceRecovery()
164    // when NFC crashes.
165    // Not locked - we accept a best effort attempt when NFC crashes.
166    /*package*/ INfcAdapter mService;
167
168    private NfcAdapter(INfcAdapter service) {
169        mService = service;
170    }
171
172    /**
173     * Helper to check if this device has FEATURE_NFC, but without using
174     * a context.
175     * Equivalent to
176     * context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)
177     */
178    private static boolean hasNfcFeature() {
179        IPackageManager pm = ActivityThread.getPackageManager();
180        if (pm == null) {
181            Log.e(TAG, "Cannot get package manager, assuming no NFC feature");
182            return false;
183        }
184        try {
185            return pm.hasSystemFeature(PackageManager.FEATURE_NFC);
186        } catch (RemoteException e) {
187            Log.e(TAG, "Package manager query failed, assuming no NFC feature", e);
188            return false;
189        }
190    }
191
192    /** get handle to NFC service interface */
193    private static synchronized INfcAdapter getServiceInterface() {
194        /* get a handle to NFC service */
195        IBinder b = ServiceManager.getService("nfc");
196        if (b == null) {
197            return null;
198        }
199        return INfcAdapter.Stub.asInterface(b);
200    }
201
202    /**
203     * Get a handle to the default NFC Adapter on this Android device.
204     * <p>
205     * Most Android devices will only have one NFC Adapter (NFC Controller).
206     *
207     * @return the default NFC adapter, or null if no NFC adapter exists
208     */
209    public static NfcAdapter getDefaultAdapter() {
210        synchronized (NfcAdapter.class) {
211            if (sIsInitialized) {
212                return sAdapter;
213            }
214            sIsInitialized = true;
215
216            /* is this device meant to have NFC */
217            if (!hasNfcFeature()) {
218                Log.v(TAG, "this device does not have NFC support");
219                return null;
220            }
221
222            INfcAdapter service = getServiceInterface();
223            if (service == null) {
224                Log.e(TAG, "could not retrieve NFC service");
225                return null;
226            }
227
228            sAdapter = new NfcAdapter(service);
229            return sAdapter;
230        }
231    }
232
233    /** NFC service dead - attempt best effort recovery */
234    /*package*/ void attemptDeadServiceRecovery(Exception e) {
235        Log.e(TAG, "NFC service dead - attempting to recover", e);
236        INfcAdapter service = getServiceInterface();
237        if (service == null) {
238            Log.e(TAG, "could not retrieve NFC service during service recovery");
239            return;
240        }
241        /* assigning to mService is not thread-safe, but this is best-effort code
242         * and on a well-behaved system should never happen */
243        mService = service;
244        return;
245    }
246
247    /**
248     * Return true if this NFC Adapter has any features enabled.
249     * <p>
250     * If this method returns false, then applications should request the user
251     * turn on NFC tag discovery in Settings.
252     * <p>
253     * If this method returns false, the NFC hardware is guaranteed not to
254     * perform or respond to any NFC communication.
255     *
256     * @return true if this NFC Adapter is enabled to discover new tags
257     */
258    public boolean isEnabled() {
259        try {
260            return mService.isEnabled();
261        } catch (RemoteException e) {
262            attemptDeadServiceRecovery(e);
263            return false;
264        }
265    }
266
267    /**
268     * Enable NFC hardware.
269     * <p>
270     * NOTE: may block for ~second or more.  Poor API.  Avoid
271     * calling from the UI thread.
272     *
273     * @hide
274     */
275    public boolean enable() {
276        try {
277            return mService.enable();
278        } catch (RemoteException e) {
279            attemptDeadServiceRecovery(e);
280            return false;
281        }
282    }
283
284    /**
285     * Disable NFC hardware.
286     * No NFC features will work after this call, and the hardware
287     * will not perform or respond to any NFC communication.
288     * <p>
289     * NOTE: may block for ~second or more.  Poor API.  Avoid
290     * calling from the UI thread.
291     *
292     * @hide
293     */
294    public boolean disable() {
295        try {
296            return mService.disable();
297        } catch (RemoteException e) {
298            attemptDeadServiceRecovery(e);
299            return false;
300        }
301    }
302
303    /**
304     * Set the NDEF Message that this NFC adapter should appear as to Tag
305     * readers.
306     * <p>
307     * Any Tag reader can read the contents of the local tag when it is in
308     * proximity, without any further user confirmation.
309     * <p>
310     * The implementation of this method must either
311     * <ul>
312     * <li>act as a passive tag containing this NDEF message
313     * <li>provide the NDEF message on over LLCP to peer NFC adapters
314     * </ul>
315     * The NDEF message is preserved across reboot.
316     * <p>Requires {@link android.Manifest.permission#NFC} permission.
317     *
318     * @param message NDEF message to make public
319     * @hide
320     */
321    public void setLocalNdefMessage(NdefMessage message) {
322        try {
323            mService.localSet(message);
324        } catch (RemoteException e) {
325            attemptDeadServiceRecovery(e);
326        }
327    }
328
329    /**
330     * Get the NDEF Message that this adapter appears as to Tag readers.
331     * <p>Requires {@link android.Manifest.permission#NFC} permission.
332     *
333     * @return NDEF Message that is publicly readable
334     * @hide
335     */
336    public NdefMessage getLocalNdefMessage() {
337        try {
338            return mService.localGet();
339        } catch (RemoteException e) {
340            attemptDeadServiceRecovery(e);
341            return null;
342        }
343    }
344
345    /**
346     * Create a raw tag connection to the default Target
347     * <p>Requires {@link android.Manifest.permission#NFC} permission.
348     * @hide
349     */
350    public RawTagConnection createRawTagConnection(Tag tag) {
351        if (tag.mServiceHandle == 0) {
352            throw new IllegalArgumentException("mock tag cannot be used for connections");
353        }
354        try {
355            return new RawTagConnection(this, tag);
356        } catch (RemoteException e) {
357            attemptDeadServiceRecovery(e);
358            return null;
359        }
360    }
361
362    /**
363     * Create a raw tag connection to the specified Target
364     * <p>Requires {@link android.Manifest.permission#NFC} permission.
365     * @hide
366     */
367    public RawTagConnection createRawTagConnection(Tag tag, String target) {
368        if (tag.mServiceHandle == 0) {
369            throw new IllegalArgumentException("mock tag cannot be used for connections");
370        }
371        try {
372            return new RawTagConnection(this, tag, target);
373        } catch (RemoteException e) {
374            attemptDeadServiceRecovery(e);
375            return null;
376        }
377    }
378
379    /**
380     * Create an NDEF tag connection to the default Target
381     * <p>Requires {@link android.Manifest.permission#NFC} permission.
382     * @hide
383     */
384    public NdefTagConnection createNdefTagConnection(NdefTag tag) {
385        if (tag.mServiceHandle == 0) {
386            throw new IllegalArgumentException("mock tag cannot be used for connections");
387        }
388        try {
389            return new NdefTagConnection(this, tag);
390        } catch (RemoteException e) {
391            attemptDeadServiceRecovery(e);
392            return null;
393        }
394    }
395
396    /**
397     * Create an NDEF tag connection to the specified Target
398     * <p>Requires {@link android.Manifest.permission#NFC} permission.
399     * @hide
400     */
401    public NdefTagConnection createNdefTagConnection(NdefTag tag, String target) {
402        if (tag.mServiceHandle == 0) {
403            throw new IllegalArgumentException("mock tag cannot be used for connections");
404        }
405        try {
406            return new NdefTagConnection(this, tag, target);
407        } catch (RemoteException e) {
408            attemptDeadServiceRecovery(e);
409            return null;
410        }
411    }
412}
413