1 /* 2 * 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.qs.panels.ui.viewmodel 18 19 import android.content.res.Configuration 20 import android.content.res.Resources 21 import androidx.compose.runtime.getValue 22 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor 23 import com.android.systemui.lifecycle.ExclusiveActivatable 24 import com.android.systemui.lifecycle.Hydrator 25 import com.android.systemui.media.controls.ui.controller.MediaHostStatesManager 26 import com.android.systemui.media.controls.ui.controller.MediaLocation 27 import com.android.systemui.media.controls.ui.view.MediaHostState 28 import com.android.systemui.qs.composefragment.dagger.QSFragmentComposeModule 29 import com.android.systemui.shade.ShadeDisplayAware 30 import com.android.systemui.shade.domain.interactor.ShadeModeInteractor 31 import com.android.systemui.shade.shared.model.ShadeMode 32 import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow 33 import dagger.assisted.Assisted 34 import dagger.assisted.AssistedFactory 35 import dagger.assisted.AssistedInject 36 import javax.inject.Named 37 import kotlinx.coroutines.channels.awaitClose 38 import kotlinx.coroutines.flow.map 39 import kotlinx.coroutines.flow.onStart 40 41 /** 42 * Indicates whether a particular UMO in [LOCATION_QQS] or [LOCATION_QS] should currently show in a 43 * row with the tiles, based on its visibility and device configuration. If the player is not 44 * visible, it will never indicate that media should show in row. 45 */ 46 class MediaInRowInLandscapeViewModel 47 @AssistedInject 48 constructor( 49 @ShadeDisplayAware resources: Resources, 50 configurationInteractor: ConfigurationInteractor, 51 shadeModeInteractor: ShadeModeInteractor, 52 private val mediaHostStatesManager: MediaHostStatesManager, 53 @Named(QSFragmentComposeModule.QS_USING_MEDIA_PLAYER) private val usingMedia: Boolean, 54 @Assisted @MediaLocation private val inLocation: Int, 55 ) : ExclusiveActivatable() { 56 57 private val hydrator = Hydrator("MediaInRowInLanscapeViewModel - $inLocation") 58 59 val shouldMediaShowInRow: Boolean 60 get() = usingMedia && inSingleShade && isLandscapeAndLong && isMediaVisible 61 62 private val inSingleShade: Boolean by 63 hydrator.hydratedStateOf( 64 traceName = "inSingleShade", 65 initialValue = shadeModeInteractor.shadeMode.value == ShadeMode.Single, <lambda>null66 source = shadeModeInteractor.shadeMode.map { it == ShadeMode.Single }, 67 ) 68 69 private val isLandscapeAndLong: Boolean by 70 hydrator.hydratedStateOf( 71 traceName = "isLandscapeAndLong", 72 initialValue = resources.configuration.isLandscapeAndLong, <lambda>null73 source = configurationInteractor.configurationValues.map { it.isLandscapeAndLong }, 74 ) 75 76 private val isMediaVisible by 77 hydrator.hydratedStateOf( 78 traceName = "isMediaVisible", 79 initialValue = false, 80 source = <lambda>null81 conflatedCallbackFlow { 82 val callback = 83 object : MediaHostStatesManager.Callback { 84 override fun onHostStateChanged( 85 location: Int, 86 mediaHostState: MediaHostState, 87 ) { 88 if (location == inLocation) { 89 trySend(mediaHostState.visible) 90 } 91 } 92 } 93 mediaHostStatesManager.addCallback(callback) 94 95 awaitClose { mediaHostStatesManager.removeCallback(callback) } 96 } <lambda>null97 .onStart { 98 emit( 99 mediaHostStatesManager.mediaHostStates.get(inLocation)?.visible ?: false 100 ) 101 }, 102 ) 103 onActivatednull104 override suspend fun onActivated(): Nothing { 105 hydrator.activate() 106 } 107 108 @AssistedFactory 109 interface Factory { createnull110 fun create(@MediaLocation inLocation: Int): MediaInRowInLandscapeViewModel 111 } 112 } 113 114 private val Configuration.isLandscapeAndLong: Boolean 115 get() = 116 orientation == Configuration.ORIENTATION_LANDSCAPE && 117 (screenLayout and Configuration.SCREENLAYOUT_LONG_MASK) == 118 Configuration.SCREENLAYOUT_LONG_YES 119