1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.nfc; 18 19 import android.content.Context; 20 import android.os.Handler; 21 import android.os.Looper; 22 import android.util.AtomicFile; 23 import android.util.Log; 24 25 import com.android.internal.annotations.VisibleForTesting; 26 import com.android.nfc.proto.NfcEventProto; 27 28 import com.google.protobuf.InvalidProtocolBufferException; 29 30 import java.io.FileDescriptor; 31 import java.io.FileOutputStream; 32 import java.io.IOException; 33 import java.io.PrintWriter; 34 import java.time.format.DateTimeFormatter; 35 import java.util.ArrayDeque; 36 37 /** 38 * Used to store important NFC event logs persistently for debugging purposes. 39 */ 40 public final class NfcEventLog { 41 private static final String TAG = "NfcEventLog"; 42 @VisibleForTesting 43 public static final DateTimeFormatter FORMATTER = 44 DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"); 45 private final Context mContext; 46 private final NfcInjector mNfcInjector; 47 private final Handler mHander; 48 private final int mMaxEventNum; 49 private final AtomicFile mLogFile; 50 private final ArrayDeque<NfcEventProto.Event> mEventList; 51 NfcEventLog(Context context, NfcInjector nfcInjector, Looper looper, AtomicFile logFile)52 public NfcEventLog(Context context, NfcInjector nfcInjector, Looper looper, 53 AtomicFile logFile) { 54 mContext = context; 55 mNfcInjector = nfcInjector; 56 mMaxEventNum = context.getResources().getInteger(R.integer.max_event_log_num); 57 mEventList = new ArrayDeque<>(0); 58 mHander = new Handler(looper); 59 mLogFile = logFile; 60 mHander.post(() -> readListFromLogFile()); 61 } 62 readLogFile()63 private byte[] readLogFile() { 64 byte[] bytes; 65 try { 66 bytes = mLogFile.readFully(); 67 } catch (IOException e) { 68 return null; 69 } 70 return bytes; 71 } 72 writeLogFile(byte[] bytes)73 private void writeLogFile(byte[] bytes) throws IOException { 74 FileOutputStream out = null; 75 try { 76 out = mLogFile.startWrite(); 77 out.write(bytes); 78 mLogFile.finishWrite(out); 79 } catch (IOException e) { 80 if (out != null) { 81 mLogFile.failWrite(out); 82 } 83 throw e; 84 } 85 } 86 readListFromLogFile()87 private void readListFromLogFile() { 88 byte[] bytes = readLogFile(); 89 if (bytes == null) { 90 Log.i(TAG, "readListFromLogFile: No NFC events found in log file"); 91 return; 92 } 93 NfcEventProto.EventList eventList; 94 try { 95 eventList = NfcEventProto.EventList.parseFrom(bytes); 96 } catch (InvalidProtocolBufferException e) { 97 Log.e(TAG, "readListFromLogFile: Failed to deserialize events from log file", e); 98 return; 99 } 100 synchronized (mEventList) { 101 for (NfcEventProto.Event event : eventList.getEventsList()) { 102 mEventList.add(event); 103 } 104 } 105 } 106 writeListToLogFile()107 private void writeListToLogFile() { 108 NfcEventProto.EventList.Builder eventListBuilder = 109 NfcEventProto.EventList.newBuilder(); 110 synchronized (mEventList) { 111 for (NfcEventProto.Event event: mEventList) { 112 eventListBuilder.addEvents(event); 113 } 114 } 115 byte[] bytes = eventListBuilder.build().toByteArray(); 116 try { 117 writeLogFile(bytes); 118 } catch (IOException e) { 119 Log.e(TAG, "writeListToLogFile: e=", e); 120 } 121 } 122 addAndWriteListToLogFile(NfcEventProto.Event event)123 private void addAndWriteListToLogFile(NfcEventProto.Event event) { 124 synchronized (mEventList) { 125 // Trim the list to MAX_EVENTS. 126 if (mEventList.size() == mMaxEventNum) { 127 mEventList.remove(); 128 } 129 mEventList.add(event); 130 writeListToLogFile(); 131 } 132 } 133 134 /** 135 * Log NFC event 136 * Does not block the main NFC thread for logging, posts it to the logging thraead. 137 */ logEvent(NfcEventProto.EventType eventType)138 public void logEvent(NfcEventProto.EventType eventType) { 139 mHander.post(() -> { 140 NfcEventProto.Event event = NfcEventProto.Event.newBuilder() 141 .setTimestamp(mNfcInjector.getLocalDateTime().format(FORMATTER)) 142 .setEventType(eventType) 143 .build(); 144 addAndWriteListToLogFile(event); 145 }); 146 } 147 dump(FileDescriptor fd, PrintWriter pw, String[] args)148 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 149 pw.println("===== Nfc Event Log ====="); 150 synchronized (mEventList) { 151 for (NfcEventProto.Event event: mEventList) { 152 // Cleanup the proto string output to make it more readable. 153 String eventTypeString = event.getEventType().toString() 154 .replaceAll("# com.android.nfc.proto.*", "") 155 .replaceAll("\\s+", " "); 156 pw.println(event.getTimestamp() + ": " + eventTypeString); 157 } 158 } 159 pw.println("===== Nfc Event Log ====="); 160 } 161 162 @VisibleForTesting getEventsList()163 public ArrayDeque<NfcEventProto.Event> getEventsList() { 164 return mEventList; 165 } 166 } 167