Skip to content
Snippets Groups Projects
Commit 80ba92cf authored by Darrell Shi's avatar Darrell Shi Committed by Android (Google) Code Review
Browse files

Merge "UIEvents for enter and exit the communal hub" into main

parents a5dcf9e3 f9ca2c6b
No related branches found
No related tags found
No related merge requests found
/*
* 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.communal.log
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory
import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.any
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class CommunalLoggerStartableTest : SysuiTestCase() {
@Mock private lateinit var uiEventLogger: UiEventLogger
private lateinit var testScope: TestScope
private lateinit var communalInteractor: CommunalInteractor
private lateinit var underTest: CommunalLoggerStartable
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
val withDeps = CommunalInteractorFactory.create()
testScope = withDeps.testScope
communalInteractor = withDeps.communalInteractor
underTest =
CommunalLoggerStartable(
testScope.backgroundScope,
communalInteractor,
uiEventLogger,
)
underTest.start()
}
@Test
fun transitionStateLogging_enterCommunalHub() =
testScope.runTest {
// Transition state is default (non-communal)
val transitionState =
MutableStateFlow<ObservableCommunalTransitionState>(idle(CommunalSceneKey.DEFAULT))
communalInteractor.setTransitionState(transitionState)
runCurrent()
// Verify nothing is logged from the default state
verify(uiEventLogger, never()).log(any())
// Start transition to communal
transitionState.value = transition(to = CommunalSceneKey.Communal)
runCurrent()
// Verify UiEvent logged
verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_START)
// Finish transition to communal
transitionState.value = idle(CommunalSceneKey.Communal)
runCurrent()
// Verify UiEvent logged
verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_FINISH)
verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SHOWN)
}
@Test
fun transitionStateLogging_enterCommunalHub_canceled() =
testScope.runTest {
// Transition state is default (non-communal)
val transitionState =
MutableStateFlow<ObservableCommunalTransitionState>(idle(CommunalSceneKey.DEFAULT))
communalInteractor.setTransitionState(transitionState)
runCurrent()
// Verify nothing is logged from the default state
verify(uiEventLogger, never()).log(any())
// Start transition to communal
transitionState.value = transition(to = CommunalSceneKey.Communal)
runCurrent()
// Verify UiEvent logged
verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_START)
// Cancel the transition
transitionState.value = idle(CommunalSceneKey.DEFAULT)
runCurrent()
// Verify UiEvent logged
verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_CANCEL)
// Verify neither SHOWN nor GONE is logged
verify(uiEventLogger, never()).log(CommunalUiEvent.COMMUNAL_HUB_SHOWN)
verify(uiEventLogger, never()).log(CommunalUiEvent.COMMUNAL_HUB_GONE)
}
@Test
fun transitionStateLogging_exitCommunalHub() =
testScope.runTest {
// Transition state is communal
val transitionState =
MutableStateFlow<ObservableCommunalTransitionState>(idle(CommunalSceneKey.Communal))
communalInteractor.setTransitionState(transitionState)
runCurrent()
// Verify SHOWN is logged when it's the default state
verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SHOWN)
// Start transition from communal
transitionState.value = transition(from = CommunalSceneKey.Communal)
runCurrent()
// Verify UiEvent logged
verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_START)
// Finish transition to communal
transitionState.value = idle(CommunalSceneKey.DEFAULT)
runCurrent()
// Verify UiEvent logged
verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_FINISH)
verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_GONE)
}
@Test
fun transitionStateLogging_exitCommunalHub_canceled() =
testScope.runTest {
// Transition state is communal
val transitionState =
MutableStateFlow<ObservableCommunalTransitionState>(idle(CommunalSceneKey.Communal))
communalInteractor.setTransitionState(transitionState)
runCurrent()
// Clear the initial SHOWN event from the logger
clearInvocations(uiEventLogger)
// Start transition from communal
transitionState.value = transition(from = CommunalSceneKey.Communal)
runCurrent()
// Verify UiEvent logged
verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_START)
// Cancel the transition
transitionState.value = idle(CommunalSceneKey.Communal)
runCurrent()
// Verify UiEvent logged
verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_CANCEL)
// Verify neither SHOWN nor GONE is logged
verify(uiEventLogger, never()).log(CommunalUiEvent.COMMUNAL_HUB_SHOWN)
verify(uiEventLogger, never()).log(CommunalUiEvent.COMMUNAL_HUB_GONE)
}
private fun transition(
from: CommunalSceneKey = CommunalSceneKey.DEFAULT,
to: CommunalSceneKey = CommunalSceneKey.DEFAULT,
): ObservableCommunalTransitionState.Transition {
return ObservableCommunalTransitionState.Transition(
fromScene = from,
toScene = to,
progress = emptyFlow(),
isInitiatedByUserInput = true,
isUserInputOngoing = emptyFlow(),
)
}
private fun idle(sceneKey: CommunalSceneKey): ObservableCommunalTransitionState.Idle {
return ObservableCommunalTransitionState.Idle(sceneKey)
}
}
/*
* 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.communal.log
import com.android.internal.logging.UiEventLogger
import com.android.systemui.CoreStartable
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.util.kotlin.pairwise
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
/** A [CoreStartable] responsible for logging metrics for the communal hub. */
@SysUISingleton
class CommunalLoggerStartable
@Inject
constructor(
@Background private val backgroundScope: CoroutineScope,
private val communalInteractor: CommunalInteractor,
private val uiEventLogger: UiEventLogger,
) : CoreStartable {
override fun start() {
communalInteractor.transitionState
.map { state ->
when {
state.isOnCommunal() -> CommunalUiEvent.COMMUNAL_HUB_SHOWN
state.isNotOnCommunal() -> CommunalUiEvent.COMMUNAL_HUB_GONE
else -> null
}
}
.filterNotNull()
.distinctUntilChanged()
// Drop the default value.
.drop(1)
.onEach { uiEvent -> uiEventLogger.log(uiEvent) }
.launchIn(backgroundScope)
communalInteractor.transitionState
.pairwise()
.map { (old, new) ->
when {
new.isOnCommunal() && old.isSwipingToCommunal() ->
CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_FINISH
new.isOnCommunal() && old.isSwipingFromCommunal() ->
CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_CANCEL
new.isNotOnCommunal() && old.isSwipingFromCommunal() ->
CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_FINISH
new.isNotOnCommunal() && old.isSwipingToCommunal() ->
CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_CANCEL
new.isSwipingToCommunal() && old.isNotOnCommunal() ->
CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_START
new.isSwipingFromCommunal() && old.isOnCommunal() ->
CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_START
else -> null
}
}
.filterNotNull()
.distinctUntilChanged()
.onEach { uiEvent -> uiEventLogger.log(uiEvent) }
.launchIn(backgroundScope)
}
}
/** Whether currently in communal scene. */
private fun ObservableCommunalTransitionState.isOnCommunal(): Boolean {
return this is ObservableCommunalTransitionState.Idle && scene == CommunalSceneKey.Communal
}
/** Whether currently in a scene other than communal. */
private fun ObservableCommunalTransitionState.isNotOnCommunal(): Boolean {
return this is ObservableCommunalTransitionState.Idle && scene != CommunalSceneKey.Communal
}
/** Whether currently transitioning from another scene to communal. */
private fun ObservableCommunalTransitionState.isSwipingToCommunal(): Boolean {
return this is ObservableCommunalTransitionState.Transition &&
toScene == CommunalSceneKey.Communal &&
isInitiatedByUserInput
}
/** Whether currently transitioning from communal to another scene. */
private fun ObservableCommunalTransitionState.isSwipingFromCommunal(): Boolean {
return this is ObservableCommunalTransitionState.Transition &&
fromScene == CommunalSceneKey.Communal &&
isInitiatedByUserInput
}
......@@ -22,8 +22,6 @@ import com.android.internal.logging.UiEventLogger.UiEventEnum
/** UI events for the Communal Hub. */
enum class CommunalUiEvent(private val id: Int) : UiEventEnum {
@UiEvent(doc = "Communal Hub is fully shown") COMMUNAL_HUB_SHOWN(1566),
@UiEvent(doc = "Communal Hub starts entering") COMMUNAL_HUB_ENTERING(1575),
@UiEvent(doc = "Communal Hub starts exiting") COMMUNAL_HUB_EXITING(1576),
@UiEvent(doc = "Communal Hub is fully gone") COMMUNAL_HUB_GONE(1577),
@UiEvent(doc = "Communal Hub times out") COMMUNAL_HUB_TIMEOUT(1578),
@UiEvent(doc = "The visible content in the Communal Hub is fully loaded and rendered")
......
......@@ -24,6 +24,7 @@ import com.android.systemui.accessibility.Magnification
import com.android.systemui.back.domain.interactor.BackActionInteractor
import com.android.systemui.biometrics.BiometricNotificationService
import com.android.systemui.clipboardoverlay.ClipboardListener
import com.android.systemui.communal.log.CommunalLoggerStartable
import com.android.systemui.controls.dagger.StartControlsStartableModule
import com.android.systemui.dagger.qualifiers.PerUser
import com.android.systemui.dreams.AssistantAttentionMonitor
......@@ -318,4 +319,9 @@ abstract class SystemUICoreStartableModule {
@IntoMap
@ClassKey(KeyguardDismissBinder::class)
abstract fun bindKeyguardDismissBinder(impl: KeyguardDismissBinder): CoreStartable
@Binds
@IntoMap
@ClassKey(CommunalLoggerStartable::class)
abstract fun bindCommunalLoggerStartable(impl: CommunalLoggerStartable): CoreStartable
}
......@@ -35,7 +35,8 @@ object CommunalInteractorFactory {
@JvmStatic
fun create(
testScope: TestScope = TestScope(),
communalRepository: FakeCommunalRepository = FakeCommunalRepository(),
communalRepository: FakeCommunalRepository =
FakeCommunalRepository(testScope.backgroundScope),
widgetRepository: FakeCommunalWidgetRepository =
FakeCommunalWidgetRepository(testScope.backgroundScope),
mediaRepository: FakeCommunalMediaRepository = FakeCommunalMediaRepository(),
......@@ -51,6 +52,7 @@ object CommunalInteractorFactory {
communalRepository = communalRepository,
)
return WithDependencies(
testScope,
communalRepository,
widgetRepository,
mediaRepository,
......@@ -74,6 +76,7 @@ object CommunalInteractorFactory {
}
data class WithDependencies(
val testScope: TestScope,
val communalRepository: FakeCommunalRepository,
val widgetRepository: FakeCommunalWidgetRepository,
val mediaRepository: FakeCommunalMediaRepository,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment