blob: d30a0c68f8fe4607528277a24d6b38614fdbb21e [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.Activity;
import android.app.ActivityThread;
import android.app.OnActivityPausedListener;
import android.app.PendingIntent;
import android.content.Context;
import android.content.IntentFilter;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.os.IBinder;
import android.os.Parcel;
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_TECH_DISCOVERED}.
*
* If any activities respond to this intent neither
* {@link #ACTION_TECH_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#getTechList()} is sorted 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_TECH_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;
/**
* 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();
}
}
/**
* Returns the binder interface to the service.
* @hide
*/
public INfcAdapter getService() {
isEnabled(); // NOP call to recover sService if it is stale
return sService;
}
/**
* Returns the binder interface to the tag service.
* @hide
*/
public INfcTag getTagService() {
isEnabled(); // NOP call to recover sTagService if it is stale
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;
}
}
/**
* Enables foreground dispatching to the given Activity. This will force all NFC Intents that
* match the given filters to be delivered to the activity bypassing the standard dispatch
* mechanism. If no IntentFilters are given all the PendingIntent will be invoked for every
* dispatch Intent.
*
* This method must be called from the main thread.
*
* @param activity the Activity to dispatch to
* @param intent the PendingIntent to start for the dispatch
* @param filters the IntentFilters to override dispatching for, or null to always dispatch
* @throws IllegalStateException
*/
public void enableForegroundDispatch(Activity activity, PendingIntent intent,
IntentFilter[] filters, String[][] techLists) {
if (activity == null || intent == null) {
throw new NullPointerException();
}
if (!activity.isResumed()) {
throw new IllegalStateException("Foregorund dispatching can only be enabled " +
"when your activity is resumed");
}
try {
TechListParcel parcel = null;
if (techLists != null && techLists.length > 0) {
parcel = new TechListParcel(techLists);
}
ActivityThread.currentActivityThread().registerOnActivityPausedListener(activity,
mForegroundDispatchListener);
sService.enableForegroundDispatch(activity.getComponentName(), intent, filters,
parcel);
} catch (RemoteException e) {
attemptDeadServiceRecovery(e);
}
}
/**
* Disables foreground activity dispatching setup with
* {@link #enableForegroundDispatch}.
*
* <p>This must be called before the Activity returns from
* it's <code>onPause()</code> or this method will throw an IllegalStateException.
*
* <p>This method must be called from the main thread.
*/
public void disableForegroundDispatch(Activity activity) {
ActivityThread.currentActivityThread().unregisterOnActivityPausedListener(activity,
mForegroundDispatchListener);
disableForegroundDispatchInternal(activity, false);
}
OnActivityPausedListener mForegroundDispatchListener = new OnActivityPausedListener() {
@Override
public void onPaused(Activity activity) {
disableForegroundDispatchInternal(activity, true);
}
};
void disableForegroundDispatchInternal(Activity activity, boolean force) {
try {
sService.disableForegroundDispatch(activity.getComponentName());
if (!force && !activity.isResumed()) {
throw new IllegalStateException("You must disable forgeground dispatching " +
"while your activity is still resumed");
}
} catch (RemoteException e) {
attemptDeadServiceRecovery(e);
}
}
/**
* Enable NDEF message push over P2P while this Activity is in the foreground. For this to
* function properly the other NFC device being scanned must support the "com.android.npp"
* NDEF push protocol.
*
* <p><em>NOTE</em> While foreground NDEF push is active standard tag dispatch is disabled.
* Only the foreground activity may receive tag discovered dispatches via
* {@link #enableForegroundDispatch}.
*/
public void enableForegroundNdefPush(Activity activity, NdefMessage msg) {
if (activity == null || msg == null) {
throw new NullPointerException();
}
if (!activity.isResumed()) {
throw new IllegalStateException("Foregorund NDEF push can only be enabled " +
"when your activity is resumed");
}
try {
ActivityThread.currentActivityThread().registerOnActivityPausedListener(activity,
mForegroundNdefPushListener);
sService.enableForegroundNdefPush(activity.getComponentName(), msg);
} catch (RemoteException e) {
attemptDeadServiceRecovery(e);
}
}
/**
* Disables foreground NDEF push setup with
* {@link #enableForegroundNdefPush}.
*
* <p>This must be called before the Activity returns from
* it's <code>onPause()</code> or this method will throw an IllegalStateException.
*
* <p>This method must be called from the main thread.
*/
public void disableForegroundNdefPush(Activity activity) {
ActivityThread.currentActivityThread().unregisterOnActivityPausedListener(activity,
mForegroundNdefPushListener);
disableForegroundNdefPushInternal(activity, false);
}
OnActivityPausedListener mForegroundNdefPushListener = new OnActivityPausedListener() {
@Override
public void onPaused(Activity activity) {
disableForegroundNdefPushInternal(activity, true);
}
};
void disableForegroundNdefPushInternal(Activity activity, boolean force) {
try {
sService.disableForegroundNdefPush(activity.getComponentName());
if (!force && !activity.isResumed()) {
throw new IllegalStateException("You must disable forgeground NDEF push " +
"while your activity is still resumed");
}
} catch (RemoteException e) {
attemptDeadServiceRecovery(e);
}
}
/**
* 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;
}
}
}