1 /* <lambda>null2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.volume.ui.navigation 18 19 import android.app.Dialog 20 import android.content.Intent 21 import android.provider.Settings 22 import androidx.compose.runtime.LaunchedEffect 23 import androidx.compose.runtime.remember 24 import androidx.compose.runtime.rememberCoroutineScope 25 import com.android.internal.logging.UiEventLogger 26 import com.android.systemui.dagger.SysUISingleton 27 import com.android.systemui.dagger.qualifiers.Application 28 import com.android.systemui.dagger.qualifiers.Main 29 import com.android.systemui.plugins.ActivityStarter 30 import com.android.systemui.statusbar.phone.SystemUIDialogFactory 31 import com.android.systemui.statusbar.phone.createBottomSheet 32 import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow 33 import com.android.systemui.volume.VolumePanelFactory 34 import com.android.systemui.volume.domain.model.VolumePanelRoute 35 import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractor 36 import com.android.systemui.volume.panel.ui.VolumePanelUiEvent 37 import com.android.systemui.volume.panel.ui.composable.VolumePanelRoot 38 import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel 39 import javax.inject.Inject 40 import kotlin.coroutines.CoroutineContext 41 import kotlinx.coroutines.CoroutineScope 42 import kotlinx.coroutines.ExperimentalCoroutinesApi 43 import kotlinx.coroutines.channels.awaitClose 44 import kotlinx.coroutines.flow.distinctUntilChanged 45 import kotlinx.coroutines.flow.emptyFlow 46 import kotlinx.coroutines.flow.flatMapLatest 47 import kotlinx.coroutines.flow.flowOn 48 import kotlinx.coroutines.flow.launchIn 49 import kotlinx.coroutines.flow.map 50 51 @OptIn(ExperimentalCoroutinesApi::class) 52 @SysUISingleton 53 class VolumeNavigator 54 @Inject 55 constructor( 56 @Application private val applicationScope: CoroutineScope, 57 @Main private val mainContext: CoroutineContext, 58 private val volumePanelFactory: VolumePanelFactory, 59 private val activityStarter: ActivityStarter, 60 private val viewModelFactory: VolumePanelViewModel.Factory, 61 private val dialogFactory: SystemUIDialogFactory, 62 private val uiEventLogger: UiEventLogger, 63 private val volumePanelGlobalStateInteractor: VolumePanelGlobalStateInteractor, 64 ) { 65 66 init { 67 volumePanelGlobalStateInteractor.globalState 68 .map { it.isVisible } 69 .distinctUntilChanged() 70 .flatMapLatest { isVisible -> 71 if (isVisible) { 72 conflatedCallbackFlow<Unit> { 73 val dialog = createNewVolumePanelDialog() 74 uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_SHOWN) 75 dialog.show() 76 awaitClose { dialog.dismiss() } 77 } 78 .flowOn(mainContext) 79 } else { 80 emptyFlow() 81 } 82 } 83 .launchIn(applicationScope) 84 } 85 86 fun openVolumePanel(route: VolumePanelRoute) { 87 when (route) { 88 VolumePanelRoute.COMPOSE_VOLUME_PANEL -> showNewVolumePanel() 89 VolumePanelRoute.SETTINGS_VOLUME_PANEL -> 90 activityStarter.startActivity( 91 /* intent= */ Intent(Settings.Panel.ACTION_VOLUME), 92 /* dismissShade= */ true 93 ) 94 VolumePanelRoute.SYSTEM_UI_VOLUME_PANEL -> 95 volumePanelFactory.create(aboveStatusBar = true, view = null) 96 } 97 } 98 99 private fun showNewVolumePanel() { 100 activityStarter.dismissKeyguardThenExecute( 101 /* action = */ { 102 volumePanelGlobalStateInteractor.setVisible(true) 103 false 104 }, 105 /* cancel = */ {}, 106 /* afterKeyguardGone = */ true, 107 ) 108 } 109 110 private fun createNewVolumePanelDialog(): Dialog { 111 return dialogFactory.createBottomSheet( 112 content = { dialog -> 113 LaunchedEffect(dialog) { 114 dialog.setOnDismissListener { 115 uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_GONE) 116 volumePanelGlobalStateInteractor.setVisible(false) 117 } 118 } 119 120 val coroutineScope = rememberCoroutineScope() 121 VolumePanelRoot( 122 remember(coroutineScope) { viewModelFactory.create(coroutineScope) } 123 ) 124 }, 125 ) 126 } 127 } 128