• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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