Home | History | Annotate | Download | only in nfc
      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 
     17 package android.nfc;
     18 
     19 import java.util.HashMap;
     20 
     21 import android.annotation.SdkConstant;
     22 import android.annotation.SdkConstant.SdkConstantType;
     23 import android.app.Activity;
     24 import android.app.ActivityThread;
     25 import android.app.OnActivityPausedListener;
     26 import android.app.PendingIntent;
     27 import android.content.Context;
     28 import android.content.IntentFilter;
     29 import android.content.pm.IPackageManager;
     30 import android.content.pm.PackageManager;
     31 import android.nfc.tech.MifareClassic;
     32 import android.nfc.tech.Ndef;
     33 import android.nfc.tech.NfcA;
     34 import android.nfc.tech.NfcF;
     35 import android.os.IBinder;
     36 import android.os.RemoteException;
     37 import android.os.ServiceManager;
     38 import android.util.Log;
     39 
     40 /**
     41  * Represents the local NFC adapter.
     42  * <p>
     43  * Use the helper {@link #getDefaultAdapter(Context)} to get the default NFC
     44  * adapter for this Android device.
     45  */
     46 public final class NfcAdapter {
     47     static final String TAG = "NFC";
     48 
     49     /**
     50      * Intent to start an activity when a tag with NDEF payload is discovered.
     51      *
     52      * <p>The system inspects the first {@link NdefRecord} in the first {@link NdefMessage} and
     53      * looks for a URI, SmartPoster, or MIME record. If a URI or SmartPoster record is found the
     54      * intent will contain the URI in its data field. If a MIME record is found the intent will
     55      * contain the MIME type in its type field. This allows activities to register
     56      * {@link IntentFilter}s targeting specific content on tags. Activities should register the
     57      * most specific intent filters possible to avoid the activity chooser dialog, which can
     58      * disrupt the interaction with the tag as the user interacts with the screen.
     59      *
     60      * <p>If the tag has an NDEF payload this intent is started before
     61      * {@link #ACTION_TECH_DISCOVERED}. If any activities respond to this intent neither
     62      * {@link #ACTION_TECH_DISCOVERED} or {@link #ACTION_TAG_DISCOVERED} will be started.
     63      */
     64     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     65     public static final String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED";
     66 
     67     /**
     68      * Intent to start an activity when a tag is discovered and activities are registered for the
     69      * specific technologies on the tag.
     70      *
     71      * <p>To receive this intent an activity must include an intent filter
     72      * for this action and specify the desired tech types in a
     73      * manifest <code>meta-data</code> entry. Here is an example manfiest entry:
     74      * <pre>
     75      *   &lt;activity android:name=".nfc.TechFilter" android:label="NFC/TechFilter"&gt;
     76      *       &lt;!-- Add a technology filter --&gt;
     77      *       &lt;intent-filter&gt;
     78      *           &lt;action android:name="android.nfc.action.TECH_DISCOVERED" /&gt;
     79      *       &lt;/intent-filter&gt;
     80      *
     81      *       &lt;meta-data android:name="android.nfc.action.TECH_DISCOVERED"
     82      *           android:resource="@xml/filter_nfc"
     83      *       /&gt;
     84      *   &lt;/activity&gt;
     85      * </pre>
     86      *
     87      * <p>The meta-data XML file should contain one or more <code>tech-list</code> entries
     88      * each consisting or one or more <code>tech</code> entries. The <code>tech</code> entries refer
     89      * to the qualified class name implementing the technology, for example "android.nfc.tech.NfcA".
     90      *
     91      * <p>A tag matches if any of the
     92      * <code>tech-list</code> sets is a subset of {@link Tag#getTechList() Tag.getTechList()}. Each
     93      * of the <code>tech-list</code>s is considered independently and the
     94      * activity is considered a match is any single <code>tech-list</code> matches the tag that was
     95      * discovered. This provides AND and OR semantics for filtering desired techs. Here is an
     96      * example that will match any tag using {@link NfcF} or any tag using {@link NfcA},
     97      * {@link MifareClassic}, and {@link Ndef}:
     98      *
     99      * <pre>
    100      * &lt;resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"&gt;
    101      *     &lt;!-- capture anything using NfcF --&gt;
    102      *     &lt;tech-list&gt;
    103      *         &lt;tech&gt;android.nfc.tech.NfcF&lt;/tech&gt;
    104      *     &lt;/tech-list&gt;
    105      *
    106      *     &lt;!-- OR --&gt;
    107      *
    108      *     &lt;!-- capture all MIFARE Classics with NDEF payloads --&gt;
    109      *     &lt;tech-list&gt;
    110      *         &lt;tech&gt;android.nfc.tech.NfcA&lt;/tech&gt;
    111      *         &lt;tech&gt;android.nfc.tech.MifareClassic&lt;/tech&gt;
    112      *         &lt;tech&gt;android.nfc.tech.Ndef&lt;/tech&gt;
    113      *     &lt;/tech-list&gt;
    114      * &lt;/resources&gt;
    115      * </pre>
    116      *
    117      * <p>This intent is started after {@link #ACTION_NDEF_DISCOVERED} and before
    118      * {@link #ACTION_TAG_DISCOVERED}. If any activities respond to {@link #ACTION_NDEF_DISCOVERED}
    119      * this intent will not be started. If any activities respond to this intent
    120      * {@link #ACTION_TAG_DISCOVERED} will not be started.
    121      */
    122     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
    123     public static final String ACTION_TECH_DISCOVERED = "android.nfc.action.TECH_DISCOVERED";
    124 
    125     /**
    126      * Intent to start an activity when a tag is discovered.
    127      *
    128      * <p>This intent will not be started when a tag is discovered if any activities respond to
    129      * {@link #ACTION_NDEF_DISCOVERED} or {@link #ACTION_TECH_DISCOVERED} for the current tag.
    130      */
    131     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
    132     public static final String ACTION_TAG_DISCOVERED = "android.nfc.action.TAG_DISCOVERED";
    133 
    134     /**
    135      * Broadcast to only the activity that handles ACTION_TAG_DISCOVERED
    136      * @hide
    137      */
    138     public static final String ACTION_TAG_LEFT_FIELD = "android.nfc.action.TAG_LOST";
    139 
    140     /**
    141      * Mandatory extra containing the {@link Tag} that was discovered for the
    142      * {@link #ACTION_NDEF_DISCOVERED}, {@link #ACTION_TECH_DISCOVERED}, and
    143      * {@link #ACTION_TAG_DISCOVERED} intents.
    144      */
    145     public static final String EXTRA_TAG = "android.nfc.extra.TAG";
    146 
    147     /**
    148      * Optional extra containing an array of {@link NdefMessage} present on the discovered tag for
    149      * the {@link #ACTION_NDEF_DISCOVERED}, {@link #ACTION_TECH_DISCOVERED}, and
    150      * {@link #ACTION_TAG_DISCOVERED} intents.
    151      */
    152     public static final String EXTRA_NDEF_MESSAGES = "android.nfc.extra.NDEF_MESSAGES";
    153 
    154     /**
    155      * Optional extra containing a byte array containing the ID of the discovered tag for
    156      * the {@link #ACTION_NDEF_DISCOVERED}, {@link #ACTION_TECH_DISCOVERED}, and
    157      * {@link #ACTION_TAG_DISCOVERED} intents.
    158      */
    159     public static final String EXTRA_ID = "android.nfc.extra.ID";
    160 
    161     /**
    162      * Broadcast Action: The state of the local NFC adapter has been
    163      * changed.
    164      * <p>For example, NFC has been turned on or off.
    165      * <p>Always contains the extra field {@link #EXTRA_STATE}
    166      * @hide
    167      */
    168     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
    169     public static final String ACTION_ADAPTER_STATE_CHANGED =
    170             "android.nfc.action.ADAPTER_STATE_CHANGED";
    171 
    172     /**
    173      * Used as an int extra field in {@link #ACTION_STATE_CHANGED}
    174      * intents to request the current power state. Possible values are:
    175      * {@link #STATE_OFF},
    176      * {@link #STATE_TURNING_ON},
    177      * {@link #STATE_ON},
    178      * {@link #STATE_TURNING_OFF},
    179      * @hide
    180      */
    181     public static final String EXTRA_ADAPTER_STATE = "android.nfc.extra.ADAPTER_STATE";
    182 
    183     /** @hide */
    184     public static final int STATE_OFF = 1;
    185     /** @hide */
    186     public static final int STATE_TURNING_ON = 2;
    187     /** @hide */
    188     public static final int STATE_ON = 3;
    189     /** @hide */
    190     public static final int STATE_TURNING_OFF = 4;
    191 
    192     // Guarded by NfcAdapter.class
    193     static boolean sIsInitialized = false;
    194 
    195     // Final after first constructor, except for
    196     // attemptDeadServiceRecovery() when NFC crashes - we accept a best effort
    197     // recovery
    198     static INfcAdapter sService;
    199     static INfcTag sTagService;
    200 
    201     /**
    202      * The NfcAdapter object for each application context.
    203      * There is a 1-1 relationship between application context and
    204      * NfcAdapter object.
    205      */
    206     static HashMap<Context, NfcAdapter> sNfcAdapters = new HashMap(); //guard by NfcAdapter.class
    207 
    208     /**
    209      * NfcAdapter used with a null context. This ctor was deprecated but we have
    210      * to support it for backwards compatibility. New methods that require context
    211      * might throw when called on the null-context NfcAdapter.
    212      */
    213     static NfcAdapter sNullContextNfcAdapter;  // protected by NfcAdapter.class
    214 
    215     final NfcActivityManager mNfcActivityManager;
    216     final Context mContext;
    217 
    218     /**
    219      * A callback to be invoked when the system successfully delivers your {@link NdefMessage}
    220      * to another device.
    221      * @see #setOnNdefPushCompleteCallback
    222      */
    223     public interface OnNdefPushCompleteCallback {
    224         /**
    225          * Called on successful NDEF push.
    226          *
    227          * <p>This callback is usually made on a binder thread (not the UI thread).
    228          *
    229          * @param event {@link NfcEvent} with the {@link NfcEvent#nfcAdapter} field set
    230          * @see #setNdefPushMessageCallback
    231          */
    232         public void onNdefPushComplete(NfcEvent event);
    233     }
    234 
    235     /**
    236      * A callback to be invoked when another NFC device capable of NDEF push (Android Beam)
    237      * is within range.
    238      * <p>Implement this interface and pass it to {@link
    239      * NfcAdapter#setNdefPushMessageCallback setNdefPushMessageCallback()} in order to create an
    240      * {@link NdefMessage} at the moment that another device is within range for NFC. Using this
    241      * callback allows you to create a message with data that might vary based on the
    242      * content currently visible to the user. Alternatively, you can call {@link
    243      * #setNdefPushMessage setNdefPushMessage()} if the {@link NdefMessage} always contains the
    244      * same data.
    245      */
    246     public interface CreateNdefMessageCallback {
    247         /**
    248          * Called to provide a {@link NdefMessage} to push.
    249          *
    250          * <p>This callback is usually made on a binder thread (not the UI thread).
    251          *
    252          * <p>Called when this device is in range of another device
    253          * that might support NDEF push. It allows the application to
    254          * create the NDEF message only when it is required.
    255          *
    256          * <p>NDEF push cannot occur until this method returns, so do not
    257          * block for too long.
    258          *
    259          * <p>The Android operating system will usually show a system UI
    260          * on top of your activity during this time, so do not try to request
    261          * input from the user to complete the callback, or provide custom NDEF
    262          * push UI. The user probably will not see it.
    263          *
    264          * @param event {@link NfcEvent} with the {@link NfcEvent#nfcAdapter} field set
    265          * @return NDEF message to push, or null to not provide a message
    266          */
    267         public NdefMessage createNdefMessage(NfcEvent event);
    268     }
    269 
    270     /**
    271      * Helper to check if this device has FEATURE_NFC, but without using
    272      * a context.
    273      * Equivalent to
    274      * context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)
    275      */
    276     private static boolean hasNfcFeature() {
    277         IPackageManager pm = ActivityThread.getPackageManager();
    278         if (pm == null) {
    279             Log.e(TAG, "Cannot get package manager, assuming no NFC feature");
    280             return false;
    281         }
    282         try {
    283             return pm.hasSystemFeature(PackageManager.FEATURE_NFC);
    284         } catch (RemoteException e) {
    285             Log.e(TAG, "Package manager query failed, assuming no NFC feature", e);
    286             return false;
    287         }
    288     }
    289 
    290     /**
    291      * Returns the NfcAdapter for application context,
    292      * or throws if NFC is not available.
    293      * @hide
    294      */
    295     public static synchronized NfcAdapter getNfcAdapter(Context context) {
    296         if (!sIsInitialized) {
    297             /* is this device meant to have NFC */
    298             if (!hasNfcFeature()) {
    299                 Log.v(TAG, "this device does not have NFC support");
    300                 throw new UnsupportedOperationException();
    301             }
    302 
    303             sService = getServiceInterface();
    304             if (sService == null) {
    305                 Log.e(TAG, "could not retrieve NFC service");
    306                 throw new UnsupportedOperationException();
    307             }
    308             try {
    309                 sTagService = sService.getNfcTagInterface();
    310             } catch (RemoteException e) {
    311                 Log.e(TAG, "could not retrieve NFC Tag service");
    312                 throw new UnsupportedOperationException();
    313             }
    314 
    315             sIsInitialized = true;
    316         }
    317         if (context == null) {
    318             if (sNullContextNfcAdapter == null) {
    319                 sNullContextNfcAdapter = new NfcAdapter(null);
    320             }
    321             return sNullContextNfcAdapter;
    322         }
    323         NfcAdapter adapter = sNfcAdapters.get(context);
    324         if (adapter == null) {
    325             adapter = new NfcAdapter(context);
    326             sNfcAdapters.put(context, adapter);
    327         }
    328         return adapter;
    329     }
    330 
    331     /** get handle to NFC service interface */
    332     private static INfcAdapter getServiceInterface() {
    333         /* get a handle to NFC service */
    334         IBinder b = ServiceManager.getService("nfc");
    335         if (b == null) {
    336             return null;
    337         }
    338         return INfcAdapter.Stub.asInterface(b);
    339     }
    340 
    341     /**
    342      * Helper to get the default NFC Adapter.
    343      * <p>
    344      * Most Android devices will only have one NFC Adapter (NFC Controller).
    345      * <p>
    346      * This helper is the equivalent of:
    347      * <pre>{@code
    348      * NfcManager manager = (NfcManager) context.getSystemService(Context.NFC_SERVICE);
    349      * NfcAdapter adapter = manager.getDefaultAdapter();
    350      * }</pre>
    351      * @param context the calling application's context
    352      *
    353      * @return the default NFC adapter, or null if no NFC adapter exists
    354      */
    355     public static NfcAdapter getDefaultAdapter(Context context) {
    356         if (context == null) {
    357             throw new IllegalArgumentException("context cannot be null");
    358         }
    359         context = context.getApplicationContext();
    360         /* use getSystemService() instead of just instantiating to take
    361          * advantage of the context's cached NfcManager & NfcAdapter */
    362         NfcManager manager = (NfcManager) context.getSystemService(Context.NFC_SERVICE);
    363         if (manager == null) {
    364             // NFC not available
    365             return null;
    366         }
    367         return manager.getDefaultAdapter();
    368     }
    369 
    370     /**
    371      * Legacy NfcAdapter getter, always use {@link #getDefaultAdapter(Context)} instead.<p>
    372      * This method was deprecated at API level 10 (Gingerbread MR1) because a context is required
    373      * for many NFC API methods. Those methods will fail when called on an NfcAdapter
    374      * object created from this method.<p>
    375      * @deprecated use {@link #getDefaultAdapter(Context)}
    376      */
    377     @Deprecated
    378     public static NfcAdapter getDefaultAdapter() {
    379         Log.w(TAG, "WARNING: NfcAdapter.getDefaultAdapter() is deprecated, use " +
    380                 "NfcAdapter.getDefaultAdapter(Context) instead", new Exception());
    381 
    382         return NfcAdapter.getNfcAdapter(null);
    383     }
    384 
    385     NfcAdapter(Context context) {
    386         mContext = context;
    387         mNfcActivityManager = new NfcActivityManager(this);
    388     }
    389 
    390     /**
    391      * @hide
    392      */
    393     public Context getContext() {
    394         return mContext;
    395     }
    396 
    397     /**
    398      * Returns the binder interface to the service.
    399      * @hide
    400      */
    401     public INfcAdapter getService() {
    402         isEnabled();  // NOP call to recover sService if it is stale
    403         return sService;
    404     }
    405 
    406     /**
    407      * Returns the binder interface to the tag service.
    408      * @hide
    409      */
    410     public INfcTag getTagService() {
    411         isEnabled();  // NOP call to recover sTagService if it is stale
    412         return sTagService;
    413     }
    414 
    415     /**
    416      * NFC service dead - attempt best effort recovery
    417      * @hide
    418      */
    419     public void attemptDeadServiceRecovery(Exception e) {
    420         Log.e(TAG, "NFC service dead - attempting to recover", e);
    421         INfcAdapter service = getServiceInterface();
    422         if (service == null) {
    423             Log.e(TAG, "could not retrieve NFC service during service recovery");
    424             // nothing more can be done now, sService is still stale, we'll hit
    425             // this recovery path again later
    426             return;
    427         }
    428         // assigning to sService is not thread-safe, but this is best-effort code
    429         // and on a well-behaved system should never happen
    430         sService = service;
    431         try {
    432             sTagService = service.getNfcTagInterface();
    433         } catch (RemoteException ee) {
    434             Log.e(TAG, "could not retrieve NFC tag service during service recovery");
    435             // nothing more can be done now, sService is still stale, we'll hit
    436             // this recovery path again later
    437         }
    438 
    439         return;
    440     }
    441 
    442     /**
    443      * Return true if this NFC Adapter has any features enabled.
    444      *
    445      * <p>Application may use this as a helper to suggest that the user
    446      * should turn on NFC in Settings.
    447      * <p>If this method returns false, the NFC hardware is guaranteed not to
    448      * generate or respond to any NFC transactions.
    449      *
    450      * @return true if this NFC Adapter has any features enabled
    451      */
    452     public boolean isEnabled() {
    453         try {
    454             return sService.getState() == STATE_ON;
    455         } catch (RemoteException e) {
    456             attemptDeadServiceRecovery(e);
    457             return false;
    458         }
    459     }
    460 
    461     /**
    462      * Return the state of this NFC Adapter.
    463      *
    464      * <p>Returns one of {@link #STATE_ON}, {@link #STATE_TURNING_ON},
    465      * {@link #STATE_OFF}, {@link #STATE_TURNING_OFF}.
    466      *
    467      * <p>{@link #isEnabled()} is equivalent to
    468      * <code>{@link #getAdapterState()} == {@link #STATE_ON}</code>
    469      *
    470      * @return the current state of this NFC adapter
    471      *
    472      * @hide
    473      */
    474     public int getAdapterState() {
    475         try {
    476             return sService.getState();
    477         } catch (RemoteException e) {
    478             attemptDeadServiceRecovery(e);
    479             return NfcAdapter.STATE_OFF;
    480         }
    481     }
    482 
    483     /**
    484      * Enable NFC hardware.
    485      *
    486      * <p>This call is asynchronous. Listen for
    487      * {@link #ACTION_ADAPTER_STATE_CHANGED} broadcasts to find out when the
    488      * operation is complete.
    489      *
    490      * <p>If this returns true, then either NFC is already on, or
    491      * a {@link #ACTION_ADAPTER_STATE_CHANGED} broadcast will be sent
    492      * to indicate a state transition. If this returns false, then
    493      * there is some problem that prevents an attempt to turn
    494      * NFC on (for example we are in airplane mode and NFC is not
    495      * toggleable in airplane mode on this platform).
    496      *
    497      * @hide
    498      */
    499     public boolean enable() {
    500         try {
    501             return sService.enable();
    502         } catch (RemoteException e) {
    503             attemptDeadServiceRecovery(e);
    504             return false;
    505         }
    506     }
    507 
    508     /**
    509      * Disable NFC hardware.
    510      *
    511      * <p>No NFC features will work after this call, and the hardware
    512      * will not perform or respond to any NFC communication.
    513      *
    514      * <p>This call is asynchronous. Listen for
    515      * {@link #ACTION_ADAPTER_STATE_CHANGED} broadcasts to find out when the
    516      * operation is complete.
    517      *
    518      * <p>If this returns true, then either NFC is already off, or
    519      * a {@link #ACTION_ADAPTER_STATE_CHANGED} broadcast will be sent
    520      * to indicate a state transition. If this returns false, then
    521      * there is some problem that prevents an attempt to turn
    522      * NFC off.
    523      *
    524      * @hide
    525      */
    526     public boolean disable() {
    527         try {
    528             return sService.disable();
    529         } catch (RemoteException e) {
    530             attemptDeadServiceRecovery(e);
    531             return false;
    532         }
    533     }
    534 
    535     /**
    536      * Set the {@link NdefMessage} to push over NFC during the specified activities.
    537      *
    538      * <p>This method may be called at any time, but the NDEF message is
    539      * only made available for NDEF push when one of the specified activities
    540      * is in resumed (foreground) state.
    541      *
    542      * <p>Only one NDEF message can be pushed by the currently resumed activity.
    543      * If both {@link #setNdefPushMessage} and
    544      * {@link #setNdefPushMessageCallback} are set then
    545      * the callback will take priority.
    546      *
    547      * <p>Pass a null NDEF message to disable foreground NDEF push in the
    548      * specified activities.
    549      *
    550      * <p>At least one activity must be specified, and usually only one is necessary.
    551      *
    552      * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
    553      *
    554      * @param message NDEF message to push over NFC, or null to disable
    555      * @param activity an activity in which NDEF push should be enabled to share the provided
    556      *                 NDEF message
    557      * @param activities optional additional activities that should also enable NDEF push with
    558      *                   the provided NDEF message
    559      */
    560     public void setNdefPushMessage(NdefMessage message, Activity activity,
    561             Activity ... activities) {
    562         if (activity == null) {
    563             throw new NullPointerException("activity cannot be null");
    564         }
    565         mNfcActivityManager.setNdefPushMessage(activity, message);
    566         for (Activity a : activities) {
    567             if (a == null) {
    568                 throw new NullPointerException("activities cannot contain null");
    569             }
    570             mNfcActivityManager.setNdefPushMessage(a, message);
    571         }
    572     }
    573 
    574     /**
    575      * Set the callback to create a {@link NdefMessage} to push over NFC.
    576      *
    577      * <p>This method may be called at any time, but this callback is
    578      * only made if one of the specified activities
    579      * is in resumed (foreground) state.
    580      *
    581      * <p>Only one NDEF message can be pushed by the currently resumed activity.
    582      * If both {@link #setNdefPushMessage} and
    583      * {@link #setNdefPushMessageCallback} are set then
    584      * the callback will take priority.
    585      *
    586      * <p>Pass a null callback to disable the callback in the
    587      * specified activities.
    588      *
    589      * <p>At least one activity must be specified, and usually only one is necessary.
    590      *
    591      * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
    592      *
    593      * @param callback callback, or null to disable
    594      * @param activity an activity in which NDEF push should be enabled to share an NDEF message
    595      *                 that's retrieved from the provided callback
    596      * @param activities optional additional activities that should also enable NDEF push using
    597      *                   the provided callback
    598      */
    599     public void setNdefPushMessageCallback(CreateNdefMessageCallback callback, Activity activity,
    600             Activity ... activities) {
    601         if (activity == null) {
    602             throw new NullPointerException("activity cannot be null");
    603         }
    604         mNfcActivityManager.setNdefPushMessageCallback(activity, callback);
    605         for (Activity a : activities) {
    606             if (a == null) {
    607                 throw new NullPointerException("activities cannot contain null");
    608             }
    609             mNfcActivityManager.setNdefPushMessageCallback(a, callback);
    610         }
    611     }
    612 
    613     /**
    614      * Set the callback on a successful NDEF push over NFC.
    615      *
    616      * <p>This method may be called at any time, but NDEF push and this callback
    617      * can only occur when one of the specified activities is in resumed
    618      * (foreground) state.
    619      *
    620      * <p>One or more activities must be specified.
    621      *
    622      * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
    623      *
    624      * @param callback callback, or null to disable
    625      * @param activity an activity to enable the callback (at least one is required)
    626      * @param activities zero or more additional activities to enable to callback
    627      */
    628     public void setOnNdefPushCompleteCallback(OnNdefPushCompleteCallback callback,
    629             Activity activity, Activity ... activities) {
    630         if (activity == null) {
    631             throw new NullPointerException("activity cannot be null");
    632         }
    633         mNfcActivityManager.setOnNdefPushCompleteCallback(activity, callback);
    634         for (Activity a : activities) {
    635             if (a == null) {
    636                 throw new NullPointerException("activities cannot contain null");
    637             }
    638             mNfcActivityManager.setOnNdefPushCompleteCallback(a, callback);
    639         }
    640     }
    641 
    642     /**
    643      * Enable foreground dispatch to the given Activity.
    644      *
    645      * <p>This will give give priority to the foreground activity when
    646      * dispatching a discovered {@link Tag} to an application.
    647      *
    648      * <p>If any IntentFilters are provided to this method they are used to match dispatch Intents
    649      * for both the {@link NfcAdapter#ACTION_NDEF_DISCOVERED} and
    650      * {@link NfcAdapter#ACTION_TAG_DISCOVERED}. Since {@link NfcAdapter#ACTION_TECH_DISCOVERED}
    651      * relies on meta data outside of the IntentFilter matching for that dispatch Intent is handled
    652      * by passing in the tech lists separately. Each first level entry in the tech list represents
    653      * an array of technologies that must all be present to match. If any of the first level sets
    654      * match then the dispatch is routed through the given PendingIntent. In other words, the second
    655      * level is ANDed together and the first level entries are ORed together.
    656      *
    657      * <p>If you pass {@code null} for both the {@code filters} and {@code techLists} parameters
    658      * that acts a wild card and will cause the foreground activity to receive all tags via the
    659      * {@link NfcAdapter#ACTION_TAG_DISCOVERED} intent.
    660      *
    661      * <p>This method must be called from the main thread, and only when the activity is in the
    662      * foreground (resumed). Also, activities must call {@link #disableForegroundDispatch} before
    663      * the completion of their {@link Activity#onPause} callback to disable foreground dispatch
    664      * after it has been enabled.
    665      *
    666      * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
    667      *
    668      * @param activity the Activity to dispatch to
    669      * @param intent the PendingIntent to start for the dispatch
    670      * @param filters the IntentFilters to override dispatching for, or null to always dispatch
    671      * @param techLists the tech lists used to perform matching for dispatching of the
    672      *      {@link NfcAdapter#ACTION_TECH_DISCOVERED} intent
    673      * @throws IllegalStateException if the Activity is not currently in the foreground
    674      */
    675     public void enableForegroundDispatch(Activity activity, PendingIntent intent,
    676             IntentFilter[] filters, String[][] techLists) {
    677         if (activity == null || intent == null) {
    678             throw new NullPointerException();
    679         }
    680         if (!activity.isResumed()) {
    681             throw new IllegalStateException("Foreground dispatch can only be enabled " +
    682                     "when your activity is resumed");
    683         }
    684         try {
    685             TechListParcel parcel = null;
    686             if (techLists != null && techLists.length > 0) {
    687                 parcel = new TechListParcel(techLists);
    688             }
    689             ActivityThread.currentActivityThread().registerOnActivityPausedListener(activity,
    690                     mForegroundDispatchListener);
    691             sService.setForegroundDispatch(intent, filters, parcel);
    692         } catch (RemoteException e) {
    693             attemptDeadServiceRecovery(e);
    694         }
    695     }
    696 
    697     /**
    698      * Disable foreground dispatch to the given activity.
    699      *
    700      * <p>After calling {@link #enableForegroundDispatch}, an activity
    701      * must call this method before its {@link Activity#onPause} callback
    702      * completes.
    703      *
    704      * <p>This method must be called from the main thread.
    705      *
    706      * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
    707      *
    708      * @param activity the Activity to disable dispatch to
    709      * @throws IllegalStateException if the Activity has already been paused
    710      */
    711     public void disableForegroundDispatch(Activity activity) {
    712         ActivityThread.currentActivityThread().unregisterOnActivityPausedListener(activity,
    713                 mForegroundDispatchListener);
    714         disableForegroundDispatchInternal(activity, false);
    715     }
    716 
    717     OnActivityPausedListener mForegroundDispatchListener = new OnActivityPausedListener() {
    718         @Override
    719         public void onPaused(Activity activity) {
    720             disableForegroundDispatchInternal(activity, true);
    721         }
    722     };
    723 
    724     void disableForegroundDispatchInternal(Activity activity, boolean force) {
    725         try {
    726             sService.setForegroundDispatch(null, null, null);
    727             if (!force && !activity.isResumed()) {
    728                 throw new IllegalStateException("You must disable foreground dispatching " +
    729                         "while your activity is still resumed");
    730             }
    731         } catch (RemoteException e) {
    732             attemptDeadServiceRecovery(e);
    733         }
    734     }
    735 
    736     /**
    737      * Enable NDEF message push over NFC while this Activity is in the foreground.
    738      *
    739      * <p>You must explicitly call this method every time the activity is
    740      * resumed, and you must call {@link #disableForegroundNdefPush} before
    741      * your activity completes {@link Activity#onPause}.
    742      *
    743      * <p>Strongly recommend to use the new {@link #setNdefPushMessage}
    744      * instead: it automatically hooks into your activity life-cycle,
    745      * so you do not need to call enable/disable in your onResume/onPause.
    746      *
    747      * <p>For NDEF push to function properly the other NFC device must
    748      * support either NFC Forum's SNEP (Simple Ndef Exchange Protocol), or
    749      * Android's "com.android.npp" (Ndef Push Protocol). This was optional
    750      * on Gingerbread level Android NFC devices, but SNEP is mandatory on
    751      * Ice-Cream-Sandwich and beyond.
    752      *
    753      * <p>This method must be called from the main thread.
    754      *
    755      * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
    756      *
    757      * @param activity foreground activity
    758      * @param message a NDEF Message to push over NFC
    759      * @throws IllegalStateException if the activity is not currently in the foreground
    760      * @deprecated use {@link #setNdefPushMessage} instead
    761      */
    762     @Deprecated
    763     public void enableForegroundNdefPush(Activity activity, NdefMessage message) {
    764         if (activity == null || message == null) {
    765             throw new NullPointerException();
    766         }
    767         enforceResumed(activity);
    768         mNfcActivityManager.setNdefPushMessage(activity, message);
    769     }
    770 
    771     /**
    772      * Disable NDEF message push over P2P.
    773      *
    774      * <p>After calling {@link #enableForegroundNdefPush}, an activity
    775      * must call this method before its {@link Activity#onPause} callback
    776      * completes.
    777      *
    778      * <p>Strongly recommend to use the new {@link #setNdefPushMessage}
    779      * instead: it automatically hooks into your activity life-cycle,
    780      * so you do not need to call enable/disable in your onResume/onPause.
    781      *
    782      * <p>This method must be called from the main thread.
    783      *
    784      * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
    785      *
    786      * @param activity the Foreground activity
    787      * @throws IllegalStateException if the Activity has already been paused
    788      * @deprecated use {@link #setNdefPushMessage} instead
    789      */
    790     public void disableForegroundNdefPush(Activity activity) {
    791         if (activity == null) {
    792             throw new NullPointerException();
    793         }
    794         enforceResumed(activity);
    795         mNfcActivityManager.setNdefPushMessage(activity, null);
    796         mNfcActivityManager.setNdefPushMessageCallback(activity, null);
    797         mNfcActivityManager.setOnNdefPushCompleteCallback(activity, null);
    798     }
    799 
    800     /**
    801      * TODO: Remove this once pre-built apk's (Maps, Youtube etc) are updated
    802      * @deprecated use {@link CreateNdefMessageCallback} or {@link OnNdefPushCompleteCallback}
    803      * @hide
    804      */
    805     @Deprecated
    806     public interface NdefPushCallback {
    807         /**
    808          * @deprecated use {@link CreateNdefMessageCallback} instead
    809          */
    810         @Deprecated
    811         NdefMessage createMessage();
    812         /**
    813          * @deprecated use{@link OnNdefPushCompleteCallback} instead
    814          */
    815         @Deprecated
    816         void onMessagePushed();
    817     }
    818 
    819     /**
    820      * TODO: Remove this
    821      * Converts new callbacks to old callbacks.
    822      */
    823     static final class LegacyCallbackWrapper implements CreateNdefMessageCallback,
    824             OnNdefPushCompleteCallback {
    825         final NdefPushCallback mLegacyCallback;
    826         LegacyCallbackWrapper(NdefPushCallback legacyCallback) {
    827             mLegacyCallback = legacyCallback;
    828         }
    829         @Override
    830         public void onNdefPushComplete(NfcEvent event) {
    831             mLegacyCallback.onMessagePushed();
    832         }
    833         @Override
    834         public NdefMessage createNdefMessage(NfcEvent event) {
    835             return mLegacyCallback.createMessage();
    836         }
    837     }
    838 
    839     /**
    840      * TODO: Remove this once pre-built apk's (Maps, Youtube etc) are updated
    841      * @deprecated use {@link #setNdefPushMessageCallback} instead
    842      * @hide
    843      */
    844     @Deprecated
    845     public void enableForegroundNdefPush(Activity activity, final NdefPushCallback callback) {
    846         if (activity == null || callback == null) {
    847             throw new NullPointerException();
    848         }
    849         enforceResumed(activity);
    850         LegacyCallbackWrapper callbackWrapper = new LegacyCallbackWrapper(callback);
    851         mNfcActivityManager.setNdefPushMessageCallback(activity, callbackWrapper);
    852         mNfcActivityManager.setOnNdefPushCompleteCallback(activity, callbackWrapper);
    853     }
    854 
    855     /**
    856      * Enable NDEF Push feature.
    857      * <p>This API is for the Settings application.
    858      * @hide
    859      */
    860     public boolean enableNdefPush() {
    861         try {
    862             return sService.enableNdefPush();
    863         } catch (RemoteException e) {
    864             attemptDeadServiceRecovery(e);
    865             return false;
    866         }
    867     }
    868 
    869     /**
    870      * Disable NDEF Push feature.
    871      * <p>This API is for the Settings application.
    872      * @hide
    873      */
    874     public boolean disableNdefPush() {
    875         try {
    876             return sService.disableNdefPush();
    877         } catch (RemoteException e) {
    878             attemptDeadServiceRecovery(e);
    879             return false;
    880         }
    881     }
    882 
    883     /**
    884      * Return true if NDEF Push feature is enabled.
    885      * <p>This function can return true even if NFC is currently turned-off.
    886      * This indicates that NDEF Push is not currently active, but it has
    887      * been requested by the user and will be active as soon as NFC is turned
    888      * on.
    889      * <p>If you want to check if NDEF PUsh sharing is currently active, use
    890      * <code>{@link #isEnabled()} && {@link #isNdefPushEnabled()}</code>
    891      *
    892      * @return true if NDEF Push feature is enabled
    893      * @hide
    894      */
    895     public boolean isNdefPushEnabled() {
    896         try {
    897             return sService.isNdefPushEnabled();
    898         } catch (RemoteException e) {
    899             attemptDeadServiceRecovery(e);
    900             return false;
    901         }
    902     }
    903 
    904     /**
    905      * @hide
    906      */
    907     public INfcAdapterExtras getNfcAdapterExtrasInterface() {
    908         if (mContext == null) {
    909             throw new UnsupportedOperationException("You need a context on NfcAdapter to use the "
    910                     + " NFC extras APIs");
    911         }
    912         try {
    913             return sService.getNfcAdapterExtrasInterface(mContext.getPackageName());
    914         } catch (RemoteException e) {
    915             attemptDeadServiceRecovery(e);
    916             return null;
    917         }
    918     }
    919 
    920     void enforceResumed(Activity activity) {
    921         if (!activity.isResumed()) {
    922             throw new IllegalStateException("API cannot be called while activity is paused");
    923         }
    924     }
    925 }
    926