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