content: /*
   * Copyright (C) 2019 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.android.server.wm;
  
  import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER;
  import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_H;
  import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_L;
  
  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.ArrayDeque;
  import java.util.Arrays;
  import java.util.Queue;
  
  /**
   * Buffer used for window tracing.
   */
  class WindowTraceBuffer {
      private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
  
      private final Object mBufferLock = new Object();
  
      private final Queue<ProtoOutputStream> mBuffer = new ArrayDeque<>();
      private int mBufferUsedSize;
      private int mBufferCapacity;
  
      WindowTraceBuffer(int bufferCapacity) {
          mBufferCapacity = bufferCapacity;
          resetBuffer();
      }
  
      int getAvailableSpace() {
          return mBufferCapacity - mBufferUsedSize;
      }
  
      int size() {
          return mBuffer.size();
      }
  
      void setCapacity(int capacity) {
          mBufferCapacity = capacity;
      }
  
      /**
       * Inserts the specified element into this buffer.
       *
       * @param proto the element to add
       * @throws IllegalStateException if the element cannot be added because it is larger
       *                               than the buffer size.
       */
      void add(ProtoOutputStream proto) {
          int protoLength = proto.getRawSize();
          if (protoLength > mBufferCapacity) {
              throw new IllegalStateException("Trace object too large for the buffer. Buffer size:"
                      + mBufferCapacity + " Object size: " + protoLength);
          }
          synchronized (mBufferLock) {
              discardOldest(protoLength);
              mBuffer.add(proto);
              mBufferUsedSize += protoLength;
              mBufferLock.notify();
          }
      }
  
      boolean contains(byte[] other) {
          return mBuffer.stream()
                  .anyMatch(p -> Arrays.equals(p.getBytes(), other));
      }
  
      /**
       * Writes the trace buffer to disk.
       */
      void writeTraceToFile(File traceFile) throws IOException {
          synchronized (mBufferLock) {
              traceFile.delete();
              try (OutputStream os = new FileOutputStream(traceFile)) {
                  traceFile.setReadable(true /* readable */, false /* ownerOnly */);
                  ProtoOutputStream proto = new ProtoOutputStream();
                  proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
                  os.write(proto.getBytes());
                  for (ProtoOutputStream protoOutputStream : mBuffer) {
                      proto = protoOutputStream;
                      byte[] protoBytes = proto.getBytes();
                      os.write(protoBytes);
                  }
                  os.flush();
              }
          }
      }
  
      /**
       * Checks if the element can be added to the buffer. The element is already certain to be
       * smaller than the overall buffer size.
       *
       * @param protoLength byte array representation of the Proto object to add
       */
      private void discardOldest(int protoLength) {
          long availableSpace = getAvailableSpace();
  
          while (availableSpace < protoLength) {
  
              ProtoOutputStream item = mBuffer.poll();
              if (item == null) {
                  throw new IllegalStateException("No element to discard from buffer");
              }
              mBufferUsedSize -= item.getRawSize();
              availableSpace = getAvailableSpace();
          }
      }
  
      /**
       * Removes all elements form the buffer
       */
      void resetBuffer() {
          synchronized (mBufferLock) {
              mBuffer.clear();
              mBufferUsedSize = 0;
          }
      }
  
      @VisibleForTesting
      int getBufferSize() {
          return mBufferUsedSize;
      }
  
      String getStatus() {
          synchronized (mBufferLock) {
              return "Buffer size: "
                      + mBufferCapacity
                      + " bytes"
                      + "\n"
                      + "Buffer usage: "
                      + mBufferUsedSize
                      + " bytes"
                      + "\n"
                      + "Elements in the buffer: "
                      + mBuffer.size();
          }
      }
  }
  