1 /* <lambda>null2 * 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.accessibility.data.repository 18 19 import android.hardware.display.ColorDisplayManager 20 import android.hardware.display.NightDisplayListener 21 import android.os.UserHandle 22 import android.provider.Settings 23 import com.android.systemui.accessibility.data.model.NightDisplayChangeEvent 24 import com.android.systemui.accessibility.data.model.NightDisplayState 25 import com.android.systemui.dagger.NightDisplayListenerModule 26 import com.android.systemui.dagger.qualifiers.Application 27 import com.android.systemui.dagger.qualifiers.Background 28 import com.android.systemui.statusbar.policy.LocationController 29 import com.android.systemui.user.utils.UserScopedService 30 import com.android.systemui.util.kotlin.isLocationEnabledFlow 31 import com.android.systemui.util.settings.GlobalSettings 32 import com.android.systemui.util.settings.SecureSettings 33 import com.android.systemui.util.settings.SettingsProxyExt.observerFlow 34 import java.time.LocalTime 35 import javax.inject.Inject 36 import kotlin.coroutines.CoroutineContext 37 import kotlinx.coroutines.CoroutineScope 38 import kotlinx.coroutines.channels.awaitClose 39 import kotlinx.coroutines.flow.Flow 40 import kotlinx.coroutines.flow.SharingStarted 41 import kotlinx.coroutines.flow.callbackFlow 42 import kotlinx.coroutines.flow.combine 43 import kotlinx.coroutines.flow.conflate 44 import kotlinx.coroutines.flow.distinctUntilChanged 45 import kotlinx.coroutines.flow.flowOf 46 import kotlinx.coroutines.flow.flowOn 47 import kotlinx.coroutines.flow.map 48 import kotlinx.coroutines.flow.merge 49 import kotlinx.coroutines.flow.onStart 50 import kotlinx.coroutines.flow.scan 51 import kotlinx.coroutines.flow.stateIn 52 import kotlinx.coroutines.withContext 53 54 class NightDisplayRepository 55 @Inject 56 constructor( 57 @Background private val bgCoroutineContext: CoroutineContext, 58 @Application private val scope: CoroutineScope, 59 private val globalSettings: GlobalSettings, 60 private val secureSettings: SecureSettings, 61 private val nightDisplayListenerBuilder: NightDisplayListenerModule.Builder, 62 private val colorDisplayManagerUserScopedService: UserScopedService<ColorDisplayManager>, 63 private val locationController: LocationController, 64 ) { 65 private val stateFlowUserMap = mutableMapOf<Int, Flow<NightDisplayState>>() 66 67 fun nightDisplayState(user: UserHandle): Flow<NightDisplayState> = 68 stateFlowUserMap.getOrPut(user.identifier) { 69 return merge( 70 colorDisplayManagerChangeEventFlow(user), 71 shouldForceAutoMode(user).map { 72 NightDisplayChangeEvent.OnForceAutoModeChanged(it) 73 }, 74 locationController.isLocationEnabledFlow().map { 75 NightDisplayChangeEvent.OnLocationEnabledChanged(it) 76 } 77 ) 78 .scan(initialState(user)) { state, event -> 79 when (event) { 80 is NightDisplayChangeEvent.OnActivatedChanged -> 81 state.copy(isActivated = event.isActivated) 82 is NightDisplayChangeEvent.OnAutoModeChanged -> 83 state.copy(autoMode = event.autoMode) 84 is NightDisplayChangeEvent.OnCustomStartTimeChanged -> 85 state.copy(startTime = event.startTime) 86 is NightDisplayChangeEvent.OnCustomEndTimeChanged -> 87 state.copy(endTime = event.endTime) 88 is NightDisplayChangeEvent.OnForceAutoModeChanged -> 89 state.copy(shouldForceAutoMode = event.shouldForceAutoMode) 90 is NightDisplayChangeEvent.OnLocationEnabledChanged -> 91 state.copy(locationEnabled = event.locationEnabled) 92 } 93 } 94 .conflate() 95 .onStart { emit(initialState(user)) } 96 .flowOn(bgCoroutineContext) 97 .stateIn(scope, SharingStarted.WhileSubscribed(), NightDisplayState()) 98 } 99 100 /** Track changes in night display enabled state and its auto mode */ 101 private fun colorDisplayManagerChangeEventFlow(user: UserHandle) = callbackFlow { 102 val nightDisplayListener = nightDisplayListenerBuilder.setUser(user.identifier).build() 103 val nightDisplayCallback = 104 object : NightDisplayListener.Callback { 105 override fun onActivated(activated: Boolean) { 106 trySend(NightDisplayChangeEvent.OnActivatedChanged(activated)) 107 } 108 109 override fun onAutoModeChanged(autoMode: Int) { 110 trySend(NightDisplayChangeEvent.OnAutoModeChanged(autoMode)) 111 } 112 113 override fun onCustomStartTimeChanged(startTime: LocalTime?) { 114 trySend(NightDisplayChangeEvent.OnCustomStartTimeChanged(startTime)) 115 } 116 117 override fun onCustomEndTimeChanged(endTime: LocalTime?) { 118 trySend(NightDisplayChangeEvent.OnCustomEndTimeChanged(endTime)) 119 } 120 } 121 nightDisplayListener.setCallback(nightDisplayCallback) 122 awaitClose { nightDisplayListener.setCallback(null) } 123 } 124 125 /** @return true when the option to force auto mode is available and a value has not been set */ 126 private fun shouldForceAutoMode(userHandle: UserHandle): Flow<Boolean> = 127 combine(isForceAutoModeAvailable, isDisplayAutoModeRawNotSet(userHandle)) { 128 isForceAutoModeAvailable, 129 isDisplayAutoModeRawNotSet, 130 -> 131 isForceAutoModeAvailable && isDisplayAutoModeRawNotSet 132 } 133 134 private val isForceAutoModeAvailable: Flow<Boolean> = 135 globalSettings 136 .observerFlow(IS_FORCE_AUTO_MODE_AVAILABLE_SETTING_NAME) 137 .onStart { emit(Unit) } 138 .map { 139 globalSettings.getString(IS_FORCE_AUTO_MODE_AVAILABLE_SETTING_NAME) == 140 NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE 141 } 142 .distinctUntilChanged() 143 144 /** Inspired by [ColorDisplayService.getNightDisplayAutoModeRawInternal] */ 145 private fun isDisplayAutoModeRawNotSet(userHandle: UserHandle): Flow<Boolean> = 146 if (userHandle.identifier == UserHandle.USER_NULL) { 147 flowOf(IS_AUTO_MODE_RAW_NOT_SET_DEFAULT) 148 } else { 149 secureSettings 150 .observerFlow(userHandle.identifier, DISPLAY_AUTO_MODE_RAW_SETTING_NAME) 151 .onStart { emit(Unit) } 152 .map { isNightDisplayAutoModeRawSettingNotSet(userHandle.identifier) } 153 } 154 .distinctUntilChanged() 155 156 suspend fun setNightDisplayAutoMode(autoMode: Int, user: UserHandle) { 157 withContext(bgCoroutineContext) { 158 colorDisplayManagerUserScopedService.forUser(user).nightDisplayAutoMode = autoMode 159 } 160 } 161 162 suspend fun setNightDisplayActivated(activated: Boolean, user: UserHandle) { 163 withContext(bgCoroutineContext) { 164 colorDisplayManagerUserScopedService.forUser(user).isNightDisplayActivated = activated 165 } 166 } 167 168 private fun initialState(user: UserHandle): NightDisplayState { 169 val colorDisplayManager = colorDisplayManagerUserScopedService.forUser(user) 170 return NightDisplayState( 171 colorDisplayManager.nightDisplayAutoMode, 172 colorDisplayManager.isNightDisplayActivated, 173 colorDisplayManager.nightDisplayCustomStartTime, 174 colorDisplayManager.nightDisplayCustomEndTime, 175 globalSettings.getString(IS_FORCE_AUTO_MODE_AVAILABLE_SETTING_NAME) == 176 NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE && 177 isNightDisplayAutoModeRawSettingNotSet(user.identifier), 178 locationController.isLocationEnabled, 179 ) 180 } 181 182 private fun isNightDisplayAutoModeRawSettingNotSet(userId: Int): Boolean { 183 return secureSettings.getIntForUser( 184 DISPLAY_AUTO_MODE_RAW_SETTING_NAME, 185 NIGHT_DISPLAY_AUTO_MODE_RAW_NOT_SET, 186 userId 187 ) == NIGHT_DISPLAY_AUTO_MODE_RAW_NOT_SET 188 } 189 190 private companion object { 191 const val NIGHT_DISPLAY_AUTO_MODE_RAW_NOT_SET = -1 192 const val NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE = "1" 193 const val IS_AUTO_MODE_RAW_NOT_SET_DEFAULT = true 194 const val IS_FORCE_AUTO_MODE_AVAILABLE_SETTING_NAME = 195 Settings.Global.NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE 196 const val DISPLAY_AUTO_MODE_RAW_SETTING_NAME = Settings.Secure.NIGHT_DISPLAY_AUTO_MODE 197 } 198 } 199