Move RingBuffer to SystemUICommon.

This change adds a new module SystemUICommon and moves RingBuffer to
it. This is to prepare introducing a new logging library which would
depend on it.

Bug: 276475093
Test: RingBuffer does not have existing tests, so just verified
LogBuffer works correctly on device

Change-Id: Ib71fcf8526021be1ac3fe279210c54cb7d4cd0e6
diff --git a/packages/SystemUI/common/.gitignore b/packages/SystemUI/common/.gitignore
new file mode 100644
index 0000000..f9a33db
--- /dev/null
+++ b/packages/SystemUI/common/.gitignore
@@ -0,0 +1,9 @@
+.idea/
+.gradle/
+gradle/
+build/
+gradlew*
+local.properties
+*.iml
+android.properties
+buildSrc
\ No newline at end of file
diff --git a/packages/SystemUI/common/Android.bp b/packages/SystemUI/common/Android.bp
new file mode 100644
index 0000000..e36ada8
--- /dev/null
+++ b/packages/SystemUI/common/Android.bp
@@ -0,0 +1,39 @@
+// Copyright (C) 2023 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 {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_library {
+
+    name: "SystemUICommon",
+
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
+
+    static_libs: [
+        "androidx.core_core-ktx",
+    ],
+
+    manifest: "AndroidManifest.xml",
+    kotlincflags: ["-Xjvm-default=all"],
+}
diff --git a/packages/SystemUI/common/AndroidManifest.xml b/packages/SystemUI/common/AndroidManifest.xml
new file mode 100644
index 0000000..6f757eb
--- /dev/null
+++ b/packages/SystemUI/common/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2023 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.systemui.common">
+
+</manifest>
diff --git a/packages/SystemUI/common/OWNERS b/packages/SystemUI/common/OWNERS
new file mode 100644
index 0000000..9b8a79e
--- /dev/null
+++ b/packages/SystemUI/common/OWNERS
@@ -0,0 +1,2 @@
+darrellshi@google.com
+evanlaird@google.com
diff --git a/packages/SystemUI/common/README.md b/packages/SystemUI/common/README.md
new file mode 100644
index 0000000..1cc5277
--- /dev/null
+++ b/packages/SystemUI/common/README.md
@@ -0,0 +1,5 @@
+# SystemUICommon
+
+`SystemUICommon` is a module within SystemUI that hosts standalone helper libraries. It is intended to be used by other modules, and therefore should not have other SystemUI dependencies to avoid circular dependencies.
+
+To maintain the structure of this module, please refrain from adding components at the top level. Instead, add them to specific sub-packages, such as `systemui/common/buffer/`. This will help to keep the module organized and easy to navigate.
diff --git a/packages/SystemUI/common/src/com/android/systemui/common/buffer/RingBuffer.kt b/packages/SystemUI/common/src/com/android/systemui/common/buffer/RingBuffer.kt
new file mode 100644
index 0000000..de49d1c
--- /dev/null
+++ b/packages/SystemUI/common/src/com/android/systemui/common/buffer/RingBuffer.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2022 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.systemui.common.buffer
+
+import kotlin.math.max
+
+/**
+ * A simple ring buffer implementation
+ *
+ * Use [advance] to get the least recent item in the buffer (and then presumably fill it with
+ * appropriate data). This will cause it to become the most recent item.
+ *
+ * As the buffer is used, it will grow, allocating new instances of T using [factory] until it
+ * reaches [maxSize]. After this point, no new instances will be created. Instead, the "oldest"
+ * instances will be recycled from the back of the buffer and placed at the front.
+ *
+ * @param maxSize The maximum size the buffer can grow to before it begins functioning as a ring.
+ * @param factory A function that creates a fresh instance of T. Used by the buffer while it's
+ *   growing to [maxSize].
+ */
+class RingBuffer<T>(private val maxSize: Int, private val factory: () -> T) : Iterable<T> {
+
+    private val buffer = MutableList<T?>(maxSize) { null }
+
+    /**
+     * An abstract representation that points to the "end" of the buffer. Increments every time
+     * [advance] is called and never wraps. Use [indexOf] to calculate the associated index into the
+     * backing array. Always points to the "next" available slot in the buffer. Before the buffer
+     * has completely filled, the value pointed to will be null. Afterward, it will be the value at
+     * the "beginning" of the buffer.
+     *
+     * This value is unlikely to overflow. Assuming [advance] is called at rate of 100 calls/ms,
+     * omega will overflow after a little under three million years of continuous operation.
+     */
+    private var omega: Long = 0
+
+    /**
+     * The number of items currently stored in the buffer. Calls to [advance] will cause this value
+     * to increase by one until it reaches [maxSize].
+     */
+    val size: Int
+        get() = if (omega < maxSize) omega.toInt() else maxSize
+
+    /**
+     * Advances the buffer's position by one and returns the value that is now present at the "end"
+     * of the buffer. If the buffer is not yet full, uses [factory] to create a new item. Otherwise,
+     * reuses the value that was previously at the "beginning" of the buffer.
+     *
+     * IMPORTANT: The value is returned as-is, without being reset. It will retain any data that was
+     * previously stored on it.
+     */
+    fun advance(): T {
+        val index = indexOf(omega)
+        omega += 1
+        val entry = buffer[index] ?: factory().also { buffer[index] = it }
+        return entry
+    }
+
+    /**
+     * Returns the value stored at [index], which can range from 0 (the "start", or oldest element
+     * of the buffer) to [size]
+     * - 1 (the "end", or newest element of the buffer).
+     */
+    operator fun get(index: Int): T {
+        if (index < 0 || index >= size) {
+            throw IndexOutOfBoundsException("Index $index is out of bounds")
+        }
+
+        // If omega is larger than the maxSize, then the buffer is full, and omega is equivalent
+        // to the "start" of the buffer. If omega is smaller than the maxSize, then the buffer is
+        // not yet full and our start should be 0. However, in modspace, maxSize and 0 are
+        // equivalent, so we can get away with using it as the start value instead.
+        val start = max(omega, maxSize.toLong())
+
+        return buffer[indexOf(start + index)]!!
+    }
+
+    inline fun forEach(action: (T) -> Unit) {
+        for (i in 0 until size) {
+            action(get(i))
+        }
+    }
+
+    override fun iterator(): Iterator<T> {
+        return object : Iterator<T> {
+            private var position: Int = 0
+
+            override fun next(): T {
+                if (position >= size) {
+                    throw NoSuchElementException()
+                }
+                return get(position).also { position += 1 }
+            }
+
+            override fun hasNext(): Boolean {
+                return position < size
+            }
+        }
+    }
+
+    private fun indexOf(position: Long): Int {
+        return (position % maxSize).toInt()
+    }
+}