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