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

Commit 073beda4 authored by Ale Nijamkin's avatar Ale Nijamkin Committed by Alejandro Nijamkin
Browse files

SysUiViewModel

An Activatable base class for view-models and composable utilities to
get them.

Bug: 354269846
Test: unit tests included
Flag: NONE this is unused
Change-Id: I1a80783559a99354ab04cf5228418d14fc2d8b23
parent 2535a652
Loading
Loading
Loading
Loading
+76 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.lifecycle

import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
class ActivatableTest : SysuiTestCase() {

    @get:Rule val composeRule = createComposeRule()

    @Test
    fun rememberActivated() {
        val keepAliveMutable = mutableStateOf(true)
        var isActive = false
        composeRule.setContent {
            val keepAlive by keepAliveMutable
            if (keepAlive) {
                rememberActivated {
                    FakeActivatable(
                        onActivation = { isActive = true },
                        onDeactivation = { isActive = false },
                    )
                }
            }
        }
        assertThat(isActive).isTrue()
    }

    @Test
    fun rememberActivated_leavingTheComposition() {
        val keepAliveMutable = mutableStateOf(true)
        var isActive = false
        composeRule.setContent {
            val keepAlive by keepAliveMutable
            if (keepAlive) {
                rememberActivated {
                    FakeActivatable(
                        onActivation = { isActive = true },
                        onDeactivation = { isActive = false },
                    )
                }
            }
        }

        // Tear down the composable.
        composeRule.runOnUiThread { keepAliveMutable.value = false }
        composeRule.waitForIdle()

        assertThat(isActive).isFalse()
    }
}
+121 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.
 */

@file:OptIn(ExperimentalCoroutinesApi::class)

package com.android.systemui.lifecycle

import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith

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

    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope

    private val underTest = FakeActivatable()

    @Test
    fun activate() =
        testScope.runTest {
            assertThat(underTest.isActive).isFalse()
            assertThat(underTest.activationCount).isEqualTo(0)
            assertThat(underTest.cancellationCount).isEqualTo(0)

            underTest.activateIn(testScope)
            runCurrent()
            assertThat(underTest.isActive).isTrue()
            assertThat(underTest.activationCount).isEqualTo(1)
            assertThat(underTest.cancellationCount).isEqualTo(0)
        }

    @Test
    fun activate_andCancel() =
        testScope.runTest {
            assertThat(underTest.isActive).isFalse()
            assertThat(underTest.activationCount).isEqualTo(0)
            assertThat(underTest.cancellationCount).isEqualTo(0)

            val job = Job()
            underTest.activateIn(testScope, context = job)
            runCurrent()
            assertThat(underTest.isActive).isTrue()
            assertThat(underTest.activationCount).isEqualTo(1)
            assertThat(underTest.cancellationCount).isEqualTo(0)

            job.cancel()
            runCurrent()
            assertThat(underTest.isActive).isFalse()
            assertThat(underTest.activationCount).isEqualTo(1)
            assertThat(underTest.cancellationCount).isEqualTo(1)
        }

    @Test
    fun activate_afterCancellation() =
        testScope.runTest {
            assertThat(underTest.isActive).isFalse()
            assertThat(underTest.activationCount).isEqualTo(0)
            assertThat(underTest.cancellationCount).isEqualTo(0)

            val job = Job()
            underTest.activateIn(testScope, context = job)
            runCurrent()
            assertThat(underTest.isActive).isTrue()
            assertThat(underTest.activationCount).isEqualTo(1)
            assertThat(underTest.cancellationCount).isEqualTo(0)

            job.cancel()
            runCurrent()
            assertThat(underTest.isActive).isFalse()
            assertThat(underTest.activationCount).isEqualTo(1)
            assertThat(underTest.cancellationCount).isEqualTo(1)

            underTest.activateIn(testScope)
            runCurrent()
            assertThat(underTest.isActive).isTrue()
            assertThat(underTest.activationCount).isEqualTo(2)
            assertThat(underTest.cancellationCount).isEqualTo(1)
        }

    @Test(expected = IllegalStateException::class)
    fun activate_whileActive_throws() =
        testScope.runTest {
            assertThat(underTest.isActive).isFalse()
            assertThat(underTest.activationCount).isEqualTo(0)
            assertThat(underTest.cancellationCount).isEqualTo(0)

            underTest.activateIn(testScope)
            runCurrent()
            assertThat(underTest.isActive).isTrue()
            assertThat(underTest.activationCount).isEqualTo(1)
            assertThat(underTest.cancellationCount).isEqualTo(0)

            underTest.activateIn(testScope)
            runCurrent()
        }
}
+113 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.lifecycle

import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
class SysUiViewModelTest : SysuiTestCase() {

    @get:Rule val composeRule = createComposeRule()

    @Test
    fun rememberActivated() {
        val keepAliveMutable = mutableStateOf(true)
        var isActive = false
        composeRule.setContent {
            val keepAlive by keepAliveMutable
            if (keepAlive) {
                rememberViewModel {
                    FakeSysUiViewModel(
                        onActivation = { isActive = true },
                        onDeactivation = { isActive = false },
                    )
                }
            }
        }
        assertThat(isActive).isTrue()
    }

    @Test
    fun rememberActivated_withKey() {
        val keyMutable = mutableStateOf(1)
        var isActive1 = false
        var isActive2 = false
        composeRule.setContent {
            val key by keyMutable
            rememberViewModel(key) {
                when (key) {
                    1 ->
                        FakeSysUiViewModel(
                            onActivation = { isActive1 = true },
                            onDeactivation = { isActive1 = false },
                        )
                    2 ->
                        FakeSysUiViewModel(
                            onActivation = { isActive2 = true },
                            onDeactivation = { isActive2 = false },
                        )
                    else -> error("unsupported key $key")
                }
            }
        }
        assertThat(isActive1).isTrue()
        assertThat(isActive2).isFalse()

        composeRule.runOnUiThread { keyMutable.value = 2 }
        composeRule.waitForIdle()
        assertThat(isActive1).isFalse()
        assertThat(isActive2).isTrue()

        composeRule.runOnUiThread { keyMutable.value = 1 }
        composeRule.waitForIdle()
        assertThat(isActive1).isTrue()
        assertThat(isActive2).isFalse()
    }

    @Test
    fun rememberActivated_leavingTheComposition() {
        val keepAliveMutable = mutableStateOf(true)
        var isActive = false
        composeRule.setContent {
            val keepAlive by keepAliveMutable
            if (keepAlive) {
                rememberViewModel {
                    FakeSysUiViewModel(
                        onActivation = { isActive = true },
                        onDeactivation = { isActive = false },
                    )
                }
            }
        }

        // Tear down the composable.
        composeRule.runOnUiThread { keepAliveMutable.value = false }
        composeRule.waitForIdle()

        assertThat(isActive).isFalse()
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -25,7 +25,6 @@ import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.SwipeDirection
import com.android.systemui.SysuiTestCase
import com.android.systemui.activatable.activateIn
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
@@ -37,6 +36,7 @@ import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintA
import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.media.controls.data.repository.mediaFilterRepository
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.qs.ui.adapter.fakeQSSceneAdapter
+22 −1
Original line number Diff line number Diff line
@@ -14,7 +14,11 @@
 * limitations under the License.
 */

package com.android.systemui.activatable
package com.android.systemui.lifecycle

import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember

/** Defines interface for classes that can be activated to do coroutine work. */
interface Activatable {
@@ -55,3 +59,20 @@ interface Activatable {
     */
    suspend fun activate()
}

/**
 * Returns a remembered [Activatable] of the type [T] that's automatically kept active until this
 * composable leaves the composition.
 *
 * If the [key] changes, the old [Activatable] is deactivated and a new one will be instantiated,
 * activated, and returned.
 */
@Composable
fun <T : Activatable> rememberActivated(
    key: Any = Unit,
    factory: () -> T,
): T {
    val instance = remember(key) { factory() }
    LaunchedEffect(instance) { instance.activate() }
    return instance
}
Loading