diff options
9 files changed, 308 insertions, 20 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractorTest.kt new file mode 100644 index 000000000000..8acf53869774 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractorTest.kt @@ -0,0 +1,123 @@ +/* + * 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() + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt index 094ec39c1c98..203a157f6ffc 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt @@ -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() } + } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractor.kt new file mode 100644 index 000000000000..f707d6713f9b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractor.kt @@ -0,0 +1,55 @@ +/* + * 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) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt index 5c7289baa401..51e79242daaf 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt @@ -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 diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/model/VolumeDialogSafetyWarningModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/model/VolumeDialogSafetyWarningModel.kt new file mode 100644 index 000000000000..39fc222860c6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/model/VolumeDialogSafetyWarningModel.kt @@ -0,0 +1,27 @@ +/* + * 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 +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/model/VolumeDialogStateModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/model/VolumeDialogStateModel.kt index 1792b9967681..838006d0adb2 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/model/VolumeDialogStateModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/model/VolumeDialogStateModel.kt @@ -21,6 +21,8 @@ import android.content.ComponentName /** Models a state of the Volume Dialog. */ data class VolumeDialogStateModel( val shouldShowA11ySlider: Boolean = false, + val isShowingSafetyWarning: VolumeDialogSafetyWarningModel = + VolumeDialogSafetyWarningModel.Invisible, val streamModels: Map<Int, VolumeDialogStreamModel> = mapOf(), val ringerModeInternal: Int = 0, val ringerModeExternal: Int = 0, diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt index ff525f46a7ed..9bab1b0aa25d 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt @@ -18,14 +18,17 @@ package com.android.systemui.volume.dialog.ui.viewmodel import com.android.systemui.volume.Events import com.android.systemui.volume.dialog.VolumeDialog +import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPlugin import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope +import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogSafetyWarningInteractor import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor import com.android.systemui.volume.dialog.shared.VolumeDialogLogger import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel import javax.inject.Inject import javax.inject.Provider +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.mapLatest @@ -34,29 +37,35 @@ import kotlinx.coroutines.flow.mapLatest class VolumeDialogPluginViewModel @Inject constructor( + @VolumeDialogPlugin private val coroutineScope: CoroutineScope, private val dialogVisibilityInteractor: VolumeDialogVisibilityInteractor, + private val dialogSafetyWarningInteractor: VolumeDialogSafetyWarningInteractor, private val volumeDialogProvider: Provider<VolumeDialog>, private val logger: VolumeDialogLogger, ) { - suspend fun launchVolumeDialog() { - coroutineScope { - dialogVisibilityInteractor.dialogVisibility - .mapLatest { visibilityModel -> - with(visibilityModel) { - if (this is VolumeDialogVisibilityModel.Visible) { - showDialog() - Events.writeEvent(Events.EVENT_SHOW_DIALOG, reason, keyguardLocked) - logger.onShow(reason) - } - if (this is VolumeDialogVisibilityModel.Dismissed) { - Events.writeEvent(Events.EVENT_DISMISS_DIALOG, reason) - logger.onDismiss(reason) - } + fun launchVolumeDialog() { + dialogVisibilityInteractor.dialogVisibility + .mapLatest { visibilityModel -> + with(visibilityModel) { + if (this is VolumeDialogVisibilityModel.Visible) { + showDialog() + Events.writeEvent(Events.EVENT_SHOW_DIALOG, reason, keyguardLocked) + logger.onShow(reason) + } + if (this is VolumeDialogVisibilityModel.Dismissed) { + Events.writeEvent(Events.EVENT_DISMISS_DIALOG, reason) + logger.onDismiss(reason) } } - .launchIn(this) - } + } + .launchIn(coroutineScope) + } + + val isShowingSafetyWarning: Flow<Boolean> = dialogSafetyWarningInteractor.isShowingSafetyWarning + + fun onSafetyWarningDismissed() { + dialogSafetyWarningInteractor.onSafetyWarningDismissed() } private fun showDialog() { diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt index 7a6ede4c8b9c..b20dffb8ac33 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt @@ -44,9 +44,9 @@ class VolumeDialogViewModel @Inject constructor( private val context: Context, - dialogVisibilityInteractor: VolumeDialogVisibilityInteractor, + private val dialogVisibilityInteractor: VolumeDialogVisibilityInteractor, volumeDialogSlidersInteractor: VolumeDialogSlidersInteractor, - volumeDialogStateInteractor: VolumeDialogStateInteractor, + private val volumeDialogStateInteractor: VolumeDialogStateInteractor, devicePostureController: DevicePostureController, configurationController: ConfigurationController, ) { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractorKosmos.kt new file mode 100644 index 000000000000..ddd001400879 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractorKosmos.kt @@ -0,0 +1,27 @@ +/* + * 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 + +val Kosmos.volumeDialogSafetyWarningInteractor: VolumeDialogSafetyWarningInteractor by + Kosmos.Fixture { + VolumeDialogSafetyWarningInteractor( + volumeDialogStateInteractor, + volumeDialogVisibilityInteractor, + ) + } |