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