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