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

Commit 9e527238 authored by Anton Potapov's avatar Anton Potapov Committed by Android (Google) Code Review
Browse files

Merge "Add showing a SafetyWarningDialog when requested by the VolumeController" into main

parents 68fe97e6 65c4cb7f
Loading
Loading
Loading
Loading
+123 −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.media.AudioManager
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.kosmos.Kosmos
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.plugins.fakeVolumeDialogController
import com.android.systemui.testKosmos
import com.android.systemui.volume.Events
import com.android.systemui.volume.dialog.data.repository.volumeDialogVisibilityRepository
import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
class VolumeDialogSafetyWarningInteractorTest : SysuiTestCase() {

    private val kosmos: Kosmos = testKosmos()

    private lateinit var underTest: VolumeDialogSafetyWarningInteractor

    @Before
    fun setup() {
        kosmos.useUnconfinedTestDispatcher()
        underTest = kosmos.volumeDialogSafetyWarningInteractor
    }

    @Test
    fun dismiss_isShowingSafetyWarning_isFalse() =
        with(kosmos) {
            runTest {
                val isShowingSafetyWarning by collectLastValue(underTest.isShowingSafetyWarning)

                underTest.onSafetyWarningDismissed()

                assertThat(isShowingSafetyWarning).isFalse()
            }
        }

    @Test
    fun flagShowUi_isShowingSafetyWarning_isTrue() =
        with(kosmos) {
            runTest {
                val isShowingSafetyWarning by collectLastValue(underTest.isShowingSafetyWarning)

                fakeVolumeDialogController.onShowSafetyWarning(AudioManager.FLAG_SHOW_UI)

                assertThat(isShowingSafetyWarning).isTrue()
            }
        }

    @Test
    fun flagShowUiWarnings_isShowingSafetyWarning_isTrue() =
        with(kosmos) {
            runTest {
                val isShowingSafetyWarning by collectLastValue(underTest.isShowingSafetyWarning)

                fakeVolumeDialogController.onShowSafetyWarning(AudioManager.FLAG_SHOW_UI_WARNINGS)

                assertThat(isShowingSafetyWarning).isTrue()
            }
        }

    @Test
    fun invisibleAndNoFlags_isShowingSafetyWarning_isFalse() =
        with(kosmos) {
            runTest {
                val isShowingSafetyWarning by collectLastValue(underTest.isShowingSafetyWarning)
                volumeDialogVisibilityRepository.updateVisibility {
                    VolumeDialogVisibilityModel.Invisible
                }

                fakeVolumeDialogController.onShowSafetyWarning(0)

                assertThat(isShowingSafetyWarning).isFalse()
            }
        }

    @Test
    fun visibleAndNoFlags_isShowingSafetyWarning_isTrue() =
        with(kosmos) {
            runTest {
                val isShowingSafetyWarning by collectLastValue(underTest.isShowingSafetyWarning)
                volumeDialogVisibilityRepository.updateVisibility {
                    VolumeDialogVisibilityModel.Visible(
                        Events.SHOW_REASON_VOLUME_CHANGED,
                        false,
                        ActivityManager.LOCK_TASK_MODE_LOCKED,
                    )
                }

                fakeVolumeDialogController.onShowSafetyWarning(0)

                assertThat(isShowingSafetyWarning).isTrue()
            }
        }
}
+38 −1
Original line number Diff line number Diff line
@@ -16,19 +16,31 @@

package com.android.systemui.volume.dialog

import android.content.Context
import android.media.AudioManager
import com.android.app.tracing.coroutines.coroutineScopeTraced
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.plugins.VolumeDialog
import com.android.systemui.volume.SafetyWarningDialog
import com.android.systemui.volume.dialog.dagger.VolumeDialogPluginComponent
import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogPluginViewModel
import javax.inject.Inject
import kotlin.coroutines.resume
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.suspendCancellableCoroutine

@OptIn(ExperimentalCoroutinesApi::class)
class VolumeDialogPlugin
@Inject
constructor(
    @Application private val applicationCoroutineScope: CoroutineScope,
    private val context: Context,
    private val audioManager: AudioManager,
    private val volumeDialogPluginComponentFactory: VolumeDialogPluginComponent.Factory,
) : VolumeDialog {

@@ -41,14 +53,39 @@ constructor(
                coroutineScopeTraced("[Volume]plugin") {
                    pluginComponent =
                        volumeDialogPluginComponentFactory.create(this).also {
                            it.viewModel().launchVolumeDialog()
                            bindPlugin(it.viewModel())
                        }
                }
            }
    }

    private fun CoroutineScope.bindPlugin(viewModel: VolumeDialogPluginViewModel) {
        viewModel.launchVolumeDialog()

        viewModel.isShowingSafetyWarning
            .mapLatest { isShowingSafetyWarning ->
                if (isShowingSafetyWarning) {
                    showSafetyWarningVisibility { viewModel.onSafetyWarningDismissed() }
                }
            }
            .launchIn(this)
    }

    override fun destroy() {
        job?.cancel()
        pluginComponent = null
    }

    private suspend fun showSafetyWarningVisibility(onDismissed: () -> Unit) =
        suspendCancellableCoroutine { continuation ->
            val dialog =
                object : SafetyWarningDialog(context, audioManager) {
                    override fun cleanUp() {
                        onDismissed()
                        continuation.resume(Unit)
                    }
                }
            dialog.show()
            continuation.invokeOnCancellation { dialog.dismiss() }
        }
}
+55 −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.media.AudioManager
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
import com.android.systemui.volume.dialog.shared.model.VolumeDialogSafetyWarningModel
import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map

private const val VISIBLE_FLAGS = AudioManager.FLAG_SHOW_UI or AudioManager.FLAG_SHOW_UI_WARNINGS

@VolumeDialogPluginScope
class VolumeDialogSafetyWarningInteractor
@Inject
constructor(
    private val stateInteractor: VolumeDialogStateInteractor,
    visibilityInteractor: VolumeDialogVisibilityInteractor,
) {

    val isShowingSafetyWarning: Flow<Boolean> =
        stateInteractor.volumeDialogState.map {
            when (it.isShowingSafetyWarning) {
                is VolumeDialogSafetyWarningModel.Visible ->
                    if (it.isShowingSafetyWarning.flags and VISIBLE_FLAGS == 0) {
                        visibilityInteractor.dialogVisibility.first() is
                            VolumeDialogVisibilityModel.Visible
                    } else {
                        true
                    }
                is VolumeDialogSafetyWarningModel.Invisible -> false
            }
        }

    fun onSafetyWarningDismissed() {
        stateInteractor.setSafetyWarning(VolumeDialogSafetyWarningModel.Invisible)
    }
}
+8 −0
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPlugin
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
import com.android.systemui.volume.dialog.data.repository.VolumeDialogStateRepository
import com.android.systemui.volume.dialog.domain.model.VolumeDialogEventModel
import com.android.systemui.volume.dialog.shared.model.VolumeDialogSafetyWarningModel
import com.android.systemui.volume.dialog.shared.model.VolumeDialogStateModel
import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel
import javax.inject.Inject
@@ -61,6 +62,9 @@ constructor(
                            oldState.copy(shouldShowA11ySlider = event.showA11yStream)
                        }
                    }
                    is VolumeDialogEventModel.ShowSafetyWarning -> {
                        setSafetyWarning(VolumeDialogSafetyWarningModel.Visible(event.flags))
                    }
                    else -> {
                        // do nothing
                    }
@@ -72,6 +76,10 @@ constructor(

    val volumeDialogState: Flow<VolumeDialogStateModel> = volumeDialogStateRepository.state

    fun setSafetyWarning(model: VolumeDialogSafetyWarningModel) {
        volumeDialogStateRepository.updateState { it.copy(isShowingSafetyWarning = model) }
    }

    /** Returns a copy of [model] filled with the values from [VolumeDialogController.State]. */
    private fun VolumeDialogController.State.copyIntoModel(
        model: VolumeDialogStateModel
+27 −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.shared.model

/** Models current Volume Safety Dialog state. */
sealed interface VolumeDialogSafetyWarningModel {

    /** Volume Safety Dialog is visible and has been shown with the [flags]. */
    data class Visible(val flags: Int) : VolumeDialogSafetyWarningModel

    /** Volume Safety Dialog is invisible. */
    data object Invisible : VolumeDialogSafetyWarningModel
}
Loading