• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 package com.android.systemui.common.ui.data.repository
17 
18 import android.content.Context
19 import android.content.res.Configuration
20 import android.view.Display
21 import android.view.DisplayInfo
22 import androidx.annotation.DimenRes
23 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
24 import com.android.systemui.dagger.SysUISingleton
25 import com.android.systemui.dagger.qualifiers.Application
26 import com.android.systemui.dagger.qualifiers.Main
27 import com.android.systemui.statusbar.policy.ConfigurationController
28 import com.android.systemui.util.wrapper.DisplayUtilsWrapper
29 import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
30 import dagger.Binds
31 import dagger.Module
32 import dagger.Provides
33 import dagger.assisted.Assisted
34 import dagger.assisted.AssistedFactory
35 import dagger.assisted.AssistedInject
36 import kotlinx.coroutines.CoroutineScope
37 import kotlinx.coroutines.channels.awaitClose
38 import kotlinx.coroutines.flow.Flow
39 import kotlinx.coroutines.flow.MutableStateFlow
40 import kotlinx.coroutines.flow.SharingStarted
41 import kotlinx.coroutines.flow.StateFlow
42 import kotlinx.coroutines.flow.distinctUntilChanged
43 import kotlinx.coroutines.flow.mapLatest
44 import kotlinx.coroutines.flow.stateIn
45 
46 interface ConfigurationRepository {
47     /** Called whenever ui mode, theme or configuration has changed. */
48     val onAnyConfigurationChange: Flow<Unit>
49 
50     /** Called whenever the configuration has changed. */
51     val onConfigurationChange: Flow<Unit>
52 
53     val scaleForResolution: StateFlow<Float>
54 
55     val configurationValues: Flow<Configuration>
56 
57     /** Emits the latest display this configuration controller has been moved to. */
58     val onMovedToDisplay: StateFlow<Int>
59 
getResolutionScalenull60     fun getResolutionScale(): Float
61 
62     /** Convenience to context.resources.getDimensionPixelSize() */
63     fun getDimensionPixelSize(id: Int): Int
64 }
65 
66 class ConfigurationRepositoryImpl
67 @AssistedInject
68 constructor(
69     @Assisted private val configurationController: ConfigurationController,
70     @Assisted private val context: Context,
71     @Application private val scope: CoroutineScope,
72     private val displayUtils: DisplayUtilsWrapper,
73 ) : ConfigurationRepository {
74     private val displayInfo = MutableStateFlow(DisplayInfo())
75 
76     override val onAnyConfigurationChange: Flow<Unit> = conflatedCallbackFlow {
77         val callback =
78             object : ConfigurationController.ConfigurationListener {
79                 override fun onUiModeChanged() {
80                     sendUpdate("ConfigurationRepository#onUiModeChanged")
81                 }
82 
83                 override fun onThemeChanged() {
84                     sendUpdate("ConfigurationRepository#onThemeChanged")
85                 }
86 
87                 override fun onConfigChanged(newConfig: Configuration) {
88                     sendUpdate("ConfigurationRepository#onConfigChanged")
89                 }
90 
91                 fun sendUpdate(reason: String) {
92                     trySendWithFailureLogging(Unit, reason)
93                 }
94             }
95         configurationController.addCallback(callback)
96         awaitClose { configurationController.removeCallback(callback) }
97     }
98 
99     override val onConfigurationChange: Flow<Unit> = conflatedCallbackFlow {
100         val callback =
101             object : ConfigurationController.ConfigurationListener {
102                 override fun onConfigChanged(newConfig: Configuration) {
103                     trySendWithFailureLogging(Unit, "ConfigurationRepository#onConfigChanged")
104                 }
105             }
106         configurationController.addCallback(callback)
107         awaitClose { configurationController.removeCallback(callback) }
108     }
109 
110     override val configurationValues: Flow<Configuration> = conflatedCallbackFlow {
111         val callback =
112             object : ConfigurationController.ConfigurationListener {
113                 override fun onConfigChanged(newConfig: Configuration) {
114                     trySend(newConfig)
115                 }
116             }
117 
118         trySend(context.resources.configuration)
119         configurationController.addCallback(callback)
120         awaitClose { configurationController.removeCallback(callback) }
121     }
122     override val onMovedToDisplay: StateFlow<Int> =
123         conflatedCallbackFlow {
124                 val callback =
125                     object : ConfigurationController.ConfigurationListener {
126                         override fun onMovedToDisplay(
127                             newDisplayId: Int,
128                             newConfiguration: Configuration?,
129                         ) {
130                             trySend(newDisplayId)
131                         }
132                     }
133                 configurationController.addCallback(callback)
134                 awaitClose { configurationController.removeCallback(callback) }
135             }
136             .stateIn(scope, SharingStarted.Eagerly, Display.DEFAULT_DISPLAY)
137 
138     override val scaleForResolution: StateFlow<Float> =
139         onConfigurationChange
140             .mapLatest { getResolutionScale() }
141             .distinctUntilChanged()
142             .stateIn(scope, SharingStarted.WhileSubscribed(), getResolutionScale())
143 
144     override fun getResolutionScale(): Float {
145         context.display?.getDisplayInfo(displayInfo.value)
146         val maxDisplayMode =
147             displayUtils.getMaximumResolutionDisplayMode(displayInfo.value.supportedModes)
148         maxDisplayMode?.let {
149             val scaleFactor =
150                 displayUtils.getPhysicalPixelDisplaySizeRatio(
151                     maxDisplayMode.physicalWidth,
152                     maxDisplayMode.physicalHeight,
153                     displayInfo.value.naturalWidth,
154                     displayInfo.value.naturalHeight,
155                 )
156             return if (scaleFactor == Float.POSITIVE_INFINITY) 1f else scaleFactor
157         }
158         return 1f
159     }
160 
161     override fun getDimensionPixelSize(@DimenRes id: Int): Int {
162         return context.resources.getDimensionPixelSize(id)
163     }
164 
165     @AssistedFactory
166     interface Factory {
167         fun create(
168             context: Context,
169             configurationController: ConfigurationController,
170         ): ConfigurationRepositoryImpl
171     }
172 }
173 
174 @Module
175 abstract class ConfigurationRepositoryModule {
176 
177     /**
178      * For compatibility reasons. Ideally, only the an annotated [ConfigurationRepository] should be
179      * injected.
180      */
181     @Binds
182     @Deprecated("Use the ConfigurationRepository annotated with @Main instead.")
183     @SysUISingleton
provideDefaultConfigRepositorynull184     abstract fun provideDefaultConfigRepository(
185         @Main configurationRepository: ConfigurationRepository
186     ): ConfigurationRepository
187 
188     companion object {
189         @Provides
190         @Main
191         @SysUISingleton
192         fun provideGlobalConfigRepository(
193             context: Context,
194             @Main configurationController: ConfigurationController,
195             factory: ConfigurationRepositoryImpl.Factory,
196         ): ConfigurationRepository {
197             return factory.create(context, configurationController)
198         }
199     }
200 }
201