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 libcore.util.HexEncoding; 29 30 import com.google.protobuf.InvalidProtocolBufferException; 31 32 import java.io.File; 33 import java.io.FileDescriptor; 34 import java.io.FileOutputStream; 35 import java.io.IOException; 36 import java.io.PrintWriter; 37 import java.time.LocalDateTime; 38 import java.time.format.DateTimeFormatter; 39 import java.util.ArrayDeque; 40 41 /** 42 * Used to store important NFC event logs persistently for debugging purposes. 43 */ 44 public final class NfcEventLog { 45 private static final String TAG = "NfcEventLog"; 46 @VisibleForTesting 47 public static final DateTimeFormatter FORMATTER = 48 DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"); 49 private final Context mContext; 50 private final NfcInjector mNfcInjector; 51 private final Handler mHander; 52 private final int mMaxEventNum; 53 private final AtomicFile mLogFile; 54 private final ArrayDeque<NfcEventProto.Event> mEventList; 55 NfcEventLog(Context context, NfcInjector nfcInjector, Looper looper, AtomicFile logFile)56 public NfcEventLog(Context context, NfcInjector nfcInjector, Looper looper, 57 AtomicFile logFile) { 58 mContext = context; 59 mNfcInjector = nfcInjector; 60 mMaxEventNum = context.getResources().getInteger(R.integer.max_event_log_num); 61 mEventList = new ArrayDeque<>(0); 62 mHander = new Handler(looper); 63 mLogFile = logFile; 64 mHander.post(() -> readListFromLogFile()); 65 } 66 readLogFile()67 private byte[] readLogFile() { 68 byte[] bytes; 69 try { 70 bytes = mLogFile.readFully(); 71 } catch (IOException e) { 72 return null; 73 } 74 return bytes; 75 } 76 writeLogFile(byte[] bytes)77 private void writeLogFile(byte[] bytes) throws IOException { 78 FileOutputStream out = null; 79 try { 80 out = mLogFile.startWrite(); 81 out.write(bytes); 82 mLogFile.finishWrite(out); 83 } catch (IOException e) { 84 if (out != null) { 85 mLogFile.failWrite(out); 86 } 87 throw e; 88 } 89 } 90 readListFromLogFile()91 private void readListFromLogFile() { 92 byte[] bytes = readLogFile(); 93 if (bytes == null) { 94 Log.i(TAG, "No NFC events found in log file"); 95 return; 96 } 97 NfcEventProto.EventList eventList; 98 try { 99 eventList = NfcEventProto.EventList.parseFrom(bytes); 100 } catch (InvalidProtocolBufferException e) { 101 Log.e(TAG, "Failed to deserialize events from log file", e); 102 return; 103 } 104 synchronized (mEventList) { 105 for (NfcEventProto.Event event : eventList.getEventsList()) { 106 mEventList.add(event); 107 } 108 } 109 } 110 writeListToLogFile()111 private void writeListToLogFile() { 112 NfcEventProto.EventList.Builder eventListBuilder = 113 NfcEventProto.EventList.newBuilder(); 114 synchronized (mEventList) { 115 for (NfcEventProto.Event event: mEventList) { 116 eventListBuilder.addEvents(event); 117 } 118 } 119 byte[] bytes = eventListBuilder.build().toByteArray(); 120 Log.d(TAG, "writeListToLogFile: " + HexEncoding.encodeToString(bytes)); 121 try { 122 writeLogFile(bytes); 123 } catch (IOException e) { 124 Log.e(TAG, "Failed to write to log file", e); 125 } 126 } 127 addAndWriteListToLogFile(NfcEventProto.Event event)128 private void addAndWriteListToLogFile(NfcEventProto.Event event) { 129 synchronized (mEventList) { 130 // Trim the list to MAX_EVENTS. 131 if (mEventList.size() == mMaxEventNum) { 132 mEventList.remove(); 133 } 134 mEventList.add(event); 135 writeListToLogFile(); 136 } 137 } 138 139 /** 140 * Log NFC event 141 * Does not block the main NFC thread for logging, posts it to the logging thraead. 142 */ logEvent(NfcEventProto.EventType eventType)143 public void logEvent(NfcEventProto.EventType eventType) { 144 mHander.post(() -> { 145 NfcEventProto.Event event = NfcEventProto.Event.newBuilder() 146 .setTimestamp(mNfcInjector.getLocalDateTime().format(FORMATTER)) 147 .setEventType(eventType) 148 .build(); 149 addAndWriteListToLogFile(event); 150 }); 151 } 152 dump(FileDescriptor fd, PrintWriter pw, String[] args)153 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 154 pw.println("===== Nfc Event Log ====="); 155 synchronized (mEventList) { 156 for (NfcEventProto.Event event: mEventList) { 157 // Cleanup the proto string output to make it more readable. 158 String eventTypeString = event.getEventType().toString() 159 .replaceAll("# com.android.nfc.proto.*", "") 160 .replaceAll("\n", ""); 161 pw.println(event.getTimestamp() + ": " + eventTypeString); 162 } 163 } 164 pw.println("===== Nfc Event Log ====="); 165 } 166 167 @VisibleForTesting getEventsList()168 public ArrayDeque<NfcEventProto.Event> getEventsList() { 169 return mEventList; 170 } 171 } 172