/* * 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.DurationMillisLong; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.HexFormat; import java.util.List; /** * 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{ /** * @hide */ @IntDef(prefix = { "POLLING_LOOP_TYPE_"}, value = { POLLING_LOOP_TYPE_A, POLLING_LOOP_TYPE_B, POLLING_LOOP_TYPE_F, POLLING_LOOP_TYPE_OFF, POLLING_LOOP_TYPE_ON, POLLING_LOOP_TYPE_UNKNOWN }) @Retention(RetentionPolicy.SOURCE) @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) public @interface PollingFrameType {} /** * POLLING_LOOP_TYPE_A is the value associated with the key * POLLING_LOOP_TYPE in the Bundle passed to {@link HostApduService#processPollingFrames(List)} * when the polling loop is for NFC-A. */ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) public static final int 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 HostApduService#processPollingFrames(List)} * when the polling loop is for NFC-B. */ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) public static final int 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 HostApduService#processPollingFrames(List)} * when the polling loop is for NFC-F. */ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) public static final int 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 HostApduService#processPollingFrames(List)} * when the polling loop turns on. */ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) public static final int 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 HostApduService#processPollingFrames(List)} * when the polling loop turns off. */ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) public static final int 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 HostApduService#processPollingFrames(List)} * when the polling loop frame isn't recognized. */ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) public static final int POLLING_LOOP_TYPE_UNKNOWN = 'U'; /** * KEY_POLLING_LOOP_TYPE is the Bundle key for the type of * polling loop frame in the Bundle included in MSG_POLLING_LOOP. */ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) private static final String KEY_POLLING_LOOP_TYPE = "android.nfc.cardemulation.TYPE"; /** * KEY_POLLING_LOOP_DATA is the Bundle key for the raw data of captured from * the polling loop frame in the Bundle included in MSG_POLLING_LOOP. */ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) private 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 included in MSG_POLLING_LOOP. */ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) private 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 included in MSG_POLLING_LOOP. */ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) private static final String KEY_POLLING_LOOP_TIMESTAMP = "android.nfc.cardemulation.TIMESTAMP"; /** * KEY_POLLING_LOOP_TIMESTAMP is the Bundle key for whether this polling frame triggered * autoTransact in the Bundle included in MSG_POLLING_LOOP. */ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) private static final String KEY_POLLING_LOOP_TRIGGERED_AUTOTRANSACT = "android.nfc.cardemulation.TRIGGERED_AUTOTRANSACT"; @PollingFrameType private final int mType; private final byte[] mData; private final int mGain; @DurationMillisLong private final long mTimestamp; private boolean mTriggeredAutoTransact; public static final @NonNull Parcelable.Creator CREATOR = new Parcelable.Creator<>() { @Override public PollingFrame createFromParcel(Parcel source) { return new PollingFrame(source.readBundle()); } @Override public PollingFrame[] newArray(int size) { return new PollingFrame[size]; } }; private PollingFrame(Bundle frame) { mType = frame.getInt(KEY_POLLING_LOOP_TYPE); byte[] data = frame.getByteArray(KEY_POLLING_LOOP_DATA); mData = (data == null) ? new byte[0] : data; mGain = frame.getInt(KEY_POLLING_LOOP_GAIN, -1); mTimestamp = frame.getLong(KEY_POLLING_LOOP_TIMESTAMP); mTriggeredAutoTransact = frame.containsKey(KEY_POLLING_LOOP_TRIGGERED_AUTOTRANSACT) && frame.getBoolean(KEY_POLLING_LOOP_TRIGGERED_AUTOTRANSACT); } /** * Constructor for Polling Frames. * * @param type the type of the frame * @param data a byte array of the data contained in the frame * @param gain the vendor-specific gain of the field * @param timestampMillis the timestamp in millisecones * @param triggeredAutoTransact whether or not this frame triggered the device to start a * transaction automatically * * @hide */ public PollingFrame(@PollingFrameType int type, @Nullable byte[] data, int gain, @DurationMillisLong long timestampMillis, boolean triggeredAutoTransact) { mType = type; mData = data == null ? new byte[0] : data; mGain = gain; mTimestamp = timestampMillis; mTriggeredAutoTransact = triggeredAutoTransact; } /** * Returns the type of frame for this polling loop frame. * The possible return values are: * */ public @PollingFrameType int 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. * @return the gain or -1 if there is no gain measurement associated with this frame. */ public int getVendorSpecificGain() { 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 for comparing the timing of * frames relative to each other. * @return the timestamp in milliseconds */ public @DurationMillisLong long getTimestamp() { return mTimestamp; } /** * @hide */ public void setTriggeredAutoTransact(boolean triggeredAutoTransact) { mTriggeredAutoTransact = triggeredAutoTransact; } /** * Returns whether this frame triggered the device to automatically disable observe mode and * allow one transaction. */ public boolean getTriggeredAutoTransact() { return mTriggeredAutoTransact; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeBundle(toBundle()); } /** * @return a Bundle representing this frame */ private Bundle toBundle() { Bundle frame = new Bundle(); frame.putInt(KEY_POLLING_LOOP_TYPE, getType()); if (getVendorSpecificGain() != -1) { frame.putInt(KEY_POLLING_LOOP_GAIN, (byte) getVendorSpecificGain()); } frame.putByteArray(KEY_POLLING_LOOP_DATA, getData()); frame.putLong(KEY_POLLING_LOOP_TIMESTAMP, getTimestamp()); frame.putBoolean(KEY_POLLING_LOOP_TRIGGERED_AUTOTRANSACT, getTriggeredAutoTransact()); return frame; } @Override public String toString() { return "PollingFrame { Type: " + (char) getType() + ", gain: " + getVendorSpecificGain() + ", timestamp: " + Long.toUnsignedString(getTimestamp()) + ", data: [" + HexFormat.ofDelimiter(" ").formatHex(getData()) + "] }"; } }