blob: 64652a4be01006d92233addda366602cf6933532 [file] [log] [blame]
/*
* Copyright (C) 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 com.android.nfc;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.util.AtomicFile;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.nfc.proto.NfcEventProto;
import libcore.util.HexEncoding;
import com.google.protobuf.InvalidProtocolBufferException;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayDeque;
/**
* Used to store important NFC event logs persistently for debugging purposes.
*/
public final class NfcEventLog {
private static final String TAG = "NfcEventLog";
@VisibleForTesting
public static final DateTimeFormatter FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
private final Context mContext;
private final NfcInjector mNfcInjector;
private final Handler mHander;
private final int mMaxEventNum;
private final AtomicFile mLogFile;
private final ArrayDeque<NfcEventProto.Event> mEventList;
public NfcEventLog(Context context, NfcInjector nfcInjector, Looper looper,
AtomicFile logFile) {
mContext = context;
mNfcInjector = nfcInjector;
mMaxEventNum = context.getResources().getInteger(R.integer.max_event_log_num);
mEventList = new ArrayDeque<>(0);
mHander = new Handler(looper);
mLogFile = logFile;
mHander.post(() -> readListFromLogFile());
}
private byte[] readLogFile() {
byte[] bytes;
try {
bytes = mLogFile.readFully();
} catch (IOException e) {
return null;
}
return bytes;
}
private void writeLogFile(byte[] bytes) throws IOException {
FileOutputStream out = null;
try {
out = mLogFile.startWrite();
out.write(bytes);
mLogFile.finishWrite(out);
} catch (IOException e) {
if (out != null) {
mLogFile.failWrite(out);
}
throw e;
}
}
private void readListFromLogFile() {
byte[] bytes = readLogFile();
if (bytes == null) {
Log.i(TAG, "No NFC events found in log file");
return;
}
NfcEventProto.EventList eventList;
try {
eventList = NfcEventProto.EventList.parseFrom(bytes);
} catch (InvalidProtocolBufferException e) {
Log.e(TAG, "Failed to deserialize events from log file", e);
return;
}
synchronized (mEventList) {
for (NfcEventProto.Event event : eventList.getEventsList()) {
mEventList.add(event);
}
}
}
private void writeListToLogFile() {
NfcEventProto.EventList.Builder eventListBuilder =
NfcEventProto.EventList.newBuilder();
synchronized (mEventList) {
for (NfcEventProto.Event event: mEventList) {
eventListBuilder.addEvents(event);
}
}
byte[] bytes = eventListBuilder.build().toByteArray();
try {
writeLogFile(bytes);
} catch (IOException e) {
Log.e(TAG, "Failed to write to log file", e);
}
}
private void addAndWriteListToLogFile(NfcEventProto.Event event) {
synchronized (mEventList) {
// Trim the list to MAX_EVENTS.
if (mEventList.size() == mMaxEventNum) {
mEventList.remove();
}
mEventList.add(event);
writeListToLogFile();
}
}
/**
* Log NFC event
* Does not block the main NFC thread for logging, posts it to the logging thraead.
*/
public void logEvent(NfcEventProto.EventType eventType) {
mHander.post(() -> {
NfcEventProto.Event event = NfcEventProto.Event.newBuilder()
.setTimestamp(mNfcInjector.getLocalDateTime().format(FORMATTER))
.setEventType(eventType)
.build();
addAndWriteListToLogFile(event);
});
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("===== Nfc Event Log =====");
synchronized (mEventList) {
for (NfcEventProto.Event event: mEventList) {
// Cleanup the proto string output to make it more readable.
String eventTypeString = event.getEventType().toString()
.replaceAll("# com.android.nfc.proto.*", "")
.replaceAll("\\s+", " ");
pw.println(event.getTimestamp() + ": " + eventTypeString);
}
}
pw.println("===== Nfc Event Log =====");
}
@VisibleForTesting
public ArrayDeque<NfcEventProto.Event> getEventsList() {
return mEventList;
}
}