/* | |
* Copyright 2024 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.IntDef; | |
import android.annotation.NonNull; | |
import android.annotation.Nullable; | |
import android.annotation.SystemApi; | |
import android.os.Parcel; | |
import android.os.Parcelable; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
import java.time.Instant; | |
/** | |
* A log class for OEMs to get log information of NFC events. | |
* @hide | |
*/ | |
@FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) | |
@SystemApi | |
public final class OemLogItems implements Parcelable { | |
/** | |
* Used when RF field state is changed. | |
*/ | |
public static final int LOG_ACTION_RF_FIELD_STATE_CHANGED = 0X01; | |
/** | |
* Used when NFC is toggled. Event should be set to {@link LogEvent#EVENT_ENABLE} or | |
* {@link LogEvent#EVENT_DISABLE} if this action is used. | |
*/ | |
public static final int LOG_ACTION_NFC_TOGGLE = 0x0201; | |
/** | |
* Used when sending host routing status. | |
*/ | |
public static final int LOG_ACTION_HCE_DATA = 0x0204; | |
/** | |
* Used when screen state is changed. | |
*/ | |
public static final int LOG_ACTION_SCREEN_STATE_CHANGED = 0x0206; | |
/** | |
* Used when tag is detected. | |
*/ | |
public static final int LOG_ACTION_TAG_DETECTED = 0x03; | |
/** | |
* @hide | |
*/ | |
@IntDef(prefix = { "LOG_ACTION_" }, value = { | |
LOG_ACTION_RF_FIELD_STATE_CHANGED, | |
LOG_ACTION_NFC_TOGGLE, | |
LOG_ACTION_HCE_DATA, | |
LOG_ACTION_SCREEN_STATE_CHANGED, | |
LOG_ACTION_TAG_DETECTED, | |
}) | |
@Retention(RetentionPolicy.SOURCE) | |
public @interface LogAction {} | |
/** | |
* Represents the event is not set. | |
*/ | |
public static final int EVENT_UNSET = 0; | |
/** | |
* Represents nfc enable is called. | |
*/ | |
public static final int EVENT_ENABLE = 1; | |
/** | |
* Represents nfc disable is called. | |
*/ | |
public static final int EVENT_DISABLE = 2; | |
/** @hide */ | |
@IntDef(prefix = { "EVENT_" }, value = { | |
EVENT_UNSET, | |
EVENT_ENABLE, | |
EVENT_DISABLE, | |
}) | |
@Retention(RetentionPolicy.SOURCE) | |
public @interface LogEvent {} | |
private int mAction; | |
private int mEvent; | |
private int mCallingPid; | |
private byte[] mCommandApdus; | |
private byte[] mResponseApdus; | |
private Instant mRfFieldOnTime; | |
private Tag mTag; | |
/** @hide */ | |
public OemLogItems(@LogAction int action, @LogEvent int event, int callingPid, | |
byte[] commandApdus, byte[] responseApdus, Instant rfFieldOnTime, | |
Tag tag) { | |
mAction = action; | |
mEvent = event; | |
mTag = tag; | |
mCallingPid = callingPid; | |
mCommandApdus = commandApdus; | |
mResponseApdus = responseApdus; | |
mRfFieldOnTime = rfFieldOnTime; | |
} | |
/** | |
* Describe the kinds of special objects contained in this Parcelable | |
* instance's marshaled representation. For example, if the object will | |
* include a file descriptor in the output of {@link #writeToParcel(Parcel, int)}, | |
* the return value of this method must include the | |
* {@link #CONTENTS_FILE_DESCRIPTOR} bit. | |
* | |
* @return a bitmask indicating the set of special object types marshaled | |
* by this Parcelable object instance. | |
*/ | |
@Override | |
public int describeContents() { | |
return 0; | |
} | |
/** | |
* Flatten this object in to a Parcel. | |
* | |
* @param dest The Parcel in which the object should be written. | |
* @param flags Additional flags about how the object should be written. | |
* May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}. | |
*/ | |
@Override | |
public void writeToParcel(@NonNull Parcel dest, int flags) { | |
dest.writeInt(mAction); | |
dest.writeInt(mEvent); | |
dest.writeInt(mCallingPid); | |
dest.writeInt(mCommandApdus.length); | |
dest.writeByteArray(mCommandApdus); | |
dest.writeInt(mResponseApdus.length); | |
dest.writeByteArray(mResponseApdus); | |
dest.writeBoolean(mRfFieldOnTime != null); | |
if (mRfFieldOnTime != null) { | |
dest.writeLong(mRfFieldOnTime.getEpochSecond()); | |
dest.writeInt(mRfFieldOnTime.getNano()); | |
} | |
dest.writeParcelable(mTag, 0); | |
} | |
/** @hide */ | |
public static class Builder { | |
private final OemLogItems mItem; | |
public Builder(@LogAction int type) { | |
mItem = new OemLogItems(type, EVENT_UNSET, 0, new byte[0], new byte[0], null, null); | |
} | |
/** Setter of the log action. */ | |
public OemLogItems.Builder setAction(@LogAction int action) { | |
mItem.mAction = action; | |
return this; | |
} | |
/** Setter of the log calling event. */ | |
public OemLogItems.Builder setCallingEvent(@LogEvent int event) { | |
mItem.mEvent = event; | |
return this; | |
} | |
/** Setter of the log calling Pid. */ | |
public OemLogItems.Builder setCallingPid(int pid) { | |
mItem.mCallingPid = pid; | |
return this; | |
} | |
/** Setter of APDU command. */ | |
public OemLogItems.Builder setApduCommand(byte[] apdus) { | |
mItem.mCommandApdus = apdus; | |
return this; | |
} | |
/** Setter of RF field on time. */ | |
public OemLogItems.Builder setRfFieldOnTime(Instant time) { | |
mItem.mRfFieldOnTime = time; | |
return this; | |
} | |
/** Setter of APDU response. */ | |
public OemLogItems.Builder setApduResponse(byte[] apdus) { | |
mItem.mResponseApdus = apdus; | |
return this; | |
} | |
/** Setter of dispatched tag. */ | |
public OemLogItems.Builder setTag(Tag tag) { | |
mItem.mTag = tag; | |
return this; | |
} | |
/** Builds an {@link OemLogItems} instance. */ | |
public OemLogItems build() { | |
return mItem; | |
} | |
} | |
/** | |
* Gets the action of this log. | |
* @return one of {@link LogAction} | |
*/ | |
@LogAction | |
public int getAction() { | |
return mAction; | |
} | |
/** | |
* Gets the event of this log. This will be set to {@link LogEvent#EVENT_ENABLE} or | |
* {@link LogEvent#EVENT_DISABLE} only when action is set to | |
* {@link LogAction#LOG_ACTION_NFC_TOGGLE} | |
* @return one of {@link LogEvent} | |
*/ | |
@LogEvent | |
public int getEvent() { | |
return mEvent; | |
} | |
/** | |
* Gets the calling Pid of this log. This field will be set only when action is set to | |
* {@link LogAction#LOG_ACTION_NFC_TOGGLE} | |
* @return calling Pid | |
*/ | |
public int getCallingPid() { | |
return mCallingPid; | |
} | |
/** | |
* Gets the command APDUs of this log. This field will be set only when action is set to | |
* {@link LogAction#LOG_ACTION_HCE_DATA} | |
* @return a byte array of command APDUs with the same format as | |
* {@link android.nfc.cardemulation.HostApduService#sendResponseApdu(byte[])} | |
*/ | |
@Nullable | |
public byte[] getCommandApdu() { | |
return mCommandApdus; | |
} | |
/** | |
* Gets the response APDUs of this log. This field will be set only when action is set to | |
* {@link LogAction#LOG_ACTION_HCE_DATA} | |
* @return a byte array of response APDUs with the same format as | |
* {@link android.nfc.cardemulation.HostApduService#sendResponseApdu(byte[])} | |
*/ | |
@Nullable | |
public byte[] getResponseApdu() { | |
return mResponseApdus; | |
} | |
/** | |
* Gets the RF field event time in this log in millisecond. This field will be set only when | |
* action is set to {@link LogAction#LOG_ACTION_RF_FIELD_STATE_CHANGED} | |
* @return an {@link Instant} of RF field event time. | |
*/ | |
@Nullable | |
public Instant getRfFieldEventTimeMillis() { | |
return mRfFieldOnTime; | |
} | |
/** | |
* Gets the tag of this log. This field will be set only when action is set to | |
* {@link LogAction#LOG_ACTION_TAG_DETECTED} | |
* @return a detected {@link Tag} in {@link #LOG_ACTION_TAG_DETECTED} case. Return | |
* null otherwise. | |
*/ | |
@Nullable | |
public Tag getTag() { | |
return mTag; | |
} | |
private String byteToHex(byte[] bytes) { | |
char[] HexArray = "0123456789ABCDEF".toCharArray(); | |
char[] hexChars = new char[bytes.length * 2]; | |
for (int j = 0; j < bytes.length; j++) { | |
int v = bytes[j] & 0xFF; | |
hexChars[j * 2] = HexArray[v >>> 4]; | |
hexChars[j * 2 + 1] = HexArray[v & 0x0F]; | |
} | |
return new String(hexChars); | |
} | |
@Override | |
public String toString() { | |
return "[mCommandApdus: " | |
+ ((mCommandApdus != null) ? byteToHex(mCommandApdus) : "null") | |
+ "[mResponseApdus: " | |
+ ((mResponseApdus != null) ? byteToHex(mResponseApdus) : "null") | |
+ ", mCallingApi= " + mEvent | |
+ ", mAction= " + mAction | |
+ ", mCallingPId = " + mCallingPid | |
+ ", mRfFieldOnTime= " + mRfFieldOnTime; | |
} | |
private OemLogItems(Parcel in) { | |
this.mAction = in.readInt(); | |
this.mEvent = in.readInt(); | |
this.mCallingPid = in.readInt(); | |
this.mCommandApdus = new byte[in.readInt()]; | |
in.readByteArray(this.mCommandApdus); | |
this.mResponseApdus = new byte[in.readInt()]; | |
in.readByteArray(this.mResponseApdus); | |
boolean isRfFieldOnTimeSet = in.readBoolean(); | |
if (isRfFieldOnTimeSet) { | |
this.mRfFieldOnTime = Instant.ofEpochSecond(in.readLong(), in.readInt()); | |
} else { | |
this.mRfFieldOnTime = null; | |
} | |
this.mTag = in.readParcelable(Tag.class.getClassLoader(), Tag.class); | |
} | |
public static final @NonNull Parcelable.Creator<OemLogItems> CREATOR = | |
new Parcelable.Creator<OemLogItems>() { | |
@Override | |
public OemLogItems createFromParcel(Parcel in) { | |
return new OemLogItems(in); | |
} | |
@Override | |
public OemLogItems[] newArray(int size) { | |
return new OemLogItems[size]; | |
} | |
}; | |
} |