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.shade.domain.interactor 18 19 import android.provider.Settings 20 import androidx.annotation.FloatRange 21 import com.android.systemui.dagger.qualifiers.Application 22 import com.android.systemui.log.table.TableLogBuffer 23 import com.android.systemui.log.table.logDiffsForTable 24 import com.android.systemui.scene.domain.SceneFrameworkTableLog 25 import com.android.systemui.scene.shared.flag.SceneContainerFlag 26 import com.android.systemui.shade.data.repository.ShadeRepository 27 import com.android.systemui.shade.shared.model.ShadeMode 28 import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository 29 import javax.inject.Inject 30 import kotlinx.coroutines.CoroutineScope 31 import kotlinx.coroutines.flow.Flow 32 import kotlinx.coroutines.flow.MutableStateFlow 33 import kotlinx.coroutines.flow.SharingStarted 34 import kotlinx.coroutines.flow.StateFlow 35 import kotlinx.coroutines.flow.combine 36 import kotlinx.coroutines.flow.flowOf 37 import kotlinx.coroutines.flow.stateIn 38 39 /** 40 * Defines interface for classes that can provide state and business logic related to the mode of 41 * the shade. 42 */ 43 interface ShadeModeInteractor { 44 45 /** 46 * The version of the shade layout to use. 47 * 48 * Note: Most likely, you want to read [isShadeLayoutWide] instead of this. 49 */ 50 val shadeMode: StateFlow<ShadeMode> 51 52 /** 53 * Whether the shade layout should be wide (true) or narrow (false). 54 * 55 * In a wide layout, notifications and quick settings each take up only half the screen width 56 * (whether they are shown at the same time or not). In a narrow layout, they can each be as 57 * wide as the entire screen. 58 */ 59 val isShadeLayoutWide: StateFlow<Boolean> 60 61 /** Convenience shortcut for querying whether the current [shadeMode] is [ShadeMode.Dual]. */ 62 val isDualShade: Boolean 63 get() = shadeMode.value is ShadeMode.Dual 64 65 /** Convenience shortcut for querying whether the current [shadeMode] is [ShadeMode.Split]. */ 66 val isSplitShade: Boolean 67 get() = shadeMode.value is ShadeMode.Split 68 69 /** 70 * The fraction between [0..1] (i.e., percentage) of screen width to consider the threshold 71 * between "top-left" and "top-right" for the purposes of dual-shade invocation. 72 * 73 * When the dual-shade is not wide, this always returns 0.5 (the top edge is evenly split). On 74 * wide layouts however, a larger fraction is returned because only the area of the system 75 * status icons is considered top-right. 76 * 77 * Note that this fraction only determines the split between the absolute left and right 78 * directions. In RTL layouts, the "top-start" edge will resolve to "top-right", and "top-end" 79 * will resolve to "top-left". 80 */ getTopEdgeSplitFractionnull81 @FloatRange(from = 0.0, to = 1.0) fun getTopEdgeSplitFraction(): Float 82 } 83 84 class ShadeModeInteractorImpl 85 @Inject 86 constructor( 87 @Application applicationScope: CoroutineScope, 88 private val repository: ShadeRepository, 89 secureSettingsRepository: SecureSettingsRepository, 90 @SceneFrameworkTableLog private val tableLogBuffer: TableLogBuffer, 91 ) : ShadeModeInteractor { 92 93 private val isDualShadeEnabled: Flow<Boolean> = 94 if (SceneContainerFlag.isEnabled) { 95 secureSettingsRepository.boolSetting( 96 Settings.Secure.DUAL_SHADE, 97 defaultValue = DUAL_SHADE_ENABLED_DEFAULT, 98 ) 99 } else { 100 flowOf(false) 101 } 102 103 override val isShadeLayoutWide: StateFlow<Boolean> = repository.isShadeLayoutWide 104 105 private val shadeModeInitialValue: ShadeMode 106 get() = 107 determineShadeMode( 108 isDualShadeEnabled = DUAL_SHADE_ENABLED_DEFAULT, 109 isShadeLayoutWide = repository.isShadeLayoutWide.value, 110 ) 111 112 override val shadeMode: StateFlow<ShadeMode> = 113 combine(isDualShadeEnabled, repository.isShadeLayoutWide, ::determineShadeMode) 114 .logDiffsForTable(tableLogBuffer = tableLogBuffer, initialValue = shadeModeInitialValue) 115 .stateIn(applicationScope, SharingStarted.Eagerly, initialValue = shadeModeInitialValue) 116 117 @FloatRange(from = 0.0, to = 1.0) override fun getTopEdgeSplitFraction(): Float = 0.5f 118 119 private fun determineShadeMode( 120 isDualShadeEnabled: Boolean, 121 isShadeLayoutWide: Boolean, 122 ): ShadeMode { 123 return when { 124 isDualShadeEnabled -> ShadeMode.Dual 125 isShadeLayoutWide -> ShadeMode.Split 126 else -> ShadeMode.Single 127 } 128 } 129 130 companion object { 131 /* Whether the Dual Shade setting is enabled by default. */ 132 private const val DUAL_SHADE_ENABLED_DEFAULT = false 133 } 134 } 135 136 class ShadeModeInteractorEmptyImpl @Inject constructor() : ShadeModeInteractor { 137 138 override val shadeMode: StateFlow<ShadeMode> = MutableStateFlow(ShadeMode.Single) 139 140 override val isShadeLayoutWide: StateFlow<Boolean> = MutableStateFlow(false) 141 getTopEdgeSplitFractionnull142 override fun getTopEdgeSplitFraction(): Float = 0.5f 143 } 144