diff options
| author | 2024-10-04 15:20:41 +0000 | |
|---|---|---|
| committer | 2024-10-04 15:20:41 +0000 | |
| commit | b2be7ebe535ff2349a10c48dd191d3b38436d84c (patch) | |
| tree | 336884fc9cbd28d9fe854c21c521a0a6f0e30888 | |
| parent | fa5e4152054e1197a97f640c4f959c3eef91e652 (diff) | |
| parent | 584582dd7b25d50515ae5664bb596ead2cba911f (diff) | |
Merge changes Iad89f93d,I6bcc80e1,I43e2245d into main
* changes:
Add tests for the dialog showing logic
Add showing the new Volume Dialog based on the VolumeDialogController callback
Add window configuration for the new Volume Dialog
20 files changed, 949 insertions, 119 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorTest.kt new file mode 100644 index 000000000000..7ce421a5aa62 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorTest.kt @@ -0,0 +1,150 @@ +/* + * 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) + } + } +} diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 1c09f84b6fd0..94b0b5f67934 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -540,6 +540,7 @@ <!-- Overridden by values-television/styles.xml with tv-specific settings --> <style name="volume_dialog_theme" parent="Theme.SystemUI"> <item name="android:windowIsFloating">true</item> + <item name="android:showWhenLocked">true</item> </style> <style name="Theme.SystemUI.DayNightDialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog"/> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerExt.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerExt.kt new file mode 100644 index 000000000000..441cbb33f821 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerExt.kt @@ -0,0 +1,33 @@ +/* + * 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.statusbar.policy + +import com.android.systemui.statusbar.policy.DevicePostureController.DevicePostureInt +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.onStart + +/** [DevicePostureController.getDevicePosture] as a [Flow]. */ +@DevicePostureInt +fun DevicePostureController.devicePosture(): Flow<Int> = + conflatedCallbackFlow { + val callback = DevicePostureController.Callback { posture -> trySend(posture) } + addCallback(callback) + awaitClose { removeCallback(callback) } + } + .onStart { emit(devicePosture) } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/NewVolumeDialogPlugin.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/NewVolumeDialogPlugin.kt deleted file mode 100644 index b93714ae4fd4..000000000000 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/NewVolumeDialogPlugin.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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 - -import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.plugins.VolumeDialog -import com.android.systemui.volume.dialog.dagger.VolumeDialogComponent -import com.android.systemui.volume.dialog.dagger.VolumeDialogPluginComponent -import javax.inject.Inject -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.cancel -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.launch - -class NewVolumeDialogPlugin -@Inject -constructor( - @Application private val applicationCoroutineScope: CoroutineScope, - private val volumeDialogPluginComponentFactory: VolumeDialogPluginComponent.Factory, -) : VolumeDialog { - - private var volumeDialogPluginComponent: VolumeDialogPluginComponent? = null - private var job: Job? = null - - override fun init(windowType: Int, callback: VolumeDialog.Callback?) { - job = - applicationCoroutineScope.launch { - coroutineScope { - volumeDialogPluginComponent = volumeDialogPluginComponentFactory.create(this) - } - } - } - - private fun showDialog() { - val volumeDialogPluginComponent = - volumeDialogPluginComponent ?: error("Creating dialog before init was called") - volumeDialogPluginComponent.coroutineScope().launch { - coroutineScope { - val volumeDialogComponent: VolumeDialogComponent = - volumeDialogPluginComponent.volumeDialogComponentFactory().create(this) - with(volumeDialogComponent.volumeDialog()) { - setOnDismissListener { volumeDialogComponent.coroutineScope().cancel() } - show() - } - } - } - } - - override fun destroy() { - job?.cancel() - } -} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt index 74e823e9f592..7476c6a279f3 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt @@ -20,15 +20,39 @@ import android.app.Dialog import android.content.Context import android.os.Bundle import android.view.ContextThemeWrapper +import android.view.MotionEvent import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.res.R +import com.android.systemui.volume.Events +import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor +import com.android.systemui.volume.dialog.ui.binder.VolumeDialogBinder import javax.inject.Inject -class VolumeDialog @Inject constructor(@Application context: Context) : - Dialog(ContextThemeWrapper(context, R.style.volume_dialog_theme)) { +class VolumeDialog +@Inject +constructor( + @Application context: Context, + private val dialogBinder: VolumeDialogBinder, + private val visibilityInteractor: VolumeDialogVisibilityInteractor, +) : Dialog(ContextThemeWrapper(context, R.style.volume_dialog_theme)) { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.volume_dialog) + dialogBinder.bind(this) + } + + /** + * NOTE: This will be called with ACTION_OUTSIDE MotionEvents for touches that occur outside of + * the touchable region of the volume dialog (as returned by [.onComputeInternalInsets]) even if + * those touches occurred within the bounds of the volume dialog. + */ + override fun onTouchEvent(event: MotionEvent): Boolean { + if (isShowing) { + if (event.action == MotionEvent.ACTION_OUTSIDE) { + visibilityInteractor.dismissDialog(Events.DISMISS_REASON_TOUCH_OUTSIDE) + return true + } + } + return false } } 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 a2e81d942ae2..4b7a9782cc6b 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt @@ -18,12 +18,10 @@ package com.android.systemui.volume.dialog import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.plugins.VolumeDialog -import com.android.systemui.volume.dialog.dagger.VolumeDialogComponent import com.android.systemui.volume.dialog.dagger.VolumeDialogPluginComponent import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job -import kotlinx.coroutines.cancel import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch @@ -34,31 +32,17 @@ constructor( private val volumeDialogPluginComponentFactory: VolumeDialogPluginComponent.Factory, ) : VolumeDialog { - private var volumeDialogPluginComponent: VolumeDialogPluginComponent? = null private var job: Job? = null override fun init(windowType: Int, callback: VolumeDialog.Callback?) { job = applicationCoroutineScope.launch { coroutineScope { - volumeDialogPluginComponent = volumeDialogPluginComponentFactory.create(this) - } - } - } + val component = volumeDialogPluginComponentFactory.create(this) - private fun showDialog() { - val volumeDialogPluginComponent = - volumeDialogPluginComponent ?: error("Creating dialog before init was called") - volumeDialogPluginComponent.coroutineScope().launch { - coroutineScope { - val volumeDialogComponent: VolumeDialogComponent = - volumeDialogPluginComponent.volumeDialogComponentFactory().create(this) - with(volumeDialogComponent.volumeDialog()) { - setOnDismissListener { volumeDialogComponent.coroutineScope().cancel() } - show() + component.viewModel().activate() } } - } } override fun destroy() { diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogPluginComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogPluginComponent.kt index 82612a79f6ce..4e0098ccdf99 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogPluginComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogPluginComponent.kt @@ -19,6 +19,7 @@ package com.android.systemui.volume.dialog.dagger import com.android.systemui.volume.dialog.dagger.module.VolumeDialogPluginModule import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPlugin import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope +import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogPluginViewModel import dagger.BindsInstance import dagger.Subcomponent import kotlinx.coroutines.CoroutineScope @@ -31,15 +32,7 @@ import kotlinx.coroutines.CoroutineScope @Subcomponent(modules = [VolumeDialogPluginModule::class]) interface VolumeDialogPluginComponent { - /** - * Provides a coroutine scope to use inside [VolumeDialogPluginScope]. - * [com.android.systemui.volume.dialog.VolumeDialogPlugin] manages the lifecycle of this scope. - * It's cancelled when the dialog is disposed. This helps to free occupied resources when volume - * dialog is not shown. - */ - @VolumeDialogPlugin fun coroutineScope(): CoroutineScope - - fun volumeDialogComponentFactory(): VolumeDialogComponent.Factory + fun viewModel(): VolumeDialogPluginViewModel @Subcomponent.Factory interface Factory { diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt index ec7c6cee21ff..2e26fd6de410 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt @@ -20,7 +20,8 @@ import android.annotation.SuppressLint import android.os.Handler import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.plugins.VolumeDialogController -import com.android.systemui.volume.dialog.dagger.scope.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.model.VolumeDialogEventModel import com.android.systemui.volume.dialog.domain.model.VolumeDialogStateModel import javax.inject.Inject @@ -40,12 +41,12 @@ private const val BUFFER_CAPACITY = 16 * * @see VolumeDialogController.Callbacks */ -@VolumeDialog +@VolumeDialogPluginScope class VolumeDialogCallbacksInteractor @Inject constructor( private val volumeDialogController: VolumeDialogController, - @VolumeDialog private val coroutineScope: CoroutineScope, + @VolumeDialogPlugin private val coroutineScope: CoroutineScope, @Background private val bgHandler: Handler, ) { 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 dd511088cb06..4a709a44b42f 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 @@ -17,7 +17,8 @@ package com.android.systemui.volume.dialog.domain.interactor import com.android.systemui.plugins.VolumeDialogController -import com.android.systemui.volume.dialog.dagger.scope.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.model.VolumeDialogEventModel import com.android.systemui.volume.dialog.domain.model.VolumeDialogStateModel import javax.inject.Inject @@ -35,13 +36,13 @@ import kotlinx.coroutines.flow.stateIn * * @see [VolumeDialogController] */ -@VolumeDialog +@VolumeDialogPluginScope class VolumeDialogStateInteractor @Inject constructor( volumeDialogCallbacksInteractor: VolumeDialogCallbacksInteractor, private val volumeDialogController: VolumeDialogController, - @VolumeDialog private val coroutineScope: CoroutineScope, + @VolumeDialogPlugin private val coroutineScope: CoroutineScope, ) { val volumeDialogState: Flow<VolumeDialogStateModel> = diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt new file mode 100644 index 000000000000..6c92754b6f60 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt @@ -0,0 +1,102 @@ +/* + * 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.annotation.SuppressLint +import com.android.systemui.volume.Events +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.model.VolumeDialogEventModel +import com.android.systemui.volume.dialog.domain.model.VolumeDialogVisibilityModel +import javax.inject.Inject +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.mapLatest +import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.update + +private val maxDialogShowTime: Duration = 3.seconds + +/** + * Handles Volume Dialog visibility state. It might change from several sources: + * - [com.android.systemui.plugins.VolumeDialogController] requests visibility change; + * - it might be dismissed by the inactivity timeout; + * - it can be dismissed by the user; + */ +@OptIn(ExperimentalCoroutinesApi::class) +@VolumeDialogPluginScope +class VolumeDialogVisibilityInteractor +@Inject +constructor( + @VolumeDialogPlugin coroutineScope: CoroutineScope, + callbacksInteractor: VolumeDialogCallbacksInteractor, +) { + + @SuppressLint("SharedFlowCreation") + private val mutableDismissDialogEvents = MutableSharedFlow<Unit>() + private val mutableDialogVisibility = + MutableStateFlow<VolumeDialogVisibilityModel>(VolumeDialogVisibilityModel.Invisible) + + val dialogVisibility: Flow<VolumeDialogVisibilityModel> = mutableDialogVisibility.asStateFlow() + + init { + merge( + mutableDismissDialogEvents.mapLatest { + delay(maxDialogShowTime) + VolumeDialogEventModel.DismissRequested(Events.DISMISS_REASON_TIMEOUT) + }, + callbacksInteractor.event, + ) + .onEach { event -> + VolumeDialogVisibilityModel.fromEvent(event)?.let { model -> + mutableDialogVisibility.value = model + if (model is VolumeDialogVisibilityModel.Visible) { + resetDismissTimeout() + } + } + } + .launchIn(coroutineScope) + } + + /** + * Dismisses the dialog with a given [reason]. The new state will be emitted in the + * [dialogVisibility]. + */ + fun dismissDialog(reason: Int) { + mutableDialogVisibility.update { + if (it is VolumeDialogVisibilityModel.Dismissed) { + it + } else { + VolumeDialogVisibilityModel.Dismissed(reason) + } + } + } + + /** Resets current dialog timeout. */ + suspend fun resetDismissTimeout() { + mutableDismissDialogEvents.emit(Unit) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogVisibilityModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogVisibilityModel.kt new file mode 100644 index 000000000000..646445d33f51 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogVisibilityModel.kt @@ -0,0 +1,48 @@ +/* + * 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.model + +/** Models current Volume Dialog visibility state. */ +sealed interface VolumeDialogVisibilityModel { + + /** Dialog is currently visible. */ + data class Visible(val reason: Int, val keyguardLocked: Boolean, val lockTaskModeState: Int) : + VolumeDialogVisibilityModel + + /** Dialog has never been shown. So it's just invisible. */ + interface Invisible : VolumeDialogVisibilityModel { + companion object : Invisible + } + + /** Dialog has been shown and then dismissed. */ + data class Dismissed(val reason: Int) : Invisible + + companion object { + + /** + * Creates [VolumeDialogVisibilityModel] from appropriate events and returns null otherwise. + */ + fun fromEvent(event: VolumeDialogEventModel): VolumeDialogVisibilityModel? { + return when (event) { + is VolumeDialogEventModel.DismissRequested -> Dismissed(event.reason) + is VolumeDialogEventModel.ShowRequested -> + Visible(event.reason, event.keyguardLocked, event.lockTaskModeState) + else -> null + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt new file mode 100644 index 000000000000..59c38c019823 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt @@ -0,0 +1,46 @@ +/* + * 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 + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.dagger.VolumeLog +import com.android.systemui.volume.Events +import javax.inject.Inject + +private const val TAG = "SysUI_VolumeDialog" + +/** Logs events related to the Volume Panel. */ +class VolumeDialogLogger @Inject constructor(@VolumeLog private val logBuffer: LogBuffer) { + + fun onShow(reason: Int) { + logBuffer.log( + TAG, + LogLevel.DEBUG, + { int1 = reason }, + { "Show: ${Events.SHOW_REASONS[int1]}" }, + ) + } + + fun onDismiss(reason: Int) { + logBuffer.log( + TAG, + LogLevel.DEBUG, + { int1 = reason }, + { "Dismiss: ${Events.DISMISS_REASONS[int1]}" }, + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt new file mode 100644 index 000000000000..3f2c39bba6e7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt @@ -0,0 +1,86 @@ +/* + * 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.ui.binder + +import android.app.Dialog +import android.graphics.Color +import android.graphics.PixelFormat +import android.graphics.drawable.ColorDrawable +import android.view.View +import android.view.ViewGroup +import android.view.Window +import android.view.WindowManager +import androidx.lifecycle.lifecycleScope +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.res.R +import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog +import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope +import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogGravityViewModel +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch + +@VolumeDialogScope +class VolumeDialogBinder +@Inject +constructor( + @VolumeDialog private val coroutineScope: CoroutineScope, + private val volumeDialogViewBinder: VolumeDialogViewBinder, + private val gravityViewModel: VolumeDialogGravityViewModel, +) { + + fun bind(dialog: Dialog) { + with(dialog) { + setupWindow(window!!) + dialog.setContentView(R.layout.volume_dialog) + + val volumeDialogView: View = dialog.requireViewById(R.id.volume_dialog_container) + volumeDialogView.repeatWhenAttached { + lifecycleScope.launch { volumeDialogViewBinder.bind(volumeDialogView) } + } + } + } + + /** Configures [Window] for the [Dialog]. */ + private fun setupWindow(window: Window) = + with(window) { + clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) + addFlags( + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or + WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or + WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or + WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED + ) + addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY) + + requestFeature(Window.FEATURE_NO_TITLE) + setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY) + setWindowAnimations(-1) + setFormat(PixelFormat.TRANSLUCENT) + + attributes = + attributes.apply { + title = "VolumeDialog" // Not the same as Window#setTitle + } + setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) + + gravityViewModel.dialogGravity.onEach { window.setGravity(it) }.launchIn(coroutineScope) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogGravityViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogGravityViewModel.kt new file mode 100644 index 000000000000..df6523c9d750 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogGravityViewModel.kt @@ -0,0 +1,92 @@ +/* + * 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.ui.viewmodel + +import android.content.Context +import android.content.res.Configuration +import android.view.Gravity +import androidx.annotation.GravityInt +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.UiBackground +import com.android.systemui.res.R +import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.statusbar.policy.DevicePostureController +import com.android.systemui.statusbar.policy.devicePosture +import com.android.systemui.statusbar.policy.onConfigChanged +import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog +import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.withContext + +@VolumeDialogScope +class VolumeDialogGravityViewModel +@Inject +constructor( + @Application private val context: Context, + @VolumeDialog private val coroutineScope: CoroutineScope, + @UiBackground private val uiBackgroundCoroutineContext: CoroutineContext, + configurationController: ConfigurationController, + private val devicePostureController: DevicePostureController, +) { + + @GravityInt private var originalGravity: Int = context.getAbsoluteGravity() + + val dialogGravity: Flow<Int> = + combine( + devicePostureController.devicePosture(), + configurationController.onConfigChanged.onEach { onConfigurationChanged() }, + ) { devicePosture, configuration -> + context.calculateGravity(devicePosture, configuration) + } + .stateIn( + scope = coroutineScope, + started = SharingStarted.Eagerly, + context.calculateGravity(), + ) + + private suspend fun onConfigurationChanged() { + withContext(uiBackgroundCoroutineContext) { originalGravity = context.getAbsoluteGravity() } + } + + @GravityInt + private fun Context.calculateGravity( + devicePosture: Int = devicePostureController.devicePosture, + config: Configuration = resources.configuration, + ): Int { + val isLandscape = config.orientation == Configuration.ORIENTATION_LANDSCAPE + val isHalfOpen = devicePosture == DevicePostureController.DEVICE_POSTURE_HALF_OPENED + val gravity = + if (isLandscape && isHalfOpen) { + originalGravity or Gravity.TOP + } else { + originalGravity + } + return getAbsoluteGravity(gravity) + } +} + +@GravityInt +private fun Context.getAbsoluteGravity( + gravity: Int = resources.getInteger(R.integer.volume_dialog_gravity) +): Int = with(resources) { Gravity.getAbsoluteGravity(gravity, configuration.layoutDirection) } 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 new file mode 100644 index 000000000000..329a947f24ef --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt @@ -0,0 +1,93 @@ +/* + * 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.ui.viewmodel + +import android.app.Dialog +import com.android.systemui.lifecycle.ExclusiveActivatable +import com.android.systemui.plugins.VolumeDialogController +import com.android.systemui.volume.Events +import com.android.systemui.volume.dialog.dagger.VolumeDialogComponent +import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope +import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor +import com.android.systemui.volume.dialog.domain.model.VolumeDialogVisibilityModel +import com.android.systemui.volume.dialog.shared.VolumeDialogLogger +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.cancel +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.mapLatest +import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine + +@OptIn(ExperimentalCoroutinesApi::class) +@VolumeDialogPluginScope +class VolumeDialogPluginViewModel +@Inject +constructor( + private val componentFactory: VolumeDialogComponent.Factory, + private val dialogVisibilityInteractor: VolumeDialogVisibilityInteractor, + private val controller: VolumeDialogController, + private val logger: VolumeDialogLogger, +) : ExclusiveActivatable() { + + override suspend fun onActivated(): Nothing { + coroutineScope { + dialogVisibilityInteractor.dialogVisibility + .mapLatest { visibilityModel -> + with(visibilityModel) { + if (this is VolumeDialogVisibilityModel.Visible) { + showDialog(reason, keyguardLocked, lockTaskModeState) + } + if (this is VolumeDialogVisibilityModel.Dismissed) { + Events.writeEvent(Events.EVENT_DISMISS_DIALOG, reason) + logger.onDismiss(reason) + } + } + } + .launchIn(this) + } + awaitCancellation() + } + + suspend fun showDialog(reason: Int, keyguardLocked: Boolean, lockTaskModeState: Int): Unit = + coroutineScope { + logger.onShow(reason) + + controller.notifyVisible(true) + + val volumeDialogComponent: VolumeDialogComponent = componentFactory.create(this) + val dialog = + volumeDialogComponent.volumeDialog().apply { + setOnDismissListener { + volumeDialogComponent.coroutineScope().cancel() + dialogVisibilityInteractor.dismissDialog(Events.DISMISS_REASON_UNKNOWN) + } + } + launch { dialog.awaitShow() } + + Events.writeEvent(Events.EVENT_SHOW_DIALOG, reason, keyguardLocked) + } +} + +/** Shows [Dialog] until suspend function is cancelled. */ +private suspend fun Dialog.awaitShow() = + suspendCancellableCoroutine<Unit> { + show() + it.invokeOnCancellation { dismiss() } + } 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 f9e91aee4922..30c8c15387eb 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 @@ -19,11 +19,12 @@ package com.android.systemui.volume.dialog.ui.viewmodel import com.android.systemui.lifecycle.ExclusiveActivatable import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import kotlinx.coroutines.awaitCancellation class VolumeDialogViewModel @AssistedInject constructor() : ExclusiveActivatable() { override suspend fun onActivated(): Nothing { - TODO("Not yet implemented") + awaitCancellation() } @AssistedFactory diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt new file mode 100644 index 000000000000..e4a2a874b848 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt @@ -0,0 +1,195 @@ +/* + * 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) + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/VolumeDialogControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/VolumeDialogControllerKosmos.kt new file mode 100644 index 000000000000..2f6d4fa32e65 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/VolumeDialogControllerKosmos.kt @@ -0,0 +1,24 @@ +/* + * 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 } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorKosmos.kt new file mode 100644 index 000000000000..db9c48d9be6f --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorKosmos.kt @@ -0,0 +1,32 @@ +/* + * 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), + ) + } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/NewVolumeDialog.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorKosmos.kt index 869b3c65d70e..e73539eac6f1 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/NewVolumeDialog.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorKosmos.kt @@ -14,21 +14,12 @@ * limitations under the License. */ -package com.android.systemui.volume.dialog +package com.android.systemui.volume.dialog.domain.interactor -import android.app.Dialog -import android.content.Context -import android.os.Bundle -import android.view.ContextThemeWrapper -import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.res.R -import javax.inject.Inject +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope -class NewVolumeDialog @Inject constructor(@Application context: Context) : - Dialog(ContextThemeWrapper(context, R.style.volume_dialog_theme)) { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.volume_dialog) +val Kosmos.volumeDialogVisibilityInteractor by + Kosmos.Fixture { + VolumeDialogVisibilityInteractor(applicationCoroutineScope, volumeDialogCallbacksInteractor) } -} |