Support a ring buffer or a queue to store window manager traces

Currently the WM uses a BlockingQueue with limited capacity to store
the traces and a separate thread to continuously write the buffer
elements to disk. This implementation allows the WM trace to use its
current logging behavior or to use a ring buffer instead.

The ring buffer:
- Stores the most recent traces and discard the oldest ones when it
reaches maximum capacity.
- Write to disk only when the trace logging is stopped or during a
bugreport, instead of having a thread to continuously write to disk.

The ring buffer can be selected (or disabled)  using the command:
- adb shell cmd window tracing continuous (true/false)
Any value other than "true" (case insensitive) is considered false

After selecting ring buffer, start and stop WM trace
monitoring with 'adb shell cmd window tracing (start/stop)'

Current capacity is fixed to 512KB. Future implementations will make
this value configurable.

Test: Flash a device. Enable continuous mode and window manager
tracing. Use the device. Stop the trace and pull the logged trace.
Open the trace in Winscope and check if the last X KB of data are
there. Unit tests: atest WmTests:WindowTraceBufferTest

Change-Id: Id4108b0231ab7af504240ea35c8a030fecf8b8a9
diff --git a/services/core/java/com/android/server/wm/WindowTraceBuffer.java b/services/core/java/com/android/server/wm/WindowTraceBuffer.java
index 936ee85..2f672f2 100644
--- a/services/core/java/com/android/server/wm/WindowTraceBuffer.java
+++ b/services/core/java/com/android/server/wm/WindowTraceBuffer.java
@@ -23,12 +23,15 @@
 import android.os.Trace;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
+import java.util.ArrayDeque;
+import java.util.Arrays;
+import java.util.Queue;
 
 /**
  * Buffer used for window tracing.
@@ -36,16 +39,15 @@
 abstract class WindowTraceBuffer {
     private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
 
-    final Object mBufferSizeLock = new Object();
-    final BlockingQueue<byte[]> mBuffer;
+    final Object mBufferLock = new Object();
+    final Queue<byte[]> mBuffer = new ArrayDeque<>();
+    final File mTraceFile;
     int mBufferSize;
     private final int mBufferCapacity;
-    private final File mTraceFile;
 
     WindowTraceBuffer(int size, File traceFile) throws IOException {
         mBufferCapacity = size;
         mTraceFile = traceFile;
-        mBuffer = new LinkedBlockingQueue<>();
 
         initTraceFile();
     }
@@ -57,65 +59,45 @@
     /**
      * Inserts the specified element into this buffer.
      *
-     * This method is synchronized with {@code #take()} and {@code #clear()}
-     * for consistency.
-     *
      * @param proto the element to add
-     * @return {@code true} if the inserted item was inserted into the buffer
      * @throws IllegalStateException if the element cannot be added because it is larger
      *                               than the buffer size.
      */
-    boolean add(ProtoOutputStream proto) throws InterruptedException {
+    void add(ProtoOutputStream proto) {
         byte[] protoBytes = proto.getBytes();
         int protoLength = protoBytes.length;
         if (protoLength > mBufferCapacity) {
             throw new IllegalStateException("Trace object too large for the buffer. Buffer size:"
                     + mBufferCapacity + " Object size: " + protoLength);
         }
-        synchronized (mBufferSizeLock) {
-            boolean canAdd = canAdd(protoBytes);
+        synchronized (mBufferLock) {
+            boolean canAdd = canAdd(protoLength);
             if (canAdd) {
                 mBuffer.offer(protoBytes);
                 mBufferSize += protoLength;
             }
-            return canAdd;
+            mBufferLock.notify();
         }
     }
 
-    void writeNextBufferElementToFile() throws IOException {
-        byte[] proto;
+    /**
+     * Stops the buffer execution and flush all buffer content to the disk.
+     *
+     * @throws IOException if the buffer cannot write its contents to the {@link #mTraceFile}
+     */
+    void dump() throws IOException, InterruptedException {
         try {
-            proto = take();
-        } catch (InterruptedException e) {
-            Thread.currentThread().interrupt();
-            return;
-        }
-
-        try {
-            Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeToFile");
-            try (OutputStream os = new FileOutputStream(mTraceFile, true)) {
-                os.write(proto);
-            }
+            Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeTraceToFile");
+            writeTraceToFile();
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
         }
     }
 
-    /**
-     * Retrieves and removes the head of this queue, waiting if necessary
-     * until an element becomes available.
-     *
-     * This method is synchronized with {@code #add(ProtoOutputStream)} and {@code #clear()}
-     * for consistency.
-     *
-     * @return the head of this buffer, or {@code null} if this buffer is empty
-     */
-    private byte[] take() throws InterruptedException {
-        byte[] item = mBuffer.take();
-        synchronized (mBufferSizeLock) {
-            mBufferSize -= item.length;
-            return item;
-        }
+    @VisibleForTesting
+    boolean contains(byte[] other) {
+        return mBuffer.stream()
+                .anyMatch(p -> Arrays.equals(p, other));
     }
 
     private void initTraceFile() throws IOException {
@@ -132,25 +114,31 @@
      * Checks if the element can be added to the buffer. The element is already certain to be
      * smaller than the overall buffer size.
      *
-     * @param protoBytes byte array representation of the Proto object to add
-     * @return <tt>true<</tt> if the element can be added to the buffer or not
+     * @param protoLength byte array representation of the Proto object to add
+     * @return {@code true} if the element can be added to the buffer or not
      */
-    abstract boolean canAdd(byte[] protoBytes) throws InterruptedException;
+    abstract boolean canAdd(int protoLength);
 
     /**
      * Flush all buffer content to the disk.
      *
      * @throws IOException if the buffer cannot write its contents to the {@link #mTraceFile}
      */
-    abstract void writeToDisk() throws IOException, InterruptedException;
+    abstract void writeTraceToFile() throws IOException, InterruptedException;
 
     /**
-     * Builder for a {@code WindowTraceBuffer} which creates a {@link WindowTraceQueueBuffer}
+     * Builder for a {@code WindowTraceBuffer} which creates a {@link WindowTraceRingBuffer} for
+     * continuous mode or a {@link WindowTraceQueueBuffer} otherwise
      */
     static class Builder {
+        private boolean mContinuous;
         private File mTraceFile;
         private int mBufferCapacity;
 
+        Builder setContinuousMode(boolean continuous) {
+            mContinuous = continuous;
+            return this;
+        }
 
         Builder setTraceFile(File traceFile) {
             mTraceFile = traceFile;
@@ -175,7 +163,11 @@
                 throw new IllegalArgumentException("A valid trace file must be specified.");
             }
 
-            return new WindowTraceQueueBuffer(mBufferCapacity, mTraceFile);
+            if (mContinuous) {
+                return new WindowTraceRingBuffer(mBufferCapacity, mTraceFile);
+            } else {
+                return new WindowTraceQueueBuffer(mBufferCapacity, mTraceFile);
+            }
         }
     }
 }