NfcAdapter.java revision 3bba8d0457408421a6468f03bbb36e9ff32b81cf
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 java.util.HashMap;
20
21import android.annotation.SdkConstant;
22import android.annotation.SdkConstant.SdkConstantType;
23import android.app.Activity;
24import android.app.ActivityThread;
25import android.app.OnActivityPausedListener;
26import android.app.PendingIntent;
27import android.content.Context;
28import android.content.IntentFilter;
29import android.content.pm.IPackageManager;
30import android.content.pm.PackageManager;
31import android.net.Uri;
32import android.nfc.tech.MifareClassic;
33import android.nfc.tech.Ndef;
34import android.nfc.tech.NfcA;
35import android.nfc.tech.NfcF;
36import android.os.IBinder;
37import android.os.RemoteException;
38import android.os.ServiceManager;
39import android.util.Log;
40
41/**
42 * Represents the local NFC adapter.
43 * <p>
44 * Use the helper {@link #getDefaultAdapter(Context)} to get the default NFC
45 * adapter for this Android device.
46 *
47 * <div class="special reference">
48 * <h3>Developer Guides</h3>
49 * <p>For more information about using NFC, read the
50 * <a href="{@docRoot}guide/topics/nfc/index.html">Near Field Communication</a> developer guide.</p>
51 * </div>
52 */
53public final class NfcAdapter {
54    static final String TAG = "NFC";
55
56    /**
57     * Intent to start an activity when a tag with NDEF payload is discovered.
58     *
59     * <p>The system inspects the first {@link NdefRecord} in the first {@link NdefMessage} and
60     * looks for a URI, SmartPoster, or MIME record. If a URI or SmartPoster record is found the
61     * intent will contain the URI in its data field. If a MIME record is found the intent will
62     * contain the MIME type in its type field. This allows activities to register
63     * {@link IntentFilter}s targeting specific content on tags. Activities should register the
64     * most specific intent filters possible to avoid the activity chooser dialog, which can
65     * disrupt the interaction with the tag as the user interacts with the screen.
66     *
67     * <p>If the tag has an NDEF payload this intent is started before
68     * {@link #ACTION_TECH_DISCOVERED}. If any activities respond to this intent neither
69     * {@link #ACTION_TECH_DISCOVERED} or {@link #ACTION_TAG_DISCOVERED} will be started.
70     *
71     * <p>The MIME type or data URI of this intent are normalized before dispatch -
72     * so that MIME, URI scheme and URI host are always lower-case.
73     */
74    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
75    public static final String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED";
76
77    /**
78     * Intent to start an activity when a tag is discovered and activities are registered for the
79     * specific technologies on the tag.
80     *
81     * <p>To receive this intent an activity must include an intent filter
82     * for this action and specify the desired tech types in a
83     * manifest <code>meta-data</code> entry. Here is an example manfiest entry:
84     * <pre>
85     *   &lt;activity android:name=".nfc.TechFilter" android:label="NFC/TechFilter"&gt;
86     *       &lt;!-- Add a technology filter --&gt;
87     *       &lt;intent-filter&gt;
88     *           &lt;action android:name="android.nfc.action.TECH_DISCOVERED" /&gt;
89     *       &lt;/intent-filter&gt;
90     *
91     *       &lt;meta-data android:name="android.nfc.action.TECH_DISCOVERED"
92     *           android:resource="@xml/filter_nfc"
93     *       /&gt;
94     *   &lt;/activity&gt;
95     * </pre>
96     *
97     * <p>The meta-data XML file should contain one or more <code>tech-list</code> entries
98     * each consisting or one or more <code>tech</code> entries. The <code>tech</code> entries refer
99     * to the qualified class name implementing the technology, for example "android.nfc.tech.NfcA".
100     *
101     * <p>A tag matches if any of the
102     * <code>tech-list</code> sets is a subset of {@link Tag#getTechList() Tag.getTechList()}. Each
103     * of the <code>tech-list</code>s is considered independently and the
104     * activity is considered a match is any single <code>tech-list</code> matches the tag that was
105     * discovered. This provides AND and OR semantics for filtering desired techs. Here is an
106     * example that will match any tag using {@link NfcF} or any tag using {@link NfcA},
107     * {@link MifareClassic}, and {@link Ndef}:
108     *
109     * <pre>
110     * &lt;resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"&gt;
111     *     &lt;!-- capture anything using NfcF --&gt;
112     *     &lt;tech-list&gt;
113     *         &lt;tech&gt;android.nfc.tech.NfcF&lt;/tech&gt;
114     *     &lt;/tech-list&gt;
115     *
116     *     &lt;!-- OR --&gt;
117     *
118     *     &lt;!-- capture all MIFARE Classics with NDEF payloads --&gt;
119     *     &lt;tech-list&gt;
120     *         &lt;tech&gt;android.nfc.tech.NfcA&lt;/tech&gt;
121     *         &lt;tech&gt;android.nfc.tech.MifareClassic&lt;/tech&gt;
122     *         &lt;tech&gt;android.nfc.tech.Ndef&lt;/tech&gt;
123     *     &lt;/tech-list&gt;
124     * &lt;/resources&gt;
125     * </pre>
126     *
127     * <p>This intent is started after {@link #ACTION_NDEF_DISCOVERED} and before
128     * {@link #ACTION_TAG_DISCOVERED}. If any activities respond to {@link #ACTION_NDEF_DISCOVERED}
129     * this intent will not be started. If any activities respond to this intent
130     * {@link #ACTION_TAG_DISCOVERED} will not be started.
131     */
132    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
133    public static final String ACTION_TECH_DISCOVERED = "android.nfc.action.TECH_DISCOVERED";
134
135    /**
136     * Intent to start an activity when a tag is discovered.
137     *
138     * <p>This intent will not be started when a tag is discovered if any activities respond to
139     * {@link #ACTION_NDEF_DISCOVERED} or {@link #ACTION_TECH_DISCOVERED} for the current tag.
140     */
141    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
142    public static final String ACTION_TAG_DISCOVERED = "android.nfc.action.TAG_DISCOVERED";
143
144    /**
145     * Broadcast to only the activity that handles ACTION_TAG_DISCOVERED
146     * @hide
147     */
148    public static final String ACTION_TAG_LEFT_FIELD = "android.nfc.action.TAG_LOST";
149
150    /**
151     * Mandatory extra containing the {@link Tag} that was discovered for the
152     * {@link #ACTION_NDEF_DISCOVERED}, {@link #ACTION_TECH_DISCOVERED}, and
153     * {@link #ACTION_TAG_DISCOVERED} intents.
154     */
155    public static final String EXTRA_TAG = "android.nfc.extra.TAG";
156
157    /**
158     * Extra containing an array of {@link NdefMessage} present on the discovered tag.<p>
159     * This extra is mandatory for {@link #ACTION_NDEF_DISCOVERED} intents,
160     * and optional for {@link #ACTION_TECH_DISCOVERED}, and
161     * {@link #ACTION_TAG_DISCOVERED} intents.<p>
162     * When this extra is present there will always be at least one
163     * {@link NdefMessage} element. Most NDEF tags have only one NDEF message,
164     * but we use an array for future compatibility.
165     */
166    public static final String EXTRA_NDEF_MESSAGES = "android.nfc.extra.NDEF_MESSAGES";
167
168    /**
169     * Optional extra containing a byte array containing the ID of the discovered tag for
170     * the {@link #ACTION_NDEF_DISCOVERED}, {@link #ACTION_TECH_DISCOVERED}, and
171     * {@link #ACTION_TAG_DISCOVERED} intents.
172     */
173    public static final String EXTRA_ID = "android.nfc.extra.ID";
174
175    /**
176     * Broadcast Action: The state of the local NFC adapter has been
177     * changed.
178     * <p>For example, NFC has been turned on or off.
179     * <p>Always contains the extra field {@link #EXTRA_STATE}
180     * @hide
181     */
182    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
183    public static final String ACTION_ADAPTER_STATE_CHANGED =
184            "android.nfc.action.ADAPTER_STATE_CHANGED";
185
186    /**
187     * Used as an int extra field in {@link #ACTION_STATE_CHANGED}
188     * intents to request the current power state. Possible values are:
189     * {@link #STATE_OFF},
190     * {@link #STATE_TURNING_ON},
191     * {@link #STATE_ON},
192     * {@link #STATE_TURNING_OFF},
193     * @hide
194     */
195    public static final String EXTRA_ADAPTER_STATE = "android.nfc.extra.ADAPTER_STATE";
196
197    /** @hide */
198    public static final int STATE_OFF = 1;
199    /** @hide */
200    public static final int STATE_TURNING_ON = 2;
201    /** @hide */
202    public static final int STATE_ON = 3;
203    /** @hide */
204    public static final int STATE_TURNING_OFF = 4;
205
206    /** @hide */
207    public static final String ACTION_HANDOVER_TRANSFER_STARTED =
208            "android.nfc.action.HANDOVER_TRANSFER_STARTED";
209
210    /** @hide */
211    public static final String ACTION_HANDOVER_TRANSFER_DONE =
212            "android.nfc.action.HANDOVER_TRANSFER_DONE";
213
214    /** @hide */
215    public static final String EXTRA_HANDOVER_TRANSFER_STATUS =
216            "android.nfc.extra.HANDOVER_TRANSFER_STATUS";
217
218    /** @hide */
219    public static final int HANDOVER_TRANSFER_STATUS_SUCCESS = 0;
220    /** @hide */
221    public static final int HANDOVER_TRANSFER_STATUS_FAILURE = 1;
222
223    /** @hide */
224    public static final String EXTRA_HANDOVER_TRANSFER_URI =
225            "android.nfc.extra.HANDOVER_TRANSFER_URI";
226
227    // Guarded by NfcAdapter.class
228    static boolean sIsInitialized = false;
229
230    // Final after first constructor, except for
231    // attemptDeadServiceRecovery() when NFC crashes - we accept a best effort
232    // recovery
233    static INfcAdapter sService;
234    static INfcTag sTagService;
235
236    /**
237     * The NfcAdapter object for each application context.
238     * There is a 1-1 relationship between application context and
239     * NfcAdapter object.
240     */
241    static HashMap<Context, NfcAdapter> sNfcAdapters = new HashMap(); //guard by NfcAdapter.class
242
243    /**
244     * NfcAdapter used with a null context. This ctor was deprecated but we have
245     * to support it for backwards compatibility. New methods that require context
246     * might throw when called on the null-context NfcAdapter.
247     */
248    static NfcAdapter sNullContextNfcAdapter;  // protected by NfcAdapter.class
249
250    final NfcActivityManager mNfcActivityManager;
251    final Context mContext;
252
253    /**
254     * A callback to be invoked when the system successfully delivers your {@link NdefMessage}
255     * to another device.
256     * @see #setOnNdefPushCompleteCallback
257     */
258    public interface OnNdefPushCompleteCallback {
259        /**
260         * Called on successful NDEF push.
261         *
262         * <p>This callback is usually made on a binder thread (not the UI thread).
263         *
264         * @param event {@link NfcEvent} with the {@link NfcEvent#nfcAdapter} field set
265         * @see #setNdefPushMessageCallback
266         */
267        public void onNdefPushComplete(NfcEvent event);
268    }
269
270    /**
271     * A callback to be invoked when another NFC device capable of NDEF push (Android Beam)
272     * is within range.
273     * <p>Implement this interface and pass it to {@link
274     * NfcAdapter#setNdefPushMessageCallback setNdefPushMessageCallback()} in order to create an
275     * {@link NdefMessage} at the moment that another device is within range for NFC. Using this
276     * callback allows you to create a message with data that might vary based on the
277     * content currently visible to the user. Alternatively, you can call {@link
278     * #setNdefPushMessage setNdefPushMessage()} if the {@link NdefMessage} always contains the
279     * same data.
280     */
281    public interface CreateNdefMessageCallback {
282        /**
283         * Called to provide a {@link NdefMessage} to push.
284         *
285         * <p>This callback is usually made on a binder thread (not the UI thread).
286         *
287         * <p>Called when this device is in range of another device
288         * that might support NDEF push. It allows the application to
289         * create the NDEF message only when it is required.
290         *
291         * <p>NDEF push cannot occur until this method returns, so do not
292         * block for too long.
293         *
294         * <p>The Android operating system will usually show a system UI
295         * on top of your activity during this time, so do not try to request
296         * input from the user to complete the callback, or provide custom NDEF
297         * push UI. The user probably will not see it.
298         *
299         * @param event {@link NfcEvent} with the {@link NfcEvent#nfcAdapter} field set
300         * @return NDEF message to push, or null to not provide a message
301         */
302        public NdefMessage createNdefMessage(NfcEvent event);
303    }
304
305
306    // TODO javadoc
307    public interface CreateBeamUrisCallback {
308        public Uri[] createBeamUris(NfcEvent event);
309    }
310
311    /**
312     * Helper to check if this device has FEATURE_NFC, but without using
313     * a context.
314     * Equivalent to
315     * context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)
316     */
317    private static boolean hasNfcFeature() {
318        IPackageManager pm = ActivityThread.getPackageManager();
319        if (pm == null) {
320            Log.e(TAG, "Cannot get package manager, assuming no NFC feature");
321            return false;
322        }
323        try {
324            return pm.hasSystemFeature(PackageManager.FEATURE_NFC);
325        } catch (RemoteException e) {
326            Log.e(TAG, "Package manager query failed, assuming no NFC feature", e);
327            return false;
328        }
329    }
330
331    /**
332     * Returns the NfcAdapter for application context,
333     * or throws if NFC is not available.
334     * @hide
335     */
336    public static synchronized NfcAdapter getNfcAdapter(Context context) {
337        if (!sIsInitialized) {
338            /* is this device meant to have NFC */
339            if (!hasNfcFeature()) {
340                Log.v(TAG, "this device does not have NFC support");
341                throw new UnsupportedOperationException();
342            }
343
344            sService = getServiceInterface();
345            if (sService == null) {
346                Log.e(TAG, "could not retrieve NFC service");
347                throw new UnsupportedOperationException();
348            }
349            try {
350                sTagService = sService.getNfcTagInterface();
351            } catch (RemoteException e) {
352                Log.e(TAG, "could not retrieve NFC Tag service");
353                throw new UnsupportedOperationException();
354            }
355
356            sIsInitialized = true;
357        }
358        if (context == null) {
359            if (sNullContextNfcAdapter == null) {
360                sNullContextNfcAdapter = new NfcAdapter(null);
361            }
362            return sNullContextNfcAdapter;
363        }
364        NfcAdapter adapter = sNfcAdapters.get(context);
365        if (adapter == null) {
366            adapter = new NfcAdapter(context);
367            sNfcAdapters.put(context, adapter);
368        }
369        return adapter;
370    }
371
372    /** get handle to NFC service interface */
373    private static INfcAdapter getServiceInterface() {
374        /* get a handle to NFC service */
375        IBinder b = ServiceManager.getService("nfc");
376        if (b == null) {
377            return null;
378        }
379        return INfcAdapter.Stub.asInterface(b);
380    }
381
382    /**
383     * Helper to get the default NFC Adapter.
384     * <p>
385     * Most Android devices will only have one NFC Adapter (NFC Controller).
386     * <p>
387     * This helper is the equivalent of:
388     * <pre>{@code
389     * NfcManager manager = (NfcManager) context.getSystemService(Context.NFC_SERVICE);
390     * NfcAdapter adapter = manager.getDefaultAdapter();
391     * }</pre>
392     * @param context the calling application's context
393     *
394     * @return the default NFC adapter, or null if no NFC adapter exists
395     */
396    public static NfcAdapter getDefaultAdapter(Context context) {
397        if (context == null) {
398            throw new IllegalArgumentException("context cannot be null");
399        }
400        context = context.getApplicationContext();
401        if (context == null) {
402            throw new IllegalArgumentException(
403                    "context not associated with any application (using a mock context?)");
404        }
405        /* use getSystemService() for consistency */
406        NfcManager manager = (NfcManager) context.getSystemService(Context.NFC_SERVICE);
407        if (manager == null) {
408            // NFC not available
409            return null;
410        }
411        return manager.getDefaultAdapter();
412    }
413
414    /**
415     * Legacy NfcAdapter getter, always use {@link #getDefaultAdapter(Context)} instead.<p>
416     * This method was deprecated at API level 10 (Gingerbread MR1) because a context is required
417     * for many NFC API methods. Those methods will fail when called on an NfcAdapter
418     * object created from this method.<p>
419     * @deprecated use {@link #getDefaultAdapter(Context)}
420     * @hide
421     */
422    @Deprecated
423    public static NfcAdapter getDefaultAdapter() {
424        // introduced in API version 9 (GB 2.3)
425        // deprecated in API version 10 (GB 2.3.3)
426        // removed from public API in version 16 (ICS MR2)
427        // should maintain as a hidden API for binary compatibility for a little longer
428        Log.w(TAG, "WARNING: NfcAdapter.getDefaultAdapter() is deprecated, use " +
429                "NfcAdapter.getDefaultAdapter(Context) instead", new Exception());
430
431        return NfcAdapter.getNfcAdapter(null);
432    }
433
434    NfcAdapter(Context context) {
435        mContext = context;
436        mNfcActivityManager = new NfcActivityManager(this);
437    }
438
439    /**
440     * @hide
441     */
442    public Context getContext() {
443        return mContext;
444    }
445
446    /**
447     * Returns the binder interface to the service.
448     * @hide
449     */
450    public INfcAdapter getService() {
451        isEnabled();  // NOP call to recover sService if it is stale
452        return sService;
453    }
454
455    /**
456     * Returns the binder interface to the tag service.
457     * @hide
458     */
459    public INfcTag getTagService() {
460        isEnabled();  // NOP call to recover sTagService if it is stale
461        return sTagService;
462    }
463
464    /**
465     * NFC service dead - attempt best effort recovery
466     * @hide
467     */
468    public void attemptDeadServiceRecovery(Exception e) {
469        Log.e(TAG, "NFC service dead - attempting to recover", e);
470        INfcAdapter service = getServiceInterface();
471        if (service == null) {
472            Log.e(TAG, "could not retrieve NFC service during service recovery");
473            // nothing more can be done now, sService is still stale, we'll hit
474            // this recovery path again later
475            return;
476        }
477        // assigning to sService is not thread-safe, but this is best-effort code
478        // and on a well-behaved system should never happen
479        sService = service;
480        try {
481            sTagService = service.getNfcTagInterface();
482        } catch (RemoteException ee) {
483            Log.e(TAG, "could not retrieve NFC tag service during service recovery");
484            // nothing more can be done now, sService is still stale, we'll hit
485            // this recovery path again later
486        }
487
488        return;
489    }
490
491    /**
492     * Return true if this NFC Adapter has any features enabled.
493     *
494     * <p>If this method returns false, the NFC hardware is guaranteed not to
495     * generate or respond to any NFC communication over its NFC radio.
496     * <p>Applications can use this to check if NFC is enabled. Applications
497     * can request Settings UI allowing the user to toggle NFC using:
498     * <p><pre>startActivity(new Intent(Settings.ACTION_NFC_SETTINGS))</pre>
499     *
500     * @see android.provider.Settings#ACTION_NFC_SETTINGS
501     * @return true if this NFC Adapter has any features enabled
502     */
503    public boolean isEnabled() {
504        try {
505            return sService.getState() == STATE_ON;
506        } catch (RemoteException e) {
507            attemptDeadServiceRecovery(e);
508            return false;
509        }
510    }
511
512    /**
513     * Return the state of this NFC Adapter.
514     *
515     * <p>Returns one of {@link #STATE_ON}, {@link #STATE_TURNING_ON},
516     * {@link #STATE_OFF}, {@link #STATE_TURNING_OFF}.
517     *
518     * <p>{@link #isEnabled()} is equivalent to
519     * <code>{@link #getAdapterState()} == {@link #STATE_ON}</code>
520     *
521     * @return the current state of this NFC adapter
522     *
523     * @hide
524     */
525    public int getAdapterState() {
526        try {
527            return sService.getState();
528        } catch (RemoteException e) {
529            attemptDeadServiceRecovery(e);
530            return NfcAdapter.STATE_OFF;
531        }
532    }
533
534    /**
535     * Enable NFC hardware.
536     *
537     * <p>This call is asynchronous. Listen for
538     * {@link #ACTION_ADAPTER_STATE_CHANGED} broadcasts to find out when the
539     * operation is complete.
540     *
541     * <p>If this returns true, then either NFC is already on, or
542     * a {@link #ACTION_ADAPTER_STATE_CHANGED} broadcast will be sent
543     * to indicate a state transition. If this returns false, then
544     * there is some problem that prevents an attempt to turn
545     * NFC on (for example we are in airplane mode and NFC is not
546     * toggleable in airplane mode on this platform).
547     *
548     * @hide
549     */
550    public boolean enable() {
551        try {
552            return sService.enable();
553        } catch (RemoteException e) {
554            attemptDeadServiceRecovery(e);
555            return false;
556        }
557    }
558
559    /**
560     * Disable NFC hardware.
561     *
562     * <p>No NFC features will work after this call, and the hardware
563     * will not perform or respond to any NFC communication.
564     *
565     * <p>This call is asynchronous. Listen for
566     * {@link #ACTION_ADAPTER_STATE_CHANGED} broadcasts to find out when the
567     * operation is complete.
568     *
569     * <p>If this returns true, then either NFC is already off, or
570     * a {@link #ACTION_ADAPTER_STATE_CHANGED} broadcast will be sent
571     * to indicate a state transition. If this returns false, then
572     * there is some problem that prevents an attempt to turn
573     * NFC off.
574     *
575     * @hide
576     */
577
578    public boolean disable() {
579        try {
580            return sService.disable(true);
581        } catch (RemoteException e) {
582            attemptDeadServiceRecovery(e);
583            return false;
584        }
585    }
586
587    //TODO: make sure NFC service has permission for URI
588    //TODO: see if we will eventually support multiple URIs
589    //TODO: javadoc
590    public void setBeamPushUris(Uri[] uris, Activity activity) {
591        if (activity == null) {
592            throw new NullPointerException("activity cannot be null");
593        }
594        mNfcActivityManager.setNdefPushContentUri(activity, uris);
595    }
596
597    // TODO javadoc
598    public void setBeamPushUrisCallback(CreateBeamUrisCallback callback, Activity activity) {
599        if (activity == null) {
600            throw new NullPointerException("activity cannot be null");
601        }
602        mNfcActivityManager.setNdefPushContentUriCallback(activity, callback);
603    }
604
605    /**
606     * Set a static {@link NdefMessage} to send using Android Beam (TM).
607     *
608     * <p>This method may be called at any time before {@link Activity#onDestroy},
609     * but the NDEF message is only made available for NDEF push when the
610     * specified activity(s) are in resumed (foreground) state. The recommended
611     * approach is to call this method during your Activity's
612     * {@link Activity#onCreate} - see sample
613     * code below. This method does not immediately perform any I/O or blocking work,
614     * so is safe to call on your main thread.
615     *
616     * <p>Only one NDEF message can be pushed by the currently resumed activity.
617     * If both {@link #setNdefPushMessage} and
618     * {@link #setNdefPushMessageCallback} are set, then
619     * the callback will take priority.
620     *
621     * <p>If neither {@link #setNdefPushMessage} or
622     * {@link #setNdefPushMessageCallback} have been called for your activity, then
623     * the Android OS may choose to send a default NDEF message on your behalf,
624     * such as a URI for your application.
625     *
626     * <p>If {@link #setNdefPushMessage} is called with a null NDEF message,
627     * and/or {@link #setNdefPushMessageCallback} is called with a null callback,
628     * then NDEF push will be completely disabled for the specified activity(s).
629     * This also disables any default NDEF message the Android OS would have
630     * otherwise sent on your behalf for those activity(s).
631     *
632     * <p>If you want to prevent the Android OS from sending default NDEF
633     * messages completely (for all activities), you can include a
634     * <code><meta-data></code> element inside the <code><application></code>
635     * element of your AndroidManifest.xml file, like this:
636     * <pre>{@code
637     *  <application ...>
638     *      <meta-data android:name="android.nfc.disable_beam_default"
639     *          android:value="true" />
640     *  </application>
641     * }</pre>
642     *
643     * <p>The API allows for multiple activities to be specified at a time,
644     * but it is strongly recommended to just register one at a time,
645     * and to do so during the activity's {@link Activity#onCreate}. For example:
646     * <pre>
647     * protected void onCreate(Bundle savedInstanceState) {
648     *     super.onCreate(savedInstanceState);
649     *     NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
650     *     if (nfcAdapter == null) return;  // NFC not available on this device
651     *     nfcAdapter.setNdefPushMessage(ndefMessage, this);
652     * }
653     * </pre>
654     * And that is it. Only one call per activity is necessary. The Android
655     * OS will automatically release its references to the NDEF message and the
656     * Activity object when it is destroyed if you follow this pattern.
657     *
658     * <p>If your Activity wants to dynamically generate an NDEF message,
659     * then set a callback using {@link #setNdefPushMessageCallback} instead
660     * of a static message.
661     *
662     * <p class="note">Do not pass in an Activity that has already been through
663     * {@link Activity#onDestroy}. This is guaranteed if you call this API
664     * during {@link Activity#onCreate}.
665     *
666     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
667     *
668     * @param message NDEF message to push over NFC, or null to disable
669     * @param activity activity for which the NDEF message will be pushed
670     * @param activities optional additional activities, however we strongly recommend
671     *        to only register one at a time, and to do so in that activity's
672     *        {@link Activity#onCreate}
673     */
674    public void setNdefPushMessage(NdefMessage message, Activity activity,
675            Activity ... activities) {
676        int targetSdkVersion = getSdkVersion();
677        try {
678            if (activity == null) {
679                throw new NullPointerException("activity cannot be null");
680            }
681            mNfcActivityManager.setNdefPushMessage(activity, message);
682            for (Activity a : activities) {
683                if (a == null) {
684                    throw new NullPointerException("activities cannot contain null");
685                }
686                mNfcActivityManager.setNdefPushMessage(a, message);
687            }
688        } catch (IllegalStateException e) {
689            if (targetSdkVersion < android.os.Build.VERSION_CODES.JELLY_BEAN) {
690                // Less strict on old applications - just log the error
691                Log.e(TAG, "Cannot call API with Activity that has already " +
692                        "been destroyed", e);
693            } else {
694                // Prevent new applications from making this mistake, re-throw
695                throw(e);
696            }
697        }
698    }
699
700    /**
701     * Set a callback that dynamically generates NDEF messages to send using Android Beam (TM).
702     *
703     * <p>This method may be called at any time before {@link Activity#onDestroy},
704     * but the NDEF message callback can only occur when the
705     * specified activity(s) are in resumed (foreground) state. The recommended
706     * approach is to call this method during your Activity's
707     * {@link Activity#onCreate} - see sample
708     * code below. This method does not immediately perform any I/O or blocking work,
709     * so is safe to call on your main thread.
710     *
711     * <p>Only one NDEF message can be pushed by the currently resumed activity.
712     * If both {@link #setNdefPushMessage} and
713     * {@link #setNdefPushMessageCallback} are set, then
714     * the callback will take priority.
715     *
716     * <p>If neither {@link #setNdefPushMessage} or
717     * {@link #setNdefPushMessageCallback} have been called for your activity, then
718     * the Android OS may choose to send a default NDEF message on your behalf,
719     * such as a URI for your application.
720     *
721     * <p>If {@link #setNdefPushMessage} is called with a null NDEF message,
722     * and/or {@link #setNdefPushMessageCallback} is called with a null callback,
723     * then NDEF push will be completely disabled for the specified activity(s).
724     * This also disables any default NDEF message the Android OS would have
725     * otherwise sent on your behalf for those activity(s).
726     *
727     * <p>If you want to prevent the Android OS from sending default NDEF
728     * messages completely (for all activities), you can include a
729     * <code><meta-data></code> element inside the <code><application></code>
730     * element of your AndroidManifest.xml file, like this:
731     * <pre>{@code
732     *  <application ...>
733     *      <meta-data android:name="android.nfc.disable_beam_default"
734     *          android:value="true" />
735     *  </application>
736     * }</pre>
737     *
738     * <p>The API allows for multiple activities to be specified at a time,
739     * but it is strongly recommended to just register one at a time,
740     * and to do so during the activity's {@link Activity#onCreate}. For example:
741     * <pre>
742     * protected void onCreate(Bundle savedInstanceState) {
743     *     super.onCreate(savedInstanceState);
744     *     NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
745     *     if (nfcAdapter == null) return;  // NFC not available on this device
746     *     nfcAdapter.setNdefPushMessageCallback(callback, this);
747     * }
748     * </pre>
749     * And that is it. Only one call per activity is necessary. The Android
750     * OS will automatically release its references to the callback and the
751     * Activity object when it is destroyed if you follow this pattern.
752     *
753     * <p class="note">Do not pass in an Activity that has already been through
754     * {@link Activity#onDestroy}. This is guaranteed if you call this API
755     * during {@link Activity#onCreate}.
756     *
757     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
758     *
759     * @param callback callback, or null to disable
760     * @param activity activity for which the NDEF message will be pushed
761     * @param activities optional additional activities, however we strongly recommend
762     *        to only register one at a time, and to do so in that activity's
763     *        {@link Activity#onCreate}
764     */
765    public void setNdefPushMessageCallback(CreateNdefMessageCallback callback, Activity activity,
766            Activity ... activities) {
767        int targetSdkVersion = getSdkVersion();
768        try {
769            if (activity == null) {
770                throw new NullPointerException("activity cannot be null");
771            }
772            mNfcActivityManager.setNdefPushMessageCallback(activity, callback);
773            for (Activity a : activities) {
774                if (a == null) {
775                    throw new NullPointerException("activities cannot contain null");
776                }
777                mNfcActivityManager.setNdefPushMessageCallback(a, callback);
778            }
779        } catch (IllegalStateException e) {
780            if (targetSdkVersion < android.os.Build.VERSION_CODES.JELLY_BEAN) {
781                // Less strict on old applications - just log the error
782                Log.e(TAG, "Cannot call API with Activity that has already " +
783                        "been destroyed", e);
784            } else {
785                // Prevent new applications from making this mistake, re-throw
786                throw(e);
787            }
788        }
789    }
790
791    /**
792     * Set a callback on successful Android Beam (TM).
793     *
794     * <p>This method may be called at any time before {@link Activity#onDestroy},
795     * but the callback can only occur when the
796     * specified activity(s) are in resumed (foreground) state. The recommended
797     * approach is to call this method during your Activity's
798     * {@link Activity#onCreate} - see sample
799     * code below. This method does not immediately perform any I/O or blocking work,
800     * so is safe to call on your main thread.
801     *
802     * <p>The API allows for multiple activities to be specified at a time,
803     * but it is strongly recommended to just register one at a time,
804     * and to do so during the activity's {@link Activity#onCreate}. For example:
805     * <pre>
806     * protected void onCreate(Bundle savedInstanceState) {
807     *     super.onCreate(savedInstanceState);
808     *     NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
809     *     if (nfcAdapter == null) return;  // NFC not available on this device
810     *     nfcAdapter.setOnNdefPushCompleteCallback(callback, this);
811     * }
812     * </pre>
813     * And that is it. Only one call per activity is necessary. The Android
814     * OS will automatically release its references to the callback and the
815     * Activity object when it is destroyed if you follow this pattern.
816     *
817     * <p class="note">Do not pass in an Activity that has already been through
818     * {@link Activity#onDestroy}. This is guaranteed if you call this API
819     * during {@link Activity#onCreate}.
820     *
821     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
822     *
823     * @param callback callback, or null to disable
824     * @param activity activity for which the NDEF message will be pushed
825     * @param activities optional additional activities, however we strongly recommend
826     *        to only register one at a time, and to do so in that activity's
827     *        {@link Activity#onCreate}
828     */
829    public void setOnNdefPushCompleteCallback(OnNdefPushCompleteCallback callback,
830            Activity activity, Activity ... activities) {
831        int targetSdkVersion = getSdkVersion();
832        try {
833            if (activity == null) {
834                throw new NullPointerException("activity cannot be null");
835            }
836            mNfcActivityManager.setOnNdefPushCompleteCallback(activity, callback);
837            for (Activity a : activities) {
838                if (a == null) {
839                    throw new NullPointerException("activities cannot contain null");
840                }
841                mNfcActivityManager.setOnNdefPushCompleteCallback(a, callback);
842            }
843        } catch (IllegalStateException e) {
844            if (targetSdkVersion < android.os.Build.VERSION_CODES.JELLY_BEAN) {
845                // Less strict on old applications - just log the error
846                Log.e(TAG, "Cannot call API with Activity that has already " +
847                        "been destroyed", e);
848            } else {
849                // Prevent new applications from making this mistake, re-throw
850                throw(e);
851            }
852        }
853    }
854
855    /**
856     * Enable foreground dispatch to the given Activity.
857     *
858     * <p>This will give give priority to the foreground activity when
859     * dispatching a discovered {@link Tag} to an application.
860     *
861     * <p>If any IntentFilters are provided to this method they are used to match dispatch Intents
862     * for both the {@link NfcAdapter#ACTION_NDEF_DISCOVERED} and
863     * {@link NfcAdapter#ACTION_TAG_DISCOVERED}. Since {@link NfcAdapter#ACTION_TECH_DISCOVERED}
864     * relies on meta data outside of the IntentFilter matching for that dispatch Intent is handled
865     * by passing in the tech lists separately. Each first level entry in the tech list represents
866     * an array of technologies that must all be present to match. If any of the first level sets
867     * match then the dispatch is routed through the given PendingIntent. In other words, the second
868     * level is ANDed together and the first level entries are ORed together.
869     *
870     * <p>If you pass {@code null} for both the {@code filters} and {@code techLists} parameters
871     * that acts a wild card and will cause the foreground activity to receive all tags via the
872     * {@link NfcAdapter#ACTION_TAG_DISCOVERED} intent.
873     *
874     * <p>This method must be called from the main thread, and only when the activity is in the
875     * foreground (resumed). Also, activities must call {@link #disableForegroundDispatch} before
876     * the completion of their {@link Activity#onPause} callback to disable foreground dispatch
877     * after it has been enabled.
878     *
879     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
880     *
881     * @param activity the Activity to dispatch to
882     * @param intent the PendingIntent to start for the dispatch
883     * @param filters the IntentFilters to override dispatching for, or null to always dispatch
884     * @param techLists the tech lists used to perform matching for dispatching of the
885     *      {@link NfcAdapter#ACTION_TECH_DISCOVERED} intent
886     * @throws IllegalStateException if the Activity is not currently in the foreground
887     */
888    public void enableForegroundDispatch(Activity activity, PendingIntent intent,
889            IntentFilter[] filters, String[][] techLists) {
890        if (activity == null || intent == null) {
891            throw new NullPointerException();
892        }
893        if (!activity.isResumed()) {
894            throw new IllegalStateException("Foreground dispatch can only be enabled " +
895                    "when your activity is resumed");
896        }
897        try {
898            TechListParcel parcel = null;
899            if (techLists != null && techLists.length > 0) {
900                parcel = new TechListParcel(techLists);
901            }
902            ActivityThread.currentActivityThread().registerOnActivityPausedListener(activity,
903                    mForegroundDispatchListener);
904            sService.setForegroundDispatch(intent, filters, parcel);
905        } catch (RemoteException e) {
906            attemptDeadServiceRecovery(e);
907        }
908    }
909
910    /**
911     * Disable foreground dispatch to the given activity.
912     *
913     * <p>After calling {@link #enableForegroundDispatch}, an activity
914     * must call this method before its {@link Activity#onPause} callback
915     * completes.
916     *
917     * <p>This method must be called from the main thread.
918     *
919     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
920     *
921     * @param activity the Activity to disable dispatch to
922     * @throws IllegalStateException if the Activity has already been paused
923     */
924    public void disableForegroundDispatch(Activity activity) {
925        ActivityThread.currentActivityThread().unregisterOnActivityPausedListener(activity,
926                mForegroundDispatchListener);
927        disableForegroundDispatchInternal(activity, false);
928    }
929
930    OnActivityPausedListener mForegroundDispatchListener = new OnActivityPausedListener() {
931        @Override
932        public void onPaused(Activity activity) {
933            disableForegroundDispatchInternal(activity, true);
934        }
935    };
936
937    void disableForegroundDispatchInternal(Activity activity, boolean force) {
938        try {
939            sService.setForegroundDispatch(null, null, null);
940            if (!force && !activity.isResumed()) {
941                throw new IllegalStateException("You must disable foreground dispatching " +
942                        "while your activity is still resumed");
943            }
944        } catch (RemoteException e) {
945            attemptDeadServiceRecovery(e);
946        }
947    }
948
949    /**
950     * Enable NDEF message push over NFC while this Activity is in the foreground.
951     *
952     * <p>You must explicitly call this method every time the activity is
953     * resumed, and you must call {@link #disableForegroundNdefPush} before
954     * your activity completes {@link Activity#onPause}.
955     *
956     * <p>Strongly recommend to use the new {@link #setNdefPushMessage}
957     * instead: it automatically hooks into your activity life-cycle,
958     * so you do not need to call enable/disable in your onResume/onPause.
959     *
960     * <p>For NDEF push to function properly the other NFC device must
961     * support either NFC Forum's SNEP (Simple Ndef Exchange Protocol), or
962     * Android's "com.android.npp" (Ndef Push Protocol). This was optional
963     * on Gingerbread level Android NFC devices, but SNEP is mandatory on
964     * Ice-Cream-Sandwich and beyond.
965     *
966     * <p>This method must be called from the main thread.
967     *
968     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
969     *
970     * @param activity foreground activity
971     * @param message a NDEF Message to push over NFC
972     * @throws IllegalStateException if the activity is not currently in the foreground
973     * @deprecated use {@link #setNdefPushMessage} instead
974     */
975    @Deprecated
976    public void enableForegroundNdefPush(Activity activity, NdefMessage message) {
977        if (activity == null || message == null) {
978            throw new NullPointerException();
979        }
980        enforceResumed(activity);
981        mNfcActivityManager.setNdefPushMessage(activity, message);
982    }
983
984    /**
985     * Disable NDEF message push over P2P.
986     *
987     * <p>After calling {@link #enableForegroundNdefPush}, an activity
988     * must call this method before its {@link Activity#onPause} callback
989     * completes.
990     *
991     * <p>Strongly recommend to use the new {@link #setNdefPushMessage}
992     * instead: it automatically hooks into your activity life-cycle,
993     * so you do not need to call enable/disable in your onResume/onPause.
994     *
995     * <p>This method must be called from the main thread.
996     *
997     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
998     *
999     * @param activity the Foreground activity
1000     * @throws IllegalStateException if the Activity has already been paused
1001     * @deprecated use {@link #setNdefPushMessage} instead
1002     */
1003    @Deprecated
1004    public void disableForegroundNdefPush(Activity activity) {
1005        if (activity == null) {
1006            throw new NullPointerException();
1007        }
1008        enforceResumed(activity);
1009        mNfcActivityManager.setNdefPushMessage(activity, null);
1010        mNfcActivityManager.setNdefPushMessageCallback(activity, null);
1011        mNfcActivityManager.setOnNdefPushCompleteCallback(activity, null);
1012    }
1013
1014    /**
1015     * Enable NDEF Push feature.
1016     * <p>This API is for the Settings application.
1017     * @hide
1018     */
1019    public boolean enableNdefPush() {
1020        try {
1021            return sService.enableNdefPush();
1022        } catch (RemoteException e) {
1023            attemptDeadServiceRecovery(e);
1024            return false;
1025        }
1026    }
1027
1028    /**
1029     * Disable NDEF Push feature.
1030     * <p>This API is for the Settings application.
1031     * @hide
1032     */
1033    public boolean disableNdefPush() {
1034        try {
1035            return sService.disableNdefPush();
1036        } catch (RemoteException e) {
1037            attemptDeadServiceRecovery(e);
1038            return false;
1039        }
1040    }
1041
1042    /**
1043     * Return true if the NDEF Push (Android Beam) feature is enabled.
1044     * <p>This function will return true only if both NFC is enabled, and the
1045     * NDEF Push feature is enabled.
1046     * <p>Note that if NFC is enabled but NDEF Push is disabled then this
1047     * device can still <i>receive</i> NDEF messages, it just cannot send them.
1048     * <p>Applications cannot directly toggle the NDEF Push feature, but they
1049     * can request Settings UI allowing the user to toggle NDEF Push using
1050     * <code>startActivity(new Intent(Settings.ACTION_NFCSHARING_SETTINGS))</code>
1051     * <p>Example usage in an Activity that requires NDEF Push:
1052     * <p><pre>
1053     * protected void onResume() {
1054     *     super.onResume();
1055     *     if (!nfcAdapter.isEnabled()) {
1056     *         startActivity(new Intent(Settings.ACTION_NFC_SETTINGS));
1057     *     } else if (!nfcAdapter.isNdefPushEnabled()) {
1058     *         startActivity(new Intent(Settings.ACTION_NFCSHARING_SETTINGS));
1059     *     }
1060     * }
1061     * </pre>
1062     *
1063     * @see android.provider.Settings#ACTION_NFCSHARING_SETTINGS
1064     * @return true if NDEF Push feature is enabled
1065     */
1066    public boolean isNdefPushEnabled() {
1067        try {
1068            return sService.isNdefPushEnabled();
1069        } catch (RemoteException e) {
1070            attemptDeadServiceRecovery(e);
1071            return false;
1072        }
1073    }
1074
1075    /**
1076     * Inject a mock NFC tag.<p>
1077     * Used for testing purposes.
1078     * <p class="note">Requires the
1079     * {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} permission.
1080     * @hide
1081     */
1082    public void dispatch(Tag tag) {
1083        if (tag == null) {
1084            throw new NullPointerException("tag cannot be null");
1085        }
1086        try {
1087            sService.dispatch(tag);
1088        } catch (RemoteException e) {
1089            attemptDeadServiceRecovery(e);
1090        }
1091    }
1092
1093    /**
1094     * @hide
1095     */
1096    public void setP2pModes(int initiatorModes, int targetModes) {
1097        try {
1098            sService.setP2pModes(initiatorModes, targetModes);
1099        } catch (RemoteException e) {
1100            attemptDeadServiceRecovery(e);
1101        }
1102    }
1103
1104    /**
1105     * @hide
1106     */
1107    public INfcAdapterExtras getNfcAdapterExtrasInterface() {
1108        if (mContext == null) {
1109            throw new UnsupportedOperationException("You need a context on NfcAdapter to use the "
1110                    + " NFC extras APIs");
1111        }
1112        try {
1113            return sService.getNfcAdapterExtrasInterface(mContext.getPackageName());
1114        } catch (RemoteException e) {
1115            attemptDeadServiceRecovery(e);
1116            return null;
1117        }
1118    }
1119
1120    void enforceResumed(Activity activity) {
1121        if (!activity.isResumed()) {
1122            throw new IllegalStateException("API cannot be called while activity is paused");
1123        }
1124    }
1125
1126    int getSdkVersion() {
1127        if (mContext == null) {
1128            return android.os.Build.VERSION_CODES.GINGERBREAD; // best guess
1129        } else {
1130            return mContext.getApplicationInfo().targetSdkVersion;
1131        }
1132    }
1133}
1134