/* * 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 CREATOR = new Parcelable.Creator() { @Override public OemLogItems createFromParcel(Parcel in) { return new OemLogItems(in); } @Override public OemLogItems[] newArray(int size) { return new OemLogItems[size]; } }; }