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.statusbar.policy.ConfigurationController 24 25 import java.util.ArrayList 26 27 class ConfigurationControllerImpl(context: Context) : ConfigurationController { 28 29 private val listeners: MutableList<ConfigurationController.ConfigurationListener> = ArrayList() 30 private val lastConfig = Configuration() 31 private var density: Int = 0 32 private var smallestScreenWidth: Int = 0 33 private var maxBounds: Rect? = null 34 private var fontScale: Float = 0.toFloat() 35 private val inCarMode: Boolean 36 private var uiMode: Int = 0 37 private var localeList: LocaleList? = null 38 private val context: Context 39 private var layoutDirection: Int 40 41 init { 42 val currentConfig = context.resources.configuration 43 this.context = context 44 fontScale = currentConfig.fontScale 45 density = currentConfig.densityDpi 46 smallestScreenWidth = currentConfig.smallestScreenWidthDp 47 inCarMode = currentConfig.uiMode and Configuration.UI_MODE_TYPE_MASK == 48 Configuration.UI_MODE_TYPE_CAR 49 uiMode = currentConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK 50 localeList = currentConfig.locales 51 layoutDirection = currentConfig.layoutDirection 52 } 53 54 override fun notifyThemeChanged() { 55 val listeners = ArrayList(listeners) 56 57 listeners.filterForEach({ this.listeners.contains(it) }) { 58 it.onThemeChanged() 59 } 60 } 61 62 override fun onConfigurationChanged(newConfig: Configuration) { 63 // Avoid concurrent modification exception 64 val listeners = ArrayList(listeners) 65 66 listeners.filterForEach({ this.listeners.contains(it) }) { 67 it.onConfigChanged(newConfig) 68 } 69 val fontScale = newConfig.fontScale 70 val density = newConfig.densityDpi 71 val uiMode = newConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK 72 val uiModeChanged = uiMode != this.uiMode 73 if (density != this.density || fontScale != this.fontScale || 74 inCarMode && uiModeChanged) { 75 listeners.filterForEach({ this.listeners.contains(it) }) { 76 it.onDensityOrFontScaleChanged() 77 } 78 this.density = density 79 this.fontScale = fontScale 80 } 81 82 val smallestScreenWidth = newConfig.smallestScreenWidthDp 83 if (smallestScreenWidth != this.smallestScreenWidth) { 84 this.smallestScreenWidth = smallestScreenWidth 85 listeners.filterForEach({ this.listeners.contains(it) }) { 86 it.onSmallestScreenWidthChanged() 87 } 88 } 89 90 val maxBounds = newConfig.windowConfiguration.maxBounds 91 if (maxBounds != this.maxBounds) { 92 this.maxBounds = maxBounds 93 listeners.filterForEach({ this.listeners.contains(it) }) { 94 it.onMaxBoundsChanged() 95 } 96 } 97 98 val localeList = newConfig.locales 99 if (localeList != this.localeList) { 100 this.localeList = localeList 101 listeners.filterForEach({ this.listeners.contains(it) }) { 102 it.onLocaleListChanged() 103 } 104 } 105 106 if (uiModeChanged) { 107 // We need to force the style re-evaluation to make sure that it's up to date 108 // and attrs were reloaded. 109 context.theme.applyStyle(context.themeResId, true) 110 111 this.uiMode = uiMode 112 listeners.filterForEach({ this.listeners.contains(it) }) { 113 it.onUiModeChanged() 114 } 115 } 116 117 if (layoutDirection != newConfig.layoutDirection) { 118 layoutDirection = newConfig.layoutDirection 119 listeners.filterForEach({ this.listeners.contains(it) }) { 120 it.onLayoutDirectionChanged(layoutDirection == LAYOUT_DIRECTION_RTL) 121 } 122 } 123 124 if (lastConfig.updateFrom(newConfig) and ActivityInfo.CONFIG_ASSETS_PATHS != 0) { 125 listeners.filterForEach({ this.listeners.contains(it) }) { 126 it.onThemeChanged() 127 } 128 } 129 } 130 131 override fun addCallback(listener: ConfigurationController.ConfigurationListener) { 132 listeners.add(listener) 133 listener.onDensityOrFontScaleChanged() 134 } 135 136 override fun removeCallback(listener: ConfigurationController.ConfigurationListener) { 137 listeners.remove(listener) 138 } 139 140 override fun isLayoutRtl(): Boolean { 141 return layoutDirection == LAYOUT_DIRECTION_RTL 142 } 143 } 144 145 // This could be done with a Collection.filter and Collection.forEach, but Collection.filter 146 // creates a new array to store them in and we really don't need that here, so this provides 147 // a little more optimized inline version. 148 inline fun <T> Collection<T>.filterForEach(f: (T) -> Boolean, execute: (T) -> Unit) { 149 forEach { 150 if (f.invoke(it)) { 151 execute.invoke(it) 152 } 153 } 154 } 155