• 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.brightness.data.repository
18 
19 import android.annotation.SuppressLint
20 import android.hardware.display.BrightnessInfo
21 import android.hardware.display.DisplayManager
22 import com.android.app.tracing.coroutines.launchTraced as launch
23 import com.android.systemui.brightness.shared.model.BrightnessLog
24 import com.android.systemui.brightness.shared.model.LinearBrightness
25 import com.android.systemui.brightness.shared.model.formatBrightness
26 import com.android.systemui.brightness.shared.model.logDiffForTable
27 import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
28 import com.android.systemui.dagger.SysUISingleton
29 import com.android.systemui.dagger.qualifiers.Application
30 import com.android.systemui.dagger.qualifiers.Background
31 import com.android.systemui.dagger.qualifiers.DisplayId
32 import com.android.systemui.log.LogBuffer
33 import com.android.systemui.log.core.LogLevel
34 import com.android.systemui.log.table.TableLogBuffer
35 import javax.inject.Inject
36 import kotlin.coroutines.CoroutineContext
37 import kotlinx.coroutines.CoroutineScope
38 import kotlinx.coroutines.channels.Channel
39 import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
40 import kotlinx.coroutines.channels.awaitClose
41 import kotlinx.coroutines.flow.Flow
42 import kotlinx.coroutines.flow.SharedFlow
43 import kotlinx.coroutines.flow.SharingStarted
44 import kotlinx.coroutines.flow.StateFlow
45 import kotlinx.coroutines.flow.filterNotNull
46 import kotlinx.coroutines.flow.flowOn
47 import kotlinx.coroutines.flow.map
48 import kotlinx.coroutines.flow.onStart
49 import kotlinx.coroutines.flow.stateIn
50 import kotlinx.coroutines.withContext
51 
52 /**
53  * Repository for tracking brightness in the current display.
54  *
55  * Values are in a linear space, as used by [DisplayManager].
56  */
57 interface ScreenBrightnessRepository {
58     /** Current brightness as a value between [minLinearBrightness] and [maxLinearBrightness] */
59     val linearBrightness: Flow<LinearBrightness>
60 
61     /** Current minimum value for the brightness */
62     val minLinearBrightness: Flow<LinearBrightness>
63 
64     /** Current maximum value for the brightness */
65     val maxLinearBrightness: Flow<LinearBrightness>
66 
67     /** Whether the current brightness value is overridden by the application window */
68     val isBrightnessOverriddenByWindow: StateFlow<Boolean>
69 
70     /** Gets the current values for min and max brightness */
getMinMaxLinearBrightnessnull71     suspend fun getMinMaxLinearBrightness(): Pair<LinearBrightness, LinearBrightness>
72 
73     /**
74      * Sets the temporary value for the brightness. This should change the display brightness but
75      * not trigger any updates.
76      */
77     fun setTemporaryBrightness(value: LinearBrightness)
78 
79     /** Sets the brightness definitively. */
80     fun setBrightness(value: LinearBrightness)
81 }
82 
83 @SuppressLint("MissingPermission")
84 @SysUISingleton
85 class ScreenBrightnessDisplayManagerRepository
86 @Inject
87 constructor(
88     @DisplayId private val displayId: Int,
89     private val displayManager: DisplayManager,
90     @BrightnessLog private val logBuffer: LogBuffer,
91     @BrightnessLog private val tableBuffer: TableLogBuffer,
92     @Application private val applicationScope: CoroutineScope,
93     @Background private val backgroundContext: CoroutineContext,
94 ) : ScreenBrightnessRepository {
95 
96     private val apiQueue = Channel<SetBrightnessMethod>(capacity = UNLIMITED)
97 
98     init {
99         applicationScope.launch(context = backgroundContext) {
100             for (call in apiQueue) {
101                 val bounds = getMinMaxLinearBrightness()
102                 val value = call.value.clamp(bounds.first, bounds.second).floatValue
103                 when (call) {
104                     is SetBrightnessMethod.Temporary -> {
105                         displayManager.setTemporaryBrightness(displayId, value)
106                     }
107                     is SetBrightnessMethod.Permanent -> {
108                         displayManager.setBrightness(displayId, value)
109                     }
110                 }
111                 logBrightnessChange(call is SetBrightnessMethod.Permanent, value)
112             }
113         }
114     }
115 
116     private val brightnessInfo: StateFlow<BrightnessInfo?> =
117         conflatedCallbackFlow {
118                 val listener =
119                     object : DisplayManager.DisplayListener {
120                         override fun onDisplayAdded(displayId: Int) {}
121 
122                         override fun onDisplayRemoved(displayId: Int) {}
123 
124                         override fun onDisplayChanged(displayId: Int) {
125                             if (
126                                 displayId == this@ScreenBrightnessDisplayManagerRepository.displayId
127                             ) {
128                                 trySend(Unit)
129                             }
130                         }
131                     }
132                 displayManager.registerDisplayListener(
133                     listener,
134                     null,
135                     /* eventFlags */ 0,
136                     DisplayManager.PRIVATE_EVENT_TYPE_DISPLAY_BRIGHTNESS,
137                 )
138 
139                 awaitClose { displayManager.unregisterDisplayListener(listener) }
140             }
141             .onStart { emit(Unit) }
142             .map { brightnessInfoValue() }
143             .flowOn(backgroundContext)
144             .stateIn(
145                 applicationScope,
146                 SharingStarted.WhileSubscribed(replayExpirationMillis = 0L),
147                 null,
148             )
149 
150     private suspend fun brightnessInfoValue(): BrightnessInfo? {
151         return withContext(backgroundContext) {
152             displayManager.getDisplay(displayId).brightnessInfo
153         }
154     }
155 
156     override val minLinearBrightness =
157         brightnessInfo
158             .filterNotNull()
159             .map { LinearBrightness(it.brightnessMinimum) }
160             .logDiffForTable(tableBuffer, TABLE_PREFIX_LINEAR, TABLE_COLUMN_MIN, null)
161             .stateIn(applicationScope, SharingStarted.WhileSubscribed(), LinearBrightness(0f))
162 
163     override val maxLinearBrightness: SharedFlow<LinearBrightness> =
164         brightnessInfo
165             .filterNotNull()
166             .map { LinearBrightness(it.brightnessMaximum) }
167             .logDiffForTable(tableBuffer, TABLE_PREFIX_LINEAR, TABLE_COLUMN_MAX, null)
168             .stateIn(applicationScope, SharingStarted.WhileSubscribed(), LinearBrightness(1f))
169 
170     override suspend fun getMinMaxLinearBrightness(): Pair<LinearBrightness, LinearBrightness> {
171         val brightnessInfo = brightnessInfo.value ?: brightnessInfoValue()
172         val min = brightnessInfo?.brightnessMinimum ?: 0f
173         val max = brightnessInfo?.brightnessMaximum ?: 1f
174         return LinearBrightness(min) to LinearBrightness(max)
175     }
176 
177     override val linearBrightness =
178         brightnessInfo
179             .filterNotNull()
180             .map { LinearBrightness(it.brightness) }
181             .logDiffForTable(tableBuffer, TABLE_PREFIX_LINEAR, TABLE_COLUMN_BRIGHTNESS, null)
182             .stateIn(applicationScope, SharingStarted.WhileSubscribed(), LinearBrightness(0f))
183 
184     override val isBrightnessOverriddenByWindow =
185         brightnessInfo
186             .filterNotNull()
187             .map { it.isBrightnessOverrideByWindow }
188             .stateIn(applicationScope, SharingStarted.WhileSubscribed(), false)
189 
190     override fun setTemporaryBrightness(value: LinearBrightness) {
191         apiQueue.trySend(SetBrightnessMethod.Temporary(value))
192     }
193 
194     override fun setBrightness(value: LinearBrightness) {
195         apiQueue.trySend(SetBrightnessMethod.Permanent(value))
196     }
197 
198     private sealed interface SetBrightnessMethod {
199         val value: LinearBrightness
200 
201         @JvmInline
202         value class Temporary(override val value: LinearBrightness) : SetBrightnessMethod
203 
204         @JvmInline
205         value class Permanent(override val value: LinearBrightness) : SetBrightnessMethod
206     }
207 
208     private fun logBrightnessChange(permanent: Boolean, value: Float) {
209         logBuffer.log(
210             LOG_BUFFER_BRIGHTNESS_CHANGE_TAG,
211             if (permanent) LogLevel.DEBUG else LogLevel.VERBOSE,
212             { str1 = value.formatBrightness() },
213             { "Change requested: $str1" },
214         )
215     }
216 
217     private companion object {
218         const val TABLE_COLUMN_BRIGHTNESS = "brightness"
219         const val TABLE_COLUMN_MIN = "min"
220         const val TABLE_COLUMN_MAX = "max"
221         const val TABLE_PREFIX_LINEAR = "linear"
222         const val LOG_BUFFER_BRIGHTNESS_CHANGE_TAG = "BrightnessChange"
223     }
224 }
225