1 /* 2 * Copyright (C) 2018 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.server.am; 18 19 import android.annotation.NonNull; 20 import android.content.ContentResolver; 21 import android.database.ContentObserver; 22 import android.net.Uri; 23 import android.os.AsyncTask; 24 import android.os.Build; 25 import android.os.SystemProperties; 26 import android.provider.DeviceConfig; 27 import android.provider.Settings; 28 import android.text.TextUtils; 29 import android.util.Slog; 30 31 import com.android.internal.annotations.VisibleForTesting; 32 33 import java.io.BufferedReader; 34 import java.io.File; 35 import java.io.FileReader; 36 import java.io.IOException; 37 import java.util.HashSet; 38 39 /** 40 * Maps system settings to system properties. 41 * <p>The properties are dynamically updated when settings change. 42 * @hide 43 */ 44 public class SettingsToPropertiesMapper { 45 46 private static final String TAG = "SettingsToPropertiesMapper"; 47 48 private static final String SYSTEM_PROPERTY_PREFIX = "persist.device_config."; 49 50 private static final String RESET_PERFORMED_PROPERTY = "device_config.reset_performed"; 51 52 private static final String RESET_RECORD_FILE_PATH = 53 "/data/server_configurable_flags/reset_flags"; 54 55 private static final String SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX = "^[\\w\\.\\-@:]*$"; 56 57 private static final String SYSTEM_PROPERTY_INVALID_SUBSTRING = ".."; 58 59 private static final int SYSTEM_PROPERTY_MAX_LENGTH = 92; 60 61 // experiment flags added to Global.Settings(before new "Config" provider table is available) 62 // will be added under this category. 63 private static final String GLOBAL_SETTINGS_CATEGORY = "global_settings"; 64 65 // Add the global setting you want to push to native level as experiment flag into this list. 66 // 67 // NOTE: please grant write permission system property prefix 68 // with format persist.device_config.global_settings.[flag_name] in system_server.te and grant 69 // read permission in the corresponding .te file your feature belongs to. 70 @VisibleForTesting 71 static final String[] sGlobalSettings = new String[] { 72 Settings.Global.NATIVE_FLAGS_HEALTH_CHECK_ENABLED, 73 }; 74 75 // TODO(b/282593625): Move this constant to DeviceConfig module 76 private static final String NAMESPACE_TETHERING_U_OR_LATER_NATIVE = 77 "tethering_u_or_later_native"; 78 79 // All the flags under the listed DeviceConfig scopes will be synced to native level. 80 // 81 // NOTE: please grant write permission system property prefix 82 // with format persist.device_config.[device_config_scope]. in system_server.te and grant read 83 // permission in the corresponding .te file your feature belongs to. 84 @VisibleForTesting 85 static final String[] sDeviceConfigScopes = new String[] { 86 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT, 87 DeviceConfig.NAMESPACE_CAMERA_NATIVE, 88 DeviceConfig.NAMESPACE_CONFIGURATION, 89 DeviceConfig.NAMESPACE_CONNECTIVITY, 90 DeviceConfig.NAMESPACE_EDGETPU_NATIVE, 91 DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT, 92 DeviceConfig.NAMESPACE_INTELLIGENCE_CONTENT_SUGGESTIONS, 93 DeviceConfig.NAMESPACE_LMKD_NATIVE, 94 DeviceConfig.NAMESPACE_MEDIA_NATIVE, 95 DeviceConfig.NAMESPACE_MGLRU_NATIVE, 96 DeviceConfig.NAMESPACE_NETD_NATIVE, 97 DeviceConfig.NAMESPACE_NNAPI_NATIVE, 98 DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT, 99 DeviceConfig.NAMESPACE_REMOTE_KEY_PROVISIONING_NATIVE, 100 DeviceConfig.NAMESPACE_RUNTIME_NATIVE, 101 DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT, 102 DeviceConfig.NAMESPACE_STATSD_NATIVE, 103 DeviceConfig.NAMESPACE_STATSD_NATIVE_BOOT, 104 DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT, 105 DeviceConfig.NAMESPACE_SURFACE_FLINGER_NATIVE_BOOT, 106 DeviceConfig.NAMESPACE_SWCODEC_NATIVE, 107 DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE, 108 DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE_BOOT, 109 DeviceConfig.NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE, 110 DeviceConfig.NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT, 111 DeviceConfig.NAMESPACE_MEMORY_SAFETY_NATIVE_BOOT, 112 DeviceConfig.NAMESPACE_MEMORY_SAFETY_NATIVE, 113 DeviceConfig.NAMESPACE_HDMI_CONTROL, 114 NAMESPACE_TETHERING_U_OR_LATER_NATIVE 115 }; 116 117 private final String[] mGlobalSettings; 118 119 private final String[] mDeviceConfigScopes; 120 121 private final ContentResolver mContentResolver; 122 123 @VisibleForTesting SettingsToPropertiesMapper(ContentResolver contentResolver, String[] globalSettings, String[] deviceConfigScopes)124 protected SettingsToPropertiesMapper(ContentResolver contentResolver, 125 String[] globalSettings, 126 String[] deviceConfigScopes) { 127 mContentResolver = contentResolver; 128 mGlobalSettings = globalSettings; 129 mDeviceConfigScopes = deviceConfigScopes; 130 } 131 132 @VisibleForTesting updatePropertiesFromSettings()133 void updatePropertiesFromSettings() { 134 for (String globalSetting : mGlobalSettings) { 135 Uri settingUri = Settings.Global.getUriFor(globalSetting); 136 String propName = makePropertyName(GLOBAL_SETTINGS_CATEGORY, globalSetting); 137 if (settingUri == null) { 138 log("setting uri is null for globalSetting " + globalSetting); 139 continue; 140 } 141 if (propName == null) { 142 log("invalid prop name for globalSetting " + globalSetting); 143 continue; 144 } 145 146 ContentObserver co = new ContentObserver(null) { 147 @Override 148 public void onChange(boolean selfChange) { 149 updatePropertyFromSetting(globalSetting, propName); 150 } 151 }; 152 153 // only updating on starting up when no native flags reset is performed during current 154 // booting. 155 if (!isNativeFlagsResetPerformed()) { 156 updatePropertyFromSetting(globalSetting, propName); 157 } 158 mContentResolver.registerContentObserver(settingUri, false, co); 159 } 160 161 for (String deviceConfigScope : mDeviceConfigScopes) { 162 DeviceConfig.addOnPropertiesChangedListener( 163 deviceConfigScope, 164 AsyncTask.THREAD_POOL_EXECUTOR, 165 (DeviceConfig.Properties properties) -> { 166 String scope = properties.getNamespace(); 167 for (String key : properties.getKeyset()) { 168 String propertyName = makePropertyName(scope, key); 169 if (propertyName == null) { 170 log("unable to construct system property for " + scope + "/" 171 + key); 172 return; 173 } 174 setProperty(propertyName, properties.getString(key, null)); 175 } 176 }); 177 } 178 } 179 start(ContentResolver contentResolver)180 public static SettingsToPropertiesMapper start(ContentResolver contentResolver) { 181 SettingsToPropertiesMapper mapper = new SettingsToPropertiesMapper( 182 contentResolver, sGlobalSettings, sDeviceConfigScopes); 183 mapper.updatePropertiesFromSettings(); 184 return mapper; 185 } 186 187 /** 188 * If native level flags reset has been performed as an attempt to recover from a crash loop 189 * during current device booting. 190 * @return 191 */ isNativeFlagsResetPerformed()192 public static boolean isNativeFlagsResetPerformed() { 193 String value = SystemProperties.get(RESET_PERFORMED_PROPERTY); 194 return "true".equals(value); 195 } 196 197 /** 198 * return an array of native flag categories under which flags got reset during current device 199 * booting. 200 * @return 201 */ getResetNativeCategories()202 public static @NonNull String[] getResetNativeCategories() { 203 if (!isNativeFlagsResetPerformed()) { 204 return new String[0]; 205 } 206 207 String content = getResetFlagsFileContent(); 208 if (TextUtils.isEmpty(content)) { 209 return new String[0]; 210 } 211 212 String[] property_names = content.split(";"); 213 HashSet<String> categories = new HashSet<>(); 214 for (String property_name : property_names) { 215 String[] segments = property_name.split("\\."); 216 if (segments.length < 3) { 217 log("failed to extract category name from property " + property_name); 218 continue; 219 } 220 categories.add(segments[2]); 221 } 222 return categories.toArray(new String[0]); 223 } 224 225 /** 226 * system property name constructing rule: "persist.device_config.[category_name].[flag_name]". 227 * If the name contains invalid characters or substrings for system property name, 228 * will return null. 229 * @param categoryName 230 * @param flagName 231 * @return 232 */ 233 @VisibleForTesting makePropertyName(String categoryName, String flagName)234 static String makePropertyName(String categoryName, String flagName) { 235 String propertyName = SYSTEM_PROPERTY_PREFIX + categoryName + "." + flagName; 236 237 if (!propertyName.matches(SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX) 238 || propertyName.contains(SYSTEM_PROPERTY_INVALID_SUBSTRING)) { 239 return null; 240 } 241 242 return propertyName; 243 } 244 setProperty(String key, String value)245 private void setProperty(String key, String value) { 246 // Check if need to clear the property 247 if (value == null) { 248 // It's impossible to remove system property, therefore we check previous value to 249 // avoid setting an empty string if the property wasn't set. 250 if (TextUtils.isEmpty(SystemProperties.get(key))) { 251 return; 252 } 253 value = ""; 254 } else if (value.length() > SYSTEM_PROPERTY_MAX_LENGTH) { 255 log("key=" + key + " value=" + value + " exceeds system property max length."); 256 return; 257 } 258 259 try { 260 SystemProperties.set(key, value); 261 } catch (Exception e) { 262 // Failure to set a property can be caused by SELinux denial. This usually indicates 263 // that the property wasn't allowlisted in sepolicy. 264 // No need to report it on all user devices, only on debug builds. 265 log("Unable to set property " + key + " value '" + value + "'", e); 266 } 267 } 268 log(String msg, Exception e)269 private static void log(String msg, Exception e) { 270 if (Build.IS_DEBUGGABLE) { 271 Slog.wtf(TAG, msg, e); 272 } else { 273 Slog.e(TAG, msg, e); 274 } 275 } 276 log(String msg)277 private static void log(String msg) { 278 if (Build.IS_DEBUGGABLE) { 279 Slog.wtf(TAG, msg); 280 } else { 281 Slog.e(TAG, msg); 282 } 283 } 284 285 @VisibleForTesting getResetFlagsFileContent()286 static String getResetFlagsFileContent() { 287 String content = null; 288 try { 289 File reset_flag_file = new File(RESET_RECORD_FILE_PATH); 290 BufferedReader br = new BufferedReader(new FileReader(reset_flag_file)); 291 content = br.readLine(); 292 293 br.close(); 294 } catch (IOException ioe) { 295 log("failed to read file " + RESET_RECORD_FILE_PATH, ioe); 296 } 297 return content; 298 } 299 300 @VisibleForTesting updatePropertyFromSetting(String settingName, String propName)301 void updatePropertyFromSetting(String settingName, String propName) { 302 String settingValue = Settings.Global.getString(mContentResolver, settingName); 303 setProperty(propName, settingValue); 304 } 305 } 306