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

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

Merge "Handle visibility and timeout" into main

parents 5ab3134c 2c8856fd
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -33,8 +33,8 @@ fun AmbientCueContainer(
) {
    val viewModel = rememberViewModel("AmbientCueContainer") { ambientCueViewModelFactory.create() }

    val visible = viewModel.isOverlayVisible
    val expanded = viewModel.isOverlayExpanded
    val visible = viewModel.isVisible
    val expanded = viewModel.isExpanded
    val actions = viewModel.actions

    // TODO: b/414507396 - Replace with the height of the navbar
+71 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.ambientcue.ui.viewmodel

import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.ambientcue.domain.interactor.ambientCueInteractor
import com.android.systemui.kosmos.advanceTimeBy
import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
@SmallTest
class AmbientCueViewModelTest : SysuiTestCase() {
    private val kosmos = testKosmos()
    private val viewModel = kosmos.ambientCueViewModelFactory.create()

    @Before
    fun setUp() {
        viewModel.activateIn(kosmos.testScope)
    }

    @Test
    fun isVisible_timesOut() =
        kosmos.runTest {
            ambientCueInteractor.setIsVisible(true)
            runCurrent()
            assertThat(viewModel.isVisible).isTrue()

            // Times out when there's no interaction
            advanceTimeBy(AmbientCueViewModel.AMBIENT_CUE_TIMEOUT_SEC)
            runCurrent()
            assertThat(viewModel.isVisible).isFalse()
        }

    @Test
    fun isVisible_whenExpanded_doesntTimeOut() =
        kosmos.runTest {
            ambientCueInteractor.setIsVisible(true)
            runCurrent()
            assertThat(viewModel.isVisible).isTrue()

            // Doesn't time out when expanded
            viewModel.expand()
            advanceTimeBy(AmbientCueViewModel.AMBIENT_CUE_TIMEOUT_SEC)
            runCurrent()
            assertThat(viewModel.isVisible).isTrue()
        }
}
+1 −0
Original line number Diff line number Diff line
@@ -145,6 +145,7 @@ constructor(
        @VisibleForTesting const val AMBIENT_ACTION_FEATURE = 72
        // Surface that PCC wants to push cards into
        @VisibleForTesting const val AMBIENT_CUE_SURFACE = "ambientcue"
        // Timeout to hide cuebar if it wasn't interacted with
        private const val TAG = "AmbientCueRepository"
        private const val DEBUG = false
        private const val ACTION_CREATE_AMBIENT_CUE =
+30 −9
Original line number Diff line number Diff line
@@ -16,29 +16,36 @@

package com.android.systemui.ambientcue.ui.viewmodel

import androidx.annotation.VisibleForTesting
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import com.android.app.tracing.coroutines.coroutineScopeTraced
import com.android.systemui.ambientcue.domain.interactor.AmbientCueInteractor
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.Hydrator
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch

class AmbientCueViewModel
@AssistedInject
constructor(private val ambientCueInteractor: AmbientCueInteractor) : ExclusiveActivatable() {
    private val hydrator = Hydrator("OverlayViewModel.hydrator")

    val isOverlayVisible: Boolean by
    val isVisible: Boolean by
        hydrator.hydratedStateOf(
            traceName = "isOverlayVisible",
            traceName = "isVisible",
            initialValue = false,
            source = ambientCueInteractor.isVisible,
        )

    var isOverlayExpanded: Boolean by mutableStateOf(false)
    var isExpanded: Boolean by mutableStateOf(false)
        private set

    val actions: List<ActionViewModel> by
@@ -55,24 +62,37 @@ constructor(private val ambientCueInteractor: AmbientCueInteractor) : ExclusiveA

    fun show() {
        ambientCueInteractor.setIsVisible(true)
        isOverlayExpanded = false
        isExpanded = false
    }

    fun expand() {
        isOverlayExpanded = true
        isExpanded = true
    }

    fun collapse() {
        isOverlayExpanded = false
        isExpanded = false
    }

    fun hide() {
        ambientCueInteractor.setIsVisible(false)
        isOverlayExpanded = false
        isExpanded = false
    }

    override suspend fun onActivated(): Nothing {
        hydrator.activate()
        coroutineScopeTraced("AmbientCueViewModel") {
            launch { hydrator.activate() }
            launch {
                // Hide the UI if the user doesn't interact with it after N seconds
                ambientCueInteractor.isVisible.collectLatest { isVisible ->
                    if (!isVisible) return@collectLatest
                    delay(AMBIENT_CUE_TIMEOUT_SEC)
                    if (!isExpanded) {
                        ambientCueInteractor.setIsVisible(false)
                    }
                }
            }
            awaitCancellation()
        }
    }

    @AssistedFactory
@@ -81,6 +101,7 @@ constructor(private val ambientCueInteractor: AmbientCueInteractor) : ExclusiveA
    }

    companion object {
        private const val TAG = "OverlayViewModel"
        private const val TAG = "AmbientCueViewModel"
        @VisibleForTesting val AMBIENT_CUE_TIMEOUT_SEC = 15.seconds
    }
}