Introduce dedicated PollingFrame class to replace the use of Bundles per
API Council feedback
Bug: b/315131060
Test: Tested with CTS
Change-Id: I9f69dba67d3f4d204da05148f20cbfefc2a8c89a
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index f111327..0383276 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -228,14 +228,10 @@
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 @FlaggedApi("android.nfc.nfc_read_polling_loop") public void processPollingFrames(@NonNull java.util.List<android.nfc.cardemulation.PollingFrame>);
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 KEY_POLLING_LOOP_DATA = "android.nfc.cardemulation.DATA";
- field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String KEY_POLLING_LOOP_GAIN = "android.nfc.cardemulation.GAIN";
- field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String KEY_POLLING_LOOP_TIMESTAMP = "android.nfc.cardemulation.TIMESTAMP";
- field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String KEY_POLLING_LOOP_TYPE = "android.nfc.cardemulation.TYPE";
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'
@@ -274,6 +270,17 @@
field public static final String SERVICE_META_DATA = "android.nfc.cardemulation.off_host_apdu_service";
}
+ @FlaggedApi("android.nfc.nfc_read_polling_loop") public final class PollingFrame implements android.os.Parcelable {
+ ctor public PollingFrame(char, @Nullable byte[], int, int);
+ method public int describeContents();
+ method @NonNull public byte[] getData();
+ method public int getGain();
+ method public int getTimestamp();
+ method public char getType();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.nfc.cardemulation.PollingFrame> CREATOR;
+ }
+
}
package android.nfc.tech {
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index c5b7582..313e6d2 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -38,6 +38,8 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
+import android.nfc.cardemulation.HostApduService;
+import android.nfc.cardemulation.PollingFrame;
import android.nfc.tech.MifareClassic;
import android.nfc.tech.Ndef;
import android.nfc.tech.NfcA;
@@ -2799,7 +2801,12 @@
*/
@TestApi
@FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
- public void notifyPollingLoop(@NonNull Bundle frame) {
+ public void notifyPollingLoop(@NonNull PollingFrame pollingFrame) {
+ Bundle frame = new Bundle();
+ frame.putChar(HostApduService.KEY_POLLING_LOOP_TYPE, pollingFrame.getType());
+ frame.putByte(HostApduService.KEY_POLLING_LOOP_GAIN, (byte) pollingFrame.getGain());
+ frame.putByteArray(HostApduService.KEY_POLLING_LOOP_DATA, pollingFrame.getData());
+ frame.putInt(HostApduService.KEY_POLLING_LOOP_TIMESTAMP, pollingFrame.getTimestamp());
try {
if (sService == null) {
attemptDeadServiceRecovery(null);
diff --git a/nfc/java/android/nfc/cardemulation/HostApduService.java b/nfc/java/android/nfc/cardemulation/HostApduService.java
index 363788e..a71764d 100644
--- a/nfc/java/android/nfc/cardemulation/HostApduService.java
+++ b/nfc/java/android/nfc/cardemulation/HostApduService.java
@@ -245,7 +245,9 @@
/**
* KEY_POLLING_LOOP_TYPE is the Bundle key for the type of
- * polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
+ * polling loop frame in the Bundle included in MSG_POLLING_LOOP.
+ *
+ * @hide
*/
@FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
public static final String KEY_POLLING_LOOP_TYPE = "android.nfc.cardemulation.TYPE";
@@ -300,24 +302,27 @@
/**
* KEY_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.
+ * the polling loop frame in the Bundle included in MSG_POLLING_LOOP.
+ *
+ * @hide
*/
@FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
public static final String KEY_POLLING_LOOP_DATA = "android.nfc.cardemulation.DATA";
/**
* KEY_POLLING_LOOP_GAIN 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.
+ * the polling loop frame in the Bundle included in MSG_POLLING_LOOP.
+ *
+ * @hide
*/
@FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
public static final String KEY_POLLING_LOOP_GAIN = "android.nfc.cardemulation.GAIN";
/**
* KEY_POLLING_LOOP_TIMESTAMP 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.
+ * the polling loop frame in the Bundle included in MSG_POLLING_LOOP.
+ *
+ * @hide
*/
@FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
public static final String KEY_POLLING_LOOP_TIMESTAMP = "android.nfc.cardemulation.TIMESTAMP";
@@ -407,7 +412,12 @@
ArrayList<Bundle> frames =
msg.getData().getParcelableArrayList(KEY_POLLING_LOOP_FRAMES_BUNDLE,
Bundle.class);
- processPollingFrames(frames);
+ ArrayList<PollingFrame> pollingFrames =
+ new ArrayList<PollingFrame>(frames.size());
+ for (Bundle frame : frames) {
+ pollingFrames.add(new PollingFrame(frame));
+ }
+ processPollingFrames(pollingFrames);
break;
default:
super.handleMessage(msg);
@@ -482,7 +492,7 @@
* @param frame A description of the polling frame.
*/
@FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
- public void processPollingFrames(@NonNull List<Bundle> frame) {
+ public void processPollingFrames(@NonNull List<PollingFrame> frame) {
}
/**
diff --git a/nfc/java/android/nfc/cardemulation/PollingFrame.java b/nfc/java/android/nfc/cardemulation/PollingFrame.java
new file mode 100644
index 0000000..2e7c77c
--- /dev/null
+++ b/nfc/java/android/nfc/cardemulation/PollingFrame.java
@@ -0,0 +1,143 @@
+/*
+ * 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.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Polling Frames represent data about individual frames of an NFC polling loop. These frames will
+ * be deliverd to subclasses of {@link HostApduService} that have registered filters with
+ * {@link CardEmulation#registerPollingLoopFilterForService(ComponentName, String)} that match a
+ * given frame in a loop and will be delivered through calls to
+ * {@link HostApduService#processPollingFrames(List)}.
+ */
+@FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+public final class PollingFrame implements Parcelable{
+ private char mType;
+ private byte[] mData;
+ private int mGain;
+ private int mTimestamp;
+
+ public static final @NonNull Parcelable.Creator<PollingFrame> CREATOR =
+ new Parcelable.Creator<PollingFrame>() {
+
+ @Override
+ public PollingFrame createFromParcel(Parcel source) {
+ return new PollingFrame(source.readBundle());
+ }
+
+ @Override
+ public PollingFrame[] newArray(int size) {
+ return new PollingFrame[0];
+ }
+ };
+
+ PollingFrame(Bundle frame) {
+ mType = frame.getChar(HostApduService.KEY_POLLING_LOOP_TYPE);
+ mData = frame.getByteArray(HostApduService.KEY_POLLING_LOOP_DATA);
+ if (mData == null) {
+ mData = new byte[0];
+ }
+ mGain = frame.getByte(HostApduService.KEY_POLLING_LOOP_GAIN);
+ mTimestamp = frame.getInt(HostApduService.KEY_POLLING_LOOP_TIMESTAMP);
+ }
+
+ public PollingFrame(char type, @Nullable byte[] data, int gain, int timestamp) {
+ mType = type;
+ mData = data == null ? new byte[0] : data;
+ mGain = gain;
+ mTimestamp = timestamp;
+ }
+
+ private PollingFrame(Parcel source) {
+ mType = (char) source.readInt();
+ source.readByteArray(mData);
+ mGain = source.readInt();
+ mTimestamp = source.readInt();
+ }
+
+ /**
+ * Returns the type of frame for this polling loop frame.
+ *
+ * The possible return values are:
+ * <ul>
+ * <li>{@link HostApduService#POLLING_LOOP_TYPE_ON}</li>
+ * <li>{@link HostApduService#POLLING_LOOP_TYPE_OFF}</li>
+ * <li>{@link HostApduService#POLLING_LOOP_TYPE_A}</li>
+ * <li>{@link HostApduService#POLLING_LOOP_TYPE_B}</li>
+ * <li>{@link HostApduService#POLLING_LOOP_TYPE_F}</li>
+ * </ul>
+ */
+ public char getType() {
+ return mType;
+ }
+
+ /**
+ * Returns the raw data from the polling type frame.
+ */
+ public @NonNull byte[] getData() {
+ return mData;
+ }
+
+ /**
+ * Returns the gain representing the field strength of the NFC field when this polling loop
+ * frame was observed.
+ */
+ public int getGain() {
+ return mGain;
+ }
+
+ /**
+ * Returns the timestamp of when the polling loop frame was observed in milliseconds. These
+ * timestamps are relative and not absolute and should only be used fro comparing the timing of
+ * frames relative to each other.
+ * @return the timestamp in milliseconds
+ */
+ public int getTimestamp() {
+ return mTimestamp;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeBundle(toBundle());
+ }
+
+ /**
+ *
+ * @hide
+ * @return a Bundle representing this frame
+ */
+ public Bundle toBundle() {
+ Bundle frame = new Bundle();
+ frame.putInt(HostApduService.KEY_POLLING_LOOP_TYPE, getType());
+ frame.putByte(HostApduService.KEY_POLLING_LOOP_GAIN, (byte) getGain());
+ frame.putByteArray(HostApduService.KEY_POLLING_LOOP_DATA, getData());
+ frame.putInt(HostApduService.KEY_POLLING_LOOP_TIMESTAMP, getTimestamp());
+ return frame;
+ }
+}