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

Commit 3fbbb096 authored by Anton Potapov's avatar Anton Potapov
Browse files

Add interactors to wrap VolumeDialogController callbacks into flows and make...

Add interactors to wrap VolumeDialogController callbacks into flows and make them easier to integrate into the new architecture.

Flag: com.android.systemui.volume_redesign
Test: passes presubmits
Bug: 369994090
Change-Id: Ie34cc7c5e023da560ba4ce5ae77d4b9d40be5c7c
parent 05c7ed41
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -105,7 +105,7 @@ public interface VolumeModule {
    /**  */
    @Provides
    static VolumeDialog provideVolumeDialog(
            Lazy<VolumeDialogPlugin> newVolumeDialogProvider,
            Lazy<VolumeDialogPlugin> volumeDialogProvider,
            Context context,
            VolumeDialogController volumeDialogController,
            AccessibilityManagerWrapper accessibilityManagerWrapper,
@@ -124,7 +124,7 @@ public interface VolumeModule {
            SystemClock systemClock,
            VolumeDialogInteractor interactor) {
        if (Flags.volumeRedesign()) {
            return newVolumeDialogProvider.get();
            return volumeDialogProvider.get();
        } else {
            VolumeDialogImpl impl = new VolumeDialogImpl(
                    context,
+34 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.volume.dialog

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)
    }
}
+67 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.volume.dialog

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()
    }
}
+135 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.volume.dialog.domain.interactor

import android.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)
        }
    }
}
+54 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.volume.dialog.domain.interactor

import 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()
}
Loading