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

Commit 584582dd authored by Anton Potapov's avatar Anton Potapov
Browse files

Add tests for the dialog showing logic

Flag: com.android.systemui.volume_redesign
Test: atest VolumeDialogTimeoutInteractorTest
Test: atest VolumeDialogVisibilityInteractorTest
Test: passes presubmits
Bug: 369994090
Change-Id: Iad89f93d182b5b29f049b2679de23e4ab39d18ec
parent b89a27f4
Loading
Loading
Loading
Loading
+150 −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.volume.dialog.domain.interactor

import android.app.ActivityManager
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.fakeVolumeDialogController
import com.android.systemui.testKosmos
import com.android.systemui.volume.Events
import com.android.systemui.volume.dialog.domain.model.VolumeDialogVisibilityModel
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.days
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

private val dialogTimeoutDuration = 3.seconds

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper()
class VolumeDialogVisibilityInteractorTest : SysuiTestCase() {

    private val kosmos: Kosmos = testKosmos()

    private lateinit var underTest: VolumeDialogVisibilityInteractor

    @Before
    fun setUp() {
        underTest = kosmos.volumeDialogVisibilityInteractor
    }

    @Test
    fun testShowRequest_visible() =
        with(kosmos) {
            testScope.runTest {
                runCurrent()
                val visibilityModel by collectLastValue(underTest.dialogVisibility)
                fakeVolumeDialogController.onShowRequested(
                    Events.SHOW_REASON_VOLUME_CHANGED,
                    false,
                    ActivityManager.LOCK_TASK_MODE_LOCKED,
                )
                runCurrent()

                assertThat(visibilityModel!!)
                    .isEqualTo(
                        VolumeDialogVisibilityModel.Visible(
                            Events.SHOW_REASON_VOLUME_CHANGED,
                            false,
                            ActivityManager.LOCK_TASK_MODE_LOCKED,
                        )
                    )
            }
        }

    @Test
    fun testDismissRequest_dismissed() =
        with(kosmos) {
            testScope.runTest {
                runCurrent()
                val visibilityModel by collectLastValue(underTest.dialogVisibility)
                fakeVolumeDialogController.onShowRequested(
                    Events.SHOW_REASON_VOLUME_CHANGED,
                    false,
                    ActivityManager.LOCK_TASK_MODE_LOCKED,
                )
                runCurrent()

                fakeVolumeDialogController.onDismissRequested(Events.DISMISS_REASON_SCREEN_OFF)

                assertThat(visibilityModel!!)
                    .isEqualTo(
                        VolumeDialogVisibilityModel.Dismissed(Events.DISMISS_REASON_SCREEN_OFF)
                    )
            }
        }

    @Test
    fun testTimeout_dismissed() =
        with(kosmos) {
            testScope.runTest {
                runCurrent()
                underTest.resetDismissTimeout()
                val visibilityModel by collectLastValue(underTest.dialogVisibility)
                fakeVolumeDialogController.onShowRequested(
                    Events.SHOW_REASON_VOLUME_CHANGED,
                    false,
                    ActivityManager.LOCK_TASK_MODE_LOCKED,
                )
                runCurrent()

                advanceTimeBy(1.days)

                assertThat(visibilityModel!!)
                    .isEqualTo(VolumeDialogVisibilityModel.Dismissed(Events.DISMISS_REASON_TIMEOUT))
            }
        }

    @Test
    fun testResetTimeoutInterruptsEvents() =
        with(kosmos) {
            testScope.runTest {
                runCurrent()
                underTest.resetDismissTimeout()
                val visibilityModel by collectLastValue(underTest.dialogVisibility)
                fakeVolumeDialogController.onShowRequested(
                    Events.SHOW_REASON_VOLUME_CHANGED,
                    false,
                    ActivityManager.LOCK_TASK_MODE_LOCKED,
                )
                runCurrent()

                advanceTimeBy(dialogTimeoutDuration / 2)
                underTest.resetDismissTimeout()
                advanceTimeBy(dialogTimeoutDuration / 2)
                underTest.resetDismissTimeout()
                advanceTimeBy(dialogTimeoutDuration / 2)

                assertThat(visibilityModel)
                    .isInstanceOf(VolumeDialogVisibilityModel.Visible::class.java)
            }
        }
}
+195 −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.plugins

import android.media.AudioManager
import android.media.AudioManager.CsdWarning
import android.os.Handler
import android.os.VibrationEffect
import androidx.core.util.getOrElse
import java.util.concurrent.CopyOnWriteArraySet

class FakeVolumeDialogController(private val audioManager: AudioManager) : VolumeDialogController {

    var isVisible: Boolean = false
        private set

    var hasScheduledTouchFeedback: Boolean = false
        private set

    var vibrationEffect: VibrationEffect? = null
        private set

    var hasUserActivity: Boolean = false
        private set

    private var hasVibrator: Boolean = true

    private val state = VolumeDialogController.State()
    private val callbacks = CopyOnWriteArraySet<VolumeDialogController.Callbacks>()

    override fun setActiveStream(stream: Int) {
        // ensure streamState existence for the active stream
        state.states.getOrElse(stream) {
            VolumeDialogController.StreamState().also { streamState ->
                state.states.put(stream, streamState)
            }
        }
        state.activeStream = stream
    }

    override fun setStreamVolume(stream: Int, userLevel: Int) {
        val streamState =
            state.states.getOrElse(stream) {
                VolumeDialogController.StreamState().also { streamState ->
                    state.states.put(stream, streamState)
                }
            }
        streamState.level = userLevel.coerceIn(streamState.levelMin, streamState.levelMax)
    }

    override fun setRingerMode(ringerModeNormal: Int, external: Boolean) {
        if (external) {
            state.ringerModeExternal = ringerModeNormal
        } else {
            state.ringerModeInternal = ringerModeNormal
        }
    }

    fun setHasVibrator(hasVibrator: Boolean) {
        this.hasVibrator = hasVibrator
    }

    override fun hasVibrator(): Boolean = hasVibrator

    override fun vibrate(effect: VibrationEffect) {
        vibrationEffect = effect
    }

    override fun scheduleTouchFeedback() {
        hasScheduledTouchFeedback = true
    }

    fun resetScheduledTouchFeedback() {
        hasScheduledTouchFeedback = false
    }

    override fun getAudioManager(): AudioManager = audioManager

    override fun notifyVisible(visible: Boolean) {
        isVisible = visible
    }

    override fun addCallback(callbacks: VolumeDialogController.Callbacks?, handler: Handler?) {
        this.callbacks.add(callbacks)
    }

    override fun removeCallback(callbacks: VolumeDialogController.Callbacks?) {
        this.callbacks.remove(callbacks)
    }

    override fun userActivity() {
        hasUserActivity = true
    }

    fun resetUserActivity() {
        hasUserActivity = false
    }

    override fun getState() {
        callbacks.sendEvent { it.onStateChanged(state) }
    }

    /** @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onShowRequested */
    fun onShowRequested(reason: Int, keyguardLocked: Boolean, lockTaskModeState: Int) {
        callbacks.sendEvent { it.onShowRequested(reason, keyguardLocked, lockTaskModeState) }
    }

    /** @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onDismissRequested */
    fun onDismissRequested(reason: Int) {
        callbacks.sendEvent { it.onDismissRequested(reason) }
    }

    /**
     * @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onLayoutDirectionChanged
     */
    fun onLayoutDirectionChanged(layoutDirection: Int) {
        callbacks.sendEvent { it.onLayoutDirectionChanged(layoutDirection) }
    }

    /** @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onConfigurationChanged */
    fun onConfigurationChanged() {
        callbacks.sendEvent { it.onConfigurationChanged() }
    }

    /** @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onShowVibrateHint */
    fun onShowVibrateHint() {
        callbacks.sendEvent { it.onShowVibrateHint() }
    }

    /** @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onShowSilentHint */
    fun onShowSilentHint() {
        callbacks.sendEvent { it.onShowSilentHint() }
    }

    /** @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onScreenOff */
    fun onScreenOff() {
        callbacks.sendEvent { it.onScreenOff() }
    }

    /** @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onShowSafetyWarning */
    fun onShowSafetyWarning(flags: Int) {
        callbacks.sendEvent { it.onShowSafetyWarning(flags) }
    }

    /**
     * @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onAccessibilityModeChanged
     */
    fun onAccessibilityModeChanged(showA11yStream: Boolean?) {
        callbacks.sendEvent { it.onAccessibilityModeChanged(showA11yStream) }
    }

    /** @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onShowCsdWarning */
    fun onShowCsdWarning(@CsdWarning csdWarning: Int, durationMs: Int) {
        callbacks.sendEvent { it.onShowCsdWarning(csdWarning, durationMs) }
    }

    /** @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onVolumeChangedFromKey */
    fun onVolumeChangedFromKey() {
        callbacks.sendEvent { it.onVolumeChangedFromKey() }
    }

    override fun getCaptionsEnabledState(checkForSwitchState: Boolean) {
        error("Unsupported for the new Volume Dialog")
    }

    override fun setCaptionsEnabledState(enabled: Boolean) {
        error("Unsupported for the new Volume Dialog")
    }

    override fun getCaptionsComponentState(fromTooltip: Boolean) {
        error("Unsupported for the new Volume Dialog")
    }
}

private inline fun CopyOnWriteArraySet<VolumeDialogController.Callbacks>.sendEvent(
    event: (callback: VolumeDialogController.Callbacks) -> Unit
) {
    for (callback in this) {
        event(callback)
    }
}
+24 −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.plugins

import com.android.systemui.kosmos.Kosmos
import org.mockito.kotlin.mock

val Kosmos.fakeVolumeDialogController by Kosmos.Fixture { FakeVolumeDialogController(mock {}) }
var Kosmos.volumeDialogController: VolumeDialogController by
    Kosmos.Fixture { fakeVolumeDialogController }
+32 −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.volume.dialog.domain.interactor

import android.os.Handler
import android.os.looper
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.plugins.volumeDialogController

val Kosmos.volumeDialogCallbacksInteractor: VolumeDialogCallbacksInteractor by
    Kosmos.Fixture {
        VolumeDialogCallbacksInteractor(
            volumeDialogController = volumeDialogController,
            coroutineScope = applicationCoroutineScope,
            bgHandler = Handler(looper),
        )
    }
+25 −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.volume.dialog.domain.interactor

import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope

val Kosmos.volumeDialogVisibilityInteractor by
    Kosmos.Fixture {
        VolumeDialogVisibilityInteractor(applicationCoroutineScope, volumeDialogCallbacksInteractor)
    }