1 /*
2  * Copyright (C) 2023 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.common.ui
16 
17 import android.content.Context
18 import android.view.LayoutInflater
19 import android.view.View
20 import android.view.ViewGroup
21 import androidx.annotation.AttrRes
22 import androidx.annotation.ColorInt
23 import androidx.annotation.DimenRes
24 import androidx.annotation.LayoutRes
25 import com.android.settingslib.Utils
26 import com.android.systemui.dagger.qualifiers.Application
27 import com.android.systemui.statusbar.policy.ConfigurationController
28 import com.android.systemui.statusbar.policy.onDensityOrFontScaleChanged
29 import com.android.systemui.statusbar.policy.onThemeChanged
30 import com.android.systemui.util.kotlin.emitOnStart
31 import javax.inject.Inject
32 import kotlinx.coroutines.flow.Flow
33 import kotlinx.coroutines.flow.map
34 import kotlinx.coroutines.flow.merge
35 
36 /** Configuration-aware-state-tracking utilities. */
37 class ConfigurationState
38 @Inject
39 constructor(
40     private val configurationController: ConfigurationController,
41     @Application private val context: Context,
42     private val layoutInflater: LayoutInflater,
43 ) {
44     /**
45      * Returns a [Flow] that emits a dimension pixel size that is kept in sync with the device
46      * configuration.
47      *
48      * @see android.content.res.Resources.getDimensionPixelSize
49      */
getDimensionPixelSizenull50     fun getDimensionPixelSize(@DimenRes id: Int): Flow<Int> {
51         return configurationController.onDensityOrFontScaleChanged.emitOnStart().map {
52             context.resources.getDimensionPixelSize(id)
53         }
54     }
55 
56     /**
57      * Returns a [Flow] that emits a dimension pixel size that is kept in sync with the device
58      * configuration.
59      *
60      * @see android.content.res.Resources.getDimensionPixelSize
61      */
getDimensionPixelOffsetnull62     fun getDimensionPixelOffset(@DimenRes id: Int): Flow<Int> {
63         return configurationController.onDensityOrFontScaleChanged.emitOnStart().map {
64             context.resources.getDimensionPixelOffset(id)
65         }
66     }
67 
68     /**
69      * Returns a [Flow] that emits a color that is kept in sync with the device theme.
70      *
71      * @see Utils.getColorAttrDefaultColor
72      */
getColorAttrnull73     fun getColorAttr(@AttrRes id: Int, @ColorInt defaultValue: Int): Flow<Int> {
74         return configurationController.onThemeChanged.emitOnStart().map {
75             Utils.getColorAttrDefaultColor(context, id, defaultValue)
76         }
77     }
78 
79     /**
80      * Returns a [Flow] that emits a [View] that is re-inflated as necessary to remain in sync with
81      * the device configuration.
82      *
83      * @see LayoutInflater.inflate
84      */
85     @Suppress("UNCHECKED_CAST")
inflateLayoutnull86     fun <T : View> inflateLayout(
87         @LayoutRes id: Int,
88         root: ViewGroup?,
89         attachToRoot: Boolean,
90     ): Flow<T> {
91         // TODO(b/305930747): This may lead to duplicate invocations if both flows emit, find a
92         //  solution to only emit one event.
93         return merge(
94                 configurationController.onThemeChanged,
95                 configurationController.onDensityOrFontScaleChanged,
96             )
97             .emitOnStart()
98             .map { layoutInflater.inflate(id, root, attachToRoot) as T }
99     }
100 }
101