286 lines
10 KiB
Java
286 lines
10 KiB
Java
/*
|
|
* 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<PollingFrame> 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:
|
|
* <ul>
|
|
* <li>{@link POLLING_LOOP_TYPE_ON}</li>
|
|
* <li>{@link POLLING_LOOP_TYPE_OFF}</li>
|
|
* <li>{@link POLLING_LOOP_TYPE_A}</li>
|
|
* <li>{@link POLLING_LOOP_TYPE_B}</li>
|
|
* <li>{@link POLLING_LOOP_TYPE_F}</li>
|
|
* </ul>
|
|
*/
|
|
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()) + "] }";
|
|
}
|
|
}
|