summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt99
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NPVCDownEventState.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/collection/RingBuffer.kt122
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/collection/RingBufferTest.kt131
10 files changed, 316 insertions, 95 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
index 25effec42f1a..db446c39767a 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
@@ -19,13 +19,14 @@ package com.android.systemui.log
import android.os.Trace
import android.util.Log
import com.android.systemui.log.dagger.LogModule
+import com.android.systemui.util.collection.RingBuffer
import java.io.PrintWriter
import java.text.SimpleDateFormat
-import java.util.ArrayDeque
import java.util.Locale
import java.util.concurrent.ArrayBlockingQueue
import java.util.concurrent.BlockingQueue
import kotlin.concurrent.thread
+import kotlin.math.max
/**
* A simple ring buffer of recyclable log messages
@@ -63,29 +64,22 @@ import kotlin.concurrent.thread
*
* Buffers are provided by [LogModule]. Instances should be created using a [LogBufferFactory].
*
- * @param name The name of this buffer
- * @param maxLogs The maximum number of messages to keep in memory at any one time, including the
- * unused pool. Must be >= [poolSize].
- * @param poolSize The maximum amount that the size of the buffer is allowed to flex in response to
- * sequential calls to [document] that aren't immediately followed by a matching call to [push].
+ * @param name The name of this buffer, printed when the buffer is dumped and in some other
+ * situations.
+ * @param maxSize The maximum number of messages to keep in memory at any one time. Buffers start
+ * out empty and grow up to [maxSize] as new messages are logged. Once the buffer's size reaches
+ * the maximum, it behaves like a ring buffer.
*/
class LogBuffer @JvmOverloads constructor(
private val name: String,
- private val maxLogs: Int,
- private val poolSize: Int,
+ private val maxSize: Int,
private val logcatEchoTracker: LogcatEchoTracker,
private val systrace: Boolean = true
) {
- init {
- if (maxLogs < poolSize) {
- throw IllegalArgumentException("maxLogs must be greater than or equal to poolSize, " +
- "but maxLogs=$maxLogs < $poolSize=poolSize")
- }
- }
+ private val buffer = RingBuffer(maxSize) { LogMessageImpl.create() }
- private val buffer: ArrayDeque<LogMessageImpl> = ArrayDeque()
- private val echoMessageQueue: BlockingQueue<LogMessageImpl>? =
- if (logcatEchoTracker.logInBackgroundThread) ArrayBlockingQueue(poolSize) else null
+ private val echoMessageQueue: BlockingQueue<LogMessage>? =
+ if (logcatEchoTracker.logInBackgroundThread) ArrayBlockingQueue(10) else null
init {
if (logcatEchoTracker.logInBackgroundThread && echoMessageQueue != null) {
@@ -104,6 +98,9 @@ class LogBuffer @JvmOverloads constructor(
var frozen = false
private set
+ private val mutable
+ get() = !frozen && maxSize > 0
+
/**
* Logs a message to the log buffer
*
@@ -138,34 +135,19 @@ class LogBuffer @JvmOverloads constructor(
initializer: LogMessage.() -> Unit,
noinline printer: LogMessage.() -> String
) {
- if (!frozen) {
- val message = obtain(tag, level, printer)
- initializer(message)
- push(message)
- }
- }
-
- /**
- * Same as [log], but doesn't push the message to the buffer. Useful if you need to supply a
- * "reason" for doing something (the thing you supply the reason to will presumably call [push]
- * on that message at some point).
- */
- inline fun document(
- tag: String,
- level: LogLevel,
- initializer: LogMessage.() -> Unit,
- noinline printer: LogMessage.() -> String
- ): LogMessage {
val message = obtain(tag, level, printer)
initializer(message)
- return message
+ commit(message)
}
/**
- * Obtains an instance of [LogMessageImpl], usually from the object pool. If the pool has been
- * exhausted, creates a new instance.
+ * You should call [log] instead of this method.
*
- * In general, you should call [log] or [document] instead of this method.
+ * Obtains the next [LogMessage] from the ring buffer. If the buffer is not yet at max size,
+ * grows the buffer by one.
+ *
+ * After calling [obtain], the message will now be at the end of the buffer. The caller must
+ * store any relevant data on the message and then call [commit].
*/
@Synchronized
fun obtain(
@@ -173,28 +155,26 @@ class LogBuffer @JvmOverloads constructor(
level: LogLevel,
printer: (LogMessage) -> String
): LogMessageImpl {
- val message = when {
- frozen -> LogMessageImpl.create()
- buffer.size > maxLogs - poolSize -> buffer.removeFirst()
- else -> LogMessageImpl.create()
+ if (!mutable) {
+ return FROZEN_MESSAGE
}
+ val message = buffer.advance()
message.reset(tag, level, System.currentTimeMillis(), printer)
return message
}
/**
- * Pushes a message into buffer, possibly evicting an older message if the buffer is full.
+ * You should call [log] instead of this method.
+ *
+ * After acquiring a message via [obtain], call this method to signal to the buffer that you
+ * have finished filling in its data fields. The message will be echoed to logcat if
+ * necessary.
*/
@Synchronized
- fun push(message: LogMessage) {
- if (frozen) {
+ fun commit(message: LogMessage) {
+ if (!mutable) {
return
}
- if (buffer.size == maxLogs) {
- Log.e(TAG, "LogBuffer $name has exceeded its pool size")
- buffer.removeFirst()
- }
- buffer.add(message as LogMessageImpl)
// Log in the background thread only if echoMessageQueue exists and has capacity (checking
// capacity avoids the possibility of blocking this thread)
if (echoMessageQueue != null && echoMessageQueue.remainingCapacity() > 0) {
@@ -210,7 +190,7 @@ class LogBuffer @JvmOverloads constructor(
}
/** Sends message to echo after determining whether to use Logcat and/or systrace. */
- private fun echoToDesiredEndpoints(message: LogMessageImpl) {
+ private fun echoToDesiredEndpoints(message: LogMessage) {
val includeInLogcat = logcatEchoTracker.isBufferLoggable(name, message.level) ||
logcatEchoTracker.isTagLoggable(message.tag, message.level)
echo(message, toLogcat = includeInLogcat, toSystrace = systrace)
@@ -219,19 +199,17 @@ class LogBuffer @JvmOverloads constructor(
/** Converts the entire buffer to a newline-delimited string */
@Synchronized
fun dump(pw: PrintWriter, tailLength: Int) {
- val start = if (tailLength <= 0) { 0 } else { buffer.size - tailLength }
+ val iterationStart = if (tailLength <= 0) { 0 } else { max(0, buffer.size - tailLength) }
- for ((i, message) in buffer.withIndex()) {
- if (i >= start) {
- dumpMessage(message, pw)
- }
+ for (i in iterationStart until buffer.size) {
+ dumpMessage(buffer[i], pw)
}
}
/**
- * "Freezes" the contents of the buffer, making them immutable until [unfreeze] is called.
- * Calls to [log], [document], [obtain], and [push] will not affect the buffer and will return
- * dummy values if necessary.
+ * "Freezes" the contents of the buffer, making it immutable until [unfreeze] is called.
+ * Calls to [log], [obtain], and [commit] will not affect the buffer and will return dummy
+ * values if necessary.
*/
@Synchronized
fun freeze() {
@@ -293,3 +271,4 @@ class LogBuffer @JvmOverloads constructor(
private const val TAG = "LogBuffer"
private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
+private val FROZEN_MESSAGE = LogMessageImpl.create() \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
index cbfca25e0c60..5651399cb891 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
@@ -27,22 +27,21 @@ class LogBufferFactory @Inject constructor(
private val logcatEchoTracker: LogcatEchoTracker
) {
/* limit the size of maxPoolSize for low ram (Go) devices */
- private fun poolLimit(maxPoolSize_requested: Int): Int {
- if (ActivityManager.isLowRamDeviceStatic()) {
- return minOf(maxPoolSize_requested, 20) /* low ram max log size*/
+ private fun adjustMaxSize(requestedMaxSize: Int): Int {
+ return if (ActivityManager.isLowRamDeviceStatic()) {
+ minOf(requestedMaxSize, 20) /* low ram max log size*/
} else {
- return maxPoolSize_requested
+ requestedMaxSize
}
}
@JvmOverloads
fun create(
name: String,
- maxPoolSize: Int,
- flexSize: Int = 10,
+ maxSize: Int,
systrace: Boolean = true
): LogBuffer {
- val buffer = LogBuffer(name, poolLimit(maxPoolSize), flexSize, logcatEchoTracker, systrace)
+ val buffer = LogBuffer(name, adjustMaxSize(maxSize), logcatEchoTracker, systrace)
dumpManager.registerBuffer(name, buffer)
return buffer
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt b/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt
index 91734cc361b0..40b0cdc173d8 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt
@@ -60,7 +60,7 @@ class LogcatEchoTrackerDebug private constructor(
Settings.Global.getUriFor(BUFFER_PATH),
true,
object : ContentObserver(Handler(mainLooper)) {
- override fun onChange(selfChange: Boolean, uri: Uri) {
+ override fun onChange(selfChange: Boolean, uri: Uri?) {
super.onChange(selfChange, uri)
cachedBufferLevels.clear()
}
@@ -70,7 +70,7 @@ class LogcatEchoTrackerDebug private constructor(
Settings.Global.getUriFor(TAG_PATH),
true,
object : ContentObserver(Handler(mainLooper)) {
- override fun onChange(selfChange: Boolean, uri: Uri) {
+ override fun onChange(selfChange: Boolean, uri: Uri?) {
super.onChange(selfChange, uri)
cachedTagLevels.clear()
}
@@ -110,7 +110,7 @@ class LogcatEchoTrackerDebug private constructor(
}
private fun parseProp(propValue: String?): LogLevel {
- return when (propValue?.toLowerCase()) {
+ return when (propValue?.lowercase()) {
"verbose" -> LogLevel.VERBOSE
"v" -> LogLevel.VERBOSE
"debug" -> LogLevel.DEBUG
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 1e7a292dadba..eff025f771a5 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -49,8 +49,7 @@ public class LogModule {
@SysUISingleton
@NotificationLog
public static LogBuffer provideNotificationsLogBuffer(LogBufferFactory factory) {
- return factory.create("NotifLog", 1000 /* maxPoolSize */,
- 10 /* flexSize */, false /* systrace */);
+ return factory.create("NotifLog", 1000 /* maxSize */, false /* systrace */);
}
/** Provides a logging buffer for all logs related to the data layer of notifications. */
@@ -74,8 +73,7 @@ public class LogModule {
@SysUISingleton
@NotificationSectionLog
public static LogBuffer provideNotificationSectionLogBuffer(LogBufferFactory factory) {
- return factory.create("NotifSectionLog", 1000 /* maxPoolSize */,
- 10 /* flexSize */, false /* systrace */);
+ return factory.create("NotifSectionLog", 1000 /* maxSize */, false /* systrace */);
}
/** Provides a logging buffer for all logs related to the data layer of notifications. */
@@ -91,8 +89,7 @@ public class LogModule {
@SysUISingleton
@QSLog
public static LogBuffer provideQuickSettingsLogBuffer(LogBufferFactory factory) {
- return factory.create("QSLog", 500 /* maxPoolSize */,
- 10 /* flexSize */, false /* systrace */);
+ return factory.create("QSLog", 500 /* maxSize */, false /* systrace */);
}
/** Provides a logging buffer for {@link com.android.systemui.broadcast.BroadcastDispatcher} */
@@ -100,8 +97,8 @@ public class LogModule {
@SysUISingleton
@BroadcastDispatcherLog
public static LogBuffer provideBroadcastDispatcherLogBuffer(LogBufferFactory factory) {
- return factory.create("BroadcastDispatcherLog", 500 /* maxPoolSize */,
- 10 /* flexSize */, false /* systrace */);
+ return factory.create("BroadcastDispatcherLog", 500 /* maxSize */,
+ false /* systrace */);
}
/** Provides a logging buffer for all logs related to Toasts shown by SystemUI. */
@@ -139,8 +136,8 @@ public class LogModule {
@SysUISingleton
@QSFragmentDisableLog
public static LogBuffer provideQSFragmentDisableLogBuffer(LogBufferFactory factory) {
- return factory.create("QSFragmentDisableFlagsLog", 10 /* maxPoolSize */,
- 10 /* flexSize */, false /* systrace */);
+ return factory.create("QSFragmentDisableFlagsLog", 10 /* maxSize */,
+ false /* systrace */);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NPVCDownEventState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NPVCDownEventState.kt
index 06235071734e..d44a56942065 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NPVCDownEventState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NPVCDownEventState.kt
@@ -14,9 +14,9 @@
package com.android.systemui.statusbar.phone
import android.view.MotionEvent
-import com.android.internal.util.RingBuffer
import com.android.systemui.dump.DumpsysTableLogger
import com.android.systemui.dump.Row
+import com.android.systemui.util.collection.RingBuffer
import java.text.SimpleDateFormat
import java.util.Locale
@@ -67,16 +67,9 @@ class NPVCDownEventState private constructor(
* Do not use [append] to add new elements. Instead use [insert], as it will recycle if
* necessary.
*/
- class Buffer(
- capacity: Int
- ) : RingBuffer<NPVCDownEventState>(NPVCDownEventState::class.java, capacity) {
- override fun append(t: NPVCDownEventState?) {
- throw UnsupportedOperationException("Not supported, use insert instead")
- }
+ class Buffer(capacity: Int) {
- override fun createNewItem(): NPVCDownEventState {
- return NPVCDownEventState()
- }
+ private val buffer = RingBuffer(capacity) { NPVCDownEventState() }
/**
* Insert a new element in the buffer.
@@ -94,7 +87,7 @@ class NPVCDownEventState private constructor(
touchSlopExceededBeforeDown: Boolean,
lastEventSynthesized: Boolean
) {
- nextSlot.apply {
+ buffer.advance().apply {
this.timeStamp = timeStamp
this.x = x
this.y = y
@@ -115,7 +108,7 @@ class NPVCDownEventState private constructor(
* @see NPVCDownEventState.asStringList
*/
fun toList(): List<Row> {
- return toArray().map { it.asStringList }
+ return buffer.asSequence().map { it.asStringList }.toList()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/collection/RingBuffer.kt b/packages/SystemUI/src/com/android/systemui/util/collection/RingBuffer.kt
new file mode 100644
index 000000000000..97dc842ec699
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/collection/RingBuffer.kt
@@ -0,0 +1,122 @@
+/*
+ * 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.util.collection
+
+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()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt
index 0720bdb06556..bd029a727ee3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt
@@ -24,7 +24,7 @@ import com.android.systemui.log.LogcatEchoTracker
* Creates a LogBuffer that will echo everything to logcat, which is useful for debugging tests.
*/
fun logcatLogBuffer(name: String = "EchoToLogcatLogBuffer") =
- LogBuffer(name, 50, 50, LogcatEchoTrackerAlways())
+ LogBuffer(name, 50, LogcatEchoTrackerAlways())
/**
* A [LogcatEchoTracker] that always allows echoing to the logcat.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt
index 6971c63ed6d4..8cb530c355bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt
@@ -30,7 +30,7 @@ class LSShadeTransitionLoggerTest : SysuiTestCase() {
@Before
fun setup() {
logger = LSShadeTransitionLogger(
- LogBuffer("Test", 10, 10, mock()),
+ LogBuffer("Test", 10, mock()),
gestureLogger,
displayMetrics)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 98397fb01b6b..6abc687f0ebb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -379,7 +379,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
mCommandQueue,
mCarrierConfigTracker,
new CollapsedStatusBarFragmentLogger(
- new LogBuffer("TEST", 1, 1, mock(LogcatEchoTracker.class)),
+ new LogBuffer("TEST", 1, mock(LogcatEchoTracker.class)),
new DisableFlagsLogger()
),
mOperatorNameViewControllerFactory,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/collection/RingBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/collection/RingBufferTest.kt
new file mode 100644
index 000000000000..5e09b81da4e8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/collection/RingBufferTest.kt
@@ -0,0 +1,131 @@
+/*
+ * 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.util.collection
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertSame
+import org.junit.Assert.assertThrows
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class RingBufferTest : SysuiTestCase() {
+
+ private val buffer = RingBuffer(5) { TestElement() }
+
+ private val history = mutableListOf<TestElement>()
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun testBarelyFillBuffer() {
+ fillBuffer(5)
+
+ assertEquals(0, buffer[0].id)
+ assertEquals(1, buffer[1].id)
+ assertEquals(2, buffer[2].id)
+ assertEquals(3, buffer[3].id)
+ assertEquals(4, buffer[4].id)
+ }
+
+ @Test
+ fun testPartiallyFillBuffer() {
+ fillBuffer(3)
+
+ assertEquals(3, buffer.size)
+
+ assertEquals(0, buffer[0].id)
+ assertEquals(1, buffer[1].id)
+ assertEquals(2, buffer[2].id)
+
+ assertThrows(IndexOutOfBoundsException::class.java) { buffer[3] }
+ assertThrows(IndexOutOfBoundsException::class.java) { buffer[4] }
+ }
+
+ @Test
+ fun testSpinBuffer() {
+ fillBuffer(277)
+
+ assertEquals(272, buffer[0].id)
+ assertEquals(273, buffer[1].id)
+ assertEquals(274, buffer[2].id)
+ assertEquals(275, buffer[3].id)
+ assertEquals(276, buffer[4].id)
+ assertThrows(IndexOutOfBoundsException::class.java) { buffer[5] }
+
+ assertEquals(5, buffer.size)
+ }
+
+ @Test
+ fun testElementsAreRecycled() {
+ fillBuffer(23)
+
+ assertSame(history[4], buffer[1])
+ assertSame(history[9], buffer[1])
+ assertSame(history[14], buffer[1])
+ assertSame(history[19], buffer[1])
+ }
+
+ @Test
+ fun testIterator() {
+ fillBuffer(13)
+
+ val iterator = buffer.iterator()
+
+ for (i in 0 until 5) {
+ assertEquals(history[8 + i], iterator.next())
+ }
+ assertFalse(iterator.hasNext())
+ assertThrows(NoSuchElementException::class.java) { iterator.next() }
+ }
+
+ @Test
+ fun testForEach() {
+ fillBuffer(13)
+ var i = 8
+
+ buffer.forEach {
+ assertEquals(history[i], it)
+ i++
+ }
+ assertEquals(13, i)
+ }
+
+ private fun fillBuffer(count: Int) {
+ for (i in 0 until count) {
+ val elem = buffer.advance()
+ elem.id = history.size
+ history.add(elem)
+ }
+ }
+}
+
+private class TestElement(var id: Int = 0) {
+ override fun toString(): String {
+ return "{TestElement $id}"
+ }
+} \ No newline at end of file