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.systemui.brightness.shared.model.BrightnessLog 23 import com.android.systemui.brightness.shared.model.LinearBrightness 24 import com.android.systemui.brightness.shared.model.formatBrightness 25 import com.android.systemui.brightness.shared.model.logDiffForTable 26 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow 27 import com.android.systemui.dagger.SysUISingleton 28 import com.android.systemui.dagger.qualifiers.Application 29 import com.android.systemui.dagger.qualifiers.Background 30 import com.android.systemui.dagger.qualifiers.DisplayId 31 import com.android.systemui.log.LogBuffer 32 import com.android.systemui.log.core.LogLevel 33 import com.android.systemui.log.table.TableLogBuffer 34 import javax.inject.Inject 35 import kotlin.coroutines.CoroutineContext 36 import kotlinx.coroutines.CoroutineScope 37 import kotlinx.coroutines.channels.Channel 38 import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED 39 import kotlinx.coroutines.channels.awaitClose 40 import kotlinx.coroutines.flow.Flow 41 import kotlinx.coroutines.flow.SharedFlow 42 import kotlinx.coroutines.flow.SharingStarted 43 import kotlinx.coroutines.flow.StateFlow 44 import kotlinx.coroutines.flow.filterNotNull 45 import kotlinx.coroutines.flow.flowOn 46 import kotlinx.coroutines.flow.map 47 import kotlinx.coroutines.flow.onStart 48 import kotlinx.coroutines.flow.stateIn 49 import kotlinx.coroutines.launch 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 /** Gets the current values for min and max brightness */ getMinMaxLinearBrightnessnull68 suspend fun getMinMaxLinearBrightness(): Pair<LinearBrightness, LinearBrightness> 69 70 /** 71 * Sets the temporary value for the brightness. This should change the display brightness but 72 * not trigger any updates. 73 */ 74 fun setTemporaryBrightness(value: LinearBrightness) 75 76 /** Sets the brightness definitively. */ 77 fun setBrightness(value: LinearBrightness) 78 } 79 80 @SuppressLint("MissingPermission") 81 @SysUISingleton 82 class ScreenBrightnessDisplayManagerRepository 83 @Inject 84 constructor( 85 @DisplayId private val displayId: Int, 86 private val displayManager: DisplayManager, 87 @BrightnessLog private val logBuffer: LogBuffer, 88 @BrightnessLog private val tableBuffer: TableLogBuffer, 89 @Application private val applicationScope: CoroutineScope, 90 @Background private val backgroundContext: CoroutineContext, 91 ) : ScreenBrightnessRepository { 92 93 private val apiQueue = 94 Channel<SetBrightnessMethod>( 95 capacity = UNLIMITED, 96 ) 97 98 init { 99 applicationScope.launch(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 DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS, 136 ) 137 138 awaitClose { displayManager.unregisterDisplayListener(listener) } 139 } 140 .onStart { emit(Unit) } 141 .map { brightnessInfoValue() } 142 .flowOn(backgroundContext) 143 .stateIn( 144 applicationScope, 145 SharingStarted.WhileSubscribed(replayExpirationMillis = 0L), 146 null, 147 ) 148 149 private suspend fun brightnessInfoValue(): BrightnessInfo? { 150 return withContext(backgroundContext) { 151 displayManager.getDisplay(displayId).brightnessInfo 152 } 153 } 154 155 override val minLinearBrightness = 156 brightnessInfo 157 .filterNotNull() 158 .map { LinearBrightness(it.brightnessMinimum) } 159 .logDiffForTable(tableBuffer, TABLE_PREFIX_LINEAR, TABLE_COLUMN_MIN, null) 160 .stateIn(applicationScope, SharingStarted.WhileSubscribed(), LinearBrightness(0f)) 161 162 override val maxLinearBrightness: SharedFlow<LinearBrightness> = 163 brightnessInfo 164 .filterNotNull() 165 .map { LinearBrightness(it.brightnessMaximum) } 166 .logDiffForTable(tableBuffer, TABLE_PREFIX_LINEAR, TABLE_COLUMN_MAX, null) 167 .stateIn(applicationScope, SharingStarted.WhileSubscribed(), LinearBrightness(1f)) 168 169 override suspend fun getMinMaxLinearBrightness(): Pair<LinearBrightness, LinearBrightness> { 170 val brightnessInfo = brightnessInfo.value ?: brightnessInfoValue() 171 val min = brightnessInfo?.brightnessMinimum ?: 0f 172 val max = brightnessInfo?.brightnessMaximum ?: 1f 173 return LinearBrightness(min) to LinearBrightness(max) 174 } 175 176 override val linearBrightness = 177 brightnessInfo 178 .filterNotNull() 179 .map { LinearBrightness(it.brightness) } 180 .logDiffForTable(tableBuffer, TABLE_PREFIX_LINEAR, TABLE_COLUMN_BRIGHTNESS, null) 181 .stateIn(applicationScope, SharingStarted.WhileSubscribed(), LinearBrightness(0f)) 182 183 override fun setTemporaryBrightness(value: LinearBrightness) { 184 apiQueue.trySend(SetBrightnessMethod.Temporary(value)) 185 } 186 187 override fun setBrightness(value: LinearBrightness) { 188 apiQueue.trySend(SetBrightnessMethod.Permanent(value)) 189 } 190 191 private sealed interface SetBrightnessMethod { 192 val value: LinearBrightness 193 @JvmInline 194 value class Temporary(override val value: LinearBrightness) : SetBrightnessMethod 195 @JvmInline 196 value class Permanent(override val value: LinearBrightness) : SetBrightnessMethod 197 } 198 199 private fun logBrightnessChange(permanent: Boolean, value: Float) { 200 logBuffer.log( 201 LOG_BUFFER_BRIGHTNESS_CHANGE_TAG, 202 if (permanent) LogLevel.DEBUG else LogLevel.VERBOSE, 203 { str1 = value.formatBrightness() }, 204 { "Change requested: $str1" } 205 ) 206 } 207 208 private companion object { 209 const val TABLE_COLUMN_BRIGHTNESS = "brightness" 210 const val TABLE_COLUMN_MIN = "min" 211 const val TABLE_COLUMN_MAX = "max" 212 const val TABLE_PREFIX_LINEAR = "linear" 213 const val LOG_BUFFER_BRIGHTNESS_CHANGE_TAG = "BrightnessChange" 214 } 215 } 216