• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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