blob: 769e2d0a0cabe1a2fe615e0f20459e15d0f58d2c [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nfc;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.app.ActivityThread;
import android.content.Context;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.nfc.technology.TagTechnology;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
/**
* Represents the device's local NFC adapter.
* <p>
* Use the helper {@link #getDefaultAdapter(Context)} to get the default NFC
* adapter for this Android device.
*/
public final class NfcAdapter {
private static final String TAG = "NFC";
/**
* Intent to start an activity when a tag with NDEF payload is discovered.
* If the tag has and NDEF payload this intent is started before
* {@link #ACTION_TECHNOLOGY_DISCOVERED}.
*
* If any activities respond to this intent neither
* {@link #ACTION_TECHNOLOGY_DISCOVERED} or {@link #ACTION_TAG_DISCOVERED} will be started.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED";
/**
* Intent to started when a tag is discovered. The data URI is formated as
* {@code vnd.android.nfc://tag/} with the path having a directory entry for each technology
* in the {@link Tag#getTechnologyList()} is ascending order.
*
* This intent is started after {@link #ACTION_NDEF_DISCOVERED} and before
* {@link #ACTION_TAG_DISCOVERED}
*
* If any activities respond to this intent {@link #ACTION_TAG_DISCOVERED} will not be started.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_TECHNOLOGY_DISCOVERED = "android.nfc.action.TECH_DISCOVERED";
/**
* Intent to start an activity when a tag is discovered.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_TAG_DISCOVERED = "android.nfc.action.TAG_DISCOVERED";
/**
* Broadcast to only the activity that handles ACTION_TAG_DISCOVERED
* @hide
*/
public static final String ACTION_TAG_LEFT_FIELD = "android.nfc.action.TAG_LOST";
/**
* Mandatory Tag extra for the ACTION_TAG intents.
*/
public static final String EXTRA_TAG = "android.nfc.extra.TAG";
/**
* Optional NdefMessage[] extra for the ACTION_TAG intents.
*/
public static final String EXTRA_NDEF_MESSAGES = "android.nfc.extra.NDEF_MESSAGES";
/**
* Optional byte[] extra for the tag identifier.
*/
public static final String EXTRA_ID = "android.nfc.extra.ID";
/**
* Broadcast Action: a transaction with a secure element has been detected.
* <p>
* Always contains the extra field
* {@link android.nfc.NfcAdapter#EXTRA_AID}
* @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_TRANSACTION_DETECTED =
"android.nfc.action.TRANSACTION_DETECTED";
/**
* Broadcast Action: an RF field ON has been detected.
* @hide
*/
public static final String ACTION_RF_FIELD_ON_DETECTED =
"android.nfc.action.RF_FIELD_ON_DETECTED";
/**
* Broadcast Action: an RF Field OFF has been detected.
* @hide
*/
public static final String ACTION_RF_FIELD_OFF_DETECTED =
"android.nfc.action.RF_FIELD_OFF_DETECTED";
/**
* Broadcast Action: an adapter's state changed between enabled and disabled.
*
* The new value is stored in the extra EXTRA_NEW_BOOLEAN_STATE and just contains
* whether it's enabled or disabled, not including any information about whether it's
* actively enabling or disabling.
*
* @hide
*/
public static final String ACTION_ADAPTER_STATE_CHANGE =
"android.nfc.action.ADAPTER_STATE_CHANGE";
/**
* The Intent extra for ACTION_ADAPTER_STATE_CHANGE, saying what the new state is.
*
* @hide
*/
public static final String EXTRA_NEW_BOOLEAN_STATE = "android.nfc.isEnabled";
/**
* Mandatory byte array extra field in
* {@link android.nfc.NfcAdapter#ACTION_TRANSACTION_DETECTED}.
* <p>
* Contains the AID of the applet involved in the transaction.
* @hide
*/
public static final String EXTRA_AID = "android.nfc.extra.AID";
/**
* LLCP link status: The LLCP link is activated.
* @hide
*/
public static final int LLCP_LINK_STATE_ACTIVATED = 0;
/**
* LLCP link status: The LLCP link is deactivated.
* @hide
*/
public static final int LLCP_LINK_STATE_DEACTIVATED = 1;
/**
* Broadcast Action: the LLCP link state changed.
* <p>
* Always contains the extra field
* {@link android.nfc.NfcAdapter#EXTRA_LLCP_LINK_STATE_CHANGED}.
* @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_LLCP_LINK_STATE_CHANGED =
"android.nfc.action.LLCP_LINK_STATE_CHANGED";
/**
* Used as int extra field in
* {@link android.nfc.NfcAdapter#ACTION_LLCP_LINK_STATE_CHANGED}.
* <p>
* It contains the new state of the LLCP link.
* @hide
*/
public static final String EXTRA_LLCP_LINK_STATE_CHANGED = "android.nfc.extra.LLCP_LINK_STATE";
/**
* Tag Reader Discovery mode
* @hide
*/
private static final int DISCOVERY_MODE_TAG_READER = 0;
/**
* NFC-IP1 Peer-to-Peer mode Enables the manager to act as a peer in an
* NFC-IP1 communication. Implementations should not assume that the
* controller will end up behaving as an NFC-IP1 target or initiator and
* should handle both cases, depending on the type of the remote peer type.
* @hide
*/
private static final int DISCOVERY_MODE_NFCIP1 = 1;
/**
* Card Emulation mode Enables the manager to act as an NFC tag. Provided
* that a Secure Element (an UICC for instance) is connected to the NFC
* controller through its SWP interface, it can be exposed to the outside
* NFC world and be addressed by external readers the same way they would
* with a tag.
* <p>
* Which Secure Element is exposed is implementation-dependent.
*
* @hide
*/
private static final int DISCOVERY_MODE_CARD_EMULATION = 2;
// Guarded by NfcAdapter.class
private static boolean sIsInitialized = false;
// Final after first constructor, except for
// attemptDeadServiceRecovery() when NFC crashes - we accept a best effort
// recovery
private static INfcAdapter sService;
private static INfcTag sTagService;
private final Context mContext;
/**
* Helper to check if this device has FEATURE_NFC, but without using
* a context.
* Equivalent to
* context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)
*/
private static boolean hasNfcFeature() {
IPackageManager pm = ActivityThread.getPackageManager();
if (pm == null) {
Log.e(TAG, "Cannot get package manager, assuming no NFC feature");
return false;
}
try {
return pm.hasSystemFeature(PackageManager.FEATURE_NFC);
} catch (RemoteException e) {
Log.e(TAG, "Package manager query failed, assuming no NFC feature", e);
return false;
}
}
private static synchronized INfcAdapter setupService() {
if (!sIsInitialized) {
sIsInitialized = true;
/* is this device meant to have NFC */
if (!hasNfcFeature()) {
Log.v(TAG, "this device does not have NFC support");
return null;
}
sService = getServiceInterface();
if (sService == null) {
Log.e(TAG, "could not retrieve NFC service");
return null;
}
try {
sTagService = sService.getNfcTagInterface();
} catch (RemoteException e) {
Log.e(TAG, "could not retrieve NFC Tag service");
return null;
}
}
return sService;
}
/** get handle to NFC service interface */
private static INfcAdapter getServiceInterface() {
/* get a handle to NFC service */
IBinder b = ServiceManager.getService("nfc");
if (b == null) {
return null;
}
return INfcAdapter.Stub.asInterface(b);
}
/**
* Helper to get the default NFC Adapter.
* <p>
* Most Android devices will only have one NFC Adapter (NFC Controller).
* <p>
* This helper is the equivalent of:
* <pre>{@code
* NfcManager manager = (NfcManager) context.getSystemService(Context.NFC_SERVICE);
* NfcAdapter adapter = manager.getDefaultAdapter();
* }</pre>
* @param context the calling application's context
*
* @return the default NFC adapter, or null if no NFC adapter exists
*/
public static NfcAdapter getDefaultAdapter(Context context) {
/* use getSystemService() instead of just instantiating to take
* advantage of the context's cached NfcManager & NfcAdapter */
NfcManager manager = (NfcManager) context.getSystemService(Context.NFC_SERVICE);
return manager.getDefaultAdapter();
}
/**
* Get a handle to the default NFC Adapter on this Android device.
* <p>
* Most Android devices will only have one NFC Adapter (NFC Controller).
*
* @return the default NFC adapter, or null if no NFC adapter exists
* @deprecated use {@link #getDefaultAdapter(Context)}
*/
@Deprecated
public static NfcAdapter getDefaultAdapter() {
Log.w(TAG, "WARNING: NfcAdapter.getDefaultAdapter() is deprecated, use " +
"NfcAdapter.getDefaultAdapter(Context) instead", new Exception());
return new NfcAdapter(null);
}
/*package*/ NfcAdapter(Context context) {
if (setupService() == null) {
throw new UnsupportedOperationException();
}
mContext = context;
}
/**
* Returns the binder interface to the service.
* @hide
*/
public INfcAdapter getService() {
return sService;
}
/**
* Returns the binder interface to the tag service.
* @hide
*/
public INfcTag getTagService() {
return sTagService;
}
/**
* NFC service dead - attempt best effort recovery
* @hide
*/
public void attemptDeadServiceRecovery(Exception e) {
Log.e(TAG, "NFC service dead - attempting to recover", e);
INfcAdapter service = getServiceInterface();
if (service == null) {
Log.e(TAG, "could not retrieve NFC service during service recovery");
// nothing more can be done now, sService is still stale, we'll hit
// this recovery path again later
return;
}
// assigning to sService is not thread-safe, but this is best-effort code
// and on a well-behaved system should never happen
sService = service;
try {
sTagService = service.getNfcTagInterface();
} catch (RemoteException ee) {
Log.e(TAG, "could not retrieve NFC tag service during service recovery");
// nothing more can be done now, sService is still stale, we'll hit
// this recovery path again later
}
return;
}
/**
* Return true if this NFC Adapter has any features enabled.
* <p>
* If this method returns false, then applications should request the user
* turn on NFC tag discovery in Settings.
* <p>
* If this method returns false, the NFC hardware is guaranteed not to
* perform or respond to any NFC communication.
*
* @return true if this NFC Adapter is enabled to discover new tags
*/
public boolean isEnabled() {
try {
return sService.isEnabled();
} catch (RemoteException e) {
attemptDeadServiceRecovery(e);
return false;
}
}
/**
* Enable NFC hardware.
* <p>
* NOTE: may block for ~second or more. Poor API. Avoid
* calling from the UI thread.
*
* @hide
*/
public boolean enable() {
try {
return sService.enable();
} catch (RemoteException e) {
attemptDeadServiceRecovery(e);
return false;
}
}
/**
* Disable NFC hardware.
* No NFC features will work after this call, and the hardware
* will not perform or respond to any NFC communication.
* <p>
* NOTE: may block for ~second or more. Poor API. Avoid
* calling from the UI thread.
*
* @hide
*/
public boolean disable() {
try {
return sService.disable();
} catch (RemoteException e) {
attemptDeadServiceRecovery(e);
return false;
}
}
/**
* Retrieve a TagTechnology object used to interact with a Tag that is
* in field.
* <p>
* @return TagTechnology object, or null if not present
*/
public TagTechnology getTechnology(Tag tag, int tech) {
return tag.getTechnology(NfcAdapter.this, tech);
}
/**
* Set the NDEF Message that this NFC adapter should appear as to Tag
* readers.
* <p>
* Any Tag reader can read the contents of the local tag when it is in
* proximity, without any further user confirmation.
* <p>
* The implementation of this method must either
* <ul>
* <li>act as a passive tag containing this NDEF message
* <li>provide the NDEF message on over LLCP to peer NFC adapters
* </ul>
* The NDEF message is preserved across reboot.
* <p>Requires {@link android.Manifest.permission#NFC} permission.
*
* @param message NDEF message to make public
* @hide
*/
public void setLocalNdefMessage(NdefMessage message) {
try {
sService.localSet(message);
} catch (RemoteException e) {
attemptDeadServiceRecovery(e);
}
}
/**
* Get the NDEF Message that this adapter appears as to Tag readers.
* <p>Requires {@link android.Manifest.permission#NFC} permission.
*
* @return NDEF Message that is publicly readable
* @hide
*/
public NdefMessage getLocalNdefMessage() {
try {
return sService.localGet();
} catch (RemoteException e) {
attemptDeadServiceRecovery(e);
return null;
}
}
/**
* Create an Nfc Secure Element Connection
* @hide
*/
public NfcSecureElement createNfcSecureElementConnection() {
try {
return new NfcSecureElement(sService.getNfcSecureElementInterface());
} catch (RemoteException e) {
Log.e(TAG, "createNfcSecureElementConnection failed", e);
return null;
}
}
}