1 /* 2 * Copyright (C) 2023 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.common.ui.data.repository 18 19 import android.content.Context 20 import android.content.res.Configuration 21 import android.view.DisplayInfo 22 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging 23 import com.android.systemui.common.coroutine.ConflatedCallbackFlow 24 import com.android.systemui.dagger.SysUISingleton 25 import com.android.systemui.dagger.qualifiers.Application 26 import com.android.systemui.statusbar.policy.ConfigurationController 27 import com.android.systemui.util.wrapper.DisplayUtilsWrapper 28 import javax.inject.Inject 29 import kotlinx.coroutines.CoroutineScope 30 import kotlinx.coroutines.ExperimentalCoroutinesApi 31 import kotlinx.coroutines.channels.awaitClose 32 import kotlinx.coroutines.flow.Flow 33 import kotlinx.coroutines.flow.MutableStateFlow 34 import kotlinx.coroutines.flow.SharingStarted 35 import kotlinx.coroutines.flow.StateFlow 36 import kotlinx.coroutines.flow.distinctUntilChanged 37 import kotlinx.coroutines.flow.mapLatest 38 import kotlinx.coroutines.flow.stateIn 39 40 interface ConfigurationRepository { 41 /** Called whenever ui mode, theme or configuration has changed. */ 42 val onAnyConfigurationChange: Flow<Unit> 43 val scaleForResolution: Flow<Float> 44 45 fun getResolutionScale(): Float 46 } 47 48 @ExperimentalCoroutinesApi 49 @SysUISingleton 50 class ConfigurationRepositoryImpl 51 @Inject 52 constructor( 53 private val configurationController: ConfigurationController, 54 private val context: Context, 55 @Application private val scope: CoroutineScope, 56 private val displayUtils: DisplayUtilsWrapper, 57 ) : ConfigurationRepository { 58 private val displayInfo = MutableStateFlow(DisplayInfo()) 59 60 override val onAnyConfigurationChange: Flow<Unit> = 61 ConflatedCallbackFlow.conflatedCallbackFlow { 62 val callback = 63 object : ConfigurationController.ConfigurationListener { 64 override fun onUiModeChanged() { 65 sendUpdate("ConfigurationRepository#onUiModeChanged") 66 } 67 68 override fun onThemeChanged() { 69 sendUpdate("ConfigurationRepository#onThemeChanged") 70 } 71 72 override fun onConfigChanged(newConfig: Configuration) { 73 sendUpdate("ConfigurationRepository#onConfigChanged") 74 } 75 76 fun sendUpdate(reason: String) { 77 trySendWithFailureLogging(Unit, reason) 78 } 79 } 80 configurationController.addCallback(callback) 81 awaitClose { configurationController.removeCallback(callback) } 82 } 83 84 private val configurationChange: Flow<Unit> = 85 ConflatedCallbackFlow.conflatedCallbackFlow { 86 val callback = 87 object : ConfigurationController.ConfigurationListener { 88 override fun onConfigChanged(newConfig: Configuration) { 89 trySendWithFailureLogging(Unit, "ConfigurationRepository#onConfigChanged") 90 } 91 } 92 configurationController.addCallback(callback) 93 awaitClose { configurationController.removeCallback(callback) } 94 } 95 96 override val scaleForResolution: StateFlow<Float> = 97 configurationChange 98 .mapLatest { getResolutionScale() } 99 .distinctUntilChanged() 100 .stateIn(scope, SharingStarted.WhileSubscribed(), getResolutionScale()) 101 102 override fun getResolutionScale(): Float { 103 context.display?.getDisplayInfo(displayInfo.value) 104 val maxDisplayMode = 105 displayUtils.getMaximumResolutionDisplayMode(displayInfo.value.supportedModes) 106 maxDisplayMode?.let { 107 val scaleFactor = 108 displayUtils.getPhysicalPixelDisplaySizeRatio( 109 maxDisplayMode.physicalWidth, 110 maxDisplayMode.physicalHeight, 111 displayInfo.value.naturalWidth, 112 displayInfo.value.naturalHeight 113 ) 114 return if (scaleFactor == Float.POSITIVE_INFINITY) 1f else scaleFactor 115 } 116 return 1f 117 } 118 } 119