Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 83aa0229 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "BaseActivatable" into main

parents 00e2a716 465342be
Loading
Loading
Loading
Loading
+328 −0
Original line number Diff line number Diff line
@@ -33,7 +33,7 @@ import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
class SafeActivatableTest : SysuiTestCase() {
class BaseActivatableTest : SysuiTestCase() {

    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope
@@ -118,4 +118,211 @@ class SafeActivatableTest : SysuiTestCase() {
            underTest.activateIn(testScope)
            runCurrent()
        }

    @Test
    fun addChild_beforeActive_activatesChildrenOnceActivated() =
        testScope.runTest {
            val child1 = FakeActivatable()
            val child2 = FakeActivatable()
            assertThat(child1.isActive).isFalse()
            assertThat(child2.isActive).isFalse()

            assertThat(underTest.isActive).isFalse()
            underTest.addChild(child1)
            underTest.addChild(child2)
            assertThat(underTest.isActive).isFalse()
            assertThat(child1.isActive).isFalse()
            assertThat(child2.isActive).isFalse()

            underTest.activateIn(this)
            runCurrent()

            assertThat(underTest.isActive).isTrue()
            assertThat(child1.isActive).isTrue()
            assertThat(child2.isActive).isTrue()
        }

    @Test
    fun addChild_whileActive_activatesChildrenImmediately() =
        testScope.runTest {
            underTest.activateIn(this)
            runCurrent()
            assertThat(underTest.isActive).isTrue()

            val child1 = FakeActivatable()
            val child2 = FakeActivatable()
            assertThat(child1.isActive).isFalse()
            assertThat(child2.isActive).isFalse()

            underTest.addChild(child1)
            underTest.addChild(child2)
            runCurrent()

            assertThat(child1.isActive).isTrue()
            assertThat(child2.isActive).isTrue()
        }

    @Test
    fun addChild_afterCancellation_doesNotActivateChildren() =
        testScope.runTest {
            val job = Job()
            underTest.activateIn(this, context = job)
            runCurrent()
            assertThat(underTest.isActive).isTrue()
            job.cancel()
            runCurrent()
            assertThat(underTest.isActive).isFalse()

            val child1 = FakeActivatable()
            val child2 = FakeActivatable()
            assertThat(child1.isActive).isFalse()
            assertThat(child2.isActive).isFalse()

            underTest.addChild(child1)
            underTest.addChild(child2)
            runCurrent()

            assertThat(child1.isActive).isFalse()
            assertThat(child2.isActive).isFalse()
        }

    @Test
    fun activate_cancellation_cancelsCurrentChildren() =
        testScope.runTest {
            val job = Job()
            underTest.activateIn(this, context = job)
            runCurrent()
            assertThat(underTest.isActive).isTrue()

            val child1 = FakeActivatable()
            val child2 = FakeActivatable()
            assertThat(child1.isActive).isFalse()
            assertThat(child2.isActive).isFalse()

            underTest.addChild(child1)
            underTest.addChild(child2)
            runCurrent()

            assertThat(child1.isActive).isTrue()
            assertThat(child2.isActive).isTrue()

            job.cancel()
            runCurrent()
            assertThat(underTest.isActive).isFalse()
            assertThat(child1.isActive).isFalse()
            assertThat(child2.isActive).isFalse()
        }

    @Test
    fun activate_afterCancellation_reactivatesCurrentChildren() =
        testScope.runTest {
            val job = Job()
            underTest.activateIn(this, context = job)
            runCurrent()
            assertThat(underTest.isActive).isTrue()

            val child1 = FakeActivatable()
            val child2 = FakeActivatable()
            assertThat(child1.isActive).isFalse()
            assertThat(child2.isActive).isFalse()

            underTest.addChild(child1)
            underTest.addChild(child2)
            runCurrent()

            assertThat(child1.isActive).isTrue()
            assertThat(child2.isActive).isTrue()

            job.cancel()
            runCurrent()
            assertThat(underTest.isActive).isFalse()
            assertThat(child1.isActive).isFalse()
            assertThat(child2.isActive).isFalse()

            underTest.activateIn(this)
            runCurrent()
            assertThat(underTest.isActive).isTrue()
            assertThat(child1.isActive).isTrue()
            assertThat(child2.isActive).isTrue()
        }

    @Test
    fun removeChild_beforeActive_neverActivatesChild() =
        testScope.runTest {
            val child1 = FakeActivatable()
            val child2 = FakeActivatable()
            assertThat(child1.isActive).isFalse()
            assertThat(child2.isActive).isFalse()

            assertThat(underTest.isActive).isFalse()
            underTest.addChild(child1)
            underTest.addChild(child2)
            assertThat(underTest.isActive).isFalse()
            assertThat(child1.isActive).isFalse()
            assertThat(child2.isActive).isFalse()
        }

    @Test
    fun removeChild_whileActive_cancelsChild() =
        testScope.runTest {
            val child1 = FakeActivatable()
            val child2 = FakeActivatable()
            assertThat(child1.isActive).isFalse()
            assertThat(child2.isActive).isFalse()

            assertThat(underTest.isActive).isFalse()
            underTest.addChild(child1)
            underTest.addChild(child2)
            assertThat(underTest.isActive).isFalse()
            assertThat(child1.isActive).isFalse()
            assertThat(child2.isActive).isFalse()

            underTest.activateIn(this)
            runCurrent()
            assertThat(underTest.isActive).isTrue()
            assertThat(child1.isActive).isTrue()
            assertThat(child2.isActive).isTrue()

            underTest.removeChild(child1)
            runCurrent()
            assertThat(underTest.isActive).isTrue()
            assertThat(child1.isActive).isFalse()
            assertThat(child2.isActive).isTrue()
        }

    @Test
    fun removeChild_afterCancellation_doesNotReactivateChildren() =
        testScope.runTest {
            val child1 = FakeActivatable()
            val child2 = FakeActivatable()
            assertThat(child1.isActive).isFalse()
            assertThat(child2.isActive).isFalse()

            assertThat(underTest.isActive).isFalse()
            underTest.addChild(child1)
            underTest.addChild(child2)
            assertThat(underTest.isActive).isFalse()
            assertThat(child1.isActive).isFalse()
            assertThat(child2.isActive).isFalse()

            val job = Job()
            underTest.activateIn(this, context = job)
            runCurrent()
            assertThat(underTest.isActive).isTrue()
            assertThat(child1.isActive).isTrue()
            assertThat(child2.isActive).isTrue()

            job.cancel()
            runCurrent()
            assertThat(underTest.isActive).isFalse()
            assertThat(child1.isActive).isFalse()
            assertThat(child2.isActive).isFalse()

            underTest.removeChild(child1)
            underTest.activateIn(this)
            runCurrent()
            assertThat(underTest.isActive).isTrue()
            assertThat(child1.isActive).isFalse()
            assertThat(child2.isActive).isTrue()
        }
}
+115 −0
Original line number Diff line number Diff line
@@ -17,14 +17,24 @@
package com.android.systemui.lifecycle

import java.util.concurrent.atomic.AtomicBoolean
import kotlinx.coroutines.Job
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch

/**
 * An [Activatable] that can be concurrently activated by no more than one owner.
 *
 * A previous call to [activate] must be canceled before a new call to [activate] can be made.
 * Trying to call [activate] while already active will fail with an error.
 * A base [Activatable] with the following characteristics:
 * 1. **Can be concurrently activated by no more than one owner.** A previous call to [activate]
 *    must be canceled before a new call to [activate] can be made. Trying to call [activate] while
 *    already active will fail with an error
 * 2. **Can manage child [Activatable]s**. See [addChild] and [removeChild]. Added children
 *    automatically track the activation state of the parent such that when the parent is active,
 *    the children are active and vice-versa. Children are also retained such that deactivating the
 *    parent and reactivating it also cancels and reactivates the children.
 */
abstract class SafeActivatable : Activatable {
abstract class BaseActivatable : Activatable {

    private val _isActive = AtomicBoolean(false)

@@ -38,12 +48,15 @@ abstract class SafeActivatable : Activatable {
        val allowed = _isActive.compareAndSet(false, true)
        check(allowed) { "Cannot activate an already active activatable!" }

        coroutineScope {
            try {
                launch { manageChildren() }
                onActivated()
            } finally {
                isActive = false
            }
        }
    }

    /**
     * Notifies that the [Activatable] has been activated.
@@ -69,4 +82,34 @@ abstract class SafeActivatable : Activatable {
     * @see activate
     */
    protected abstract suspend fun onActivated(): Nothing

    private val newChildren = Channel<Activatable>(Channel.BUFFERED)
    private val jobByChild: MutableMap<Activatable, Job> by lazy { mutableMapOf() }

    private suspend fun manageChildren(): Nothing {
        coroutineScope {
            // Reactivate children that were added during a previous activation:
            jobByChild.keys.forEach { child -> jobByChild[child] = launch { child.activate() } }

            // Process requests to add more children:
            newChildren.receiveAsFlow().collect { newChild ->
                removeChildInternal(newChild)
                jobByChild[newChild] = launch { newChild.activate() }
            }

            awaitCancellation()
        }
    }

    fun addChild(child: Activatable) {
        newChildren.trySend(child)
    }

    fun removeChild(child: Activatable) {
        removeChildInternal(child)
    }

    private fun removeChildInternal(child: Activatable) {
        jobByChild.remove(child)?.cancel()
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -23,7 +23,7 @@ import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.launch

/** Base class for all System UI view-models. */
abstract class SysUiViewModel : SafeActivatable() {
abstract class SysUiViewModel : BaseActivatable() {

    override suspend fun onActivated(): Nothing {
        awaitCancellation()
+2 −2
Original line number Diff line number Diff line
@@ -19,7 +19,7 @@ package com.android.systemui.util.kotlin
import android.util.IndentingPrintWriter
import com.android.systemui.Dumpable
import com.android.systemui.dump.DumpManager
import com.android.systemui.lifecycle.SafeActivatable
import com.android.systemui.lifecycle.BaseActivatable
import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.util.asIndenting
import com.android.systemui.util.printCollection
@@ -189,7 +189,7 @@ class ActivatableFlowDumperImpl(
) : SimpleFlowDumper(), ActivatableFlowDumper {

    private val registration =
        object : SafeActivatable() {
        object : BaseActivatable() {
            override suspend fun onActivated(): Nothing {
                try {
                    dumpManager.registerCriticalDumpable(
+1 −1
Original line number Diff line number Diff line
@@ -21,7 +21,7 @@ import kotlinx.coroutines.awaitCancellation
class FakeActivatable(
    private val onActivation: () -> Unit = {},
    private val onDeactivation: () -> Unit = {},
) : SafeActivatable() {
) : BaseActivatable() {
    var activationCount = 0
    var cancellationCount = 0

Loading