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