CEC: Dump recent message history for better logging

Logging for CEC messages is disabled by default, so it is hard to debug
using bug report. Providing recent history of cec messages would be
helpful to see what happened via CEC line.

Bug: 33682018
Test: adb shell dumpsys hdmi_control
Change-Id: I476ce454418d6da8d65f520cc79d653bf96f3afd
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index 461a9b0..4a10f50 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -29,9 +29,12 @@
 import com.android.server.hdmi.HdmiAnnotations.IoThreadOnly;
 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
 import com.android.server.hdmi.HdmiControlService.DevicePollingCallback;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
 import libcore.util.EmptyArray;
 import sun.util.locale.LanguageTag;
 
@@ -69,6 +72,8 @@
 
     private static final int NUM_LOGICAL_ADDRESS = 16;
 
+    private static final int MAX_CEC_MESSAGE_HISTORY = 20;
+
     // Predicate for whether the given logical address is remote device's one or not.
     private final Predicate<Integer> mRemoteDeviceAddressPredicate = new Predicate<Integer>() {
         @Override
@@ -101,6 +106,10 @@
     // Stores the local CEC devices in the system. Device type is used for key.
     private final SparseArray<HdmiCecLocalDevice> mLocalDevices = new SparseArray<>();
 
+    // Stores recent CEC messages history for debugging purpose.
+    private final ArrayBlockingQueue<MessageHistoryRecord> mMessageHistory =
+            new ArrayBlockingQueue<>(MAX_CEC_MESSAGE_HISTORY);
+
     // Private constructor.  Use HdmiCecController.create().
     private HdmiCecController(HdmiControlService service) {
         mService = service;
@@ -580,6 +589,7 @@
     void sendCommand(final HdmiCecMessage cecMessage,
             final HdmiControlService.SendMessageCallback callback) {
         assertRunOnServiceThread();
+        addMessageToHistory(false /* isReceived */, cecMessage);
         runOnIoThread(new Runnable() {
             @Override
             public void run() {
@@ -597,7 +607,7 @@
 
                 final int finalError = errorCode;
                 if (finalError != SendMessageResult.SUCCESS) {
-                    Slog.w(TAG, "Failed to send " + cecMessage);
+                    Slog.w(TAG, "Failed to send " + cecMessage + " with errorCode=" + finalError);
                 }
                 if (callback != null) {
                     runOnServiceThread(new Runnable() {
@@ -619,6 +629,7 @@
         assertRunOnServiceThread();
         HdmiCecMessage command = HdmiCecMessageBuilder.of(srcAddress, dstAddress, body);
         HdmiLogger.debug("[R]:" + command);
+        addMessageToHistory(true /* isReceived */, command);
         onReceiveCommand(command);
     }
 
@@ -632,6 +643,16 @@
         mService.onHotplug(port, connected);
     }
 
+    @ServiceThreadOnly
+    private void addMessageToHistory(boolean isReceived, HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        MessageHistoryRecord record = new MessageHistoryRecord(isReceived, message);
+        if (!mMessageHistory.offer(record)) {
+            mMessageHistory.poll();
+            mMessageHistory.offer(record);
+        }
+    }
+
     void dump(final IndentingPrintWriter pw) {
         for (int i = 0; i < mLocalDevices.size(); ++i) {
             pw.println("HdmiCecLocalDevice #" + i + ":");
@@ -639,6 +660,13 @@
             mLocalDevices.valueAt(i).dump(pw);
             pw.decreaseIndent();
         }
+        pw.println("CEC message history:");
+        pw.increaseIndent();
+        final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        for (MessageHistoryRecord record : mMessageHistory) {
+            record.dump(pw, sdf);
+        }
+        pw.decreaseIndent();
     }
 
     private static native long nativeInit(HdmiCecController handler, MessageQueue messageQueue);
@@ -654,4 +682,24 @@
     private static native void nativeSetLanguage(long controllerPtr, String language);
     private static native void nativeEnableAudioReturnChannel(long controllerPtr, int port, boolean flag);
     private static native boolean nativeIsConnected(long controllerPtr, int port);
+
+    private final class MessageHistoryRecord {
+        private final long mTime;
+        private final boolean mIsReceived; // true if received message and false if sent message
+        private final HdmiCecMessage mMessage;
+
+        public MessageHistoryRecord(boolean isReceived, HdmiCecMessage message) {
+            mTime = System.currentTimeMillis();
+            mIsReceived = isReceived;
+            mMessage = message;
+        }
+
+        void dump(final IndentingPrintWriter pw, SimpleDateFormat sdf) {
+            pw.print(mIsReceived ? "[R]" : "[S]");
+            pw.print(" time=");
+            pw.print(sdf.format(new Date(mTime)));
+            pw.print(" message=");
+            pw.println(mMessage);
+        }
+    }
 }