NfcAdapter.java revision d88e9aa575eb3a9d20cdb0e8918d54993e1ce1e0
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.os.IBinder;
30import android.os.Parcel;
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#getTechList()} is sorted 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        isEnabled();  // NOP call to recover sService if it is stale
321        return sService;
322    }
323
324    /**
325     * Returns the binder interface to the tag service.
326     * @hide
327     */
328    public INfcTag getTagService() {
329        isEnabled();  // NOP call to recover sTagService if it is stale
330        return sTagService;
331    }
332
333    /**
334     * NFC service dead - attempt best effort recovery
335     * @hide
336     */
337    public void attemptDeadServiceRecovery(Exception e) {
338        Log.e(TAG, "NFC service dead - attempting to recover", e);
339        INfcAdapter service = getServiceInterface();
340        if (service == null) {
341            Log.e(TAG, "could not retrieve NFC service during service recovery");
342            // nothing more can be done now, sService is still stale, we'll hit
343            // this recovery path again later
344            return;
345        }
346        // assigning to sService is not thread-safe, but this is best-effort code
347        // and on a well-behaved system should never happen
348        sService = service;
349        try {
350            sTagService = service.getNfcTagInterface();
351        } catch (RemoteException ee) {
352            Log.e(TAG, "could not retrieve NFC tag service during service recovery");
353            // nothing more can be done now, sService is still stale, we'll hit
354            // this recovery path again later
355        }
356
357        return;
358    }
359
360    /**
361     * Return true if this NFC Adapter has any features enabled.
362     * <p>
363     * If this method returns false, then applications should request the user
364     * turn on NFC tag discovery in Settings.
365     * <p>
366     * If this method returns false, the NFC hardware is guaranteed not to
367     * perform or respond to any NFC communication.
368     *
369     * @return true if this NFC Adapter is enabled to discover new tags
370     */
371    public boolean isEnabled() {
372        try {
373            return sService.isEnabled();
374        } catch (RemoteException e) {
375            attemptDeadServiceRecovery(e);
376            return false;
377        }
378    }
379
380    /**
381     * Enable NFC hardware.
382     * <p>
383     * NOTE: may block for ~second or more.  Poor API.  Avoid
384     * calling from the UI thread.
385     *
386     * @hide
387     */
388    public boolean enable() {
389        try {
390            return sService.enable();
391        } catch (RemoteException e) {
392            attemptDeadServiceRecovery(e);
393            return false;
394        }
395    }
396
397    /**
398     * Disable NFC hardware.
399     * No NFC features will work after this call, and the hardware
400     * will not perform or respond to any NFC communication.
401     * <p>
402     * NOTE: may block for ~second or more.  Poor API.  Avoid
403     * calling from the UI thread.
404     *
405     * @hide
406     */
407    public boolean disable() {
408        try {
409            return sService.disable();
410        } catch (RemoteException e) {
411            attemptDeadServiceRecovery(e);
412            return false;
413        }
414    }
415
416    /**
417     * Enables foreground dispatching to the given Activity. This will force all NFC Intents that
418     * match the given filters to be delivered to the activity bypassing the standard dispatch
419     * mechanism. If no IntentFilters are given all the PendingIntent will be invoked for every
420     * dispatch Intent.
421     *
422     * This method must be called from the main thread.
423     *
424     * @param activity the Activity to dispatch to
425     * @param intent the PendingIntent to start for the dispatch
426     * @param filters the IntentFilters to override dispatching for, or null to always dispatch
427     * @throws IllegalStateException
428     */
429    public void enableForegroundDispatch(Activity activity, PendingIntent intent,
430            IntentFilter[] filters, String[][] techLists) {
431        if (activity == null || intent == null) {
432            throw new NullPointerException();
433        }
434        if (!activity.isResumed()) {
435            throw new IllegalStateException("Foregorund dispatching can only be enabled " +
436                    "when your activity is resumed");
437        }
438        try {
439            TechListParcel parcel = null;
440            if (techLists != null && techLists.length > 0) {
441                parcel = new TechListParcel(techLists);
442            }
443            ActivityThread.currentActivityThread().registerOnActivityPausedListener(activity,
444                    mForegroundDispatchListener);
445            sService.enableForegroundDispatch(activity.getComponentName(), intent, filters,
446                    parcel);
447        } catch (RemoteException e) {
448            attemptDeadServiceRecovery(e);
449        }
450    }
451
452    /**
453     * Disables foreground activity dispatching setup with
454     * {@link #enableForegroundDispatch}.
455     *
456     * <p>This must be called before the Activity returns from
457     * it's <code>onPause()</code> or this method will throw an IllegalStateException.
458     *
459     * <p>This method must be called from the main thread.
460     */
461    public void disableForegroundDispatch(Activity activity) {
462        ActivityThread.currentActivityThread().unregisterOnActivityPausedListener(activity,
463                mForegroundDispatchListener);
464        disableForegroundDispatchInternal(activity, false);
465    }
466
467    OnActivityPausedListener mForegroundDispatchListener = new OnActivityPausedListener() {
468        @Override
469        public void onPaused(Activity activity) {
470            disableForegroundDispatchInternal(activity, true);
471        }
472    };
473
474    void disableForegroundDispatchInternal(Activity activity, boolean force) {
475        try {
476            sService.disableForegroundDispatch(activity.getComponentName());
477            if (!force && !activity.isResumed()) {
478                throw new IllegalStateException("You must disable forgeground dispatching " +
479                        "while your activity is still resumed");
480            }
481        } catch (RemoteException e) {
482            attemptDeadServiceRecovery(e);
483        }
484    }
485
486    /**
487     * Enable NDEF message push over P2P while this Activity is in the foreground. For this to
488     * function properly the other NFC device being scanned must support the "com.android.npp"
489     * NDEF push protocol.
490     *
491     * <p><em>NOTE</em> While foreground NDEF push is active standard tag dispatch is disabled.
492     * Only the foreground activity may receive tag discovered dispatches via
493     * {@link #enableForegroundDispatch}.
494     */
495    public void enableForegroundNdefPush(Activity activity, NdefMessage msg) {
496        if (activity == null || msg == null) {
497            throw new NullPointerException();
498        }
499        if (!activity.isResumed()) {
500            throw new IllegalStateException("Foregorund NDEF push can only be enabled " +
501                    "when your activity is resumed");
502        }
503        try {
504            ActivityThread.currentActivityThread().registerOnActivityPausedListener(activity,
505                    mForegroundNdefPushListener);
506            sService.enableForegroundNdefPush(activity.getComponentName(), msg);
507        } catch (RemoteException e) {
508            attemptDeadServiceRecovery(e);
509        }
510    }
511
512    /**
513     * Disables foreground NDEF push setup with
514     * {@link #enableForegroundNdefPush}.
515     *
516     * <p>This must be called before the Activity returns from
517     * it's <code>onPause()</code> or this method will throw an IllegalStateException.
518     *
519     * <p>This method must be called from the main thread.
520     */
521    public void disableForegroundNdefPush(Activity activity) {
522        ActivityThread.currentActivityThread().unregisterOnActivityPausedListener(activity,
523                mForegroundNdefPushListener);
524        disableForegroundNdefPushInternal(activity, false);
525    }
526
527    OnActivityPausedListener mForegroundNdefPushListener = new OnActivityPausedListener() {
528        @Override
529        public void onPaused(Activity activity) {
530            disableForegroundNdefPushInternal(activity, true);
531        }
532    };
533
534    void disableForegroundNdefPushInternal(Activity activity, boolean force) {
535        try {
536            sService.disableForegroundNdefPush(activity.getComponentName());
537            if (!force && !activity.isResumed()) {
538                throw new IllegalStateException("You must disable forgeground NDEF push " +
539                        "while your activity is still resumed");
540            }
541        } catch (RemoteException e) {
542            attemptDeadServiceRecovery(e);
543        }
544    }
545
546    /**
547     * Set the NDEF Message that this NFC adapter should appear as to Tag
548     * readers.
549     * <p>
550     * Any Tag reader can read the contents of the local tag when it is in
551     * proximity, without any further user confirmation.
552     * <p>
553     * The implementation of this method must either
554     * <ul>
555     * <li>act as a passive tag containing this NDEF message
556     * <li>provide the NDEF message on over LLCP to peer NFC adapters
557     * </ul>
558     * The NDEF message is preserved across reboot.
559     * <p>Requires {@link android.Manifest.permission#NFC} permission.
560     *
561     * @param message NDEF message to make public
562     * @hide
563     */
564    public void setLocalNdefMessage(NdefMessage message) {
565        try {
566            sService.localSet(message);
567        } catch (RemoteException e) {
568            attemptDeadServiceRecovery(e);
569        }
570    }
571
572    /**
573     * Get the NDEF Message that this adapter appears as to Tag readers.
574     * <p>Requires {@link android.Manifest.permission#NFC} permission.
575     *
576     * @return NDEF Message that is publicly readable
577     * @hide
578     */
579    public NdefMessage getLocalNdefMessage() {
580        try {
581            return sService.localGet();
582        } catch (RemoteException e) {
583            attemptDeadServiceRecovery(e);
584            return null;
585        }
586    }
587
588    /**
589     * Create an Nfc Secure Element Connection
590     * @hide
591     */
592    public NfcSecureElement createNfcSecureElementConnection() {
593        try {
594            return new NfcSecureElement(sService.getNfcSecureElementInterface());
595        } catch (RemoteException e) {
596            Log.e(TAG, "createNfcSecureElementConnection failed", e);
597            return null;
598        }
599    }
600}
601