nfc(api): Move nfc classes to framework-nfc
Splits out the NFC API classes that are going to be part of NFC mainline
module.
Note: These files will be eventually moved out to packages/modules/Nfc
at some point.
Bug: 303286040
Test: Device boots up after flashing
Test: atest CtsNfcTestCases
Change-Id: I41c1146401236963b9fd83f214fed0b6cecf325e
diff --git a/nfc/Android.bp b/nfc/Android.bp
index 4333374..3c2efe7 100644
--- a/nfc/Android.bp
+++ b/nfc/Android.bp
@@ -10,7 +10,15 @@
filegroup {
name: "framework-nfc-non-updatable-sources",
path: "java",
- srcs: [],
+ srcs: [
+ "java/android/nfc/NfcServiceManager.java",
+ "java/android/nfc/cardemulation/ApduServiceInfo.aidl",
+ "java/android/nfc/cardemulation/ApduServiceInfo.java",
+ "java/android/nfc/cardemulation/NfcFServiceInfo.aidl",
+ "java/android/nfc/cardemulation/NfcFServiceInfo.java",
+ "java/android/nfc/cardemulation/AidGroup.aidl",
+ "java/android/nfc/cardemulation/AidGroup.java",
+ ],
}
filegroup {
@@ -30,12 +38,17 @@
libs: [
"unsupportedappusage", // for android.compat.annotation.UnsupportedAppUsage
],
+ static_libs: [
+ "android.nfc.flags-aconfig-java",
+ "android.permission.flags-aconfig-java",
+ ],
srcs: [
":framework-nfc-updatable-sources",
+ ":framework-nfc-javastream-protos",
],
defaults: ["framework-module-defaults"],
sdk_version: "module_current",
- min_sdk_version: "VanillaIceCream",
+ min_sdk_version: "34", // should be 35 (making it 34 for compiling for `-next`)
installable: true,
optimize: {
enabled: false,
@@ -48,4 +61,15 @@
hidden_api_packages: [
"com.android.nfc",
],
+ impl_library_visibility: [
+ "//frameworks/base:__subpackages__",
+ "//cts/tests/tests/nfc",
+ "//packages/apps/Nfc:__subpackages__",
+ ],
+ jarjar_rules: ":nfc-jarjar-rules",
+}
+
+filegroup {
+ name: "nfc-jarjar-rules",
+ srcs: ["jarjar-rules.txt"],
}
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index d802177..24c145f 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -1 +1,455 @@
// Signature format: 2.0
+package android.nfc {
+
+ public final class AvailableNfcAntenna implements android.os.Parcelable {
+ ctor public AvailableNfcAntenna(int, int);
+ method public int describeContents();
+ method public int getLocationX();
+ method public int getLocationY();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.nfc.AvailableNfcAntenna> CREATOR;
+ }
+
+ public class FormatException extends java.lang.Exception {
+ ctor public FormatException();
+ ctor public FormatException(String);
+ ctor public FormatException(String, Throwable);
+ }
+
+ public final class NdefMessage implements android.os.Parcelable {
+ ctor public NdefMessage(byte[]) throws android.nfc.FormatException;
+ ctor public NdefMessage(android.nfc.NdefRecord, android.nfc.NdefRecord...);
+ ctor public NdefMessage(android.nfc.NdefRecord[]);
+ method public int describeContents();
+ method public int getByteArrayLength();
+ method public android.nfc.NdefRecord[] getRecords();
+ method public byte[] toByteArray();
+ method public void writeToParcel(android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.nfc.NdefMessage> CREATOR;
+ }
+
+ public final class NdefRecord implements android.os.Parcelable {
+ ctor public NdefRecord(short, byte[], byte[], byte[]);
+ ctor @Deprecated public NdefRecord(byte[]) throws android.nfc.FormatException;
+ method public static android.nfc.NdefRecord createApplicationRecord(String);
+ method public static android.nfc.NdefRecord createExternal(String, String, byte[]);
+ method public static android.nfc.NdefRecord createMime(String, byte[]);
+ method public static android.nfc.NdefRecord createTextRecord(String, String);
+ method public static android.nfc.NdefRecord createUri(android.net.Uri);
+ method public static android.nfc.NdefRecord createUri(String);
+ method public int describeContents();
+ method public byte[] getId();
+ method public byte[] getPayload();
+ method public short getTnf();
+ method public byte[] getType();
+ method @Deprecated public byte[] toByteArray();
+ method public String toMimeType();
+ method public android.net.Uri toUri();
+ method public void writeToParcel(android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.nfc.NdefRecord> CREATOR;
+ field public static final byte[] RTD_ALTERNATIVE_CARRIER;
+ field public static final byte[] RTD_HANDOVER_CARRIER;
+ field public static final byte[] RTD_HANDOVER_REQUEST;
+ field public static final byte[] RTD_HANDOVER_SELECT;
+ field public static final byte[] RTD_SMART_POSTER;
+ field public static final byte[] RTD_TEXT;
+ field public static final byte[] RTD_URI;
+ field public static final short TNF_ABSOLUTE_URI = 3; // 0x3
+ field public static final short TNF_EMPTY = 0; // 0x0
+ field public static final short TNF_EXTERNAL_TYPE = 4; // 0x4
+ field public static final short TNF_MIME_MEDIA = 2; // 0x2
+ field public static final short TNF_UNCHANGED = 6; // 0x6
+ field public static final short TNF_UNKNOWN = 5; // 0x5
+ field public static final short TNF_WELL_KNOWN = 1; // 0x1
+ }
+
+ public final class NfcAdapter {
+ method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean allowTransaction();
+ method public void disableForegroundDispatch(android.app.Activity);
+ method public void disableReaderMode(android.app.Activity);
+ method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean disallowTransaction();
+ method public void enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]);
+ method public void enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle);
+ method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context);
+ method @Nullable public android.nfc.NfcAntennaInfo getNfcAntennaInfo();
+ method @FlaggedApi("android.nfc.enable_nfc_charging") @Nullable public android.nfc.WlcLDeviceInfo getWlcLDeviceInfo();
+ method public boolean ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler);
+ method public boolean isEnabled();
+ method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean isObserveModeSupported();
+ method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionEnabled();
+ method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionSupported();
+ method public boolean isSecureNfcEnabled();
+ method public boolean isSecureNfcSupported();
+ method @FlaggedApi("android.nfc.enable_nfc_charging") public boolean isWlcEnabled();
+ method @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public void resetDiscoveryTechnology(@NonNull android.app.Activity);
+ method @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public void setDiscoveryTechnology(@NonNull android.app.Activity, int, int);
+ field public static final String ACTION_ADAPTER_STATE_CHANGED = "android.nfc.action.ADAPTER_STATE_CHANGED";
+ field public static final String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED";
+ field @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public static final String ACTION_PREFERRED_PAYMENT_CHANGED = "android.nfc.action.PREFERRED_PAYMENT_CHANGED";
+ field public static final String ACTION_TAG_DISCOVERED = "android.nfc.action.TAG_DISCOVERED";
+ field public static final String ACTION_TECH_DISCOVERED = "android.nfc.action.TECH_DISCOVERED";
+ field @RequiresPermission(android.Manifest.permission.NFC_TRANSACTION_EVENT) public static final String ACTION_TRANSACTION_DETECTED = "android.nfc.action.TRANSACTION_DETECTED";
+ field public static final String EXTRA_ADAPTER_STATE = "android.nfc.extra.ADAPTER_STATE";
+ field public static final String EXTRA_AID = "android.nfc.extra.AID";
+ field public static final String EXTRA_DATA = "android.nfc.extra.DATA";
+ field public static final String EXTRA_ID = "android.nfc.extra.ID";
+ field public static final String EXTRA_NDEF_MESSAGES = "android.nfc.extra.NDEF_MESSAGES";
+ field public static final String EXTRA_PREFERRED_PAYMENT_CHANGED_REASON = "android.nfc.extra.PREFERRED_PAYMENT_CHANGED_REASON";
+ field public static final String EXTRA_READER_PRESENCE_CHECK_DELAY = "presence";
+ field public static final String EXTRA_SECURE_ELEMENT_NAME = "android.nfc.extra.SECURE_ELEMENT_NAME";
+ field public static final String EXTRA_TAG = "android.nfc.extra.TAG";
+ field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_DISABLE = 0; // 0x0
+ field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_KEEP = -1; // 0xffffffff
+ field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_NFC_PASSIVE_A = 1; // 0x1
+ field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_NFC_PASSIVE_B = 2; // 0x2
+ field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_NFC_PASSIVE_F = 4; // 0x4
+ field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_READER_DISABLE = 0; // 0x0
+ field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_READER_KEEP = -1; // 0xffffffff
+ field public static final int FLAG_READER_NFC_A = 1; // 0x1
+ field public static final int FLAG_READER_NFC_B = 2; // 0x2
+ field public static final int FLAG_READER_NFC_BARCODE = 16; // 0x10
+ field public static final int FLAG_READER_NFC_F = 4; // 0x4
+ field public static final int FLAG_READER_NFC_V = 8; // 0x8
+ field public static final int FLAG_READER_NO_PLATFORM_SOUNDS = 256; // 0x100
+ field public static final int FLAG_READER_SKIP_NDEF_CHECK = 128; // 0x80
+ field public static final int PREFERRED_PAYMENT_CHANGED = 2; // 0x2
+ field public static final int PREFERRED_PAYMENT_LOADED = 1; // 0x1
+ field public static final int PREFERRED_PAYMENT_UPDATED = 3; // 0x3
+ field public static final int STATE_OFF = 1; // 0x1
+ field public static final int STATE_ON = 3; // 0x3
+ field public static final int STATE_TURNING_OFF = 4; // 0x4
+ field public static final int STATE_TURNING_ON = 2; // 0x2
+ }
+
+ @Deprecated public static interface NfcAdapter.CreateBeamUrisCallback {
+ method @Deprecated public android.net.Uri[] createBeamUris(android.nfc.NfcEvent);
+ }
+
+ @Deprecated public static interface NfcAdapter.CreateNdefMessageCallback {
+ method @Deprecated public android.nfc.NdefMessage createNdefMessage(android.nfc.NfcEvent);
+ }
+
+ @Deprecated public static interface NfcAdapter.OnNdefPushCompleteCallback {
+ method @Deprecated public void onNdefPushComplete(android.nfc.NfcEvent);
+ }
+
+ public static interface NfcAdapter.OnTagRemovedListener {
+ method public void onTagRemoved();
+ }
+
+ public static interface NfcAdapter.ReaderCallback {
+ method public void onTagDiscovered(android.nfc.Tag);
+ }
+
+ public final class NfcAntennaInfo implements android.os.Parcelable {
+ ctor public NfcAntennaInfo(int, int, boolean, @NonNull java.util.List<android.nfc.AvailableNfcAntenna>);
+ method public int describeContents();
+ method @NonNull public java.util.List<android.nfc.AvailableNfcAntenna> getAvailableNfcAntennas();
+ method public int getDeviceHeight();
+ method public int getDeviceWidth();
+ method public boolean isDeviceFoldable();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.nfc.NfcAntennaInfo> CREATOR;
+ }
+
+ public final class NfcEvent {
+ field public final android.nfc.NfcAdapter nfcAdapter;
+ field public final int peerLlcpMajorVersion;
+ field public final int peerLlcpMinorVersion;
+ }
+
+ public final class NfcManager {
+ method public android.nfc.NfcAdapter getDefaultAdapter();
+ }
+
+ public final class Tag implements android.os.Parcelable {
+ method public int describeContents();
+ method public byte[] getId();
+ method public String[] getTechList();
+ method public void writeToParcel(android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.nfc.Tag> CREATOR;
+ }
+
+ public class TagLostException extends java.io.IOException {
+ ctor public TagLostException();
+ ctor public TagLostException(String);
+ }
+
+ @FlaggedApi("android.nfc.enable_nfc_charging") public final class WlcLDeviceInfo implements android.os.Parcelable {
+ ctor public WlcLDeviceInfo(double, double, double, int);
+ method public int describeContents();
+ method public double getBatteryLevel();
+ method public double getProductId();
+ method public int getState();
+ method public double getTemperature();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int CONNECTED_CHARGING = 2; // 0x2
+ field public static final int CONNECTED_DISCHARGING = 3; // 0x3
+ field @NonNull public static final android.os.Parcelable.Creator<android.nfc.WlcLDeviceInfo> CREATOR;
+ field public static final int DISCONNECTED = 1; // 0x1
+ }
+
+}
+
+package android.nfc.cardemulation {
+
+ public final class CardEmulation {
+ method public boolean categoryAllowsForegroundPreference(String);
+ method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public java.util.List<java.lang.String> getAidsForPreferredPaymentService();
+ method public java.util.List<java.lang.String> getAidsForService(android.content.ComponentName, String);
+ method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public CharSequence getDescriptionForPreferredPaymentService();
+ method public static android.nfc.cardemulation.CardEmulation getInstance(android.nfc.NfcAdapter);
+ method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public String getRouteDestinationForPreferredPaymentService();
+ method public int getSelectionModeForCategory(String);
+ method public boolean isDefaultServiceForAid(android.content.ComponentName, String);
+ method public boolean isDefaultServiceForCategory(android.content.ComponentName, String);
+ method public boolean registerAidsForService(android.content.ComponentName, String, java.util.List<java.lang.String>);
+ method public boolean removeAidsForService(android.content.ComponentName, String);
+ method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean setOffHostForService(@NonNull android.content.ComponentName, @NonNull String);
+ method public boolean setPreferredService(android.app.Activity, android.content.ComponentName);
+ method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setServiceObserveModeDefault(@NonNull android.content.ComponentName, boolean);
+ method public boolean supportsAidPrefixRegistration();
+ method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean unsetOffHostForService(@NonNull android.content.ComponentName);
+ method public boolean unsetPreferredService(android.app.Activity);
+ field @Deprecated public static final String ACTION_CHANGE_DEFAULT = "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT";
+ field public static final String CATEGORY_OTHER = "other";
+ field public static final String CATEGORY_PAYMENT = "payment";
+ field public static final String EXTRA_CATEGORY = "category";
+ field public static final String EXTRA_SERVICE_COMPONENT = "component";
+ field public static final int SELECTION_MODE_ALWAYS_ASK = 1; // 0x1
+ field public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2; // 0x2
+ field public static final int SELECTION_MODE_PREFER_DEFAULT = 0; // 0x0
+ }
+
+ public abstract class HostApduService extends android.app.Service {
+ ctor public HostApduService();
+ method public final void notifyUnhandled();
+ method public final android.os.IBinder onBind(android.content.Intent);
+ method public abstract void onDeactivated(int);
+ method public abstract byte[] processCommandApdu(byte[], android.os.Bundle);
+ method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void processPollingFrames(@NonNull java.util.List<android.os.Bundle>);
+ method public final void sendResponseApdu(byte[]);
+ field public static final int DEACTIVATION_DESELECTED = 1; // 0x1
+ field public static final int DEACTIVATION_LINK_LOSS = 0; // 0x0
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_DATA_KEY = "android.nfc.cardemulation.DATA";
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_GAIN_KEY = "android.nfc.cardemulation.GAIN";
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_TIMESTAMP_KEY = "android.nfc.cardemulation.TIMESTAMP";
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_A = 65; // 0x0041 'A'
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_B = 66; // 0x0042 'B'
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_F = 70; // 0x0046 'F'
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_TYPE_KEY = "android.nfc.cardemulation.TYPE";
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_OFF = 88; // 0x0058 'X'
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_ON = 79; // 0x004f 'O'
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_UNKNOWN = 85; // 0x0055 'U'
+ field public static final String SERVICE_INTERFACE = "android.nfc.cardemulation.action.HOST_APDU_SERVICE";
+ field public static final String SERVICE_META_DATA = "android.nfc.cardemulation.host_apdu_service";
+ }
+
+ public abstract class HostNfcFService extends android.app.Service {
+ ctor public HostNfcFService();
+ method public final android.os.IBinder onBind(android.content.Intent);
+ method public abstract void onDeactivated(int);
+ method public abstract byte[] processNfcFPacket(byte[], android.os.Bundle);
+ method public final void sendResponsePacket(byte[]);
+ field public static final int DEACTIVATION_LINK_LOSS = 0; // 0x0
+ field public static final String SERVICE_INTERFACE = "android.nfc.cardemulation.action.HOST_NFCF_SERVICE";
+ field public static final String SERVICE_META_DATA = "android.nfc.cardemulation.host_nfcf_service";
+ }
+
+ public final class NfcFCardEmulation {
+ method public boolean disableService(android.app.Activity) throws java.lang.RuntimeException;
+ method public boolean enableService(android.app.Activity, android.content.ComponentName) throws java.lang.RuntimeException;
+ method public static android.nfc.cardemulation.NfcFCardEmulation getInstance(android.nfc.NfcAdapter);
+ method public String getNfcid2ForService(android.content.ComponentName) throws java.lang.RuntimeException;
+ method public String getSystemCodeForService(android.content.ComponentName) throws java.lang.RuntimeException;
+ method public boolean registerSystemCodeForService(android.content.ComponentName, String) throws java.lang.RuntimeException;
+ method public boolean setNfcid2ForService(android.content.ComponentName, String) throws java.lang.RuntimeException;
+ method public boolean unregisterSystemCodeForService(android.content.ComponentName) throws java.lang.RuntimeException;
+ }
+
+ public abstract class OffHostApduService extends android.app.Service {
+ ctor public OffHostApduService();
+ field public static final String SERVICE_INTERFACE = "android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE";
+ field public static final String SERVICE_META_DATA = "android.nfc.cardemulation.off_host_apdu_service";
+ }
+
+}
+
+package android.nfc.tech {
+
+ public final class IsoDep implements android.nfc.tech.TagTechnology {
+ method public void close() throws java.io.IOException;
+ method public void connect() throws java.io.IOException;
+ method public static android.nfc.tech.IsoDep get(android.nfc.Tag);
+ method public byte[] getHiLayerResponse();
+ method public byte[] getHistoricalBytes();
+ method public int getMaxTransceiveLength();
+ method public android.nfc.Tag getTag();
+ method public int getTimeout();
+ method public boolean isConnected();
+ method public boolean isExtendedLengthApduSupported();
+ method public void setTimeout(int);
+ method public byte[] transceive(byte[]) throws java.io.IOException;
+ }
+
+ public final class MifareClassic implements android.nfc.tech.TagTechnology {
+ method public boolean authenticateSectorWithKeyA(int, byte[]) throws java.io.IOException;
+ method public boolean authenticateSectorWithKeyB(int, byte[]) throws java.io.IOException;
+ method public int blockToSector(int);
+ method public void close() throws java.io.IOException;
+ method public void connect() throws java.io.IOException;
+ method public void decrement(int, int) throws java.io.IOException;
+ method public static android.nfc.tech.MifareClassic get(android.nfc.Tag);
+ method public int getBlockCount();
+ method public int getBlockCountInSector(int);
+ method public int getMaxTransceiveLength();
+ method public int getSectorCount();
+ method public int getSize();
+ method public android.nfc.Tag getTag();
+ method public int getTimeout();
+ method public int getType();
+ method public void increment(int, int) throws java.io.IOException;
+ method public boolean isConnected();
+ method public byte[] readBlock(int) throws java.io.IOException;
+ method public void restore(int) throws java.io.IOException;
+ method public int sectorToBlock(int);
+ method public void setTimeout(int);
+ method public byte[] transceive(byte[]) throws java.io.IOException;
+ method public void transfer(int) throws java.io.IOException;
+ method public void writeBlock(int, byte[]) throws java.io.IOException;
+ field public static final int BLOCK_SIZE = 16; // 0x10
+ field public static final byte[] KEY_DEFAULT;
+ field public static final byte[] KEY_MIFARE_APPLICATION_DIRECTORY;
+ field public static final byte[] KEY_NFC_FORUM;
+ field public static final int SIZE_1K = 1024; // 0x400
+ field public static final int SIZE_2K = 2048; // 0x800
+ field public static final int SIZE_4K = 4096; // 0x1000
+ field public static final int SIZE_MINI = 320; // 0x140
+ field public static final int TYPE_CLASSIC = 0; // 0x0
+ field public static final int TYPE_PLUS = 1; // 0x1
+ field public static final int TYPE_PRO = 2; // 0x2
+ field public static final int TYPE_UNKNOWN = -1; // 0xffffffff
+ }
+
+ public final class MifareUltralight implements android.nfc.tech.TagTechnology {
+ method public void close() throws java.io.IOException;
+ method public void connect() throws java.io.IOException;
+ method public static android.nfc.tech.MifareUltralight get(android.nfc.Tag);
+ method public int getMaxTransceiveLength();
+ method public android.nfc.Tag getTag();
+ method public int getTimeout();
+ method public int getType();
+ method public boolean isConnected();
+ method public byte[] readPages(int) throws java.io.IOException;
+ method public void setTimeout(int);
+ method public byte[] transceive(byte[]) throws java.io.IOException;
+ method public void writePage(int, byte[]) throws java.io.IOException;
+ field public static final int PAGE_SIZE = 4; // 0x4
+ field public static final int TYPE_ULTRALIGHT = 1; // 0x1
+ field public static final int TYPE_ULTRALIGHT_C = 2; // 0x2
+ field public static final int TYPE_UNKNOWN = -1; // 0xffffffff
+ }
+
+ public final class Ndef implements android.nfc.tech.TagTechnology {
+ method public boolean canMakeReadOnly();
+ method public void close() throws java.io.IOException;
+ method public void connect() throws java.io.IOException;
+ method public static android.nfc.tech.Ndef get(android.nfc.Tag);
+ method public android.nfc.NdefMessage getCachedNdefMessage();
+ method public int getMaxSize();
+ method public android.nfc.NdefMessage getNdefMessage() throws android.nfc.FormatException, java.io.IOException;
+ method public android.nfc.Tag getTag();
+ method public String getType();
+ method public boolean isConnected();
+ method public boolean isWritable();
+ method public boolean makeReadOnly() throws java.io.IOException;
+ method public void writeNdefMessage(android.nfc.NdefMessage) throws android.nfc.FormatException, java.io.IOException;
+ field public static final String MIFARE_CLASSIC = "com.nxp.ndef.mifareclassic";
+ field public static final String NFC_FORUM_TYPE_1 = "org.nfcforum.ndef.type1";
+ field public static final String NFC_FORUM_TYPE_2 = "org.nfcforum.ndef.type2";
+ field public static final String NFC_FORUM_TYPE_3 = "org.nfcforum.ndef.type3";
+ field public static final String NFC_FORUM_TYPE_4 = "org.nfcforum.ndef.type4";
+ }
+
+ public final class NdefFormatable implements android.nfc.tech.TagTechnology {
+ method public void close() throws java.io.IOException;
+ method public void connect() throws java.io.IOException;
+ method public void format(android.nfc.NdefMessage) throws android.nfc.FormatException, java.io.IOException;
+ method public void formatReadOnly(android.nfc.NdefMessage) throws android.nfc.FormatException, java.io.IOException;
+ method public static android.nfc.tech.NdefFormatable get(android.nfc.Tag);
+ method public android.nfc.Tag getTag();
+ method public boolean isConnected();
+ }
+
+ public final class NfcA implements android.nfc.tech.TagTechnology {
+ method public void close() throws java.io.IOException;
+ method public void connect() throws java.io.IOException;
+ method public static android.nfc.tech.NfcA get(android.nfc.Tag);
+ method public byte[] getAtqa();
+ method public int getMaxTransceiveLength();
+ method public short getSak();
+ method public android.nfc.Tag getTag();
+ method public int getTimeout();
+ method public boolean isConnected();
+ method public void setTimeout(int);
+ method public byte[] transceive(byte[]) throws java.io.IOException;
+ }
+
+ public final class NfcB implements android.nfc.tech.TagTechnology {
+ method public void close() throws java.io.IOException;
+ method public void connect() throws java.io.IOException;
+ method public static android.nfc.tech.NfcB get(android.nfc.Tag);
+ method public byte[] getApplicationData();
+ method public int getMaxTransceiveLength();
+ method public byte[] getProtocolInfo();
+ method public android.nfc.Tag getTag();
+ method public boolean isConnected();
+ method public byte[] transceive(byte[]) throws java.io.IOException;
+ }
+
+ public final class NfcBarcode implements android.nfc.tech.TagTechnology {
+ method public void close() throws java.io.IOException;
+ method public void connect() throws java.io.IOException;
+ method public static android.nfc.tech.NfcBarcode get(android.nfc.Tag);
+ method public byte[] getBarcode();
+ method public android.nfc.Tag getTag();
+ method public int getType();
+ method public boolean isConnected();
+ field public static final int TYPE_KOVIO = 1; // 0x1
+ field public static final int TYPE_UNKNOWN = -1; // 0xffffffff
+ }
+
+ public final class NfcF implements android.nfc.tech.TagTechnology {
+ method public void close() throws java.io.IOException;
+ method public void connect() throws java.io.IOException;
+ method public static android.nfc.tech.NfcF get(android.nfc.Tag);
+ method public byte[] getManufacturer();
+ method public int getMaxTransceiveLength();
+ method public byte[] getSystemCode();
+ method public android.nfc.Tag getTag();
+ method public int getTimeout();
+ method public boolean isConnected();
+ method public void setTimeout(int);
+ method public byte[] transceive(byte[]) throws java.io.IOException;
+ }
+
+ public final class NfcV implements android.nfc.tech.TagTechnology {
+ method public void close() throws java.io.IOException;
+ method public void connect() throws java.io.IOException;
+ method public static android.nfc.tech.NfcV get(android.nfc.Tag);
+ method public byte getDsfId();
+ method public int getMaxTransceiveLength();
+ method public byte getResponseFlags();
+ method public android.nfc.Tag getTag();
+ method public boolean isConnected();
+ method public byte[] transceive(byte[]) throws java.io.IOException;
+ }
+
+ public interface TagTechnology extends java.io.Closeable {
+ method public void connect() throws java.io.IOException;
+ method public android.nfc.Tag getTag();
+ method public boolean isConnected();
+ }
+
+}
+
diff --git a/nfc/api/module-lib-current.txt b/nfc/api/module-lib-current.txt
index d802177..5ebe911 100644
--- a/nfc/api/module-lib-current.txt
+++ b/nfc/api/module-lib-current.txt
@@ -1 +1,10 @@
// Signature format: 2.0
+package android.nfc {
+
+ public class NfcFrameworkInitializer {
+ method public static void registerServiceWrappers();
+ method public static void setNfcServiceManager(@NonNull android.nfc.NfcServiceManager);
+ }
+
+}
+
diff --git a/nfc/api/removed.txt b/nfc/api/removed.txt
index d802177..fb82b5d 100644
--- a/nfc/api/removed.txt
+++ b/nfc/api/removed.txt
@@ -1 +1,17 @@
// Signature format: 2.0
+package android.nfc {
+
+ public final class NfcAdapter {
+ method @Deprecated @android.compat.annotation.UnsupportedAppUsage public void disableForegroundNdefPush(android.app.Activity);
+ method @Deprecated @android.compat.annotation.UnsupportedAppUsage public void enableForegroundNdefPush(android.app.Activity, android.nfc.NdefMessage);
+ method @Deprecated @android.compat.annotation.UnsupportedAppUsage public boolean invokeBeam(android.app.Activity);
+ method @Deprecated @android.compat.annotation.UnsupportedAppUsage public boolean isNdefPushEnabled();
+ method @Deprecated @android.compat.annotation.UnsupportedAppUsage public void setBeamPushUris(android.net.Uri[], android.app.Activity);
+ method @Deprecated @android.compat.annotation.UnsupportedAppUsage public void setBeamPushUrisCallback(android.nfc.NfcAdapter.CreateBeamUrisCallback, android.app.Activity);
+ method @Deprecated @android.compat.annotation.UnsupportedAppUsage public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, android.app.Activity...);
+ method @Deprecated @android.compat.annotation.UnsupportedAppUsage public void setNdefPushMessageCallback(android.nfc.NfcAdapter.CreateNdefMessageCallback, android.app.Activity, android.app.Activity...);
+ method @Deprecated @android.compat.annotation.UnsupportedAppUsage public void setOnNdefPushCompleteCallback(android.nfc.NfcAdapter.OnNdefPushCompleteCallback, android.app.Activity, android.app.Activity...);
+ }
+
+}
+
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index d802177..40672a1 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -1 +1,53 @@
// Signature format: 2.0
+package android.nfc {
+
+ public final class NfcAdapter {
+ method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean addNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler, String[]);
+ method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable();
+ method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable(boolean);
+ method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable();
+ method @FlaggedApi("android.nfc.enable_nfc_reader_option") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableReaderOption(boolean);
+ method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableSecureNfc(boolean);
+ method @FlaggedApi("android.nfc.enable_nfc_charging") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableWlc(boolean);
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") public int getAdapterState();
+ method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public java.util.Map<java.lang.String,java.lang.Boolean> getTagIntentAppPreferenceForUser(int);
+ method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOn();
+ method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOnSupported();
+ method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isTagIntentAppPreferenceSupported();
+ method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void registerControllerAlwaysOnListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
+ method @FlaggedApi("android.nfc.enable_nfc_charging") public void registerWlcStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.WlcStateListener);
+ method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean removeNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler);
+ method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean setControllerAlwaysOn(boolean);
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setReaderMode(boolean);
+ method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int setTagIntentAppPreferenceForUser(int, @NonNull String, boolean);
+ method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnListener(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
+ method @FlaggedApi("android.nfc.enable_nfc_charging") public void unregisterWlcStateListener(@NonNull android.nfc.NfcAdapter.WlcStateListener);
+ field @FlaggedApi("android.nfc.enable_nfc_mainline") public static final String ACTION_REQUIRE_UNLOCK_FOR_NFC = "android.nfc.action.REQUIRE_UNLOCK_FOR_NFC";
+ field public static final int TAG_INTENT_APP_PREF_RESULT_PACKAGE_NOT_FOUND = -1; // 0xffffffff
+ field public static final int TAG_INTENT_APP_PREF_RESULT_SUCCESS = 0; // 0x0
+ field public static final int TAG_INTENT_APP_PREF_RESULT_UNAVAILABLE = -2; // 0xfffffffe
+ }
+
+ public static interface NfcAdapter.ControllerAlwaysOnListener {
+ method public void onControllerAlwaysOnChanged(boolean);
+ }
+
+ public static interface NfcAdapter.NfcUnlockHandler {
+ method public boolean onUnlockAttempted(android.nfc.Tag);
+ }
+
+ @FlaggedApi("android.nfc.enable_nfc_charging") public static interface NfcAdapter.WlcStateListener {
+ method public void onWlcStateChanged(@NonNull android.nfc.WlcLDeviceInfo);
+ }
+
+}
+
+package android.nfc.cardemulation {
+
+ public final class CardEmulation {
+ method @FlaggedApi("android.permission.flags.wallet_role_enabled") @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public android.nfc.cardemulation.ApduServiceInfo getPreferredPaymentService();
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<android.nfc.cardemulation.ApduServiceInfo> getServices(@NonNull String, int);
+ }
+
+}
+
diff --git a/nfc/api/system-removed.txt b/nfc/api/system-removed.txt
index d802177..c6eaa57 100644
--- a/nfc/api/system-removed.txt
+++ b/nfc/api/system-removed.txt
@@ -1 +1,12 @@
// Signature format: 2.0
+package android.nfc {
+
+ public final class NfcAdapter {
+ method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disableNdefPush();
+ method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableNdefPush();
+ method public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, int);
+ field public static final int FLAG_NDEF_PUSH_NO_CONFIRM = 1; // 0x1
+ }
+
+}
+
diff --git a/nfc/jarjar-rules.txt b/nfc/jarjar-rules.txt
new file mode 100644
index 0000000..4cd652d
--- /dev/null
+++ b/nfc/jarjar-rules.txt
@@ -0,0 +1,38 @@
+# Used by framework-nfc for proto debug dumping
+rule android.app.PendingIntentProto* com.android.nfc.x.@0
+rule android.content.ComponentNameProto* com.android.nfc.x.@0
+rule android.content.IntentProto* com.android.nfc.x.@0
+rule android.content.IntentFilterProto* com.android.nfc.x.@0
+rule android.content.AuthorityEntryProto* com.android.nfc.x.@0
+rule android.nfc.cardemulation.AidGroupProto* com.android.nfc.x.@0
+rule android.nfc.cardemulation.ApduServiceInfoProto* com.android.nfc.x.@0
+rule android.nfc.cardemulation.NfcFServiceInfoProto* com.android.nfc.x.@0
+rule android.nfc.NdefMessageProto* com.android.nfc.x.@0
+rule android.nfc.NdefRecordProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.CardEmulationManagerProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.RegisteredServicesCacheProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.RegisteredNfcFServicesCacheProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.PreferredServicesProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.EnabledNfcFServicesProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.RegisteredAidCacheProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.AidRoutingManagerProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.RegisteredT3tIdentifiersCacheProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.SystemCodeRoutingManagerProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.HostEmulationManagerProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.HostNfcFEmulationManagerProto* com.android.nfc.x.@0
+rule com.android.nfc.NfcServiceDumpProto* com.android.nfc.x.@0
+rule com.android.nfc.DiscoveryParamsProto* com.android.nfc.x.@0
+rule com.android.nfc.NfcDispatcherProto* com.android.nfc.x.@0
+rule android.os.PersistableBundleProto* com.android.nfc.x.@0
+
+# Used by framework-nfc for reading trunk stable flags
+rule android.nfc.FakeFeatureFlagsImpl* com.android.nfc.x.@0
+rule android.nfc.FeatureFlags* com.android.nfc.x.@0
+rule android.nfc.Flags* com.android.nfc.x.@0
+rule android.permission.flags.** com.android.nfc.x.@0
+
+# Used by framework-nfc for misc utilities
+rule android.os.PatternMatcher* com.android.nfc.x.@0
+
+rule com.android.incident.Privacy* com.android.nfc.x.@0
+rule com.android.incident.PrivacyFlags* com.android.nfc.x.@0
diff --git a/nfc/java/android/nfc/ApduList.aidl b/nfc/java/android/nfc/ApduList.aidl
new file mode 100644
index 0000000..f6236b2
--- /dev/null
+++ b/nfc/java/android/nfc/ApduList.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2011 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;
+
+parcelable ApduList;
\ No newline at end of file
diff --git a/nfc/java/android/nfc/ApduList.java b/nfc/java/android/nfc/ApduList.java
new file mode 100644
index 0000000..027141d
--- /dev/null
+++ b/nfc/java/android/nfc/ApduList.java
@@ -0,0 +1,68 @@
+package android.nfc;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @hide
+ */
+public class ApduList implements Parcelable {
+
+ private ArrayList<byte[]> commands = new ArrayList<byte[]>();
+
+ public ApduList() {
+ }
+
+ public void add(byte[] command) {
+ commands.add(command);
+ }
+
+ public List<byte[]> get() {
+ return commands;
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<ApduList> CREATOR =
+ new Parcelable.Creator<ApduList>() {
+ @Override
+ public ApduList createFromParcel(Parcel in) {
+ return new ApduList(in);
+ }
+
+ @Override
+ public ApduList[] newArray(int size) {
+ return new ApduList[size];
+ }
+ };
+
+ private ApduList(Parcel in) {
+ int count = in.readInt();
+
+ for (int i = 0 ; i < count ; i++) {
+
+ int length = in.readInt();
+ byte[] cmd = new byte[length];
+ in.readByteArray(cmd);
+ commands.add(cmd);
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(commands.size());
+
+ for (byte[] cmd : commands) {
+ dest.writeInt(cmd.length);
+ dest.writeByteArray(cmd);
+ }
+ }
+}
+
+
diff --git a/nfc/java/android/nfc/AvailableNfcAntenna.aidl b/nfc/java/android/nfc/AvailableNfcAntenna.aidl
new file mode 100644
index 0000000..9d06e2d
--- /dev/null
+++ b/nfc/java/android/nfc/AvailableNfcAntenna.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2013 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;
+
+parcelable AvailableNfcAntenna;
diff --git a/nfc/java/android/nfc/AvailableNfcAntenna.java b/nfc/java/android/nfc/AvailableNfcAntenna.java
new file mode 100644
index 0000000..6e6512a
--- /dev/null
+++ b/nfc/java/android/nfc/AvailableNfcAntenna.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2015 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.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Represents a single available Nfc antenna
+ * on an Android device.
+ */
+public final class AvailableNfcAntenna implements Parcelable {
+ /**
+ * Location of the antenna on the Y axis in millimeters.
+ * 0 is the bottom-left when the user is facing the screen
+ * and the device orientation is Portrait.
+ */
+ private final int mLocationX;
+ /**
+ * Location of the antenna on the Y axis in millimeters.
+ * 0 is the bottom-left when the user is facing the screen
+ * and the device orientation is Portrait.
+ */
+ private final int mLocationY;
+
+ public AvailableNfcAntenna(int locationX, int locationY) {
+ this.mLocationX = locationX;
+ this.mLocationY = locationY;
+ }
+
+ /**
+ * Location of the antenna on the X axis in millimeters.
+ * 0 is the bottom-left when the user is facing the screen
+ * and the device orientation is Portrait.
+ */
+ public int getLocationX() {
+ return mLocationX;
+ }
+
+ /**
+ * Location of the antenna on the Y axis in millimeters.
+ * 0 is the bottom-left when the user is facing the screen
+ * and the device orientation is Portrait.
+ */
+ public int getLocationY() {
+ return mLocationY;
+ }
+
+ private AvailableNfcAntenna(Parcel in) {
+ this.mLocationX = in.readInt();
+ this.mLocationY = in.readInt();
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<AvailableNfcAntenna>
+ CREATOR = new Parcelable.Creator<AvailableNfcAntenna>() {
+ @Override
+ public AvailableNfcAntenna createFromParcel(Parcel in) {
+ return new AvailableNfcAntenna(in);
+ }
+
+ @Override
+ public AvailableNfcAntenna[] newArray(int size) {
+ return new AvailableNfcAntenna[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mLocationX);
+ dest.writeInt(mLocationY);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + mLocationX;
+ result = prime * result + mLocationY;
+ return result;
+ }
+
+ /**
+ * Returns true if the specified AvailableNfcAntenna contains
+ * identical specifications.
+ */
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+ AvailableNfcAntenna other = (AvailableNfcAntenna) obj;
+ if (this.mLocationX != other.mLocationX) return false;
+ return this.mLocationY == other.mLocationY;
+ }
+
+ @Override
+ public String toString() {
+ return "AvailableNfcAntenna " + "x: " + mLocationX + " y: " + mLocationY;
+ }
+}
diff --git a/nfc/java/android/nfc/Constants.java b/nfc/java/android/nfc/Constants.java
new file mode 100644
index 0000000..f768330
--- /dev/null
+++ b/nfc/java/android/nfc/Constants.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 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;
+
+/**
+ * @hide
+ * TODO(b/303286040): Holds @hide API constants. Formalize these APIs.
+ */
+public final class Constants {
+ private Constants() { }
+
+ public static final String SETTINGS_SECURE_NFC_PAYMENT_FOREGROUND = "nfc_payment_foreground";
+ public static final String SETTINGS_SECURE_NFC_PAYMENT_DEFAULT_COMPONENT = "nfc_payment_default_component";
+ public static final String FEATURE_NFC_ANY = "android.hardware.nfc.any";
+}
diff --git a/nfc/java/android/nfc/ErrorCodes.java b/nfc/java/android/nfc/ErrorCodes.java
new file mode 100644
index 0000000..d2c81cd
--- /dev/null
+++ b/nfc/java/android/nfc/ErrorCodes.java
@@ -0,0 +1,114 @@
+/*
+ * 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.compat.annotation.UnsupportedAppUsage;
+
+/**
+ * This class defines all the error codes that can be returned by the service
+ * and producing an exception on the application level. These are needed since
+ * binders does not support exceptions.
+ *
+ * @hide
+ */
+public class ErrorCodes {
+
+ @UnsupportedAppUsage
+ public static boolean isError(int code) {
+ if (code < 0) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public static String asString(int code) {
+ switch (code) {
+ case SUCCESS: return "SUCCESS";
+ case ERROR_IO: return "IO";
+ case ERROR_CANCELLED: return "CANCELLED";
+ case ERROR_TIMEOUT: return "TIMEOUT";
+ case ERROR_BUSY: return "BUSY";
+ case ERROR_CONNECT: return "CONNECT/DISCONNECT";
+// case ERROR_DISCONNECT: return "DISCONNECT";
+ case ERROR_READ: return "READ";
+ case ERROR_WRITE: return "WRITE";
+ case ERROR_INVALID_PARAM: return "INVALID_PARAM";
+ case ERROR_INSUFFICIENT_RESOURCES: return "INSUFFICIENT_RESOURCES";
+ case ERROR_SOCKET_CREATION: return "SOCKET_CREATION";
+ case ERROR_SOCKET_NOT_CONNECTED: return "SOCKET_NOT_CONNECTED";
+ case ERROR_BUFFER_TO_SMALL: return "BUFFER_TO_SMALL";
+ case ERROR_SAP_USED: return "SAP_USED";
+ case ERROR_SERVICE_NAME_USED: return "SERVICE_NAME_USED";
+ case ERROR_SOCKET_OPTIONS: return "SOCKET_OPTIONS";
+ case ERROR_NFC_ON: return "NFC_ON";
+ case ERROR_NOT_INITIALIZED: return "NOT_INITIALIZED";
+ case ERROR_SE_ALREADY_SELECTED: return "SE_ALREADY_SELECTED";
+ case ERROR_SE_CONNECTED: return "SE_CONNECTED";
+ case ERROR_NO_SE_CONNECTED: return "NO_SE_CONNECTED";
+ case ERROR_NOT_SUPPORTED: return "NOT_SUPPORTED";
+ default: return "UNKNOWN ERROR";
+ }
+ }
+
+ public static final int SUCCESS = 0;
+
+ public static final int ERROR_IO = -1;
+
+ public static final int ERROR_CANCELLED = -2;
+
+ public static final int ERROR_TIMEOUT = -3;
+
+ public static final int ERROR_BUSY = -4;
+
+ public static final int ERROR_CONNECT = -5;
+
+ public static final int ERROR_DISCONNECT = -5;
+
+ public static final int ERROR_READ = -6;
+
+ public static final int ERROR_WRITE = -7;
+
+ public static final int ERROR_INVALID_PARAM = -8;
+
+ public static final int ERROR_INSUFFICIENT_RESOURCES = -9;
+
+ public static final int ERROR_SOCKET_CREATION = -10;
+
+ public static final int ERROR_SOCKET_NOT_CONNECTED = -11;
+
+ public static final int ERROR_BUFFER_TO_SMALL = -12;
+
+ public static final int ERROR_SAP_USED = -13;
+
+ public static final int ERROR_SERVICE_NAME_USED = -14;
+
+ public static final int ERROR_SOCKET_OPTIONS = -15;
+
+ public static final int ERROR_NFC_ON = -16;
+
+ public static final int ERROR_NOT_INITIALIZED = -17;
+
+ public static final int ERROR_SE_ALREADY_SELECTED = -18;
+
+ public static final int ERROR_SE_CONNECTED = -19;
+
+ public static final int ERROR_NO_SE_CONNECTED = -20;
+
+ public static final int ERROR_NOT_SUPPORTED = -21;
+
+}
diff --git a/nfc/java/android/nfc/FormatException.java b/nfc/java/android/nfc/FormatException.java
new file mode 100644
index 0000000..a57de1e
--- /dev/null
+++ b/nfc/java/android/nfc/FormatException.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+public class FormatException extends Exception {
+ public FormatException() {
+ super();
+ }
+
+ public FormatException(String message) {
+ super(message);
+ }
+
+ public FormatException(String message, Throwable e) {
+ super(message, e);
+ }
+}
diff --git a/nfc/java/android/nfc/IAppCallback.aidl b/nfc/java/android/nfc/IAppCallback.aidl
new file mode 100644
index 0000000..b06bf06
--- /dev/null
+++ b/nfc/java/android/nfc/IAppCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2011 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.nfc.Tag;
+
+/**
+ * @hide
+ */
+interface IAppCallback
+{
+ oneway void onTagDiscovered(in Tag tag);
+}
diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl
new file mode 100644
index 0000000..286cf28
--- /dev/null
+++ b/nfc/java/android/nfc/INfcAdapter.aidl
@@ -0,0 +1,100 @@
+/*
+ * 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.app.PendingIntent;
+import android.content.IntentFilter;
+import android.nfc.NdefMessage;
+import android.nfc.Tag;
+import android.nfc.TechListParcel;
+import android.nfc.IAppCallback;
+import android.nfc.INfcAdapterExtras;
+import android.nfc.INfcControllerAlwaysOnListener;
+import android.nfc.INfcTag;
+import android.nfc.INfcCardEmulation;
+import android.nfc.INfcFCardEmulation;
+import android.nfc.INfcUnlockHandler;
+import android.nfc.ITagRemovedCallback;
+import android.nfc.INfcDta;
+import android.nfc.INfcWlcStateListener;
+import android.nfc.NfcAntennaInfo;
+import android.os.Bundle;
+import android.nfc.WlcLDeviceInfo;
+
+/**
+ * @hide
+ */
+interface INfcAdapter
+{
+ INfcTag getNfcTagInterface();
+ INfcCardEmulation getNfcCardEmulationInterface();
+ INfcFCardEmulation getNfcFCardEmulationInterface();
+ INfcAdapterExtras getNfcAdapterExtrasInterface(in String pkg);
+ INfcDta getNfcDtaInterface(in String pkg);
+ int getState();
+ boolean disable(boolean saveState);
+ boolean enable();
+ void pausePolling(int timeoutInMs);
+ void resumePolling();
+
+ void setForegroundDispatch(in PendingIntent intent,
+ in IntentFilter[] filters, in TechListParcel techLists);
+ void setAppCallback(in IAppCallback callback);
+
+ boolean ignore(int nativeHandle, int debounceMs, ITagRemovedCallback callback);
+
+ void dispatch(in Tag tag);
+
+ void setReaderMode (IBinder b, IAppCallback callback, int flags, in Bundle extras);
+
+ void addNfcUnlockHandler(INfcUnlockHandler unlockHandler, in int[] techList);
+ void removeNfcUnlockHandler(INfcUnlockHandler unlockHandler);
+
+ void verifyNfcPermission();
+ boolean isNfcSecureEnabled();
+ boolean deviceSupportsNfcSecure();
+ boolean setNfcSecure(boolean enable);
+ NfcAntennaInfo getNfcAntennaInfo();
+
+ boolean setControllerAlwaysOn(boolean value);
+ boolean isControllerAlwaysOn();
+ boolean isControllerAlwaysOnSupported();
+ void registerControllerAlwaysOnListener(in INfcControllerAlwaysOnListener listener);
+ void unregisterControllerAlwaysOnListener(in INfcControllerAlwaysOnListener listener);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)")
+ boolean isTagIntentAppPreferenceSupported();
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)")
+ Map getTagIntentAppPreferenceForUser(int userId);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)")
+ int setTagIntentAppPreferenceForUser(int userId, String pkg, boolean allow);
+
+ boolean isReaderOptionEnabled();
+ boolean isReaderOptionSupported();
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)")
+ boolean enableReaderOption(boolean enable);
+ boolean isObserveModeSupported();
+ boolean setObserveMode(boolean enabled);
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)")
+ boolean enableWlc(boolean enable);
+ boolean isWlcEnabled();
+ void registerWlcStateListener(in INfcWlcStateListener listener);
+ void unregisterWlcStateListener(in INfcWlcStateListener listener);
+ WlcLDeviceInfo getWlcLDeviceInfo();
+
+ void updateDiscoveryTechnology(IBinder b, int pollFlags, int listenFlags);
+}
diff --git a/nfc/java/android/nfc/INfcAdapterExtras.aidl b/nfc/java/android/nfc/INfcAdapterExtras.aidl
new file mode 100644
index 0000000..cde57c5
--- /dev/null
+++ b/nfc/java/android/nfc/INfcAdapterExtras.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2011 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.os.Bundle;
+
+
+/**
+ * {@hide}
+ */
+interface INfcAdapterExtras {
+ @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
+ Bundle open(in String pkg, IBinder b);
+ @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
+ Bundle close(in String pkg, IBinder b);
+ @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
+ Bundle transceive(in String pkg, in byte[] data_in);
+ @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
+ int getCardEmulationRoute(in String pkg);
+ @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
+ void setCardEmulationRoute(in String pkg, int route);
+ @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
+ void authenticate(in String pkg, in byte[] token);
+ @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
+ String getDriverName(in String pkg);
+}
diff --git a/nfc/java/android/nfc/INfcCardEmulation.aidl b/nfc/java/android/nfc/INfcCardEmulation.aidl
new file mode 100644
index 0000000..f4b4604
--- /dev/null
+++ b/nfc/java/android/nfc/INfcCardEmulation.aidl
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2013 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.content.ComponentName;
+import android.nfc.cardemulation.AidGroup;
+import android.nfc.cardemulation.ApduServiceInfo;
+import android.os.RemoteCallback;
+
+/**
+ * @hide
+ */
+interface INfcCardEmulation
+{
+ boolean isDefaultServiceForCategory(int userHandle, in ComponentName service, String category);
+ boolean isDefaultServiceForAid(int userHandle, in ComponentName service, String aid);
+ boolean setDefaultServiceForCategory(int userHandle, in ComponentName service, String category);
+ boolean setDefaultForNextTap(int userHandle, in ComponentName service);
+ boolean setServiceObserveModeDefault(int userId, in android.content.ComponentName service, boolean enable);
+ boolean registerAidGroupForService(int userHandle, in ComponentName service, in AidGroup aidGroup);
+ boolean setOffHostForService(int userHandle, in ComponentName service, in String offHostSecureElement);
+ boolean unsetOffHostForService(int userHandle, in ComponentName service);
+ AidGroup getAidGroupForService(int userHandle, in ComponentName service, String category);
+ boolean removeAidGroupForService(int userHandle, in ComponentName service, String category);
+ List<ApduServiceInfo> getServices(int userHandle, in String category);
+ boolean setPreferredService(in ComponentName service);
+ boolean unsetPreferredService();
+ boolean supportsAidPrefixRegistration();
+ ApduServiceInfo getPreferredPaymentService(int userHandle);
+ boolean setServiceEnabledForCategoryOther(int userHandle, in ComponentName app, boolean status);
+ boolean isDefaultPaymentRegistered();
+
+ boolean overrideRoutingTable(int userHandle, String protocol, String technology);
+ boolean recoverRoutingTable(int userHandle);
+}
diff --git a/nfc/java/android/nfc/INfcControllerAlwaysOnListener.aidl b/nfc/java/android/nfc/INfcControllerAlwaysOnListener.aidl
new file mode 100644
index 0000000..1bb7680
--- /dev/null
+++ b/nfc/java/android/nfc/INfcControllerAlwaysOnListener.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2021 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;
+
+/**
+ * @hide
+ */
+oneway interface INfcControllerAlwaysOnListener {
+ /**
+ * Called whenever the controller always on state changes
+ *
+ * @param isEnabled true if the state is enabled, false otherwise
+ */
+ void onControllerAlwaysOnChanged(boolean isEnabled);
+}
diff --git a/nfc/java/android/nfc/INfcDta.aidl b/nfc/java/android/nfc/INfcDta.aidl
new file mode 100644
index 0000000..4cc5927
--- /dev/null
+++ b/nfc/java/android/nfc/INfcDta.aidl
@@ -0,0 +1,34 @@
+ /*
+ * Copyright (C) 2017 NXP Semiconductors
+ *
+ * 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.os.Bundle;
+
+/**
+ * {@hide}
+ */
+interface INfcDta {
+
+ void enableDta();
+ void disableDta();
+ boolean enableServer(String serviceName, int serviceSap, int miu,
+ int rwSize,int testCaseId);
+ void disableServer();
+ boolean enableClient(String serviceName, int miu, int rwSize,
+ int testCaseId);
+ void disableClient();
+ boolean registerMessageService(String msgServiceName);
+}
diff --git a/nfc/java/android/nfc/INfcFCardEmulation.aidl b/nfc/java/android/nfc/INfcFCardEmulation.aidl
new file mode 100644
index 0000000..124bfac
--- /dev/null
+++ b/nfc/java/android/nfc/INfcFCardEmulation.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015 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.content.ComponentName;
+import android.nfc.cardemulation.NfcFServiceInfo;
+
+/**
+ * @hide
+ */
+interface INfcFCardEmulation
+{
+ String getSystemCodeForService(int userHandle, in ComponentName service);
+ boolean registerSystemCodeForService(int userHandle, in ComponentName service, String systemCode);
+ boolean removeSystemCodeForService(int userHandle, in ComponentName service);
+ String getNfcid2ForService(int userHandle, in ComponentName service);
+ boolean setNfcid2ForService(int userHandle, in ComponentName service, String nfcid2);
+ boolean enableNfcFForegroundService(in ComponentName service);
+ boolean disableNfcFForegroundService();
+ List<NfcFServiceInfo> getNfcFServices(int userHandle);
+ int getMaxNumOfRegisterableSystemCodes();
+}
diff --git a/nfc/java/android/nfc/INfcTag.aidl b/nfc/java/android/nfc/INfcTag.aidl
new file mode 100644
index 0000000..170df71
--- /dev/null
+++ b/nfc/java/android/nfc/INfcTag.aidl
@@ -0,0 +1,50 @@
+/*
+ * 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.nfc.NdefMessage;
+import android.nfc.Tag;
+import android.nfc.TransceiveResult;
+
+/**
+ * @hide
+ */
+interface INfcTag
+{
+ int connect(int nativeHandle, int technology);
+ int reconnect(int nativeHandle);
+ int[] getTechList(int nativeHandle);
+ boolean isNdef(int nativeHandle);
+ boolean isPresent(int nativeHandle);
+ TransceiveResult transceive(int nativeHandle, in byte[] data, boolean raw);
+
+ NdefMessage ndefRead(int nativeHandle);
+ int ndefWrite(int nativeHandle, in NdefMessage msg);
+ int ndefMakeReadOnly(int nativeHandle);
+ boolean ndefIsWritable(int nativeHandle);
+ int formatNdef(int nativeHandle, in byte[] key);
+ Tag rediscover(int nativehandle);
+
+ int setTimeout(int technology, int timeout);
+ int getTimeout(int technology);
+ void resetTimeouts();
+ boolean canMakeReadOnly(int ndefType);
+ int getMaxTransceiveLength(int technology);
+ boolean getExtendedLengthApdusSupported();
+
+ boolean isTagUpToDate(long cookie);
+}
diff --git a/nfc/java/android/nfc/INfcUnlockHandler.aidl b/nfc/java/android/nfc/INfcUnlockHandler.aidl
new file mode 100644
index 0000000..e1cace9
--- /dev/null
+++ b/nfc/java/android/nfc/INfcUnlockHandler.aidl
@@ -0,0 +1,12 @@
+package android.nfc;
+
+import android.nfc.Tag;
+
+/**
+ * @hide
+ */
+interface INfcUnlockHandler {
+
+ boolean onUnlockAttempted(in Tag tag);
+
+}
diff --git a/nfc/java/android/nfc/INfcWlcStateListener.aidl b/nfc/java/android/nfc/INfcWlcStateListener.aidl
new file mode 100644
index 0000000..c2b7075
--- /dev/null
+++ b/nfc/java/android/nfc/INfcWlcStateListener.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2023 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.nfc.WlcLDeviceInfo;
+/**
+ * @hide
+ */
+oneway interface INfcWlcStateListener {
+ /**
+ * Called whenever NFC WLC state changes
+ *
+ * @param wlcLDeviceInfo NFC wlc listener information
+ */
+ void onWlcStateChanged(in WlcLDeviceInfo wlcLDeviceInfo);
+}
diff --git a/nfc/java/android/nfc/ITagRemovedCallback.aidl b/nfc/java/android/nfc/ITagRemovedCallback.aidl
new file mode 100644
index 0000000..2a06ff3
--- /dev/null
+++ b/nfc/java/android/nfc/ITagRemovedCallback.aidl
@@ -0,0 +1,8 @@
+package android.nfc;
+
+/**
+ * @hide
+ */
+oneway interface ITagRemovedCallback {
+ void onTagRemoved();
+}
diff --git a/nfc/java/android/nfc/NdefMessage.aidl b/nfc/java/android/nfc/NdefMessage.aidl
new file mode 100644
index 0000000..378b9d0
--- /dev/null
+++ b/nfc/java/android/nfc/NdefMessage.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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;
+
+parcelable NdefMessage;
diff --git a/nfc/java/android/nfc/NdefMessage.java b/nfc/java/android/nfc/NdefMessage.java
new file mode 100644
index 0000000..553f6c0
--- /dev/null
+++ b/nfc/java/android/nfc/NdefMessage.java
@@ -0,0 +1,272 @@
+/*
+ * 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.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.proto.ProtoOutputStream;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+/**
+ * Represents an immutable NDEF Message.
+ * <p>
+ * NDEF (NFC Data Exchange Format) is a light-weight binary format,
+ * used to encapsulate typed data. It is specified by the NFC Forum,
+ * for transmission and storage with NFC, however it is transport agnostic.
+ * <p>
+ * NDEF defines messages and records. An NDEF Record contains
+ * typed data, such as MIME-type media, a URI, or a custom
+ * application payload. An NDEF Message is a container for
+ * one or more NDEF Records.
+ * <p>
+ * When an Android device receives an NDEF Message
+ * (for example by reading an NFC tag) it processes it through
+ * a dispatch mechanism to determine an activity to launch.
+ * The type of the <em>first</em> record in the message has
+ * special importance for message dispatch, so design this record
+ * carefully.
+ * <p>
+ * Use {@link #NdefMessage(byte[])} to construct an NDEF Message from
+ * binary data, or {@link #NdefMessage(NdefRecord[])} to
+ * construct from one or more {@link NdefRecord}s.
+ * <p class="note">
+ * {@link NdefMessage} and {@link NdefRecord} implementations are
+ * always available, even on Android devices that do not have NFC hardware.
+ * <p class="note">
+ * {@link NdefRecord}s are intended to be immutable (and thread-safe),
+ * however they may contain mutable fields. So take care not to modify
+ * mutable fields passed into constructors, or modify mutable fields
+ * obtained by getter methods, unless such modification is explicitly
+ * marked as safe.
+ *
+ * @see NfcAdapter#ACTION_NDEF_DISCOVERED
+ * @see NdefRecord
+ */
+public final class NdefMessage implements Parcelable {
+ private final NdefRecord[] mRecords;
+
+ /**
+ * Construct an NDEF Message by parsing raw bytes.<p>
+ * Strict validation of the NDEF binary structure is performed:
+ * there must be at least one record, every record flag must
+ * be correct, and the total length of the message must match
+ * the length of the input data.<p>
+ * This parser can handle chunked records, and converts them
+ * into logical {@link NdefRecord}s within the message.<p>
+ * Once the input data has been parsed to one or more logical
+ * records, basic validation of the tnf, type, id, and payload fields
+ * of each record is performed, as per the documentation on
+ * on {@link NdefRecord#NdefRecord(short, byte[], byte[], byte[])}<p>
+ * If either strict validation of the binary format fails, or
+ * basic validation during record construction fails, a
+ * {@link FormatException} is thrown<p>
+ * Deep inspection of the type, id and payload fields of
+ * each record is not performed, so it is possible to parse input
+ * that has a valid binary format and confirms to the basic
+ * validation requirements of
+ * {@link NdefRecord#NdefRecord(short, byte[], byte[], byte[])},
+ * but fails more strict requirements as specified by the
+ * NFC Forum.
+ *
+ * <p class="note">
+ * It is safe to re-use the data byte array after construction:
+ * this constructor will make an internal copy of all necessary fields.
+ *
+ * @param data raw bytes to parse
+ * @throws FormatException if the data cannot be parsed
+ */
+ public NdefMessage(byte[] data) throws FormatException {
+ if (data == null) throw new NullPointerException("data is null");
+ ByteBuffer buffer = ByteBuffer.wrap(data);
+
+ mRecords = NdefRecord.parse(buffer, false);
+
+ if (buffer.remaining() > 0) {
+ throw new FormatException("trailing data");
+ }
+ }
+
+ /**
+ * Construct an NDEF Message from one or more NDEF Records.
+ *
+ * @param record first record (mandatory)
+ * @param records additional records (optional)
+ */
+ public NdefMessage(NdefRecord record, NdefRecord ... records) {
+ // validate
+ if (record == null) throw new NullPointerException("record cannot be null");
+
+ for (NdefRecord r : records) {
+ if (r == null) {
+ throw new NullPointerException("record cannot be null");
+ }
+ }
+
+ mRecords = new NdefRecord[1 + records.length];
+ mRecords[0] = record;
+ System.arraycopy(records, 0, mRecords, 1, records.length);
+ }
+
+ /**
+ * Construct an NDEF Message from one or more NDEF Records.
+ *
+ * @param records one or more records
+ */
+ public NdefMessage(NdefRecord[] records) {
+ // validate
+ if (records.length < 1) {
+ throw new IllegalArgumentException("must have at least one record");
+ }
+ for (NdefRecord r : records) {
+ if (r == null) {
+ throw new NullPointerException("records cannot contain null");
+ }
+ }
+
+ mRecords = records;
+ }
+
+ /**
+ * Get the NDEF Records inside this NDEF Message.<p>
+ * An {@link NdefMessage} always has one or more NDEF Records: so the
+ * following code to retrieve the first record is always safe
+ * (no need to check for null or array length >= 1):
+ * <pre>
+ * NdefRecord firstRecord = ndefMessage.getRecords()[0];
+ * </pre>
+ *
+ * @return array of one or more NDEF records.
+ */
+ public NdefRecord[] getRecords() {
+ return mRecords;
+ }
+
+ /**
+ * Return the length of this NDEF Message if it is written to a byte array
+ * with {@link #toByteArray}.<p>
+ * An NDEF Message can be formatted to bytes in different ways
+ * depending on chunking, SR, and ID flags, so the length returned
+ * by this method may not be equal to the length of the original
+ * byte array used to construct this NDEF Message. However it will
+ * always be equal to the length of the byte array produced by
+ * {@link #toByteArray}.
+ *
+ * @return length of this NDEF Message when written to bytes with {@link #toByteArray}
+ * @see #toByteArray
+ */
+ public int getByteArrayLength() {
+ int length = 0;
+ for (NdefRecord r : mRecords) {
+ length += r.getByteLength();
+ }
+ return length;
+ }
+
+ /**
+ * Return this NDEF Message as raw bytes.<p>
+ * The NDEF Message is formatted as per the NDEF 1.0 specification,
+ * and the byte array is suitable for network transmission or storage
+ * in an NFC Forum NDEF compatible tag.<p>
+ * This method will not chunk any records, and will always use the
+ * short record (SR) format and omit the identifier field when possible.
+ *
+ * @return NDEF Message in binary format
+ * @see #getByteArrayLength()
+ */
+ public byte[] toByteArray() {
+ int length = getByteArrayLength();
+ ByteBuffer buffer = ByteBuffer.allocate(length);
+
+ for (int i=0; i<mRecords.length; i++) {
+ boolean mb = (i == 0); // first record
+ boolean me = (i == mRecords.length - 1); // last record
+ mRecords[i].writeToByteBuffer(buffer, mb, me);
+ }
+
+ return buffer.array();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mRecords.length);
+ dest.writeTypedArray(mRecords, flags);
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<NdefMessage> CREATOR =
+ new Parcelable.Creator<NdefMessage>() {
+ @Override
+ public NdefMessage createFromParcel(Parcel in) {
+ int recordsLength = in.readInt();
+ NdefRecord[] records = new NdefRecord[recordsLength];
+ in.readTypedArray(records, NdefRecord.CREATOR);
+ return new NdefMessage(records);
+ }
+ @Override
+ public NdefMessage[] newArray(int size) {
+ return new NdefMessage[size];
+ }
+ };
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(mRecords);
+ }
+
+ /**
+ * Returns true if the specified NDEF Message contains
+ * identical NDEF Records.
+ */
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+ NdefMessage other = (NdefMessage) obj;
+ return Arrays.equals(mRecords, other.mRecords);
+ }
+
+ @Override
+ public String toString() {
+ return "NdefMessage " + Arrays.toString(mRecords);
+ }
+
+ /**
+ * Dump debugging information as a NdefMessageProto
+ * @hide
+ *
+ * Note:
+ * See proto definition in frameworks/base/core/proto/android/nfc/ndef.proto
+ * When writing a nested message, must call {@link ProtoOutputStream#start(long)} before and
+ * {@link ProtoOutputStream#end(long)} after.
+ * Never reuse a proto field number. When removing a field, mark it as reserved.
+ */
+ public void dumpDebug(ProtoOutputStream proto) {
+ for (NdefRecord record : mRecords) {
+ long token = proto.start(NdefMessageProto.NDEF_RECORDS);
+ record.dumpDebug(proto);
+ proto.end(token);
+ }
+ }
+}
\ No newline at end of file
diff --git a/nfc/java/android/nfc/NdefRecord.aidl b/nfc/java/android/nfc/NdefRecord.aidl
new file mode 100644
index 0000000..10f89d0
--- /dev/null
+++ b/nfc/java/android/nfc/NdefRecord.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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;
+
+parcelable NdefRecord;
\ No newline at end of file
diff --git a/nfc/java/android/nfc/NdefRecord.java b/nfc/java/android/nfc/NdefRecord.java
new file mode 100644
index 0000000..7bf4355
--- /dev/null
+++ b/nfc/java/android/nfc/NdefRecord.java
@@ -0,0 +1,1080 @@
+/*
+ * 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.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.proto.ProtoOutputStream;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Represents an immutable NDEF Record.
+ * <p>
+ * NDEF (NFC Data Exchange Format) is a light-weight binary format,
+ * used to encapsulate typed data. It is specified by the NFC Forum,
+ * for transmission and storage with NFC, however it is transport agnostic.
+ * <p>
+ * NDEF defines messages and records. An NDEF Record contains
+ * typed data, such as MIME-type media, a URI, or a custom
+ * application payload. An NDEF Message is a container for
+ * one or more NDEF Records.
+ * <p>
+ * This class represents logical (complete) NDEF Records, and can not be
+ * used to represent chunked (partial) NDEF Records. However
+ * {@link NdefMessage#NdefMessage(byte[])} can be used to parse a message
+ * containing chunked records, and will return a message with unchunked
+ * (complete) records.
+ * <p>
+ * A logical NDEF Record always contains a 3-bit TNF (Type Name Field)
+ * that provides high level typing for the rest of the record. The
+ * remaining fields are variable length and not always present:
+ * <ul>
+ * <li><em>type</em>: detailed typing for the payload</li>
+ * <li><em>id</em>: identifier meta-data, not commonly used</li>
+ * <li><em>payload</em>: the actual payload</li>
+ * </ul>
+ * <p>
+ * Helpers such as {@link NdefRecord#createUri}, {@link NdefRecord#createMime}
+ * and {@link NdefRecord#createExternal} are included to create well-formatted
+ * NDEF Records with correctly set tnf, type, id and payload fields, please
+ * use these helpers whenever possible.
+ * <p>
+ * Use the constructor {@link #NdefRecord(short, byte[], byte[], byte[])}
+ * if you know what you are doing and what to set the fields individually.
+ * Only basic validation is performed with this constructor, so it is possible
+ * to create records that do not confirm to the strict NFC Forum
+ * specifications.
+ * <p>
+ * The binary representation of an NDEF Record includes additional flags to
+ * indicate location with an NDEF message, provide support for chunking of
+ * NDEF records, and to pack optional fields. This class does not expose
+ * those details. To write an NDEF Record as binary you must first put it
+ * into an {@link NdefMessage}, then call {@link NdefMessage#toByteArray()}.
+ * <p class="note">
+ * {@link NdefMessage} and {@link NdefRecord} implementations are
+ * always available, even on Android devices that do not have NFC hardware.
+ * <p class="note">
+ * {@link NdefRecord}s are intended to be immutable (and thread-safe),
+ * however they may contain mutable fields. So take care not to modify
+ * mutable fields passed into constructors, or modify mutable fields
+ * obtained by getter methods, unless such modification is explicitly
+ * marked as safe.
+ *
+ * @see NfcAdapter#ACTION_NDEF_DISCOVERED
+ * @see NdefMessage
+ */
+public final class NdefRecord implements Parcelable {
+ /**
+ * Indicates the record is empty.<p>
+ * Type, id and payload fields are empty in a {@literal TNF_EMPTY} record.
+ */
+ public static final short TNF_EMPTY = 0x00;
+
+ /**
+ * Indicates the type field contains a well-known RTD type name.<p>
+ * Use this tnf with RTD types such as {@link #RTD_TEXT}, {@link #RTD_URI}.
+ * <p>
+ * The RTD type name format is specified in NFCForum-TS-RTD_1.0.
+ *
+ * @see #RTD_URI
+ * @see #RTD_TEXT
+ * @see #RTD_SMART_POSTER
+ * @see #createUri
+ */
+ public static final short TNF_WELL_KNOWN = 0x01;
+
+ /**
+ * Indicates the type field contains a media-type BNF
+ * construct, defined by RFC 2046.<p>
+ * Use this with MIME type names such as {@literal "image/jpeg"}, or
+ * using the helper {@link #createMime}.
+ *
+ * @see #createMime
+ */
+ public static final short TNF_MIME_MEDIA = 0x02;
+
+ /**
+ * Indicates the type field contains an absolute-URI
+ * BNF construct defined by RFC 3986.<p>
+ * When creating new records prefer {@link #createUri},
+ * since it offers more compact URI encoding
+ * ({@literal #RTD_URI} allows compression of common URI prefixes).
+ *
+ * @see #createUri
+ */
+ public static final short TNF_ABSOLUTE_URI = 0x03;
+
+ /**
+ * Indicates the type field contains an external type name.<p>
+ * Used to encode custom payloads. When creating new records
+ * use the helper {@link #createExternal}.<p>
+ * The external-type RTD format is specified in NFCForum-TS-RTD_1.0.<p>
+ * <p>
+ * Note this TNF should not be used with RTD_TEXT or RTD_URI constants.
+ * Those are well known RTD constants, not external RTD constants.
+ *
+ * @see #createExternal
+ */
+ public static final short TNF_EXTERNAL_TYPE = 0x04;
+
+ /**
+ * Indicates the payload type is unknown.<p>
+ * NFC Forum explains this should be treated similarly to the
+ * "application/octet-stream" MIME type. The payload
+ * type is not explicitly encoded within the record.
+ * <p>
+ * The type field is empty in an {@literal TNF_UNKNOWN} record.
+ */
+ public static final short TNF_UNKNOWN = 0x05;
+
+ /**
+ * Indicates the payload is an intermediate or final chunk of a chunked
+ * NDEF Record.<p>
+ * {@literal TNF_UNCHANGED} can not be used with this class
+ * since all {@link NdefRecord}s are already unchunked, however they
+ * may appear in the binary format.
+ */
+ public static final short TNF_UNCHANGED = 0x06;
+
+ /**
+ * Reserved TNF type.
+ * <p>
+ * The NFC Forum NDEF Specification v1.0 suggests for NDEF parsers to treat this
+ * value like TNF_UNKNOWN.
+ * @hide
+ */
+ public static final short TNF_RESERVED = 0x07;
+
+ /**
+ * RTD Text type. For use with {@literal TNF_WELL_KNOWN}.
+ * @see #TNF_WELL_KNOWN
+ */
+ public static final byte[] RTD_TEXT = {0x54}; // "T"
+
+ /**
+ * RTD URI type. For use with {@literal TNF_WELL_KNOWN}.
+ * @see #TNF_WELL_KNOWN
+ */
+ public static final byte[] RTD_URI = {0x55}; // "U"
+
+ /**
+ * RTD Smart Poster type. For use with {@literal TNF_WELL_KNOWN}.
+ * @see #TNF_WELL_KNOWN
+ */
+ public static final byte[] RTD_SMART_POSTER = {0x53, 0x70}; // "Sp"
+
+ /**
+ * RTD Alternative Carrier type. For use with {@literal TNF_WELL_KNOWN}.
+ * @see #TNF_WELL_KNOWN
+ */
+ public static final byte[] RTD_ALTERNATIVE_CARRIER = {0x61, 0x63}; // "ac"
+
+ /**
+ * RTD Handover Carrier type. For use with {@literal TNF_WELL_KNOWN}.
+ * @see #TNF_WELL_KNOWN
+ */
+ public static final byte[] RTD_HANDOVER_CARRIER = {0x48, 0x63}; // "Hc"
+
+ /**
+ * RTD Handover Request type. For use with {@literal TNF_WELL_KNOWN}.
+ * @see #TNF_WELL_KNOWN
+ */
+ public static final byte[] RTD_HANDOVER_REQUEST = {0x48, 0x72}; // "Hr"
+
+ /**
+ * RTD Handover Select type. For use with {@literal TNF_WELL_KNOWN}.
+ * @see #TNF_WELL_KNOWN
+ */
+ public static final byte[] RTD_HANDOVER_SELECT = {0x48, 0x73}; // "Hs"
+
+ /**
+ * RTD Android app type. For use with {@literal TNF_EXTERNAL}.
+ * <p>
+ * The payload of a record with type RTD_ANDROID_APP
+ * should be the package name identifying an application.
+ * Multiple RTD_ANDROID_APP records may be included
+ * in a single {@link NdefMessage}.
+ * <p>
+ * Use {@link #createApplicationRecord(String)} to create
+ * RTD_ANDROID_APP records.
+ * @hide
+ */
+ public static final byte[] RTD_ANDROID_APP = "android.com:pkg".getBytes();
+
+ private static final byte FLAG_MB = (byte) 0x80;
+ private static final byte FLAG_ME = (byte) 0x40;
+ private static final byte FLAG_CF = (byte) 0x20;
+ private static final byte FLAG_SR = (byte) 0x10;
+ private static final byte FLAG_IL = (byte) 0x08;
+
+ /**
+ * NFC Forum "URI Record Type Definition"<p>
+ * This is a mapping of "URI Identifier Codes" to URI string prefixes,
+ * per section 3.2.2 of the NFC Forum URI Record Type Definition document.
+ */
+ private static final String[] URI_PREFIX_MAP = new String[] {
+ "", // 0x00
+ "http://www.", // 0x01
+ "https://www.", // 0x02
+ "http://", // 0x03
+ "https://", // 0x04
+ "tel:", // 0x05
+ "mailto:", // 0x06
+ "ftp://anonymous:anonymous@", // 0x07
+ "ftp://ftp.", // 0x08
+ "ftps://", // 0x09
+ "sftp://", // 0x0A
+ "smb://", // 0x0B
+ "nfs://", // 0x0C
+ "ftp://", // 0x0D
+ "dav://", // 0x0E
+ "news:", // 0x0F
+ "telnet://", // 0x10
+ "imap:", // 0x11
+ "rtsp://", // 0x12
+ "urn:", // 0x13
+ "pop:", // 0x14
+ "sip:", // 0x15
+ "sips:", // 0x16
+ "tftp:", // 0x17
+ "btspp://", // 0x18
+ "btl2cap://", // 0x19
+ "btgoep://", // 0x1A
+ "tcpobex://", // 0x1B
+ "irdaobex://", // 0x1C
+ "file://", // 0x1D
+ "urn:epc:id:", // 0x1E
+ "urn:epc:tag:", // 0x1F
+ "urn:epc:pat:", // 0x20
+ "urn:epc:raw:", // 0x21
+ "urn:epc:", // 0x22
+ "urn:nfc:", // 0x23
+ };
+
+ private static final int MAX_PAYLOAD_SIZE = 10 * (1 << 20); // 10 MB payload limit
+
+ private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
+
+ private final short mTnf;
+ private final byte[] mType;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ private final byte[] mId;
+ private final byte[] mPayload;
+
+ /**
+ * Create a new Android Application Record (AAR).
+ * <p>
+ * This record indicates to other Android devices the package
+ * that should be used to handle the entire NDEF message.
+ * You can embed this record anywhere into your message
+ * to ensure that the intended package receives the message.
+ * <p>
+ * When an Android device dispatches an {@link NdefMessage}
+ * containing one or more Android application records,
+ * the applications contained in those records will be the
+ * preferred target for the {@link NfcAdapter#ACTION_NDEF_DISCOVERED}
+ * intent, in the order in which they appear in the message.
+ * This dispatch behavior was first added to Android in
+ * Ice Cream Sandwich.
+ * <p>
+ * If none of the applications have a are installed on the device,
+ * a Market link will be opened to the first application.
+ * <p>
+ * Note that Android application records do not overrule
+ * applications that have called
+ * {@link NfcAdapter#enableForegroundDispatch}.
+ *
+ * @param packageName Android package name
+ * @return Android application NDEF record
+ */
+ public static NdefRecord createApplicationRecord(String packageName) {
+ if (packageName == null) throw new NullPointerException("packageName is null");
+ if (packageName.length() == 0) throw new IllegalArgumentException("packageName is empty");
+
+ return new NdefRecord(TNF_EXTERNAL_TYPE, RTD_ANDROID_APP, null,
+ packageName.getBytes(StandardCharsets.UTF_8));
+ }
+
+ /**
+ * Create a new NDEF Record containing a URI.<p>
+ * Use this method to encode a URI (or URL) into an NDEF Record.<p>
+ * Uses the well known URI type representation: {@link #TNF_WELL_KNOWN}
+ * and {@link #RTD_URI}. This is the most efficient encoding
+ * of a URI into NDEF.<p>
+ * The uri parameter will be normalized with
+ * {@link Uri#normalizeScheme} to set the scheme to lower case to
+ * follow Android best practices for intent filtering.
+ * However the unchecked exception
+ * {@link IllegalArgumentException} may be thrown if the uri
+ * parameter has serious problems, for example if it is empty, so always
+ * catch this exception if you are passing user-generated data into this
+ * method.<p>
+ *
+ * Reference specification: NFCForum-TS-RTD_URI_1.0
+ *
+ * @param uri URI to encode.
+ * @return an NDEF Record containing the URI
+ * @throws IllegalArugmentException if the uri is empty or invalid
+ */
+ public static NdefRecord createUri(Uri uri) {
+ if (uri == null) throw new NullPointerException("uri is null");
+
+ uri = uri.normalizeScheme();
+ String uriString = uri.toString();
+ if (uriString.length() == 0) throw new IllegalArgumentException("uri is empty");
+
+ byte prefix = 0;
+ for (int i = 1; i < URI_PREFIX_MAP.length; i++) {
+ if (uriString.startsWith(URI_PREFIX_MAP[i])) {
+ prefix = (byte) i;
+ uriString = uriString.substring(URI_PREFIX_MAP[i].length());
+ break;
+ }
+ }
+ byte[] uriBytes = uriString.getBytes(StandardCharsets.UTF_8);
+ byte[] recordBytes = new byte[uriBytes.length + 1];
+ recordBytes[0] = prefix;
+ System.arraycopy(uriBytes, 0, recordBytes, 1, uriBytes.length);
+ return new NdefRecord(TNF_WELL_KNOWN, RTD_URI, null, recordBytes);
+ }
+
+ /**
+ * Create a new NDEF Record containing a URI.<p>
+ * Use this method to encode a URI (or URL) into an NDEF Record.<p>
+ * Uses the well known URI type representation: {@link #TNF_WELL_KNOWN}
+ * and {@link #RTD_URI}. This is the most efficient encoding
+ * of a URI into NDEF.<p>
+ * The uriString parameter will be normalized with
+ * {@link Uri#normalizeScheme} to set the scheme to lower case to
+ * follow Android best practices for intent filtering.
+ * However the unchecked exception
+ * {@link IllegalArgumentException} may be thrown if the uriString
+ * parameter has serious problems, for example if it is empty, so always
+ * catch this exception if you are passing user-generated data into this
+ * method.<p>
+ *
+ * Reference specification: NFCForum-TS-RTD_URI_1.0
+ *
+ * @param uriString string URI to encode.
+ * @return an NDEF Record containing the URI
+ * @throws IllegalArugmentException if the uriString is empty or invalid
+ */
+ public static NdefRecord createUri(String uriString) {
+ return createUri(Uri.parse(uriString));
+ }
+
+ /**
+ * Create a new NDEF Record containing MIME data.<p>
+ * Use this method to encode MIME-typed data into an NDEF Record,
+ * such as "text/plain", or "image/jpeg".<p>
+ * The mimeType parameter will be normalized with
+ * {@link Intent#normalizeMimeType} to follow Android best
+ * practices for intent filtering, for example to force lower-case.
+ * However the unchecked exception
+ * {@link IllegalArgumentException} may be thrown
+ * if the mimeType parameter has serious problems,
+ * for example if it is empty, so always catch this
+ * exception if you are passing user-generated data into this method.
+ * <p>
+ * For efficiency, This method might not make an internal copy of the
+ * mimeData byte array, so take care not
+ * to modify the mimeData byte array while still using the returned
+ * NdefRecord.
+ *
+ * @param mimeType a valid MIME type
+ * @param mimeData MIME data as bytes
+ * @return an NDEF Record containing the MIME-typed data
+ * @throws IllegalArugmentException if the mimeType is empty or invalid
+ *
+ */
+ public static NdefRecord createMime(String mimeType, byte[] mimeData) {
+ if (mimeType == null) throw new NullPointerException("mimeType is null");
+
+ // We only do basic MIME type validation: trying to follow the
+ // RFCs strictly only ends in tears, since there are lots of MIME
+ // types in common use that are not strictly valid as per RFC rules
+ mimeType = Intent.normalizeMimeType(mimeType);
+ if (mimeType.length() == 0) throw new IllegalArgumentException("mimeType is empty");
+ int slashIndex = mimeType.indexOf('/');
+ if (slashIndex == 0) throw new IllegalArgumentException("mimeType must have major type");
+ if (slashIndex == mimeType.length() - 1) {
+ throw new IllegalArgumentException("mimeType must have minor type");
+ }
+ // missing '/' is allowed
+
+ // MIME RFCs suggest ASCII encoding for content-type
+ byte[] typeBytes = mimeType.getBytes(StandardCharsets.US_ASCII);
+ return new NdefRecord(TNF_MIME_MEDIA, typeBytes, null, mimeData);
+ }
+
+ /**
+ * Create a new NDEF Record containing external (application-specific) data.<p>
+ * Use this method to encode application specific data into an NDEF Record.
+ * The data is typed by a domain name (usually your Android package name) and
+ * a domain-specific type. This data is packaged into a "NFC Forum External
+ * Type" NDEF Record.<p>
+ * NFC Forum requires that the domain and type used in an external record
+ * are treated as case insensitive, however Android intent filtering is
+ * always case sensitive. So this method will force the domain and type to
+ * lower-case before creating the NDEF Record.<p>
+ * The unchecked exception {@link IllegalArgumentException} will be thrown
+ * if the domain and type have serious problems, for example if either field
+ * is empty, so always catch this
+ * exception if you are passing user-generated data into this method.<p>
+ * There are no such restrictions on the payload data.<p>
+ * For efficiency, This method might not make an internal copy of the
+ * data byte array, so take care not
+ * to modify the data byte array while still using the returned
+ * NdefRecord.
+ *
+ * Reference specification: NFCForum-TS-RTD_1.0
+ * @param domain domain-name of issuing organization
+ * @param type domain-specific type of data
+ * @param data payload as bytes
+ * @throws IllegalArugmentException if either domain or type are empty or invalid
+ */
+ public static NdefRecord createExternal(String domain, String type, byte[] data) {
+ if (domain == null) throw new NullPointerException("domain is null");
+ if (type == null) throw new NullPointerException("type is null");
+
+ domain = domain.trim().toLowerCase(Locale.ROOT);
+ type = type.trim().toLowerCase(Locale.ROOT);
+
+ if (domain.length() == 0) throw new IllegalArgumentException("domain is empty");
+ if (type.length() == 0) throw new IllegalArgumentException("type is empty");
+
+ byte[] byteDomain = domain.getBytes(StandardCharsets.UTF_8);
+ byte[] byteType = type.getBytes(StandardCharsets.UTF_8);
+ byte[] b = new byte[byteDomain.length + 1 + byteType.length];
+ System.arraycopy(byteDomain, 0, b, 0, byteDomain.length);
+ b[byteDomain.length] = ':';
+ System.arraycopy(byteType, 0, b, byteDomain.length + 1, byteType.length);
+
+ return new NdefRecord(TNF_EXTERNAL_TYPE, b, null, data);
+ }
+
+ /**
+ * Create a new NDEF record containing UTF-8 text data.<p>
+ *
+ * The caller can either specify the language code for the provided text,
+ * or otherwise the language code corresponding to the current default
+ * locale will be used.
+ *
+ * Reference specification: NFCForum-TS-RTD_Text_1.0
+ * @param languageCode The languageCode for the record. If locale is empty or null,
+ * the language code of the current default locale will be used.
+ * @param text The text to be encoded in the record. Will be represented in UTF-8 format.
+ * @throws IllegalArgumentException if text is null
+ */
+ public static NdefRecord createTextRecord(String languageCode, String text) {
+ if (text == null) throw new NullPointerException("text is null");
+
+ byte[] textBytes = text.getBytes(StandardCharsets.UTF_8);
+
+ byte[] languageCodeBytes = null;
+ if (languageCode != null && !languageCode.isEmpty()) {
+ languageCodeBytes = languageCode.getBytes(StandardCharsets.US_ASCII);
+ } else {
+ languageCodeBytes = Locale.getDefault().getLanguage().
+ getBytes(StandardCharsets.US_ASCII);
+ }
+ // We only have 6 bits to indicate ISO/IANA language code.
+ if (languageCodeBytes.length >= 64) {
+ throw new IllegalArgumentException("language code is too long, must be <64 bytes.");
+ }
+ ByteBuffer buffer = ByteBuffer.allocate(1 + languageCodeBytes.length + textBytes.length);
+
+ byte status = (byte) (languageCodeBytes.length & 0xFF);
+ buffer.put(status);
+ buffer.put(languageCodeBytes);
+ buffer.put(textBytes);
+
+ return new NdefRecord(TNF_WELL_KNOWN, RTD_TEXT, null, buffer.array());
+ }
+
+ /**
+ * Construct an NDEF Record from its component fields.<p>
+ * Recommend to use helpers such as {#createUri} or
+ * {{@link #createExternal} where possible, since they perform
+ * stricter validation that the record is correctly formatted
+ * as per NDEF specifications. However if you know what you are
+ * doing then this constructor offers the most flexibility.<p>
+ * An {@link NdefRecord} represents a logical (complete)
+ * record, and cannot represent NDEF Record chunks.<p>
+ * Basic validation of the tnf, type, id and payload is performed
+ * as per the following rules:
+ * <ul>
+ * <li>The tnf paramter must be a 3-bit value.</li>
+ * <li>Records with a tnf of {@link #TNF_EMPTY} cannot have a type,
+ * id or payload.</li>
+ * <li>Records with a tnf of {@link #TNF_UNKNOWN} or {@literal 0x07}
+ * cannot have a type.</li>
+ * <li>Records with a tnf of {@link #TNF_UNCHANGED} are not allowed
+ * since this class only represents complete (unchunked) records.</li>
+ * </ul>
+ * This minimal validation is specified by
+ * NFCForum-TS-NDEF_1.0 section 3.2.6 (Type Name Format).<p>
+ * If any of the above validation
+ * steps fail then {@link IllegalArgumentException} is thrown.<p>
+ * Deep inspection of the type, id and payload fields is not
+ * performed, so it is possible to create NDEF Records
+ * that conform to section 3.2.6
+ * but fail other more strict NDEF specification requirements. For
+ * example, the payload may be invalid given the tnf and type.
+ * <p>
+ * To omit a type, id or payload field, set the parameter to an
+ * empty byte array or null.
+ *
+ * @param tnf a 3-bit TNF constant
+ * @param type byte array, containing zero to 255 bytes, or null
+ * @param id byte array, containing zero to 255 bytes, or null
+ * @param payload byte array, containing zero to (2 ** 32 - 1) bytes,
+ * or null
+ * @throws IllegalArugmentException if a valid record cannot be created
+ */
+ public NdefRecord(short tnf, byte[] type, byte[] id, byte[] payload) {
+ /* convert nulls */
+ if (type == null) type = EMPTY_BYTE_ARRAY;
+ if (id == null) id = EMPTY_BYTE_ARRAY;
+ if (payload == null) payload = EMPTY_BYTE_ARRAY;
+
+ String message = validateTnf(tnf, type, id, payload);
+ if (message != null) {
+ throw new IllegalArgumentException(message);
+ }
+
+ mTnf = tnf;
+ mType = type;
+ mId = id;
+ mPayload = payload;
+ }
+
+ /**
+ * Construct an NDEF Record from raw bytes.<p>
+ * This method is deprecated, use {@link NdefMessage#NdefMessage(byte[])}
+ * instead. This is because it does not make sense to parse a record:
+ * the NDEF binary format is only defined for a message, and the
+ * record flags MB and ME do not make sense outside of the context of
+ * an entire message.<p>
+ * This implementation will attempt to parse a single record by ignoring
+ * the MB and ME flags, and otherwise following the rules of
+ * {@link NdefMessage#NdefMessage(byte[])}.<p>
+ *
+ * @param data raw bytes to parse
+ * @throws FormatException if the data cannot be parsed into a valid record
+ * @deprecated use {@link NdefMessage#NdefMessage(byte[])} instead.
+ */
+ @Deprecated
+ public NdefRecord(byte[] data) throws FormatException {
+ ByteBuffer buffer = ByteBuffer.wrap(data);
+ NdefRecord[] rs = parse(buffer, true);
+
+ if (buffer.remaining() > 0) {
+ throw new FormatException("data too long");
+ }
+
+ mTnf = rs[0].mTnf;
+ mType = rs[0].mType;
+ mId = rs[0].mId;
+ mPayload = rs[0].mPayload;
+ }
+
+ /**
+ * Returns the 3-bit TNF.
+ * <p>
+ * TNF is the top-level type.
+ */
+ public short getTnf() {
+ return mTnf;
+ }
+
+ /**
+ * Returns the variable length Type field.
+ * <p>
+ * This should be used in conjunction with the TNF field to determine the
+ * payload format.
+ * <p>
+ * Returns an empty byte array if this record
+ * does not have a type field.
+ */
+ public byte[] getType() {
+ return mType.clone();
+ }
+
+ /**
+ * Returns the variable length ID.
+ * <p>
+ * Returns an empty byte array if this record
+ * does not have an id field.
+ */
+ public byte[] getId() {
+ return mId.clone();
+ }
+
+ /**
+ * Returns the variable length payload.
+ * <p>
+ * Returns an empty byte array if this record
+ * does not have a payload field.
+ */
+ public byte[] getPayload() {
+ return mPayload.clone();
+ }
+
+ /**
+ * Return this NDEF Record as a byte array.<p>
+ * This method is deprecated, use {@link NdefMessage#toByteArray}
+ * instead. This is because the NDEF binary format is not defined for
+ * a record outside of the context of a message: the MB and ME flags
+ * cannot be set without knowing the location inside a message.<p>
+ * This implementation will attempt to serialize a single record by
+ * always setting the MB and ME flags (in other words, assume this
+ * is a single-record NDEF Message).<p>
+ *
+ * @deprecated use {@link NdefMessage#toByteArray()} instead
+ */
+ @Deprecated
+ public byte[] toByteArray() {
+ ByteBuffer buffer = ByteBuffer.allocate(getByteLength());
+ writeToByteBuffer(buffer, true, true);
+ return buffer.array();
+ }
+
+ /**
+ * Map this record to a MIME type, or return null if it cannot be mapped.<p>
+ * Currently this method considers all {@link #TNF_MIME_MEDIA} records to
+ * be MIME records, as well as some {@link #TNF_WELL_KNOWN} records such as
+ * {@link #RTD_TEXT}. If this is a MIME record then the MIME type as string
+ * is returned, otherwise null is returned.<p>
+ * This method does not perform validation that the MIME type is
+ * actually valid. It always attempts to
+ * return a string containing the type if this is a MIME record.<p>
+ * The returned MIME type will by normalized to lower-case using
+ * {@link Intent#normalizeMimeType}.<p>
+ * The MIME payload can be obtained using {@link #getPayload}.
+ *
+ * @return MIME type as a string, or null if this is not a MIME record
+ */
+ public String toMimeType() {
+ switch (mTnf) {
+ case NdefRecord.TNF_WELL_KNOWN:
+ if (Arrays.equals(mType, NdefRecord.RTD_TEXT)) {
+ return "text/plain";
+ }
+ break;
+ case NdefRecord.TNF_MIME_MEDIA:
+ String mimeType = new String(mType, StandardCharsets.US_ASCII);
+ return Intent.normalizeMimeType(mimeType);
+ }
+ return null;
+ }
+
+ /**
+ * Map this record to a URI, or return null if it cannot be mapped.<p>
+ * Currently this method considers the following to be URI records:
+ * <ul>
+ * <li>{@link #TNF_ABSOLUTE_URI} records.</li>
+ * <li>{@link #TNF_WELL_KNOWN} with a type of {@link #RTD_URI}.</li>
+ * <li>{@link #TNF_WELL_KNOWN} with a type of {@link #RTD_SMART_POSTER}
+ * and containing a URI record in the NDEF message nested in the payload.
+ * </li>
+ * <li>{@link #TNF_EXTERNAL_TYPE} records.</li>
+ * </ul>
+ * If this is not a URI record by the above rules, then null is returned.<p>
+ * This method does not perform validation that the URI is
+ * actually valid: it always attempts to create and return a URI if
+ * this record appears to be a URI record by the above rules.<p>
+ * The returned URI will be normalized to have a lower case scheme
+ * using {@link Uri#normalizeScheme}.<p>
+ *
+ * @return URI, or null if this is not a URI record
+ */
+ public Uri toUri() {
+ return toUri(false);
+ }
+
+ private Uri toUri(boolean inSmartPoster) {
+ switch (mTnf) {
+ case TNF_WELL_KNOWN:
+ if (Arrays.equals(mType, RTD_SMART_POSTER) && !inSmartPoster) {
+ try {
+ // check payload for a nested NDEF Message containing a URI
+ NdefMessage nestedMessage = new NdefMessage(mPayload);
+ for (NdefRecord nestedRecord : nestedMessage.getRecords()) {
+ Uri uri = nestedRecord.toUri(true);
+ if (uri != null) {
+ return uri;
+ }
+ }
+ } catch (FormatException e) { }
+ } else if (Arrays.equals(mType, RTD_URI)) {
+ Uri wktUri = parseWktUri();
+ return (wktUri != null ? wktUri.normalizeScheme() : null);
+ }
+ break;
+
+ case TNF_ABSOLUTE_URI:
+ Uri uri = Uri.parse(new String(mType, StandardCharsets.UTF_8));
+ return uri.normalizeScheme();
+
+ case TNF_EXTERNAL_TYPE:
+ if (inSmartPoster) {
+ break;
+ }
+ return Uri.parse("vnd.android.nfc://ext/" + new String(mType, StandardCharsets.US_ASCII));
+ }
+ return null;
+ }
+
+ /**
+ * Return complete URI of {@link #TNF_WELL_KNOWN}, {@link #RTD_URI} records.
+ * @return complete URI, or null if invalid
+ */
+ private Uri parseWktUri() {
+ if (mPayload.length < 2) {
+ return null;
+ }
+
+ // payload[0] contains the URI Identifier Code, as per
+ // NFC Forum "URI Record Type Definition" section 3.2.2.
+ int prefixIndex = (mPayload[0] & (byte)0xFF);
+ if (prefixIndex < 0 || prefixIndex >= URI_PREFIX_MAP.length) {
+ return null;
+ }
+ String prefix = URI_PREFIX_MAP[prefixIndex];
+ String suffix = new String(Arrays.copyOfRange(mPayload, 1, mPayload.length),
+ StandardCharsets.UTF_8);
+ return Uri.parse(prefix + suffix);
+ }
+
+ /**
+ * Main record parsing method.<p>
+ * Expects NdefMessage to begin immediately, allows trailing data.<p>
+ * Currently has strict validation of all fields as per NDEF 1.0
+ * specification section 2.5. We will attempt to keep this as strict as
+ * possible to encourage well-formatted NDEF.<p>
+ * Always returns 1 or more NdefRecord's, or throws FormatException.
+ *
+ * @param buffer ByteBuffer to read from
+ * @param ignoreMbMe ignore MB and ME flags, and read only 1 complete record
+ * @return one or more records
+ * @throws FormatException on any parsing error
+ */
+ static NdefRecord[] parse(ByteBuffer buffer, boolean ignoreMbMe) throws FormatException {
+ List<NdefRecord> records = new ArrayList<NdefRecord>();
+
+ try {
+ byte[] type = null;
+ byte[] id = null;
+ byte[] payload = null;
+ ArrayList<byte[]> chunks = new ArrayList<byte[]>();
+ boolean inChunk = false;
+ short chunkTnf = -1;
+ boolean me = false;
+
+ while (!me) {
+ byte flag = buffer.get();
+
+ boolean mb = (flag & NdefRecord.FLAG_MB) != 0;
+ me = (flag & NdefRecord.FLAG_ME) != 0;
+ boolean cf = (flag & NdefRecord.FLAG_CF) != 0;
+ boolean sr = (flag & NdefRecord.FLAG_SR) != 0;
+ boolean il = (flag & NdefRecord.FLAG_IL) != 0;
+ short tnf = (short)(flag & 0x07);
+
+ if (!mb && records.size() == 0 && !inChunk && !ignoreMbMe) {
+ throw new FormatException("expected MB flag");
+ } else if (mb && (records.size() != 0 || inChunk) && !ignoreMbMe) {
+ throw new FormatException("unexpected MB flag");
+ } else if (inChunk && il) {
+ throw new FormatException("unexpected IL flag in non-leading chunk");
+ } else if (cf && me) {
+ throw new FormatException("unexpected ME flag in non-trailing chunk");
+ } else if (inChunk && tnf != NdefRecord.TNF_UNCHANGED) {
+ throw new FormatException("expected TNF_UNCHANGED in non-leading chunk");
+ } else if (!inChunk && tnf == NdefRecord.TNF_UNCHANGED) {
+ throw new FormatException("" +
+ "unexpected TNF_UNCHANGED in first chunk or unchunked record");
+ }
+
+ int typeLength = buffer.get() & 0xFF;
+ long payloadLength = sr ? (buffer.get() & 0xFF) : (buffer.getInt() & 0xFFFFFFFFL);
+ int idLength = il ? (buffer.get() & 0xFF) : 0;
+
+ if (inChunk && typeLength != 0) {
+ throw new FormatException("expected zero-length type in non-leading chunk");
+ }
+
+ if (!inChunk) {
+ type = (typeLength > 0 ? new byte[typeLength] : EMPTY_BYTE_ARRAY);
+ id = (idLength > 0 ? new byte[idLength] : EMPTY_BYTE_ARRAY);
+ buffer.get(type);
+ buffer.get(id);
+ }
+
+ ensureSanePayloadSize(payloadLength);
+ payload = (payloadLength > 0 ? new byte[(int)payloadLength] : EMPTY_BYTE_ARRAY);
+ buffer.get(payload);
+
+ if (cf && !inChunk) {
+ // first chunk
+ if (typeLength == 0 && tnf != NdefRecord.TNF_UNKNOWN) {
+ throw new FormatException("expected non-zero type length in first chunk");
+ }
+ chunks.clear();
+ chunkTnf = tnf;
+ }
+ if (cf || inChunk) {
+ // any chunk
+ chunks.add(payload);
+ }
+ if (!cf && inChunk) {
+ // last chunk, flatten the payload
+ payloadLength = 0;
+ for (byte[] p : chunks) {
+ payloadLength += p.length;
+ }
+ ensureSanePayloadSize(payloadLength);
+ payload = new byte[(int)payloadLength];
+ int i = 0;
+ for (byte[] p : chunks) {
+ System.arraycopy(p, 0, payload, i, p.length);
+ i += p.length;
+ }
+ tnf = chunkTnf;
+ }
+ if (cf) {
+ // more chunks to come
+ inChunk = true;
+ continue;
+ } else {
+ inChunk = false;
+ }
+
+ String error = validateTnf(tnf, type, id, payload);
+ if (error != null) {
+ throw new FormatException(error);
+ }
+ records.add(new NdefRecord(tnf, type, id, payload));
+ if (ignoreMbMe) { // for parsing a single NdefRecord
+ break;
+ }
+ }
+ } catch (BufferUnderflowException e) {
+ throw new FormatException("expected more data", e);
+ }
+ return records.toArray(new NdefRecord[records.size()]);
+ }
+
+ private static void ensureSanePayloadSize(long size) throws FormatException {
+ if (size > MAX_PAYLOAD_SIZE) {
+ throw new FormatException(
+ "payload above max limit: " + size + " > " + MAX_PAYLOAD_SIZE);
+ }
+ }
+
+ /**
+ * Perform simple validation that the tnf is valid.<p>
+ * Validates the requirements of NFCForum-TS-NDEF_1.0 section
+ * 3.2.6 (Type Name Format). This just validates that the tnf
+ * is valid, and that the relevant type, id and payload
+ * fields are present (or empty) for this tnf. It does not
+ * perform any deep inspection of the type, id and payload fields.<p>
+ * Also does not allow TNF_UNCHANGED since this class is only used
+ * to present logical (unchunked) records.
+ *
+ * @return null if valid, or a string error if invalid.
+ */
+ static String validateTnf(short tnf, byte[] type, byte[] id, byte[] payload) {
+ switch (tnf) {
+ case TNF_EMPTY:
+ if (type.length != 0 || id.length != 0 || payload.length != 0) {
+ return "unexpected data in TNF_EMPTY record";
+ }
+ return null;
+ case TNF_WELL_KNOWN:
+ case TNF_MIME_MEDIA:
+ case TNF_ABSOLUTE_URI:
+ case TNF_EXTERNAL_TYPE:
+ return null;
+ case TNF_UNKNOWN:
+ case TNF_RESERVED:
+ if (type.length != 0) {
+ return "unexpected type field in TNF_UNKNOWN or TNF_RESERVEd record";
+ }
+ return null;
+ case TNF_UNCHANGED:
+ return "unexpected TNF_UNCHANGED in first chunk or logical record";
+ default:
+ return String.format("unexpected tnf value: 0x%02x", tnf);
+ }
+ }
+
+ /**
+ * Serialize record for network transmission.<p>
+ * Uses specified MB and ME flags.<p>
+ * Does not chunk records.
+ */
+ void writeToByteBuffer(ByteBuffer buffer, boolean mb, boolean me) {
+ boolean sr = mPayload.length < 256;
+ boolean il = mTnf == TNF_EMPTY ? true : mId.length > 0;
+
+ byte flags = (byte)((mb ? FLAG_MB : 0) | (me ? FLAG_ME : 0) |
+ (sr ? FLAG_SR : 0) | (il ? FLAG_IL : 0) | mTnf);
+ buffer.put(flags);
+
+ buffer.put((byte)mType.length);
+ if (sr) {
+ buffer.put((byte)mPayload.length);
+ } else {
+ buffer.putInt(mPayload.length);
+ }
+ if (il) {
+ buffer.put((byte)mId.length);
+ }
+
+ buffer.put(mType);
+ buffer.put(mId);
+ buffer.put(mPayload);
+ }
+
+ /**
+ * Get byte length of serialized record.
+ */
+ int getByteLength() {
+ int length = 3 + mType.length + mId.length + mPayload.length;
+
+ boolean sr = mPayload.length < 256;
+ boolean il = mTnf == TNF_EMPTY ? true : mId.length > 0;
+
+ if (!sr) length += 3;
+ if (il) length += 1;
+
+ return length;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mTnf);
+ dest.writeInt(mType.length);
+ dest.writeByteArray(mType);
+ dest.writeInt(mId.length);
+ dest.writeByteArray(mId);
+ dest.writeInt(mPayload.length);
+ dest.writeByteArray(mPayload);
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<NdefRecord> CREATOR =
+ new Parcelable.Creator<NdefRecord>() {
+ @Override
+ public NdefRecord createFromParcel(Parcel in) {
+ short tnf = (short)in.readInt();
+ int typeLength = in.readInt();
+ byte[] type = new byte[typeLength];
+ in.readByteArray(type);
+ int idLength = in.readInt();
+ byte[] id = new byte[idLength];
+ in.readByteArray(id);
+ int payloadLength = in.readInt();
+ byte[] payload = new byte[payloadLength];
+ in.readByteArray(payload);
+
+ return new NdefRecord(tnf, type, id, payload);
+ }
+ @Override
+ public NdefRecord[] newArray(int size) {
+ return new NdefRecord[size];
+ }
+ };
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + Arrays.hashCode(mId);
+ result = prime * result + Arrays.hashCode(mPayload);
+ result = prime * result + mTnf;
+ result = prime * result + Arrays.hashCode(mType);
+ return result;
+ }
+
+ /**
+ * Returns true if the specified NDEF Record contains
+ * identical tnf, type, id and payload fields.
+ */
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+ NdefRecord other = (NdefRecord) obj;
+ if (!Arrays.equals(mId, other.mId)) return false;
+ if (!Arrays.equals(mPayload, other.mPayload)) return false;
+ if (mTnf != other.mTnf) return false;
+ return Arrays.equals(mType, other.mType);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder(String.format("NdefRecord tnf=%X", mTnf));
+ if (mType.length > 0) b.append(" type=").append(bytesToString(mType));
+ if (mId.length > 0) b.append(" id=").append(bytesToString(mId));
+ if (mPayload.length > 0) b.append(" payload=").append(bytesToString(mPayload));
+ return b.toString();
+ }
+
+ /**
+ * Dump debugging information as a NdefRecordProto
+ * @hide
+ *
+ * Note:
+ * See proto definition in frameworks/base/core/proto/android/nfc/ndef.proto
+ * When writing a nested message, must call {@link ProtoOutputStream#start(long)} before and
+ * {@link ProtoOutputStream#end(long)} after.
+ * Never reuse a proto field number. When removing a field, mark it as reserved.
+ */
+ public void dumpDebug(ProtoOutputStream proto) {
+ proto.write(NdefRecordProto.TYPE, mType);
+ proto.write(NdefRecordProto.ID, mId);
+ proto.write(NdefRecordProto.PAYLOAD_BYTES, mPayload.length);
+ }
+
+ private static StringBuilder bytesToString(byte[] bs) {
+ StringBuilder s = new StringBuilder();
+ for (byte b : bs) {
+ s.append(String.format("%02X", b));
+ }
+ return s;
+ }
+}
diff --git a/nfc/java/android/nfc/NfcActivityManager.java b/nfc/java/android/nfc/NfcActivityManager.java
new file mode 100644
index 0000000..f03fc0a
--- /dev/null
+++ b/nfc/java/android/nfc/NfcActivityManager.java
@@ -0,0 +1,410 @@
+/*
+ * Copyright (C) 2011 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.app.Activity;
+import android.app.Application;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.nfc.NfcAdapter.ReaderCallback;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Manages NFC API's that are coupled to the life-cycle of an Activity.
+ *
+ * <p>Uses {@link Application#registerActivityLifecycleCallbacks} to hook
+ * into activity life-cycle events such as onPause() and onResume().
+ *
+ * @hide
+ */
+public final class NfcActivityManager extends IAppCallback.Stub
+ implements Application.ActivityLifecycleCallbacks {
+ static final String TAG = NfcAdapter.TAG;
+ static final Boolean DBG = false;
+
+ @UnsupportedAppUsage
+ final NfcAdapter mAdapter;
+
+ // All objects in the lists are protected by this
+ final List<NfcApplicationState> mApps; // Application(s) that have NFC state. Usually one
+ final List<NfcActivityState> mActivities; // Activities that have NFC state
+
+ /**
+ * NFC State associated with an {@link Application}.
+ */
+ class NfcApplicationState {
+ int refCount = 0;
+ final Application app;
+ public NfcApplicationState(Application app) {
+ this.app = app;
+ }
+ public void register() {
+ refCount++;
+ if (refCount == 1) {
+ this.app.registerActivityLifecycleCallbacks(NfcActivityManager.this);
+ }
+ }
+ public void unregister() {
+ refCount--;
+ if (refCount == 0) {
+ this.app.unregisterActivityLifecycleCallbacks(NfcActivityManager.this);
+ } else if (refCount < 0) {
+ Log.e(TAG, "-ve refcount for " + app);
+ }
+ }
+ }
+
+ NfcApplicationState findAppState(Application app) {
+ for (NfcApplicationState appState : mApps) {
+ if (appState.app == app) {
+ return appState;
+ }
+ }
+ return null;
+ }
+
+ void registerApplication(Application app) {
+ NfcApplicationState appState = findAppState(app);
+ if (appState == null) {
+ appState = new NfcApplicationState(app);
+ mApps.add(appState);
+ }
+ appState.register();
+ }
+
+ void unregisterApplication(Application app) {
+ NfcApplicationState appState = findAppState(app);
+ if (appState == null) {
+ Log.e(TAG, "app was not registered " + app);
+ return;
+ }
+ appState.unregister();
+ }
+
+ /**
+ * NFC state associated with an {@link Activity}
+ */
+ class NfcActivityState {
+ boolean resumed = false;
+ Activity activity;
+ NfcAdapter.ReaderCallback readerCallback = null;
+ int readerModeFlags = 0;
+ Bundle readerModeExtras = null;
+ Binder token;
+
+ int mPollTech = NfcAdapter.FLAG_USE_ALL_TECH;
+ int mListenTech = NfcAdapter.FLAG_USE_ALL_TECH;
+
+ public NfcActivityState(Activity activity) {
+ if (activity.isDestroyed()) {
+ throw new IllegalStateException("activity is already destroyed");
+ }
+ // Check if activity is resumed right now, as we will not
+ // immediately get a callback for that.
+ resumed = activity.isResumed();
+
+ this.activity = activity;
+ this.token = new Binder();
+ registerApplication(activity.getApplication());
+ }
+ public void destroy() {
+ unregisterApplication(activity.getApplication());
+ resumed = false;
+ activity = null;
+ readerCallback = null;
+ readerModeFlags = 0;
+ readerModeExtras = null;
+ token = null;
+
+ mPollTech = NfcAdapter.FLAG_USE_ALL_TECH;
+ mListenTech = NfcAdapter.FLAG_USE_ALL_TECH;
+ }
+ @Override
+ public String toString() {
+ StringBuilder s = new StringBuilder("[");
+ s.append(readerCallback);
+ s.append("]");
+ return s.toString();
+ }
+ }
+
+ /** find activity state from mActivities */
+ synchronized NfcActivityState findActivityState(Activity activity) {
+ for (NfcActivityState state : mActivities) {
+ if (state.activity == activity) {
+ return state;
+ }
+ }
+ return null;
+ }
+
+ /** find or create activity state from mActivities */
+ synchronized NfcActivityState getActivityState(Activity activity) {
+ NfcActivityState state = findActivityState(activity);
+ if (state == null) {
+ state = new NfcActivityState(activity);
+ mActivities.add(state);
+ }
+ return state;
+ }
+
+ synchronized NfcActivityState findResumedActivityState() {
+ for (NfcActivityState state : mActivities) {
+ if (state.resumed) {
+ return state;
+ }
+ }
+ return null;
+ }
+
+ synchronized void destroyActivityState(Activity activity) {
+ NfcActivityState activityState = findActivityState(activity);
+ if (activityState != null) {
+ activityState.destroy();
+ mActivities.remove(activityState);
+ }
+ }
+
+ public NfcActivityManager(NfcAdapter adapter) {
+ mAdapter = adapter;
+ mActivities = new LinkedList<NfcActivityState>();
+ mApps = new ArrayList<NfcApplicationState>(1); // Android VM usually has 1 app
+ }
+
+ public void enableReaderMode(Activity activity, ReaderCallback callback, int flags,
+ Bundle extras) {
+ boolean isResumed;
+ Binder token;
+ synchronized (NfcActivityManager.this) {
+ NfcActivityState state = getActivityState(activity);
+ state.readerCallback = callback;
+ state.readerModeFlags = flags;
+ state.readerModeExtras = extras;
+ token = state.token;
+ isResumed = state.resumed;
+ }
+ if (isResumed) {
+ setReaderMode(token, flags, extras);
+ }
+ }
+
+ public void disableReaderMode(Activity activity) {
+ boolean isResumed;
+ Binder token;
+ synchronized (NfcActivityManager.this) {
+ NfcActivityState state = getActivityState(activity);
+ state.readerCallback = null;
+ state.readerModeFlags = 0;
+ state.readerModeExtras = null;
+ token = state.token;
+ isResumed = state.resumed;
+ }
+ if (isResumed) {
+ setReaderMode(token, 0, null);
+ }
+
+ }
+
+ public void setReaderMode(Binder token, int flags, Bundle extras) {
+ if (DBG) Log.d(TAG, "Setting reader mode");
+ try {
+ NfcAdapter.sService.setReaderMode(token, this, flags, extras);
+ } catch (RemoteException e) {
+ mAdapter.attemptDeadServiceRecovery(e);
+ }
+ }
+
+ /**
+ * Request or unrequest NFC service callbacks.
+ * Makes IPC call - do not hold lock.
+ */
+ void requestNfcServiceCallback() {
+ try {
+ NfcAdapter.sService.setAppCallback(this);
+ } catch (RemoteException e) {
+ mAdapter.attemptDeadServiceRecovery(e);
+ }
+ }
+
+ void verifyNfcPermission() {
+ try {
+ NfcAdapter.sService.verifyNfcPermission();
+ } catch (RemoteException e) {
+ mAdapter.attemptDeadServiceRecovery(e);
+ }
+ }
+
+ @Override
+ public void onTagDiscovered(Tag tag) throws RemoteException {
+ NfcAdapter.ReaderCallback callback;
+ synchronized (NfcActivityManager.this) {
+ NfcActivityState state = findResumedActivityState();
+ if (state == null) return;
+
+ callback = state.readerCallback;
+ }
+
+ // Make callback without lock
+ if (callback != null) {
+ callback.onTagDiscovered(tag);
+ }
+
+ }
+ /** Callback from Activity life-cycle, on main thread */
+ @Override
+ public void onActivityCreated(Activity activity, Bundle savedInstanceState) { /* NO-OP */ }
+
+ /** Callback from Activity life-cycle, on main thread */
+ @Override
+ public void onActivityStarted(Activity activity) { /* NO-OP */ }
+
+ /** Callback from Activity life-cycle, on main thread */
+ @Override
+ public void onActivityResumed(Activity activity) {
+ int readerModeFlags = 0;
+ Bundle readerModeExtras = null;
+ Binder token;
+ int pollTech;
+ int listenTech;
+
+ synchronized (NfcActivityManager.this) {
+ NfcActivityState state = findActivityState(activity);
+ if (DBG) Log.d(TAG, "onResume() for " + activity + " " + state);
+ if (state == null) return;
+ state.resumed = true;
+ token = state.token;
+ readerModeFlags = state.readerModeFlags;
+ readerModeExtras = state.readerModeExtras;
+
+ pollTech = state.mPollTech;
+ listenTech = state.mListenTech;
+ }
+ if (readerModeFlags != 0) {
+ setReaderMode(token, readerModeFlags, readerModeExtras);
+ } else if (listenTech != NfcAdapter.FLAG_USE_ALL_TECH
+ || pollTech != NfcAdapter.FLAG_USE_ALL_TECH) {
+ changeDiscoveryTech(token, pollTech, listenTech);
+ }
+ requestNfcServiceCallback();
+ }
+
+ /** Callback from Activity life-cycle, on main thread */
+ @Override
+ public void onActivityPaused(Activity activity) {
+ boolean readerModeFlagsSet;
+ Binder token;
+ int pollTech;
+ int listenTech;
+
+ synchronized (NfcActivityManager.this) {
+ NfcActivityState state = findActivityState(activity);
+ if (DBG) Log.d(TAG, "onPause() for " + activity + " " + state);
+ if (state == null) return;
+ state.resumed = false;
+ token = state.token;
+ readerModeFlagsSet = state.readerModeFlags != 0;
+
+ pollTech = state.mPollTech;
+ listenTech = state.mListenTech;
+ }
+ if (readerModeFlagsSet) {
+ // Restore default p2p modes
+ setReaderMode(token, 0, null);
+ } else if (listenTech != NfcAdapter.FLAG_USE_ALL_TECH
+ || pollTech != NfcAdapter.FLAG_USE_ALL_TECH) {
+ changeDiscoveryTech(token,
+ NfcAdapter.FLAG_USE_ALL_TECH, NfcAdapter.FLAG_USE_ALL_TECH);
+ }
+ }
+
+ /** Callback from Activity life-cycle, on main thread */
+ @Override
+ public void onActivityStopped(Activity activity) { /* NO-OP */ }
+
+ /** Callback from Activity life-cycle, on main thread */
+ @Override
+ public void onActivitySaveInstanceState(Activity activity, Bundle outState) { /* NO-OP */ }
+
+ /** Callback from Activity life-cycle, on main thread */
+ @Override
+ public void onActivityDestroyed(Activity activity) {
+ synchronized (NfcActivityManager.this) {
+ NfcActivityState state = findActivityState(activity);
+ if (DBG) Log.d(TAG, "onDestroy() for " + activity + " " + state);
+ if (state != null) {
+ // release all associated references
+ destroyActivityState(activity);
+ }
+ }
+ }
+
+ /** setDiscoveryTechnology() implementation */
+ public void setDiscoveryTech(Activity activity, int pollTech, int listenTech) {
+ boolean isResumed;
+ Binder token;
+ boolean readerModeFlagsSet;
+ synchronized (NfcActivityManager.this) {
+ NfcActivityState state = getActivityState(activity);
+ readerModeFlagsSet = state.readerModeFlags != 0;
+ state.mListenTech = listenTech;
+ state.mPollTech = pollTech;
+ token = state.token;
+ isResumed = state.resumed;
+ }
+ if (!readerModeFlagsSet && isResumed) {
+ changeDiscoveryTech(token, pollTech, listenTech);
+ } else if (readerModeFlagsSet) {
+ throw new IllegalStateException("Cannot be used when the Reader Mode is enabled");
+ }
+ }
+
+ /** resetDiscoveryTechnology() implementation */
+ public void resetDiscoveryTech(Activity activity) {
+ boolean isResumed;
+ Binder token;
+ boolean readerModeFlagsSet;
+ synchronized (NfcActivityManager.this) {
+ NfcActivityState state = getActivityState(activity);
+ readerModeFlagsSet = state.readerModeFlags != 0;
+ state.mListenTech = NfcAdapter.FLAG_USE_ALL_TECH;
+ state.mPollTech = NfcAdapter.FLAG_USE_ALL_TECH;
+ token = state.token;
+ isResumed = state.resumed;
+ }
+ if (readerModeFlagsSet) {
+ disableReaderMode(activity);
+ } else if (isResumed) {
+ changeDiscoveryTech(token, NfcAdapter.FLAG_USE_ALL_TECH, NfcAdapter.FLAG_USE_ALL_TECH);
+ }
+
+ }
+
+ private void changeDiscoveryTech(Binder token, int pollTech, int listenTech) {
+ try {
+ NfcAdapter.sService.updateDiscoveryTechnology(token, pollTech, listenTech);
+ } catch (RemoteException e) {
+ mAdapter.attemptDeadServiceRecovery(e);
+ }
+ }
+
+}
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
new file mode 100644
index 0000000..75f5491
--- /dev/null
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -0,0 +1,2909 @@
+/*
+ * 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.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.annotation.UserIdInt;
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.nfc.tech.MifareClassic;
+import android.nfc.tech.Ndef;
+import android.nfc.tech.NfcA;
+import android.nfc.tech.NfcF;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * Represents the local NFC adapter.
+ * <p>
+ * Use the helper {@link #getDefaultAdapter(Context)} to get the default NFC
+ * adapter for this Android device.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about using NFC, read the
+ * <a href="{@docRoot}guide/topics/nfc/index.html">Near Field Communication</a> developer guide.</p>
+ * <p>To perform basic file sharing between devices, read
+ * <a href="{@docRoot}training/beam-files/index.html">Sharing Files with NFC</a>.
+ * </div>
+ */
+public final class NfcAdapter {
+ static final String TAG = "NFC";
+
+ private final NfcControllerAlwaysOnListener mControllerAlwaysOnListener;
+ private final NfcWlcStateListener mNfcWlcStateListener;
+
+ /**
+ * Intent to start an activity when a tag with NDEF payload is discovered.
+ *
+ * <p>The system inspects the first {@link NdefRecord} in the first {@link NdefMessage} and
+ * looks for a URI, SmartPoster, or MIME record. If a URI or SmartPoster record is found the
+ * intent will contain the URI in its data field. If a MIME record is found the intent will
+ * contain the MIME type in its type field. This allows activities to register
+ * {@link IntentFilter}s targeting specific content on tags. Activities should register the
+ * most specific intent filters possible to avoid the activity chooser dialog, which can
+ * disrupt the interaction with the tag as the user interacts with the screen.
+ *
+ * <p>If the tag has an 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.
+ *
+ * <p>The MIME type or data URI of this intent are normalized before dispatch -
+ * so that MIME, URI scheme and URI host are always lower-case.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED";
+
+ /**
+ * Intent to start an activity when a tag is discovered and activities are registered for the
+ * specific technologies on the tag.
+ *
+ * <p>To receive this intent an activity must include an intent filter
+ * for this action and specify the desired tech types in a
+ * manifest <code>meta-data</code> entry. Here is an example manfiest entry:
+ * <pre>
+ * <activity android:name=".nfc.TechFilter" android:label="NFC/TechFilter">
+ * <!-- Add a technology filter -->
+ * <intent-filter>
+ * <action android:name="android.nfc.action.TECH_DISCOVERED" />
+ * </intent-filter>
+ *
+ * <meta-data android:name="android.nfc.action.TECH_DISCOVERED"
+ * android:resource="@xml/filter_nfc"
+ * />
+ * </activity></pre>
+ *
+ * <p>The meta-data XML file should contain one or more <code>tech-list</code> entries
+ * each consisting or one or more <code>tech</code> entries. The <code>tech</code> entries refer
+ * to the qualified class name implementing the technology, for example "android.nfc.tech.NfcA".
+ *
+ * <p>A tag matches if any of the
+ * <code>tech-list</code> sets is a subset of {@link Tag#getTechList() Tag.getTechList()}. Each
+ * of the <code>tech-list</code>s is considered independently and the
+ * activity is considered a match is any single <code>tech-list</code> matches the tag that was
+ * discovered. This provides AND and OR semantics for filtering desired techs. Here is an
+ * example that will match any tag using {@link NfcF} or any tag using {@link NfcA},
+ * {@link MifareClassic}, and {@link Ndef}:
+ *
+ * <pre>
+ * <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ * <!-- capture anything using NfcF -->
+ * <tech-list>
+ * <tech>android.nfc.tech.NfcF</tech>
+ * </tech-list>
+ *
+ * <!-- OR -->
+ *
+ * <!-- capture all MIFARE Classics with NDEF payloads -->
+ * <tech-list>
+ * <tech>android.nfc.tech.NfcA</tech>
+ * <tech>android.nfc.tech.MifareClassic</tech>
+ * <tech>android.nfc.tech.Ndef</tech>
+ * </tech-list>
+ * </resources></pre>
+ *
+ * <p>This intent is started after {@link #ACTION_NDEF_DISCOVERED} and before
+ * {@link #ACTION_TAG_DISCOVERED}. If any activities respond to {@link #ACTION_NDEF_DISCOVERED}
+ * this intent will not be started. 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.
+ *
+ * <p>This intent will not be started when a tag is discovered if any activities respond to
+ * {@link #ACTION_NDEF_DISCOVERED} or {@link #ACTION_TECH_DISCOVERED} for the current tag.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_TAG_DISCOVERED = "android.nfc.action.TAG_DISCOVERED";
+
+ /**
+ * Broadcast Action: Intent to notify an application that a transaction event has occurred
+ * on the Secure Element.
+ *
+ * <p>This intent will only be sent if the application has requested permission for
+ * {@link android.Manifest.permission#NFC_TRANSACTION_EVENT} and if the application has the
+ * necessary access to Secure Element which witnessed the particular event.
+ */
+ @RequiresPermission(android.Manifest.permission.NFC_TRANSACTION_EVENT)
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_TRANSACTION_DETECTED =
+ "android.nfc.action.TRANSACTION_DETECTED";
+
+ /**
+ * Broadcast Action: Intent to notify if the preferred payment service changed.
+ *
+ * <p>This intent will only be sent to the application has requested permission for
+ * {@link android.Manifest.permission#NFC_PREFERRED_PAYMENT_INFO} and if the application
+ * has the necessary access to Secure Element which witnessed the particular event.
+ */
+ @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PREFERRED_PAYMENT_CHANGED =
+ "android.nfc.action.PREFERRED_PAYMENT_CHANGED";
+
+ /**
+ * 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 extra containing the {@link Tag} that was discovered for the
+ * {@link #ACTION_NDEF_DISCOVERED}, {@link #ACTION_TECH_DISCOVERED}, and
+ * {@link #ACTION_TAG_DISCOVERED} intents.
+ */
+ public static final String EXTRA_TAG = "android.nfc.extra.TAG";
+
+ /**
+ * Extra containing an array of {@link NdefMessage} present on the discovered tag.<p>
+ * This extra is mandatory for {@link #ACTION_NDEF_DISCOVERED} intents,
+ * and optional for {@link #ACTION_TECH_DISCOVERED}, and
+ * {@link #ACTION_TAG_DISCOVERED} intents.<p>
+ * When this extra is present there will always be at least one
+ * {@link NdefMessage} element. Most NDEF tags have only one NDEF message,
+ * but we use an array for future compatibility.
+ */
+ public static final String EXTRA_NDEF_MESSAGES = "android.nfc.extra.NDEF_MESSAGES";
+
+ /**
+ * Optional extra containing a byte array containing the ID of the discovered tag for
+ * the {@link #ACTION_NDEF_DISCOVERED}, {@link #ACTION_TECH_DISCOVERED}, and
+ * {@link #ACTION_TAG_DISCOVERED} intents.
+ */
+ public static final String EXTRA_ID = "android.nfc.extra.ID";
+
+ /**
+ * Broadcast Action: The state of the local NFC adapter has been
+ * changed.
+ * <p>For example, NFC has been turned on or off.
+ * <p>Always contains the extra field {@link #EXTRA_ADAPTER_STATE}
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_ADAPTER_STATE_CHANGED =
+ "android.nfc.action.ADAPTER_STATE_CHANGED";
+
+ /**
+ * Used as an int extra field in {@link #ACTION_ADAPTER_STATE_CHANGED}
+ * intents to request the current power state. Possible values are:
+ * {@link #STATE_OFF},
+ * {@link #STATE_TURNING_ON},
+ * {@link #STATE_ON},
+ * {@link #STATE_TURNING_OFF},
+ */
+ public static final String EXTRA_ADAPTER_STATE = "android.nfc.extra.ADAPTER_STATE";
+
+ /**
+ * Mandatory byte[] extra field in {@link #ACTION_TRANSACTION_DETECTED}
+ */
+ public static final String EXTRA_AID = "android.nfc.extra.AID";
+
+ /**
+ * Optional byte[] extra field in {@link #ACTION_TRANSACTION_DETECTED}
+ */
+ public static final String EXTRA_DATA = "android.nfc.extra.DATA";
+
+ /**
+ * Mandatory String extra field in {@link #ACTION_TRANSACTION_DETECTED}
+ * Indicates the Secure Element on which the transaction occurred.
+ * eSE1...eSEn for Embedded Secure Elements, SIM1...SIMn for UICC, etc.
+ */
+ public static final String EXTRA_SECURE_ELEMENT_NAME = "android.nfc.extra.SECURE_ELEMENT_NAME";
+
+ /**
+ * Mandatory String extra field in {@link #ACTION_PREFERRED_PAYMENT_CHANGED}
+ * Indicates the condition when trigger this event. Possible values are:
+ * {@link #PREFERRED_PAYMENT_LOADED},
+ * {@link #PREFERRED_PAYMENT_CHANGED},
+ * {@link #PREFERRED_PAYMENT_UPDATED},
+ */
+ public static final String EXTRA_PREFERRED_PAYMENT_CHANGED_REASON =
+ "android.nfc.extra.PREFERRED_PAYMENT_CHANGED_REASON";
+ /**
+ * Nfc is enabled and the preferred payment aids are registered.
+ */
+ public static final int PREFERRED_PAYMENT_LOADED = 1;
+ /**
+ * User selected another payment application as the preferred payment.
+ */
+ public static final int PREFERRED_PAYMENT_CHANGED = 2;
+ /**
+ * Current preferred payment has issued an update (registered/unregistered new aids or has been
+ * updated itself).
+ */
+ public static final int PREFERRED_PAYMENT_UPDATED = 3;
+
+ public static final int STATE_OFF = 1;
+ public static final int STATE_TURNING_ON = 2;
+ public static final int STATE_ON = 3;
+ public static final int STATE_TURNING_OFF = 4;
+
+ /**
+ * Possible states from {@link #getAdapterState}.
+ *
+ * @hide
+ */
+ @IntDef(prefix = { "STATE_" }, value = {
+ STATE_OFF,
+ STATE_TURNING_ON,
+ STATE_ON,
+ STATE_TURNING_OFF
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AdapterState{}
+
+ /**
+ * Flag for use with {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}.
+ * <p>
+ * Setting this flag enables polling for Nfc-A technology.
+ */
+ public static final int FLAG_READER_NFC_A = 0x1;
+
+ /**
+ * Flag for use with {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}.
+ * <p>
+ * Setting this flag enables polling for Nfc-B technology.
+ */
+ public static final int FLAG_READER_NFC_B = 0x2;
+
+ /**
+ * Flag for use with {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}.
+ * <p>
+ * Setting this flag enables polling for Nfc-F technology.
+ */
+ public static final int FLAG_READER_NFC_F = 0x4;
+
+ /**
+ * Flag for use with {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}.
+ * <p>
+ * Setting this flag enables polling for Nfc-V (ISO15693) technology.
+ */
+ public static final int FLAG_READER_NFC_V = 0x8;
+
+ /**
+ * Flag for use with {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}.
+ * <p>
+ * Setting this flag enables polling for NfcBarcode technology.
+ */
+ public static final int FLAG_READER_NFC_BARCODE = 0x10;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = {"FLAG_READER_"}, value = {
+ FLAG_READER_KEEP,
+ FLAG_READER_DISABLE,
+ FLAG_READER_NFC_A,
+ FLAG_READER_NFC_B,
+ FLAG_READER_NFC_F,
+ FLAG_READER_NFC_V,
+ FLAG_READER_NFC_BARCODE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PollTechnology {}
+
+ /**
+ * Flag for use with {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}.
+ * <p>
+ * Setting this flag allows the caller to prevent the
+ * platform from performing an NDEF check on the tags it
+ * finds.
+ */
+ public static final int FLAG_READER_SKIP_NDEF_CHECK = 0x80;
+
+ /**
+ * Flag for use with {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}.
+ * <p>
+ * Setting this flag allows the caller to prevent the
+ * platform from playing sounds when it discovers a tag.
+ */
+ public static final int FLAG_READER_NO_PLATFORM_SOUNDS = 0x100;
+
+ /**
+ * Int Extra for use with {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}.
+ * <p>
+ * Setting this integer extra allows the calling application to specify
+ * the delay that the platform will use for performing presence checks
+ * on any discovered tag.
+ */
+ public static final String EXTRA_READER_PRESENCE_CHECK_DELAY = "presence";
+
+ /**
+ * Flag for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
+ * <p>
+ * Setting this flag enables listening for Nfc-A technology.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+ public static final int FLAG_LISTEN_NFC_PASSIVE_A = 0x1;
+
+ /**
+ * Flag for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
+ * <p>
+ * Setting this flag enables listening for Nfc-B technology.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+ public static final int FLAG_LISTEN_NFC_PASSIVE_B = 1 << 1;
+
+ /**
+ * Flag for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
+ * <p>
+ * Setting this flag enables listening for Nfc-F technology.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+ public static final int FLAG_LISTEN_NFC_PASSIVE_F = 1 << 2;
+
+ /**
+ * Flags for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
+ * <p>
+ * Setting this flag disables listening.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+ public static final int FLAG_LISTEN_DISABLE = 0x0;
+
+ /**
+ * Flags for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
+ * <p>
+ * Setting this flag disables polling.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+ public static final int FLAG_READER_DISABLE = 0x0;
+
+ /**
+ * Flags for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
+ * <p>
+ * Setting this flag makes listening to use current flags.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+ public static final int FLAG_LISTEN_KEEP = -1;
+
+ /**
+ * Flags for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
+ * <p>
+ * Setting this flag makes polling to use current flags.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+ public static final int FLAG_READER_KEEP = -1;
+
+ /** @hide */
+ public static final int FLAG_USE_ALL_TECH = 0xff;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = {"FLAG_LISTEN_"}, value = {
+ FLAG_LISTEN_KEEP,
+ FLAG_LISTEN_DISABLE,
+ FLAG_LISTEN_NFC_PASSIVE_A,
+ FLAG_LISTEN_NFC_PASSIVE_B,
+ FLAG_LISTEN_NFC_PASSIVE_F
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ListenTechnology {}
+
+ /**
+ * @hide
+ * @removed
+ */
+ @SystemApi
+ @UnsupportedAppUsage
+ public static final int FLAG_NDEF_PUSH_NO_CONFIRM = 0x1;
+
+ /** @hide */
+ public static final String ACTION_HANDOVER_TRANSFER_STARTED =
+ "android.nfc.action.HANDOVER_TRANSFER_STARTED";
+
+ /** @hide */
+ public static final String ACTION_HANDOVER_TRANSFER_DONE =
+ "android.nfc.action.HANDOVER_TRANSFER_DONE";
+
+ /** @hide */
+ public static final String EXTRA_HANDOVER_TRANSFER_STATUS =
+ "android.nfc.extra.HANDOVER_TRANSFER_STATUS";
+
+ /** @hide */
+ public static final int HANDOVER_TRANSFER_STATUS_SUCCESS = 0;
+ /** @hide */
+ public static final int HANDOVER_TRANSFER_STATUS_FAILURE = 1;
+
+ /** @hide */
+ public static final String EXTRA_HANDOVER_TRANSFER_URI =
+ "android.nfc.extra.HANDOVER_TRANSFER_URI";
+
+ /**
+ * Broadcast Action: Notify possible NFC transaction blocked because device is locked.
+ * <p>An external NFC field detected when device locked and SecureNfc enabled.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ public static final String ACTION_REQUIRE_UNLOCK_FOR_NFC =
+ "android.nfc.action.REQUIRE_UNLOCK_FOR_NFC";
+
+ /**
+ * The requested app is correctly added to the Tag intent app preference.
+ *
+ * @see #setTagIntentAppPreferenceForUser(int userId, String pkg, boolean allow)
+ * @hide
+ */
+ @SystemApi
+ public static final int TAG_INTENT_APP_PREF_RESULT_SUCCESS = 0;
+
+ /**
+ * The requested app is not installed on the device.
+ *
+ * @see #setTagIntentAppPreferenceForUser(int userId, String pkg, boolean allow)
+ * @hide
+ */
+ @SystemApi
+ public static final int TAG_INTENT_APP_PREF_RESULT_PACKAGE_NOT_FOUND = -1;
+
+ /**
+ * The NfcService is not available.
+ *
+ * @see #setTagIntentAppPreferenceForUser(int userId, String pkg, boolean allow)
+ * @hide
+ */
+ @SystemApi
+ public static final int TAG_INTENT_APP_PREF_RESULT_UNAVAILABLE = -2;
+
+ /**
+ * Possible response codes from {@link #setTagIntentAppPreferenceForUser}.
+ *
+ * @hide
+ */
+ @IntDef(prefix = { "TAG_INTENT_APP_PREF_RESULT" }, value = {
+ TAG_INTENT_APP_PREF_RESULT_SUCCESS,
+ TAG_INTENT_APP_PREF_RESULT_PACKAGE_NOT_FOUND,
+ TAG_INTENT_APP_PREF_RESULT_UNAVAILABLE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TagIntentAppPreferenceResult {}
+
+ // Guarded by sLock
+ static boolean sIsInitialized = false;
+ static boolean sHasNfcFeature;
+ static boolean sHasCeFeature;
+ static boolean sHasNfcWlcFeature;
+
+ static Object sLock = new Object();
+
+ // Final after first constructor, except for
+ // attemptDeadServiceRecovery() when NFC crashes - we accept a best effort
+ // recovery
+ @UnsupportedAppUsage
+ static INfcAdapter sService;
+ static NfcServiceManager.ServiceRegisterer sServiceRegisterer;
+ static INfcTag sTagService;
+ static INfcCardEmulation sCardEmulationService;
+ static INfcFCardEmulation sNfcFCardEmulationService;
+
+ /**
+ * The NfcAdapter object for each application context.
+ * There is a 1-1 relationship between application context and
+ * NfcAdapter object.
+ */
+ static HashMap<Context, NfcAdapter> sNfcAdapters = new HashMap(); //guard by NfcAdapter.class
+
+ /**
+ * NfcAdapter used with a null context. This ctor was deprecated but we have
+ * to support it for backwards compatibility. New methods that require context
+ * might throw when called on the null-context NfcAdapter.
+ */
+ static NfcAdapter sNullContextNfcAdapter; // protected by NfcAdapter.class
+
+ final NfcActivityManager mNfcActivityManager;
+ final Context mContext;
+ final HashMap<NfcUnlockHandler, INfcUnlockHandler> mNfcUnlockHandlers;
+ final Object mLock;
+
+ ITagRemovedCallback mTagRemovedListener; // protected by mLock
+
+ /**
+ * A callback to be invoked when the system finds a tag while the foreground activity is
+ * operating in reader mode.
+ * <p>Register your {@code ReaderCallback} implementation with {@link
+ * NfcAdapter#enableReaderMode} and disable it with {@link
+ * NfcAdapter#disableReaderMode}.
+ * @see NfcAdapter#enableReaderMode
+ */
+ public interface ReaderCallback {
+ public void onTagDiscovered(Tag tag);
+ }
+
+ /**
+ * A listener to be invoked when NFC controller always on state changes.
+ * <p>Register your {@code ControllerAlwaysOnListener} implementation with {@link
+ * NfcAdapter#registerControllerAlwaysOnListener} and disable it with {@link
+ * NfcAdapter#unregisterControllerAlwaysOnListener}.
+ * @see #registerControllerAlwaysOnListener
+ * @hide
+ */
+ @SystemApi
+ public interface ControllerAlwaysOnListener {
+ /**
+ * Called on NFC controller always on state changes
+ */
+ void onControllerAlwaysOnChanged(boolean isEnabled);
+ }
+
+ /**
+ * A callback to be invoked when the system successfully delivers your {@link NdefMessage}
+ * to another device.
+ * @deprecated this feature is removed. File sharing can work using other technology like
+ * Bluetooth.
+ */
+ @java.lang.Deprecated
+ public interface OnNdefPushCompleteCallback {
+ /**
+ * Called on successful NDEF push.
+ *
+ * <p>This callback is usually made on a binder thread (not the UI thread).
+ *
+ * @param event {@link NfcEvent} with the {@link NfcEvent#nfcAdapter} field set
+ */
+ public void onNdefPushComplete(NfcEvent event);
+ }
+
+ /**
+ * A callback to be invoked when another NFC device capable of NDEF push (Android Beam)
+ * is within range.
+ * <p>Implement this interface and pass it to {@code
+ * NfcAdapter#setNdefPushMessageCallback setNdefPushMessageCallback()} in order to create an
+ * {@link NdefMessage} at the moment that another device is within range for NFC. Using this
+ * callback allows you to create a message with data that might vary based on the
+ * content currently visible to the user. Alternatively, you can call {@code
+ * #setNdefPushMessage setNdefPushMessage()} if the {@link NdefMessage} always contains the
+ * same data.
+ * @deprecated this feature is removed. File sharing can work using other technology like
+ * Bluetooth.
+ */
+ @java.lang.Deprecated
+ public interface CreateNdefMessageCallback {
+ /**
+ * Called to provide a {@link NdefMessage} to push.
+ *
+ * <p>This callback is usually made on a binder thread (not the UI thread).
+ *
+ * <p>Called when this device is in range of another device
+ * that might support NDEF push. It allows the application to
+ * create the NDEF message only when it is required.
+ *
+ * <p>NDEF push cannot occur until this method returns, so do not
+ * block for too long.
+ *
+ * <p>The Android operating system will usually show a system UI
+ * on top of your activity during this time, so do not try to request
+ * input from the user to complete the callback, or provide custom NDEF
+ * push UI. The user probably will not see it.
+ *
+ * @param event {@link NfcEvent} with the {@link NfcEvent#nfcAdapter} field set
+ * @return NDEF message to push, or null to not provide a message
+ */
+ public NdefMessage createNdefMessage(NfcEvent event);
+ }
+
+
+ /**
+ * @deprecated this feature is removed. File sharing can work using other technology like
+ * Bluetooth.
+ */
+ @java.lang.Deprecated
+ public interface CreateBeamUrisCallback {
+ public Uri[] createBeamUris(NfcEvent event);
+ }
+
+ /**
+ * A callback that is invoked when a tag is removed from the field.
+ * @see NfcAdapter#ignore
+ */
+ public interface OnTagRemovedListener {
+ void onTagRemoved();
+ }
+
+ /**
+ * A callback to be invoked when an application has registered as a
+ * handler to unlock the device given an NFC tag at the lockscreen.
+ * @hide
+ */
+ @SystemApi
+ public interface NfcUnlockHandler {
+ /**
+ * Called at the lock screen to attempt to unlock the device with the given tag.
+ * @param tag the detected tag, to be used to unlock the device
+ * @return true if the device was successfully unlocked
+ */
+ public boolean onUnlockAttempted(Tag tag);
+ }
+
+ /**
+ * Return list of Secure Elements which support off host card emulation.
+ *
+ * @return List<String> containing secure elements on the device which supports
+ * off host card emulation. eSE for Embedded secure element,
+ * SIM for UICC and so on.
+ * @hide
+ */
+ public @NonNull List<String> getSupportedOffHostSecureElements() {
+ if (mContext == null) {
+ throw new UnsupportedOperationException("You need a context on NfcAdapter to use the "
+ + " getSupportedOffHostSecureElements APIs");
+ }
+ List<String> offHostSE = new ArrayList<String>();
+ PackageManager pm = mContext.getPackageManager();
+ if (pm == null) {
+ Log.e(TAG, "Cannot get package manager, assuming no off-host CE feature");
+ return offHostSE;
+ }
+ if (pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC)) {
+ offHostSE.add("SIM");
+ }
+ if (pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE)) {
+ offHostSE.add("eSE");
+ }
+ return offHostSE;
+ }
+
+ private static void retrieveServiceRegisterer() {
+ if (sServiceRegisterer == null) {
+ NfcServiceManager manager = NfcFrameworkInitializer.getNfcServiceManager();
+ if (manager == null) {
+ Log.e(TAG, "NfcServiceManager is null");
+ throw new UnsupportedOperationException();
+ }
+ sServiceRegisterer = manager.getNfcManagerServiceRegisterer();
+ }
+ }
+
+ /**
+ * Returns the NfcAdapter for application context,
+ * or throws if NFC is not available.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static synchronized NfcAdapter getNfcAdapter(Context context) {
+ if (context == null) {
+ if (sNullContextNfcAdapter == null) {
+ sNullContextNfcAdapter = new NfcAdapter(null);
+ }
+ return sNullContextNfcAdapter;
+ }
+ if (!sIsInitialized) {
+ PackageManager pm;
+ pm = context.getPackageManager();
+ sHasNfcFeature = pm.hasSystemFeature(PackageManager.FEATURE_NFC);
+ sHasCeFeature =
+ pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)
+ || pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF)
+ || pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC)
+ || pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE);
+ sHasNfcWlcFeature = pm.hasSystemFeature(PackageManager.FEATURE_NFC_CHARGING);
+ /* is this device meant to have NFC */
+ if (!sHasNfcFeature && !sHasCeFeature && !sHasNfcWlcFeature) {
+ Log.v(TAG, "this device does not have NFC support");
+ throw new UnsupportedOperationException();
+ }
+ retrieveServiceRegisterer();
+ sService = getServiceInterface();
+ if (sService == null) {
+ Log.e(TAG, "could not retrieve NFC service");
+ throw new UnsupportedOperationException();
+ }
+ if (sHasNfcFeature) {
+ try {
+ sTagService = sService.getNfcTagInterface();
+ } catch (RemoteException e) {
+ sTagService = null;
+ Log.e(TAG, "could not retrieve NFC Tag service");
+ throw new UnsupportedOperationException();
+ }
+ }
+ if (sHasCeFeature) {
+ try {
+ sNfcFCardEmulationService = sService.getNfcFCardEmulationInterface();
+ } catch (RemoteException e) {
+ sNfcFCardEmulationService = null;
+ Log.e(TAG, "could not retrieve NFC-F card emulation service");
+ throw new UnsupportedOperationException();
+ }
+ try {
+ sCardEmulationService = sService.getNfcCardEmulationInterface();
+ } catch (RemoteException e) {
+ sCardEmulationService = null;
+ Log.e(TAG, "could not retrieve card emulation service");
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ sIsInitialized = true;
+ }
+ NfcAdapter adapter = sNfcAdapters.get(context);
+ if (adapter == null) {
+ adapter = new NfcAdapter(context);
+ sNfcAdapters.put(context, adapter);
+ }
+ return adapter;
+ }
+
+ /** get handle to NFC service interface */
+ private static INfcAdapter getServiceInterface() {
+ /* get a handle to NFC service */
+ IBinder b = sServiceRegisterer.get();
+ 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>
+ * 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) {
+ if (context == null) {
+ throw new IllegalArgumentException("context cannot be null");
+ }
+ Context applicationContext = context.getApplicationContext();
+ if (applicationContext == null) {
+ throw new IllegalArgumentException(
+ "context not associated with any application (using a mock context?)");
+ }
+ retrieveServiceRegisterer();
+ if (sServiceRegisterer.tryGet() == null) {
+ if (sIsInitialized) {
+ synchronized (NfcAdapter.class) {
+ /* Stale sService pointer */
+ if (sIsInitialized) sIsInitialized = false;
+ }
+ }
+ return null;
+ }
+ /* Try to initialize the service */
+ NfcManager manager = (NfcManager) context.getSystemService(Context.NFC_SERVICE);
+ if (manager == null) {
+ // NFC not available
+ return null;
+ }
+ return manager.getDefaultAdapter();
+ }
+
+ /**
+ * Legacy NfcAdapter getter, always use {@link #getDefaultAdapter(Context)} instead.<p>
+ * This method was deprecated at API level 10 (Gingerbread MR1) because a context is required
+ * for many NFC API methods. Those methods will fail when called on an NfcAdapter
+ * object created from this method.<p>
+ * @deprecated use {@link #getDefaultAdapter(Context)}
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public static NfcAdapter getDefaultAdapter() {
+ // introduced in API version 9 (GB 2.3)
+ // deprecated in API version 10 (GB 2.3.3)
+ // removed from public API in version 16 (ICS MR2)
+ // should maintain as a hidden API for binary compatibility for a little longer
+ Log.w(TAG, "WARNING: NfcAdapter.getDefaultAdapter() is deprecated, use " +
+ "NfcAdapter.getDefaultAdapter(Context) instead", new Exception());
+
+ return NfcAdapter.getNfcAdapter(null);
+ }
+
+ NfcAdapter(Context context) {
+ mContext = context;
+ mNfcActivityManager = new NfcActivityManager(this);
+ mNfcUnlockHandlers = new HashMap<NfcUnlockHandler, INfcUnlockHandler>();
+ mTagRemovedListener = null;
+ mLock = new Object();
+ mControllerAlwaysOnListener = new NfcControllerAlwaysOnListener(getService());
+ mNfcWlcStateListener = new NfcWlcStateListener(getService());
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public Context getContext() {
+ return mContext;
+ }
+
+ /**
+ * Returns the binder interface to the service.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ 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;
+ }
+
+ /**
+ * Returns the binder interface to the card emulation service.
+ * @hide
+ */
+ public INfcCardEmulation getCardEmulationService() {
+ isEnabled();
+ return sCardEmulationService;
+ }
+
+ /**
+ * Returns the binder interface to the NFC-F card emulation service.
+ * @hide
+ */
+ public INfcFCardEmulation getNfcFCardEmulationService() {
+ isEnabled();
+ return sNfcFCardEmulationService;
+ }
+
+ /**
+ * Returns the binder interface to the NFC-DTA test interface.
+ * @hide
+ */
+ public INfcDta getNfcDtaInterface() {
+ if (mContext == null) {
+ throw new UnsupportedOperationException("You need a context on NfcAdapter to use the "
+ + " NFC extras APIs");
+ }
+ try {
+ return sService.getNfcDtaInterface(mContext.getPackageName());
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ return null;
+ }
+ try {
+ return sService.getNfcDtaInterface(mContext.getPackageName());
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ return null;
+ }
+ }
+
+ /**
+ * NFC service dead - attempt best effort recovery
+ * @hide
+ */
+ @UnsupportedAppUsage
+ 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;
+ if (sHasNfcFeature) {
+ try {
+ sTagService = service.getNfcTagInterface();
+ } catch (RemoteException ee) {
+ sTagService = null;
+ 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;
+ }
+ }
+
+ if (sHasCeFeature) {
+ try {
+ sCardEmulationService = service.getNfcCardEmulationInterface();
+ } catch (RemoteException ee) {
+ sCardEmulationService = null;
+ Log.e(TAG,
+ "could not retrieve NFC card emulation service during service recovery");
+ }
+
+ try {
+ sNfcFCardEmulationService = service.getNfcFCardEmulationInterface();
+ } catch (RemoteException ee) {
+ sNfcFCardEmulationService = null;
+ Log.e(TAG,
+ "could not retrieve NFC-F card emulation service during service recovery");
+ }
+ }
+
+ return;
+ }
+
+ private boolean isCardEmulationEnabled() {
+ if (sHasCeFeature) {
+ return (sCardEmulationService != null || sNfcFCardEmulationService != null);
+ }
+ return false;
+ }
+
+ private boolean isTagReadingEnabled() {
+ if (sHasNfcFeature) {
+ return sTagService != null;
+ }
+ return false;
+ }
+
+
+ /**
+ * Return true if this NFC Adapter has any features enabled.
+ *
+ * <p>If this method returns false, the NFC hardware is guaranteed not to
+ * generate or respond to any NFC communication over its NFC radio.
+ * <p>Applications can use this to check if NFC is enabled. Applications
+ * can request Settings UI allowing the user to toggle NFC using:
+ * <p><pre>startActivity(new Intent(Settings.ACTION_NFC_SETTINGS))</pre>
+ *
+ * @see android.provider.Settings#ACTION_NFC_SETTINGS
+ * @return true if this NFC Adapter has any features enabled
+ */
+ public boolean isEnabled() {
+ boolean serviceState = false;
+ try {
+ serviceState = sService.getState() == STATE_ON;
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ return false;
+ }
+ try {
+ serviceState = sService.getState() == STATE_ON;
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ }
+ return serviceState
+ && (isTagReadingEnabled() || isCardEmulationEnabled() || sHasNfcWlcFeature);
+ }
+
+ /**
+ * Return the state of this NFC Adapter.
+ *
+ * <p>Returns one of {@link #STATE_ON}, {@link #STATE_TURNING_ON},
+ * {@link #STATE_OFF}, {@link #STATE_TURNING_OFF}.
+ *
+ * <p>{@link #isEnabled()} is equivalent to
+ * <code>{@link #getAdapterState()} == {@link #STATE_ON}</code>
+ *
+ * @return the current state of this NFC adapter
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ public @AdapterState int getAdapterState() {
+ try {
+ return sService.getState();
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ return NfcAdapter.STATE_OFF;
+ }
+ try {
+ return sService.getState();
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ return NfcAdapter.STATE_OFF;
+ }
+ }
+
+ /**
+ * Enable NFC hardware.
+ *
+ * <p>This call is asynchronous. Listen for
+ * {@link #ACTION_ADAPTER_STATE_CHANGED} broadcasts to find out when the
+ * operation is complete.
+ *
+ * <p>If this returns true, then either NFC is already on, or
+ * a {@link #ACTION_ADAPTER_STATE_CHANGED} broadcast will be sent
+ * to indicate a state transition. If this returns false, then
+ * there is some problem that prevents an attempt to turn
+ * NFC on (for example we are in airplane mode and NFC is not
+ * toggleable in airplane mode on this platform).
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ public boolean enable() {
+ try {
+ return sService.enable();
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ return false;
+ }
+ try {
+ return sService.enable();
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Disable NFC hardware.
+ *
+ * <p>No NFC features will work after this call, and the hardware
+ * will not perform or respond to any NFC communication.
+ *
+ * <p>This call is asynchronous. Listen for
+ * {@link #ACTION_ADAPTER_STATE_CHANGED} broadcasts to find out when the
+ * operation is complete.
+ *
+ * <p>If this returns true, then either NFC is already off, or
+ * a {@link #ACTION_ADAPTER_STATE_CHANGED} broadcast will be sent
+ * to indicate a state transition. If this returns false, then
+ * there is some problem that prevents an attempt to turn
+ * NFC off.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ public boolean disable() {
+ try {
+ return sService.disable(true);
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ return false;
+ }
+ try {
+ return sService.disable(true);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Disable NFC hardware.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ public boolean disable(boolean persist) {
+ try {
+ return sService.disable(persist);
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ return false;
+ }
+ try {
+ return sService.disable(persist);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Pauses polling for a {@code timeoutInMs} millis. If polling must be resumed before timeout,
+ * use {@link #resumePolling()}.
+ * @hide
+ */
+ public void pausePolling(int timeoutInMs) {
+ try {
+ sService.pausePolling(timeoutInMs);
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ }
+ }
+
+
+ /**
+ * Returns whether the device supports observer mode or not. When observe
+ * mode is enabled, the NFC hardware will listen for NFC readers, but not
+ * respond to them. When observe mode is disabled, the NFC hardware will
+ * resoond to the reader and proceed with the transaction.
+ * @return true if the mode is supported, false otherwise.
+ */
+ @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
+ public boolean isObserveModeSupported() {
+ try {
+ return sService.isObserveModeSupported();
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ return false;
+ }
+ }
+
+ /**
+ * Disables observe mode to allow the transaction to proceed. See
+ * {@link #isObserveModeSupported()} for a description of observe mode and
+ * use {@link #disallowTransaction()} to enable observe mode and block
+ * transactions again.
+ *
+ * @return boolean indicating success or failure.
+ */
+ @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
+ public boolean allowTransaction() {
+ try {
+ return sService.setObserveMode(false);
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ return false;
+ }
+ }
+
+ /**
+ * Signals that the transaction has completed and observe mode may be
+ * reenabled. See {@link #isObserveModeSupported()} for a description of
+ * observe mode and use {@link #allowTransaction()} to disable observe
+ * mode and allow transactions to proceed.
+ *
+ * @return boolean indicating success or failure.
+ */
+
+ @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
+ public boolean disallowTransaction() {
+ try {
+ return sService.setObserveMode(true);
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ return false;
+ }
+ }
+
+ /**
+ * Resumes default polling for the current device state if polling is paused. Calling
+ * this while polling is not paused is a no-op.
+ *
+ * @hide
+ */
+ public void resumePolling() {
+ try {
+ sService.resumePolling();
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ }
+ }
+
+ /**
+ * Set one or more {@link Uri}s to send using Android Beam (TM). Every
+ * Uri you provide must have either scheme 'file' or scheme 'content'.
+ *
+ * <p>For the data provided through this method, Android Beam tries to
+ * switch to alternate transports such as Bluetooth to achieve a fast
+ * transfer speed. Hence this method is very suitable
+ * for transferring large files such as pictures or songs.
+ *
+ * <p>The receiving side will store the content of each Uri in
+ * a file and present a notification to the user to open the file
+ * with a {@link android.content.Intent} with action
+ * {@link android.content.Intent#ACTION_VIEW}.
+ * If multiple URIs are sent, the {@link android.content.Intent} will refer
+ * to the first of the stored files.
+ *
+ * <p>This method may be called at any time before {@link Activity#onDestroy},
+ * but the URI(s) are only made available for Android Beam when the
+ * specified activity(s) are in resumed (foreground) state. The recommended
+ * approach is to call this method during your Activity's
+ * {@link Activity#onCreate} - see sample
+ * code below. This method does not immediately perform any I/O or blocking work,
+ * so is safe to call on your main thread.
+ *
+ * <p>{@link #setBeamPushUris} and {@link #setBeamPushUrisCallback}
+ * have priority over both {@link #setNdefPushMessage} and
+ * {@link #setNdefPushMessageCallback}.
+ *
+ * <p>If {@link #setBeamPushUris} is called with a null Uri array,
+ * and/or {@link #setBeamPushUrisCallback} is called with a null callback,
+ * then the Uri push will be completely disabled for the specified activity(s).
+ *
+ * <p>Code example:
+ * <pre>
+ * protected void onCreate(Bundle savedInstanceState) {
+ * super.onCreate(savedInstanceState);
+ * NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
+ * if (nfcAdapter == null) return; // NFC not available on this device
+ * nfcAdapter.setBeamPushUris(new Uri[] {uri1, uri2}, this);
+ * }</pre>
+ * And that is it. Only one call per activity is necessary. The Android
+ * OS will automatically release its references to the Uri(s) and the
+ * Activity object when it is destroyed if you follow this pattern.
+ *
+ * <p>If your Activity wants to dynamically supply Uri(s),
+ * then set a callback using {@link #setBeamPushUrisCallback} instead
+ * of using this method.
+ *
+ * <p class="note">Do not pass in an Activity that has already been through
+ * {@link Activity#onDestroy}. This is guaranteed if you call this API
+ * during {@link Activity#onCreate}.
+ *
+ * <p class="note">If this device does not support alternate transports
+ * such as Bluetooth or WiFI, calling this method does nothing.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param uris an array of Uri(s) to push over Android Beam
+ * @param activity activity for which the Uri(s) will be pushed
+ * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+ * @removed this feature is removed. File sharing can work using other technology like
+ * Bluetooth.
+ */
+ @java.lang.Deprecated
+ @UnsupportedAppUsage
+ public void setBeamPushUris(Uri[] uris, Activity activity) {
+ synchronized (sLock) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ /**
+ * Set a callback that will dynamically generate one or more {@link Uri}s
+ * to send using Android Beam (TM). Every Uri the callback provides
+ * must have either scheme 'file' or scheme 'content'.
+ *
+ * <p>For the data provided through this callback, Android Beam tries to
+ * switch to alternate transports such as Bluetooth to achieve a fast
+ * transfer speed. Hence this method is very suitable
+ * for transferring large files such as pictures or songs.
+ *
+ * <p>The receiving side will store the content of each Uri in
+ * a file and present a notification to the user to open the file
+ * with a {@link android.content.Intent} with action
+ * {@link android.content.Intent#ACTION_VIEW}.
+ * If multiple URIs are sent, the {@link android.content.Intent} will refer
+ * to the first of the stored files.
+ *
+ * <p>This method may be called at any time before {@link Activity#onDestroy},
+ * but the URI(s) are only made available for Android Beam when the
+ * specified activity(s) are in resumed (foreground) state. The recommended
+ * approach is to call this method during your Activity's
+ * {@link Activity#onCreate} - see sample
+ * code below. This method does not immediately perform any I/O or blocking work,
+ * so is safe to call on your main thread.
+ *
+ * <p>{@link #setBeamPushUris} and {@link #setBeamPushUrisCallback}
+ * have priority over both {@link #setNdefPushMessage} and
+ * {@link #setNdefPushMessageCallback}.
+ *
+ * <p>If {@link #setBeamPushUris} is called with a null Uri array,
+ * and/or {@link #setBeamPushUrisCallback} is called with a null callback,
+ * then the Uri push will be completely disabled for the specified activity(s).
+ *
+ * <p>Code example:
+ * <pre>
+ * protected void onCreate(Bundle savedInstanceState) {
+ * super.onCreate(savedInstanceState);
+ * NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
+ * if (nfcAdapter == null) return; // NFC not available on this device
+ * nfcAdapter.setBeamPushUrisCallback(callback, this);
+ * }</pre>
+ * And that is it. Only one call per activity is necessary. The Android
+ * OS will automatically release its references to the Uri(s) and the
+ * Activity object when it is destroyed if you follow this pattern.
+ *
+ * <p class="note">Do not pass in an Activity that has already been through
+ * {@link Activity#onDestroy}. This is guaranteed if you call this API
+ * during {@link Activity#onCreate}.
+ *
+ * <p class="note">If this device does not support alternate transports
+ * such as Bluetooth or WiFI, calling this method does nothing.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param callback callback, or null to disable
+ * @param activity activity for which the Uri(s) will be pushed
+ * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+ * @removed this feature is removed. File sharing can work using other technology like
+ * Bluetooth.
+ */
+ @java.lang.Deprecated
+ @UnsupportedAppUsage
+ public void setBeamPushUrisCallback(CreateBeamUrisCallback callback, Activity activity) {
+ synchronized (sLock) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ /**
+ * Set a static {@link NdefMessage} to send using Android Beam (TM).
+ *
+ * <p>This method may be called at any time before {@link Activity#onDestroy},
+ * but the NDEF message is only made available for NDEF push when the
+ * specified activity(s) are in resumed (foreground) state. The recommended
+ * approach is to call this method during your Activity's
+ * {@link Activity#onCreate} - see sample
+ * code below. This method does not immediately perform any I/O or blocking work,
+ * so is safe to call on your main thread.
+ *
+ * <p>Only one NDEF message can be pushed by the currently resumed activity.
+ * If both {@link #setNdefPushMessage} and
+ * {@link #setNdefPushMessageCallback} are set, then
+ * the callback will take priority.
+ *
+ * <p>If neither {@link #setNdefPushMessage} or
+ * {@link #setNdefPushMessageCallback} have been called for your activity, then
+ * the Android OS may choose to send a default NDEF message on your behalf,
+ * such as a URI for your application.
+ *
+ * <p>If {@link #setNdefPushMessage} is called with a null NDEF message,
+ * and/or {@link #setNdefPushMessageCallback} is called with a null callback,
+ * then NDEF push will be completely disabled for the specified activity(s).
+ * This also disables any default NDEF message the Android OS would have
+ * otherwise sent on your behalf for those activity(s).
+ *
+ * <p>If you want to prevent the Android OS from sending default NDEF
+ * messages completely (for all activities), you can include a
+ * {@code <meta-data>} element inside the {@code <application>}
+ * element of your AndroidManifest.xml file, like this:
+ * <pre>
+ * <application ...>
+ * <meta-data android:name="android.nfc.disable_beam_default"
+ * android:value="true" />
+ * </application></pre>
+ *
+ * <p>The API allows for multiple activities to be specified at a time,
+ * but it is strongly recommended to just register one at a time,
+ * and to do so during the activity's {@link Activity#onCreate}. For example:
+ * <pre>
+ * protected void onCreate(Bundle savedInstanceState) {
+ * super.onCreate(savedInstanceState);
+ * NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
+ * if (nfcAdapter == null) return; // NFC not available on this device
+ * nfcAdapter.setNdefPushMessage(ndefMessage, this);
+ * }</pre>
+ * And that is it. Only one call per activity is necessary. The Android
+ * OS will automatically release its references to the NDEF message and the
+ * Activity object when it is destroyed if you follow this pattern.
+ *
+ * <p>If your Activity wants to dynamically generate an NDEF message,
+ * then set a callback using {@link #setNdefPushMessageCallback} instead
+ * of a static message.
+ *
+ * <p class="note">Do not pass in an Activity that has already been through
+ * {@link Activity#onDestroy}. This is guaranteed if you call this API
+ * during {@link Activity#onCreate}.
+ *
+ * <p class="note">For sending large content such as pictures and songs,
+ * consider using {@link #setBeamPushUris}, which switches to alternate transports
+ * such as Bluetooth to achieve a fast transfer rate.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param message NDEF message to push over NFC, or null to disable
+ * @param activity activity for which the NDEF message will be pushed
+ * @param activities optional additional activities, however we strongly recommend
+ * to only register one at a time, and to do so in that activity's
+ * {@link Activity#onCreate}
+ * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+ * @removed this feature is removed. File sharing can work using other technology like
+ * Bluetooth.
+ */
+ @java.lang.Deprecated
+ @UnsupportedAppUsage
+ public void setNdefPushMessage(NdefMessage message, Activity activity,
+ Activity ... activities) {
+ synchronized (sLock) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ /**
+ * @hide
+ * @removed
+ */
+ @SystemApi
+ @UnsupportedAppUsage
+ public void setNdefPushMessage(NdefMessage message, Activity activity, int flags) {
+ synchronized (sLock) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ /**
+ * Set a callback that dynamically generates NDEF messages to send using Android Beam (TM).
+ *
+ * <p>This method may be called at any time before {@link Activity#onDestroy},
+ * but the NDEF message callback can only occur when the
+ * specified activity(s) are in resumed (foreground) state. The recommended
+ * approach is to call this method during your Activity's
+ * {@link Activity#onCreate} - see sample
+ * code below. This method does not immediately perform any I/O or blocking work,
+ * so is safe to call on your main thread.
+ *
+ * <p>Only one NDEF message can be pushed by the currently resumed activity.
+ * If both {@link #setNdefPushMessage} and
+ * {@link #setNdefPushMessageCallback} are set, then
+ * the callback will take priority.
+ *
+ * <p>If neither {@link #setNdefPushMessage} or
+ * {@link #setNdefPushMessageCallback} have been called for your activity, then
+ * the Android OS may choose to send a default NDEF message on your behalf,
+ * such as a URI for your application.
+ *
+ * <p>If {@link #setNdefPushMessage} is called with a null NDEF message,
+ * and/or {@link #setNdefPushMessageCallback} is called with a null callback,
+ * then NDEF push will be completely disabled for the specified activity(s).
+ * This also disables any default NDEF message the Android OS would have
+ * otherwise sent on your behalf for those activity(s).
+ *
+ * <p>If you want to prevent the Android OS from sending default NDEF
+ * messages completely (for all activities), you can include a
+ * {@code <meta-data>} element inside the {@code <application>}
+ * element of your AndroidManifest.xml file, like this:
+ * <pre>
+ * <application ...>
+ * <meta-data android:name="android.nfc.disable_beam_default"
+ * android:value="true" />
+ * </application></pre>
+ *
+ * <p>The API allows for multiple activities to be specified at a time,
+ * but it is strongly recommended to just register one at a time,
+ * and to do so during the activity's {@link Activity#onCreate}. For example:
+ * <pre>
+ * protected void onCreate(Bundle savedInstanceState) {
+ * super.onCreate(savedInstanceState);
+ * NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
+ * if (nfcAdapter == null) return; // NFC not available on this device
+ * nfcAdapter.setNdefPushMessageCallback(callback, this);
+ * }</pre>
+ * And that is it. Only one call per activity is necessary. The Android
+ * OS will automatically release its references to the callback and the
+ * Activity object when it is destroyed if you follow this pattern.
+ *
+ * <p class="note">Do not pass in an Activity that has already been through
+ * {@link Activity#onDestroy}. This is guaranteed if you call this API
+ * during {@link Activity#onCreate}.
+ * <p class="note">For sending large content such as pictures and songs,
+ * consider using {@link #setBeamPushUris}, which switches to alternate transports
+ * such as Bluetooth to achieve a fast transfer rate.
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param callback callback, or null to disable
+ * @param activity activity for which the NDEF message will be pushed
+ * @param activities optional additional activities, however we strongly recommend
+ * to only register one at a time, and to do so in that activity's
+ * {@link Activity#onCreate}
+ * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+ * @removed this feature is removed. File sharing can work using other technology like
+ * Bluetooth.
+ */
+ @java.lang.Deprecated
+ @UnsupportedAppUsage
+ public void setNdefPushMessageCallback(CreateNdefMessageCallback callback, Activity activity,
+ Activity ... activities) {
+ synchronized (sLock) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ /**
+ * Set a callback on successful Android Beam (TM).
+ *
+ * <p>This method may be called at any time before {@link Activity#onDestroy},
+ * but the callback can only occur when the
+ * specified activity(s) are in resumed (foreground) state. The recommended
+ * approach is to call this method during your Activity's
+ * {@link Activity#onCreate} - see sample
+ * code below. This method does not immediately perform any I/O or blocking work,
+ * so is safe to call on your main thread.
+ *
+ * <p>The API allows for multiple activities to be specified at a time,
+ * but it is strongly recommended to just register one at a time,
+ * and to do so during the activity's {@link Activity#onCreate}. For example:
+ * <pre>
+ * protected void onCreate(Bundle savedInstanceState) {
+ * super.onCreate(savedInstanceState);
+ * NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
+ * if (nfcAdapter == null) return; // NFC not available on this device
+ * nfcAdapter.setOnNdefPushCompleteCallback(callback, this);
+ * }</pre>
+ * And that is it. Only one call per activity is necessary. The Android
+ * OS will automatically release its references to the callback and the
+ * Activity object when it is destroyed if you follow this pattern.
+ *
+ * <p class="note">Do not pass in an Activity that has already been through
+ * {@link Activity#onDestroy}. This is guaranteed if you call this API
+ * during {@link Activity#onCreate}.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param callback callback, or null to disable
+ * @param activity activity for which the NDEF message will be pushed
+ * @param activities optional additional activities, however we strongly recommend
+ * to only register one at a time, and to do so in that activity's
+ * {@link Activity#onCreate}
+ * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+ * @removed this feature is removed. File sharing can work using other technology like
+ * Bluetooth.
+ */
+ @java.lang.Deprecated
+ @UnsupportedAppUsage
+ public void setOnNdefPushCompleteCallback(OnNdefPushCompleteCallback callback,
+ Activity activity, Activity ... activities) {
+ synchronized (sLock) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ /**
+ * Enable foreground dispatch to the given Activity.
+ *
+ * <p>This will give priority to the foreground activity when
+ * dispatching a discovered {@link Tag} to an application.
+ *
+ * <p>If any IntentFilters are provided to this method they are used to match dispatch Intents
+ * for both the {@link NfcAdapter#ACTION_NDEF_DISCOVERED} and
+ * {@link NfcAdapter#ACTION_TAG_DISCOVERED}. Since {@link NfcAdapter#ACTION_TECH_DISCOVERED}
+ * relies on meta data outside of the IntentFilter matching for that dispatch Intent is handled
+ * by passing in the tech lists separately. Each first level entry in the tech list represents
+ * an array of technologies that must all be present to match. If any of the first level sets
+ * match then the dispatch is routed through the given PendingIntent. In other words, the second
+ * level is ANDed together and the first level entries are ORed together.
+ *
+ * <p>If you pass {@code null} for both the {@code filters} and {@code techLists} parameters
+ * that acts a wild card and will cause the foreground activity to receive all tags via the
+ * {@link NfcAdapter#ACTION_TAG_DISCOVERED} intent.
+ *
+ * <p>This method must be called from the main thread, and only when the activity is in the
+ * foreground (resumed). Also, activities must call {@link #disableForegroundDispatch} before
+ * the completion of their {@link Activity#onPause} callback to disable foreground dispatch
+ * after it has been enabled.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @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
+ * @param techLists the tech lists used to perform matching for dispatching of the
+ * {@link NfcAdapter#ACTION_TECH_DISCOVERED} intent
+ * @throws IllegalStateException if the Activity is not currently in the foreground
+ * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+ */
+ public void enableForegroundDispatch(Activity activity, PendingIntent intent,
+ IntentFilter[] filters, String[][] techLists) {
+ synchronized (sLock) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
+ if (activity == null || intent == null) {
+ throw new NullPointerException();
+ }
+ try {
+ TechListParcel parcel = null;
+ if (techLists != null && techLists.length > 0) {
+ parcel = new TechListParcel(techLists);
+ }
+ sService.setForegroundDispatch(intent, filters, parcel);
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ }
+ }
+
+ /**
+ * Disable foreground dispatch to the given activity.
+ *
+ * <p>After calling {@link #enableForegroundDispatch}, an activity
+ * must call this method before its {@link Activity#onPause} callback
+ * completes.
+ *
+ * <p>This method must be called from the main thread.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param activity the Activity to disable dispatch to
+ * @throws IllegalStateException if the Activity has already been paused
+ * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+ */
+ public void disableForegroundDispatch(Activity activity) {
+ synchronized (sLock) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
+ try {
+ sService.setForegroundDispatch(null, null, null);
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ }
+ }
+
+ /**
+ * Limit the NFC controller to reader mode while this Activity is in the foreground.
+ *
+ * <p>In this mode the NFC controller will only act as an NFC tag reader/writer,
+ * thus disabling any peer-to-peer (Android Beam) and card-emulation modes of
+ * the NFC adapter on this device.
+ *
+ * <p>Use {@link #FLAG_READER_SKIP_NDEF_CHECK} to prevent the platform from
+ * performing any NDEF checks in reader mode. Note that this will prevent the
+ * {@link Ndef} tag technology from being enumerated on the tag, and that
+ * NDEF-based tag dispatch will not be functional.
+ *
+ * <p>For interacting with tags that are emulated on another Android device
+ * using Android's host-based card-emulation, the recommended flags are
+ * {@link #FLAG_READER_NFC_A} and {@link #FLAG_READER_SKIP_NDEF_CHECK}.
+ *
+ * @param activity the Activity that requests the adapter to be in reader mode
+ * @param callback the callback to be called when a tag is discovered
+ * @param flags Flags indicating poll technologies and other optional parameters
+ * @param extras Additional extras for configuring reader mode.
+ * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+ */
+ public void enableReaderMode(Activity activity, ReaderCallback callback, int flags,
+ Bundle extras) {
+ synchronized (sLock) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
+ mNfcActivityManager.enableReaderMode(activity, callback, flags, extras);
+ }
+
+ /**
+ * Restore the NFC adapter to normal mode of operation: supporting
+ * peer-to-peer (Android Beam), card emulation, and polling for
+ * all supported tag technologies.
+ *
+ * @param activity the Activity that currently has reader mode enabled
+ * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+ */
+ public void disableReaderMode(Activity activity) {
+ synchronized (sLock) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
+ mNfcActivityManager.disableReaderMode(activity);
+ }
+
+ // Flags arguments to NFC adapter to enable/disable NFC
+ private static final int DISABLE_POLLING_FLAGS = 0x1000;
+ private static final int ENABLE_POLLING_FLAGS = 0x0000;
+
+ /**
+ * Privileged API to enable disable reader polling.
+ * Note: Use with caution! The app is responsible for ensuring that the polling state is
+ * returned to normal.
+ *
+ * @see #enableReaderMode(Activity, ReaderCallback, int, Bundle) for more detailed
+ * documentation.
+ *
+ * @param enablePolling whether to enable or disable polling.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @SuppressLint("VisiblySynchronized")
+ public void setReaderMode(boolean enablePolling) {
+ synchronized (sLock) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
+ Binder token = new Binder();
+ int flags = enablePolling ? ENABLE_POLLING_FLAGS : DISABLE_POLLING_FLAGS;
+ try {
+ NfcAdapter.sService.setReaderMode(token, null, flags, null);
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ }
+ }
+
+ /**
+ * Set the NFC controller to enable specific poll/listen technologies,
+ * as specified in parameters, while this Activity is in the foreground.
+ *
+ * Use {@link #FLAG_READER_KEEP} to keep current polling technology.
+ * Use {@link #FLAG_LISTEN_KEEP} to keep current listenig technology.
+ * Use {@link #FLAG_READER_DISABLE} to disable polling.
+ * Use {@link #FLAG_LISTEN_DISABLE} to disable listening.
+ * Also refer to {@link #resetDiscoveryTechnology(Activity)} to restore these changes.
+ * </p>
+ * The pollTechnology, listenTechnology parameters can be one or several of below list.
+ * <pre>
+ * Poll Listen
+ * Passive A 0x01 (NFC_A) 0x01 (NFC_PASSIVE_A)
+ * Passive B 0x02 (NFC_B) 0x02 (NFC_PASSIVE_B)
+ * Passive F 0x04 (NFC_F) 0x04 (NFC_PASSIVE_F)
+ * ISO 15693 0x08 (NFC_V) -
+ * Kovio 0x10 (NFC_BARCODE) -
+ * </pre>
+ * <p>Example usage in an Activity that requires to disable poll,
+ * keep current listen technologies:
+ * <pre>
+ * protected void onResume() {
+ * mNfcAdapter = NfcAdapter.getDefaultAdapter(getApplicationContext());
+ * mNfcAdapter.setDiscoveryTechnology(this,
+ * NfcAdapter.FLAG_READER_DISABLE, NfcAdapter.FLAG_LISTEN_KEEP);
+ * }</pre></p>
+ * @param activity The Activity that requests NFC controller to enable specific technologies.
+ * @param pollTechnology Flags indicating poll technologies.
+ * @param listenTechnology Flags indicating listen technologies.
+ * @throws UnsupportedOperationException if FEATURE_NFC,
+ * FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF are unavailable.
+ */
+
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+ public void setDiscoveryTechnology(@NonNull Activity activity,
+ @PollTechnology int pollTechnology, @ListenTechnology int listenTechnology) {
+ if (listenTechnology == FLAG_LISTEN_DISABLE) {
+ synchronized (sLock) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
+ mNfcActivityManager.enableReaderMode(activity, null, pollTechnology, null);
+ return;
+ }
+ if (pollTechnology == FLAG_READER_DISABLE) {
+ synchronized (sLock) {
+ if (!sHasCeFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
+ } else {
+ synchronized (sLock) {
+ if (!sHasNfcFeature || !sHasCeFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+ mNfcActivityManager.setDiscoveryTech(activity, pollTechnology, listenTechnology);
+ }
+
+ /**
+ * Restore the poll/listen technologies of NFC controller,
+ * which were changed by {@link #setDiscoveryTechnology(Activity , int , int)}
+ *
+ * @param activity The Activity that requests to changed technologies.
+ */
+
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+ public void resetDiscoveryTechnology(@NonNull Activity activity) {
+ mNfcActivityManager.resetDiscoveryTech(activity);
+ }
+
+ /**
+ * Manually invoke Android Beam to share data.
+ *
+ * <p>The Android Beam animation is normally only shown when two NFC-capable
+ * devices come into range.
+ * By calling this method, an Activity can invoke the Beam animation directly
+ * even if no other NFC device is in range yet. The Beam animation will then
+ * prompt the user to tap another NFC-capable device to complete the data
+ * transfer.
+ *
+ * <p>The main advantage of using this method is that it avoids the need for the
+ * user to tap the screen to complete the transfer, as this method already
+ * establishes the direction of the transfer and the consent of the user to
+ * share data. Callers are responsible for making sure that the user has
+ * consented to sharing data on NFC tap.
+ *
+ * <p>Note that to use this method, the passed in Activity must have already
+ * set data to share over Beam by using method calls such as
+ * {@link #setNdefPushMessageCallback} or
+ * {@link #setBeamPushUrisCallback}.
+ *
+ * @param activity the current foreground Activity that has registered data to share
+ * @return whether the Beam animation was successfully invoked
+ * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+ * @removed this feature is removed. File sharing can work using other technology like
+ * Bluetooth.
+ */
+ @java.lang.Deprecated
+ @UnsupportedAppUsage
+ public boolean invokeBeam(Activity activity) {
+ synchronized (sLock) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Enable NDEF message push over NFC while this Activity is in the foreground.
+ *
+ * <p>You must explicitly call this method every time the activity is
+ * resumed, and you must call {@link #disableForegroundNdefPush} before
+ * your activity completes {@link Activity#onPause}.
+ *
+ * <p>Strongly recommend to use the new {@link #setNdefPushMessage}
+ * instead: it automatically hooks into your activity life-cycle,
+ * so you do not need to call enable/disable in your onResume/onPause.
+ *
+ * <p>For NDEF push to function properly the other NFC device must
+ * support either NFC Forum's SNEP (Simple Ndef Exchange Protocol), or
+ * Android's "com.android.npp" (Ndef Push Protocol). This was optional
+ * on Gingerbread level Android NFC devices, but SNEP is mandatory on
+ * Ice-Cream-Sandwich and beyond.
+ *
+ * <p>This method must be called from the main thread.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param activity foreground activity
+ * @param message a NDEF Message to push over NFC
+ * @throws UnsupportedOperationException if FEATURE_NFC is unavailable
+ * @removed this feature is removed. File sharing can work using other technology like
+ * Bluetooth.
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public void enableForegroundNdefPush(Activity activity, NdefMessage message) {
+ synchronized (sLock) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ /**
+ * Disable NDEF message push over P2P.
+ *
+ * <p>After calling {@link #enableForegroundNdefPush}, an activity
+ * must call this method before its {@link Activity#onPause} callback
+ * completes.
+ *
+ * <p>Strongly recommend to use the new {@link #setNdefPushMessage}
+ * instead: it automatically hooks into your activity life-cycle,
+ * so you do not need to call enable/disable in your onResume/onPause.
+ *
+ * <p>This method must be called from the main thread.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param activity the Foreground activity
+ * @throws UnsupportedOperationException if FEATURE_NFC is unavailable
+ * @removed this feature is removed. File sharing can work using other technology like
+ * Bluetooth.
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public void disableForegroundNdefPush(Activity activity) {
+ synchronized (sLock) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ /**
+ * Sets Secure NFC feature.
+ * <p>This API is for the Settings application.
+ * @return True if successful
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ public boolean enableSecureNfc(boolean enable) {
+ if (!sHasNfcFeature && !sHasCeFeature) {
+ throw new UnsupportedOperationException();
+ }
+ try {
+ return sService.setNfcSecure(enable);
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ return false;
+ }
+ try {
+ return sService.setNfcSecure(enable);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Checks if the device supports Secure NFC functionality.
+ *
+ * @return True if device supports Secure NFC, false otherwise
+ * @throws UnsupportedOperationException if FEATURE_NFC,
+ * FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
+ * FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC and FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE
+ * are unavailable
+ */
+ public boolean isSecureNfcSupported() {
+ if (!sHasNfcFeature && !sHasCeFeature) {
+ throw new UnsupportedOperationException();
+ }
+ try {
+ return sService.deviceSupportsNfcSecure();
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ return false;
+ }
+ try {
+ return sService.deviceSupportsNfcSecure();
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Returns information regarding Nfc antennas on the device
+ * such as their relative positioning on the device.
+ *
+ * @return Information on the nfc antenna(s) on the device.
+ * @throws UnsupportedOperationException if FEATURE_NFC,
+ * FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
+ * FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC and FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE
+ * are unavailable
+ */
+ @Nullable
+ public NfcAntennaInfo getNfcAntennaInfo() {
+ if (!sHasNfcFeature && !sHasCeFeature) {
+ throw new UnsupportedOperationException();
+ }
+ try {
+ return sService.getNfcAntennaInfo();
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ return null;
+ }
+ try {
+ return sService.getNfcAntennaInfo();
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Checks Secure NFC feature is enabled.
+ *
+ * @return True if Secure NFC is enabled, false otherwise
+ * @throws UnsupportedOperationException if FEATURE_NFC,
+ * FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
+ * FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC and FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE
+ * are unavailable
+ * @throws UnsupportedOperationException if device doesn't support
+ * Secure NFC functionality. {@link #isSecureNfcSupported}
+ */
+ public boolean isSecureNfcEnabled() {
+ if (!sHasNfcFeature && !sHasCeFeature) {
+ throw new UnsupportedOperationException();
+ }
+ try {
+ return sService.isNfcSecureEnabled();
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ return false;
+ }
+ try {
+ return sService.isNfcSecureEnabled();
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Sets NFC Reader option feature.
+ * <p>This API is for the Settings application.
+ * @return True if successful
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_READER_OPTION)
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ public boolean enableReaderOption(boolean enable) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ try {
+ return sService.enableReaderOption(enable);
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ return false;
+ }
+ try {
+ return sService.enableReaderOption(enable);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Checks if the device supports NFC Reader option functionality.
+ *
+ * @return True if device supports NFC Reader option, false otherwise
+ * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_READER_OPTION)
+ public boolean isReaderOptionSupported() {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ try {
+ return sService.isReaderOptionSupported();
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ return false;
+ }
+ try {
+ return sService.isReaderOptionSupported();
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Checks NFC Reader option feature is enabled.
+ *
+ * @return True if NFC Reader option is enabled, false otherwise
+ * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+ * @throws UnsupportedOperationException if device doesn't support
+ * NFC Reader option functionality. {@link #isReaderOptionSupported}
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_READER_OPTION)
+ public boolean isReaderOptionEnabled() {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ try {
+ return sService.isReaderOptionEnabled();
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ return false;
+ }
+ try {
+ return sService.isReaderOptionEnabled();
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Enable NDEF Push feature.
+ * <p>This API is for the Settings application.
+ * @hide
+ * @removed
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @UnsupportedAppUsage
+ public boolean enableNdefPush() {
+ return false;
+ }
+
+ /**
+ * Disable NDEF Push feature.
+ * <p>This API is for the Settings application.
+ * @hide
+ * @removed
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @UnsupportedAppUsage
+ public boolean disableNdefPush() {
+ return false;
+ }
+
+ /**
+ * Return true if the NDEF Push (Android Beam) feature is enabled.
+ * <p>This function will return true only if both NFC is enabled, and the
+ * NDEF Push feature is enabled.
+ * <p>Note that if NFC is enabled but NDEF Push is disabled then this
+ * device can still <i>receive</i> NDEF messages, it just cannot send them.
+ * <p>Applications cannot directly toggle the NDEF Push feature, but they
+ * can request Settings UI allowing the user to toggle NDEF Push using
+ * <code>startActivity(new Intent(Settings.ACTION_NFCSHARING_SETTINGS))</code>
+ * <p>Example usage in an Activity that requires NDEF Push:
+ * <p><pre>
+ * protected void onResume() {
+ * super.onResume();
+ * if (!nfcAdapter.isEnabled()) {
+ * startActivity(new Intent(Settings.ACTION_NFC_SETTINGS));
+ * } else if (!nfcAdapter.isNdefPushEnabled()) {
+ * startActivity(new Intent(Settings.ACTION_NFCSHARING_SETTINGS));
+ * }
+ * }</pre>
+ *
+ * @see android.provider.Settings#ACTION_NFCSHARING_SETTINGS
+ * @return true if NDEF Push feature is enabled
+ * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+ * @removed this feature is removed. File sharing can work using other technology like
+ * Bluetooth.
+ */
+ @java.lang.Deprecated
+ @UnsupportedAppUsage
+ public boolean isNdefPushEnabled() {
+ synchronized (sLock) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Signals that you are no longer interested in communicating with an NFC tag
+ * for as long as it remains in range.
+ *
+ * All future attempted communication to this tag will fail with {@link IOException}.
+ * The NFC controller will be put in a low-power polling mode, allowing the device
+ * to save power in cases where it's "attached" to a tag all the time (e.g. a tag in
+ * car dock).
+ *
+ * Additionally the debounceMs parameter allows you to specify for how long the tag needs
+ * to have gone out of range, before it will be dispatched again.
+ *
+ * Note: the NFC controller typically polls at a pretty slow interval (100 - 500 ms).
+ * This means that if the tag repeatedly goes in and out of range (for example, in
+ * case of a flaky connection), and the controller happens to poll every time the
+ * tag is out of range, it *will* re-dispatch the tag after debounceMs, despite the tag
+ * having been "in range" during the interval.
+ *
+ * Note 2: if a tag with another UID is detected after this API is called, its effect
+ * will be cancelled; if this tag shows up before the amount of time specified in
+ * debounceMs, it will be dispatched again.
+ *
+ * Note 3: some tags have a random UID, in which case this API won't work reliably.
+ *
+ * @param tag the {@link android.nfc.Tag Tag} to ignore.
+ * @param debounceMs minimum amount of time the tag needs to be out of range before being
+ * dispatched again.
+ * @param tagRemovedListener listener to be called when the tag is removed from the field.
+ * Note that this will only be called if the tag has been out of range
+ * for at least debounceMs, or if another tag came into range before
+ * debounceMs. May be null in case you don't want a callback.
+ * @param handler the {@link android.os.Handler Handler} that will be used for delivering
+ * the callback. if the handler is null, then the thread used for delivering
+ * the callback is unspecified.
+ * @return false if the tag couldn't be found (or has already gone out of range), true otherwise
+ */
+ public boolean ignore(final Tag tag, int debounceMs,
+ final OnTagRemovedListener tagRemovedListener, final Handler handler) {
+ ITagRemovedCallback.Stub iListener = null;
+ if (tagRemovedListener != null) {
+ iListener = new ITagRemovedCallback.Stub() {
+ @Override
+ public void onTagRemoved() throws RemoteException {
+ if (handler != null) {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ tagRemovedListener.onTagRemoved();
+ }
+ });
+ } else {
+ tagRemovedListener.onTagRemoved();
+ }
+ synchronized (mLock) {
+ mTagRemovedListener = null;
+ }
+ }
+ };
+ }
+ synchronized (mLock) {
+ mTagRemovedListener = iListener;
+ }
+ try {
+ return sService.ignore(tag.getServiceHandle(), debounceMs, iListener);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Inject a mock NFC tag.<p>
+ * Used for testing purposes.
+ * <p class="note">Requires the
+ * {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} permission.
+ * @hide
+ */
+ public void dispatch(Tag tag) {
+ if (tag == null) {
+ throw new NullPointerException("tag cannot be null");
+ }
+ try {
+ sService.dispatch(tag);
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ }
+ }
+
+ /**
+ * Registers a new NFC unlock handler with the NFC service.
+ *
+ * <p />NFC unlock handlers are intended to unlock the keyguard in the presence of a trusted
+ * NFC device. The handler should return true if it successfully authenticates the user and
+ * unlocks the keyguard.
+ *
+ * <p /> The parameter {@code tagTechnologies} determines which Tag technologies will be polled for
+ * at the lockscreen. Polling for less tag technologies reduces latency, and so it is
+ * strongly recommended to only provide the Tag technologies that the handler is expected to
+ * receive. There must be at least one tag technology provided, otherwise the unlock handler
+ * is ignored.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ public boolean addNfcUnlockHandler(final NfcUnlockHandler unlockHandler,
+ String[] tagTechnologies) {
+ synchronized (sLock) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
+ // If there are no tag technologies, don't bother adding unlock handler
+ if (tagTechnologies.length == 0) {
+ return false;
+ }
+
+ try {
+ synchronized (mLock) {
+ if (mNfcUnlockHandlers.containsKey(unlockHandler)) {
+ // update the tag technologies
+ sService.removeNfcUnlockHandler(mNfcUnlockHandlers.get(unlockHandler));
+ mNfcUnlockHandlers.remove(unlockHandler);
+ }
+
+ INfcUnlockHandler.Stub iHandler = new INfcUnlockHandler.Stub() {
+ @Override
+ public boolean onUnlockAttempted(Tag tag) throws RemoteException {
+ return unlockHandler.onUnlockAttempted(tag);
+ }
+ };
+
+ sService.addNfcUnlockHandler(iHandler,
+ Tag.getTechCodesFromStrings(tagTechnologies));
+ mNfcUnlockHandlers.put(unlockHandler, iHandler);
+ }
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ return false;
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Unable to register LockscreenDispatch", e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Removes a previously registered unlock handler. Also removes the tag technologies
+ * associated with the removed unlock handler.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ public boolean removeNfcUnlockHandler(NfcUnlockHandler unlockHandler) {
+ synchronized (sLock) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
+ try {
+ synchronized (mLock) {
+ if (mNfcUnlockHandlers.containsKey(unlockHandler)) {
+ sService.removeNfcUnlockHandler(mNfcUnlockHandlers.remove(unlockHandler));
+ }
+
+ return true;
+ }
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ return false;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public INfcAdapterExtras getNfcAdapterExtrasInterface() {
+ if (mContext == null) {
+ throw new UnsupportedOperationException("You need a context on NfcAdapter to use the "
+ + " NFC extras APIs");
+ }
+ try {
+ return sService.getNfcAdapterExtrasInterface(mContext.getPackageName());
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ return null;
+ }
+ try {
+ return sService.getNfcAdapterExtrasInterface(mContext.getPackageName());
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ return null;
+ }
+ }
+
+ void enforceResumed(Activity activity) {
+ if (!activity.isResumed()) {
+ throw new IllegalStateException("API cannot be called while activity is paused");
+ }
+ }
+
+ int getSdkVersion() {
+ if (mContext == null) {
+ return android.os.Build.VERSION_CODES.GINGERBREAD; // best guess
+ } else {
+ return mContext.getApplicationInfo().targetSdkVersion;
+ }
+ }
+
+ /**
+ * Sets NFC controller always on feature.
+ * <p>This API is for the NFCC internal state management. It allows to discriminate
+ * the controller function from the NFC function by keeping the NFC controller on without
+ * any NFC RF enabled if necessary.
+ * <p>This call is asynchronous. Register a listener {@link #ControllerAlwaysOnListener}
+ * by {@link #registerControllerAlwaysOnListener} to find out when the operation is
+ * complete.
+ * <p>If this returns true, then either NFCC always on state has been set based on the value,
+ * or a {@link ControllerAlwaysOnListener#onControllerAlwaysOnChanged(boolean)} will be invoked
+ * to indicate the state change.
+ * If this returns false, then there is some problem that prevents an attempt to turn NFCC
+ * always on.
+ * @param value if true the NFCC will be kept on (with no RF enabled if NFC adapter is
+ * disabled), if false the NFCC will follow completely the Nfc adapter state.
+ * @throws UnsupportedOperationException if FEATURE_NFC,
+ * FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
+ * FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC and FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE
+ * are unavailable
+ * @return void
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON)
+ public boolean setControllerAlwaysOn(boolean value) {
+ if (!sHasNfcFeature && !sHasCeFeature) {
+ throw new UnsupportedOperationException();
+ }
+ try {
+ return sService.setControllerAlwaysOn(value);
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ return false;
+ }
+ try {
+ return sService.setControllerAlwaysOn(value);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Checks NFC controller always on feature is enabled.
+ *
+ * @return True if NFC controller always on is enabled, false otherwise
+ * @throws UnsupportedOperationException if FEATURE_NFC,
+ * FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
+ * FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC and FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE
+ * are unavailable
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON)
+ public boolean isControllerAlwaysOn() {
+ try {
+ return sService.isControllerAlwaysOn();
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ return false;
+ }
+ try {
+ return sService.isControllerAlwaysOn();
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Checks if the device supports NFC controller always on functionality.
+ *
+ * @return True if device supports NFC controller always on, false otherwise
+ * @throws UnsupportedOperationException if FEATURE_NFC,
+ * FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
+ * FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC and FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE
+ * are unavailable
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON)
+ public boolean isControllerAlwaysOnSupported() {
+ if (!sHasNfcFeature && !sHasCeFeature) {
+ throw new UnsupportedOperationException();
+ }
+ try {
+ return sService.isControllerAlwaysOnSupported();
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ return false;
+ }
+ try {
+ return sService.isControllerAlwaysOnSupported();
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Register a {@link ControllerAlwaysOnListener} to listen for NFC controller always on
+ * state changes
+ * <p>The provided listener will be invoked by the given {@link Executor}.
+ *
+ * @param executor an {@link Executor} to execute given listener
+ * @param listener user implementation of the {@link ControllerAlwaysOnListener}
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON)
+ public void registerControllerAlwaysOnListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull ControllerAlwaysOnListener listener) {
+ mControllerAlwaysOnListener.register(executor, listener);
+ }
+
+ /**
+ * Unregister the specified {@link ControllerAlwaysOnListener}
+ * <p>The same {@link ControllerAlwaysOnListener} object used when calling
+ * {@link #registerControllerAlwaysOnListener(Executor, ControllerAlwaysOnListener)}
+ * must be used.
+ *
+ * <p>Listeners are automatically unregistered when application process goes away
+ *
+ * @param listener user implementation of the {@link ControllerAlwaysOnListener}
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON)
+ public void unregisterControllerAlwaysOnListener(
+ @NonNull ControllerAlwaysOnListener listener) {
+ mControllerAlwaysOnListener.unregister(listener);
+ }
+
+
+ /**
+ * Sets whether we dispatch NFC Tag intents to the package.
+ *
+ * <p>{@link #ACTION_NDEF_DISCOVERED}, {@link #ACTION_TECH_DISCOVERED} or
+ * {@link #ACTION_TAG_DISCOVERED} will not be dispatched to an Activity if its package is
+ * disallowed.
+ * <p>An app is added to the preference list with the allowed flag set to {@code true}
+ * when a Tag intent is dispatched to the package for the first time. This API is called
+ * by settings to note that the user wants to change this default preference.
+ *
+ * @param userId the user to whom this package name will belong to
+ * @param pkg the full name (i.e. com.google.android.tag) of the package that will be added to
+ * the preference list
+ * @param allow {@code true} to allow dispatching Tag intents to the package's activity,
+ * {@code false} otherwise
+ * @return the {@link #TagIntentAppPreferenceResult} value
+ * @throws UnsupportedOperationException if {@link isTagIntentAppPreferenceSupported} returns
+ * {@code false}
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @TagIntentAppPreferenceResult
+ public int setTagIntentAppPreferenceForUser(@UserIdInt int userId,
+ @NonNull String pkg, boolean allow) {
+ Objects.requireNonNull(pkg, "pkg cannot be null");
+ if (!isTagIntentAppPreferenceSupported()) {
+ Log.e(TAG, "TagIntentAppPreference is not supported");
+ throw new UnsupportedOperationException();
+ }
+ try {
+ return sService.setTagIntentAppPreferenceForUser(userId, pkg, allow);
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ try {
+ return sService.setTagIntentAppPreferenceForUser(userId, pkg, allow);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ return TAG_INTENT_APP_PREF_RESULT_UNAVAILABLE;
+ }
+ }
+
+
+ /**
+ * Get the Tag dispatch preference list of the UserId.
+ *
+ * <p>This returns a mapping of package names for this user id to whether we dispatch Tag
+ * intents to the package. {@link #ACTION_NDEF_DISCOVERED}, {@link #ACTION_TECH_DISCOVERED} or
+ * {@link #ACTION_TAG_DISCOVERED} will not be dispatched to an Activity if its package is
+ * mapped to {@code false}.
+ * <p>There are three different possible cases:
+ * <p>A package not being in the preference list.
+ * It does not contain any Tag intent filters or the user never triggers a Tag detection that
+ * matches the intent filter of the package.
+ * <p>A package being mapped to {@code true}.
+ * When a package has been launched by a tag detection for the first time, the package name is
+ * put to the map and by default mapped to {@code true}. The package will receive Tag intents as
+ * usual.
+ * <p>A package being mapped to {@code false}.
+ * The user chooses to disable this package and it will not receive any Tag intents anymore.
+ *
+ * @param userId the user to whom this preference list will belong to
+ * @return a map of the UserId which indicates the mapping from package name to
+ * boolean(allow status), otherwise return an empty map
+ * @throws UnsupportedOperationException if {@link isTagIntentAppPreferenceSupported} returns
+ * {@code false}
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @NonNull
+ public Map<String, Boolean> getTagIntentAppPreferenceForUser(@UserIdInt int userId) {
+ if (!isTagIntentAppPreferenceSupported()) {
+ Log.e(TAG, "TagIntentAppPreference is not supported");
+ throw new UnsupportedOperationException();
+ }
+ try {
+ Map<String, Boolean> result = (Map<String, Boolean>) sService
+ .getTagIntentAppPreferenceForUser(userId);
+ return result;
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ return Collections.emptyMap();
+ }
+ try {
+ Map<String, Boolean> result = (Map<String, Boolean>) sService
+ .getTagIntentAppPreferenceForUser(userId);
+ return result;
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ return Collections.emptyMap();
+ }
+ }
+
+ /**
+ * Checks if the device supports Tag application preference.
+ *
+ * @return {@code true} if the device supports Tag application preference, {@code false}
+ * otherwise
+ * @throws UnsupportedOperationException if FEATURE_NFC is unavailable
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ public boolean isTagIntentAppPreferenceSupported() {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ try {
+ return sService.isTagIntentAppPreferenceSupported();
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ return false;
+ }
+ try {
+ return sService.isTagIntentAppPreferenceSupported();
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Sets NFC charging feature.
+ * <p>This API is for the Settings application.
+ * @return True if successful
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ public boolean enableWlc(boolean enable) {
+ if (!sHasNfcWlcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ try {
+ return sService.enableWlc(enable);
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ return false;
+ }
+ try {
+ return sService.enableWlc(enable);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Checks NFC charging feature is enabled.
+ *
+ * @return True if NFC charging is enabled, false otherwise
+ * @throws UnsupportedOperationException if FEATURE_NFC_CHARGING
+ * is unavailable
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+ public boolean isWlcEnabled() {
+ if (!sHasNfcWlcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ try {
+ return sService.isWlcEnabled();
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ return false;
+ }
+ try {
+ return sService.isWlcEnabled();
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ return false;
+ }
+ }
+
+ /**
+ * A listener to be invoked when NFC controller always on state changes.
+ * <p>Register your {@code ControllerAlwaysOnListener} implementation with {@link
+ * NfcAdapter#registerWlcStateListener} and disable it with {@link
+ * NfcAdapter#unregisterWlcStateListenerListener}.
+ * @see #registerWlcStateListener
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+ public interface WlcStateListener {
+ /**
+ * Called on NFC WLC state changes
+ */
+ void onWlcStateChanged(@NonNull WlcLDeviceInfo wlcLDeviceInfo);
+ }
+
+ /**
+ * Register a {@link WlcStateListener} to listen for NFC WLC state changes
+ * <p>The provided listener will be invoked by the given {@link Executor}.
+ *
+ * @param executor an {@link Executor} to execute given listener
+ * @param listener user implementation of the {@link WlcStateListener}
+ * @throws UnsupportedOperationException if FEATURE_NFC_CHARGING
+ * is unavailable
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+ public void registerWlcStateListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull WlcStateListener listener) {
+ if (!sHasNfcWlcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ mNfcWlcStateListener.register(executor, listener);
+ }
+
+ /**
+ * Unregister the specified {@link WlcStateListener}
+ * <p>The same {@link WlcStateListener} object used when calling
+ * {@link #registerWlcStateListener(Executor, WlcStateListener)}
+ * must be used.
+ *
+ * <p>Listeners are automatically unregistered when application process goes away
+ *
+ * @param listener user implementation of the {@link WlcStateListener}a
+ * @throws UnsupportedOperationException if FEATURE_NFC_CHARGING
+ * is unavailable
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+ public void unregisterWlcStateListener(
+ @NonNull WlcStateListener listener) {
+ if (!sHasNfcWlcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ mNfcWlcStateListener.unregister(listener);
+ }
+
+ /**
+ * Returns information on the NFC charging listener device
+ *
+ * @return Information on the NFC charging listener device
+ * @throws UnsupportedOperationException if FEATURE_NFC_CHARGING
+ * is unavailable
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+ @Nullable
+ public WlcLDeviceInfo getWlcLDeviceInfo() {
+ if (!sHasNfcWlcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ try {
+ return sService.getWlcLDeviceInfo();
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ return null;
+ }
+ try {
+ return sService.getWlcLDeviceInfo();
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ return null;
+ }
+ }
+}
diff --git a/nfc/java/android/nfc/NfcAntennaInfo.aidl b/nfc/java/android/nfc/NfcAntennaInfo.aidl
new file mode 100644
index 0000000..d5e79fc
--- /dev/null
+++ b/nfc/java/android/nfc/NfcAntennaInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2013 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;
+
+parcelable NfcAntennaInfo;
diff --git a/nfc/java/android/nfc/NfcAntennaInfo.java b/nfc/java/android/nfc/NfcAntennaInfo.java
new file mode 100644
index 0000000..b002ca2
--- /dev/null
+++ b/nfc/java/android/nfc/NfcAntennaInfo.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2015 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.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * Contains information on all available Nfc
+ * antennas on an Android device as well as information
+ * on the device itself in relation positioning of the
+ * antennas.
+ */
+public final class NfcAntennaInfo implements Parcelable {
+ // Width of the device in millimeters.
+ private final int mDeviceWidth;
+ // Height of the device in millimeters.
+ private final int mDeviceHeight;
+ // Whether the device is foldable.
+ private final boolean mDeviceFoldable;
+ // All available Nfc Antennas on the device.
+ private final List<AvailableNfcAntenna> mAvailableNfcAntennas;
+
+ public NfcAntennaInfo(int deviceWidth, int deviceHeight, boolean deviceFoldable,
+ @NonNull List<AvailableNfcAntenna> availableNfcAntennas) {
+ this.mDeviceWidth = deviceWidth;
+ this.mDeviceHeight = deviceHeight;
+ this.mDeviceFoldable = deviceFoldable;
+ this.mAvailableNfcAntennas = availableNfcAntennas;
+ }
+
+ /**
+ * Width of the device in millimeters.
+ */
+ public int getDeviceWidth() {
+ return mDeviceWidth;
+ }
+
+ /**
+ * Height of the device in millimeters.
+ */
+ public int getDeviceHeight() {
+ return mDeviceHeight;
+ }
+
+ /**
+ * Whether the device is foldable. When the device is foldable,
+ * the 0, 0 is considered to be bottom-left when the device is unfolded and
+ * the screens are facing the user. For non-foldable devices 0, 0
+ * is bottom-left when the user is facing the screen.
+ */
+ public boolean isDeviceFoldable() {
+ return mDeviceFoldable;
+ }
+
+ /**
+ * Get all NFC antennas that exist on the device.
+ */
+ @NonNull
+ public List<AvailableNfcAntenna> getAvailableNfcAntennas() {
+ return mAvailableNfcAntennas;
+ }
+
+ private NfcAntennaInfo(Parcel in) {
+ this.mDeviceWidth = in.readInt();
+ this.mDeviceHeight = in.readInt();
+ this.mDeviceFoldable = in.readByte() != 0;
+ this.mAvailableNfcAntennas = new ArrayList<>();
+ in.readTypedList(this.mAvailableNfcAntennas,
+ AvailableNfcAntenna.CREATOR);
+ }
+
+ public static final @NonNull Parcelable.Creator<NfcAntennaInfo> CREATOR =
+ new Parcelable.Creator<NfcAntennaInfo>() {
+ @Override
+ public NfcAntennaInfo createFromParcel(Parcel in) {
+ return new NfcAntennaInfo(in);
+ }
+
+ @Override
+ public NfcAntennaInfo[] newArray(int size) {
+ return new NfcAntennaInfo[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mDeviceWidth);
+ dest.writeInt(mDeviceHeight);
+ dest.writeByte((byte) (mDeviceFoldable ? 1 : 0));
+ dest.writeTypedList(mAvailableNfcAntennas, 0);
+ }
+}
diff --git a/nfc/java/android/nfc/NfcControllerAlwaysOnListener.java b/nfc/java/android/nfc/NfcControllerAlwaysOnListener.java
new file mode 100644
index 0000000..6ae58fd
--- /dev/null
+++ b/nfc/java/android/nfc/NfcControllerAlwaysOnListener.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2021 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.NonNull;
+import android.nfc.NfcAdapter.ControllerAlwaysOnListener;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * @hide
+ */
+public class NfcControllerAlwaysOnListener extends INfcControllerAlwaysOnListener.Stub {
+ private static final String TAG = NfcControllerAlwaysOnListener.class.getSimpleName();
+
+ private final INfcAdapter mAdapter;
+
+ private final Map<ControllerAlwaysOnListener, Executor> mListenerMap = new HashMap<>();
+
+ private boolean mCurrentState = false;
+ private boolean mIsRegistered = false;
+
+ public NfcControllerAlwaysOnListener(@NonNull INfcAdapter adapter) {
+ mAdapter = adapter;
+ }
+
+ /**
+ * Register a {@link ControllerAlwaysOnListener} with this
+ * {@link NfcControllerAlwaysOnListener}
+ *
+ * @param executor an {@link Executor} to execute given listener
+ * @param listener user implementation of the {@link ControllerAlwaysOnListener}
+ */
+ public void register(@NonNull Executor executor,
+ @NonNull ControllerAlwaysOnListener listener) {
+ try {
+ if (!mAdapter.isControllerAlwaysOnSupported()) {
+ return;
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to register");
+ return;
+ }
+ synchronized (this) {
+ if (mListenerMap.containsKey(listener)) {
+ return;
+ }
+
+ mListenerMap.put(listener, executor);
+ if (!mIsRegistered) {
+ try {
+ mAdapter.registerControllerAlwaysOnListener(this);
+ mIsRegistered = true;
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to register");
+ }
+ }
+ }
+ }
+
+ /**
+ * Unregister the specified {@link ControllerAlwaysOnListener}
+ *
+ * @param listener user implementation of the {@link ControllerAlwaysOnListener}
+ */
+ public void unregister(@NonNull ControllerAlwaysOnListener listener) {
+ try {
+ if (!mAdapter.isControllerAlwaysOnSupported()) {
+ return;
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to unregister");
+ return;
+ }
+ synchronized (this) {
+ if (!mListenerMap.containsKey(listener)) {
+ return;
+ }
+
+ mListenerMap.remove(listener);
+
+ if (mListenerMap.isEmpty() && mIsRegistered) {
+ try {
+ mAdapter.unregisterControllerAlwaysOnListener(this);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to unregister");
+ }
+ mIsRegistered = false;
+ }
+ }
+ }
+
+ private void sendCurrentState(@NonNull ControllerAlwaysOnListener listener) {
+ synchronized (this) {
+ Executor executor = mListenerMap.get(listener);
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> listener.onControllerAlwaysOnChanged(
+ mCurrentState));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ @Override
+ public void onControllerAlwaysOnChanged(boolean isEnabled) {
+ synchronized (this) {
+ mCurrentState = isEnabled;
+ for (ControllerAlwaysOnListener cb : mListenerMap.keySet()) {
+ sendCurrentState(cb);
+ }
+ }
+ }
+}
+
diff --git a/nfc/java/android/nfc/NfcEvent.java b/nfc/java/android/nfc/NfcEvent.java
new file mode 100644
index 0000000..aff4f52
--- /dev/null
+++ b/nfc/java/android/nfc/NfcEvent.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2011 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;
+
+/**
+ * Wraps information associated with any NFC event.
+ *
+ * <p>Immutable object, with direct access to the (final) fields.
+ *
+ * <p>An {@link NfcEvent} object is usually included in callbacks from
+ * {@link NfcAdapter}. Check the documentation of the callback to see
+ * which fields may be set.
+ *
+ * <p>This wrapper object is used (instead of parameters
+ * in the callback) because it allows new fields to be added without breaking
+ * API compatibility.
+ *
+ * @see NfcAdapter.OnNdefPushCompleteCallback#onNdefPushComplete
+ * @see NfcAdapter.CreateNdefMessageCallback#createNdefMessage
+ */
+public final class NfcEvent {
+ /**
+ * The {@link NfcAdapter} associated with the NFC event.
+ */
+ public final NfcAdapter nfcAdapter;
+
+ /**
+ * The major LLCP version number of the peer associated with the NFC event.
+ */
+ public final int peerLlcpMajorVersion;
+
+ /**
+ * The minor LLCP version number of the peer associated with the NFC event.
+ */
+ public final int peerLlcpMinorVersion;
+
+ NfcEvent(NfcAdapter nfcAdapter, byte peerLlcpVersion) {
+ this.nfcAdapter = nfcAdapter;
+ this.peerLlcpMajorVersion = (peerLlcpVersion & 0xF0) >> 4;
+ this.peerLlcpMinorVersion = peerLlcpVersion & 0x0F;
+ }
+}
diff --git a/nfc/java/android/nfc/NfcFrameworkInitializer.java b/nfc/java/android/nfc/NfcFrameworkInitializer.java
new file mode 100644
index 0000000..1ab8a1e
--- /dev/null
+++ b/nfc/java/android/nfc/NfcFrameworkInitializer.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 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.NonNull;
+import android.annotation.SystemApi;
+import android.app.SystemServiceRegistry;
+import android.content.Context;
+
+/**
+ * Class for performing registration for Nfc service.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public class NfcFrameworkInitializer {
+ private NfcFrameworkInitializer() {}
+
+ private static volatile NfcServiceManager sNfcServiceManager;
+
+ /**
+ * Sets an instance of {@link NfcServiceManager} that allows
+ * the nfc mainline module to register/obtain nfc binder services. This is called
+ * by the platform during the system initialization.
+ *
+ * @param nfcServiceManager instance of {@link NfcServiceManager} that allows
+ * the nfc mainline module to register/obtain nfcd binder services.
+ */
+ public static void setNfcServiceManager(
+ @NonNull NfcServiceManager nfcServiceManager) {
+ if (sNfcServiceManager != null) {
+ throw new IllegalStateException("setNfcServiceManager called twice!");
+ }
+
+ if (nfcServiceManager == null) {
+ throw new IllegalArgumentException("nfcServiceManager must not be null");
+ }
+
+ sNfcServiceManager = nfcServiceManager;
+ }
+
+ /** @hide */
+ public static NfcServiceManager getNfcServiceManager() {
+ return sNfcServiceManager;
+ }
+
+ /**
+ * Called by {@link SystemServiceRegistry}'s static initializer and registers NFC service
+ * to {@link Context}, so that {@link Context#getSystemService} can return them.
+ *
+ * @throws IllegalStateException if this is called from anywhere besides
+ * {@link SystemServiceRegistry}
+ */
+ public static void registerServiceWrappers() {
+ SystemServiceRegistry.registerContextAwareService(Context.NFC_SERVICE,
+ NfcManager.class, context -> new NfcManager(context));
+ }
+}
diff --git a/nfc/java/android/nfc/NfcManager.java b/nfc/java/android/nfc/NfcManager.java
new file mode 100644
index 0000000..644e312
--- /dev/null
+++ b/nfc/java/android/nfc/NfcManager.java
@@ -0,0 +1,73 @@
+/*
+ * 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.SystemService;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.os.Build;
+
+/**
+ * High level manager used to obtain an instance of an {@link NfcAdapter}.
+ * <p>
+ * Use {@link android.content.Context#getSystemService(java.lang.String)}
+ * with {@link Context#NFC_SERVICE} to create an {@link NfcManager},
+ * then call {@link #getDefaultAdapter} to obtain the {@link NfcAdapter}.
+ * <p>
+ * Alternately, you can just call the static helper
+ * {@link NfcAdapter#getDefaultAdapter(android.content.Context)}.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about using NFC, read the
+ * <a href="{@docRoot}guide/topics/nfc/index.html">Near Field Communication</a> developer guide.</p>
+ * </div>
+ *
+ * @see NfcAdapter#getDefaultAdapter(android.content.Context)
+ */
+@SystemService(Context.NFC_SERVICE)
+public final class NfcManager {
+ private final NfcAdapter mAdapter;
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public NfcManager(Context context) {
+ NfcAdapter adapter;
+ context = context.getApplicationContext();
+ if (context == null) {
+ throw new IllegalArgumentException(
+ "context not associated with any application (using a mock context?)");
+ }
+ try {
+ adapter = NfcAdapter.getNfcAdapter(context);
+ } catch (UnsupportedOperationException e) {
+ adapter = null;
+ }
+ mAdapter = adapter;
+ }
+
+ /**
+ * Get the default NFC Adapter for this device.
+ *
+ * @return the default NFC Adapter
+ */
+ public NfcAdapter getDefaultAdapter() {
+ return mAdapter;
+ }
+}
diff --git a/nfc/java/android/nfc/NfcServiceManager.java b/nfc/java/android/nfc/NfcServiceManager.java
new file mode 100644
index 0000000..5582f11
--- /dev/null
+++ b/nfc/java/android/nfc/NfcServiceManager.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+
+/**********************************************************************
+ * This file is not a part of the NFC mainline modure *
+ * *******************************************************************/
+
+package android.nfc;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.SystemApi.Client;
+import android.content.Context;
+import android.os.IBinder;
+import android.os.ServiceManager;
+
+/**
+ * Provides a way to register and obtain the system service binder objects managed by the nfc
+ * service.
+ *
+ * @hide
+ */
+@SystemApi(client = Client.MODULE_LIBRARIES)
+public class NfcServiceManager {
+
+ /**
+ * @hide
+ */
+ public NfcServiceManager() {
+ }
+
+ /**
+ * A class that exposes the methods to register and obtain each system service.
+ */
+ public static final class ServiceRegisterer {
+ private final String mServiceName;
+
+ /**
+ * @hide
+ */
+ public ServiceRegisterer(String serviceName) {
+ mServiceName = serviceName;
+ }
+
+ /**
+ * Register a system server binding object for a service.
+ */
+ public void register(@NonNull IBinder service) {
+ ServiceManager.addService(mServiceName, service);
+ }
+
+ /**
+ * Get the system server binding object for a service.
+ *
+ * <p>This blocks until the service instance is ready,
+ * or a timeout happens, in which case it returns null.
+ */
+ @Nullable
+ public IBinder get() {
+ return ServiceManager.getService(mServiceName);
+ }
+
+ /**
+ * Get the system server binding object for a service.
+ *
+ * <p>This blocks until the service instance is ready,
+ * or a timeout happens, in which case it throws {@link ServiceNotFoundException}.
+ */
+ @NonNull
+ public IBinder getOrThrow() throws ServiceNotFoundException {
+ try {
+ return ServiceManager.getServiceOrThrow(mServiceName);
+ } catch (ServiceManager.ServiceNotFoundException e) {
+ throw new ServiceNotFoundException(mServiceName);
+ }
+ }
+
+ /**
+ * Get the system server binding object for a service. If the specified service is
+ * not available, it returns null.
+ */
+ @Nullable
+ public IBinder tryGet() {
+ return ServiceManager.checkService(mServiceName);
+ }
+ }
+
+ /**
+ * See {@link ServiceRegisterer#getOrThrow}.
+ *
+ */
+ public static class ServiceNotFoundException extends ServiceManager.ServiceNotFoundException {
+ /**
+ * Constructor.
+ *
+ * @param name the name of the binder service that cannot be found.
+ *
+ */
+ public ServiceNotFoundException(@NonNull String name) {
+ super(name);
+ }
+ }
+
+ /**
+ * Returns {@link ServiceRegisterer} for the "nfc" service.
+ */
+ @NonNull
+ public ServiceRegisterer getNfcManagerServiceRegisterer() {
+ return new ServiceRegisterer(Context.NFC_SERVICE);
+ }
+}
diff --git a/nfc/java/android/nfc/NfcWlcStateListener.java b/nfc/java/android/nfc/NfcWlcStateListener.java
new file mode 100644
index 0000000..8d79310
--- /dev/null
+++ b/nfc/java/android/nfc/NfcWlcStateListener.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2023 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.NonNull;
+import android.nfc.NfcAdapter.WlcStateListener;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * @hide
+ */
+public class NfcWlcStateListener extends INfcWlcStateListener.Stub {
+ private static final String TAG = NfcWlcStateListener.class.getSimpleName();
+
+ private final INfcAdapter mAdapter;
+
+ private final Map<WlcStateListener, Executor> mListenerMap = new HashMap<>();
+
+ private WlcLDeviceInfo mCurrentState = null;
+ private boolean mIsRegistered = false;
+
+ public NfcWlcStateListener(@NonNull INfcAdapter adapter) {
+ mAdapter = adapter;
+ }
+
+ /**
+ * Register a {@link WlcStateListener} with this
+ * {@link WlcStateListener}
+ *
+ * @param executor an {@link Executor} to execute given listener
+ * @param listener user implementation of the {@link WlcStateListener}
+ */
+ public void register(@NonNull Executor executor, @NonNull WlcStateListener listener) {
+ synchronized (this) {
+ if (mListenerMap.containsKey(listener)) {
+ return;
+ }
+
+ mListenerMap.put(listener, executor);
+
+ if (!mIsRegistered) {
+ try {
+ mAdapter.registerWlcStateListener(this);
+ mIsRegistered = true;
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to register");
+ }
+ }
+ }
+ }
+
+ /**
+ * Unregister the specified {@link WlcStateListener}
+ *
+ * @param listener user implementation of the {@link WlcStateListener}
+ */
+ public void unregister(@NonNull WlcStateListener listener) {
+ synchronized (this) {
+ if (!mListenerMap.containsKey(listener)) {
+ return;
+ }
+
+ mListenerMap.remove(listener);
+
+ if (mListenerMap.isEmpty() && mIsRegistered) {
+ try {
+ mAdapter.unregisterWlcStateListener(this);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to unregister");
+ }
+ mIsRegistered = false;
+ }
+ }
+ }
+
+ private void sendCurrentState(@NonNull WlcStateListener listener) {
+ synchronized (this) {
+ Executor executor = mListenerMap.get(listener);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> listener.onWlcStateChanged(
+ mCurrentState));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ @Override
+ public void onWlcStateChanged(@NonNull WlcLDeviceInfo wlcLDeviceInfo) {
+ synchronized (this) {
+ mCurrentState = wlcLDeviceInfo;
+
+ for (WlcStateListener cb : mListenerMap.keySet()) {
+ sendCurrentState(cb);
+ }
+ }
+ }
+}
+
diff --git a/nfc/java/android/nfc/Tag.aidl b/nfc/java/android/nfc/Tag.aidl
new file mode 100644
index 0000000..312261e
--- /dev/null
+++ b/nfc/java/android/nfc/Tag.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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;
+
+parcelable Tag;
\ No newline at end of file
diff --git a/nfc/java/android/nfc/Tag.java b/nfc/java/android/nfc/Tag.java
new file mode 100644
index 0000000..500038f
--- /dev/null
+++ b/nfc/java/android/nfc/Tag.java
@@ -0,0 +1,508 @@
+/*
+ * 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.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.nfc.tech.IsoDep;
+import android.nfc.tech.MifareClassic;
+import android.nfc.tech.MifareUltralight;
+import android.nfc.tech.Ndef;
+import android.nfc.tech.NdefFormatable;
+import android.nfc.tech.NfcA;
+import android.nfc.tech.NfcB;
+import android.nfc.tech.NfcBarcode;
+import android.nfc.tech.NfcF;
+import android.nfc.tech.NfcV;
+import android.nfc.tech.TagTechnology;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+
+/**
+ * Represents an NFC tag that has been discovered.
+ * <p>
+ * {@link Tag} is an immutable object that represents the state of a NFC tag at
+ * the time of discovery. It can be used as a handle to {@link TagTechnology} classes
+ * to perform advanced operations, or directly queried for its ID via {@link #getId} and the
+ * set of technologies it contains via {@link #getTechList}. Arrays passed to and
+ * returned by this class are <em>not</em> cloned, so be careful not to modify them.
+ * <p>
+ * A new tag object is created every time a tag is discovered (comes into range), even
+ * if it is the same physical tag. If a tag is removed and then returned into range, then
+ * only the most recent tag object can be successfully used to create a {@link TagTechnology}.
+ *
+ * <h3>Tag Dispatch</h3>
+ * When a tag is discovered, a {@link Tag} object is created and passed to a
+ * single activity via the {@link NfcAdapter#EXTRA_TAG} extra in an
+ * {@link android.content.Intent} via {@link Context#startActivity}. A four stage dispatch is used
+ * to select the
+ * most appropriate activity to handle the tag. The Android OS executes each stage in order,
+ * and completes dispatch as soon as a single matching activity is found. If there are multiple
+ * matching activities found at any one stage then the Android activity chooser dialog is shown
+ * to allow the user to select the activity to receive the tag.
+ *
+ * <p>The Tag dispatch mechanism was designed to give a high probability of dispatching
+ * a tag to the correct activity without showing the user an activity chooser dialog.
+ * This is important for NFC interactions because they are very transient -- if a user has to
+ * move the Android device to choose an application then the connection will likely be broken.
+ *
+ * <h4>1. Foreground activity dispatch</h4>
+ * A foreground activity that has called
+ * {@link NfcAdapter#enableForegroundDispatch NfcAdapter.enableForegroundDispatch()} is
+ * given priority. See the documentation on
+ * {@link NfcAdapter#enableForegroundDispatch NfcAdapter.enableForegroundDispatch()} for
+ * its usage.
+ * <h4>2. NDEF data dispatch</h4>
+ * If the tag contains NDEF data the system inspects the first {@link NdefRecord} in the first
+ * {@link NdefMessage}. If the record is a URI, SmartPoster, or MIME data
+ * {@link Context#startActivity} is called with {@link NfcAdapter#ACTION_NDEF_DISCOVERED}. For URI
+ * and SmartPoster records the URI is put into the intent's data field. For MIME records the MIME
+ * type is put in the intent's type field. This allows activities to register to be launched only
+ * when data they know how to handle is present on a tag. This is the preferred method of handling
+ * data on a tag since NDEF data can be stored on many types of tags and doesn't depend on a
+ * specific tag technology.
+ * See {@link NfcAdapter#ACTION_NDEF_DISCOVERED} for more detail. If the tag does not contain
+ * NDEF data, or if no activity is registered
+ * for {@link NfcAdapter#ACTION_NDEF_DISCOVERED} with a matching data URI or MIME type then dispatch
+ * moves to stage 3.
+ * <h4>3. Tag Technology dispatch</h4>
+ * {@link Context#startActivity} is called with {@link NfcAdapter#ACTION_TECH_DISCOVERED} to
+ * dispatch the tag to an activity that can handle the technologies present on the tag.
+ * Technologies are defined as sub-classes of {@link TagTechnology}, see the package
+ * {@link android.nfc.tech}. The Android OS looks for an activity that can handle one or
+ * more technologies in the tag. See {@link NfcAdapter#ACTION_TECH_DISCOVERED} for more detail.
+ * <h4>4. Fall-back dispatch</h4>
+ * If no activity has been matched then {@link Context#startActivity} is called with
+ * {@link NfcAdapter#ACTION_TAG_DISCOVERED}. This is intended as a fall-back mechanism.
+ * See {@link NfcAdapter#ACTION_TAG_DISCOVERED}.
+ *
+ * <h3>NFC Tag Background</h3>
+ * An NFC tag is a passive NFC device, powered by the NFC field of this Android device while
+ * it is in range. Tag's can come in many forms, such as stickers, cards, key fobs, or
+ * even embedded in a more sophisticated device.
+ * <p>
+ * Tags can have a wide range of capabilities. Simple tags just offer read/write semantics,
+ * and contain some one time
+ * programmable areas to make read-only. More complex tags offer math operations
+ * and per-sector access control and authentication. The most sophisticated tags
+ * contain operating environments allowing complex interactions with the
+ * code executing on the tag. Use {@link TagTechnology} classes to access a broad
+ * range of capabilities available in NFC tags.
+ * <p>
+ */
+public final class Tag implements Parcelable {
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ final byte[] mId;
+ final int[] mTechList;
+ final String[] mTechStringList;
+ final Bundle[] mTechExtras;
+ final int mServiceHandle; // for use by NFC service, 0 indicates a mock
+ final long mCookie; // for accessibility checking
+ final INfcTag mTagService; // interface to NFC service, will be null if mock tag
+
+ int mConnectedTechnology;
+
+ /**
+ * Hidden constructor to be used by NFC service and internal classes.
+ * @hide
+ */
+ public Tag(byte[] id, int[] techList, Bundle[] techListExtras, int serviceHandle,
+ long cookie, INfcTag tagService) {
+ if (techList == null) {
+ throw new IllegalArgumentException("rawTargets cannot be null");
+ }
+ mId = id;
+ mTechList = Arrays.copyOf(techList, techList.length);
+ mTechStringList = generateTechStringList(techList);
+ // Ensure mTechExtras is as long as mTechList
+ mTechExtras = Arrays.copyOf(techListExtras, techList.length);
+ mServiceHandle = serviceHandle;
+ mCookie = cookie;
+ mTagService = tagService;
+ mConnectedTechnology = -1;
+
+ if (tagService == null) {
+ return;
+ }
+ }
+
+ /**
+ * Construct a mock Tag.
+ * <p>This is an application constructed tag, so NfcAdapter methods on this Tag may fail
+ * with {@link IllegalArgumentException} since it does not represent a physical Tag.
+ * <p>This constructor might be useful for mock testing.
+ * @param id The tag identifier, can be null
+ * @param techList must not be null
+ * @return freshly constructed tag
+ * @hide
+ */
+ public static Tag createMockTag(byte[] id, int[] techList, Bundle[] techListExtras,
+ long cookie) {
+ // set serviceHandle to 0 and tagService to null to indicate mock tag
+ return new Tag(id, techList, techListExtras, 0, cookie, null);
+ }
+
+ private String[] generateTechStringList(int[] techList) {
+ final int size = techList.length;
+ String[] strings = new String[size];
+ for (int i = 0; i < size; i++) {
+ switch (techList[i]) {
+ case TagTechnology.ISO_DEP:
+ strings[i] = IsoDep.class.getName();
+ break;
+ case TagTechnology.MIFARE_CLASSIC:
+ strings[i] = MifareClassic.class.getName();
+ break;
+ case TagTechnology.MIFARE_ULTRALIGHT:
+ strings[i] = MifareUltralight.class.getName();
+ break;
+ case TagTechnology.NDEF:
+ strings[i] = Ndef.class.getName();
+ break;
+ case TagTechnology.NDEF_FORMATABLE:
+ strings[i] = NdefFormatable.class.getName();
+ break;
+ case TagTechnology.NFC_A:
+ strings[i] = NfcA.class.getName();
+ break;
+ case TagTechnology.NFC_B:
+ strings[i] = NfcB.class.getName();
+ break;
+ case TagTechnology.NFC_F:
+ strings[i] = NfcF.class.getName();
+ break;
+ case TagTechnology.NFC_V:
+ strings[i] = NfcV.class.getName();
+ break;
+ case TagTechnology.NFC_BARCODE:
+ strings[i] = NfcBarcode.class.getName();
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown tech type " + techList[i]);
+ }
+ }
+ return strings;
+ }
+
+ static int[] getTechCodesFromStrings(String[] techStringList) throws IllegalArgumentException {
+ if (techStringList == null) {
+ throw new IllegalArgumentException("List cannot be null");
+ }
+ int[] techIntList = new int[techStringList.length];
+ HashMap<String, Integer> stringToCodeMap = getTechStringToCodeMap();
+ for (int i = 0; i < techStringList.length; i++) {
+ Integer code = stringToCodeMap.get(techStringList[i]);
+
+ if (code == null) {
+ throw new IllegalArgumentException("Unknown tech type " + techStringList[i]);
+ }
+
+ techIntList[i] = code.intValue();
+ }
+ return techIntList;
+ }
+
+ private static HashMap<String, Integer> getTechStringToCodeMap() {
+ HashMap<String, Integer> techStringToCodeMap = new HashMap<String, Integer>();
+
+ techStringToCodeMap.put(IsoDep.class.getName(), TagTechnology.ISO_DEP);
+ techStringToCodeMap.put(MifareClassic.class.getName(), TagTechnology.MIFARE_CLASSIC);
+ techStringToCodeMap.put(MifareUltralight.class.getName(), TagTechnology.MIFARE_ULTRALIGHT);
+ techStringToCodeMap.put(Ndef.class.getName(), TagTechnology.NDEF);
+ techStringToCodeMap.put(NdefFormatable.class.getName(), TagTechnology.NDEF_FORMATABLE);
+ techStringToCodeMap.put(NfcA.class.getName(), TagTechnology.NFC_A);
+ techStringToCodeMap.put(NfcB.class.getName(), TagTechnology.NFC_B);
+ techStringToCodeMap.put(NfcF.class.getName(), TagTechnology.NFC_F);
+ techStringToCodeMap.put(NfcV.class.getName(), TagTechnology.NFC_V);
+ techStringToCodeMap.put(NfcBarcode.class.getName(), TagTechnology.NFC_BARCODE);
+
+ return techStringToCodeMap;
+ }
+
+ /**
+ * For use by NfcService only.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public int getServiceHandle() {
+ return mServiceHandle;
+ }
+
+ /**
+ * For use by NfcService only.
+ * @hide
+ */
+ public int[] getTechCodeList() {
+ return mTechList;
+ }
+
+ /**
+ * Get the Tag Identifier (if it has one).
+ * <p>The tag identifier is a low level serial number, used for anti-collision
+ * and identification.
+ * <p> Most tags have a stable unique identifier
+ * (UID), but some tags will generate a random ID every time they are discovered
+ * (RID), and there are some tags with no ID at all (the byte array will be zero-sized).
+ * <p> The size and format of an ID is specific to the RF technology used by the tag.
+ * <p> This function retrieves the ID as determined at discovery time, and does not
+ * perform any further RF communication or block.
+ * @return ID as byte array, never null
+ */
+ public byte[] getId() {
+ return mId;
+ }
+
+ /**
+ * Get the technologies available in this tag, as fully qualified class names.
+ * <p>
+ * A technology is an implementation of the {@link TagTechnology} interface,
+ * and can be instantiated by calling the static <code>get(Tag)</code>
+ * method on the implementation with this Tag. The {@link TagTechnology}
+ * object can then be used to perform advanced, technology-specific operations on a tag.
+ * <p>
+ * Android defines a mandatory set of technologies that must be correctly
+ * enumerated by all Android NFC devices, and an optional
+ * set of proprietary technologies.
+ * See {@link TagTechnology} for more details.
+ * <p>
+ * The ordering of the returned array is undefined and should not be relied upon.
+ * @return an array of fully-qualified {@link TagTechnology} class-names.
+ */
+ public String[] getTechList() {
+ return mTechStringList;
+ }
+
+ /**
+ * Rediscover the technologies available on this tag.
+ * <p>
+ * The technologies that are available on a tag may change due to
+ * operations being performed on a tag. For example, formatting a
+ * tag as NDEF adds the {@link Ndef} technology. The {@link rediscover}
+ * method reenumerates the available technologies on the tag
+ * and returns a new {@link Tag} object containing these technologies.
+ * <p>
+ * You may not be connected to any of this {@link Tag}'s technologies
+ * when calling this method.
+ * This method guarantees that you will be returned the same Tag
+ * if it is still in the field.
+ * <p>May cause RF activity and may block. Must not be called
+ * from the main application thread. A blocked call will be canceled with
+ * {@link IOException} by calling {@link #close} from another thread.
+ * <p>Does not remove power from the RF field, so a tag having a random
+ * ID should not change its ID.
+ * @return the rediscovered tag object.
+ * @throws IOException if the tag cannot be rediscovered
+ * @hide
+ */
+ // TODO See if we need TagLostException
+ // TODO Unhide for ICS
+ // TODO Update documentation to make sure it matches with the final
+ // implementation.
+ public Tag rediscover() throws IOException {
+ if (getConnectedTechnology() != -1) {
+ throw new IllegalStateException("Close connection to the technology first!");
+ }
+
+ if (mTagService == null) {
+ throw new IOException("Mock tags don't support this operation.");
+ }
+ try {
+ Tag newTag = mTagService.rediscover(getServiceHandle());
+ if (newTag != null) {
+ return newTag;
+ } else {
+ throw new IOException("Failed to rediscover tag");
+ }
+ } catch (RemoteException e) {
+ throw new IOException("NFC service dead");
+ }
+ }
+
+
+ /** @hide */
+ public boolean hasTech(int techType) {
+ for (int tech : mTechList) {
+ if (tech == techType) return true;
+ }
+ return false;
+ }
+
+ /** @hide */
+ public Bundle getTechExtras(int tech) {
+ int pos = -1;
+ for (int idx = 0; idx < mTechList.length; idx++) {
+ if (mTechList[idx] == tech) {
+ pos = idx;
+ break;
+ }
+ }
+ if (pos < 0) {
+ return null;
+ }
+
+ return mTechExtras[pos];
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public INfcTag getTagService() {
+ if (mTagService == null) {
+ return null;
+ }
+
+ try {
+ if (!mTagService.isTagUpToDate(mCookie)) {
+ String id_str = "";
+ for (int i = 0; i < mId.length; i++) {
+ id_str = id_str + String.format("%02X ", mId[i]);
+ }
+ String msg = "Permission Denial: Tag ( ID: " + id_str + ") is out of date";
+ throw new SecurityException(msg);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ return mTagService;
+ }
+
+ /**
+ * Human-readable description of the tag, for debugging.
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("TAG: Tech [");
+ String[] techList = getTechList();
+ int length = techList.length;
+ for (int i = 0; i < length; i++) {
+ sb.append(techList[i]);
+ if (i < length - 1) {
+ sb.append(", ");
+ }
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+
+ /*package*/ static byte[] readBytesWithNull(Parcel in) {
+ int len = in.readInt();
+ byte[] result = null;
+ if (len >= 0) {
+ result = new byte[len];
+ in.readByteArray(result);
+ }
+ return result;
+ }
+
+ /*package*/ static void writeBytesWithNull(Parcel out, byte[] b) {
+ if (b == null) {
+ out.writeInt(-1);
+ return;
+ }
+ out.writeInt(b.length);
+ out.writeByteArray(b);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ // Null mTagService means this is a mock tag
+ int isMock = (mTagService == null)?1:0;
+
+ writeBytesWithNull(dest, mId);
+ dest.writeInt(mTechList.length);
+ dest.writeIntArray(mTechList);
+ dest.writeTypedArray(mTechExtras, 0);
+ dest.writeInt(mServiceHandle);
+ dest.writeLong(mCookie);
+ dest.writeInt(isMock);
+ if (isMock == 0) {
+ dest.writeStrongBinder(mTagService.asBinder());
+ }
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<Tag> CREATOR =
+ new Parcelable.Creator<Tag>() {
+ @Override
+ public Tag createFromParcel(Parcel in) {
+ INfcTag tagService;
+
+ // Tag fields
+ byte[] id = Tag.readBytesWithNull(in);
+ int[] techList = new int[in.readInt()];
+ in.readIntArray(techList);
+ Bundle[] techExtras = in.createTypedArray(Bundle.CREATOR);
+ int serviceHandle = in.readInt();
+ long cookie = in.readLong();
+ int isMock = in.readInt();
+ if (isMock == 0) {
+ tagService = INfcTag.Stub.asInterface(in.readStrongBinder());
+ }
+ else {
+ tagService = null;
+ }
+
+ return new Tag(id, techList, techExtras, serviceHandle, cookie, tagService);
+ }
+
+ @Override
+ public Tag[] newArray(int size) {
+ return new Tag[size];
+ }
+ };
+
+ /**
+ * For internal use only.
+ *
+ * @hide
+ */
+ public synchronized boolean setConnectedTechnology(int technology) {
+ if (mConnectedTechnology != -1) {
+ return false;
+ }
+ mConnectedTechnology = technology;
+ return true;
+ }
+
+ /**
+ * For internal use only.
+ *
+ * @hide
+ */
+ public int getConnectedTechnology() {
+ return mConnectedTechnology;
+ }
+
+ /**
+ * For internal use only.
+ *
+ * @hide
+ */
+ public void setTechnologyDisconnected() {
+ mConnectedTechnology = -1;
+ }
+}
diff --git a/nfc/java/android/nfc/TagLostException.java b/nfc/java/android/nfc/TagLostException.java
new file mode 100644
index 0000000..1981d7c
--- /dev/null
+++ b/nfc/java/android/nfc/TagLostException.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2011, 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 java.io.IOException;
+
+public class TagLostException extends IOException {
+ public TagLostException() {
+ super();
+ }
+
+ public TagLostException(String message) {
+ super(message);
+ }
+}
diff --git a/nfc/java/android/nfc/TechListParcel.aidl b/nfc/java/android/nfc/TechListParcel.aidl
new file mode 100644
index 0000000..92e646f
--- /dev/null
+++ b/nfc/java/android/nfc/TechListParcel.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2011 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;
+
+parcelable TechListParcel;
\ No newline at end of file
diff --git a/nfc/java/android/nfc/TechListParcel.java b/nfc/java/android/nfc/TechListParcel.java
new file mode 100644
index 0000000..9f01559
--- /dev/null
+++ b/nfc/java/android/nfc/TechListParcel.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2011 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.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class TechListParcel implements Parcelable {
+
+ private String[][] mTechLists;
+
+ public TechListParcel(String[]... strings) {
+ mTechLists = strings;
+ }
+
+ public String[][] getTechLists() {
+ return mTechLists;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ int count = mTechLists.length;
+ dest.writeInt(count);
+ for (int i = 0; i < count; i++) {
+ String[] techList = mTechLists[i];
+ dest.writeStringArray(techList);
+ }
+ }
+
+ public static final @android.annotation.NonNull Creator<TechListParcel> CREATOR = new Creator<TechListParcel>() {
+ @Override
+ public TechListParcel createFromParcel(Parcel source) {
+ int count = source.readInt();
+ String[][] techLists = new String[count][];
+ for (int i = 0; i < count; i++) {
+ techLists[i] = source.createStringArray();
+ }
+ return new TechListParcel(techLists);
+ }
+
+ @Override
+ public TechListParcel[] newArray(int size) {
+ return new TechListParcel[size];
+ }
+ };
+}
diff --git a/nfc/java/android/nfc/TransceiveResult.aidl b/nfc/java/android/nfc/TransceiveResult.aidl
new file mode 100644
index 0000000..98f92ee
--- /dev/null
+++ b/nfc/java/android/nfc/TransceiveResult.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2011 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;
+
+parcelable TransceiveResult;
diff --git a/nfc/java/android/nfc/TransceiveResult.java b/nfc/java/android/nfc/TransceiveResult.java
new file mode 100644
index 0000000..7992094
--- /dev/null
+++ b/nfc/java/android/nfc/TransceiveResult.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2011, 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.os.Parcel;
+import android.os.Parcelable;
+
+import java.io.IOException;
+
+/**
+ * Class used to pipe transceive result from the NFC service.
+ *
+ * @hide
+ */
+public final class TransceiveResult implements Parcelable {
+ public static final int RESULT_SUCCESS = 0;
+ public static final int RESULT_FAILURE = 1;
+ public static final int RESULT_TAGLOST = 2;
+ public static final int RESULT_EXCEEDED_LENGTH = 3;
+
+ final int mResult;
+ final byte[] mResponseData;
+
+ public TransceiveResult(final int result, final byte[] data) {
+ mResult = result;
+ mResponseData = data;
+ }
+
+ public byte[] getResponseOrThrow() throws IOException {
+ switch (mResult) {
+ case RESULT_SUCCESS:
+ return mResponseData;
+ case RESULT_TAGLOST:
+ throw new TagLostException("Tag was lost.");
+ case RESULT_EXCEEDED_LENGTH:
+ throw new IOException("Transceive length exceeds supported maximum");
+ default:
+ throw new IOException("Transceive failed");
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mResult);
+ if (mResult == RESULT_SUCCESS) {
+ dest.writeInt(mResponseData.length);
+ dest.writeByteArray(mResponseData);
+ }
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<TransceiveResult> CREATOR =
+ new Parcelable.Creator<TransceiveResult>() {
+ @Override
+ public TransceiveResult createFromParcel(Parcel in) {
+ int result = in.readInt();
+ byte[] responseData;
+
+ if (result == RESULT_SUCCESS) {
+ int responseLength = in.readInt();
+ responseData = new byte[responseLength];
+ in.readByteArray(responseData);
+ } else {
+ responseData = null;
+ }
+ return new TransceiveResult(result, responseData);
+ }
+
+ @Override
+ public TransceiveResult[] newArray(int size) {
+ return new TransceiveResult[size];
+ }
+ };
+
+}
diff --git a/nfc/java/android/nfc/WlcLDeviceInfo.aidl b/nfc/java/android/nfc/WlcLDeviceInfo.aidl
new file mode 100644
index 0000000..33143fe
--- /dev/null
+++ b/nfc/java/android/nfc/WlcLDeviceInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2023 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;
+
+parcelable WlcLDeviceInfo;
diff --git a/nfc/java/android/nfc/WlcLDeviceInfo.java b/nfc/java/android/nfc/WlcLDeviceInfo.java
new file mode 100644
index 0000000..016431e
--- /dev/null
+++ b/nfc/java/android/nfc/WlcLDeviceInfo.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2023 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.FlaggedApi;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Contains information of the nfc wireless charging listener device information.
+ */
+@FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+public final class WlcLDeviceInfo implements Parcelable {
+ public static final int DISCONNECTED = 1;
+
+ public static final int CONNECTED_CHARGING = 2;
+
+ public static final int CONNECTED_DISCHARGING = 3;
+
+ private double mProductId;
+ private double mTemperature;
+ private double mBatteryLevel;
+ private int mState;
+
+ public WlcLDeviceInfo(double productId, double temperature, double batteryLevel, int state) {
+ this.mProductId = productId;
+ this.mTemperature = temperature;
+ this.mBatteryLevel = batteryLevel;
+ this.mState = state;
+ }
+
+ /**
+ * ProductId of the WLC listener device.
+ */
+ public double getProductId() {
+ return mProductId;
+ }
+
+ /**
+ * Temperature of the WLC listener device.
+ */
+ public double getTemperature() {
+ return mTemperature;
+ }
+
+ /**
+ * BatteryLevel of the WLC listener device.
+ */
+ public double getBatteryLevel() {
+ return mBatteryLevel;
+ }
+
+ /**
+ * State of the WLC listener device.
+ */
+ public int getState() {
+ return mState;
+ }
+
+ private WlcLDeviceInfo(Parcel in) {
+ this.mProductId = in.readDouble();
+ this.mTemperature = in.readDouble();
+ this.mBatteryLevel = in.readDouble();
+ this.mState = in.readInt();
+ }
+
+ public static final @NonNull Parcelable.Creator<WlcLDeviceInfo> CREATOR =
+ new Parcelable.Creator<WlcLDeviceInfo>() {
+ @Override
+ public WlcLDeviceInfo createFromParcel(Parcel in) {
+ return new WlcLDeviceInfo(in);
+ }
+
+ @Override
+ public WlcLDeviceInfo[] newArray(int size) {
+ return new WlcLDeviceInfo[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeDouble(mProductId);
+ dest.writeDouble(mTemperature);
+ dest.writeDouble(mBatteryLevel);
+ dest.writeDouble(mState);
+ }
+}
diff --git a/nfc/java/android/nfc/cardemulation/AidGroup.aidl b/nfc/java/android/nfc/cardemulation/AidGroup.aidl
new file mode 100644
index 0000000..56d6fa5
--- /dev/null
+++ b/nfc/java/android/nfc/cardemulation/AidGroup.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2013 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.cardemulation;
+
+parcelable AidGroup;
diff --git a/nfc/java/android/nfc/cardemulation/AidGroup.java b/nfc/java/android/nfc/cardemulation/AidGroup.java
new file mode 100644
index 0000000..ae3e333
--- /dev/null
+++ b/nfc/java/android/nfc/cardemulation/AidGroup.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2015 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.cardemulation;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.nfc.Flags;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Pattern;
+
+/**********************************************************************
+ * This file is not a part of the NFC mainline module *
+ * *******************************************************************/
+
+/**
+ * The AidGroup class represents a group of Application Identifiers (AIDs).
+ *
+ * <p>The format of AIDs is defined in the ISO/IEC 7816-4 specification. This class
+ * requires the AIDs to be input as a hexadecimal string, with an even amount of
+ * hexadecimal characters, e.g. "F014811481".
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+public final class AidGroup implements Parcelable {
+ /**
+ * The maximum number of AIDs that can be present in any one group.
+ */
+ private static final int MAX_NUM_AIDS = 256;
+
+ private static final String TAG = "AidGroup";
+
+
+ private final List<String> mAids;
+ private final String mCategory;
+ @SuppressWarnings("unused") // Unused as of now, but part of the XML input.
+ private final String mDescription;
+
+ /**
+ * Creates a new AidGroup object.
+ *
+ * @param aids list of AIDs present in the group
+ * @param category category of this group, e.g. {@link CardEmulation#CATEGORY_PAYMENT}
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ public AidGroup(@NonNull List<String> aids, @Nullable String category) {
+ if (aids == null || aids.size() == 0) {
+ throw new IllegalArgumentException("No AIDS in AID group.");
+ }
+ if (aids.size() > MAX_NUM_AIDS) {
+ throw new IllegalArgumentException("Too many AIDs in AID group.");
+ }
+ for (String aid : aids) {
+ if (!isValidAid(aid)) {
+ throw new IllegalArgumentException("AID " + aid + " is not a valid AID.");
+ }
+ }
+ if (isValidCategory(category)) {
+ this.mCategory = category;
+ } else {
+ this.mCategory = CardEmulation.CATEGORY_OTHER;
+ }
+ this.mAids = new ArrayList<String>(aids.size());
+ for (String aid : aids) {
+ this.mAids.add(aid.toUpperCase(Locale.US));
+ }
+ this.mDescription = null;
+ }
+
+ /**
+ * Creates a new AidGroup object.
+ *
+ * @param category category of this group, e.g. {@link CardEmulation#CATEGORY_PAYMENT}
+ * @param description description of this group
+ */
+ AidGroup(@NonNull String category, @NonNull String description) {
+ this.mAids = new ArrayList<String>();
+ this.mCategory = category;
+ this.mDescription = description;
+ }
+
+ /**
+ * Returns the category of this group.
+ * @return the category of this AID group
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @NonNull
+ public String getCategory() {
+ return mCategory;
+ }
+
+ /**
+ * Returns the list of AIDs in this group.
+ *
+ * @return the list of AIDs in this group
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @NonNull
+ public List<String> getAids() {
+ return mAids;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder out = new StringBuilder("Category: " + mCategory
+ + ", AIDs:");
+ for (String aid : mAids) {
+ out.append(aid);
+ out.append(", ");
+ }
+ return out.toString();
+ }
+
+ /**
+ * Dump debugging info as AidGroupProto.
+ *
+ * If the output belongs to a sub message, the caller is responsible for wrapping this function
+ * between {@link ProtoOutputStream#start(long)} and {@link ProtoOutputStream#end(long)}.
+ *
+ * @param proto the ProtoOutputStream to write to
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ public void dump(@NonNull ProtoOutputStream proto) {
+ proto.write(AidGroupProto.CATEGORY, mCategory);
+ for (String aid : mAids) {
+ proto.write(AidGroupProto.AIDS, aid);
+ }
+ }
+
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString8(mCategory);
+ dest.writeInt(mAids.size());
+ if (mAids.size() > 0) {
+ dest.writeStringList(mAids);
+ }
+ }
+
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ public static final @NonNull Parcelable.Creator<AidGroup> CREATOR =
+ new Parcelable.Creator<AidGroup>() {
+
+ @Override
+ public AidGroup createFromParcel(Parcel source) {
+ String category = source.readString8();
+ int listSize = source.readInt();
+ ArrayList<String> aidList = new ArrayList<String>();
+ if (listSize > 0) {
+ source.readStringList(aidList);
+ }
+ return new AidGroup(aidList, category);
+ }
+
+ @Override
+ public AidGroup[] newArray(int size) {
+ return new AidGroup[size];
+ }
+ };
+
+ /**
+ * Create an instance of AID group from XML file.
+ *
+ * @param parser input xml parser stream
+ * @throws XmlPullParserException If an error occurs parsing the element.
+ * @throws IOException If an error occurs reading the element.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @Nullable
+ public static AidGroup createFromXml(@NonNull XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ String category = null;
+ ArrayList<String> aids = new ArrayList<String>();
+ AidGroup group = null;
+ boolean inGroup = false;
+
+ int eventType = parser.getEventType();
+ int minDepth = parser.getDepth();
+ while (eventType != XmlPullParser.END_DOCUMENT && parser.getDepth() >= minDepth) {
+ String tagName = parser.getName();
+ if (eventType == XmlPullParser.START_TAG) {
+ if (tagName.equals("aid")) {
+ if (inGroup) {
+ String aid = parser.getAttributeValue(null, "value");
+ if (aid != null) {
+ aids.add(aid.toUpperCase());
+ }
+ } else {
+ Log.d(TAG, "Ignoring <aid> tag while not in group");
+ }
+ } else if (tagName.equals("aid-group")) {
+ category = parser.getAttributeValue(null, "category");
+ if (category == null) {
+ Log.e(TAG, "<aid-group> tag without valid category");
+ return null;
+ }
+ inGroup = true;
+ } else {
+ Log.d(TAG, "Ignoring unexpected tag: " + tagName);
+ }
+ } else if (eventType == XmlPullParser.END_TAG) {
+ if (tagName.equals("aid-group") && inGroup && aids.size() > 0) {
+ group = new AidGroup(aids, category);
+ break;
+ }
+ }
+ eventType = parser.next();
+ }
+ return group;
+ }
+
+ /**
+ * Serialize instance of AID group to XML file.
+ * @param out XML serializer stream
+ * @throws IOException If an error occurs reading the element.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ public void writeAsXml(@NonNull XmlSerializer out) throws IOException {
+ out.startTag(null, "aid-group");
+ out.attribute(null, "category", mCategory);
+ for (String aid : mAids) {
+ out.startTag(null, "aid");
+ out.attribute(null, "value", aid);
+ out.endTag(null, "aid");
+ }
+ out.endTag(null, "aid-group");
+ }
+
+ private static boolean isValidCategory(String category) {
+ return CardEmulation.CATEGORY_PAYMENT.equals(category) ||
+ CardEmulation.CATEGORY_OTHER.equals(category);
+ }
+
+ private static final Pattern AID_PATTERN = Pattern.compile("[0-9A-Fa-f]{10,32}\\*?\\#?");
+ /**
+ * Copied over from {@link CardEmulation#isValidAid(String)}
+ * @hide
+ */
+ private static boolean isValidAid(String aid) {
+ if (aid == null)
+ return false;
+
+ // If a prefix/subset AID, the total length must be odd (even # of AID chars + '*')
+ if ((aid.endsWith("*") || aid.endsWith("#")) && ((aid.length() % 2) == 0)) {
+ Log.e(TAG, "AID " + aid + " is not a valid AID.");
+ return false;
+ }
+
+ // If not a prefix/subset AID, the total length must be even (even # of AID chars)
+ if ((!(aid.endsWith("*") || aid.endsWith("#"))) && ((aid.length() % 2) != 0)) {
+ Log.e(TAG, "AID " + aid + " is not a valid AID.");
+ return false;
+ }
+
+ // Verify hex characters
+ if (!AID_PATTERN.matcher(aid).matches()) {
+ Log.e(TAG, "AID " + aid + " is not a valid AID.");
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.aidl b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.aidl
new file mode 100644
index 0000000..a62fdd6
--- /dev/null
+++ b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2013 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.cardemulation;
+
+parcelable ApduServiceInfo;
diff --git a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
new file mode 100644
index 0000000..41dee3a
--- /dev/null
+++ b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -0,0 +1,912 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+/**********************************************************************
+ * This file is not a part of the NFC mainline module *
+ * *******************************************************************/
+
+package android.nfc.cardemulation;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.nfc.Flags;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+import android.util.proto.ProtoOutputStream;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+/**
+ * Class holding APDU service info.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+public final class ApduServiceInfo implements Parcelable {
+ private static final String TAG = "ApduServiceInfo";
+
+ /**
+ * The service that implements this
+ */
+ private final ResolveInfo mService;
+
+ /**
+ * Description of the service
+ */
+ private final String mDescription;
+
+ /**
+ * Whether this service represents AIDs running on the host CPU
+ */
+ private final boolean mOnHost;
+
+ /**
+ * Offhost reader name.
+ * eg: SIM, eSE etc
+ */
+ private String mOffHostName;
+
+ /**
+ * Offhost reader name from manifest file.
+ * Used for resetOffHostSecureElement()
+ */
+ private final String mStaticOffHostName;
+
+ /**
+ * Mapping from category to static AID group
+ */
+ private final HashMap<String, AidGroup> mStaticAidGroups;
+
+ /**
+ * Mapping from category to dynamic AID group
+ */
+ private final HashMap<String, AidGroup> mDynamicAidGroups;
+
+ /**
+ * Whether this service should only be started when the device is unlocked.
+ */
+ private final boolean mRequiresDeviceUnlock;
+
+ /**
+ * Whether this service should only be started when the device is screen on.
+ */
+ private final boolean mRequiresDeviceScreenOn;
+
+ /**
+ * The id of the service banner specified in XML.
+ */
+ private final int mBannerResourceId;
+
+ /**
+ * The uid of the package the service belongs to
+ */
+ private final int mUid;
+
+ /**
+ * Settings Activity for this service
+ */
+ private final String mSettingsActivityName;
+
+ /**
+ * State of the service for CATEGORY_OTHER selection
+ */
+ private boolean mCategoryOtherServiceEnabled;
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public ApduServiceInfo(ResolveInfo info, boolean onHost, String description,
+ ArrayList<AidGroup> staticAidGroups, ArrayList<AidGroup> dynamicAidGroups,
+ boolean requiresUnlock, int bannerResource, int uid,
+ String settingsActivityName, String offHost, String staticOffHost) {
+ this(info, onHost, description, staticAidGroups, dynamicAidGroups,
+ requiresUnlock, bannerResource, uid, settingsActivityName,
+ offHost, staticOffHost, false);
+ }
+
+ /**
+ * @hide
+ */
+ public ApduServiceInfo(ResolveInfo info, boolean onHost, String description,
+ ArrayList<AidGroup> staticAidGroups, ArrayList<AidGroup> dynamicAidGroups,
+ boolean requiresUnlock, int bannerResource, int uid,
+ String settingsActivityName, String offHost, String staticOffHost,
+ boolean isEnabled) {
+ this(info, onHost, description, staticAidGroups, dynamicAidGroups,
+ requiresUnlock, onHost ? true : false, bannerResource, uid,
+ settingsActivityName, offHost, staticOffHost, isEnabled);
+ }
+
+ /**
+ * @hide
+ */
+ public ApduServiceInfo(ResolveInfo info, boolean onHost, String description,
+ List<AidGroup> staticAidGroups, List<AidGroup> dynamicAidGroups,
+ boolean requiresUnlock, boolean requiresScreenOn, int bannerResource, int uid,
+ String settingsActivityName, String offHost, String staticOffHost, boolean isEnabled) {
+ this.mService = info;
+ this.mDescription = description;
+ this.mStaticAidGroups = new HashMap<String, AidGroup>();
+ this.mDynamicAidGroups = new HashMap<String, AidGroup>();
+ this.mOffHostName = offHost;
+ this.mStaticOffHostName = staticOffHost;
+ this.mOnHost = onHost;
+ this.mRequiresDeviceUnlock = requiresUnlock;
+ this.mRequiresDeviceScreenOn = requiresScreenOn;
+ for (AidGroup aidGroup : staticAidGroups) {
+ this.mStaticAidGroups.put(aidGroup.getCategory(), aidGroup);
+ }
+ for (AidGroup aidGroup : dynamicAidGroups) {
+ this.mDynamicAidGroups.put(aidGroup.getCategory(), aidGroup);
+ }
+ this.mBannerResourceId = bannerResource;
+ this.mUid = uid;
+ this.mSettingsActivityName = settingsActivityName;
+ this.mCategoryOtherServiceEnabled = isEnabled;
+
+ }
+
+ /**
+ * Creates a new ApduServiceInfo object.
+ *
+ * @param pm packageManager instance
+ * @param info app component info
+ * @param onHost whether service is on host or not (secure element)
+ * @throws XmlPullParserException If an error occurs parsing the element.
+ * @throws IOException If an error occurs reading the element.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ public ApduServiceInfo(@NonNull PackageManager pm, @NonNull ResolveInfo info, boolean onHost)
+ throws XmlPullParserException, IOException {
+ ServiceInfo si = info.serviceInfo;
+ XmlResourceParser parser = null;
+ try {
+ if (onHost) {
+ parser = si.loadXmlMetaData(pm, HostApduService.SERVICE_META_DATA);
+ if (parser == null) {
+ throw new XmlPullParserException("No " + HostApduService.SERVICE_META_DATA +
+ " meta-data");
+ }
+ } else {
+ parser = si.loadXmlMetaData(pm, OffHostApduService.SERVICE_META_DATA);
+ if (parser == null) {
+ throw new XmlPullParserException("No " + OffHostApduService.SERVICE_META_DATA +
+ " meta-data");
+ }
+ }
+
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.START_TAG && eventType != XmlPullParser.END_DOCUMENT) {
+ eventType = parser.next();
+ }
+
+ String tagName = parser.getName();
+ if (onHost && !"host-apdu-service".equals(tagName)) {
+ throw new XmlPullParserException(
+ "Meta-data does not start with <host-apdu-service> tag");
+ } else if (!onHost && !"offhost-apdu-service".equals(tagName)) {
+ throw new XmlPullParserException(
+ "Meta-data does not start with <offhost-apdu-service> tag");
+ }
+
+ Resources res = pm.getResourcesForApplication(si.applicationInfo);
+ AttributeSet attrs = Xml.asAttributeSet(parser);
+ if (onHost) {
+ TypedArray sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.HostApduService);
+ mService = info;
+ mDescription = sa.getString(
+ com.android.internal.R.styleable.HostApduService_description);
+ mRequiresDeviceUnlock = sa.getBoolean(
+ com.android.internal.R.styleable.HostApduService_requireDeviceUnlock,
+ false);
+ mRequiresDeviceScreenOn = sa.getBoolean(
+ com.android.internal.R.styleable.HostApduService_requireDeviceScreenOn,
+ true);
+ mBannerResourceId = sa.getResourceId(
+ com.android.internal.R.styleable.HostApduService_apduServiceBanner, -1);
+ mSettingsActivityName = sa.getString(
+ com.android.internal.R.styleable.HostApduService_settingsActivity);
+ mOffHostName = null;
+ mStaticOffHostName = mOffHostName;
+ sa.recycle();
+ } else {
+ TypedArray sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.OffHostApduService);
+ mService = info;
+ mDescription = sa.getString(
+ com.android.internal.R.styleable.OffHostApduService_description);
+ mRequiresDeviceUnlock = sa.getBoolean(
+ com.android.internal.R.styleable.OffHostApduService_requireDeviceUnlock,
+ false);
+ mRequiresDeviceScreenOn = sa.getBoolean(
+ com.android.internal.R.styleable.OffHostApduService_requireDeviceScreenOn,
+ false);
+ mBannerResourceId = sa.getResourceId(
+ com.android.internal.R.styleable.OffHostApduService_apduServiceBanner, -1);
+ mSettingsActivityName = sa.getString(
+ com.android.internal.R.styleable.HostApduService_settingsActivity);
+ mOffHostName = sa.getString(
+ com.android.internal.R.styleable.OffHostApduService_secureElementName);
+ if (mOffHostName != null) {
+ if (mOffHostName.equals("eSE")) {
+ mOffHostName = "eSE1";
+ } else if (mOffHostName.equals("SIM")) {
+ mOffHostName = "SIM1";
+ }
+ }
+ mStaticOffHostName = mOffHostName;
+ sa.recycle();
+ }
+
+ mStaticAidGroups = new HashMap<String, AidGroup>();
+ mDynamicAidGroups = new HashMap<String, AidGroup>();
+ mOnHost = onHost;
+
+ final int depth = parser.getDepth();
+ AidGroup currentGroup = null;
+
+ // Parsed values for the current AID group
+ while (((eventType = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+ && eventType != XmlPullParser.END_DOCUMENT) {
+ tagName = parser.getName();
+ if (eventType == XmlPullParser.START_TAG && "aid-group".equals(tagName) &&
+ currentGroup == null) {
+ final TypedArray groupAttrs = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AidGroup);
+ // Get category of AID group
+ String groupCategory = groupAttrs.getString(
+ com.android.internal.R.styleable.AidGroup_category);
+ String groupDescription = groupAttrs.getString(
+ com.android.internal.R.styleable.AidGroup_description);
+ if (!CardEmulation.CATEGORY_PAYMENT.equals(groupCategory)) {
+ groupCategory = CardEmulation.CATEGORY_OTHER;
+ }
+ currentGroup = mStaticAidGroups.get(groupCategory);
+ if (currentGroup != null) {
+ if (!CardEmulation.CATEGORY_OTHER.equals(groupCategory)) {
+ Log.e(TAG, "Not allowing multiple aid-groups in the " +
+ groupCategory + " category");
+ currentGroup = null;
+ }
+ } else {
+ currentGroup = new AidGroup(groupCategory, groupDescription);
+ }
+ groupAttrs.recycle();
+ } else if (eventType == XmlPullParser.END_TAG && "aid-group".equals(tagName) &&
+ currentGroup != null) {
+ if (currentGroup.getAids().size() > 0) {
+ if (!mStaticAidGroups.containsKey(currentGroup.getCategory())) {
+ mStaticAidGroups.put(currentGroup.getCategory(), currentGroup);
+ }
+ } else {
+ Log.e(TAG, "Not adding <aid-group> with empty or invalid AIDs");
+ }
+ currentGroup = null;
+ } else if (eventType == XmlPullParser.START_TAG && "aid-filter".equals(tagName) &&
+ currentGroup != null) {
+ final TypedArray a = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AidFilter);
+ String aid = a.getString(com.android.internal.R.styleable.AidFilter_name).
+ toUpperCase();
+ if (isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
+ currentGroup.getAids().add(aid);
+ } else {
+ Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
+ }
+ a.recycle();
+ } else if (eventType == XmlPullParser.START_TAG &&
+ "aid-prefix-filter".equals(tagName) && currentGroup != null) {
+ final TypedArray a = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AidFilter);
+ String aid = a.getString(com.android.internal.R.styleable.AidFilter_name).
+ toUpperCase();
+ // Add wildcard char to indicate prefix
+ aid = aid.concat("*");
+ if (isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
+ currentGroup.getAids().add(aid);
+ } else {
+ Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
+ }
+ a.recycle();
+ } else if (eventType == XmlPullParser.START_TAG &&
+ tagName.equals("aid-suffix-filter") && currentGroup != null) {
+ final TypedArray a = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AidFilter);
+ String aid = a.getString(com.android.internal.R.styleable.AidFilter_name).
+ toUpperCase();
+ // Add wildcard char to indicate suffix
+ aid = aid.concat("#");
+ if (isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
+ currentGroup.getAids().add(aid);
+ } else {
+ Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
+ }
+ a.recycle();
+ }
+ }
+ } catch (NameNotFoundException e) {
+ throw new XmlPullParserException("Unable to create context for: " + si.packageName);
+ } finally {
+ if (parser != null) parser.close();
+ }
+ // Set uid
+ mUid = si.applicationInfo.uid;
+
+ mCategoryOtherServiceEnabled = true; // support other category
+
+ }
+
+ /**
+ * Returns the app component corresponding to this APDU service.
+ *
+ * @return app component for this service
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @NonNull
+ public ComponentName getComponent() {
+ return new ComponentName(mService.serviceInfo.packageName,
+ mService.serviceInfo.name);
+ }
+
+ /**
+ * Returns the offhost secure element name (if the service is offhost).
+ *
+ * @return offhost secure element name for offhost services
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @Nullable
+ public String getOffHostSecureElement() {
+ return mOffHostName;
+ }
+
+ /**
+ * Returns a consolidated list of AIDs from the AID groups
+ * registered by this service. Note that if a service has both
+ * a static (manifest-based) AID group for a category and a dynamic
+ * AID group, only the dynamically registered AIDs will be returned
+ * for that category.
+ * @return List of AIDs registered by the service
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @NonNull
+ public List<String> getAids() {
+ final ArrayList<String> aids = new ArrayList<String>();
+ for (AidGroup group : getAidGroups()) {
+ aids.addAll(group.getAids());
+ }
+ return aids;
+ }
+
+ /**
+ * Returns a consolidated list of AIDs with prefixes from the AID groups
+ * registered by this service. Note that if a service has both
+ * a static (manifest-based) AID group for a category and a dynamic
+ * AID group, only the dynamically registered AIDs will be returned
+ * for that category.
+ * @return List of prefix AIDs registered by the service
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @NonNull
+ public List<String> getPrefixAids() {
+ final ArrayList<String> prefixAids = new ArrayList<String>();
+ for (AidGroup group : getAidGroups()) {
+ for (String aid : group.getAids()) {
+ if (aid.endsWith("*")) {
+ prefixAids.add(aid);
+ }
+ }
+ }
+ return prefixAids;
+ }
+
+ /**
+ * Returns a consolidated list of AIDs with subsets from the AID groups
+ * registered by this service. Note that if a service has both
+ * a static (manifest-based) AID group for a category and a dynamic
+ * AID group, only the dynamically registered AIDs will be returned
+ * for that category.
+ * @return List of prefix AIDs registered by the service
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @NonNull
+ public List<String> getSubsetAids() {
+ final ArrayList<String> subsetAids = new ArrayList<String>();
+ for (AidGroup group : getAidGroups()) {
+ for (String aid : group.getAids()) {
+ if (aid.endsWith("#")) {
+ subsetAids.add(aid);
+ }
+ }
+ }
+ return subsetAids;
+ }
+
+ /**
+ * Returns the registered AID group for this category.
+ *
+ * @param category category name
+ * @return {@link AidGroup} instance for the provided category
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @NonNull
+ public AidGroup getDynamicAidGroupForCategory(@NonNull String category) {
+ return mDynamicAidGroups.get(category);
+ }
+
+ /**
+ * Removes the registered AID group for this category.
+ *
+ * @param category category name
+ * @return {@code true} if an AID group existed
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @NonNull
+ public boolean removeDynamicAidGroupForCategory(@NonNull String category) {
+ return (mDynamicAidGroups.remove(category) != null);
+ }
+
+ /**
+ * Returns a consolidated list of AID groups
+ * registered by this service. Note that if a service has both
+ * a static (manifest-based) AID group for a category and a dynamic
+ * AID group, only the dynamically registered AID group will be returned
+ * for that category.
+ * @return List of AIDs registered by the service
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @NonNull
+ public List<AidGroup> getAidGroups() {
+ final ArrayList<AidGroup> groups = new ArrayList<AidGroup>();
+ for (Map.Entry<String, AidGroup> entry : mDynamicAidGroups.entrySet()) {
+ groups.add(entry.getValue());
+ }
+ for (Map.Entry<String, AidGroup> entry : mStaticAidGroups.entrySet()) {
+ if (!mDynamicAidGroups.containsKey(entry.getKey())) {
+ // Consolidate AID groups - don't return static ones
+ // if a dynamic group exists for the category.
+ groups.add(entry.getValue());
+ }
+ }
+ return groups;
+ }
+
+ /**
+ * Returns the category to which this service has attributed the AID that is passed in,
+ * or null if we don't know this AID.
+ * @param aid AID to lookup for
+ * @return category name corresponding to this AID
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @NonNull
+ public String getCategoryForAid(@NonNull String aid) {
+ List<AidGroup> groups = getAidGroups();
+ for (AidGroup group : groups) {
+ if (group.getAids().contains(aid.toUpperCase())) {
+ return group.getCategory();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns whether there is any AID group for this category.
+ * @param category category name
+ * @return {@code true} if an AID group exists
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ public boolean hasCategory(@NonNull String category) {
+ return (mStaticAidGroups.containsKey(category) || mDynamicAidGroups.containsKey(category));
+ }
+
+ /**
+ * Returns whether the service is on host or not.
+ * @return true if the service is on host (not secure element)
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ public boolean isOnHost() {
+ return mOnHost;
+ }
+
+ /**
+ * Returns whether the service requires device unlock.
+ * @return whether the service requires device unlock
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ public boolean requiresUnlock() {
+ return mRequiresDeviceUnlock;
+ }
+
+ /**
+ * Returns whether this service should only be started when the device is screen on.
+ * @return whether the service requires screen on
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ public boolean requiresScreenOn() {
+ return mRequiresDeviceScreenOn;
+ }
+
+ /**
+ * Returns description of service.
+ * @return user readable description of service
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @NonNull
+ public String getDescription() {
+ return mDescription;
+ }
+
+ /**
+ * Returns uid of service.
+ * @return uid of the service
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ public int getUid() {
+ return mUid;
+ }
+
+ /**
+ * Add or replace an AID group to this service.
+ * @param aidGroup instance of aid group to set or replace
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ public void setDynamicAidGroup(@NonNull AidGroup aidGroup) {
+ mDynamicAidGroups.put(aidGroup.getCategory(), aidGroup);
+ }
+
+ /**
+ * Sets the off host Secure Element.
+ * @param offHost Secure Element to set. Only accept strings with prefix SIM or prefix eSE.
+ * Ref: GSMA TS.26 - NFC Handset Requirements
+ * TS26_NFC_REQ_069: For UICC, Secure Element Name SHALL be SIM[smartcard slot]
+ * (e.g. SIM/SIM1, SIM2… SIMn).
+ * TS26_NFC_REQ_070: For embedded SE, Secure Element Name SHALL be eSE[number]
+ * (e.g. eSE/eSE1, eSE2, etc.).
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ public void setOffHostSecureElement(@NonNull String offHost) {
+ mOffHostName = offHost;
+ }
+
+ /**
+ * Resets the off host Secure Element to statically defined
+ * by the service in the manifest file.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ public void resetOffHostSecureElement() {
+ mOffHostName = mStaticOffHostName;
+ }
+
+ /**
+ * Load label for this service.
+ * @param pm packagemanager instance
+ * @return label name corresponding to service
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @NonNull
+ public CharSequence loadLabel(@NonNull PackageManager pm) {
+ return mService.loadLabel(pm);
+ }
+
+ /**
+ * Load application label for this service.
+ * @param pm packagemanager instance
+ * @return app label name corresponding to service
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @NonNull
+ public CharSequence loadAppLabel(@NonNull PackageManager pm) {
+ try {
+ return pm.getApplicationLabel(pm.getApplicationInfo(
+ mService.resolvePackageName, PackageManager.GET_META_DATA));
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Load application icon for this service.
+ * @param pm packagemanager instance
+ * @return app icon corresponding to service
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @NonNull
+ public Drawable loadIcon(@NonNull PackageManager pm) {
+ return mService.loadIcon(pm);
+ }
+
+ /**
+ * Load application banner for this service.
+ * @param pm packagemanager instance
+ * @return app banner corresponding to service
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @NonNull
+ public Drawable loadBanner(@NonNull PackageManager pm) {
+ Resources res;
+ try {
+ res = pm.getResourcesForApplication(mService.serviceInfo.packageName);
+ Drawable banner = res.getDrawable(mBannerResourceId);
+ return banner;
+ } catch (NotFoundException e) {
+ Log.e(TAG, "Could not load banner.");
+ return null;
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Could not load banner.");
+ return null;
+ }
+ }
+
+ /**
+ * Load activity name for this service.
+ * @return activity name for this service
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @NonNull
+ public String getSettingsActivityName() { return mSettingsActivityName; }
+
+ @Override
+ public String toString() {
+ StringBuilder out = new StringBuilder("ApduService: ");
+ out.append(getComponent());
+ out.append(", UID: " + mUid);
+ out.append(", description: " + mDescription);
+ out.append(", Static AID Groups: ");
+ for (AidGroup aidGroup : mStaticAidGroups.values()) {
+ out.append(aidGroup.toString());
+ }
+ out.append(", Dynamic AID Groups: ");
+ for (AidGroup aidGroup : mDynamicAidGroups.values()) {
+ out.append(aidGroup.toString());
+ }
+ return out.toString();
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (!(o instanceof ApduServiceInfo)) return false;
+ ApduServiceInfo thatService = (ApduServiceInfo) o;
+
+ return thatService.getComponent().equals(this.getComponent())
+ && thatService.getUid() == this.getUid();
+ }
+
+ @Override
+ public int hashCode() {
+ return getComponent().hashCode();
+ }
+
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ mService.writeToParcel(dest, flags);
+ dest.writeString(mDescription);
+ dest.writeInt(mOnHost ? 1 : 0);
+ dest.writeString(mOffHostName);
+ dest.writeString(mStaticOffHostName);
+ dest.writeInt(mStaticAidGroups.size());
+ if (mStaticAidGroups.size() > 0) {
+ dest.writeTypedList(new ArrayList<AidGroup>(mStaticAidGroups.values()));
+ }
+ dest.writeInt(mDynamicAidGroups.size());
+ if (mDynamicAidGroups.size() > 0) {
+ dest.writeTypedList(new ArrayList<AidGroup>(mDynamicAidGroups.values()));
+ }
+ dest.writeInt(mRequiresDeviceUnlock ? 1 : 0);
+ dest.writeInt(mRequiresDeviceScreenOn ? 1 : 0);
+ dest.writeInt(mBannerResourceId);
+ dest.writeInt(mUid);
+ dest.writeString(mSettingsActivityName);
+
+ dest.writeInt(mCategoryOtherServiceEnabled ? 1 : 0);
+ };
+
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ public static final @NonNull Parcelable.Creator<ApduServiceInfo> CREATOR =
+ new Parcelable.Creator<ApduServiceInfo>() {
+ @Override
+ public ApduServiceInfo createFromParcel(Parcel source) {
+ ResolveInfo info = ResolveInfo.CREATOR.createFromParcel(source);
+ String description = source.readString();
+ boolean onHost = source.readInt() != 0;
+ String offHostName = source.readString();
+ String staticOffHostName = source.readString();
+ ArrayList<AidGroup> staticAidGroups = new ArrayList<AidGroup>();
+ int numStaticGroups = source.readInt();
+ if (numStaticGroups > 0) {
+ source.readTypedList(staticAidGroups, AidGroup.CREATOR);
+ }
+ ArrayList<AidGroup> dynamicAidGroups = new ArrayList<AidGroup>();
+ int numDynamicGroups = source.readInt();
+ if (numDynamicGroups > 0) {
+ source.readTypedList(dynamicAidGroups, AidGroup.CREATOR);
+ }
+ boolean requiresUnlock = source.readInt() != 0;
+ boolean requiresScreenOn = source.readInt() != 0;
+ int bannerResource = source.readInt();
+ int uid = source.readInt();
+ String settingsActivityName = source.readString();
+ boolean isEnabled = source.readInt() != 0;
+ return new ApduServiceInfo(info, onHost, description, staticAidGroups,
+ dynamicAidGroups, requiresUnlock, requiresScreenOn, bannerResource, uid,
+ settingsActivityName, offHostName, staticOffHostName,
+ isEnabled);
+ }
+
+ @Override
+ public ApduServiceInfo[] newArray(int size) {
+ return new ApduServiceInfo[size];
+ }
+ };
+
+ /**
+ * Dump contents for debugging.
+ * @param fd parcelfiledescriptor instance
+ * @param pw printwriter instance
+ * @param args args for dumping
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ public void dump(@NonNull ParcelFileDescriptor fd, @NonNull PrintWriter pw,
+ @NonNull String[] args) {
+ pw.println(" " + getComponent()
+ + " (Description: " + getDescription() + ")"
+ + " (UID: " + getUid() + ")");
+ if (mOnHost) {
+ pw.println(" On Host Service");
+ } else {
+ pw.println(" Off-host Service");
+ pw.println(" " + "Current off-host SE:" + mOffHostName
+ + " static off-host SE:" + mStaticOffHostName);
+ }
+ pw.println(" Static AID groups:");
+ for (AidGroup group : mStaticAidGroups.values()) {
+ pw.println(" Category: " + group.getCategory()
+ + "(enabled: " + mCategoryOtherServiceEnabled + ")");
+ for (String aid : group.getAids()) {
+ pw.println(" AID: " + aid);
+ }
+ }
+ pw.println(" Dynamic AID groups:");
+ for (AidGroup group : mDynamicAidGroups.values()) {
+ pw.println(" Category: " + group.getCategory()
+ + "(enabled: " + mCategoryOtherServiceEnabled + ")");
+ for (String aid : group.getAids()) {
+ pw.println(" AID: " + aid);
+ }
+ }
+ pw.println(" Settings Activity: " + mSettingsActivityName);
+ pw.println(" Requires Device Unlock: " + mRequiresDeviceUnlock);
+ pw.println(" Requires Device ScreenOn: " + mRequiresDeviceScreenOn);
+ }
+
+
+ /**
+ * Enable or disable this CATEGORY_OTHER service.
+ *
+ * @param enabled true to indicate if user has enabled this service
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ public void setCategoryOtherServiceEnabled(boolean enabled) {
+ mCategoryOtherServiceEnabled = enabled;
+ }
+
+
+ /**
+ * Returns whether this CATEGORY_OTHER service is enabled or not.
+ *
+ * @return true to indicate if user has enabled this service
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ public boolean isCategoryOtherServiceEnabled() {
+ return mCategoryOtherServiceEnabled;
+ }
+
+ /**
+ * Dump debugging info as ApduServiceInfoProto.
+ *
+ * If the output belongs to a sub message, the caller is responsible for wrapping this function
+ * between {@link ProtoOutputStream#start(long)} and {@link ProtoOutputStream#end(long)}.
+ * See proto definition in frameworks/base/core/proto/android/nfc/apdu_service_info.proto
+ *
+ * @param proto the ProtoOutputStream to write to
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ public void dumpDebug(@NonNull ProtoOutputStream proto) {
+ getComponent().dumpDebug(proto, ApduServiceInfoProto.COMPONENT_NAME);
+ proto.write(ApduServiceInfoProto.DESCRIPTION, getDescription());
+ proto.write(ApduServiceInfoProto.ON_HOST, mOnHost);
+ if (!mOnHost) {
+ proto.write(ApduServiceInfoProto.OFF_HOST_NAME, mOffHostName);
+ proto.write(ApduServiceInfoProto.STATIC_OFF_HOST_NAME, mStaticOffHostName);
+ }
+ for (AidGroup group : mStaticAidGroups.values()) {
+ long token = proto.start(ApduServiceInfoProto.STATIC_AID_GROUPS);
+ group.dump(proto);
+ proto.end(token);
+ }
+ for (AidGroup group : mDynamicAidGroups.values()) {
+ long token = proto.start(ApduServiceInfoProto.STATIC_AID_GROUPS);
+ group.dump(proto);
+ proto.end(token);
+ }
+ proto.write(ApduServiceInfoProto.SETTINGS_ACTIVITY_NAME, mSettingsActivityName);
+ }
+
+ private static final Pattern AID_PATTERN = Pattern.compile("[0-9A-Fa-f]{10,32}\\*?\\#?");
+ /**
+ * Copied over from {@link CardEmulation#isValidAid(String)}
+ * @hide
+ */
+ private static boolean isValidAid(String aid) {
+ if (aid == null)
+ return false;
+
+ // If a prefix/subset AID, the total length must be odd (even # of AID chars + '*')
+ if ((aid.endsWith("*") || aid.endsWith("#")) && ((aid.length() % 2) == 0)) {
+ Log.e(TAG, "AID " + aid + " is not a valid AID.");
+ return false;
+ }
+
+ // If not a prefix/subset AID, the total length must be even (even # of AID chars)
+ if ((!(aid.endsWith("*") || aid.endsWith("#"))) && ((aid.length() % 2) != 0)) {
+ Log.e(TAG, "AID " + aid + " is not a valid AID.");
+ return false;
+ }
+
+ // Verify hex characters
+ if (!AID_PATTERN.matcher(aid).matches()) {
+ Log.e(TAG, "AID " + aid + " is not a valid AID.");
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
new file mode 100644
index 0000000..81eab71
--- /dev/null
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -0,0 +1,1120 @@
+/*
+ * Copyright (C) 2013 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.cardemulation;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
+import android.annotation.UserIdInt;
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.nfc.Constants;
+import android.nfc.Flags;
+import android.nfc.INfcCardEmulation;
+import android.nfc.NfcAdapter;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * This class can be used to query the state of
+ * NFC card emulation services.
+ *
+ * For a general introduction into NFC card emulation,
+ * please read the <a href="{@docRoot}guide/topics/connectivity/nfc/hce.html">
+ * NFC card emulation developer guide</a>.</p>
+ *
+ * <p class="note">Use of this class requires the
+ * {@link PackageManager#FEATURE_NFC_HOST_CARD_EMULATION} to be present
+ * on the device.
+ */
+public final class CardEmulation {
+ private static final Pattern AID_PATTERN = Pattern.compile("[0-9A-Fa-f]{10,32}\\*?\\#?");
+ static final String TAG = "CardEmulation";
+
+ /**
+ * Activity action: ask the user to change the default
+ * card emulation service for a certain category. This will
+ * show a dialog that asks the user whether they want to
+ * replace the current default service with the service
+ * identified with the ComponentName specified in
+ * {@link #EXTRA_SERVICE_COMPONENT}, for the category
+ * specified in {@link #EXTRA_CATEGORY}. There is an optional
+ * extra field using {@link Intent#EXTRA_USER} to specify
+ * the {@link UserHandle} of the user that owns the app.
+ *
+ * @deprecated Please use {@link android.app.role.RoleManager#createRequestRoleIntent(String)}
+ * with {@link android.app.role.RoleManager#ROLE_WALLET} parameter
+ * and {@link Activity#startActivityForResult(Intent, int)} instead.
+ */
+ @Deprecated
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CHANGE_DEFAULT =
+ "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT";
+
+ /**
+ * The category extra for {@link #ACTION_CHANGE_DEFAULT}.
+ *
+ * @see #ACTION_CHANGE_DEFAULT
+ */
+ public static final String EXTRA_CATEGORY = "category";
+
+ /**
+ * The service {@link ComponentName} object passed in as an
+ * extra for {@link #ACTION_CHANGE_DEFAULT}.
+ *
+ * @see #ACTION_CHANGE_DEFAULT
+ */
+ public static final String EXTRA_SERVICE_COMPONENT = "component";
+
+ /**
+ * Category used for NFC payment services.
+ */
+ public static final String CATEGORY_PAYMENT = "payment";
+
+ /**
+ * Category that can be used for all other card emulation
+ * services.
+ */
+ public static final String CATEGORY_OTHER = "other";
+
+ /**
+ * Return value for {@link #getSelectionModeForCategory(String)}.
+ *
+ * <p>In this mode, the user has set a default service for this
+ * category.
+ *
+ * <p>When using ISO-DEP card emulation with {@link HostApduService}
+ * or {@link OffHostApduService}, if a remote NFC device selects
+ * any of the Application IDs (AIDs)
+ * that the default service has registered in this category,
+ * that service will automatically be bound to to handle
+ * the transaction.
+ */
+ public static final int SELECTION_MODE_PREFER_DEFAULT = 0;
+
+ /**
+ * Return value for {@link #getSelectionModeForCategory(String)}.
+ *
+ * <p>In this mode, when using ISO-DEP card emulation with {@link HostApduService}
+ * or {@link OffHostApduService}, whenever an Application ID (AID) of this category
+ * is selected, the user is asked which service they want to use to handle
+ * the transaction, even if there is only one matching service.
+ */
+ public static final int SELECTION_MODE_ALWAYS_ASK = 1;
+
+ /**
+ * Return value for {@link #getSelectionModeForCategory(String)}.
+ *
+ * <p>In this mode, when using ISO-DEP card emulation with {@link HostApduService}
+ * or {@link OffHostApduService}, the user will only be asked to select a service
+ * if the Application ID (AID) selected by the reader has been registered by multiple
+ * services. If there is only one service that has registered for the AID,
+ * that service will be invoked directly.
+ */
+ public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2;
+
+ static boolean sIsInitialized = false;
+ static HashMap<Context, CardEmulation> sCardEmus = new HashMap<Context, CardEmulation>();
+ static INfcCardEmulation sService;
+
+ final Context mContext;
+
+ private CardEmulation(Context context, INfcCardEmulation service) {
+ mContext = context.getApplicationContext();
+ sService = service;
+ }
+
+ /**
+ * Helper to get an instance of this class.
+ *
+ * @param adapter A reference to an NfcAdapter object.
+ * @return
+ */
+ public static synchronized CardEmulation getInstance(NfcAdapter adapter) {
+ if (adapter == null) throw new NullPointerException("NfcAdapter is null");
+ Context context = adapter.getContext();
+ if (context == null) {
+ Log.e(TAG, "NfcAdapter context is null.");
+ throw new UnsupportedOperationException();
+ }
+ if (!sIsInitialized) {
+ PackageManager pm = context.getPackageManager();
+ if (pm == null) {
+ Log.e(TAG, "Cannot get PackageManager");
+ throw new UnsupportedOperationException();
+ }
+ if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
+ Log.e(TAG, "This device does not support card emulation");
+ throw new UnsupportedOperationException();
+ }
+ sIsInitialized = true;
+ }
+ CardEmulation manager = sCardEmus.get(context);
+ if (manager == null) {
+ // Get card emu service
+ INfcCardEmulation service = adapter.getCardEmulationService();
+ if (service == null) {
+ Log.e(TAG, "This device does not implement the INfcCardEmulation interface.");
+ throw new UnsupportedOperationException();
+ }
+ manager = new CardEmulation(context, service);
+ sCardEmus.put(context, manager);
+ }
+ return manager;
+ }
+
+ /**
+ * Allows an application to query whether a service is currently
+ * the default service to handle a card emulation category.
+ *
+ * <p>Note that if {@link #getSelectionModeForCategory(String)}
+ * returns {@link #SELECTION_MODE_ALWAYS_ASK} or {@link #SELECTION_MODE_ASK_IF_CONFLICT},
+ * this method will always return false. That is because in these
+ * selection modes a default can't be set at the category level. For categories where
+ * the selection mode is {@link #SELECTION_MODE_ALWAYS_ASK} or
+ * {@link #SELECTION_MODE_ASK_IF_CONFLICT}, use
+ * {@link #isDefaultServiceForAid(ComponentName, String)} to determine whether a service
+ * is the default for a specific AID.
+ *
+ * @param service The ComponentName of the service
+ * @param category The category
+ * @return whether service is currently the default service for the category.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ */
+ public boolean isDefaultServiceForCategory(ComponentName service, String category) {
+ try {
+ return sService.isDefaultServiceForCategory(mContext.getUser().getIdentifier(),
+ service, category);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ try {
+ return sService.isDefaultServiceForCategory(mContext.getUser().getIdentifier(),
+ service, category);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ }
+ }
+
+ /**
+ *
+ * Allows an application to query whether a service is currently
+ * the default handler for a specified ISO7816-4 Application ID.
+ *
+ * @param service The ComponentName of the service
+ * @param aid The ISO7816-4 Application ID
+ * @return whether the service is the default handler for the specified AID
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ */
+ public boolean isDefaultServiceForAid(ComponentName service, String aid) {
+ try {
+ return sService.isDefaultServiceForAid(mContext.getUser().getIdentifier(),
+ service, aid);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ try {
+ return sService.isDefaultServiceForAid(mContext.getUser().getIdentifier(),
+ service, aid);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Returns whether the user has allowed AIDs registered in the
+ * specified category to be handled by a service that is preferred
+ * by the foreground application, instead of by a pre-configured default.
+ *
+ * Foreground applications can set such preferences using the
+ * {@link #setPreferredService(Activity, ComponentName)} method.
+ *
+ * @param category The category, e.g. {@link #CATEGORY_PAYMENT}
+ * @return whether AIDs in the category can be handled by a service
+ * specified by the foreground app.
+ */
+ @SuppressWarnings("NonUserGetterCalled")
+ public boolean categoryAllowsForegroundPreference(String category) {
+ if (CATEGORY_PAYMENT.equals(category)) {
+ boolean preferForeground = false;
+ Context contextAsUser = mContext.createContextAsUser(
+ UserHandle.of(UserHandle.myUserId()), 0);
+ try {
+ preferForeground = Settings.Secure.getInt(
+ contextAsUser.getContentResolver(),
+ Constants.SETTINGS_SECURE_NFC_PAYMENT_FOREGROUND) != 0;
+ } catch (SettingNotFoundException e) {
+ }
+ return preferForeground;
+ } else {
+ // Allowed for all other categories
+ return true;
+ }
+ }
+
+ /**
+ * Returns the service selection mode for the passed in category.
+ * Valid return values are:
+ * <p>{@link #SELECTION_MODE_PREFER_DEFAULT} the user has requested a default
+ * service for this category, which will be preferred.
+ * <p>{@link #SELECTION_MODE_ALWAYS_ASK} the user has requested to be asked
+ * every time what service they would like to use in this category.
+ * <p>{@link #SELECTION_MODE_ASK_IF_CONFLICT} the user will only be asked
+ * to pick a service if there is a conflict.
+ * @param category The category, for example {@link #CATEGORY_PAYMENT}
+ * @return the selection mode for the passed in category
+ */
+ public int getSelectionModeForCategory(String category) {
+ if (CATEGORY_PAYMENT.equals(category)) {
+ boolean paymentRegistered = false;
+ try {
+ paymentRegistered = sService.isDefaultPaymentRegistered();
+ } catch (RemoteException e) {
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return SELECTION_MODE_ALWAYS_ASK;
+ }
+ try {
+ paymentRegistered = sService.isDefaultPaymentRegistered();
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ return SELECTION_MODE_ALWAYS_ASK;
+ }
+ }
+ if (paymentRegistered) {
+ return SELECTION_MODE_PREFER_DEFAULT;
+ } else {
+ return SELECTION_MODE_ALWAYS_ASK;
+ }
+ } else {
+ return SELECTION_MODE_ASK_IF_CONFLICT;
+ }
+ }
+ /**
+ * Sets whether the system should default to observe mode or not when
+ * the service is in the foreground or the default payment service.
+ *
+ * @param service The component name of the service
+ * @param enable Whether the servic should default to observe mode or not
+ * @return whether the change was successful.
+ */
+ @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
+ public boolean setServiceObserveModeDefault(@NonNull ComponentName service, boolean enable) {
+ try {
+ return sService.setServiceObserveModeDefault(mContext.getUser().getIdentifier(),
+ service, enable);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ }
+ return false;
+ }
+
+ /**
+ * Registers a list of AIDs for a specific category for the
+ * specified service.
+ *
+ * <p>If a list of AIDs for that category was previously
+ * registered for this service (either statically
+ * through the manifest, or dynamically by using this API),
+ * that list of AIDs will be replaced with this one.
+ *
+ * <p>Note that you can only register AIDs for a service that
+ * is running under the same UID as the caller of this API. Typically
+ * this means you need to call this from the same
+ * package as the service itself, though UIDs can also
+ * be shared between packages using shared UIDs.
+ *
+ * @param service The component name of the service
+ * @param category The category of AIDs to be registered
+ * @param aids A list containing the AIDs to be registered
+ * @return whether the registration was successful.
+ */
+ public boolean registerAidsForService(ComponentName service, String category,
+ List<String> aids) {
+ AidGroup aidGroup = new AidGroup(aids, category);
+ try {
+ return sService.registerAidGroupForService(mContext.getUser().getIdentifier(),
+ service, aidGroup);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ try {
+ return sService.registerAidGroupForService(mContext.getUser().getIdentifier(),
+ service, aidGroup);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Unsets the off-host Secure Element for the given service.
+ *
+ * <p>Note that this will only remove Secure Element that was dynamically
+ * set using the {@link #setOffHostForService(ComponentName, String)}
+ * and resets it to a value that was statically assigned using manifest.
+ *
+ * <p>Note that you can only unset off-host SE for a service that
+ * is running under the same UID as the caller of this API. Typically
+ * this means you need to call this from the same
+ * package as the service itself, though UIDs can also
+ * be shared between packages using shared UIDs.
+ *
+ * @param service The component name of the service
+ * @return whether the registration was successful.
+ */
+ @RequiresPermission(android.Manifest.permission.NFC)
+ @NonNull
+ public boolean unsetOffHostForService(@NonNull ComponentName service) {
+ NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
+ if (adapter == null) {
+ return false;
+ }
+
+ try {
+ return sService.unsetOffHostForService(mContext.getUser().getIdentifier(), service);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ try {
+ return sService.unsetOffHostForService(mContext.getUser().getIdentifier(), service);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Sets the off-host Secure Element for the given service.
+ *
+ * <p>If off-host SE was initially set (either statically
+ * through the manifest, or dynamically by using this API),
+ * it will be replaced with this one. All AIDs registered by
+ * this service will be re-routed to this Secure Element if
+ * successful. AIDs that was statically assigned using manifest
+ * will re-route to off-host SE that stated in manifest after NFC
+ * toggle.
+ *
+ * <p>Note that you can only set off-host SE for a service that
+ * is running under the same UID as the caller of this API. Typically
+ * this means you need to call this from the same
+ * package as the service itself, though UIDs can also
+ * be shared between packages using shared UIDs.
+ *
+ * <p>Registeration will be successful only if the Secure Element
+ * exists on the device.
+ *
+ * @param service The component name of the service
+ * @param offHostSecureElement Secure Element to register the AID to. Only accept strings with
+ * prefix SIM or prefix eSE.
+ * Ref: GSMA TS.26 - NFC Handset Requirements
+ * TS26_NFC_REQ_069: For UICC, Secure Element Name SHALL be
+ * SIM[smartcard slot]
+ * (e.g. SIM/SIM1, SIM2… SIMn).
+ * TS26_NFC_REQ_070: For embedded SE, Secure Element Name SHALL be
+ * eSE[number]
+ * (e.g. eSE/eSE1, eSE2, etc.).
+ * @return whether the registration was successful.
+ */
+ @RequiresPermission(android.Manifest.permission.NFC)
+ @NonNull
+ public boolean setOffHostForService(@NonNull ComponentName service,
+ @NonNull String offHostSecureElement) {
+ boolean validSecureElement = false;
+
+ NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
+ if (adapter == null || offHostSecureElement == null) {
+ return false;
+ }
+
+ List<String> validSE = adapter.getSupportedOffHostSecureElements();
+ if ((offHostSecureElement.startsWith("eSE") && !validSE.contains("eSE"))
+ || (offHostSecureElement.startsWith("SIM") && !validSE.contains("SIM"))) {
+ return false;
+ }
+
+ if (!offHostSecureElement.startsWith("eSE") && !offHostSecureElement.startsWith("SIM")) {
+ return false;
+ }
+
+ if (offHostSecureElement.equals("eSE")) {
+ offHostSecureElement = "eSE1";
+ } else if (offHostSecureElement.equals("SIM")) {
+ offHostSecureElement = "SIM1";
+ }
+
+ try {
+ return sService.setOffHostForService(mContext.getUser().getIdentifier(), service,
+ offHostSecureElement);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ try {
+ return sService.setOffHostForService(mContext.getUser().getIdentifier(), service,
+ offHostSecureElement);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Retrieves the currently registered AIDs for the specified
+ * category for a service.
+ *
+ * <p>Note that this will only return AIDs that were dynamically
+ * registered using {@link #registerAidsForService(ComponentName, String, List)}
+ * method. It will *not* return AIDs that were statically registered
+ * in the manifest.
+ *
+ * @param service The component name of the service
+ * @param category The category for which the AIDs were registered,
+ * e.g. {@link #CATEGORY_PAYMENT}
+ * @return The list of AIDs registered for this category, or null if it couldn't be found.
+ */
+ public List<String> getAidsForService(ComponentName service, String category) {
+ try {
+ AidGroup group = sService.getAidGroupForService(mContext.getUser().getIdentifier(),
+ service, category);
+ return (group != null ? group.getAids() : null);
+ } catch (RemoteException e) {
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return null;
+ }
+ try {
+ AidGroup group = sService.getAidGroupForService(mContext.getUser().getIdentifier(),
+ service, category);
+ return (group != null ? group.getAids() : null);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Removes a previously registered list of AIDs for the specified category for the
+ * service provided.
+ *
+ * <p>Note that this will only remove AIDs that were dynamically
+ * registered using the {@link #registerAidsForService(ComponentName, String, List)}
+ * method. It will *not* remove AIDs that were statically registered in
+ * the manifest. If dynamically registered AIDs are removed using
+ * this method, and a statically registered AID group for the same category
+ * exists in the manifest, the static AID group will become active again.
+ *
+ * @param service The component name of the service
+ * @param category The category of the AIDs to be removed, e.g. {@link #CATEGORY_PAYMENT}
+ * @return whether the group was successfully removed.
+ */
+ public boolean removeAidsForService(ComponentName service, String category) {
+ try {
+ return sService.removeAidGroupForService(mContext.getUser().getIdentifier(), service,
+ category);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ try {
+ return sService.removeAidGroupForService(mContext.getUser().getIdentifier(),
+ service, category);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Allows a foreground application to specify which card emulation service
+ * should be preferred while a specific Activity is in the foreground.
+ *
+ * <p>The specified Activity must currently be in resumed state. A good
+ * paradigm is to call this method in your {@link Activity#onResume}, and to call
+ * {@link #unsetPreferredService(Activity)} in your {@link Activity#onPause}.
+ *
+ * <p>This method call will fail in two specific scenarios:
+ * <ul>
+ * <li> If the service registers one or more AIDs in the {@link #CATEGORY_PAYMENT}
+ * category, but the user has indicated that foreground apps are not allowed
+ * to override the default payment service.
+ * <li> If the service registers one or more AIDs in the {@link #CATEGORY_OTHER}
+ * category that are also handled by the default payment service, and the
+ * user has indicated that foreground apps are not allowed to override the
+ * default payment service.
+ * </ul>
+ *
+ * <p> Use {@link #categoryAllowsForegroundPreference(String)} to determine
+ * whether foreground apps can override the default payment service.
+ *
+ * <p>Note that this preference is not persisted by the OS, and hence must be
+ * called every time the Activity is resumed.
+ *
+ * @param activity The activity which prefers this service to be invoked
+ * @param service The service to be preferred while this activity is in the foreground
+ * @return whether the registration was successful
+ */
+ public boolean setPreferredService(Activity activity, ComponentName service) {
+ // Verify the activity is in the foreground before calling into NfcService
+ if (activity == null || service == null) {
+ throw new NullPointerException("activity or service or category is null");
+ }
+ try {
+ return sService.setPreferredService(service);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ try {
+ return sService.setPreferredService(service);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Unsets the preferred service for the specified Activity.
+ *
+ * <p>Note that the specified Activity must still be in resumed
+ * state at the time of this call. A good place to call this method
+ * is in your {@link Activity#onPause} implementation.
+ *
+ * @param activity The activity which the service was registered for
+ * @return true when successful
+ */
+ public boolean unsetPreferredService(Activity activity) {
+ if (activity == null) {
+ throw new NullPointerException("activity is null");
+ }
+ try {
+ return sService.unsetPreferredService();
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ try {
+ return sService.unsetPreferredService();
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Some devices may allow an application to register all
+ * AIDs that starts with a certain prefix, e.g.
+ * "A000000004*" to register all MasterCard AIDs.
+ *
+ * Use this method to determine whether this device
+ * supports registering AID prefixes.
+ *
+ * @return whether AID prefix registering is supported on this device.
+ */
+ public boolean supportsAidPrefixRegistration() {
+ try {
+ return sService.supportsAidPrefixRegistration();
+ } catch (RemoteException e) {
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ try {
+ return sService.supportsAidPrefixRegistration();
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Retrieves the registered AIDs for the preferred payment service.
+ *
+ * @return The list of AIDs registered for this category, or null if it couldn't be found.
+ */
+ @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
+ @Nullable
+ public List<String> getAidsForPreferredPaymentService() {
+ try {
+ ApduServiceInfo serviceInfo = sService.getPreferredPaymentService(
+ mContext.getUser().getIdentifier());
+ return (serviceInfo != null ? serviceInfo.getAids() : null);
+ } catch (RemoteException e) {
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ throw e.rethrowFromSystemServer();
+ }
+ try {
+ ApduServiceInfo serviceInfo =
+ sService.getPreferredPaymentService(mContext.getUser().getIdentifier());
+ return (serviceInfo != null ? serviceInfo.getAids() : null);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Retrieves the route destination for the preferred payment service.
+ *
+ * @return The route destination secure element name of the preferred payment service.
+ * HCE payment: "Host"
+ * OffHost payment: 1. String with prefix SIM or prefix eSE string.
+ * Ref: GSMA TS.26 - NFC Handset Requirements
+ * TS26_NFC_REQ_069: For UICC, Secure Element Name SHALL be
+ * SIM[smartcard slot]
+ * (e.g. SIM/SIM1, SIM2… SIMn).
+ * TS26_NFC_REQ_070: For embedded SE, Secure Element Name SHALL be
+ * eSE[number]
+ * (e.g. eSE/eSE1, eSE2, etc.).
+ * 2. "OffHost" if the payment service does not specify secure element
+ * name.
+ */
+ @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
+ @Nullable
+ public String getRouteDestinationForPreferredPaymentService() {
+ try {
+ ApduServiceInfo serviceInfo = sService.getPreferredPaymentService(
+ mContext.getUser().getIdentifier());
+ if (serviceInfo != null) {
+ if (!serviceInfo.isOnHost()) {
+ return serviceInfo.getOffHostSecureElement() == null ?
+ "OffHost" : serviceInfo.getOffHostSecureElement();
+ }
+ return "Host";
+ }
+ return null;
+ } catch (RemoteException e) {
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ throw e.rethrowFromSystemServer();
+ }
+ try {
+ ApduServiceInfo serviceInfo =
+ sService.getPreferredPaymentService(mContext.getUser().getIdentifier());
+ if (serviceInfo != null) {
+ if (!serviceInfo.isOnHost()) {
+ return serviceInfo.getOffHostSecureElement() == null ?
+ "Offhost" : serviceInfo.getOffHostSecureElement();
+ }
+ return "Host";
+ }
+ return null;
+
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Returns a user-visible description of the preferred payment service.
+ *
+ * @return the preferred payment service description
+ */
+ @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
+ @Nullable
+ public CharSequence getDescriptionForPreferredPaymentService() {
+ try {
+ ApduServiceInfo serviceInfo = sService.getPreferredPaymentService(
+ mContext.getUser().getIdentifier());
+ return (serviceInfo != null ? serviceInfo.getDescription() : null);
+ } catch (RemoteException e) {
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ throw e.rethrowFromSystemServer();
+ }
+ try {
+ ApduServiceInfo serviceInfo =
+ sService.getPreferredPaymentService(mContext.getUser().getIdentifier());
+ return (serviceInfo != null ? serviceInfo.getDescription() : null);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public boolean setDefaultServiceForCategory(ComponentName service, String category) {
+ try {
+ return sService.setDefaultServiceForCategory(mContext.getUser().getIdentifier(),
+ service, category);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ try {
+ return sService.setDefaultServiceForCategory(mContext.getUser().getIdentifier(),
+ service, category);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ return false;
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public boolean setDefaultForNextTap(ComponentName service) {
+ try {
+ return sService.setDefaultForNextTap(mContext.getUser().getIdentifier(), service);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ try {
+ return sService.setDefaultForNextTap(mContext.getUser().getIdentifier(), service);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ return false;
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public boolean setDefaultForNextTap(int userId, ComponentName service) {
+ try {
+ return sService.setDefaultForNextTap(userId, service);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ try {
+ return sService.setDefaultForNextTap(userId, service);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ return false;
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public List<ApduServiceInfo> getServices(String category) {
+ try {
+ return sService.getServices(mContext.getUser().getIdentifier(), category);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return null;
+ }
+ try {
+ return sService.getServices(mContext.getUser().getIdentifier(), category);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Retrieves list of services registered of the provided category for the provided user.
+ *
+ * @param category Category string, one of {@link #CATEGORY_PAYMENT} or {@link #CATEGORY_OTHER}
+ * @param userId the user handle of the user whose information is being requested.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @NonNull
+ public List<ApduServiceInfo> getServices(@NonNull String category, @UserIdInt int userId) {
+ try {
+ return sService.getServices(userId, category);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return null;
+ }
+ try {
+ return sService.getServices(userId, category);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ return null;
+ }
+ }
+ }
+
+ /**
+ * A valid AID according to ISO/IEC 7816-4:
+ * <ul>
+ * <li>Has >= 5 bytes and <=16 bytes (>=10 hex chars and <= 32 hex chars)
+ * <li>Consist of only hex characters
+ * <li>Additionally, we allow an asterisk at the end, to indicate
+ * a prefix
+ * <li>Additinally we allow an (#) at symbol at the end, to indicate
+ * a subset
+ * </ul>
+ *
+ * @hide
+ */
+ public static boolean isValidAid(String aid) {
+ if (aid == null)
+ return false;
+
+ // If a prefix/subset AID, the total length must be odd (even # of AID chars + '*')
+ if ((aid.endsWith("*") || aid.endsWith("#")) && ((aid.length() % 2) == 0)) {
+ Log.e(TAG, "AID " + aid + " is not a valid AID.");
+ return false;
+ }
+
+ // If not a prefix/subset AID, the total length must be even (even # of AID chars)
+ if ((!(aid.endsWith("*") || aid.endsWith("#"))) && ((aid.length() % 2) != 0)) {
+ Log.e(TAG, "AID " + aid + " is not a valid AID.");
+ return false;
+ }
+
+ // Verify hex characters
+ if (!AID_PATTERN.matcher(aid).matches()) {
+ Log.e(TAG, "AID " + aid + " is not a valid AID.");
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Allows to set or unset preferred service (category other) to avoid AID Collision.
+ *
+ * @param service The ComponentName of the service
+ * @param status true to enable, false to disable
+ * @return set service for the category and true if service is already set return false.
+ *
+ * @hide
+ */
+ public boolean setServiceEnabledForCategoryOther(ComponentName service, boolean status) {
+ if (service == null) {
+ throw new NullPointerException("activity or service or category is null");
+ }
+ int userId = mContext.getUser().getIdentifier();
+
+ try {
+ return sService.setServiceEnabledForCategoryOther(userId, service, status);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ try {
+ return sService.setServiceEnabledForCategoryOther(userId, service, status);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Setting NFC controller routing table, which includes Protocol Route and Technology Route,
+ * while this Activity is in the foreground.
+ *
+ * The parameter set to null can be used to keep current values for that entry.
+ * <p>
+ * Example usage in an Activity that requires to set proto route to "ESE" and keep tech route:
+ * <pre>
+ * protected void onResume() {
+ * mNfcAdapter.overrideRoutingTable(this , "ESE" , null);
+ * }</pre>
+ * </p>
+ * Also activities must call this method when it goes to the background,
+ * with all parameters set to null.
+ * @param activity The Activity that requests NFC controller routing table to be changed.
+ * @param protocol ISO-DEP route destination, which can be "DH" or "UICC" or "ESE".
+ * @param technology Tech-A, Tech-B route destination, which can be "DH" or "UICC" or "ESE".
+ * @return true if operation is successful and false otherwise
+ *
+ * This is a high risk API and only included to support mainline effort
+ * @hide
+ */
+ public boolean overrideRoutingTable(Activity activity, String protocol, String technology) {
+ if (activity == null) {
+ throw new NullPointerException("activity or service or category is null");
+ }
+ if (!activity.isResumed()) {
+ throw new IllegalArgumentException("Activity must be resumed.");
+ }
+ try {
+ return sService.overrideRoutingTable(UserHandle.myUserId(), protocol, technology);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ try {
+ return sService.overrideRoutingTable(UserHandle.myUserId(), protocol, technology);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Restore the NFC controller routing table,
+ * which was changed by {@link #overrideRoutingTable(Activity, String, String)}
+ *
+ * @param activity The Activity that requested NFC controller routing table to be changed.
+ * @return true if operation is successful and false otherwise
+ *
+ * @hide
+ */
+ public boolean recoverRoutingTable(Activity activity) {
+ if (activity == null) {
+ throw new NullPointerException("activity is null");
+ }
+ if (!activity.isResumed()) {
+ throw new IllegalArgumentException("Activity must be resumed.");
+ }
+ try {
+ return sService.recoverRoutingTable(UserHandle.myUserId());
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ try {
+ return sService.recoverRoutingTable(UserHandle.myUserId());
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ return false;
+ }
+ }
+ }
+
+ void recoverService() {
+ NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
+ sService = adapter.getCardEmulationService();
+ }
+
+ /**
+ * Returns the {@link Settings.Secure#NFC_PAYMENT_DEFAULT_COMPONENT} for the given user.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
+ @FlaggedApi(android.permission.flags.Flags.FLAG_WALLET_ROLE_ENABLED)
+ @Nullable
+ public ApduServiceInfo getPreferredPaymentService() {
+ try {
+ return sService.getPreferredPaymentService(mContext.getUser().getIdentifier());
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return null;
+ }
+ try {
+ return sService.getPreferredPaymentService(mContext.getUser().getIdentifier());
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ return null;
+ }
+ }
+ }
+
+}
diff --git a/nfc/java/android/nfc/cardemulation/HostApduService.java b/nfc/java/android/nfc/cardemulation/HostApduService.java
new file mode 100644
index 0000000..7cd2533
--- /dev/null
+++ b/nfc/java/android/nfc/cardemulation/HostApduService.java
@@ -0,0 +1,519 @@
+/*
+ * Copyright (C) 2015 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.cardemulation;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.app.Service;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.nfc.NfcAdapter;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * <p>HostApduService is a convenience {@link Service} class that can be
+ * extended to emulate an NFC card inside an Android
+ * service component.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guide</h3>
+ * For a general introduction to card emulation, see
+ * <a href="{@docRoot}guide/topics/connectivity/nfc/hce.html">
+ * Host-based Card Emulation</a>.</p>
+ * </div>
+ *
+ * <h3>NFC Protocols</h3>
+ * <p>Cards emulated by this class are based on the NFC-Forum ISO-DEP
+ * protocol (based on ISO/IEC 14443-4) and support processing
+ * command Application Protocol Data Units (APDUs) as
+ * defined in the ISO/IEC 7816-4 specification.
+ *
+ * <h3>Service selection</h3>
+ * <p>When a remote NFC device wants to talk to your
+ * service, it sends a so-called
+ * "SELECT AID" APDU as defined in the ISO/IEC 7816-4 specification.
+ * The AID is an application identifier defined in ISO/IEC 7816-4.
+ *
+ * <p>The registration procedure for AIDs is defined in the
+ * ISO/IEC 7816-5 specification. If you don't want to register an
+ * AID, you are free to use AIDs in the proprietary range:
+ * bits 8-5 of the first byte must each be set to '1'. For example,
+ * "0xF00102030405" is a proprietary AID. If you do use proprietary
+ * AIDs, it is recommended to choose an AID of at least 6 bytes,
+ * to reduce the risk of collisions with other applications that
+ * might be using proprietary AIDs as well.
+ *
+ * <h3>AID groups</h3>
+ * <p>In some cases, a service may need to register multiple AIDs
+ * to implement a certain application, and it needs to be sure
+ * that it is the default handler for all of these AIDs (as opposed
+ * to some AIDs in the group going to another service).
+ *
+ * <p>An AID group is a list of AIDs that should be considered as
+ * belonging together by the OS. For all AIDs in an AID group, the
+ * OS will guarantee one of the following:
+ * <ul>
+ * <li>All AIDs in the group are routed to this service
+ * <li>No AIDs in the group are routed to this service
+ * </ul>
+ * In other words, there is no in-between state, where some AIDs
+ * in the group can be routed to this service, and some to another.
+ * <h3>AID groups and categories</h3>
+ * <p>Each AID group can be associated with a category. This allows
+ * the Android OS to classify services, and it allows the user to
+ * set defaults at the category level instead of the AID level.
+ *
+ * <p>You can use
+ * {@link CardEmulation#isDefaultServiceForCategory(android.content.ComponentName, String)}
+ * to determine if your service is the default handler for a category.
+ *
+ * <p>In this version of the platform, the only known categories
+ * are {@link CardEmulation#CATEGORY_PAYMENT} and {@link CardEmulation#CATEGORY_OTHER}.
+ * AID groups without a category, or with a category that is not recognized
+ * by the current platform version, will automatically be
+ * grouped into the {@link CardEmulation#CATEGORY_OTHER} category.
+ * <h3>Service AID registration</h3>
+ * <p>To tell the platform which AIDs groups
+ * are requested by this service, a {@link #SERVICE_META_DATA}
+ * entry must be included in the declaration of the service. An
+ * example of a HostApduService manifest declaration is shown below:
+ * <pre> <service android:name=".MyHostApduService" android:exported="true" android:permission="android.permission.BIND_NFC_SERVICE">
+ * <intent-filter>
+ * <action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE"/>
+ * </intent-filter>
+ * <meta-data android:name="android.nfc.cardemulation.host_apdu_ervice" android:resource="@xml/apduservice"/>
+ * </service></pre>
+ *
+ * This meta-data tag points to an apduservice.xml file.
+ * An example of this file with a single AID group declaration is shown below:
+ * <pre>
+ * <host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
+ * android:description="@string/servicedesc" android:requireDeviceUnlock="false">
+ * <aid-group android:description="@string/aiddescription" android:category="other">
+ * <aid-filter android:name="F0010203040506"/>
+ * <aid-filter android:name="F0394148148100"/>
+ * </aid-group>
+ * </host-apdu-service>
+ * </pre>
+ *
+ * <p>The {@link android.R.styleable#HostApduService <host-apdu-service>} is required
+ * to contain a
+ * {@link android.R.styleable#HostApduService_description <android:description>}
+ * attribute that contains a user-friendly description of the service that may be shown in UI.
+ * The
+ * {@link android.R.styleable#HostApduService_requireDeviceUnlock <requireDeviceUnlock>}
+ * attribute can be used to specify that the device must be unlocked before this service
+ * can be invoked to handle APDUs.
+ * <p>The {@link android.R.styleable#HostApduService <host-apdu-service>} must
+ * contain one or more {@link android.R.styleable#AidGroup <aid-group>} tags.
+ * Each {@link android.R.styleable#AidGroup <aid-group>} must contain one or
+ * more {@link android.R.styleable#AidFilter <aid-filter>} tags, each of which
+ * contains a single AID. The AID must be specified in hexadecimal format, and contain
+ * an even number of characters.
+ * <h3>AID conflict resolution</h3>
+ * Multiple HostApduServices may be installed on a single device, and the same AID
+ * can be registered by more than one service. The Android platform resolves AID
+ * conflicts depending on which category an AID belongs to. Each category may
+ * have a different conflict resolution policy. For example, for some categories
+ * the user may be able to select a default service in the Android settings UI.
+ * For other categories, to policy may be to always ask the user which service
+ * is to be invoked in case of conflict.
+ *
+ * To query the conflict resolution policy for a certain category, see
+ * {@link CardEmulation#getSelectionModeForCategory(String)}.
+ *
+ * <h3>Data exchange</h3>
+ * <p>Once the platform has resolved a "SELECT AID" command APDU to a specific
+ * service component, the "SELECT AID" command APDU and all subsequent
+ * command APDUs will be sent to that service through
+ * {@link #processCommandApdu(byte[], Bundle)}, until either:
+ * <ul>
+ * <li>The NFC link is broken</li>
+ * <li>A "SELECT AID" APDU is received which resolves to another service</li>
+ * </ul>
+ * These two scenarios are indicated by a call to {@link #onDeactivated(int)}.
+ *
+ * <p class="note">Use of this class requires the
+ * {@link PackageManager#FEATURE_NFC_HOST_CARD_EMULATION} to be present
+ * on the device.
+ *
+ */
+public abstract class HostApduService extends Service {
+ /**
+ * The {@link Intent} action that must be declared as handled by the service.
+ */
+ @SdkConstant(SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE =
+ "android.nfc.cardemulation.action.HOST_APDU_SERVICE";
+
+ /**
+ * The name of the meta-data element that contains
+ * more information about this service.
+ */
+ public static final String SERVICE_META_DATA =
+ "android.nfc.cardemulation.host_apdu_service";
+
+ /**
+ * Reason for {@link #onDeactivated(int)}.
+ * Indicates deactivation was due to the NFC link
+ * being lost.
+ */
+ public static final int DEACTIVATION_LINK_LOSS = 0;
+
+ /**
+ * Reason for {@link #onDeactivated(int)}.
+ *
+ * <p>Indicates deactivation was due to a different AID
+ * being selected (which implicitly deselects the AID
+ * currently active on the logical channel).
+ *
+ * <p>Note that this next AID may still be resolved to this
+ * service, in which case {@link #processCommandApdu(byte[], Bundle)}
+ * will be called again.
+ */
+ public static final int DEACTIVATION_DESELECTED = 1;
+
+ static final String TAG = "ApduService";
+
+ /**
+ * MSG_COMMAND_APDU is sent by NfcService when
+ * a 7816-4 command APDU has been received.
+ *
+ * @hide
+ */
+ public static final int MSG_COMMAND_APDU = 0;
+
+ /**
+ * MSG_RESPONSE_APDU is sent to NfcService to send
+ * a response APDU back to the remote device.
+ *
+ * @hide
+ */
+ public static final int MSG_RESPONSE_APDU = 1;
+
+ /**
+ * MSG_DEACTIVATED is sent by NfcService when
+ * the current session is finished; either because
+ * another AID was selected that resolved to
+ * another service, or because the NFC link
+ * was deactivated.
+ *
+ * @hide
+ */
+ public static final int MSG_DEACTIVATED = 2;
+
+ /**
+ *
+ * @hide
+ */
+ public static final int MSG_UNHANDLED = 3;
+
+ /**
+ * @hide
+ */
+ public static final int MSG_POLLING_LOOP = 4;
+
+ /**
+ * @hide
+ */
+ public static final String KEY_DATA = "data";
+
+ /**
+ * POLLING_LOOP_TYPE_KEY is the Bundle key for the type of
+ * polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public static final String POLLING_LOOP_TYPE_KEY = "android.nfc.cardemulation.TYPE";
+
+ /**
+ * POLLING_LOOP_TYPE_A is the value associated with the key
+ * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)}
+ * when the polling loop is for NFC-A.
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public static final char POLLING_LOOP_TYPE_A = 'A';
+
+ /**
+ * POLLING_LOOP_TYPE_B is the value associated with the key
+ * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)}
+ * when the polling loop is for NFC-B.
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public static final char POLLING_LOOP_TYPE_B = 'B';
+
+ /**
+ * POLLING_LOOP_TYPE_F is the value associated with the key
+ * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)}
+ * when the polling loop is for NFC-F.
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public static final char POLLING_LOOP_TYPE_F = 'F';
+
+ /**
+ * POLLING_LOOP_TYPE_ON is the value associated with the key
+ * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)}
+ * when the polling loop turns on.
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public static final char POLLING_LOOP_TYPE_ON = 'O';
+
+ /**
+ * POLLING_LOOP_TYPE_OFF is the value associated with the key
+ * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)}
+ * when the polling loop turns off.
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public static final char POLLING_LOOP_TYPE_OFF = 'X';
+
+ /**
+ * POLLING_LOOP_TYPE_UNKNOWN is the value associated with the key
+ * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)}
+ * when the polling loop frame isn't recognized.
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public static final char POLLING_LOOP_TYPE_UNKNOWN = 'U';
+
+ /**
+ * POLLING_LOOP_DATA is the Bundle key for the raw data of captured from
+ * the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
+ * when the frame type isn't recognized.
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public static final String POLLING_LOOP_DATA_KEY = "android.nfc.cardemulation.DATA";
+
+ /**
+ * POLLING_LOOP_GAIN_KEY is the Bundle key for the field strength of
+ * the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
+ * when the frame type isn't recognized.
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public static final String POLLING_LOOP_GAIN_KEY = "android.nfc.cardemulation.GAIN";
+
+ /**
+ * POLLING_LOOP_TIMESTAMP_KEY is the Bundle key for the timestamp of
+ * the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
+ * when the frame type isn't recognized.
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public static final String POLLING_LOOP_TIMESTAMP_KEY = "android.nfc.cardemulation.TIMESTAMP";
+
+ /**
+ * @hide
+ */
+ public static final String POLLING_LOOP_FRAMES_BUNDLE_KEY =
+ "android.nfc.cardemulation.POLLING_FRAMES";
+
+ /**
+ * Messenger interface to NfcService for sending responses.
+ * Only accessed on main thread by the message handler.
+ *
+ * @hide
+ */
+ Messenger mNfcService = null;
+
+ final Messenger mMessenger = new Messenger(new MsgHandler());
+
+ final class MsgHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_COMMAND_APDU:
+ Bundle dataBundle = msg.getData();
+ if (dataBundle == null) {
+ return;
+ }
+ if (mNfcService == null) mNfcService = msg.replyTo;
+
+ byte[] apdu = dataBundle.getByteArray(KEY_DATA);
+ if (apdu != null) {
+ HostApduService has = HostApduService.this;
+ byte[] responseApdu = processCommandApdu(apdu, null);
+ if (responseApdu != null) {
+ if (mNfcService == null) {
+ Log.e(TAG, "Response not sent; service was deactivated.");
+ return;
+ }
+ Message responseMsg = Message.obtain(null, MSG_RESPONSE_APDU);
+ Bundle responseBundle = new Bundle();
+ responseBundle.putByteArray(KEY_DATA, responseApdu);
+ responseMsg.setData(responseBundle);
+ responseMsg.replyTo = mMessenger;
+ try {
+ mNfcService.send(responseMsg);
+ } catch (RemoteException e) {
+ Log.e("TAG", "Response not sent; RemoteException calling into " +
+ "NfcService.");
+ }
+ }
+ } else {
+ Log.e(TAG, "Received MSG_COMMAND_APDU without data.");
+ }
+ break;
+ case MSG_RESPONSE_APDU:
+ if (mNfcService == null) {
+ Log.e(TAG, "Response not sent; service was deactivated.");
+ return;
+ }
+ try {
+ msg.replyTo = mMessenger;
+ mNfcService.send(msg);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException calling into NfcService.");
+ }
+ break;
+ case MSG_DEACTIVATED:
+ // Make sure we won't call into NfcService again
+ mNfcService = null;
+ onDeactivated(msg.arg1);
+ break;
+ case MSG_UNHANDLED:
+ if (mNfcService == null) {
+ Log.e(TAG, "notifyUnhandled not sent; service was deactivated.");
+ return;
+ }
+ try {
+ msg.replyTo = mMessenger;
+ mNfcService.send(msg);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException calling into NfcService.");
+ }
+ break;
+ case MSG_POLLING_LOOP:
+ ArrayList<Bundle> frames =
+ msg.getData().getParcelableArrayList(POLLING_LOOP_FRAMES_BUNDLE_KEY,
+ Bundle.class);
+ processPollingFrames(frames);
+ break;
+ default:
+ super.handleMessage(msg);
+ }
+ }
+ }
+
+ @Override
+ public final IBinder onBind(Intent intent) {
+ return mMessenger.getBinder();
+ }
+
+ /**
+ * Sends a response APDU back to the remote device.
+ *
+ * <p>Note: this method may be called from any thread and will not block.
+ * @param responseApdu A byte-array containing the reponse APDU.
+ */
+ public final void sendResponseApdu(byte[] responseApdu) {
+ Message responseMsg = Message.obtain(null, MSG_RESPONSE_APDU);
+ Bundle dataBundle = new Bundle();
+ dataBundle.putByteArray(KEY_DATA, responseApdu);
+ responseMsg.setData(dataBundle);
+ try {
+ mMessenger.send(responseMsg);
+ } catch (RemoteException e) {
+ Log.e("TAG", "Local messenger has died.");
+ }
+ }
+
+ /**
+ * Calling this method allows the service to tell the OS
+ * that it won't be able to complete this transaction -
+ * for example, because it requires data connectivity
+ * that is not present at that moment.
+ *
+ * The OS may use this indication to give the user a list
+ * of alternative applications that can handle the last
+ * AID that was selected. If the user would select an
+ * application from the list, that action by itself
+ * will not cause the default to be changed; the selected
+ * application will be invoked for the next tap only.
+ *
+ * If there are no other applications that can handle
+ * this transaction, the OS will show an error dialog
+ * indicating your service could not complete the
+ * transaction.
+ *
+ * <p>Note: this method may be called anywhere between
+ * the first {@link #processCommandApdu(byte[], Bundle)}
+ * call and a {@link #onDeactivated(int)} call.
+ */
+ public final void notifyUnhandled() {
+ Message unhandledMsg = Message.obtain(null, MSG_UNHANDLED);
+ try {
+ mMessenger.send(unhandledMsg);
+ } catch (RemoteException e) {
+ Log.e("TAG", "Local messenger has died.");
+ }
+ }
+
+ /**
+ * This method is called when a polling frame has been received from a
+ * remote device. If the device is in observe mode, the service should
+ * call {@link NfcAdapter#allowTransaction()} once it is ready to proceed
+ * with the transaction. If the device is not in observe mode, the service
+ * can use this polling frame information to determine how to proceed if it
+ * subsequently has {@link #processCommandApdu(byte[], Bundle)} called. The
+ * service must override this method inorder to receive polling frames,
+ * otherwise the base implementation drops the frame.
+ *
+ * @param frame A description of the polling frame.
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public void processPollingFrames(@NonNull List<Bundle> frame) {
+ }
+
+ /**
+ * <p>This method will be called when a command APDU has been received
+ * from a remote device. A response APDU can be provided directly
+ * by returning a byte-array in this method. Note that in general
+ * response APDUs must be sent as quickly as possible, given the fact
+ * that the user is likely holding their device over an NFC reader
+ * when this method is called.
+ *
+ * <p class="note">If there are multiple services that have registered for the same
+ * AIDs in their meta-data entry, you will only get called if the user has
+ * explicitly selected your service, either as a default or just for the next tap.
+ *
+ * <p class="note">This method is running on the main thread of your application.
+ * If you cannot return a response APDU immediately, return null
+ * and use the {@link #sendResponseApdu(byte[])} method later.
+ *
+ * @param commandApdu The APDU that was received from the remote device
+ * @param extras A bundle containing extra data. May be null.
+ * @return a byte-array containing the response APDU, or null if no
+ * response APDU can be sent at this point.
+ */
+ public abstract byte[] processCommandApdu(byte[] commandApdu, Bundle extras);
+
+ /**
+ * This method will be called in two possible scenarios:
+ * <li>The NFC link has been deactivated or lost
+ * <li>A different AID has been selected and was resolved to a different
+ * service component
+ * @param reason Either {@link #DEACTIVATION_LINK_LOSS} or {@link #DEACTIVATION_DESELECTED}
+ */
+ public abstract void onDeactivated(int reason);
+}
diff --git a/nfc/java/android/nfc/cardemulation/HostNfcFService.java b/nfc/java/android/nfc/cardemulation/HostNfcFService.java
new file mode 100644
index 0000000..65b5ca7
--- /dev/null
+++ b/nfc/java/android/nfc/cardemulation/HostNfcFService.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2015 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.cardemulation;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * <p>HostNfcFService is a convenience {@link Service} class that can be
+ * extended to emulate an NFC-F card inside an Android service component.
+ *
+ * <h3>NFC Protocols</h3>
+ * <p>Cards emulated by this class are based on the NFC-Forum NFC-F
+ * protocol (based on the JIS-X 6319-4 specification.)</p>
+ *
+ * <h3>System Code and NFCID2 registration</h3>
+ * <p>A {@link HostNfcFService HostNfcFService service} can register
+ * exactly one System Code and one NFCID2. For details about the use of
+ * System Code and NFCID2, see the NFC Forum Digital specification.</p>
+ * <p>To statically register a System Code and NFCID2 with the service, a {@link #SERVICE_META_DATA}
+ * entry must be included in the declaration of the service.
+ *
+ * <p>All {@link HostNfcFService HostNfcFService} declarations in the manifest must require the
+ * {@link android.Manifest.permission#BIND_NFC_SERVICE} permission
+ * in their <service> tag, to ensure that only the platform can bind to your service.</p>
+ *
+ * <p>An example of a HostNfcFService manifest declaration is shown below:
+ *
+ * <pre> <service android:name=".MyHostNfcFService" android:exported="true" android:permission="android.permission.BIND_NFC_SERVICE">
+ * <intent-filter>
+ * <action android:name="android.nfc.cardemulation.action.HOST_NFCF_SERVICE"/>
+ * </intent-filter>
+ * <meta-data android:name="android.nfc.cardemulation.host_nfcf_service" android:resource="@xml/nfcfservice"/>
+ * </service></pre>
+ *
+ * This meta-data tag points to an nfcfservice.xml file.
+ * An example of this file with a System Code and NFCID2 declaration is shown below:
+ * <pre>
+ * <host-nfcf-service xmlns:android="http://schemas.android.com/apk/res/android"
+ * android:description="@string/servicedesc">
+ * <system-code-filter android:name="4000"/>
+ * <nfcid2-filter android:name="02FE000000000000"/>
+ <t3tPmm-filter android:name="FFFFFFFFFFFFFFFF"/>
+ * </host-nfcf-service>
+ * </pre>
+ *
+ * <p>The {@link android.R.styleable#HostNfcFService <host-nfcf-service>} is required
+ * to contain a
+ * {@link android.R.styleable#HostApduService_description <android:description>}
+ * attribute that contains a user-friendly description of the service that may be shown in UI.
+ * <p>The {@link android.R.styleable#HostNfcFService <host-nfcf-service>} must
+ * contain:
+ * <ul>
+ * <li>Exactly one {@link android.R.styleable#SystemCodeFilter <system-code-filter>} tag.</li>
+ * <li>Exactly one {@link android.R.styleable#Nfcid2Filter <nfcid2-filter>} tag.</li>
+ * <li>Zero or one {@link android.R.styleable#T3tPmmFilter <t3tPmm-filter>} tag.</li>
+ * </ul>
+ * </p>
+ *
+ * <p>Alternatively, the System Code and NFCID2 can be dynamically registererd for a service
+ * by using the {@link NfcFCardEmulation#registerSystemCodeForService(ComponentName, String)} and
+ * {@link NfcFCardEmulation#setNfcid2ForService(ComponentName, String)} methods.
+ * </p>
+ *
+ * <h3>Service selection</h3>
+ * <p>When a remote NFC devices wants to communicate with your service, it
+ * sends a SENSF_REQ command to the NFC controller, requesting a System Code.
+ * If a {@link NfcFCardEmulation NfcFCardEmulation service} has registered
+ * this system code and has been enabled by the foreground application, the
+ * NFC controller will respond with the NFCID2 that is registered for this service.
+ * The reader can then continue data exchange with this service by using the NFCID2.</p>
+ *
+ * <h3>Data exchange</h3>
+ * <p>After service selection, all frames addressed to the NFCID2 of this service will
+ * be sent through {@link #processNfcFPacket(byte[], Bundle)}, until the NFC link is
+ * broken.<p>
+ *
+ * <p>When the NFC link is broken, {@link #onDeactivated(int)} will be called.</p>
+ */
+public abstract class HostNfcFService extends Service {
+ /**
+ * The {@link Intent} action that must be declared as handled by the service.
+ */
+ @SdkConstant(SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE =
+ "android.nfc.cardemulation.action.HOST_NFCF_SERVICE";
+
+ /**
+ * The name of the meta-data element that contains
+ * more information about this service.
+ */
+ public static final String SERVICE_META_DATA =
+ "android.nfc.cardemulation.host_nfcf_service";
+
+ /**
+ * Reason for {@link #onDeactivated(int)}.
+ * Indicates deactivation was due to the NFC link
+ * being lost.
+ */
+ public static final int DEACTIVATION_LINK_LOSS = 0;
+
+ static final String TAG = "NfcFService";
+
+ /**
+ * MSG_COMMAND_PACKET is sent by NfcService when
+ * a NFC-F command packet has been received.
+ *
+ * @hide
+ */
+ public static final int MSG_COMMAND_PACKET = 0;
+
+ /**
+ * MSG_RESPONSE_PACKET is sent to NfcService to send
+ * a response packet back to the remote device.
+ *
+ * @hide
+ */
+ public static final int MSG_RESPONSE_PACKET = 1;
+
+ /**
+ * MSG_DEACTIVATED is sent by NfcService when
+ * the current session is finished; because
+ * the NFC link was deactivated.
+ *
+ * @hide
+ */
+ public static final int MSG_DEACTIVATED = 2;
+
+ /**
+ * @hide
+ */
+ public static final String KEY_DATA = "data";
+
+ /**
+ * @hide
+ */
+ public static final String KEY_MESSENGER = "messenger";
+
+ /**
+ * Messenger interface to NfcService for sending responses.
+ * Only accessed on main thread by the message handler.
+ *
+ * @hide
+ */
+ Messenger mNfcService = null;
+
+ final Messenger mMessenger = new Messenger(new MsgHandler());
+
+ final class MsgHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_COMMAND_PACKET:
+ Bundle dataBundle = msg.getData();
+ if (dataBundle == null) {
+ return;
+ }
+ if (mNfcService == null) mNfcService = msg.replyTo;
+
+ byte[] packet = dataBundle.getByteArray(KEY_DATA);
+ if (packet != null) {
+ byte[] responsePacket = processNfcFPacket(packet, null);
+ if (responsePacket != null) {
+ if (mNfcService == null) {
+ Log.e(TAG, "Response not sent; service was deactivated.");
+ return;
+ }
+ Message responseMsg = Message.obtain(null, MSG_RESPONSE_PACKET);
+ Bundle responseBundle = new Bundle();
+ responseBundle.putByteArray(KEY_DATA, responsePacket);
+ responseMsg.setData(responseBundle);
+ responseMsg.replyTo = mMessenger;
+ try {
+ mNfcService.send(responseMsg);
+ } catch (RemoteException e) {
+ Log.e("TAG", "Response not sent; RemoteException calling into " +
+ "NfcService.");
+ }
+ }
+ } else {
+ Log.e(TAG, "Received MSG_COMMAND_PACKET without data.");
+ }
+ break;
+ case MSG_RESPONSE_PACKET:
+ if (mNfcService == null) {
+ Log.e(TAG, "Response not sent; service was deactivated.");
+ return;
+ }
+ try {
+ msg.replyTo = mMessenger;
+ mNfcService.send(msg);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException calling into NfcService.");
+ }
+ break;
+ case MSG_DEACTIVATED:
+ // Make sure we won't call into NfcService again
+ mNfcService = null;
+ onDeactivated(msg.arg1);
+ break;
+ default:
+ super.handleMessage(msg);
+ }
+ }
+ }
+
+ @Override
+ public final IBinder onBind(Intent intent) {
+ return mMessenger.getBinder();
+ }
+
+ /**
+ * Sends a response packet back to the remote device.
+ *
+ * <p>Note: this method may be called from any thread and will not block.
+ * @param responsePacket A byte-array containing the response packet.
+ */
+ public final void sendResponsePacket(byte[] responsePacket) {
+ Message responseMsg = Message.obtain(null, MSG_RESPONSE_PACKET);
+ Bundle dataBundle = new Bundle();
+ dataBundle.putByteArray(KEY_DATA, responsePacket);
+ responseMsg.setData(dataBundle);
+ try {
+ mMessenger.send(responseMsg);
+ } catch (RemoteException e) {
+ Log.e("TAG", "Local messenger has died.");
+ }
+ }
+
+ /**
+ * <p>This method will be called when a NFC-F packet has been received
+ * from a remote device. A response packet can be provided directly
+ * by returning a byte-array in this method. Note that in general
+ * response packets must be sent as quickly as possible, given the fact
+ * that the user is likely holding their device over an NFC reader
+ * when this method is called.
+ *
+ * <p class="note">This method is running on the main thread of your application.
+ * If you cannot return a response packet immediately, return null
+ * and use the {@link #sendResponsePacket(byte[])} method later.
+ *
+ * @param commandPacket The NFC-F packet that was received from the remote device
+ * @param extras A bundle containing extra data. May be null.
+ * @return a byte-array containing the response packet, or null if no
+ * response packet can be sent at this point.
+ */
+ public abstract byte[] processNfcFPacket(byte[] commandPacket, Bundle extras);
+
+ /**
+ * This method will be called in following possible scenarios:
+ * <li>The NFC link has been lost
+ * @param reason {@link #DEACTIVATION_LINK_LOSS}
+ */
+ public abstract void onDeactivated(int reason);
+}
diff --git a/nfc/java/android/nfc/cardemulation/NfcFCardEmulation.java b/nfc/java/android/nfc/cardemulation/NfcFCardEmulation.java
new file mode 100644
index 0000000..48bbf5b6
--- /dev/null
+++ b/nfc/java/android/nfc/cardemulation/NfcFCardEmulation.java
@@ -0,0 +1,473 @@
+/*
+ * Copyright (C) 2015 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.cardemulation;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.nfc.INfcFCardEmulation;
+import android.nfc.NfcAdapter;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * This class can be used to query the state of
+ * NFC-F card emulation services.
+ *
+ * For a general introduction into NFC card emulation,
+ * please read the <a href="{@docRoot}guide/topics/connectivity/nfc/hce.html">
+ * NFC card emulation developer guide</a>.</p>
+ *
+ * <p class="note">Use of this class requires the
+ * {@link PackageManager#FEATURE_NFC_HOST_CARD_EMULATION_NFCF}
+ * to be present on the device.
+ */
+public final class NfcFCardEmulation {
+ static final String TAG = "NfcFCardEmulation";
+
+ static boolean sIsInitialized = false;
+ static HashMap<Context, NfcFCardEmulation> sCardEmus = new HashMap<Context, NfcFCardEmulation>();
+ static INfcFCardEmulation sService;
+
+ final Context mContext;
+
+ private NfcFCardEmulation(Context context, INfcFCardEmulation service) {
+ mContext = context.getApplicationContext();
+ sService = service;
+ }
+
+ /**
+ * Helper to get an instance of this class.
+ *
+ * @param adapter A reference to an NfcAdapter object.
+ * @return
+ */
+ public static synchronized NfcFCardEmulation getInstance(NfcAdapter adapter) {
+ if (adapter == null) throw new NullPointerException("NfcAdapter is null");
+ Context context = adapter.getContext();
+ if (context == null) {
+ Log.e(TAG, "NfcAdapter context is null.");
+ throw new UnsupportedOperationException();
+ }
+ if (!sIsInitialized) {
+ PackageManager pm = context.getPackageManager();
+ if (pm == null) {
+ Log.e(TAG, "Cannot get PackageManager");
+ throw new UnsupportedOperationException();
+ }
+ if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF)) {
+ Log.e(TAG, "This device does not support NFC-F card emulation");
+ throw new UnsupportedOperationException();
+ }
+ sIsInitialized = true;
+ }
+ NfcFCardEmulation manager = sCardEmus.get(context);
+ if (manager == null) {
+ // Get card emu service
+ INfcFCardEmulation service = adapter.getNfcFCardEmulationService();
+ if (service == null) {
+ Log.e(TAG, "This device does not implement the INfcFCardEmulation interface.");
+ throw new UnsupportedOperationException();
+ }
+ manager = new NfcFCardEmulation(context, service);
+ sCardEmus.put(context, manager);
+ }
+ return manager;
+ }
+
+ /**
+ * Retrieves the current System Code for the specified service.
+ *
+ * <p>Before calling {@link #registerSystemCodeForService(ComponentName, String)},
+ * the System Code contained in the Manifest file is returned. After calling
+ * {@link #registerSystemCodeForService(ComponentName, String)}, the System Code
+ * registered there is returned. After calling
+ * {@link #unregisterSystemCodeForService(ComponentName)}, "null" is returned.
+ *
+ * @param service The component name of the service
+ * @return the current System Code
+ */
+ public String getSystemCodeForService(ComponentName service) throws RuntimeException {
+ if (service == null) {
+ throw new NullPointerException("service is null");
+ }
+ try {
+ return sService.getSystemCodeForService(mContext.getUser().getIdentifier(), service);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return null;
+ }
+ try {
+ return sService.getSystemCodeForService(mContext.getUser().getIdentifier(),
+ service);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ ee.rethrowAsRuntimeException();
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Registers a System Code for the specified service.
+ *
+ * <p>The System Code must be in range from "4000" to "4FFF" (excluding "4*FF").
+ *
+ * <p>If a System Code was previously registered for this service
+ * (either statically through the manifest, or dynamically by using this API),
+ * it will be replaced with this one.
+ *
+ * <p>Even if the same System Code is already registered for another service,
+ * this method succeeds in registering the System Code.
+ *
+ * <p>Note that you can only register a System Code for a service that
+ * is running under the same UID as the caller of this API. Typically
+ * this means you need to call this from the same
+ * package as the service itself, though UIDs can also
+ * be shared between packages using shared UIDs.
+ *
+ * @param service The component name of the service
+ * @param systemCode The System Code to be registered
+ * @return whether the registration was successful.
+ */
+ public boolean registerSystemCodeForService(ComponentName service, String systemCode)
+ throws RuntimeException {
+ if (service == null || systemCode == null) {
+ throw new NullPointerException("service or systemCode is null");
+ }
+ try {
+ return sService.registerSystemCodeForService(mContext.getUser().getIdentifier(),
+ service, systemCode);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ try {
+ return sService.registerSystemCodeForService(mContext.getUser().getIdentifier(),
+ service, systemCode);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ ee.rethrowAsRuntimeException();
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Removes a registered System Code for the specified service.
+ *
+ * @param service The component name of the service
+ * @return whether the System Code was successfully removed.
+ */
+ public boolean unregisterSystemCodeForService(ComponentName service) throws RuntimeException {
+ if (service == null) {
+ throw new NullPointerException("service is null");
+ }
+ try {
+ return sService.removeSystemCodeForService(mContext.getUser().getIdentifier(), service);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ try {
+ return sService.removeSystemCodeForService(mContext.getUser().getIdentifier(),
+ service);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ ee.rethrowAsRuntimeException();
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Retrieves the current NFCID2 for the specified service.
+ *
+ * <p>Before calling {@link #setNfcid2ForService(ComponentName, String)},
+ * the NFCID2 contained in the Manifest file is returned. If "random" is specified
+ * in the Manifest file, a random number assigned by the system at installation time
+ * is returned. After setting an NFCID2
+ * with {@link #setNfcid2ForService(ComponentName, String)}, this NFCID2 is returned.
+ *
+ * @param service The component name of the service
+ * @return the current NFCID2
+ */
+ public String getNfcid2ForService(ComponentName service) throws RuntimeException {
+ if (service == null) {
+ throw new NullPointerException("service is null");
+ }
+ try {
+ return sService.getNfcid2ForService(mContext.getUser().getIdentifier(), service);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return null;
+ }
+ try {
+ return sService.getNfcid2ForService(mContext.getUser().getIdentifier(), service);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ ee.rethrowAsRuntimeException();
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Set a NFCID2 for the specified service.
+ *
+ * <p>The NFCID2 must be in range from "02FE000000000000" to "02FEFFFFFFFFFFFF".
+ *
+ * <p>If a NFCID2 was previously set for this service
+ * (either statically through the manifest, or dynamically by using this API),
+ * it will be replaced.
+ *
+ * <p>Note that you can only set the NFCID2 for a service that
+ * is running under the same UID as the caller of this API. Typically
+ * this means you need to call this from the same
+ * package as the service itself, though UIDs can also
+ * be shared between packages using shared UIDs.
+ *
+ * @param service The component name of the service
+ * @param nfcid2 The NFCID2 to be registered
+ * @return whether the setting was successful.
+ */
+ public boolean setNfcid2ForService(ComponentName service, String nfcid2)
+ throws RuntimeException {
+ if (service == null || nfcid2 == null) {
+ throw new NullPointerException("service or nfcid2 is null");
+ }
+ try {
+ return sService.setNfcid2ForService(mContext.getUser().getIdentifier(),
+ service, nfcid2);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ try {
+ return sService.setNfcid2ForService(mContext.getUser().getIdentifier(),
+ service, nfcid2);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ ee.rethrowAsRuntimeException();
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Allows a foreground application to specify which card emulation service
+ * should be enabled while a specific Activity is in the foreground.
+ *
+ * <p>The specified HCE-F service is only enabled when the corresponding application is
+ * in the foreground and this method has been called. When the application is moved to
+ * the background, {@link #disableService(Activity)} is called, or
+ * NFCID2 or System Code is replaced, the HCE-F service is disabled.
+ *
+ * <p>The specified Activity must currently be in resumed state. A good
+ * paradigm is to call this method in your {@link Activity#onResume}, and to call
+ * {@link #disableService(Activity)} in your {@link Activity#onPause}.
+ *
+ * <p>Note that this preference is not persisted by the OS, and hence must be
+ * called every time the Activity is resumed.
+ *
+ * @param activity The activity which prefers this service to be invoked
+ * @param service The service to be preferred while this activity is in the foreground
+ * @return whether the registration was successful
+ */
+ public boolean enableService(Activity activity, ComponentName service) throws RuntimeException {
+ if (activity == null || service == null) {
+ throw new NullPointerException("activity or service is null");
+ }
+ // Verify the activity is in the foreground before calling into NfcService
+ if (!activity.isResumed()) {
+ throw new IllegalArgumentException("Activity must be resumed.");
+ }
+ try {
+ return sService.enableNfcFForegroundService(service);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ try {
+ return sService.enableNfcFForegroundService(service);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ ee.rethrowAsRuntimeException();
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Disables the service for the specified Activity.
+ *
+ * <p>Note that the specified Activity must still be in resumed
+ * state at the time of this call. A good place to call this method
+ * is in your {@link Activity#onPause} implementation.
+ *
+ * @param activity The activity which the service was registered for
+ * @return true when successful
+ */
+ public boolean disableService(Activity activity) throws RuntimeException {
+ if (activity == null) {
+ throw new NullPointerException("activity is null");
+ }
+ if (!activity.isResumed()) {
+ throw new IllegalArgumentException("Activity must be resumed.");
+ }
+ try {
+ return sService.disableNfcFForegroundService();
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ try {
+ return sService.disableNfcFForegroundService();
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ ee.rethrowAsRuntimeException();
+ return false;
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public List<NfcFServiceInfo> getNfcFServices() {
+ try {
+ return sService.getNfcFServices(mContext.getUser().getIdentifier());
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return null;
+ }
+ try {
+ return sService.getNfcFServices(mContext.getUser().getIdentifier());
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ return null;
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public int getMaxNumOfRegisterableSystemCodes() {
+ try {
+ return sService.getMaxNumOfRegisterableSystemCodes();
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return -1;
+ }
+ try {
+ return sService.getMaxNumOfRegisterableSystemCodes();
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ return -1;
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static boolean isValidSystemCode(String systemCode) {
+ if (systemCode == null) {
+ return false;
+ }
+ if (systemCode.length() != 4) {
+ Log.e(TAG, "System Code " + systemCode + " is not a valid System Code.");
+ return false;
+ }
+ // check if the value is between "4000" and "4FFF" (excluding "4*FF")
+ if (!systemCode.startsWith("4") || systemCode.toUpperCase().endsWith("FF")) {
+ Log.e(TAG, "System Code " + systemCode + " is not a valid System Code.");
+ return false;
+ }
+ try {
+ Integer.parseInt(systemCode, 16);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "System Code " + systemCode + " is not a valid System Code.");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * @hide
+ */
+ public static boolean isValidNfcid2(String nfcid2) {
+ if (nfcid2 == null) {
+ return false;
+ }
+ if (nfcid2.length() != 16) {
+ Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2.");
+ return false;
+ }
+ // check if the the value starts with "02FE"
+ if (!nfcid2.toUpperCase().startsWith("02FE")) {
+ Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2.");
+ return false;
+ }
+ try {
+ Long.parseLong(nfcid2, 16);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2.");
+ return false;
+ }
+ return true;
+ }
+
+ void recoverService() {
+ NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
+ sService = adapter.getNfcFCardEmulationService();
+ }
+
+}
+
diff --git a/nfc/java/android/nfc/cardemulation/NfcFServiceInfo.aidl b/nfc/java/android/nfc/cardemulation/NfcFServiceInfo.aidl
new file mode 100644
index 0000000..56b98eb
--- /dev/null
+++ b/nfc/java/android/nfc/cardemulation/NfcFServiceInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2015 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.cardemulation;
+
+parcelable NfcFServiceInfo;
diff --git a/nfc/java/android/nfc/cardemulation/NfcFServiceInfo.java b/nfc/java/android/nfc/cardemulation/NfcFServiceInfo.java
new file mode 100644
index 0000000..33bc169
--- /dev/null
+++ b/nfc/java/android/nfc/cardemulation/NfcFServiceInfo.java
@@ -0,0 +1,497 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+/**********************************************************************
+ * This file is not a part of the NFC mainline module *
+ * *******************************************************************/
+
+package android.nfc.cardemulation;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.nfc.Flags;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+import android.util.proto.ProtoOutputStream;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ * Class to hold NfcF service info.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+public final class NfcFServiceInfo implements Parcelable {
+ static final String TAG = "NfcFServiceInfo";
+
+ private static final String DEFAULT_T3T_PMM = "FFFFFFFFFFFFFFFF";
+
+ /**
+ * The service that implements this
+ */
+ private final ResolveInfo mService;
+
+ /**
+ * Description of the service
+ */
+ private final String mDescription;
+
+ /**
+ * System Code of the service
+ */
+ private final String mSystemCode;
+
+ /**
+ * System Code of the service registered by API
+ */
+ private String mDynamicSystemCode;
+
+ /**
+ * NFCID2 of the service
+ */
+ private final String mNfcid2;
+
+ /**
+ * NFCID2 of the service registered by API
+ */
+ private String mDynamicNfcid2;
+
+ /**
+ * The uid of the package the service belongs to
+ */
+ private final int mUid;
+
+ /**
+ * LF_T3T_PMM of the service
+ */
+ private final String mT3tPmm;
+
+ /**
+ * @hide
+ */
+ public NfcFServiceInfo(ResolveInfo info, String description,
+ String systemCode, String dynamicSystemCode, String nfcid2, String dynamicNfcid2,
+ int uid, String t3tPmm) {
+ this.mService = info;
+ this.mDescription = description;
+ this.mSystemCode = systemCode;
+ this.mDynamicSystemCode = dynamicSystemCode;
+ this.mNfcid2 = nfcid2;
+ this.mDynamicNfcid2 = dynamicNfcid2;
+ this.mUid = uid;
+ this.mT3tPmm = t3tPmm;
+ }
+
+ /**
+ * Creates a new NfcFServiceInfo object.
+ *
+ * @param pm packageManager instance
+ * @param info app component info
+ * @throws XmlPullParserException If an error occurs parsing the element.
+ * @throws IOException If an error occurs reading the element.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ public NfcFServiceInfo(@NonNull PackageManager pm, @NonNull ResolveInfo info)
+ throws XmlPullParserException, IOException {
+ ServiceInfo si = info.serviceInfo;
+ XmlResourceParser parser = null;
+ try {
+ parser = si.loadXmlMetaData(pm, HostNfcFService.SERVICE_META_DATA);
+ if (parser == null) {
+ throw new XmlPullParserException("No " + HostNfcFService.SERVICE_META_DATA +
+ " meta-data");
+ }
+
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.START_TAG &&
+ eventType != XmlPullParser.END_DOCUMENT) {
+ eventType = parser.next();
+ }
+
+ String tagName = parser.getName();
+ if (!"host-nfcf-service".equals(tagName)) {
+ throw new XmlPullParserException(
+ "Meta-data does not start with <host-nfcf-service> tag");
+ }
+
+ Resources res = pm.getResourcesForApplication(si.applicationInfo);
+ AttributeSet attrs = Xml.asAttributeSet(parser);
+ TypedArray sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.HostNfcFService);
+ mService = info;
+ mDescription = sa.getString(
+ com.android.internal.R.styleable.HostNfcFService_description);
+ mDynamicSystemCode = null;
+ mDynamicNfcid2 = null;
+ sa.recycle();
+
+ String systemCode = null;
+ String nfcid2 = null;
+ String t3tPmm = null;
+ final int depth = parser.getDepth();
+
+ while (((eventType = parser.next()) != XmlPullParser.END_TAG ||
+ parser.getDepth() > depth) && eventType != XmlPullParser.END_DOCUMENT) {
+ tagName = parser.getName();
+ if (eventType == XmlPullParser.START_TAG &&
+ "system-code-filter".equals(tagName) && systemCode == null) {
+ final TypedArray a = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.SystemCodeFilter);
+ systemCode = a.getString(
+ com.android.internal.R.styleable.SystemCodeFilter_name).toUpperCase();
+ if (!isValidSystemCode(systemCode) &&
+ !systemCode.equalsIgnoreCase("NULL")) {
+ Log.e(TAG, "Invalid System Code: " + systemCode);
+ systemCode = null;
+ }
+ a.recycle();
+ } else if (eventType == XmlPullParser.START_TAG &&
+ "nfcid2-filter".equals(tagName) && nfcid2 == null) {
+ final TypedArray a = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.Nfcid2Filter);
+ nfcid2 = a.getString(
+ com.android.internal.R.styleable.Nfcid2Filter_name).toUpperCase();
+ if (!nfcid2.equalsIgnoreCase("RANDOM") &&
+ !nfcid2.equalsIgnoreCase("NULL") &&
+ !isValidNfcid2(nfcid2)) {
+ Log.e(TAG, "Invalid NFCID2: " + nfcid2);
+ nfcid2 = null;
+ }
+ a.recycle();
+ } else if (eventType == XmlPullParser.START_TAG && tagName.equals("t3tPmm-filter")
+ && t3tPmm == null) {
+ final TypedArray a = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.T3tPmmFilter);
+ t3tPmm = a.getString(
+ com.android.internal.R.styleable.T3tPmmFilter_name).toUpperCase();
+ a.recycle();
+ }
+ }
+ mSystemCode = (systemCode == null ? "NULL" : systemCode);
+ mNfcid2 = (nfcid2 == null ? "NULL" : nfcid2);
+ mT3tPmm = (t3tPmm == null ? DEFAULT_T3T_PMM : t3tPmm);
+ } catch (NameNotFoundException e) {
+ throw new XmlPullParserException("Unable to create context for: " + si.packageName);
+ } finally {
+ if (parser != null) parser.close();
+ }
+ // Set uid
+ mUid = si.applicationInfo.uid;
+ }
+
+ /**
+ * Returns the app component corresponding to this NFCF service.
+ *
+ * @return app component for this service
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @NonNull
+ public ComponentName getComponent() {
+ return new ComponentName(mService.serviceInfo.packageName,
+ mService.serviceInfo.name);
+ }
+
+ /**
+ * Returns the system code corresponding to this service.
+ *
+ * @return system code for this service
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @NonNull
+ public String getSystemCode() {
+ return (mDynamicSystemCode == null ? mSystemCode : mDynamicSystemCode);
+ }
+
+ /**
+ * Add or replace a system code to this service.
+ * @param systemCode system code to set or replace
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ public void setDynamicSystemCode(@NonNull String systemCode) {
+ mDynamicSystemCode = systemCode;
+ }
+
+ /**
+ * Returns NFC ID2.
+ *
+ * @return nfc id2 to return
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @NonNull
+ public String getNfcid2() {
+ return (mDynamicNfcid2 == null ? mNfcid2 : mDynamicNfcid2);
+ }
+
+ /**
+ * Set or replace NFC ID2
+ *
+ * @param nfcid2 NFC ID2 string
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ public void setDynamicNfcid2(@NonNull String nfcid2) {
+ mDynamicNfcid2 = nfcid2;
+ }
+
+ /**
+ * Returns description of service.
+ * @return user readable description of service
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @NonNull
+ public String getDescription() {
+ return mDescription;
+ }
+
+ /**
+ * Returns uid of service.
+ * @return uid of the service
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ public int getUid() {
+ return mUid;
+ }
+
+ /**
+ * Returns LF_T3T_PMM of the service
+ * @return returns LF_T3T_PMM of the service
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @NonNull
+ public String getT3tPmm() {
+ return mT3tPmm;
+ }
+
+ /**
+ * Load application label for this service.
+ * @param pm packagemanager instance
+ * @return label name corresponding to service
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @NonNull
+ public CharSequence loadLabel(@NonNull PackageManager pm) {
+ return mService.loadLabel(pm);
+ }
+
+ /**
+ * Load application icon for this service.
+ * @param pm packagemanager instance
+ * @return app icon corresponding to service
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @NonNull
+ public Drawable loadIcon(@NonNull PackageManager pm) {
+ return mService.loadIcon(pm);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder out = new StringBuilder("NfcFService: ");
+ out.append(getComponent());
+ out.append(", UID: " + mUid);
+ out.append(", description: " + mDescription);
+ out.append(", System Code: " + mSystemCode);
+ if (mDynamicSystemCode != null) {
+ out.append(", dynamic System Code: " + mDynamicSystemCode);
+ }
+ out.append(", NFCID2: " + mNfcid2);
+ if (mDynamicNfcid2 != null) {
+ out.append(", dynamic NFCID2: " + mDynamicNfcid2);
+ }
+ out.append(", T3T PMM:" + mT3tPmm);
+ return out.toString();
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (!(o instanceof NfcFServiceInfo)) return false;
+ NfcFServiceInfo thatService = (NfcFServiceInfo) o;
+
+ if (!thatService.getComponent().equals(this.getComponent())) return false;
+ if (thatService.getUid() != this.getUid()) return false;
+ if (!thatService.mSystemCode.equalsIgnoreCase(this.mSystemCode)) return false;
+ if (!thatService.mNfcid2.equalsIgnoreCase(this.mNfcid2)) return false;
+ if (!thatService.mT3tPmm.equalsIgnoreCase(this.mT3tPmm)) return false;
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return getComponent().hashCode();
+ }
+
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ mService.writeToParcel(dest, flags);
+ dest.writeString(mDescription);
+ dest.writeString(mSystemCode);
+ dest.writeInt(mDynamicSystemCode != null ? 1 : 0);
+ if (mDynamicSystemCode != null) {
+ dest.writeString(mDynamicSystemCode);
+ }
+ dest.writeString(mNfcid2);
+ dest.writeInt(mDynamicNfcid2 != null ? 1 : 0);
+ if (mDynamicNfcid2 != null) {
+ dest.writeString(mDynamicNfcid2);
+ }
+ dest.writeInt(mUid);
+ dest.writeString(mT3tPmm);
+ };
+
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ public static final @NonNull Parcelable.Creator<NfcFServiceInfo> CREATOR =
+ new Parcelable.Creator<NfcFServiceInfo>() {
+ @Override
+ public NfcFServiceInfo createFromParcel(Parcel source) {
+ ResolveInfo info = ResolveInfo.CREATOR.createFromParcel(source);
+ String description = source.readString();
+ String systemCode = source.readString();
+ String dynamicSystemCode = null;
+ if (source.readInt() != 0) {
+ dynamicSystemCode = source.readString();
+ }
+ String nfcid2 = source.readString();
+ String dynamicNfcid2 = null;
+ if (source.readInt() != 0) {
+ dynamicNfcid2 = source.readString();
+ }
+ int uid = source.readInt();
+ String t3tPmm = source.readString();
+ NfcFServiceInfo service = new NfcFServiceInfo(info, description,
+ systemCode, dynamicSystemCode, nfcid2, dynamicNfcid2, uid, t3tPmm);
+ return service;
+ }
+
+ @Override
+ public NfcFServiceInfo[] newArray(int size) {
+ return new NfcFServiceInfo[size];
+ }
+ };
+
+ /**
+ * Dump contents of the service for debugging.
+ * @param fd parcelfiledescriptor instance
+ * @param pw printwriter instance
+ * @param args args for dumping
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ public void dump(@NonNull ParcelFileDescriptor fd, @NonNull PrintWriter pw,
+ @NonNull String[] args) {
+ pw.println(" " + getComponent()
+ + " (Description: " + getDescription() + ")"
+ + " (UID: " + getUid() + ")");
+ pw.println(" System Code: " + getSystemCode());
+ pw.println(" NFCID2: " + getNfcid2());
+ pw.println(" T3tPmm: " + getT3tPmm());
+ }
+
+ /**
+ * Dump debugging info as NfcFServiceInfoProto.
+ *
+ * If the output belongs to a sub message, the caller is responsible for wrapping this function
+ * between {@link ProtoOutputStream#start(long)} and {@link ProtoOutputStream#end(long)}.
+ *
+ * @param proto the ProtoOutputStream to write to
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ public void dumpDebug(@NonNull ProtoOutputStream proto) {
+ getComponent().dumpDebug(proto, NfcFServiceInfoProto.COMPONENT_NAME);
+ proto.write(NfcFServiceInfoProto.DESCRIPTION, getDescription());
+ proto.write(NfcFServiceInfoProto.SYSTEM_CODE, getSystemCode());
+ proto.write(NfcFServiceInfoProto.NFCID2, getNfcid2());
+ proto.write(NfcFServiceInfoProto.T3T_PMM, getT3tPmm());
+ }
+
+ /**
+ * Copied over from {@link NfcFCardEmulation#isValidSystemCode(String)}
+ * @hide
+ */
+ private static boolean isValidSystemCode(String systemCode) {
+ if (systemCode == null) {
+ return false;
+ }
+ if (systemCode.length() != 4) {
+ Log.e(TAG, "System Code " + systemCode + " is not a valid System Code.");
+ return false;
+ }
+ // check if the value is between "4000" and "4FFF" (excluding "4*FF")
+ if (!systemCode.startsWith("4") || systemCode.toUpperCase().endsWith("FF")) {
+ Log.e(TAG, "System Code " + systemCode + " is not a valid System Code.");
+ return false;
+ }
+ try {
+ Integer.parseInt(systemCode, 16);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "System Code " + systemCode + " is not a valid System Code.");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Copied over from {@link NfcFCardEmulation#isValidNfcid2(String)}
+ * @hide
+ */
+ private static boolean isValidNfcid2(String nfcid2) {
+ if (nfcid2 == null) {
+ return false;
+ }
+ if (nfcid2.length() != 16) {
+ Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2.");
+ return false;
+ }
+ // check if the the value starts with "02FE"
+ if (!nfcid2.toUpperCase().startsWith("02FE")) {
+ Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2.");
+ return false;
+ }
+ try {
+ Long.parseLong(nfcid2, 16);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2.");
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/nfc/java/android/nfc/cardemulation/OWNERS b/nfc/java/android/nfc/cardemulation/OWNERS
new file mode 100644
index 0000000..35e9713
--- /dev/null
+++ b/nfc/java/android/nfc/cardemulation/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 48448
+include platform/packages/apps/Nfc:/OWNERS
diff --git a/nfc/java/android/nfc/cardemulation/OffHostApduService.java b/nfc/java/android/nfc/cardemulation/OffHostApduService.java
new file mode 100644
index 0000000..2286e84
--- /dev/null
+++ b/nfc/java/android/nfc/cardemulation/OffHostApduService.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2015 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.cardemulation;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.app.Service;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.IBinder;
+
+/**
+ * <p>OffHostApduService is a convenience {@link Service} class that can be
+ * extended to describe one or more NFC applications that are residing
+ * off-host, for example on an embedded secure element or a UICC.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guide</h3>
+ * For a general introduction into the topic of card emulation,
+ * please read the <a href="{@docRoot}guide/topics/connectivity/nfc/hce.html">
+ * NFC card emulation developer guide.</a></p>
+ * </div>
+ *
+ * <h3>NFC Protocols</h3>
+ * <p>Off-host applications represented by this class are based on the NFC-Forum ISO-DEP
+ * protocol (based on ISO/IEC 14443-4) and support processing
+ * command Application Protocol Data Units (APDUs) as
+ * defined in the ISO/IEC 7816-4 specification.
+ *
+ * <h3>Service selection</h3>
+ * <p>When a remote NFC device wants to talk to your
+ * off-host NFC application, it sends a so-called
+ * "SELECT AID" APDU as defined in the ISO/IEC 7816-4 specification.
+ * The AID is an application identifier defined in ISO/IEC 7816-4.
+ *
+ * <p>The registration procedure for AIDs is defined in the
+ * ISO/IEC 7816-5 specification. If you don't want to register an
+ * AID, you are free to use AIDs in the proprietary range:
+ * bits 8-5 of the first byte must each be set to '1'. For example,
+ * "0xF00102030405" is a proprietary AID. If you do use proprietary
+ * AIDs, it is recommended to choose an AID of at least 6 bytes,
+ * to reduce the risk of collisions with other applications that
+ * might be using proprietary AIDs as well.
+ *
+ * <h3>AID groups</h3>
+ * <p>In some cases, an off-host environment may need to register multiple AIDs
+ * to implement a certain application, and it needs to be sure
+ * that it is the default handler for all of these AIDs (as opposed
+ * to some AIDs in the group going to another service).
+ *
+ * <p>An AID group is a list of AIDs that should be considered as
+ * belonging together by the OS. For all AIDs in an AID group, the
+ * OS will guarantee one of the following:
+ * <ul>
+ * <li>All AIDs in the group are routed to the off-host execution environment
+ * <li>No AIDs in the group are routed to the off-host execution environment
+ * </ul>
+ * In other words, there is no in-between state, where some AIDs
+ * in the group can be routed to this off-host execution environment,
+ * and some to another or a host-based {@link HostApduService}.
+ * <h3>AID groups and categories</h3>
+ * <p>Each AID group can be associated with a category. This allows
+ * the Android OS to classify services, and it allows the user to
+ * set defaults at the category level instead of the AID level.
+ *
+ * <p>You can use
+ * {@link CardEmulation#isDefaultServiceForCategory(android.content.ComponentName, String)}
+ * to determine if your off-host service is the default handler for a category.
+ *
+ * <p>In this version of the platform, the only known categories
+ * are {@link CardEmulation#CATEGORY_PAYMENT} and {@link CardEmulation#CATEGORY_OTHER}.
+ * AID groups without a category, or with a category that is not recognized
+ * by the current platform version, will automatically be
+ * grouped into the {@link CardEmulation#CATEGORY_OTHER} category.
+ *
+ * <h3>Service AID registration</h3>
+ * <p>To tell the platform which AIDs
+ * reside off-host and are managed by this service, a {@link #SERVICE_META_DATA}
+ * entry must be included in the declaration of the service. An
+ * example of a OffHostApduService manifest declaration is shown below:
+ * <pre> <service android:name=".MyOffHostApduService" android:exported="true" android:permission="android.permission.BIND_NFC_SERVICE">
+ * <intent-filter>
+ * <action android:name="android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE"/>
+ * </intent-filter>
+ * <meta-data android:name="android.nfc.cardemulation.off_host_apdu_ervice" android:resource="@xml/apduservice"/>
+ * </service></pre>
+ *
+ * This meta-data tag points to an apduservice.xml file.
+ * An example of this file with a single AID group declaration is shown below:
+ * <pre>
+ * <offhost-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
+ * android:description="@string/servicedesc">
+ * <aid-group android:description="@string/subscription" android:category="other">
+ * <aid-filter android:name="F0010203040506"/>
+ * <aid-filter android:name="F0394148148100"/>
+ * </aid-group>
+ * </offhost-apdu-service>
+ * </pre>
+ *
+ * <p>The {@link android.R.styleable#OffHostApduService <offhost-apdu-service>} is required
+ * to contain a
+ * {@link android.R.styleable#OffHostApduService_description <android:description>}
+ * attribute that contains a user-friendly description of the service that may be shown in UI.
+ *
+ * <p>The {@link android.R.styleable#OffHostApduService <offhost-apdu-service>} must
+ * contain one or more {@link android.R.styleable#AidGroup <aid-group>} tags.
+ * Each {@link android.R.styleable#AidGroup <aid-group>} must contain one or
+ * more {@link android.R.styleable#AidFilter <aid-filter>} tags, each of which
+ * contains a single AID. The AID must be specified in hexadecimal format, and contain
+ * an even number of characters.
+ *
+ * <p>This registration will allow the service to be included
+ * as an option for being the default handler for categories.
+ * The Android OS will take care of correctly
+ * routing the AIDs to the off-host execution environment,
+ * based on which service the user has selected to be the handler for a certain category.
+ *
+ * <p>The service may define additional actions outside of the
+ * Android namespace that provide further interaction with
+ * the off-host execution environment.
+ *
+ * <p class="note">Use of this class requires the
+ * {@link PackageManager#FEATURE_NFC_HOST_CARD_EMULATION} to be present
+ * on the device.
+ */
+public abstract class OffHostApduService extends Service {
+ /**
+ * The {@link Intent} action that must be declared as handled by the service.
+ */
+ @SdkConstant(SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE =
+ "android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE";
+
+ /**
+ * The name of the meta-data element that contains
+ * more information about this service.
+ */
+ public static final String SERVICE_META_DATA =
+ "android.nfc.cardemulation.off_host_apdu_service";
+
+ /**
+ * The Android platform itself will not bind to this service,
+ * but merely uses its declaration to keep track of what AIDs
+ * the service is interested in. This information is then used
+ * to present the user with a list of applications that can handle
+ * an AID, as well as correctly route those AIDs either to the host (in case
+ * the user preferred a {@link HostApduService}), or to an off-host
+ * execution environment (in case the user preferred a {@link OffHostApduService}.
+ *
+ * Implementers may define additional actions outside of the
+ * Android namespace that allow further interactions with
+ * the off-host execution environment. Such implementations
+ * would need to override this method.
+ */
+ public abstract IBinder onBind(Intent intent);
+}
diff --git a/nfc/java/android/nfc/cardemulation/Utils.java b/nfc/java/android/nfc/cardemulation/Utils.java
new file mode 100644
index 0000000..202e1cf
--- /dev/null
+++ b/nfc/java/android/nfc/cardemulation/Utils.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 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.cardemulation;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.ComponentNameProto;
+import android.util.proto.ProtoOutputStream;
+
+/** @hide */
+public final class Utils {
+ private Utils() {
+ }
+
+ /** Copied from {@link ComponentName#dumpDebug(ProtoOutputStream, long)} */
+ public static void dumpDebugComponentName(
+ @NonNull ComponentName componentName, @NonNull ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ proto.write(ComponentNameProto.PACKAGE_NAME, componentName.getPackageName());
+ proto.write(ComponentNameProto.CLASS_NAME, componentName.getClassName());
+ proto.end(token);
+ }
+}
diff --git a/nfc/java/android/nfc/dta/NfcDta.java b/nfc/java/android/nfc/dta/NfcDta.java
new file mode 100644
index 0000000..8801662
--- /dev/null
+++ b/nfc/java/android/nfc/dta/NfcDta.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2017 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.dta;
+
+import android.content.Context;
+import android.nfc.INfcDta;
+import android.nfc.NfcAdapter;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.HashMap;
+
+/**
+ * This class provides the primary API for DTA operations.
+ * @hide
+ */
+public final class NfcDta {
+ private static final String TAG = "NfcDta";
+
+ private static INfcDta sService;
+ private static HashMap<Context, NfcDta> sNfcDtas = new HashMap<Context, NfcDta>();
+
+ private final Context mContext;
+
+ private NfcDta(Context context, INfcDta service) {
+ mContext = context.getApplicationContext();
+ sService = service;
+ }
+
+ /**
+ * Helper to get an instance of this class.
+ *
+ * @param adapter A reference to an NfcAdapter object.
+ * @return
+ */
+ public static synchronized NfcDta getInstance(NfcAdapter adapter) {
+ if (adapter == null) throw new NullPointerException("NfcAdapter is null");
+ Context context = adapter.getContext();
+ if (context == null) {
+ Log.e(TAG, "NfcAdapter context is null.");
+ throw new UnsupportedOperationException();
+ }
+
+ NfcDta manager = sNfcDtas.get(context);
+ if (manager == null) {
+ INfcDta service = adapter.getNfcDtaInterface();
+ if (service == null) {
+ Log.e(TAG, "This device does not implement the INfcDta interface.");
+ throw new UnsupportedOperationException();
+ }
+ manager = new NfcDta(context, service);
+ sNfcDtas.put(context, manager);
+ }
+ return manager;
+ }
+
+ /**
+ * Enables DTA mode
+ *
+ * @return true/false if enabling was successful
+ */
+ public boolean enableDta() {
+ try {
+ sService.enableDta();
+ } catch (RemoteException e) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Disables DTA mode
+ *
+ * @return true/false if disabling was successful
+ */
+ public boolean disableDta() {
+ try {
+ sService.disableDta();
+ } catch (RemoteException e) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Enables Server
+ *
+ * @return true/false if enabling was successful
+ */
+ public boolean enableServer(String serviceName, int serviceSap, int miu,
+ int rwSize, int testCaseId) {
+ try {
+ return sService.enableServer(serviceName, serviceSap, miu, rwSize, testCaseId);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Disables Server
+ *
+ * @return true/false if disabling was successful
+ */
+ public boolean disableServer() {
+ try {
+ sService.disableServer();
+ } catch (RemoteException e) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Enables Client
+ *
+ * @return true/false if enabling was successful
+ */
+ public boolean enableClient(String serviceName, int miu, int rwSize,
+ int testCaseId) {
+ try {
+ return sService.enableClient(serviceName, miu, rwSize, testCaseId);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Disables client
+ *
+ * @return true/false if disabling was successful
+ */
+ public boolean disableClient() {
+ try {
+ sService.disableClient();
+ } catch (RemoteException e) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Registers Message Service
+ *
+ * @return true/false if registration was successful
+ */
+ public boolean registerMessageService(String msgServiceName) {
+ try {
+ return sService.registerMessageService(msgServiceName);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+}
diff --git a/nfc/java/android/nfc/dta/OWNERS b/nfc/java/android/nfc/dta/OWNERS
new file mode 100644
index 0000000..35e9713
--- /dev/null
+++ b/nfc/java/android/nfc/dta/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 48448
+include platform/packages/apps/Nfc:/OWNERS
diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig
new file mode 100644
index 0000000..01a4570
--- /dev/null
+++ b/nfc/java/android/nfc/flags.aconfig
@@ -0,0 +1,71 @@
+package: "android.nfc"
+
+flag {
+ name: "enable_nfc_mainline"
+ namespace: "nfc"
+ description: "Flag for NFC mainline changes"
+ bug: "292140387"
+}
+
+flag {
+ name: "enable_nfc_reader_option"
+ namespace: "nfc"
+ description: "Flag for NFC reader option API changes"
+ bug: "291187960"
+}
+
+flag {
+ name: "enable_nfc_user_restriction"
+ namespace: "nfc"
+ description: "Flag for NFC user restriction"
+ bug: "291187960"
+}
+
+flag {
+ name: "nfc_observe_mode"
+ namespace: "nfc"
+ description: "Enable NFC Observe Mode"
+ bug: "294217286"
+}
+
+flag {
+ name: "nfc_read_polling_loop"
+ namespace: "nfc"
+ description: "Enable NFC Polling Loop Notifications"
+ bug: "294217286"
+}
+
+flag {
+ name: "nfc_observe_mode_st_shim"
+ namespace: "nfc"
+ description: "Enable NFC Observe Mode ST shim"
+ bug: "294217286"
+}
+
+flag {
+ name: "nfc_read_polling_loop_st_shim"
+ namespace: "nfc"
+ description: "Enable NFC Polling Loop Notifications ST shim"
+ bug: "294217286"
+}
+
+flag {
+ name: "enable_tag_detection_broadcasts"
+ namespace: "nfc"
+ description: "Enable sending broadcasts to Wallet role holder when a tag enters/leaves the field."
+ bug: "306203494"
+}
+
+flag {
+ name: "enable_nfc_charging"
+ namespace: "nfc"
+ description: "Flag for NFC charging changes"
+ bug: "292143899"
+}
+
+flag {
+ name: "enable_nfc_set_discovery_tech"
+ namespace: "nfc"
+ description: "Flag for NFC set discovery tech API"
+ bug: "300351519"
+}
diff --git a/nfc/java/android/nfc/package.html b/nfc/java/android/nfc/package.html
new file mode 100644
index 0000000..55c1d16
--- /dev/null
+++ b/nfc/java/android/nfc/package.html
@@ -0,0 +1,33 @@
+<HTML>
+<BODY>
+<p>Provides access to Near Field Communication (NFC) functionality, allowing applications to read
+NDEF message in NFC tags. A "tag" may actually be another device that appears as a tag.</p>
+
+<p>For more information, see the
+<a href="{@docRoot}guide/topics/connectivity/nfc/index.html">Near Field Communication</a> guide.</p>
+{@more}
+
+<p>Here's a summary of the classes:</p>
+
+<dl>
+ <dt>{@link android.nfc.NfcManager}</dt>
+ <dd>This is the high level manager, used to obtain this device's {@link android.nfc.NfcAdapter}. You can
+acquire an instance using {@link android.content.Context#getSystemService}.</dd>
+ <dt>{@link android.nfc.NfcAdapter}</dt>
+ <dd>This represents the device's NFC adapter, which is your entry-point to performing NFC
+operations. You can acquire an instance with {@link android.nfc.NfcManager#getDefaultAdapter}, or
+{@link android.nfc.NfcAdapter#getDefaultAdapter(android.content.Context)}.</dd>
+ <dt>{@link android.nfc.NdefMessage}</dt>
+ <dd>Represents an NDEF data message, which is the standard format in which "records"
+carrying data are transmitted between devices and tags. Your application can receive these
+messages from an {@link android.nfc.NfcAdapter#ACTION_TAG_DISCOVERED} intent.</dd>
+ <dt>{@link android.nfc.NdefRecord}</dt>
+ <dd>Represents a record, which is delivered in a {@link android.nfc.NdefMessage} and describes the
+type of data being shared and carries the data itself.</dd>
+</dl>
+
+<p class="note"><strong>Note:</strong>
+Not all Android-powered devices provide NFC functionality.</p>
+
+</BODY>
+</HTML>
diff --git a/nfc/java/android/nfc/tech/BasicTagTechnology.java b/nfc/java/android/nfc/tech/BasicTagTechnology.java
new file mode 100644
index 0000000..ae468fe
--- /dev/null
+++ b/nfc/java/android/nfc/tech/BasicTagTechnology.java
@@ -0,0 +1,161 @@
+/*
+ * 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.tech;
+
+import android.nfc.ErrorCodes;
+import android.nfc.Tag;
+import android.nfc.TransceiveResult;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.IOException;
+
+/**
+ * A base class for tag technologies that are built on top of transceive().
+ */
+abstract class BasicTagTechnology implements TagTechnology {
+ private static final String TAG = "NFC";
+
+ final Tag mTag;
+
+ boolean mIsConnected;
+ int mSelectedTechnology;
+
+ BasicTagTechnology(Tag tag, int tech) throws RemoteException {
+ mTag = tag;
+ mSelectedTechnology = tech;
+ }
+
+ @Override
+ public Tag getTag() {
+ return mTag;
+ }
+
+ /** Internal helper to throw IllegalStateException if the technology isn't connected */
+ void checkConnected() {
+ if ((mTag.getConnectedTechnology() != mSelectedTechnology) ||
+ (mTag.getConnectedTechnology() == -1)) {
+ throw new IllegalStateException("Call connect() first!");
+ }
+ }
+
+ @Override
+ public boolean isConnected() {
+ if (!mIsConnected) {
+ return false;
+ }
+
+ try {
+ return mTag.getTagService().isPresent(mTag.getServiceHandle());
+ } catch (RemoteException e) {
+ Log.e(TAG, "NFC service dead", e);
+ return false;
+ }
+ }
+
+ @Override
+ public void connect() throws IOException {
+ try {
+ int errorCode = mTag.getTagService().connect(mTag.getServiceHandle(),
+ mSelectedTechnology);
+
+ if (errorCode == ErrorCodes.SUCCESS) {
+ // Store this in the tag object
+ if (!mTag.setConnectedTechnology(mSelectedTechnology)) {
+ Log.e(TAG, "Close other technology first!");
+ throw new IOException("Only one TagTechnology can be connected at a time.");
+ }
+ mIsConnected = true;
+ } else if (errorCode == ErrorCodes.ERROR_NOT_SUPPORTED) {
+ throw new UnsupportedOperationException("Connecting to " +
+ "this technology is not supported by the NFC " +
+ "adapter.");
+ } else {
+ throw new IOException();
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "NFC service dead", e);
+ throw new IOException("NFC service died");
+ }
+ }
+
+ /** @hide */
+ @Override
+ public void reconnect() throws IOException {
+ if (!mIsConnected) {
+ throw new IllegalStateException("Technology not connected yet");
+ }
+
+ try {
+ int errorCode = mTag.getTagService().reconnect(mTag.getServiceHandle());
+
+ if (errorCode != ErrorCodes.SUCCESS) {
+ mIsConnected = false;
+ mTag.setTechnologyDisconnected();
+ throw new IOException();
+ }
+ } catch (RemoteException e) {
+ mIsConnected = false;
+ mTag.setTechnologyDisconnected();
+ Log.e(TAG, "NFC service dead", e);
+ throw new IOException("NFC service died");
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ try {
+ /* Note that we don't want to physically disconnect the tag,
+ * but just reconnect to it to reset its state
+ */
+ mTag.getTagService().resetTimeouts();
+ mTag.getTagService().reconnect(mTag.getServiceHandle());
+ } catch (RemoteException e) {
+ Log.e(TAG, "NFC service dead", e);
+ } finally {
+ mIsConnected = false;
+ mTag.setTechnologyDisconnected();
+ }
+ }
+
+ /** Internal getMaxTransceiveLength() */
+ int getMaxTransceiveLengthInternal() {
+ try {
+ return mTag.getTagService().getMaxTransceiveLength(mSelectedTechnology);
+ } catch (RemoteException e) {
+ Log.e(TAG, "NFC service dead", e);
+ return 0;
+ }
+ }
+ /** Internal transceive */
+ byte[] transceive(byte[] data, boolean raw) throws IOException {
+ checkConnected();
+
+ try {
+ TransceiveResult result = mTag.getTagService().transceive(mTag.getServiceHandle(),
+ data, raw);
+ if (result == null) {
+ throw new IOException("transceive failed");
+ } else {
+ return result.getResponseOrThrow();
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "NFC service dead", e);
+ throw new IOException("NFC service died");
+ }
+ }
+}
diff --git a/nfc/java/android/nfc/tech/IsoDep.java b/nfc/java/android/nfc/tech/IsoDep.java
new file mode 100644
index 0000000..0ba0c5a
--- /dev/null
+++ b/nfc/java/android/nfc/tech/IsoDep.java
@@ -0,0 +1,209 @@
+/*
+ * 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.tech;
+
+import android.nfc.ErrorCodes;
+import android.nfc.Tag;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.IOException;
+
+/**
+ * Provides access to ISO-DEP (ISO 14443-4) properties and I/O operations on a {@link Tag}.
+ *
+ * <p>Acquire an {@link IsoDep} object using {@link #get}.
+ * <p>The primary ISO-DEP I/O operation is {@link #transceive}. Applications must
+ * implement their own protocol stack on top of {@link #transceive}.
+ * <p>Tags that enumerate the {@link IsoDep} technology in {@link Tag#getTechList}
+ * will also enumerate
+ * {@link NfcA} or {@link NfcB} (since IsoDep builds on top of either of these).
+ *
+ * <p class="note"><strong>Note:</strong> Methods that perform I/O operations
+ * require the {@link android.Manifest.permission#NFC} permission.
+ */
+public final class IsoDep extends BasicTagTechnology {
+ private static final String TAG = "NFC";
+
+ /** @hide */
+ public static final String EXTRA_HI_LAYER_RESP = "hiresp";
+ /** @hide */
+ public static final String EXTRA_HIST_BYTES = "histbytes";
+
+ private byte[] mHiLayerResponse = null;
+ private byte[] mHistBytes = null;
+
+ /**
+ * Get an instance of {@link IsoDep} for the given tag.
+ * <p>Does not cause any RF activity and does not block.
+ * <p>Returns null if {@link IsoDep} was not enumerated in {@link Tag#getTechList}.
+ * This indicates the tag does not support ISO-DEP.
+ *
+ * @param tag an ISO-DEP compatible tag
+ * @return ISO-DEP object
+ */
+ public static IsoDep get(Tag tag) {
+ if (!tag.hasTech(TagTechnology.ISO_DEP)) return null;
+ try {
+ return new IsoDep(tag);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /** @hide */
+ public IsoDep(Tag tag)
+ throws RemoteException {
+ super(tag, TagTechnology.ISO_DEP);
+ Bundle extras = tag.getTechExtras(TagTechnology.ISO_DEP);
+ if (extras != null) {
+ mHiLayerResponse = extras.getByteArray(EXTRA_HI_LAYER_RESP);
+ mHistBytes = extras.getByteArray(EXTRA_HIST_BYTES);
+ }
+ }
+
+ /**
+ * Set the timeout of {@link #transceive} in milliseconds.
+ * <p>The timeout only applies to ISO-DEP {@link #transceive}, and is
+ * reset to a default value when {@link #close} is called.
+ * <p>Setting a longer timeout may be useful when performing
+ * transactions that require a long processing time on the tag
+ * such as key generation.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param timeout timeout value in milliseconds
+ * @throws SecurityException if the tag object is reused after the tag has left the field
+ */
+ public void setTimeout(int timeout) {
+ try {
+ int err = mTag.getTagService().setTimeout(TagTechnology.ISO_DEP, timeout);
+ if (err != ErrorCodes.SUCCESS) {
+ throw new IllegalArgumentException("The supplied timeout is not valid");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "NFC service dead", e);
+ }
+ }
+
+ /**
+ * Get the current timeout for {@link #transceive} in milliseconds.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @return timeout value in milliseconds
+ * @throws SecurityException if the tag object is reused after the tag has left the field
+ */
+ public int getTimeout() {
+ try {
+ return mTag.getTagService().getTimeout(TagTechnology.ISO_DEP);
+ } catch (RemoteException e) {
+ Log.e(TAG, "NFC service dead", e);
+ return 0;
+ }
+ }
+
+ /**
+ * Return the ISO-DEP historical bytes for {@link NfcA} tags.
+ * <p>Does not cause any RF activity and does not block.
+ * <p>The historical bytes can be used to help identify a tag. They are present
+ * only on {@link IsoDep} tags that are based on {@link NfcA} RF technology.
+ * If this tag is not {@link NfcA} then null is returned.
+ * <p>In ISO 14443-4 terminology, the historical bytes are a subset of the RATS
+ * response.
+ *
+ * @return ISO-DEP historical bytes, or null if this is not a {@link NfcA} tag
+ */
+ public byte[] getHistoricalBytes() {
+ return mHistBytes;
+ }
+
+ /**
+ * Return the higher layer response bytes for {@link NfcB} tags.
+ * <p>Does not cause any RF activity and does not block.
+ * <p>The higher layer response bytes can be used to help identify a tag.
+ * They are present only on {@link IsoDep} tags that are based on {@link NfcB}
+ * RF technology. If this tag is not {@link NfcB} then null is returned.
+ * <p>In ISO 14443-4 terminology, the higher layer bytes are a subset of the
+ * ATTRIB response.
+ *
+ * @return ISO-DEP historical bytes, or null if this is not a {@link NfcB} tag
+ */
+ public byte[] getHiLayerResponse() {
+ return mHiLayerResponse;
+ }
+
+ /**
+ * Send raw ISO-DEP data to the tag and receive the response.
+ *
+ * <p>Applications must only send the INF payload, and not the start of frame and
+ * end of frame indicators. Applications do not need to fragment the payload, it
+ * will be automatically fragmented and defragmented by {@link #transceive} if
+ * it exceeds FSD/FSC limits.
+ *
+ * <p>Use {@link #getMaxTransceiveLength} to retrieve the maximum number of bytes
+ * that can be sent with {@link #transceive}.
+ *
+ * <p>This is an I/O operation and will block until complete. It must
+ * not be called from the main application thread. A blocked call will be canceled with
+ * {@link IOException} if {@link #close} is called from another thread.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param data command bytes to send, must not be null
+ * @return response bytes received, will not be null
+ * @throws TagLostException if the tag leaves the field
+ * @throws IOException if there is an I/O failure, or this operation is canceled
+ * @throws SecurityException if the tag object is reused after the tag has left the field
+ */
+ public byte[] transceive(byte[] data) throws IOException {
+ return transceive(data, true);
+ }
+
+ /**
+ * Return the maximum number of bytes that can be sent with {@link #transceive}.
+ * @return the maximum number of bytes that can be sent with {@link #transceive}.
+ */
+ public int getMaxTransceiveLength() {
+ return getMaxTransceiveLengthInternal();
+ }
+
+ /**
+ * <p>Standard APDUs have a 1-byte length field, allowing a maximum of
+ * 255 payload bytes, which results in a maximum APDU length of 261 bytes.
+ *
+ * <p>Extended length APDUs have a 3-byte length field, allowing 65535
+ * payload bytes.
+ *
+ * <p>Some NFC adapters, like the one used in the Nexus S and the Galaxy Nexus
+ * do not support extended length APDUs. They are expected to be well-supported
+ * in the future though. Use this method to check for extended length APDU
+ * support.
+ *
+ * @return whether the NFC adapter on this device supports extended length APDUs.
+ * @throws SecurityException if the tag object is reused after the tag has left the field
+ */
+ public boolean isExtendedLengthApduSupported() {
+ try {
+ return mTag.getTagService().getExtendedLengthApdusSupported();
+ } catch (RemoteException e) {
+ Log.e(TAG, "NFC service dead", e);
+ return false;
+ }
+ }
+}
diff --git a/nfc/java/android/nfc/tech/MifareClassic.java b/nfc/java/android/nfc/tech/MifareClassic.java
new file mode 100644
index 0000000..26f54e6
--- /dev/null
+++ b/nfc/java/android/nfc/tech/MifareClassic.java
@@ -0,0 +1,655 @@
+/*
+ * 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.tech;
+
+import android.nfc.ErrorCodes;
+import android.nfc.Tag;
+import android.nfc.TagLostException;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Provides access to MIFARE Classic properties and I/O operations on a {@link Tag}.
+ *
+ * <p>Acquire a {@link MifareClassic} object using {@link #get}.
+ *
+ * <p>MIFARE Classic is also known as MIFARE Standard.
+ * <p>MIFARE Classic tags are divided into sectors, and each sector is sub-divided into
+ * blocks. Block size is always 16 bytes ({@link #BLOCK_SIZE}. Sector size varies.
+ * <ul>
+ * <li>MIFARE Classic Mini are 320 bytes ({@link #SIZE_MINI}), with 5 sectors each of 4 blocks.
+ * <li>MIFARE Classic 1k are 1024 bytes ({@link #SIZE_1K}), with 16 sectors each of 4 blocks.
+ * <li>MIFARE Classic 2k are 2048 bytes ({@link #SIZE_2K}), with 32 sectors each of 4 blocks.
+ * <li>MIFARE Classic 4k are 4096 bytes ({@link #SIZE_4K}). The first 32 sectors contain 4 blocks
+ * and the last 8 sectors contain 16 blocks.
+ * </ul>
+ *
+ * <p>MIFARE Classic tags require authentication on a per-sector basis before any
+ * other I/O operations on that sector can be performed. There are two keys per sector,
+ * and ACL bits determine what I/O operations are allowed on that sector after
+ * authenticating with a key. {@see #authenticateSectorWithKeyA} and
+ * {@see #authenticateSectorWithKeyB}.
+ *
+ * <p>Three well-known authentication keys are defined in this class:
+ * {@link #KEY_DEFAULT}, {@link #KEY_MIFARE_APPLICATION_DIRECTORY},
+ * {@link #KEY_NFC_FORUM}.
+ * <ul>
+ * <li>{@link #KEY_DEFAULT} is the default factory key for MIFARE Classic.
+ * <li>{@link #KEY_MIFARE_APPLICATION_DIRECTORY} is the well-known key for
+ * MIFARE Classic cards that have been formatted according to the
+ * MIFARE Application Directory (MAD) specification.
+ * <li>{@link #KEY_NFC_FORUM} is the well-known key for MIFARE Classic cards that
+ * have been formatted according to the NXP specification for NDEF on MIFARE Classic.
+ *
+ * <p>Implementation of this class on a Android NFC device is optional.
+ * If it is not implemented, then
+ * {@link MifareClassic} will never be enumerated in {@link Tag#getTechList}.
+ * If it is enumerated, then all {@link MifareClassic} I/O operations will be supported,
+ * and {@link Ndef#MIFARE_CLASSIC} NDEF tags will also be supported. In either case,
+ * {@link NfcA} will also be enumerated on the tag, because all MIFARE Classic tags are also
+ * {@link NfcA}.
+ *
+ * <p class="note"><strong>Note:</strong> Methods that perform I/O operations
+ * require the {@link android.Manifest.permission#NFC} permission.
+ */
+public final class MifareClassic extends BasicTagTechnology {
+ private static final String TAG = "NFC";
+
+ /**
+ * The default factory key.
+ */
+ public static final byte[] KEY_DEFAULT =
+ {(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF};
+ /**
+ * The well-known key for tags formatted according to the
+ * MIFARE Application Directory (MAD) specification.
+ */
+ public static final byte[] KEY_MIFARE_APPLICATION_DIRECTORY =
+ {(byte)0xA0,(byte)0xA1,(byte)0xA2,(byte)0xA3,(byte)0xA4,(byte)0xA5};
+ /**
+ * The well-known key for tags formatted according to the
+ * NDEF on MIFARE Classic specification.
+ */
+ public static final byte[] KEY_NFC_FORUM =
+ {(byte)0xD3,(byte)0xF7,(byte)0xD3,(byte)0xF7,(byte)0xD3,(byte)0xF7};
+
+ /** A MIFARE Classic compatible card of unknown type */
+ public static final int TYPE_UNKNOWN = -1;
+ /** A MIFARE Classic tag */
+ public static final int TYPE_CLASSIC = 0;
+ /** A MIFARE Plus tag */
+ public static final int TYPE_PLUS = 1;
+ /** A MIFARE Pro tag */
+ public static final int TYPE_PRO = 2;
+
+ /** Tag contains 16 sectors, each with 4 blocks. */
+ public static final int SIZE_1K = 1024;
+ /** Tag contains 32 sectors, each with 4 blocks. */
+ public static final int SIZE_2K = 2048;
+ /**
+ * Tag contains 40 sectors. The first 32 sectors contain 4 blocks and the last 8 sectors
+ * contain 16 blocks.
+ */
+ public static final int SIZE_4K = 4096;
+ /** Tag contains 5 sectors, each with 4 blocks. */
+ public static final int SIZE_MINI = 320;
+
+ /** Size of a MIFARE Classic block (in bytes) */
+ public static final int BLOCK_SIZE = 16;
+
+ private static final int MAX_BLOCK_COUNT = 256;
+ private static final int MAX_SECTOR_COUNT = 40;
+
+ private boolean mIsEmulated;
+ private int mType;
+ private int mSize;
+
+ /**
+ * Get an instance of {@link MifareClassic} for the given tag.
+ * <p>Does not cause any RF activity and does not block.
+ * <p>Returns null if {@link MifareClassic} was not enumerated in {@link Tag#getTechList}.
+ * This indicates the tag is not MIFARE Classic compatible, or this Android
+ * device does not support MIFARE Classic.
+ *
+ * @param tag an MIFARE Classic compatible tag
+ * @return MIFARE Classic object
+ */
+ public static MifareClassic get(Tag tag) {
+ if (!tag.hasTech(TagTechnology.MIFARE_CLASSIC)) return null;
+ try {
+ return new MifareClassic(tag);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /** @hide */
+ public MifareClassic(Tag tag) throws RemoteException {
+ super(tag, TagTechnology.MIFARE_CLASSIC);
+
+ NfcA a = NfcA.get(tag); // MIFARE Classic is always based on NFC a
+
+ mIsEmulated = false;
+
+ switch (a.getSak()) {
+ case 0x01:
+ case 0x08:
+ mType = TYPE_CLASSIC;
+ mSize = SIZE_1K;
+ break;
+ case 0x09:
+ mType = TYPE_CLASSIC;
+ mSize = SIZE_MINI;
+ break;
+ case 0x10:
+ mType = TYPE_PLUS;
+ mSize = SIZE_2K;
+ // SecLevel = SL2
+ break;
+ case 0x11:
+ mType = TYPE_PLUS;
+ mSize = SIZE_4K;
+ // Seclevel = SL2
+ break;
+ case 0x18:
+ mType = TYPE_CLASSIC;
+ mSize = SIZE_4K;
+ break;
+ case 0x28:
+ mType = TYPE_CLASSIC;
+ mSize = SIZE_1K;
+ mIsEmulated = true;
+ break;
+ case 0x38:
+ mType = TYPE_CLASSIC;
+ mSize = SIZE_4K;
+ mIsEmulated = true;
+ break;
+ case 0x88:
+ mType = TYPE_CLASSIC;
+ mSize = SIZE_1K;
+ // NXP-tag: false
+ break;
+ case 0x98:
+ case 0xB8:
+ mType = TYPE_PRO;
+ mSize = SIZE_4K;
+ break;
+ default:
+ // Stack incorrectly reported a MifareClassic. We cannot handle this
+ // gracefully - we have no idea of the memory layout. Bail.
+ throw new RuntimeException(
+ "Tag incorrectly enumerated as MIFARE Classic, SAK = " + a.getSak());
+ }
+ }
+
+ /**
+ * Return the type of this MIFARE Classic compatible tag.
+ * <p>One of {@link #TYPE_UNKNOWN}, {@link #TYPE_CLASSIC}, {@link #TYPE_PLUS} or
+ * {@link #TYPE_PRO}.
+ * <p>Does not cause any RF activity and does not block.
+ *
+ * @return type
+ */
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Return the size of the tag in bytes
+ * <p>One of {@link #SIZE_MINI}, {@link #SIZE_1K}, {@link #SIZE_2K}, {@link #SIZE_4K}.
+ * These constants are equal to their respective size in bytes.
+ * <p>Does not cause any RF activity and does not block.
+ * @return size in bytes
+ */
+ public int getSize() {
+ return mSize;
+ }
+
+ /**
+ * Return true if the tag is emulated, determined at discovery time.
+ * These are actually smart-cards that emulate a MIFARE Classic interface.
+ * They can be treated identically to a MIFARE Classic tag.
+ * @hide
+ */
+ public boolean isEmulated() {
+ return mIsEmulated;
+ }
+
+ /**
+ * Return the number of MIFARE Classic sectors.
+ * <p>Does not cause any RF activity and does not block.
+ * @return number of sectors
+ */
+ public int getSectorCount() {
+ switch (mSize) {
+ case SIZE_1K:
+ return 16;
+ case SIZE_2K:
+ return 32;
+ case SIZE_4K:
+ return 40;
+ case SIZE_MINI:
+ return 5;
+ default:
+ return 0;
+ }
+ }
+
+ /**
+ * Return the total number of MIFARE Classic blocks.
+ * <p>Does not cause any RF activity and does not block.
+ * @return total number of blocks
+ */
+ public int getBlockCount() {
+ return mSize / BLOCK_SIZE;
+ }
+
+ /**
+ * Return the number of blocks in the given sector.
+ * <p>Does not cause any RF activity and does not block.
+ *
+ * @param sectorIndex index of sector, starting from 0
+ * @return number of blocks in the sector
+ */
+ public int getBlockCountInSector(int sectorIndex) {
+ validateSector(sectorIndex);
+
+ if (sectorIndex < 32) {
+ return 4;
+ } else {
+ return 16;
+ }
+ }
+
+ /**
+ * Return the sector that contains a given block.
+ * <p>Does not cause any RF activity and does not block.
+ *
+ * @param blockIndex index of block to lookup, starting from 0
+ * @return sector index that contains the block
+ */
+ public int blockToSector(int blockIndex) {
+ validateBlock(blockIndex);
+
+ if (blockIndex < 32 * 4) {
+ return blockIndex / 4;
+ } else {
+ return 32 + (blockIndex - 32 * 4) / 16;
+ }
+ }
+
+ /**
+ * Return the first block of a given sector.
+ * <p>Does not cause any RF activity and does not block.
+ *
+ * @param sectorIndex index of sector to lookup, starting from 0
+ * @return block index of first block in sector
+ */
+ public int sectorToBlock(int sectorIndex) {
+ if (sectorIndex < 32) {
+ return sectorIndex * 4;
+ } else {
+ return 32 * 4 + (sectorIndex - 32) * 16;
+ }
+ }
+
+ /**
+ * Authenticate a sector with key A.
+ *
+ * <p>Successful authentication of a sector with key A enables other
+ * I/O operations on that sector. The set of operations granted by key A
+ * key depends on the ACL bits set in that sector. For more information
+ * see the MIFARE Classic specification on <a href="http://www.nxp.com">http://www.nxp.com</a>.
+ *
+ * <p>A failed authentication attempt causes an implicit reconnection to the
+ * tag, so authentication to other sectors will be lost.
+ *
+ * <p>This is an I/O operation and will block until complete. It must
+ * not be called from the main application thread. A blocked call will be canceled with
+ * {@link IOException} if {@link #close} is called from another thread.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param sectorIndex index of sector to authenticate, starting from 0
+ * @param key 6-byte authentication key
+ * @return true on success, false on authentication failure
+ * @throws TagLostException if the tag leaves the field
+ * @throws IOException if there is an I/O failure, or the operation is canceled
+ */
+ public boolean authenticateSectorWithKeyA(int sectorIndex, byte[] key) throws IOException {
+ return authenticate(sectorIndex, key, true);
+ }
+
+ /**
+ * Authenticate a sector with key B.
+ *
+ * <p>Successful authentication of a sector with key B enables other
+ * I/O operations on that sector. The set of operations granted by key B
+ * depends on the ACL bits set in that sector. For more information
+ * see the MIFARE Classic specification on <a href="http://www.nxp.com">http://www.nxp.com</a>.
+ *
+ * <p>A failed authentication attempt causes an implicit reconnection to the
+ * tag, so authentication to other sectors will be lost.
+ *
+ * <p>This is an I/O operation and will block until complete. It must
+ * not be called from the main application thread. A blocked call will be canceled with
+ * {@link IOException} if {@link #close} is called from another thread.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param sectorIndex index of sector to authenticate, starting from 0
+ * @param key 6-byte authentication key
+ * @return true on success, false on authentication failure
+ * @throws TagLostException if the tag leaves the field
+ * @throws IOException if there is an I/O failure, or the operation is canceled
+ */
+ public boolean authenticateSectorWithKeyB(int sectorIndex, byte[] key) throws IOException {
+ return authenticate(sectorIndex, key, false);
+ }
+
+ private boolean authenticate(int sector, byte[] key, boolean keyA) throws IOException {
+ validateSector(sector);
+ checkConnected();
+
+ byte[] cmd = new byte[12];
+
+ // First byte is the command
+ if (keyA) {
+ cmd[0] = 0x60; // phHal_eMifareAuthentA
+ } else {
+ cmd[0] = 0x61; // phHal_eMifareAuthentB
+ }
+
+ // Second byte is block address
+ // Authenticate command takes a block address. Authenticating a block
+ // of a sector will authenticate the entire sector.
+ cmd[1] = (byte) sectorToBlock(sector);
+
+ // Next 4 bytes are last 4 bytes of UID
+ byte[] uid = getTag().getId();
+ System.arraycopy(uid, uid.length - 4, cmd, 2, 4);
+
+ // Next 6 bytes are key
+ System.arraycopy(key, 0, cmd, 6, 6);
+
+ try {
+ if (transceive(cmd, false) != null) {
+ return true;
+ }
+ } catch (TagLostException e) {
+ throw e;
+ } catch (IOException e) {
+ // No need to deal with, will return false anyway
+ }
+ return false;
+ }
+
+ /**
+ * Read 16-byte block.
+ *
+ * <p>This is an I/O operation and will block until complete. It must
+ * not be called from the main application thread. A blocked call will be canceled with
+ * {@link IOException} if {@link #close} is called from another thread.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param blockIndex index of block to read, starting from 0
+ * @return 16 byte block
+ * @throws TagLostException if the tag leaves the field
+ * @throws IOException if there is an I/O failure, or the operation is canceled
+ */
+ public byte[] readBlock(int blockIndex) throws IOException {
+ validateBlock(blockIndex);
+ checkConnected();
+
+ byte[] cmd = { 0x30, (byte) blockIndex };
+ return transceive(cmd, false);
+ }
+
+ /**
+ * Write 16-byte block.
+ *
+ * <p>This is an I/O operation and will block until complete. It must
+ * not be called from the main application thread. A blocked call will be canceled with
+ * {@link IOException} if {@link #close} is called from another thread.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param blockIndex index of block to write, starting from 0
+ * @param data 16 bytes of data to write
+ * @throws TagLostException if the tag leaves the field
+ * @throws IOException if there is an I/O failure, or the operation is canceled
+ */
+ public void writeBlock(int blockIndex, byte[] data) throws IOException {
+ validateBlock(blockIndex);
+ checkConnected();
+ if (data.length != 16) {
+ throw new IllegalArgumentException("must write 16-bytes");
+ }
+
+ byte[] cmd = new byte[data.length + 2];
+ cmd[0] = (byte) 0xA0; // MF write command
+ cmd[1] = (byte) blockIndex;
+ System.arraycopy(data, 0, cmd, 2, data.length);
+
+ transceive(cmd, false);
+ }
+
+ /**
+ * Increment a value block, storing the result in the temporary block on the tag.
+ *
+ * <p>This is an I/O operation and will block until complete. It must
+ * not be called from the main application thread. A blocked call will be canceled with
+ * {@link IOException} if {@link #close} is called from another thread.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param blockIndex index of block to increment, starting from 0
+ * @param value non-negative to increment by
+ * @throws TagLostException if the tag leaves the field
+ * @throws IOException if there is an I/O failure, or the operation is canceled
+ */
+ public void increment(int blockIndex, int value) throws IOException {
+ validateBlock(blockIndex);
+ validateValueOperand(value);
+ checkConnected();
+
+ ByteBuffer cmd = ByteBuffer.allocate(6);
+ cmd.order(ByteOrder.LITTLE_ENDIAN);
+ cmd.put( (byte) 0xC1 );
+ cmd.put( (byte) blockIndex );
+ cmd.putInt(value);
+
+ transceive(cmd.array(), false);
+ }
+
+ /**
+ * Decrement a value block, storing the result in the temporary block on the tag.
+ *
+ * <p>This is an I/O operation and will block until complete. It must
+ * not be called from the main application thread. A blocked call will be canceled with
+ * {@link IOException} if {@link #close} is called from another thread.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param blockIndex index of block to decrement, starting from 0
+ * @param value non-negative to decrement by
+ * @throws TagLostException if the tag leaves the field
+ * @throws IOException if there is an I/O failure, or the operation is canceled
+ */
+ public void decrement(int blockIndex, int value) throws IOException {
+ validateBlock(blockIndex);
+ validateValueOperand(value);
+ checkConnected();
+
+ ByteBuffer cmd = ByteBuffer.allocate(6);
+ cmd.order(ByteOrder.LITTLE_ENDIAN);
+ cmd.put( (byte) 0xC0 );
+ cmd.put( (byte) blockIndex );
+ cmd.putInt(value);
+
+ transceive(cmd.array(), false);
+ }
+
+ /**
+ * Copy from the temporary block to a value block.
+ *
+ * <p>This is an I/O operation and will block until complete. It must
+ * not be called from the main application thread. A blocked call will be canceled with
+ * {@link IOException} if {@link #close} is called from another thread.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param blockIndex index of block to copy to
+ * @throws TagLostException if the tag leaves the field
+ * @throws IOException if there is an I/O failure, or the operation is canceled
+ */
+ public void transfer(int blockIndex) throws IOException {
+ validateBlock(blockIndex);
+ checkConnected();
+
+ byte[] cmd = { (byte) 0xB0, (byte) blockIndex };
+
+ transceive(cmd, false);
+ }
+
+ /**
+ * Copy from a value block to the temporary block.
+ *
+ * <p>This is an I/O operation and will block until complete. It must
+ * not be called from the main application thread. A blocked call will be canceled with
+ * {@link IOException} if {@link #close} is called from another thread.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param blockIndex index of block to copy from
+ * @throws TagLostException if the tag leaves the field
+ * @throws IOException if there is an I/O failure, or the operation is canceled
+ */
+ public void restore(int blockIndex) throws IOException {
+ validateBlock(blockIndex);
+ checkConnected();
+
+ byte[] cmd = { (byte) 0xC2, (byte) blockIndex };
+
+ transceive(cmd, false);
+ }
+
+ /**
+ * Send raw NfcA data to a tag and receive the response.
+ *
+ * <p>This is equivalent to connecting to this tag via {@link NfcA}
+ * and calling {@link NfcA#transceive}. Note that all MIFARE Classic
+ * tags are based on {@link NfcA} technology.
+ *
+ * <p>Use {@link #getMaxTransceiveLength} to retrieve the maximum number of bytes
+ * that can be sent with {@link #transceive}.
+ *
+ * <p>This is an I/O operation and will block until complete. It must
+ * not be called from the main application thread. A blocked call will be canceled with
+ * {@link IOException} if {@link #close} is called from another thread.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @see NfcA#transceive
+ */
+ public byte[] transceive(byte[] data) throws IOException {
+ return transceive(data, true);
+ }
+
+ /**
+ * Return the maximum number of bytes that can be sent with {@link #transceive}.
+ * @return the maximum number of bytes that can be sent with {@link #transceive}.
+ */
+ public int getMaxTransceiveLength() {
+ return getMaxTransceiveLengthInternal();
+ }
+
+ /**
+ * Set the {@link #transceive} timeout in milliseconds.
+ *
+ * <p>The timeout only applies to {@link #transceive} on this object,
+ * and is reset to a default value when {@link #close} is called.
+ *
+ * <p>Setting a longer timeout may be useful when performing
+ * transactions that require a long processing time on the tag
+ * such as key generation.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param timeout timeout value in milliseconds
+ * @throws SecurityException if the tag object is reused after the tag has left the field
+ */
+ public void setTimeout(int timeout) {
+ try {
+ int err = mTag.getTagService().setTimeout(TagTechnology.MIFARE_CLASSIC, timeout);
+ if (err != ErrorCodes.SUCCESS) {
+ throw new IllegalArgumentException("The supplied timeout is not valid");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "NFC service dead", e);
+ }
+ }
+
+ /**
+ * Get the current {@link #transceive} timeout in milliseconds.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @return timeout value in milliseconds
+ * @throws SecurityException if the tag object is reused after the tag has left the field
+ */
+ public int getTimeout() {
+ try {
+ return mTag.getTagService().getTimeout(TagTechnology.MIFARE_CLASSIC);
+ } catch (RemoteException e) {
+ Log.e(TAG, "NFC service dead", e);
+ return 0;
+ }
+ }
+
+ private static void validateSector(int sector) {
+ // Do not be too strict on upper bounds checking, since some cards
+ // have more addressable memory than they report. For example,
+ // MIFARE Plus 2k cards will appear as MIFARE Classic 1k cards when in
+ // MIFARE Classic compatibility mode.
+ // Note that issuing a command to an out-of-bounds block is safe - the
+ // tag should report error causing IOException. This validation is a
+ // helper to guard against obvious programming mistakes.
+ if (sector < 0 || sector >= MAX_SECTOR_COUNT) {
+ throw new IndexOutOfBoundsException("sector out of bounds: " + sector);
+ }
+ }
+
+ private static void validateBlock(int block) {
+ // Just looking for obvious out of bounds...
+ if (block < 0 || block >= MAX_BLOCK_COUNT) {
+ throw new IndexOutOfBoundsException("block out of bounds: " + block);
+ }
+ }
+
+ private static void validateValueOperand(int value) {
+ if (value < 0) {
+ throw new IllegalArgumentException("value operand negative");
+ }
+ }
+}
diff --git a/nfc/java/android/nfc/tech/MifareUltralight.java b/nfc/java/android/nfc/tech/MifareUltralight.java
new file mode 100644
index 0000000..c0416a3
--- /dev/null
+++ b/nfc/java/android/nfc/tech/MifareUltralight.java
@@ -0,0 +1,280 @@
+/*
+ * 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.tech;
+
+import android.nfc.ErrorCodes;
+import android.nfc.Tag;
+import android.nfc.TagLostException;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.IOException;
+
+//TOOD: Ultralight C 3-DES authentication, one-way counter
+
+/**
+ * Provides access to MIFARE Ultralight properties and I/O operations on a {@link Tag}.
+ *
+ * <p>Acquire a {@link MifareUltralight} object using {@link #get}.
+ *
+ * <p>MIFARE Ultralight compatible tags have 4 byte pages {@link #PAGE_SIZE}.
+ * The primary operations on an Ultralight tag are {@link #readPages} and
+ * {@link #writePage}.
+ *
+ * <p>The original MIFARE Ultralight consists of a 64 byte EEPROM. The first
+ * 4 pages are for the OTP area, manufacturer data, and locking bits. They are
+ * readable and some bits are writable. The final 12 pages are the user
+ * read/write area. For more information see the NXP data sheet MF0ICU1.
+ *
+ * <p>The MIFARE Ultralight C consists of a 192 byte EEPROM. The first 4 pages
+ * are for OTP, manufacturer data, and locking bits. The next 36 pages are the
+ * user read/write area. The next 4 pages are additional locking bits, counters
+ * and authentication configuration and are readable. The final 4 pages are for
+ * the authentication key and are not readable. For more information see the
+ * NXP data sheet MF0ICU2.
+ *
+ * <p>Implementation of this class on a Android NFC device is optional.
+ * If it is not implemented, then
+ * {@link MifareUltralight} will never be enumerated in {@link Tag#getTechList}.
+ * If it is enumerated, then all {@link MifareUltralight} I/O operations will be supported.
+ * In either case, {@link NfcA} will also be enumerated on the tag,
+ * because all MIFARE Ultralight tags are also {@link NfcA} tags.
+ *
+ * <p class="note"><strong>Note:</strong> Methods that perform I/O operations
+ * require the {@link android.Manifest.permission#NFC} permission.
+ */
+public final class MifareUltralight extends BasicTagTechnology {
+ private static final String TAG = "NFC";
+
+ /** A MIFARE Ultralight compatible tag of unknown type */
+ public static final int TYPE_UNKNOWN = -1;
+ /** A MIFARE Ultralight tag */
+ public static final int TYPE_ULTRALIGHT = 1;
+ /** A MIFARE Ultralight C tag */
+ public static final int TYPE_ULTRALIGHT_C = 2;
+
+ /** Size of a MIFARE Ultralight page in bytes */
+ public static final int PAGE_SIZE = 4;
+
+ private static final int NXP_MANUFACTURER_ID = 0x04;
+ private static final int MAX_PAGE_COUNT = 256;
+
+ /** @hide */
+ public static final String EXTRA_IS_UL_C = "isulc";
+
+ private int mType;
+
+ /**
+ * Get an instance of {@link MifareUltralight} for the given tag.
+ * <p>Returns null if {@link MifareUltralight} was not enumerated in
+ * {@link Tag#getTechList} - this indicates the tag is not MIFARE
+ * Ultralight compatible, or that this Android
+ * device does not implement MIFARE Ultralight.
+ * <p>Does not cause any RF activity and does not block.
+ *
+ * @param tag an MIFARE Ultralight compatible tag
+ * @return MIFARE Ultralight object
+ */
+ public static MifareUltralight get(Tag tag) {
+ if (!tag.hasTech(TagTechnology.MIFARE_ULTRALIGHT)) return null;
+ try {
+ return new MifareUltralight(tag);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /** @hide */
+ public MifareUltralight(Tag tag) throws RemoteException {
+ super(tag, TagTechnology.MIFARE_ULTRALIGHT);
+
+ // Check if this could actually be a MIFARE
+ NfcA a = NfcA.get(tag);
+
+ mType = TYPE_UNKNOWN;
+
+ if (a.getSak() == 0x00 && tag.getId()[0] == NXP_MANUFACTURER_ID) {
+ Bundle extras = tag.getTechExtras(TagTechnology.MIFARE_ULTRALIGHT);
+ if (extras.getBoolean(EXTRA_IS_UL_C)) {
+ mType = TYPE_ULTRALIGHT_C;
+ } else {
+ mType = TYPE_ULTRALIGHT;
+ }
+ }
+ }
+
+ /**
+ * Return the MIFARE Ultralight type of the tag.
+ * <p>One of {@link #TYPE_ULTRALIGHT} or {@link #TYPE_ULTRALIGHT_C} or
+ * {@link #TYPE_UNKNOWN}.
+ * <p>Depending on how the tag has been formatted, it can be impossible
+ * to accurately classify between original MIFARE Ultralight and
+ * Ultralight C. So treat this method as a hint.
+ * <p>Does not cause any RF activity and does not block.
+ *
+ * @return the type
+ */
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Read 4 pages (16 bytes).
+ *
+ * <p>The MIFARE Ultralight protocol always reads 4 pages at a time, to
+ * reduce the number of commands required to read an entire tag.
+ * <p>If a read spans past the last readable block, then the tag will
+ * return pages that have been wrapped back to the first blocks. MIFARE
+ * Ultralight tags have readable blocks 0x00 through 0x0F. So a read to
+ * block offset 0x0E would return blocks 0x0E, 0x0F, 0x00, 0x01. MIFARE
+ * Ultralight C tags have readable blocks 0x00 through 0x2B. So a read to
+ * block 0x2A would return blocks 0x2A, 0x2B, 0x00, 0x01.
+ *
+ * <p>This is an I/O operation and will block until complete. It must
+ * not be called from the main application thread. A blocked call will be canceled with
+ * {@link IOException} if {@link #close} is called from another thread.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param pageOffset index of first page to read, starting from 0
+ * @return 4 pages (16 bytes)
+ * @throws TagLostException if the tag leaves the field
+ * @throws IOException if there is an I/O failure, or the operation is canceled
+ */
+ public byte[] readPages(int pageOffset) throws IOException {
+ validatePageIndex(pageOffset);
+ checkConnected();
+
+ byte[] cmd = { 0x30, (byte) pageOffset};
+ return transceive(cmd, false);
+ }
+
+ /**
+ * Write 1 page (4 bytes).
+ *
+ * <p>The MIFARE Ultralight protocol always writes 1 page at a time, to
+ * minimize EEPROM write cycles.
+ *
+ * <p>This is an I/O operation and will block until complete. It must
+ * not be called from the main application thread. A blocked call will be canceled with
+ * {@link IOException} if {@link #close} is called from another thread.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param pageOffset index of page to write, starting from 0
+ * @param data 4 bytes to write
+ * @throws TagLostException if the tag leaves the field
+ * @throws IOException if there is an I/O failure, or the operation is canceled
+ */
+ public void writePage(int pageOffset, byte[] data) throws IOException {
+ validatePageIndex(pageOffset);
+ checkConnected();
+
+ byte[] cmd = new byte[data.length + 2];
+ cmd[0] = (byte) 0xA2;
+ cmd[1] = (byte) pageOffset;
+ System.arraycopy(data, 0, cmd, 2, data.length);
+
+ transceive(cmd, false);
+ }
+
+ /**
+ * Send raw NfcA data to a tag and receive the response.
+ *
+ * <p>This is equivalent to connecting to this tag via {@link NfcA}
+ * and calling {@link NfcA#transceive}. Note that all MIFARE Classic
+ * tags are based on {@link NfcA} technology.
+ *
+ * <p>Use {@link #getMaxTransceiveLength} to retrieve the maximum number of bytes
+ * that can be sent with {@link #transceive}.
+ *
+ * <p>This is an I/O operation and will block until complete. It must
+ * not be called from the main application thread. A blocked call will be canceled with
+ * {@link IOException} if {@link #close} is called from another thread.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @see NfcA#transceive
+ */
+ public byte[] transceive(byte[] data) throws IOException {
+ return transceive(data, true);
+ }
+
+ /**
+ * Return the maximum number of bytes that can be sent with {@link #transceive}.
+ * @return the maximum number of bytes that can be sent with {@link #transceive}.
+ */
+ public int getMaxTransceiveLength() {
+ return getMaxTransceiveLengthInternal();
+ }
+
+ /**
+ * Set the {@link #transceive} timeout in milliseconds.
+ *
+ * <p>The timeout only applies to {@link #transceive} on this object,
+ * and is reset to a default value when {@link #close} is called.
+ *
+ * <p>Setting a longer timeout may be useful when performing
+ * transactions that require a long processing time on the tag
+ * such as key generation.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param timeout timeout value in milliseconds
+ * @throws SecurityException if the tag object is reused after the tag has left the field
+ */
+ public void setTimeout(int timeout) {
+ try {
+ int err = mTag.getTagService().setTimeout(
+ TagTechnology.MIFARE_ULTRALIGHT, timeout);
+ if (err != ErrorCodes.SUCCESS) {
+ throw new IllegalArgumentException("The supplied timeout is not valid");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "NFC service dead", e);
+ }
+ }
+
+ /**
+ * Get the current {@link #transceive} timeout in milliseconds.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @return timeout value in milliseconds
+ * @throws SecurityException if the tag object is reused after the tag has left the field
+ */
+ public int getTimeout() {
+ try {
+ return mTag.getTagService().getTimeout(TagTechnology.MIFARE_ULTRALIGHT);
+ } catch (RemoteException e) {
+ Log.e(TAG, "NFC service dead", e);
+ return 0;
+ }
+ }
+
+ private static void validatePageIndex(int pageIndex) {
+ // Do not be too strict on upper bounds checking, since some cards
+ // may have more addressable memory than they report.
+ // Note that issuing a command to an out-of-bounds block is safe - the
+ // tag will wrap the read to an addressable area. This validation is a
+ // helper to guard against obvious programming mistakes.
+ if (pageIndex < 0 || pageIndex >= MAX_PAGE_COUNT) {
+ throw new IndexOutOfBoundsException("page out of bounds: " + pageIndex);
+ }
+ }
+}
diff --git a/nfc/java/android/nfc/tech/Ndef.java b/nfc/java/android/nfc/tech/Ndef.java
new file mode 100644
index 0000000..7d83f15
--- /dev/null
+++ b/nfc/java/android/nfc/tech/Ndef.java
@@ -0,0 +1,408 @@
+/*
+ * 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.tech;
+
+import android.nfc.ErrorCodes;
+import android.nfc.FormatException;
+import android.nfc.INfcTag;
+import android.nfc.NdefMessage;
+import android.nfc.Tag;
+import android.nfc.TagLostException;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.IOException;
+
+/**
+ * Provides access to NDEF content and operations on a {@link Tag}.
+ *
+ * <p>Acquire a {@link Ndef} object using {@link #get}.
+ *
+ * <p>NDEF is an NFC Forum data format. The data formats are implemented in
+ * {@link android.nfc.NdefMessage} and
+ * {@link android.nfc.NdefRecord}. This class provides methods to
+ * retrieve and modify the {@link android.nfc.NdefMessage}
+ * on a tag.
+ *
+ * <p>There are currently four NFC Forum standardized tag types that can be
+ * formatted to contain NDEF data.
+ * <ul>
+ * <li>NFC Forum Type 1 Tag ({@link #NFC_FORUM_TYPE_1}), such as the Innovision Topaz
+ * <li>NFC Forum Type 2 Tag ({@link #NFC_FORUM_TYPE_2}), such as the NXP MIFARE Ultralight
+ * <li>NFC Forum Type 3 Tag ({@link #NFC_FORUM_TYPE_3}), such as Sony Felica
+ * <li>NFC Forum Type 4 Tag ({@link #NFC_FORUM_TYPE_4}), such as NXP MIFARE Desfire
+ * </ul>
+ * It is mandatory for all Android devices with NFC to correctly enumerate
+ * {@link Ndef} on NFC Forum Tag Types 1-4, and implement all NDEF operations
+ * as defined in this class.
+ *
+ * <p>Some vendors have their own well defined specifications for storing NDEF data
+ * on tags that do not fall into the above categories. Android devices with NFC
+ * should enumerate and implement {@link Ndef} under these vendor specifications
+ * where possible, but it is not mandatory. {@link #getType} returns a String
+ * describing this specification, for example {@link #MIFARE_CLASSIC} is
+ * <code>com.nxp.ndef.mifareclassic</code>.
+ *
+ * <p>Android devices that support MIFARE Classic must also correctly
+ * implement {@link Ndef} on MIFARE Classic tags formatted to NDEF.
+ *
+ * <p>For guaranteed compatibility across all Android devices with NFC, it is
+ * recommended to use NFC Forum Types 1-4 in new deployments of NFC tags
+ * with NDEF payload. Vendor NDEF formats will not work on all Android devices.
+ *
+ * <p class="note"><strong>Note:</strong> Methods that perform I/O operations
+ * require the {@link android.Manifest.permission#NFC} permission.
+ */
+public final class Ndef extends BasicTagTechnology {
+ private static final String TAG = "NFC";
+
+ /** @hide */
+ public static final int NDEF_MODE_READ_ONLY = 1;
+ /** @hide */
+ public static final int NDEF_MODE_READ_WRITE = 2;
+ /** @hide */
+ public static final int NDEF_MODE_UNKNOWN = 3;
+
+ /** @hide */
+ public static final String EXTRA_NDEF_MSG = "ndefmsg";
+
+ /** @hide */
+ public static final String EXTRA_NDEF_MAXLENGTH = "ndefmaxlength";
+
+ /** @hide */
+ public static final String EXTRA_NDEF_CARDSTATE = "ndefcardstate";
+
+ /** @hide */
+ public static final String EXTRA_NDEF_TYPE = "ndeftype";
+
+ /** @hide */
+ public static final int TYPE_OTHER = -1;
+ /** @hide */
+ public static final int TYPE_1 = 1;
+ /** @hide */
+ public static final int TYPE_2 = 2;
+ /** @hide */
+ public static final int TYPE_3 = 3;
+ /** @hide */
+ public static final int TYPE_4 = 4;
+ /** @hide */
+ public static final int TYPE_MIFARE_CLASSIC = 101;
+ /** @hide */
+ public static final int TYPE_ICODE_SLI = 102;
+
+ /** @hide */
+ public static final String UNKNOWN = "android.ndef.unknown";
+
+ /** NFC Forum Tag Type 1 */
+ public static final String NFC_FORUM_TYPE_1 = "org.nfcforum.ndef.type1";
+ /** NFC Forum Tag Type 2 */
+ public static final String NFC_FORUM_TYPE_2 = "org.nfcforum.ndef.type2";
+ /** NFC Forum Tag Type 3 */
+ public static final String NFC_FORUM_TYPE_3 = "org.nfcforum.ndef.type3";
+ /** NFC Forum Tag Type 4 */
+ public static final String NFC_FORUM_TYPE_4 = "org.nfcforum.ndef.type4";
+ /** NDEF on MIFARE Classic */
+ public static final String MIFARE_CLASSIC = "com.nxp.ndef.mifareclassic";
+ /**
+ * NDEF on iCODE SLI
+ * @hide
+ */
+ public static final String ICODE_SLI = "com.nxp.ndef.icodesli";
+
+ private final int mMaxNdefSize;
+ private final int mCardState;
+ private final NdefMessage mNdefMsg;
+ private final int mNdefType;
+
+ /**
+ * Get an instance of {@link Ndef} for the given tag.
+ *
+ * <p>Returns null if {@link Ndef} was not enumerated in {@link Tag#getTechList}.
+ * This indicates the tag is not NDEF formatted, or that this tag
+ * is NDEF formatted but under a vendor specification that this Android
+ * device does not implement.
+ *
+ * <p>Does not cause any RF activity and does not block.
+ *
+ * @param tag an NDEF compatible tag
+ * @return Ndef object
+ */
+ public static Ndef get(Tag tag) {
+ if (!tag.hasTech(TagTechnology.NDEF)) return null;
+ try {
+ return new Ndef(tag);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Internal constructor, to be used by NfcAdapter
+ * @hide
+ */
+ public Ndef(Tag tag) throws RemoteException {
+ super(tag, TagTechnology.NDEF);
+ Bundle extras = tag.getTechExtras(TagTechnology.NDEF);
+ if (extras != null) {
+ mMaxNdefSize = extras.getInt(EXTRA_NDEF_MAXLENGTH);
+ mCardState = extras.getInt(EXTRA_NDEF_CARDSTATE);
+ mNdefMsg = extras.getParcelable(EXTRA_NDEF_MSG, android.nfc.NdefMessage.class);
+ mNdefType = extras.getInt(EXTRA_NDEF_TYPE);
+ } else {
+ throw new NullPointerException("NDEF tech extras are null.");
+ }
+
+ }
+
+ /**
+ * Get the {@link NdefMessage} that was read from the tag at discovery time.
+ *
+ * <p>If the NDEF Message is modified by an I/O operation then it
+ * will not be updated here, this function only returns what was discovered
+ * when the tag entered the field.
+ * <p>Note that this method may return null if the tag was in the
+ * INITIALIZED state as defined by NFC Forum, as in this state the
+ * tag is formatted to support NDEF but does not contain a message yet.
+ * <p>Does not cause any RF activity and does not block.
+ * @return NDEF Message read from the tag at discovery time, can be null
+ */
+ public NdefMessage getCachedNdefMessage() {
+ return mNdefMsg;
+ }
+
+ /**
+ * Get the NDEF tag type.
+ *
+ * <p>Returns one of {@link #NFC_FORUM_TYPE_1}, {@link #NFC_FORUM_TYPE_2},
+ * {@link #NFC_FORUM_TYPE_3}, {@link #NFC_FORUM_TYPE_4},
+ * {@link #MIFARE_CLASSIC} or another NDEF tag type that has not yet been
+ * formalized in this Android API.
+ *
+ * <p>Does not cause any RF activity and does not block.
+ *
+ * @return a string representing the NDEF tag type
+ */
+ public String getType() {
+ switch (mNdefType) {
+ case TYPE_1:
+ return NFC_FORUM_TYPE_1;
+ case TYPE_2:
+ return NFC_FORUM_TYPE_2;
+ case TYPE_3:
+ return NFC_FORUM_TYPE_3;
+ case TYPE_4:
+ return NFC_FORUM_TYPE_4;
+ case TYPE_MIFARE_CLASSIC:
+ return MIFARE_CLASSIC;
+ case TYPE_ICODE_SLI:
+ return ICODE_SLI;
+ default:
+ return UNKNOWN;
+ }
+ }
+
+ /**
+ * Get the maximum NDEF message size in bytes.
+ *
+ * <p>Does not cause any RF activity and does not block.
+ *
+ * @return size in bytes
+ */
+ public int getMaxSize() {
+ return mMaxNdefSize;
+ }
+
+ /**
+ * Determine if the tag is writable.
+ *
+ * <p>NFC Forum tags can be in read-only or read-write states.
+ *
+ * <p>Does not cause any RF activity and does not block.
+ *
+ * <p>Requires {@link android.Manifest.permission#NFC} permission.
+ *
+ * @return true if the tag is writable
+ */
+ public boolean isWritable() {
+ return (mCardState == NDEF_MODE_READ_WRITE);
+ }
+
+ /**
+ * Read the current {@link android.nfc.NdefMessage} on this tag.
+ *
+ * <p>This always reads the current NDEF Message stored on the tag.
+ *
+ * <p>Note that this method may return null if the tag was in the
+ * INITIALIZED state as defined by NFC Forum, as in that state the
+ * tag is formatted to support NDEF but does not contain a message yet.
+ *
+ * <p>This is an I/O operation and will block until complete. It must
+ * not be called from the main application thread. A blocked call will be canceled with
+ * {@link IOException} if {@link #close} is called from another thread.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @return the NDEF Message, can be null
+ * @throws TagLostException if the tag leaves the field
+ * @throws IOException if there is an I/O failure, or the operation is canceled
+ * @throws FormatException if the NDEF Message on the tag is malformed
+ * @throws SecurityException if the tag object is reused after the tag has left the field
+ */
+ public NdefMessage getNdefMessage() throws IOException, FormatException {
+ checkConnected();
+
+ try {
+ INfcTag tagService = mTag.getTagService();
+ if (tagService == null) {
+ throw new IOException("Mock tags don't support this operation.");
+ }
+ int serviceHandle = mTag.getServiceHandle();
+ if (tagService.isNdef(serviceHandle)) {
+ NdefMessage msg = tagService.ndefRead(serviceHandle);
+ if (msg == null && !tagService.isPresent(serviceHandle)) {
+ throw new TagLostException();
+ }
+ return msg;
+ } else if (!tagService.isPresent(serviceHandle)) {
+ throw new TagLostException();
+ } else {
+ return null;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "NFC service dead", e);
+ return null;
+ }
+ }
+
+ /**
+ * Overwrite the {@link NdefMessage} on this tag.
+ *
+ * <p>This is an I/O operation and will block until complete. It must
+ * not be called from the main application thread. A blocked call will be canceled with
+ * {@link IOException} if {@link #close} is called from another thread.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param msg the NDEF Message to write, must not be null
+ * @throws TagLostException if the tag leaves the field
+ * @throws IOException if there is an I/O failure, or the operation is canceled
+ * @throws FormatException if the NDEF Message to write is malformed
+ * @throws SecurityException if the tag object is reused after the tag has left the field
+ */
+ public void writeNdefMessage(NdefMessage msg) throws IOException, FormatException {
+ checkConnected();
+
+ try {
+ INfcTag tagService = mTag.getTagService();
+ if (tagService == null) {
+ throw new IOException("Mock tags don't support this operation.");
+ }
+ int serviceHandle = mTag.getServiceHandle();
+ if (tagService.isNdef(serviceHandle)) {
+ int errorCode = tagService.ndefWrite(serviceHandle, msg);
+ switch (errorCode) {
+ case ErrorCodes.SUCCESS:
+ break;
+ case ErrorCodes.ERROR_IO:
+ throw new IOException();
+ case ErrorCodes.ERROR_INVALID_PARAM:
+ throw new FormatException();
+ default:
+ // Should not happen
+ throw new IOException();
+ }
+ }
+ else {
+ throw new IOException("Tag is not ndef");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "NFC service dead", e);
+ }
+ }
+
+ /**
+ * Indicates whether a tag can be made read-only with {@link #makeReadOnly()}.
+ *
+ * <p>Does not cause any RF activity and does not block.
+ *
+ * @return true if it is possible to make this tag read-only
+ * @throws SecurityException if the tag object is reused after the tag has left the field
+ */
+ public boolean canMakeReadOnly() {
+ INfcTag tagService = mTag.getTagService();
+ if (tagService == null) {
+ return false;
+ }
+ try {
+ return tagService.canMakeReadOnly(mNdefType);
+ } catch (RemoteException e) {
+ Log.e(TAG, "NFC service dead", e);
+ return false;
+ }
+ }
+
+ /**
+ * Make a tag read-only.
+ *
+ * <p>This sets the CC field to indicate the tag is read-only,
+ * and where possible permanently sets the lock bits to prevent
+ * any further modification of the memory.
+ * <p>This is a one-way process and cannot be reverted!
+ *
+ * <p>This is an I/O operation and will block until complete. It must
+ * not be called from the main application thread. A blocked call will be canceled with
+ * {@link IOException} if {@link #close} is called from another thread.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @return true on success, false if it is not possible to make this tag read-only
+ * @throws TagLostException if the tag leaves the field
+ * @throws IOException if there is an I/O failure, or the operation is canceled
+ * @throws SecurityException if the tag object is reused after the tag has left the field
+ */
+ public boolean makeReadOnly() throws IOException {
+ checkConnected();
+
+ try {
+ INfcTag tagService = mTag.getTagService();
+ if (tagService == null) {
+ return false;
+ }
+ if (tagService.isNdef(mTag.getServiceHandle())) {
+ int errorCode = tagService.ndefMakeReadOnly(mTag.getServiceHandle());
+ switch (errorCode) {
+ case ErrorCodes.SUCCESS:
+ return true;
+ case ErrorCodes.ERROR_IO:
+ throw new IOException();
+ case ErrorCodes.ERROR_INVALID_PARAM:
+ return false;
+ default:
+ // Should not happen
+ throw new IOException();
+ }
+ }
+ else {
+ throw new IOException("Tag is not ndef");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "NFC service dead", e);
+ return false;
+ }
+ }
+}
diff --git a/nfc/java/android/nfc/tech/NdefFormatable.java b/nfc/java/android/nfc/tech/NdefFormatable.java
new file mode 100644
index 0000000..2240fe7
--- /dev/null
+++ b/nfc/java/android/nfc/tech/NdefFormatable.java
@@ -0,0 +1,182 @@
+/*
+ * 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.tech;
+
+import android.nfc.ErrorCodes;
+import android.nfc.FormatException;
+import android.nfc.INfcTag;
+import android.nfc.NdefMessage;
+import android.nfc.Tag;
+import android.nfc.TagLostException;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.IOException;
+
+/**
+ * Provide access to NDEF format operations on a {@link Tag}.
+ *
+ * <p>Acquire a {@link NdefFormatable} object using {@link #get}.
+ *
+ * <p>Android devices with NFC must only enumerate and implement this
+ * class for tags for which it can format to NDEF.
+ *
+ * <p>Unfortunately the procedures to convert unformated tags to NDEF formatted
+ * tags are not specified by NFC Forum, and are not generally well-known. So
+ * there is no mandatory set of tags for which all Android devices with NFC
+ * must support {@link NdefFormatable}.
+ *
+ * <p class="note"><strong>Note:</strong> Methods that perform I/O operations
+ * require the {@link android.Manifest.permission#NFC} permission.
+ */
+public final class NdefFormatable extends BasicTagTechnology {
+ private static final String TAG = "NFC";
+
+ /**
+ * Get an instance of {@link NdefFormatable} for the given tag.
+ * <p>Does not cause any RF activity and does not block.
+ * <p>Returns null if {@link NdefFormatable} was not enumerated in {@link Tag#getTechList}.
+ * This indicates the tag is not NDEF formatable by this Android device.
+ *
+ * @param tag an NDEF formatable tag
+ * @return NDEF formatable object
+ */
+ public static NdefFormatable get(Tag tag) {
+ if (!tag.hasTech(TagTechnology.NDEF_FORMATABLE)) return null;
+ try {
+ return new NdefFormatable(tag);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Internal constructor, to be used by NfcAdapter
+ * @hide
+ */
+ public NdefFormatable(Tag tag) throws RemoteException {
+ super(tag, TagTechnology.NDEF_FORMATABLE);
+ }
+
+ /**
+ * Format a tag as NDEF, and write a {@link NdefMessage}.
+ *
+ * <p>This is a multi-step process, an IOException is thrown
+ * if any one step fails.
+ * <p>The card is left in a read-write state after this operation.
+ *
+ * <p>This is an I/O operation and will block until complete. It must
+ * not be called from the main application thread. A blocked call will be canceled with
+ * {@link IOException} if {@link #close} is called from another thread.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param firstMessage the NDEF message to write after formatting, can be null
+ * @throws TagLostException if the tag leaves the field
+ * @throws IOException if there is an I/O failure, or the operation is canceled
+ * @throws FormatException if the NDEF Message to write is malformed
+ */
+ public void format(NdefMessage firstMessage) throws IOException, FormatException {
+ format(firstMessage, false);
+ }
+
+ /**
+ * Formats a tag as NDEF, write a {@link NdefMessage}, and make read-only.
+ *
+ * <p>This is a multi-step process, an IOException is thrown
+ * if any one step fails.
+ * <p>The card is left in a read-only state if this method returns successfully.
+ *
+ * <p>This is an I/O operation and will block until complete. It must
+ * not be called from the main application thread. A blocked call will be canceled with
+ * {@link IOException} if {@link #close} is called from another thread.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param firstMessage the NDEF message to write after formatting
+ * @throws TagLostException if the tag leaves the field
+ * @throws IOException if there is an I/O failure, or the operation is canceled
+ * @throws FormatException if the NDEF Message to write is malformed
+ * @throws SecurityException if the tag object is reused after the tag has left the field
+ */
+ public void formatReadOnly(NdefMessage firstMessage) throws IOException, FormatException {
+ format(firstMessage, true);
+ }
+
+ /*package*/ void format(NdefMessage firstMessage, boolean makeReadOnly) throws IOException,
+ FormatException {
+ checkConnected();
+
+ try {
+ int serviceHandle = mTag.getServiceHandle();
+ INfcTag tagService = mTag.getTagService();
+ if (tagService == null) {
+ throw new IOException();
+ }
+ int errorCode = tagService.formatNdef(serviceHandle, MifareClassic.KEY_DEFAULT);
+ switch (errorCode) {
+ case ErrorCodes.SUCCESS:
+ break;
+ case ErrorCodes.ERROR_IO:
+ throw new IOException();
+ case ErrorCodes.ERROR_INVALID_PARAM:
+ throw new FormatException();
+ default:
+ // Should not happen
+ throw new IOException();
+ }
+ // Now check and see if the format worked
+ if (!tagService.isNdef(serviceHandle)) {
+ throw new IOException();
+ }
+
+ // Write a message, if one was provided
+ if (firstMessage != null) {
+ errorCode = tagService.ndefWrite(serviceHandle, firstMessage);
+ switch (errorCode) {
+ case ErrorCodes.SUCCESS:
+ break;
+ case ErrorCodes.ERROR_IO:
+ throw new IOException();
+ case ErrorCodes.ERROR_INVALID_PARAM:
+ throw new FormatException();
+ default:
+ // Should not happen
+ throw new IOException();
+ }
+ }
+
+ // optionally make read-only
+ if (makeReadOnly) {
+ errorCode = tagService.ndefMakeReadOnly(serviceHandle);
+ switch (errorCode) {
+ case ErrorCodes.SUCCESS:
+ break;
+ case ErrorCodes.ERROR_IO:
+ throw new IOException();
+ case ErrorCodes.ERROR_INVALID_PARAM:
+ throw new IOException();
+ default:
+ // Should not happen
+ throw new IOException();
+ }
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "NFC service dead", e);
+ }
+ }
+}
diff --git a/nfc/java/android/nfc/tech/NfcA.java b/nfc/java/android/nfc/tech/NfcA.java
new file mode 100644
index 0000000..7e66483
--- /dev/null
+++ b/nfc/java/android/nfc/tech/NfcA.java
@@ -0,0 +1,173 @@
+/*
+ * 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.tech;
+
+import android.nfc.ErrorCodes;
+import android.nfc.Tag;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.IOException;
+
+/**
+ * Provides access to NFC-A (ISO 14443-3A) properties and I/O operations on a {@link Tag}.
+ *
+ * <p>Acquire a {@link NfcA} object using {@link #get}.
+ * <p>The primary NFC-A I/O operation is {@link #transceive}. Applications must
+ * implement their own protocol stack on top of {@link #transceive}.
+ *
+ * <p class="note"><strong>Note:</strong> Methods that perform I/O operations
+ * require the {@link android.Manifest.permission#NFC} permission.
+ */
+public final class NfcA extends BasicTagTechnology {
+ private static final String TAG = "NFC";
+
+ /** @hide */
+ public static final String EXTRA_SAK = "sak";
+ /** @hide */
+ public static final String EXTRA_ATQA = "atqa";
+
+ private short mSak;
+ private byte[] mAtqa;
+
+ /**
+ * Get an instance of {@link NfcA} for the given tag.
+ * <p>Returns null if {@link NfcA} was not enumerated in {@link Tag#getTechList}.
+ * This indicates the tag does not support NFC-A.
+ * <p>Does not cause any RF activity and does not block.
+ *
+ * @param tag an NFC-A compatible tag
+ * @return NFC-A object
+ */
+ public static NfcA get(Tag tag) {
+ if (!tag.hasTech(TagTechnology.NFC_A)) return null;
+ try {
+ return new NfcA(tag);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /** @hide */
+ public NfcA(Tag tag) throws RemoteException {
+ super(tag, TagTechnology.NFC_A);
+ Bundle extras = tag.getTechExtras(TagTechnology.NFC_A);
+ mSak = extras.getShort(EXTRA_SAK);
+ mAtqa = extras.getByteArray(EXTRA_ATQA);
+ }
+
+ /**
+ * Return the ATQA/SENS_RES bytes from tag discovery.
+ *
+ * <p>Does not cause any RF activity and does not block.
+ *
+ * @return ATQA/SENS_RES bytes
+ */
+ public byte[] getAtqa() {
+ return mAtqa;
+ }
+
+ /**
+ * Return the SAK/SEL_RES bytes from tag discovery.
+ *
+ * <p>Does not cause any RF activity and does not block.
+ *
+ * @return SAK bytes
+ */
+ public short getSak() {
+ return mSak;
+ }
+
+ /**
+ * Send raw NFC-A commands to the tag and receive the response.
+ *
+ * <p>Applications must not append the EoD (CRC) to the payload,
+ * it will be automatically calculated.
+ * <p>Applications must only send commands that are complete bytes,
+ * for example a SENS_REQ is not possible (these are used to
+ * manage tag polling and initialization).
+ *
+ * <p>Use {@link #getMaxTransceiveLength} to retrieve the maximum number of bytes
+ * that can be sent with {@link #transceive}.
+ *
+ * <p>This is an I/O operation and will block until complete. It must
+ * not be called from the main application thread. A blocked call will be canceled with
+ * {@link IOException} if {@link #close} is called from another thread.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param data bytes to send
+ * @return bytes received in response
+ * @throws TagLostException if the tag leaves the field
+ * @throws IOException if there is an I/O failure, or this operation is canceled
+ */
+ public byte[] transceive(byte[] data) throws IOException {
+ return transceive(data, true);
+ }
+
+ /**
+ * Return the maximum number of bytes that can be sent with {@link #transceive}.
+ * @return the maximum number of bytes that can be sent with {@link #transceive}.
+ */
+ public int getMaxTransceiveLength() {
+ return getMaxTransceiveLengthInternal();
+ }
+
+ /**
+ * Set the {@link #transceive} timeout in milliseconds.
+ *
+ * <p>The timeout only applies to {@link #transceive} on this object,
+ * and is reset to a default value when {@link #close} is called.
+ *
+ * <p>Setting a longer timeout may be useful when performing
+ * transactions that require a long processing time on the tag
+ * such as key generation.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param timeout timeout value in milliseconds
+ * @throws SecurityException if the tag object is reused after the tag has left the field
+ */
+ public void setTimeout(int timeout) {
+ try {
+ int err = mTag.getTagService().setTimeout(TagTechnology.NFC_A, timeout);
+ if (err != ErrorCodes.SUCCESS) {
+ throw new IllegalArgumentException("The supplied timeout is not valid");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "NFC service dead", e);
+ }
+ }
+
+ /**
+ * Get the current {@link #transceive} timeout in milliseconds.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @return timeout value in milliseconds
+ * @throws SecurityException if the tag object is reused after the tag has left the field
+ */
+ public int getTimeout() {
+ try {
+ return mTag.getTagService().getTimeout(TagTechnology.NFC_A);
+ } catch (RemoteException e) {
+ Log.e(TAG, "NFC service dead", e);
+ return 0;
+ }
+ }
+}
diff --git a/nfc/java/android/nfc/tech/NfcB.java b/nfc/java/android/nfc/tech/NfcB.java
new file mode 100644
index 0000000..3ebd47f
--- /dev/null
+++ b/nfc/java/android/nfc/tech/NfcB.java
@@ -0,0 +1,125 @@
+/*
+ * 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.tech;
+
+import android.nfc.Tag;
+import android.os.Bundle;
+import android.os.RemoteException;
+
+import java.io.IOException;
+
+/**
+ * Provides access to NFC-B (ISO 14443-3B) properties and I/O operations on a {@link Tag}.
+ *
+ * <p>Acquire a {@link NfcB} object using {@link #get}.
+ * <p>The primary NFC-B I/O operation is {@link #transceive}. Applications must
+ * implement their own protocol stack on top of {@link #transceive}.
+ *
+ * <p class="note"><strong>Note:</strong> Methods that perform I/O operations
+ * require the {@link android.Manifest.permission#NFC} permission.
+ */
+public final class NfcB extends BasicTagTechnology {
+ /** @hide */
+ public static final String EXTRA_APPDATA = "appdata";
+ /** @hide */
+ public static final String EXTRA_PROTINFO = "protinfo";
+
+ private byte[] mAppData;
+ private byte[] mProtInfo;
+
+ /**
+ * Get an instance of {@link NfcB} for the given tag.
+ * <p>Returns null if {@link NfcB} was not enumerated in {@link Tag#getTechList}.
+ * This indicates the tag does not support NFC-B.
+ * <p>Does not cause any RF activity and does not block.
+ *
+ * @param tag an NFC-B compatible tag
+ * @return NFC-B object
+ */
+ public static NfcB get(Tag tag) {
+ if (!tag.hasTech(TagTechnology.NFC_B)) return null;
+ try {
+ return new NfcB(tag);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /** @hide */
+ public NfcB(Tag tag) throws RemoteException {
+ super(tag, TagTechnology.NFC_B);
+ Bundle extras = tag.getTechExtras(TagTechnology.NFC_B);
+ mAppData = extras.getByteArray(EXTRA_APPDATA);
+ mProtInfo = extras.getByteArray(EXTRA_PROTINFO);
+ }
+
+ /**
+ * Return the Application Data bytes from ATQB/SENSB_RES at tag discovery.
+ *
+ * <p>Does not cause any RF activity and does not block.
+ *
+ * @return Application Data bytes from ATQB/SENSB_RES bytes
+ */
+ public byte[] getApplicationData() {
+ return mAppData;
+ }
+
+ /**
+ * Return the Protocol Info bytes from ATQB/SENSB_RES at tag discovery.
+ *
+ * <p>Does not cause any RF activity and does not block.
+ *
+ * @return Protocol Info bytes from ATQB/SENSB_RES bytes
+ */
+ public byte[] getProtocolInfo() {
+ return mProtInfo;
+ }
+
+ /**
+ * Send raw NFC-B commands to the tag and receive the response.
+ *
+ * <p>Applications must not append the EoD (CRC) to the payload,
+ * it will be automatically calculated.
+ * <p>Applications must not send commands that manage the polling
+ * loop and initialization (SENSB_REQ, SLOT_MARKER etc).
+ *
+ * <p>Use {@link #getMaxTransceiveLength} to retrieve the maximum number of bytes
+ * that can be sent with {@link #transceive}.
+ *
+ * <p>This is an I/O operation and will block until complete. It must
+ * not be called from the main application thread. A blocked call will be canceled with
+ * {@link IOException} if {@link #close} is called from another thread.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param data bytes to send
+ * @return bytes received in response
+ * @throws TagLostException if the tag leaves the field
+ * @throws IOException if there is an I/O failure, or this operation is canceled
+ */
+ public byte[] transceive(byte[] data) throws IOException {
+ return transceive(data, true);
+ }
+
+ /**
+ * Return the maximum number of bytes that can be sent with {@link #transceive}.
+ * @return the maximum number of bytes that can be sent with {@link #transceive}.
+ */
+ public int getMaxTransceiveLength() {
+ return getMaxTransceiveLengthInternal();
+ }
+}
diff --git a/nfc/java/android/nfc/tech/NfcBarcode.java b/nfc/java/android/nfc/tech/NfcBarcode.java
new file mode 100644
index 0000000..421ba78
--- /dev/null
+++ b/nfc/java/android/nfc/tech/NfcBarcode.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2012 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.tech;
+
+import android.nfc.Tag;
+import android.os.Bundle;
+import android.os.RemoteException;
+
+/**
+ * Provides access to tags containing just a barcode.
+ *
+ * <p>Acquire an {@link NfcBarcode} object using {@link #get}.
+ *
+ */
+public final class NfcBarcode extends BasicTagTechnology {
+
+ /** Kovio Tags */
+ public static final int TYPE_KOVIO = 1;
+ public static final int TYPE_UNKNOWN = -1;
+
+ /** @hide */
+ public static final String EXTRA_BARCODE_TYPE = "barcodetype";
+
+ private int mType;
+
+ /**
+ * Get an instance of {@link NfcBarcode} for the given tag.
+ *
+ * <p>Returns null if {@link NfcBarcode} was not enumerated in {@link Tag#getTechList}.
+ *
+ * <p>Does not cause any RF activity and does not block.
+ *
+ * @param tag an NfcBarcode compatible tag
+ * @return NfcBarcode object
+ */
+ public static NfcBarcode get(Tag tag) {
+ if (!tag.hasTech(TagTechnology.NFC_BARCODE)) return null;
+ try {
+ return new NfcBarcode(tag);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Internal constructor, to be used by NfcAdapter
+ * @hide
+ */
+ public NfcBarcode(Tag tag) throws RemoteException {
+ super(tag, TagTechnology.NFC_BARCODE);
+ Bundle extras = tag.getTechExtras(TagTechnology.NFC_BARCODE);
+ if (extras != null) {
+ mType = extras.getInt(EXTRA_BARCODE_TYPE);
+ } else {
+ throw new NullPointerException("NfcBarcode tech extras are null.");
+ }
+ }
+
+ /**
+ * Returns the NFC Barcode tag type.
+ *
+ * <p>Currently only one of {@link #TYPE_KOVIO} or {@link #TYPE_UNKNOWN}.
+ *
+ * <p>Does not cause any RF activity and does not block.
+ *
+ * @return the NFC Barcode tag type
+ */
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Returns the barcode of an NfcBarcode tag.
+ *
+ * <p> Tags of {@link #TYPE_KOVIO} return 16 bytes:
+ * <ul>
+ * <p> The first byte is 0x80 ORd with a manufacturer ID, corresponding
+ * to ISO/IEC 7816-6.
+ * <p> The second byte describes the payload data format. Defined data
+ * format types include the following:<ul>
+ * <li>0x00: Reserved for manufacturer assignment</li>
+ * <li>0x01: 96-bit URL with "http://www." prefix</li>
+ * <li>0x02: 96-bit URL with "https://www." prefix</li>
+ * <li>0x03: 96-bit URL with "http://" prefix</li>
+ * <li>0x04: 96-bit URL with "https://" prefix</li>
+ * <li>0x05: 96-bit GS1 EPC</li>
+ * <li>0x06-0xFF: reserved</li>
+ * </ul>
+ * <p>The following 12 bytes are payload:<ul>
+ * <li> In case of a URL payload, the payload is encoded in US-ASCII,
+ * following the limitations defined in RFC3987.
+ * {@see <a href="http://www.ietf.org/rfc/rfc3987.txt">RFC 3987</a>}</li>
+ * <li> In case of GS1 EPC data, see <a href="http://www.gs1.org/gsmp/kc/epcglobal/tds/">
+ * GS1 Electronic Product Code (EPC) Tag Data Standard (TDS)</a> for more details.
+ * </li>
+ * </ul>
+ * <p>The last 2 bytes comprise the CRC.
+ * </ul>
+ * <p>Does not cause any RF activity and does not block.
+ *
+ * @return a byte array containing the barcode
+ * @see <a href="http://www.thinfilm.no/docs/thinfilm-nfc-barcode-datasheet.pdf">
+ * Thinfilm NFC Barcode tag specification (previously Kovio NFC Barcode)</a>
+ * @see <a href="http://www.thinfilm.no/docs/thinfilm-nfc-barcode-data-format.pdf">
+ * Thinfilm NFC Barcode data format (previously Kovio NFC Barcode)</a>
+ */
+ public byte[] getBarcode() {
+ switch (mType) {
+ case TYPE_KOVIO:
+ // For Kovio tags the barcode matches the ID
+ return mTag.getId();
+ default:
+ return null;
+ }
+ }
+}
diff --git a/nfc/java/android/nfc/tech/NfcF.java b/nfc/java/android/nfc/tech/NfcF.java
new file mode 100644
index 0000000..2ccd388
--- /dev/null
+++ b/nfc/java/android/nfc/tech/NfcF.java
@@ -0,0 +1,177 @@
+/*
+ * 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.tech;
+
+import android.nfc.ErrorCodes;
+import android.nfc.Tag;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.IOException;
+
+/**
+ * Provides access to NFC-F (JIS 6319-4) properties and I/O operations on a {@link Tag}.
+ *
+ * <p>Acquire a {@link NfcF} object using {@link #get}.
+ * <p>The primary NFC-F I/O operation is {@link #transceive}. Applications must
+ * implement their own protocol stack on top of {@link #transceive}.
+ *
+ * <p class="note"><strong>Note:</strong> Methods that perform I/O operations
+ * require the {@link android.Manifest.permission#NFC} permission.
+ */
+public final class NfcF extends BasicTagTechnology {
+ private static final String TAG = "NFC";
+
+ /** @hide */
+ public static final String EXTRA_SC = "systemcode";
+ /** @hide */
+ public static final String EXTRA_PMM = "pmm";
+
+ private byte[] mSystemCode = null;
+ private byte[] mManufacturer = null;
+
+ /**
+ * Get an instance of {@link NfcF} for the given tag.
+ * <p>Returns null if {@link NfcF} was not enumerated in {@link Tag#getTechList}.
+ * This indicates the tag does not support NFC-F.
+ * <p>Does not cause any RF activity and does not block.
+ *
+ * @param tag an NFC-F compatible tag
+ * @return NFC-F object
+ */
+ public static NfcF get(Tag tag) {
+ if (!tag.hasTech(TagTechnology.NFC_F)) return null;
+ try {
+ return new NfcF(tag);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /** @hide */
+ public NfcF(Tag tag) throws RemoteException {
+ super(tag, TagTechnology.NFC_F);
+ Bundle extras = tag.getTechExtras(TagTechnology.NFC_F);
+ if (extras != null) {
+ mSystemCode = extras.getByteArray(EXTRA_SC);
+ mManufacturer = extras.getByteArray(EXTRA_PMM);
+ }
+ }
+
+ /**
+ * Return the System Code bytes from tag discovery.
+ *
+ * <p>Does not cause any RF activity and does not block.
+ *
+ * @return System Code bytes
+ */
+ public byte[] getSystemCode() {
+ return mSystemCode;
+ }
+
+ /**
+ * Return the Manufacturer bytes from tag discovery.
+ *
+ * <p>Does not cause any RF activity and does not block.
+ *
+ * @return Manufacturer bytes
+ */
+ public byte[] getManufacturer() {
+ return mManufacturer;
+ }
+
+ /**
+ * Send raw NFC-F commands to the tag and receive the response.
+ *
+ * <p>Applications must not prefix the SoD (preamble and sync code)
+ * and/or append the EoD (CRC) to the payload, it will be automatically calculated.
+ *
+ * <p>A typical NFC-F frame for this method looks like:
+ * <pre>
+ * LENGTH (1 byte) --- CMD (1 byte) -- IDm (8 bytes) -- PARAMS (LENGTH - 10 bytes)
+ * </pre>
+ *
+ * <p>Use {@link #getMaxTransceiveLength} to retrieve the maximum amount of bytes
+ * that can be sent with {@link #transceive}.
+ *
+ * <p>This is an I/O operation and will block until complete. It must
+ * not be called from the main application thread. A blocked call will be canceled with
+ * {@link IOException} if {@link #close} is called from another thread.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param data bytes to send
+ * @return bytes received in response
+ * @throws TagLostException if the tag leaves the field
+ * @throws IOException if there is an I/O failure, or this operation is canceled
+ */
+ public byte[] transceive(byte[] data) throws IOException {
+ return transceive(data, true);
+ }
+
+ /**
+ * Return the maximum number of bytes that can be sent with {@link #transceive}.
+ * @return the maximum number of bytes that can be sent with {@link #transceive}.
+ */
+ public int getMaxTransceiveLength() {
+ return getMaxTransceiveLengthInternal();
+ }
+
+ /**
+ * Set the {@link #transceive} timeout in milliseconds.
+ *
+ * <p>The timeout only applies to {@link #transceive} on this object,
+ * and is reset to a default value when {@link #close} is called.
+ *
+ * <p>Setting a longer timeout may be useful when performing
+ * transactions that require a long processing time on the tag
+ * such as key generation.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param timeout timeout value in milliseconds
+ * @throws SecurityException if the tag object is reused after the tag has left the field
+ */
+ public void setTimeout(int timeout) {
+ try {
+ int err = mTag.getTagService().setTimeout(TagTechnology.NFC_F, timeout);
+ if (err != ErrorCodes.SUCCESS) {
+ throw new IllegalArgumentException("The supplied timeout is not valid");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "NFC service dead", e);
+ }
+ }
+
+ /**
+ * Get the current {@link #transceive} timeout in milliseconds.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @return timeout value in milliseconds
+ * @throws SecurityException if the tag object is reused after the tag has left the field
+ */
+ public int getTimeout() {
+ try {
+ return mTag.getTagService().getTimeout(TagTechnology.NFC_F);
+ } catch (RemoteException e) {
+ Log.e(TAG, "NFC service dead", e);
+ return 0;
+ }
+ }
+}
diff --git a/nfc/java/android/nfc/tech/NfcV.java b/nfc/java/android/nfc/tech/NfcV.java
new file mode 100644
index 0000000..186c63b
--- /dev/null
+++ b/nfc/java/android/nfc/tech/NfcV.java
@@ -0,0 +1,126 @@
+/*
+ * 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.tech;
+
+import android.nfc.Tag;
+import android.os.Bundle;
+import android.os.RemoteException;
+
+import java.io.IOException;
+
+/**
+ * Provides access to NFC-V (ISO 15693) properties and I/O operations on a {@link Tag}.
+ *
+ * <p>Acquire a {@link NfcV} object using {@link #get}.
+ * <p>The primary NFC-V I/O operation is {@link #transceive}. Applications must
+ * implement their own protocol stack on top of {@link #transceive}.
+ *
+ * <p class="note"><strong>Note:</strong> Methods that perform I/O operations
+ * require the {@link android.Manifest.permission#NFC} permission.
+ */
+public final class NfcV extends BasicTagTechnology {
+ /** @hide */
+ public static final String EXTRA_RESP_FLAGS = "respflags";
+
+ /** @hide */
+ public static final String EXTRA_DSFID = "dsfid";
+
+ private byte mRespFlags;
+ private byte mDsfId;
+
+ /**
+ * Get an instance of {@link NfcV} for the given tag.
+ * <p>Returns null if {@link NfcV} was not enumerated in {@link Tag#getTechList}.
+ * This indicates the tag does not support NFC-V.
+ * <p>Does not cause any RF activity and does not block.
+ *
+ * @param tag an NFC-V compatible tag
+ * @return NFC-V object
+ */
+ public static NfcV get(Tag tag) {
+ if (!tag.hasTech(TagTechnology.NFC_V)) return null;
+ try {
+ return new NfcV(tag);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /** @hide */
+ public NfcV(Tag tag) throws RemoteException {
+ super(tag, TagTechnology.NFC_V);
+ Bundle extras = tag.getTechExtras(TagTechnology.NFC_V);
+ mRespFlags = extras.getByte(EXTRA_RESP_FLAGS);
+ mDsfId = extras.getByte(EXTRA_DSFID);
+ }
+
+ /**
+ * Return the Response Flag bytes from tag discovery.
+ *
+ * <p>Does not cause any RF activity and does not block.
+ *
+ * @return Response Flag bytes
+ */
+ public byte getResponseFlags() {
+ return mRespFlags;
+ }
+
+ /**
+ * Return the DSF ID bytes from tag discovery.
+ *
+ * <p>Does not cause any RF activity and does not block.
+ *
+ * @return DSF ID bytes
+ */
+ public byte getDsfId() {
+ return mDsfId;
+ }
+
+ /**
+ * Send raw NFC-V commands to the tag and receive the response.
+ *
+ * <p>Applications must not append the CRC to the payload,
+ * it will be automatically calculated. The application does
+ * provide FLAGS, CMD and PARAMETER bytes.
+ *
+ * <p>Use {@link #getMaxTransceiveLength} to retrieve the maximum amount of bytes
+ * that can be sent with {@link #transceive}.
+ *
+ * <p>This is an I/O operation and will block until complete. It must
+ * not be called from the main application thread. A blocked call will be canceled with
+ * {@link IOException} if {@link #close} is called from another thread.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param data bytes to send
+ * @return bytes received in response
+ * @throws TagLostException if the tag leaves the field
+ * @throws IOException if there is an I/O failure, or this operation is canceled
+ */
+ public byte[] transceive(byte[] data) throws IOException {
+ return transceive(data, true);
+ }
+
+
+ /**
+ * Return the maximum number of bytes that can be sent with {@link #transceive}.
+ * @return the maximum number of bytes that can be sent with {@link #transceive}.
+ */
+ public int getMaxTransceiveLength() {
+ return getMaxTransceiveLengthInternal();
+ }
+}
diff --git a/nfc/java/android/nfc/tech/OWNERS b/nfc/java/android/nfc/tech/OWNERS
new file mode 100644
index 0000000..35e9713
--- /dev/null
+++ b/nfc/java/android/nfc/tech/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 48448
+include platform/packages/apps/Nfc:/OWNERS
diff --git a/nfc/java/android/nfc/tech/TagTechnology.java b/nfc/java/android/nfc/tech/TagTechnology.java
new file mode 100644
index 0000000..839fe42
--- /dev/null
+++ b/nfc/java/android/nfc/tech/TagTechnology.java
@@ -0,0 +1,224 @@
+/*
+ * 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.tech;
+
+import android.nfc.Tag;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+/**
+ * {@link TagTechnology} is an interface to a technology in a {@link Tag}.
+ * <p>
+ * Obtain a {@link TagTechnology} implementation by calling the static method <code>get()</code>
+ * on the implementation class.
+ * <p>
+ * NFC tags are based on a number of independently developed technologies and offer a
+ * wide range of capabilities. The
+ * {@link TagTechnology} implementations provide access to these different
+ * technologies and capabilities. Some sub-classes map to technology
+ * specification (for example {@link NfcA}, {@link IsoDep}, others map to
+ * pseudo-technologies or capabilities (for example {@link Ndef}, {@link NdefFormatable}).
+ * <p>
+ * It is mandatory for all Android NFC devices to provide the following
+ * {@link TagTechnology} implementations.
+ * <ul>
+ * <li>{@link NfcA} (also known as ISO 14443-3A)
+ * <li>{@link NfcB} (also known as ISO 14443-3B)
+ * <li>{@link NfcF} (also known as JIS 6319-4)
+ * <li>{@link NfcV} (also known as ISO 15693)
+ * <li>{@link IsoDep}
+ * <li>{@link Ndef} on NFC Forum Type 1, Type 2, Type 3 or Type 4 compliant tags
+ * </ul>
+ * It is optional for Android NFC devices to provide the following
+ * {@link TagTechnology} implementations. If it is not provided, the
+ * Android device will never enumerate that class via {@link Tag#getTechList}.
+ * <ul>
+ * <li>{@link MifareClassic}
+ * <li>{@link MifareUltralight}
+ * <li>{@link NfcBarcode}
+ * <li>{@link NdefFormatable} must only be enumerated on tags for which this Android device
+ * is capable of formatting. Proprietary knowledge is often required to format a tag
+ * to make it NDEF compatible.
+ * </ul>
+ * <p>
+ * {@link TagTechnology} implementations provide methods that fall into two classes:
+ * <em>cached getters</em> and <em>I/O operations</em>.
+ * <h4>Cached getters</h4>
+ * These methods (usually prefixed by <code>get</code> or <code>is</code>) return
+ * properties of the tag, as determined at discovery time. These methods will never
+ * block or cause RF activity, and do not require {@link #connect} to have been called.
+ * They also never update, for example if a property is changed by an I/O operation with a tag
+ * then the cached getter will still return the result from tag discovery time.
+ * <h4>I/O operations</h4>
+ * I/O operations may require RF activity, and may block. They have the following semantics.
+ * <ul>
+ * <li>{@link #connect} must be called before using any other I/O operation.
+ * <li>{@link #close} must be called after completing I/O operations with a
+ * {@link TagTechnology}, and it will cancel all other blocked I/O operations on other threads
+ * (including {@link #connect} with {@link IOException}.
+ * <li>Only one {@link TagTechnology} can be connected at a time. Other calls to
+ * {@link #connect} will return {@link IOException}.
+ * <li>I/O operations may block, and should never be called on the main application
+ * thread.
+ * </ul>
+ *
+ * <p class="note"><strong>Note:</strong> Methods that perform I/O operations
+ * require the {@link android.Manifest.permission#NFC} permission.
+ */
+public interface TagTechnology extends Closeable {
+ /**
+ * This technology is an instance of {@link NfcA}.
+ * <p>Support for this technology type is mandatory.
+ * @hide
+ */
+ public static final int NFC_A = 1;
+
+ /**
+ * This technology is an instance of {@link NfcB}.
+ * <p>Support for this technology type is mandatory.
+ * @hide
+ */
+ public static final int NFC_B = 2;
+
+ /**
+ * This technology is an instance of {@link IsoDep}.
+ * <p>Support for this technology type is mandatory.
+ * @hide
+ */
+ public static final int ISO_DEP = 3;
+
+ /**
+ * This technology is an instance of {@link NfcF}.
+ * <p>Support for this technology type is mandatory.
+ * @hide
+ */
+ public static final int NFC_F = 4;
+
+ /**
+ * This technology is an instance of {@link NfcV}.
+ * <p>Support for this technology type is mandatory.
+ * @hide
+ */
+ public static final int NFC_V = 5;
+
+ /**
+ * This technology is an instance of {@link Ndef}.
+ * <p>Support for this technology type is mandatory.
+ * @hide
+ */
+ public static final int NDEF = 6;
+
+ /**
+ * This technology is an instance of {@link NdefFormatable}.
+ * <p>Support for this technology type is mandatory.
+ * @hide
+ */
+ public static final int NDEF_FORMATABLE = 7;
+
+ /**
+ * This technology is an instance of {@link MifareClassic}.
+ * <p>Support for this technology type is optional. If a stack doesn't support this technology
+ * type tags using it must still be discovered and present the lower level radio interface
+ * technologies in use.
+ * @hide
+ */
+ public static final int MIFARE_CLASSIC = 8;
+
+ /**
+ * This technology is an instance of {@link MifareUltralight}.
+ * <p>Support for this technology type is optional. If a stack doesn't support this technology
+ * type tags using it must still be discovered and present the lower level radio interface
+ * technologies in use.
+ * @hide
+ */
+ public static final int MIFARE_ULTRALIGHT = 9;
+
+ /**
+ * This technology is an instance of {@link NfcBarcode}.
+ * <p>Support for this technology type is optional. If a stack doesn't support this technology
+ * type tags using it must still be discovered and present the lower level radio interface
+ * technologies in use.
+ * @hide
+ */
+ public static final int NFC_BARCODE = 10;
+
+ /**
+ * Get the {@link Tag} object backing this {@link TagTechnology} object.
+ * @return the {@link Tag} backing this {@link TagTechnology} object.
+ */
+ public Tag getTag();
+
+ /**
+ * Enable I/O operations to the tag from this {@link TagTechnology} object.
+ * <p>May cause RF activity and may block. Must not be called
+ * from the main application thread. A blocked call will be canceled with
+ * {@link IOException} by calling {@link #close} from another thread.
+ * <p>Only one {@link TagTechnology} object can be connected to a {@link Tag} at a time.
+ * <p>Applications must call {@link #close} when I/O operations are complete.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @see #close()
+ * @throws TagLostException if the tag leaves the field
+ * @throws IOException if there is an I/O failure, or connect is canceled
+ * @throws SecurityException if the tag object is reused after the tag has left the field
+ */
+ public void connect() throws IOException;
+
+ /**
+ * Re-connect to the {@link Tag} associated with this connection. Reconnecting to a tag can be
+ * used to reset the state of the tag itself.
+ *
+ * <p>May cause RF activity and may block. Must not be called
+ * from the main application thread. A blocked call will be canceled with
+ * {@link IOException} by calling {@link #close} from another thread.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @see #connect()
+ * @see #close()
+ * @throws TagLostException if the tag leaves the field
+ * @throws IOException if there is an I/O failure, or connect is canceled
+ * @throws SecurityException if the tag object is reused after the tag has left the field
+ * @hide
+ */
+ public void reconnect() throws IOException;
+
+ /**
+ * Disable I/O operations to the tag from this {@link TagTechnology} object, and release resources.
+ * <p>Also causes all blocked I/O operations on other thread to be canceled and
+ * return with {@link IOException}.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @see #connect()
+ * @throws SecurityException if the tag object is reused after the tag has left the field
+ */
+ public void close() throws IOException;
+
+ /**
+ * Helper to indicate if I/O operations should be possible.
+ *
+ * <p>Returns true if {@link #connect} has completed, and {@link #close} has not been
+ * called, and the {@link Tag} is not known to be out of range.
+ * <p>Does not cause RF activity, and does not block.
+ *
+ * @return true if I/O operations should be possible
+ */
+ public boolean isConnected();
+}
diff --git a/nfc/java/android/nfc/tech/package.html b/nfc/java/android/nfc/tech/package.html
new file mode 100644
index 0000000..a99828f
--- /dev/null
+++ b/nfc/java/android/nfc/tech/package.html
@@ -0,0 +1,13 @@
+<HTML>
+<BODY>
+<p>
+These classes provide access to a tag technology's features, which vary by the type
+of tag that is scanned. A scanned tag can support multiple technologies, and you can find
+out what they are by calling {@link android.nfc.Tag#getTechList getTechList()}.</p>
+
+<p>For more information on dealing with tag technologies and handling the ones that you care about, see
+<a href="{@docRoot}guide/topics/nfc/index.html#dispatch">The Tag Dispatch System</a>.
+The {@link android.nfc.tech.TagTechnology} interface provides an overview of the
+supported technologies.</p>
+</BODY>
+</HTML>
diff --git a/nfc/tests/Android.bp b/nfc/tests/Android.bp
new file mode 100644
index 0000000..62566ee
--- /dev/null
+++ b/nfc/tests/Android.bp
@@ -0,0 +1,40 @@
+// Copyright 2021 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "NfcManagerTests",
+ static_libs: [
+ "androidx.test.ext.junit",
+ "androidx.test.rules",
+ "mockito-target-minus-junit4",
+ "truth",
+ ],
+ libs: [
+ "framework-nfc.impl",
+ "android.test.runner",
+ ],
+ srcs: ["src/**/*.java"],
+ platform_apis: true,
+ certificate: "platform",
+ test_suites: ["device-tests"],
+}
diff --git a/nfc/tests/AndroidManifest.xml b/nfc/tests/AndroidManifest.xml
new file mode 100644
index 0000000..99e2c34c
--- /dev/null
+++ b/nfc/tests/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2021 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.nfc">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <!-- This is a self-instrumenting test package. -->
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.nfc"
+ android:label="NFC Manager Tests">
+ </instrumentation>
+
+</manifest>
+
diff --git a/nfc/tests/AndroidTest.xml b/nfc/tests/AndroidTest.xml
new file mode 100644
index 0000000..490d6f5
--- /dev/null
+++ b/nfc/tests/AndroidTest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2021 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.
+-->
+<configuration description="Config for NFC Manager test cases">
+ <option name="test-suite-tag" value="apct"/>
+ <option name="test-suite-tag" value="apct-instrumentation"/>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="NfcManagerTests.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct"/>
+ <option name="test-tag" value="NfcManagerTests"/>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.nfc" />
+ <option name="hidden-api-checks" value="false"/>
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner"/>
+ </test>
+</configuration>
diff --git a/nfc/tests/src/android/nfc/NfcControllerAlwaysOnListenerTest.java b/nfc/tests/src/android/nfc/NfcControllerAlwaysOnListenerTest.java
new file mode 100644
index 0000000..48f4288
--- /dev/null
+++ b/nfc/tests/src/android/nfc/NfcControllerAlwaysOnListenerTest.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2021 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 static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.nfc.NfcAdapter.ControllerAlwaysOnListener;
+import android.os.RemoteException;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Test of {@link NfcControllerAlwaysOnListener}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class NfcControllerAlwaysOnListenerTest {
+
+ private INfcAdapter mNfcAdapter = mock(INfcAdapter.class);
+
+ private Throwable mThrowRemoteException = new RemoteException("RemoteException");
+
+ private static Executor getExecutor() {
+ return new Executor() {
+ @Override
+ public void execute(Runnable command) {
+ command.run();
+ }
+ };
+ }
+
+ private static void verifyListenerInvoked(ControllerAlwaysOnListener listener) {
+ verify(listener, times(1)).onControllerAlwaysOnChanged(anyBoolean());
+ }
+
+ @Test
+ public void testRegister_RegisterUnregisterWhenNotSupported() throws RemoteException {
+ // isControllerAlwaysOnSupported() returns false, not supported.
+ doReturn(false).when(mNfcAdapter).isControllerAlwaysOnSupported();
+ NfcControllerAlwaysOnListener mListener =
+ new NfcControllerAlwaysOnListener(mNfcAdapter);
+ ControllerAlwaysOnListener mockListener1 = mock(ControllerAlwaysOnListener.class);
+ ControllerAlwaysOnListener mockListener2 = mock(ControllerAlwaysOnListener.class);
+
+ // Verify that the state listener will not registered with the NFC Adapter
+ mListener.register(getExecutor(), mockListener1);
+ verify(mNfcAdapter, times(0)).registerControllerAlwaysOnListener(any());
+
+ // Register a second client and no any call to NFC Adapter
+ mListener.register(getExecutor(), mockListener2);
+ verify(mNfcAdapter, times(0)).registerControllerAlwaysOnListener(any());
+
+ // Unregister first listener, and no any call to NFC Adapter
+ mListener.unregister(mockListener1);
+ verify(mNfcAdapter, times(0)).registerControllerAlwaysOnListener(any());
+ verify(mNfcAdapter, times(0)).unregisterControllerAlwaysOnListener(any());
+
+ // Unregister second listener, and no any call to NFC Adapter
+ mListener.unregister(mockListener2);
+ verify(mNfcAdapter, times(0)).registerControllerAlwaysOnListener(any());
+ verify(mNfcAdapter, times(0)).unregisterControllerAlwaysOnListener(any());
+ }
+
+ @Test
+ public void testRegister_RegisterUnregister() throws RemoteException {
+ doReturn(true).when(mNfcAdapter).isControllerAlwaysOnSupported();
+ NfcControllerAlwaysOnListener mListener =
+ new NfcControllerAlwaysOnListener(mNfcAdapter);
+ ControllerAlwaysOnListener mockListener1 = mock(ControllerAlwaysOnListener.class);
+ ControllerAlwaysOnListener mockListener2 = mock(ControllerAlwaysOnListener.class);
+
+ // Verify that the state listener registered with the NFC Adapter
+ mListener.register(getExecutor(), mockListener1);
+ verify(mNfcAdapter, times(1)).registerControllerAlwaysOnListener(any());
+
+ // Register a second client and no new call to NFC Adapter
+ mListener.register(getExecutor(), mockListener2);
+ verify(mNfcAdapter, times(1)).registerControllerAlwaysOnListener(any());
+
+ // Unregister first listener
+ mListener.unregister(mockListener1);
+ verify(mNfcAdapter, times(1)).registerControllerAlwaysOnListener(any());
+ verify(mNfcAdapter, times(0)).unregisterControllerAlwaysOnListener(any());
+
+ // Unregister second listener and the state listener registered with the NFC Adapter
+ mListener.unregister(mockListener2);
+ verify(mNfcAdapter, times(1)).registerControllerAlwaysOnListener(any());
+ verify(mNfcAdapter, times(1)).unregisterControllerAlwaysOnListener(any());
+ }
+
+ @Test
+ public void testRegister_FirstRegisterFails() throws RemoteException {
+ doReturn(true).when(mNfcAdapter).isControllerAlwaysOnSupported();
+ NfcControllerAlwaysOnListener mListener =
+ new NfcControllerAlwaysOnListener(mNfcAdapter);
+ ControllerAlwaysOnListener mockListener1 = mock(ControllerAlwaysOnListener.class);
+ ControllerAlwaysOnListener mockListener2 = mock(ControllerAlwaysOnListener.class);
+
+ // Throw a remote exception whenever first registering
+ doThrow(mThrowRemoteException).when(mNfcAdapter).registerControllerAlwaysOnListener(
+ any());
+
+ mListener.register(getExecutor(), mockListener1);
+ verify(mNfcAdapter, times(1)).registerControllerAlwaysOnListener(any());
+
+ // No longer throw an exception, instead succeed
+ doNothing().when(mNfcAdapter).registerControllerAlwaysOnListener(any());
+
+ // Register a different listener
+ mListener.register(getExecutor(), mockListener2);
+ verify(mNfcAdapter, times(2)).registerControllerAlwaysOnListener(any());
+
+ // Ensure first and second listener were invoked
+ mListener.onControllerAlwaysOnChanged(true);
+ verifyListenerInvoked(mockListener1);
+ verifyListenerInvoked(mockListener2);
+ }
+
+ @Test
+ public void testRegister_RegisterSameListenerTwice() throws RemoteException {
+ doReturn(true).when(mNfcAdapter).isControllerAlwaysOnSupported();
+ NfcControllerAlwaysOnListener mListener =
+ new NfcControllerAlwaysOnListener(mNfcAdapter);
+ ControllerAlwaysOnListener mockListener = mock(ControllerAlwaysOnListener.class);
+
+ // Register the same listener Twice
+ mListener.register(getExecutor(), mockListener);
+ mListener.register(getExecutor(), mockListener);
+ verify(mNfcAdapter, times(1)).registerControllerAlwaysOnListener(any());
+
+ // Invoke a state change and ensure the listener is only called once
+ mListener.onControllerAlwaysOnChanged(true);
+ verifyListenerInvoked(mockListener);
+ }
+
+ @Test
+ public void testNotify_AllListenersNotified() throws RemoteException {
+ doReturn(true).when(mNfcAdapter).isControllerAlwaysOnSupported();
+ NfcControllerAlwaysOnListener listener = new NfcControllerAlwaysOnListener(mNfcAdapter);
+ List<ControllerAlwaysOnListener> mockListeners = new ArrayList<>();
+ for (int i = 0; i < 10; i++) {
+ ControllerAlwaysOnListener mockListener = mock(ControllerAlwaysOnListener.class);
+ listener.register(getExecutor(), mockListener);
+ mockListeners.add(mockListener);
+ }
+
+ // Invoke a state change and ensure all listeners are invoked
+ listener.onControllerAlwaysOnChanged(true);
+ for (ControllerAlwaysOnListener mListener : mockListeners) {
+ verifyListenerInvoked(mListener);
+ }
+ }
+
+ @Test
+ public void testStateChange_CorrectValue() throws RemoteException {
+ doReturn(true).when(mNfcAdapter).isControllerAlwaysOnSupported();
+ runStateChangeValue(true, true);
+ runStateChangeValue(false, false);
+
+ }
+
+ private void runStateChangeValue(boolean isEnabledIn, boolean isEnabledOut) {
+ NfcControllerAlwaysOnListener listener = new NfcControllerAlwaysOnListener(mNfcAdapter);
+ ControllerAlwaysOnListener mockListener = mock(ControllerAlwaysOnListener.class);
+ listener.register(getExecutor(), mockListener);
+ listener.onControllerAlwaysOnChanged(isEnabledIn);
+ verify(mockListener, times(1)).onControllerAlwaysOnChanged(isEnabledOut);
+ verify(mockListener, times(0)).onControllerAlwaysOnChanged(!isEnabledOut);
+ }
+}
diff --git a/nfc/tests/src/android/nfc/TechListParcelTest.java b/nfc/tests/src/android/nfc/TechListParcelTest.java
new file mode 100644
index 0000000..a12bbbc
--- /dev/null
+++ b/nfc/tests/src/android/nfc/TechListParcelTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2022 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 static com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+
+@RunWith(AndroidJUnit4.class)
+public class TechListParcelTest {
+
+ private static final String[] TECH_LIST_1 = new String[] { "tech1.1", "tech1.2" };
+ private static final String[] TECH_LIST_2 = new String[] { "tech2.1" };
+ private static final String[] TECH_LIST_EMPTY = new String[] {};
+
+ @Test
+ public void testWriteParcel() {
+ TechListParcel techListParcel = new TechListParcel(TECH_LIST_1, TECH_LIST_2);
+
+ Parcel parcel = Parcel.obtain();
+ techListParcel.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ TechListParcel actualTechList =
+ TechListParcel.CREATOR.createFromParcel(parcel);
+ parcel.recycle();
+
+ assertThat(actualTechList.getTechLists().length).isEqualTo(2);
+ assertThat(Arrays.equals(actualTechList.getTechLists()[0], TECH_LIST_1)).isTrue();
+ assertThat(Arrays.equals(actualTechList.getTechLists()[1], TECH_LIST_2)).isTrue();
+ }
+
+ @Test
+ public void testWriteParcelArrayEmpty() {
+ TechListParcel techListParcel = new TechListParcel();
+
+ Parcel parcel = Parcel.obtain();
+ techListParcel.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ TechListParcel actualTechList =
+ TechListParcel.CREATOR.createFromParcel(parcel);
+ parcel.recycle();
+
+ assertThat(actualTechList.getTechLists().length).isEqualTo(0);
+ }
+
+ @Test
+ public void testWriteParcelElementEmpty() {
+ TechListParcel techListParcel = new TechListParcel(TECH_LIST_EMPTY);
+
+ Parcel parcel = Parcel.obtain();
+ techListParcel.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ TechListParcel actualTechList =
+ TechListParcel.CREATOR.createFromParcel(parcel);
+ parcel.recycle();
+
+ assertThat(actualTechList.getTechLists().length).isEqualTo(1);
+ assertThat(Arrays.equals(actualTechList.getTechLists()[0], TECH_LIST_EMPTY)).isTrue();
+ }
+
+}