• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 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.quickstep.util
18 
19 import android.app.ActivityThread
20 import android.content.Context
21 import android.content.SharedPreferences
22 import android.content.SharedPreferences.OnSharedPreferenceChangeListener
23 import android.provider.DeviceConfig
24 import android.provider.DeviceConfig.OnPropertiesChangedListener
25 import android.provider.DeviceConfig.Properties
26 import androidx.annotation.WorkerThread
27 import com.android.launcher3.BuildConfig
28 import com.android.launcher3.util.Executors
29 
30 /** Utility class to manage a set of device configurations */
31 class DeviceConfigHelper<ConfigType>(private val factory: (PropReader) -> ConfigType) {
32 
33     var config: ConfigType
34         private set
35     private val allKeys: Set<String>
36     private val propertiesListener = OnPropertiesChangedListener { onDevicePropsChanges(it) }
37     private val sharedPrefChangeListener = OnSharedPreferenceChangeListener { _, _ ->
38         recreateConfig()
39     }
40 
41     private val changeListeners = mutableListOf<Runnable>()
42 
43     init {
44         // Initialize the default config once.
45         allKeys = HashSet()
46         config =
47             factory(
48                 PropReader(
49                     object : PropProvider {
50                         override fun <T : Any> get(key: String, fallback: T): T {
51                             if (fallback is Int) {
52                                 allKeys.add(key)
53                                 return DeviceConfig.getInt(NAMESPACE_LAUNCHER, key, fallback) as T
54                             } else if (fallback is Boolean) {
55                                 allKeys.add(key)
56                                 return DeviceConfig.getBoolean(NAMESPACE_LAUNCHER, key, fallback)
57                                     as T
58                             } else return fallback
59                         }
60                     }
61                 )
62             )
63 
64         DeviceConfig.addOnPropertiesChangedListener(
65             NAMESPACE_LAUNCHER,
66             Executors.UI_HELPER_EXECUTOR,
67             propertiesListener
68         )
69         if (BuildConfig.IS_DEBUG_DEVICE) {
70             prefs.registerOnSharedPreferenceChangeListener(sharedPrefChangeListener)
71         }
72     }
73 
74     @WorkerThread
75     private fun onDevicePropsChanges(properties: Properties) {
76         if (NAMESPACE_LAUNCHER != properties.namespace) return
77         if (!allKeys.any(properties.keyset::contains)) return
78         recreateConfig()
79     }
80 
81     private fun recreateConfig() {
82         val myProps =
83             DeviceConfig.getProperties(NAMESPACE_LAUNCHER, *allKeys.toTypedArray<String>())
84         config =
85             factory(
86                 PropReader(
87                     object : PropProvider {
88                         override fun <T : Any> get(key: String, fallback: T): T {
89                             if (fallback is Int) return myProps.getInt(key, fallback) as T
90                             else if (fallback is Boolean)
91                                 return myProps.getBoolean(key, fallback) as T
92                             else return fallback
93                         }
94                     }
95                 )
96             )
97         Executors.MAIN_EXECUTOR.execute { changeListeners.forEach(Runnable::run) }
98     }
99 
100     /** Adds a listener for property changes */
101     fun addChangeListener(r: Runnable) = changeListeners.add(r)
102 
103     /** Removes a previously added listener */
104     fun removeChangeListener(r: Runnable) = changeListeners.remove(r)
105 
106     fun close() {
107         DeviceConfig.removeOnPropertiesChangedListener(propertiesListener)
108         if (BuildConfig.IS_DEBUG_DEVICE) {
109             prefs.unregisterOnSharedPreferenceChangeListener(sharedPrefChangeListener)
110         }
111     }
112 
113     internal interface PropProvider {
114         fun <T : Any> get(key: String, fallback: T): T
115     }
116 
117     /** The reader is sent to the config for initialization */
118     class PropReader internal constructor(private val f: PropProvider) {
119 
120         @JvmOverloads
121         fun <T : Any> get(key: String, fallback: T, desc: String? = null): T {
122             val v = f.get(key, fallback)
123             if (BuildConfig.IS_DEBUG_DEVICE && desc != null) {
124                 if (v is Int) {
125                     allProps[key] = DebugInfo(key, desc, true, fallback)
126                     return prefs.getInt(key, v) as T
127                 } else if (v is Boolean) {
128                     allProps[key] = DebugInfo(key, desc, false, fallback)
129                     return prefs.getBoolean(key, v) as T
130                 }
131             }
132             return v
133         }
134     }
135 
136     class DebugInfo<T>(
137         val key: String,
138         val desc: String,
139         val isInt: Boolean,
140         val valueInCode: T,
141     )
142 
143     companion object {
144         const val NAMESPACE_LAUNCHER = "launcher"
145 
146         val allProps = mutableMapOf<String, DebugInfo<*>>()
147 
148         private const val FLAGS_PREF_NAME = "featureFlags"
149 
150         val prefs: SharedPreferences by lazy {
151             ActivityThread.currentApplication()
152                 .createDeviceProtectedStorageContext()
153                 .getSharedPreferences(FLAGS_PREF_NAME, Context.MODE_PRIVATE)
154         }
155     }
156 }
157