diff options
19 files changed, 901 insertions, 25 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java index ebb9ce9909bd..ed8de69ec482 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java +++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java @@ -23,6 +23,7 @@ import android.os.Looper; import com.android.internal.jank.InteractionJankMonitor; import com.android.systemui.CoreStartable; +import com.android.systemui.Flags; import com.android.systemui.dump.DumpManager; import com.android.systemui.media.dialog.MediaOutputDialogManager; import com.android.systemui.plugins.VolumeDialog; @@ -40,6 +41,8 @@ import com.android.systemui.volume.VolumeDialogComponent; import com.android.systemui.volume.VolumeDialogImpl; import com.android.systemui.volume.VolumePanelDialogReceiver; import com.android.systemui.volume.VolumeUI; +import com.android.systemui.volume.dialog.VolumeDialogPlugin; +import com.android.systemui.volume.dialog.dagger.VolumeDialogPluginComponent; import com.android.systemui.volume.domain.interactor.VolumeDialogInteractor; import com.android.systemui.volume.domain.interactor.VolumePanelNavigationInteractor; import com.android.systemui.volume.panel.dagger.VolumePanelComponent; @@ -66,7 +69,8 @@ import dagger.multibindings.IntoSet; SpatializerModule.class, }, subcomponents = { - VolumePanelComponent.class + VolumePanelComponent.class, + VolumeDialogPluginComponent.class, } ) public interface VolumeModule { @@ -101,6 +105,7 @@ public interface VolumeModule { /** */ @Provides static VolumeDialog provideVolumeDialog( + Lazy<VolumeDialogPlugin> volumeDialogProvider, Context context, VolumeDialogController volumeDialogController, AccessibilityManagerWrapper accessibilityManagerWrapper, @@ -118,29 +123,33 @@ public interface VolumeModule { VibratorHelper vibratorHelper, SystemClock systemClock, VolumeDialogInteractor interactor) { - VolumeDialogImpl impl = new VolumeDialogImpl( - context, - volumeDialogController, - accessibilityManagerWrapper, - deviceProvisionedController, - configurationController, - mediaOutputDialogManager, - interactionJankMonitor, - volumePanelNavigationInteractor, - volumeNavigator, - true, /* should listen for jank */ - csdFactory, - devicePostureController, - Looper.getMainLooper(), - volumePanelFlag, - dumpManager, - secureSettings, - vibratorHelper, - systemClock, - interactor); - impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false); - impl.setAutomute(true); - impl.setSilentMode(false); - return impl; + if (Flags.volumeRedesign()) { + return volumeDialogProvider.get(); + } else { + VolumeDialogImpl impl = new VolumeDialogImpl( + context, + volumeDialogController, + accessibilityManagerWrapper, + deviceProvisionedController, + configurationController, + mediaOutputDialogManager, + interactionJankMonitor, + volumePanelNavigationInteractor, + volumeNavigator, + true, /* should listen for jank */ + csdFactory, + devicePostureController, + Looper.getMainLooper(), + volumePanelFlag, + dumpManager, + secureSettings, + vibratorHelper, + systemClock, + interactor); + impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false); + impl.setAutomute(true); + impl.setSilentMode(false); + return impl; + } } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/NewVolumeDialog.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/NewVolumeDialog.kt new file mode 100644 index 000000000000..869b3c65d70e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/NewVolumeDialog.kt @@ -0,0 +1,34 @@ +/* + * 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 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 + +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) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/NewVolumeDialogPlugin.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/NewVolumeDialogPlugin.kt new file mode 100644 index 000000000000..b93714ae4fd4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/NewVolumeDialogPlugin.kt @@ -0,0 +1,67 @@ +/* + * 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 new file mode 100644 index 000000000000..74e823e9f592 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt @@ -0,0 +1,34 @@ +/* + * 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 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 + +class VolumeDialog @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) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt new file mode 100644 index 000000000000..a2e81d942ae2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt @@ -0,0 +1,67 @@ +/* + * 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 VolumeDialogPlugin +@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/dagger/VolumeDialogComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt new file mode 100644 index 000000000000..f7ad3205f3dd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.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.dagger + +import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog +import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope +import dagger.BindsInstance +import dagger.Subcomponent +import kotlinx.coroutines.CoroutineScope + +/** + * Core Volume Dialog dagger component. It's managed by + * [com.android.systemui.volume.dialog.VolumeDialogPlugin] and lives alongside it. + */ +@VolumeDialogScope +@Subcomponent(modules = []) +interface VolumeDialogComponent { + + /** + * Provides a coroutine scope to use inside [VolumeDialogScope]. + * [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. + */ + @VolumeDialog fun coroutineScope(): CoroutineScope + + @VolumeDialogScope fun volumeDialog(): com.android.systemui.volume.dialog.VolumeDialog + + @Subcomponent.Factory + interface Factory { + + fun create(@BindsInstance @VolumeDialog scope: CoroutineScope): VolumeDialogComponent + } +} 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 new file mode 100644 index 000000000000..82612a79f6ce --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogPluginComponent.kt @@ -0,0 +1,51 @@ +/* + * 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.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 dagger.BindsInstance +import dagger.Subcomponent +import kotlinx.coroutines.CoroutineScope + +/** + * Volume Dialog plugin dagger component. It's managed by + * [com.android.systemui.volume.dialog.VolumeDialogPlugin] and lives alongside it. + */ +@VolumeDialogPluginScope +@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 + + @Subcomponent.Factory + interface Factory { + + fun create( + @BindsInstance @VolumeDialogPlugin scope: CoroutineScope + ): VolumeDialogPluginComponent + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogPluginModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogPluginModule.kt new file mode 100644 index 000000000000..3fdf86a923fb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogPluginModule.kt @@ -0,0 +1,22 @@ +/* + * 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.dagger.module + +import com.android.systemui.volume.dialog.dagger.VolumeDialogComponent +import dagger.Module + +@Module(subcomponents = [VolumeDialogComponent::class]) interface VolumeDialogPluginModule diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialog.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialog.kt new file mode 100644 index 000000000000..34bddb42b891 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialog.kt @@ -0,0 +1,26 @@ +/* + * 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.dagger.scope + +import javax.inject.Qualifier + +/** + * Volume Dialog qualifier. + * + * @see VolumeDialogScope + */ +@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class VolumeDialog diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialogPlugin.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialogPlugin.kt new file mode 100644 index 000000000000..1038c30c1e9f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialogPlugin.kt @@ -0,0 +1,29 @@ +/* + * 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.dagger.scope + +import javax.inject.Qualifier + +/** + * Volume Dialog plugin qualifier. + * + * @see VolumeDialogPluginScope + */ +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class VolumeDialogPlugin diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialogPluginScope.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialogPluginScope.kt new file mode 100644 index 000000000000..6c5f672ba2c1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialogPluginScope.kt @@ -0,0 +1,29 @@ +/* + * 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.dagger.scope + +import javax.inject.Scope + +/** + * Volume Dialog plugin dependency injection scope. This scope is created alongside Volume Dialog + * plugin is initialized and destroyed alongside it. This is effectively almost similar + * to @Application now. + */ +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +@Scope +annotation class VolumeDialogPluginScope diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialogScope.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialogScope.kt new file mode 100644 index 000000000000..52caa6a42ab4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialogScope.kt @@ -0,0 +1,25 @@ +/* + * 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.dagger.scope + +import javax.inject.Scope + +/** + * Volume Panel dependency injection scope. This scope is created alongside Volume Panel and + * destroyed when it's lo longer present. + */ +@MustBeDocumented @Retention(AnnotationRetention.RUNTIME) @Scope annotation class VolumeDialogScope 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 new file mode 100644 index 000000000000..ec7c6cee21ff --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt @@ -0,0 +1,135 @@ +/* + * 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 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.domain.model.VolumeDialogEventModel +import com.android.systemui.volume.dialog.domain.model.VolumeDialogStateModel +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.ProducerScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.buffer +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.shareIn + +private const val BUFFER_CAPACITY = 16 + +/** + * Exposes [VolumeDialogController] callback events in the [event]. + * + * @see VolumeDialogController.Callbacks + */ +@VolumeDialog +class VolumeDialogCallbacksInteractor +@Inject +constructor( + private val volumeDialogController: VolumeDialogController, + @VolumeDialog private val coroutineScope: CoroutineScope, + @Background private val bgHandler: Handler, +) { + + @SuppressLint("SharedFlowCreation") // event-but needed + val event: Flow<VolumeDialogEventModel> = + callbackFlow { + val producer = VolumeDialogEventModelProducer(this) + volumeDialogController.addCallback(producer, bgHandler) + awaitClose { volumeDialogController.removeCallback(producer) } + } + .buffer(BUFFER_CAPACITY) + .shareIn(replay = 0, scope = coroutineScope, started = SharingStarted.WhileSubscribed()) + + private class VolumeDialogEventModelProducer( + private val scope: ProducerScope<VolumeDialogEventModel> + ) : VolumeDialogController.Callbacks { + override fun onShowRequested(reason: Int, keyguardLocked: Boolean, lockTaskModeState: Int) { + scope.trySend( + VolumeDialogEventModel.ShowRequested( + reason = reason, + keyguardLocked = keyguardLocked, + lockTaskModeState = lockTaskModeState, + ) + ) + } + + override fun onDismissRequested(reason: Int) { + scope.trySend(VolumeDialogEventModel.DismissRequested(reason)) + } + + override fun onStateChanged(state: VolumeDialogController.State?) { + if (state != null) { + scope.trySend(VolumeDialogEventModel.StateChanged(VolumeDialogStateModel(state))) + } + } + + override fun onLayoutDirectionChanged(layoutDirection: Int) { + scope.trySend(VolumeDialogEventModel.LayoutDirectionChanged(layoutDirection)) + } + + // Configuration change is never emitted by the VolumeDialogControllerImpl now. + override fun onConfigurationChanged() = Unit + + override fun onShowVibrateHint() { + scope.trySend(VolumeDialogEventModel.ShowVibrateHint) + } + + override fun onShowSilentHint() { + scope.trySend(VolumeDialogEventModel.ShowSilentHint) + } + + override fun onScreenOff() { + scope.trySend(VolumeDialogEventModel.ScreenOff) + } + + override fun onShowSafetyWarning(flags: Int) { + scope.trySend(VolumeDialogEventModel.ShowSafetyWarning(flags)) + } + + override fun onAccessibilityModeChanged(showA11yStream: Boolean) { + scope.trySend(VolumeDialogEventModel.AccessibilityModeChanged(showA11yStream)) + } + + // Captions button is remove from the Volume Dialog + override fun onCaptionComponentStateChanged( + isComponentEnabled: Boolean, + fromTooltip: Boolean, + ) = Unit + + // Captions button is remove from the Volume Dialog + override fun onCaptionEnabledStateChanged(isEnabled: Boolean, checkBeforeSwitch: Boolean) = + Unit + + override fun onShowCsdWarning(csdWarning: Int, durationMs: Int) { + scope.trySend( + VolumeDialogEventModel.ShowCsdWarning( + csdWarning = csdWarning, + durationMs = durationMs, + ) + ) + } + + override fun onVolumeChangedFromKey() { + scope.trySend(VolumeDialogEventModel.VolumeChangedFromKey) + } + } +} 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 new file mode 100644 index 000000000000..dd511088cb06 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.dialog.domain.interactor + +import com.android.systemui.plugins.VolumeDialogController +import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog +import com.android.systemui.volume.dialog.domain.model.VolumeDialogEventModel +import com.android.systemui.volume.dialog.domain.model.VolumeDialogStateModel +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.stateIn + +/** + * Exposes [VolumeDialogController.getState] in the [volumeDialogState]. + * + * @see [VolumeDialogController] + */ +@VolumeDialog +class VolumeDialogStateInteractor +@Inject +constructor( + volumeDialogCallbacksInteractor: VolumeDialogCallbacksInteractor, + private val volumeDialogController: VolumeDialogController, + @VolumeDialog private val coroutineScope: CoroutineScope, +) { + + val volumeDialogState: Flow<VolumeDialogStateModel> = + volumeDialogCallbacksInteractor.event + .onStart { volumeDialogController.getState() } + .filterIsInstance(VolumeDialogEventModel.StateChanged::class) + .map { it.state } + .stateIn(scope = coroutineScope, started = SharingStarted.Eagerly, initialValue = null) + .filterNotNull() +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogEventModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogEventModel.kt new file mode 100644 index 000000000000..ca0310ef8588 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogEventModel.kt @@ -0,0 +1,54 @@ +/* + * 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 + +import android.media.AudioManager + +/** + * Models VolumeDialogController callback events. + * + * @see VolumeDialogController.Callbacks + */ +sealed interface VolumeDialogEventModel { + + data class ShowRequested( + val reason: Int, + val keyguardLocked: Boolean, + val lockTaskModeState: Int, + ) : VolumeDialogEventModel + + data class DismissRequested(val reason: Int) : VolumeDialogEventModel + + data class StateChanged(val state: VolumeDialogStateModel) : VolumeDialogEventModel + + data class LayoutDirectionChanged(val layoutDirection: Int) : VolumeDialogEventModel + + data object ShowVibrateHint : VolumeDialogEventModel + + data object ShowSilentHint : VolumeDialogEventModel + + data object ScreenOff : VolumeDialogEventModel + + data class ShowSafetyWarning(val flags: Int) : VolumeDialogEventModel + + data class AccessibilityModeChanged(val showA11yStream: Boolean) : VolumeDialogEventModel + + data class ShowCsdWarning(@AudioManager.CsdWarning val csdWarning: Int, val durationMs: Int) : + VolumeDialogEventModel + + data object VolumeChangedFromKey : VolumeDialogEventModel +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStateModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStateModel.kt new file mode 100644 index 000000000000..f1443e36d019 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStateModel.kt @@ -0,0 +1,67 @@ +/* + * 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 + +import android.content.ComponentName +import android.util.SparseArray +import androidx.core.util.keyIterator +import com.android.systemui.plugins.VolumeDialogController + +/** Models a state of the Volume Dialog. */ +data class VolumeDialogStateModel( + val states: Map<Int, VolumeDialogStreamStateModel>, + val ringerModeInternal: Int = 0, + val ringerModeExternal: Int = 0, + val zenMode: Int = 0, + val effectsSuppressor: ComponentName? = null, + val effectsSuppressorName: String? = null, + val activeStream: Int = NO_ACTIVE_STREAM, + val disallowAlarms: Boolean = false, + val disallowMedia: Boolean = false, + val disallowSystem: Boolean = false, + val disallowRinger: Boolean = false, +) { + + constructor( + legacyState: VolumeDialogController.State + ) : this( + states = legacyState.states.mapToMap { VolumeDialogStreamStateModel(it) }, + ringerModeInternal = legacyState.ringerModeInternal, + ringerModeExternal = legacyState.ringerModeExternal, + zenMode = legacyState.zenMode, + effectsSuppressor = legacyState.effectsSuppressor, + effectsSuppressorName = legacyState.effectsSuppressorName, + activeStream = legacyState.activeStream, + disallowAlarms = legacyState.disallowAlarms, + disallowMedia = legacyState.disallowMedia, + disallowSystem = legacyState.disallowSystem, + disallowRinger = legacyState.disallowRinger, + ) + + companion object { + const val NO_ACTIVE_STREAM: Int = -1 + } +} + +private fun <INPUT, OUTPUT> SparseArray<INPUT>.mapToMap(map: (INPUT) -> OUTPUT): Map<Int, OUTPUT> { + val resultMap = mutableMapOf<Int, OUTPUT>() + for (key in keyIterator()) { + val mappedValue: OUTPUT = map(get(key)!!) + resultMap[key] = mappedValue + } + return resultMap +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStreamStateModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStreamStateModel.kt new file mode 100644 index 000000000000..a9d367da41e4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStreamStateModel.kt @@ -0,0 +1,47 @@ +/* + * 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 + +import android.annotation.IntegerRes +import com.android.systemui.plugins.VolumeDialogController + +/** Models a state of an audio stream of the Volume Dialog. */ +data class VolumeDialogStreamStateModel( + val isDynamic: Boolean = false, + val level: Int = 0, + val levelMin: Int = 0, + val levelMax: Int = 0, + val muted: Boolean = false, + val muteSupported: Boolean = false, + @IntegerRes val name: Int = 0, + val remoteLabel: String? = null, + val routedToBluetooth: Boolean = false, +) { + constructor( + legacyState: VolumeDialogController.StreamState + ) : this( + isDynamic = legacyState.dynamic, + level = legacyState.level, + levelMin = legacyState.levelMin, + levelMax = legacyState.levelMax, + muted = legacyState.muted, + muteSupported = legacyState.muteSupported, + name = legacyState.name, + remoteLabel = legacyState.remoteLabel, + routedToBluetooth = legacyState.routedToBluetooth, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt new file mode 100644 index 000000000000..700225d521c8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt @@ -0,0 +1,45 @@ +/* + * 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.view.View +import com.android.systemui.lifecycle.WindowLifecycleState +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.lifecycle.setSnapshotBinding +import com.android.systemui.lifecycle.viewModel +import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogViewModel +import javax.inject.Inject +import kotlinx.coroutines.awaitCancellation + +class VolumeDialogViewBinder +@Inject +constructor(private val volumeDialogViewModelFactory: VolumeDialogViewModel.Factory) { + + suspend fun bind(view: View) { + view.repeatWhenAttached { + view.viewModel( + traceName = "VolumeDialogViewBinder", + minWindowLifecycleState = WindowLifecycleState.ATTACHED, + factory = { volumeDialogViewModelFactory.create() }, + ) { viewModel -> + view.setSnapshotBinding {} + + awaitCancellation() + } + } + } +} 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 new file mode 100644 index 000000000000..f9e91aee4922 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.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.volume.dialog.ui.viewmodel + +import com.android.systemui.lifecycle.ExclusiveActivatable +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +class VolumeDialogViewModel @AssistedInject constructor() : ExclusiveActivatable() { + + override suspend fun onActivated(): Nothing { + TODO("Not yet implemented") + } + + @AssistedFactory + interface Factory { + fun create(): VolumeDialogViewModel + } +} |