1 /*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 * except in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11 * KIND, either express or implied. See the License for the specific language governing
12 * permissions and limitations under the License.
13 */
14
15 package com.android.systemui.statusbar.phone
16
17 import android.content.Context
18 import android.content.pm.ActivityInfo
19 import android.content.res.Configuration
20 import android.graphics.Rect
21 import android.os.LocaleList
22 import android.view.View.LAYOUT_DIRECTION_RTL
23 import com.android.systemui.dagger.SysUISingleton
24 import com.android.systemui.dagger.qualifiers.Application
25 import com.android.systemui.statusbar.policy.ConfigurationController
26 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
27 import javax.inject.Inject
28
29 @SysUISingleton
30 class ConfigurationControllerImpl @Inject constructor(
31 @Application context: Context,
32 ) : ConfigurationController {
33
34 private val listeners: MutableList<ConfigurationListener> = ArrayList()
35 private val lastConfig = Configuration()
36 private var density: Int = 0
37 private var smallestScreenWidth: Int = 0
38 private var maxBounds = Rect()
39 private var fontScale: Float = 0.toFloat()
40 private val inCarMode: Boolean
41 private var uiMode: Int = 0
42 private var localeList: LocaleList? = null
43 private val context: Context
44 private var layoutDirection: Int
45 private var orientation = Configuration.ORIENTATION_UNDEFINED
46
47 init {
48 val currentConfig = context.resources.configuration
49 this.context = context
50 fontScale = currentConfig.fontScale
51 density = currentConfig.densityDpi
52 smallestScreenWidth = currentConfig.smallestScreenWidthDp
53 maxBounds.set(currentConfig.windowConfiguration.maxBounds)
54 inCarMode = currentConfig.uiMode and Configuration.UI_MODE_TYPE_MASK ==
55 Configuration.UI_MODE_TYPE_CAR
56 uiMode = currentConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK
57 localeList = currentConfig.locales
58 layoutDirection = currentConfig.layoutDirection
59 }
60
notifyThemeChangednull61 override fun notifyThemeChanged() {
62 // Avoid concurrent modification exception
63 val listeners = synchronized(this.listeners) {
64 ArrayList(this.listeners)
65 }
66
67 listeners.filterForEach({ this.listeners.contains(it) }) {
68 it.onThemeChanged()
69 }
70 }
71
onConfigurationChangednull72 override fun onConfigurationChanged(newConfig: Configuration) {
73 // Avoid concurrent modification exception
74 val listeners = synchronized(this.listeners) {
75 ArrayList(this.listeners)
76 }
77 listeners.filterForEach({ this.listeners.contains(it) }) {
78 it.onConfigChanged(newConfig)
79 }
80 val fontScale = newConfig.fontScale
81 val density = newConfig.densityDpi
82 val uiMode = newConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK
83 val uiModeChanged = uiMode != this.uiMode
84 if (density != this.density || fontScale != this.fontScale ||
85 inCarMode && uiModeChanged) {
86 listeners.filterForEach({ this.listeners.contains(it) }) {
87 it.onDensityOrFontScaleChanged()
88 }
89 this.density = density
90 this.fontScale = fontScale
91 }
92
93 val smallestScreenWidth = newConfig.smallestScreenWidthDp
94 if (smallestScreenWidth != this.smallestScreenWidth) {
95 this.smallestScreenWidth = smallestScreenWidth
96 listeners.filterForEach({ this.listeners.contains(it) }) {
97 it.onSmallestScreenWidthChanged()
98 }
99 }
100
101 val maxBounds = newConfig.windowConfiguration.maxBounds
102 if (maxBounds != this.maxBounds) {
103 // Update our internal rect to have the same bounds, instead of using
104 // `this.maxBounds = maxBounds` directly. Setting it directly means that `maxBounds`
105 // would be a direct reference to windowConfiguration.maxBounds, so the if statement
106 // above would always fail. See b/245799099 for more information.
107 this.maxBounds.set(maxBounds)
108 listeners.filterForEach({ this.listeners.contains(it) }) {
109 it.onMaxBoundsChanged()
110 }
111 }
112
113 val localeList = newConfig.locales
114 if (localeList != this.localeList) {
115 this.localeList = localeList
116 listeners.filterForEach({ this.listeners.contains(it) }) {
117 it.onLocaleListChanged()
118 }
119 }
120
121 if (uiModeChanged) {
122 // We need to force the style re-evaluation to make sure that it's up to date
123 // and attrs were reloaded.
124 context.theme.applyStyle(context.themeResId, true)
125
126 this.uiMode = uiMode
127 listeners.filterForEach({ this.listeners.contains(it) }) {
128 it.onUiModeChanged()
129 }
130 }
131
132 if (layoutDirection != newConfig.layoutDirection) {
133 layoutDirection = newConfig.layoutDirection
134 listeners.filterForEach({ this.listeners.contains(it) }) {
135 it.onLayoutDirectionChanged(layoutDirection == LAYOUT_DIRECTION_RTL)
136 }
137 }
138
139 if (lastConfig.updateFrom(newConfig) and ActivityInfo.CONFIG_ASSETS_PATHS != 0) {
140 listeners.filterForEach({ this.listeners.contains(it) }) {
141 it.onThemeChanged()
142 }
143 }
144
145 val newOrientation = newConfig.orientation
146 if (orientation != newOrientation) {
147 orientation = newOrientation
148 listeners.filterForEach({ this.listeners.contains(it) }) {
149 it.onOrientationChanged(orientation)
150 }
151 }
152 }
153
addCallbacknull154 override fun addCallback(listener: ConfigurationListener) {
155 synchronized(listeners) {
156 listeners.add(listener)
157 }
158 listener.onDensityOrFontScaleChanged()
159 }
160
removeCallbacknull161 override fun removeCallback(listener: ConfigurationListener) {
162 synchronized(listeners) {
163 listeners.remove(listener)
164 }
165 }
166
isLayoutRtlnull167 override fun isLayoutRtl(): Boolean {
168 return layoutDirection == LAYOUT_DIRECTION_RTL
169 }
170
getNightModeNamenull171 override fun getNightModeName(): String {
172 return when (uiMode and Configuration.UI_MODE_NIGHT_MASK) {
173 Configuration.UI_MODE_NIGHT_YES -> "night"
174 Configuration.UI_MODE_NIGHT_NO -> "day"
175 Configuration.UI_MODE_NIGHT_UNDEFINED -> "undefined"
176 else -> "err"
177 }
178 }
179 }
180
181 // This could be done with a Collection.filter and Collection.forEach, but Collection.filter
182 // creates a new array to store them in and we really don't need that here, so this provides
183 // a little more optimized inline version.
filterForEachnull184 inline fun <T> Collection<T>.filterForEach(f: (T) -> Boolean, execute: (T) -> Unit) {
185 forEach {
186 if (f.invoke(it)) {
187 execute.invoke(it)
188 }
189 }
190 }
191