Loading packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SafeActivatableTest.kt→packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/BaseActivatableTest.kt +328 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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() } } packages/SystemUI/src/com/android/systemui/lifecycle/SafeActivatable.kt→packages/SystemUI/src/com/android/systemui/lifecycle/BaseActivatable.kt +115 −0 Original line number Diff line number Diff line Loading @@ -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) Loading @@ -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. Loading @@ -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() } } packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt +1 −1 Original line number Diff line number Diff line Loading @@ -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() Loading packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt +2 −2 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -189,7 +189,7 @@ class ActivatableFlowDumperImpl( ) : SimpleFlowDumper(), ActivatableFlowDumper { private val registration = object : SafeActivatable() { object : BaseActivatable() { override suspend fun onActivated(): Nothing { try { dumpManager.registerCriticalDumpable( Loading packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeActivatable.kt +1 −1 Original line number Diff line number Diff line Loading @@ -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 Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SafeActivatableTest.kt→packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/BaseActivatableTest.kt +328 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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() } }
packages/SystemUI/src/com/android/systemui/lifecycle/SafeActivatable.kt→packages/SystemUI/src/com/android/systemui/lifecycle/BaseActivatable.kt +115 −0 Original line number Diff line number Diff line Loading @@ -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) Loading @@ -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. Loading @@ -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() } }
packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt +1 −1 Original line number Diff line number Diff line Loading @@ -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() Loading
packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt +2 −2 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -189,7 +189,7 @@ class ActivatableFlowDumperImpl( ) : SimpleFlowDumper(), ActivatableFlowDumper { private val registration = object : SafeActivatable() { object : BaseActivatable() { override suspend fun onActivated(): Nothing { try { dumpManager.registerCriticalDumpable( Loading
packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeActivatable.kt +1 −1 Original line number Diff line number Diff line Loading @@ -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