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