diff options
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) } -} |