NfcAdapter.java revision 200ca02fcaf2c24ea7716f65ed9a7bff8cddd975
1/*
2 * Copyright (C) 2010 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 android.nfc;
18
19import android.annotation.SdkConstant;
20import android.annotation.SdkConstant.SdkConstantType;
21import android.app.ActivityThread;
22import android.content.Context;
23import android.content.pm.IPackageManager;
24import android.content.pm.PackageManager;
25import android.nfc.technology.TagTechnology;
26import android.os.IBinder;
27import android.os.RemoteException;
28import android.os.ServiceManager;
29import android.util.Log;
30
31/**
32 * Represents the device's local NFC adapter.
33 * <p>
34 * Use the helper {@link #getDefaultAdapter(Context)} to get the default NFC
35 * adapter for this Android device.
36 */
37public final class NfcAdapter {
38    private static final String TAG = "NFC";
39
40    /**
41     * Intent to start an activity when a tag with NDEF payload is discovered.
42     * If the tag has and NDEF payload this intent is started before
43     * {@link #ACTION_TECHNOLOGY_DISCOVERED}.
44     *
45     * If any activities respond to this intent neither
46     * {@link #ACTION_TECHNOLOGY_DISCOVERED} or {@link #ACTION_TAG_DISCOVERED} will be started.
47     */
48    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
49    public static final String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED";
50
51    /**
52     * Intent to started when a tag is discovered. The data URI is formated as
53     * {@code vnd.android.nfc://tag/} with the path having a directory entry for each technology
54     * in the {@link Tag#getTechnologyList()} is ascending order.
55     *
56     * This intent is started after {@link #ACTION_NDEF_DISCOVERED} and before
57     * {@link #ACTION_TAG_DISCOVERED}
58     *
59     * If any activities respond to this intent {@link #ACTION_TAG_DISCOVERED} will not be started.
60     */
61    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
62    public static final String ACTION_TECHNOLOGY_DISCOVERED = "android.nfc.action.TECH_DISCOVERED";
63
64    /**
65     * Intent to start an activity when a tag is discovered.
66     */
67    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
68    public static final String ACTION_TAG_DISCOVERED = "android.nfc.action.TAG_DISCOVERED";
69
70    /**
71     * Broadcast to only the activity that handles ACTION_TAG_DISCOVERED
72     * @hide
73     */
74    public static final String ACTION_TAG_LEFT_FIELD = "android.nfc.action.TAG_LOST";
75
76    /**
77     * Mandatory Tag extra for the ACTION_TAG intents.
78     */
79    public static final String EXTRA_TAG = "android.nfc.extra.TAG";
80
81    /**
82     * Optional NdefMessage[] extra for the ACTION_TAG intents.
83     */
84    public static final String EXTRA_NDEF_MESSAGES = "android.nfc.extra.NDEF_MESSAGES";
85
86    /**
87     * Optional byte[] extra for the tag identifier.
88     */
89    public static final String EXTRA_ID = "android.nfc.extra.ID";
90
91    /**
92     * Broadcast Action: a transaction with a secure element has been detected.
93     * <p>
94     * Always contains the extra field
95     * {@link android.nfc.NfcAdapter#EXTRA_AID}
96     * @hide
97     */
98    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
99    public static final String ACTION_TRANSACTION_DETECTED =
100            "android.nfc.action.TRANSACTION_DETECTED";
101
102    /**
103     * Broadcast Action: an RF field ON has been detected.
104     * @hide
105     */
106    public static final String ACTION_RF_FIELD_ON_DETECTED =
107            "android.nfc.action.RF_FIELD_ON_DETECTED";
108
109    /**
110     * Broadcast Action: an RF Field OFF has been detected.
111     * @hide
112     */
113    public static final String ACTION_RF_FIELD_OFF_DETECTED =
114            "android.nfc.action.RF_FIELD_OFF_DETECTED";
115
116    /**
117     * Broadcast Action: an adapter's state changed between enabled and disabled.
118     *
119     * The new value is stored in the extra EXTRA_NEW_BOOLEAN_STATE and just contains
120     * whether it's enabled or disabled, not including any information about whether it's
121     * actively enabling or disabling.
122     *
123     * @hide
124     */
125    public static final String ACTION_ADAPTER_STATE_CHANGE =
126            "android.nfc.action.ADAPTER_STATE_CHANGE";
127
128    /**
129     * The Intent extra for ACTION_ADAPTER_STATE_CHANGE, saying what the new state is.
130     *
131     * @hide
132     */
133    public static final String EXTRA_NEW_BOOLEAN_STATE = "android.nfc.isEnabled";
134
135    /**
136     * Mandatory byte array extra field in
137     * {@link android.nfc.NfcAdapter#ACTION_TRANSACTION_DETECTED}.
138     * <p>
139     * Contains the AID of the applet involved in the transaction.
140     * @hide
141     */
142    public static final String EXTRA_AID = "android.nfc.extra.AID";
143
144    /**
145     * LLCP link status: The LLCP link is activated.
146     * @hide
147     */
148    public static final int LLCP_LINK_STATE_ACTIVATED = 0;
149
150    /**
151     * LLCP link status: The LLCP link is deactivated.
152     * @hide
153     */
154    public static final int LLCP_LINK_STATE_DEACTIVATED = 1;
155
156    /**
157     * Broadcast Action: the LLCP link state changed.
158     * <p>
159     * Always contains the extra field
160     * {@link android.nfc.NfcAdapter#EXTRA_LLCP_LINK_STATE_CHANGED}.
161     * @hide
162     */
163    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
164    public static final String ACTION_LLCP_LINK_STATE_CHANGED =
165            "android.nfc.action.LLCP_LINK_STATE_CHANGED";
166
167    /**
168     * Used as int extra field in
169     * {@link android.nfc.NfcAdapter#ACTION_LLCP_LINK_STATE_CHANGED}.
170     * <p>
171     * It contains the new state of the LLCP link.
172     * @hide
173     */
174    public static final String EXTRA_LLCP_LINK_STATE_CHANGED = "android.nfc.extra.LLCP_LINK_STATE";
175
176    /**
177     * Tag Reader Discovery mode
178     * @hide
179     */
180    private static final int DISCOVERY_MODE_TAG_READER = 0;
181
182    /**
183     * NFC-IP1 Peer-to-Peer mode Enables the manager to act as a peer in an
184     * NFC-IP1 communication. Implementations should not assume that the
185     * controller will end up behaving as an NFC-IP1 target or initiator and
186     * should handle both cases, depending on the type of the remote peer type.
187     * @hide
188     */
189    private static final int DISCOVERY_MODE_NFCIP1 = 1;
190
191    /**
192     * Card Emulation mode Enables the manager to act as an NFC tag. Provided
193     * that a Secure Element (an UICC for instance) is connected to the NFC
194     * controller through its SWP interface, it can be exposed to the outside
195     * NFC world and be addressed by external readers the same way they would
196     * with a tag.
197     * <p>
198     * Which Secure Element is exposed is implementation-dependent.
199     *
200     * @hide
201     */
202    private static final int DISCOVERY_MODE_CARD_EMULATION = 2;
203
204
205    // Guarded by NfcAdapter.class
206    private static boolean sIsInitialized = false;
207
208    // Final after first constructor, except for
209    // attemptDeadServiceRecovery() when NFC crashes - we accept a best effort
210    // recovery
211    private static INfcAdapter sService;
212    private static INfcTag sTagService;
213
214    private final Context mContext;
215
216    /**
217     * Helper to check if this device has FEATURE_NFC, but without using
218     * a context.
219     * Equivalent to
220     * context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)
221     */
222    private static boolean hasNfcFeature() {
223        IPackageManager pm = ActivityThread.getPackageManager();
224        if (pm == null) {
225            Log.e(TAG, "Cannot get package manager, assuming no NFC feature");
226            return false;
227        }
228        try {
229            return pm.hasSystemFeature(PackageManager.FEATURE_NFC);
230        } catch (RemoteException e) {
231            Log.e(TAG, "Package manager query failed, assuming no NFC feature", e);
232            return false;
233        }
234    }
235
236    private static synchronized INfcAdapter setupService() {
237        if (!sIsInitialized) {
238            sIsInitialized = true;
239
240            /* is this device meant to have NFC */
241            if (!hasNfcFeature()) {
242                Log.v(TAG, "this device does not have NFC support");
243                return null;
244            }
245
246            sService = getServiceInterface();
247            if (sService == null) {
248                Log.e(TAG, "could not retrieve NFC service");
249                return null;
250            }
251            try {
252                sTagService = sService.getNfcTagInterface();
253            } catch (RemoteException e) {
254                Log.e(TAG, "could not retrieve NFC Tag service");
255                return null;
256            }
257        }
258        return sService;
259    }
260
261    /** get handle to NFC service interface */
262    private static INfcAdapter getServiceInterface() {
263        /* get a handle to NFC service */
264        IBinder b = ServiceManager.getService("nfc");
265        if (b == null) {
266            return null;
267        }
268        return INfcAdapter.Stub.asInterface(b);
269    }
270
271    /**
272     * Helper to get the default NFC Adapter.
273     * <p>
274     * Most Android devices will only have one NFC Adapter (NFC Controller).
275     * <p>
276     * This helper is the equivalent of:
277     * <pre>{@code
278     * NfcManager manager = (NfcManager) context.getSystemService(Context.NFC_SERVICE);
279     * NfcAdapter adapter = manager.getDefaultAdapter();
280     * }</pre>
281     * @param context the calling application's context
282     *
283     * @return the default NFC adapter, or null if no NFC adapter exists
284     */
285    public static NfcAdapter getDefaultAdapter(Context context) {
286        /* use getSystemService() instead of just instantiating to take
287         * advantage of the context's cached NfcManager & NfcAdapter */
288        NfcManager manager = (NfcManager) context.getSystemService(Context.NFC_SERVICE);
289        return manager.getDefaultAdapter();
290    }
291
292    /**
293     * Get a handle to the default NFC Adapter on this Android device.
294     * <p>
295     * Most Android devices will only have one NFC Adapter (NFC Controller).
296     *
297     * @return the default NFC adapter, or null if no NFC adapter exists
298     * @deprecated use {@link #getDefaultAdapter(Context)}
299     */
300    @Deprecated
301    public static NfcAdapter getDefaultAdapter() {
302        Log.w(TAG, "WARNING: NfcAdapter.getDefaultAdapter() is deprecated, use " +
303                "NfcAdapter.getDefaultAdapter(Context) instead", new Exception());
304        return new NfcAdapter(null);
305    }
306
307    /*package*/ NfcAdapter(Context context) {
308        if (setupService() == null) {
309            throw new UnsupportedOperationException();
310        }
311        mContext = context;
312    }
313
314    /**
315     * Returns the binder interface to the service.
316     * @hide
317     */
318    public INfcAdapter getService() {
319        return sService;
320    }
321
322    /**
323     * Returns the binder interface to the tag service.
324     * @hide
325     */
326    public INfcTag getTagService() {
327        return sTagService;
328    }
329
330    /**
331     * NFC service dead - attempt best effort recovery
332     * @hide
333     */
334    public void attemptDeadServiceRecovery(Exception e) {
335        Log.e(TAG, "NFC service dead - attempting to recover", e);
336        INfcAdapter service = getServiceInterface();
337        if (service == null) {
338            Log.e(TAG, "could not retrieve NFC service during service recovery");
339            // nothing more can be done now, sService is still stale, we'll hit
340            // this recovery path again later
341            return;
342        }
343        // assigning to sService is not thread-safe, but this is best-effort code
344        // and on a well-behaved system should never happen
345        sService = service;
346        try {
347            sTagService = service.getNfcTagInterface();
348        } catch (RemoteException ee) {
349            Log.e(TAG, "could not retrieve NFC tag service during service recovery");
350            // nothing more can be done now, sService is still stale, we'll hit
351            // this recovery path again later
352        }
353
354        return;
355    }
356
357    /**
358     * Return true if this NFC Adapter has any features enabled.
359     * <p>
360     * If this method returns false, then applications should request the user
361     * turn on NFC tag discovery in Settings.
362     * <p>
363     * If this method returns false, the NFC hardware is guaranteed not to
364     * perform or respond to any NFC communication.
365     *
366     * @return true if this NFC Adapter is enabled to discover new tags
367     */
368    public boolean isEnabled() {
369        try {
370            return sService.isEnabled();
371        } catch (RemoteException e) {
372            attemptDeadServiceRecovery(e);
373            return false;
374        }
375    }
376
377    /**
378     * Enable NFC hardware.
379     * <p>
380     * NOTE: may block for ~second or more.  Poor API.  Avoid
381     * calling from the UI thread.
382     *
383     * @hide
384     */
385    public boolean enable() {
386        try {
387            return sService.enable();
388        } catch (RemoteException e) {
389            attemptDeadServiceRecovery(e);
390            return false;
391        }
392    }
393
394    /**
395     * Disable NFC hardware.
396     * No NFC features will work after this call, and the hardware
397     * will not perform or respond to any NFC communication.
398     * <p>
399     * NOTE: may block for ~second or more.  Poor API.  Avoid
400     * calling from the UI thread.
401     *
402     * @hide
403     */
404    public boolean disable() {
405        try {
406            return sService.disable();
407        } catch (RemoteException e) {
408            attemptDeadServiceRecovery(e);
409            return false;
410        }
411    }
412
413    /**
414     * Retrieve a TagTechnology object used to interact with a Tag that is
415     * in field.
416     * <p>
417     * @return TagTechnology object, or null if not present
418     */
419    public TagTechnology getTechnology(Tag tag, int tech) {
420        return tag.getTechnology(NfcAdapter.this, tech);
421    }
422
423    /**
424     * Set the NDEF Message that this NFC adapter should appear as to Tag
425     * readers.
426     * <p>
427     * Any Tag reader can read the contents of the local tag when it is in
428     * proximity, without any further user confirmation.
429     * <p>
430     * The implementation of this method must either
431     * <ul>
432     * <li>act as a passive tag containing this NDEF message
433     * <li>provide the NDEF message on over LLCP to peer NFC adapters
434     * </ul>
435     * The NDEF message is preserved across reboot.
436     * <p>Requires {@link android.Manifest.permission#NFC} permission.
437     *
438     * @param message NDEF message to make public
439     * @hide
440     */
441    public void setLocalNdefMessage(NdefMessage message) {
442        try {
443            sService.localSet(message);
444        } catch (RemoteException e) {
445            attemptDeadServiceRecovery(e);
446        }
447    }
448
449    /**
450     * Get the NDEF Message that this adapter appears as to Tag readers.
451     * <p>Requires {@link android.Manifest.permission#NFC} permission.
452     *
453     * @return NDEF Message that is publicly readable
454     * @hide
455     */
456    public NdefMessage getLocalNdefMessage() {
457        try {
458            return sService.localGet();
459        } catch (RemoteException e) {
460            attemptDeadServiceRecovery(e);
461            return null;
462        }
463    }
464
465    /**
466     * Create an Nfc Secure Element Connection
467     * @hide
468     */
469    public NfcSecureElement createNfcSecureElementConnection() {
470        try {
471            return new NfcSecureElement(sService.getNfcSecureElementInterface());
472        } catch (RemoteException e) {
473            Log.e(TAG, "createNfcSecureElementConnection failed", e);
474            return null;
475        }
476    }
477}
478