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 // All the flags under the listed DeviceConfig scopes will be synced to native level. 76 // 77 // NOTE: please grant write permission system property prefix 78 // with format persist.device_config.[device_config_scope]. in system_server.te and grant read 79 // permission in the corresponding .te file your feature belongs to. 80 @VisibleForTesting 81 static final String[] sDeviceConfigScopes = new String[] { 82 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT, 83 DeviceConfig.NAMESPACE_CONFIGURATION, 84 DeviceConfig.NAMESPACE_CONNECTIVITY, 85 DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT, 86 DeviceConfig.NAMESPACE_INTELLIGENCE_CONTENT_SUGGESTIONS, 87 DeviceConfig.NAMESPACE_LMKD_NATIVE, 88 DeviceConfig.NAMESPACE_MEDIA_NATIVE, 89 DeviceConfig.NAMESPACE_NETD_NATIVE, 90 DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT, 91 DeviceConfig.NAMESPACE_RUNTIME_NATIVE, 92 DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT, 93 DeviceConfig.NAMESPACE_STATSD_NATIVE, 94 DeviceConfig.NAMESPACE_STATSD_NATIVE_BOOT, 95 DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT, 96 DeviceConfig.NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT, 97 }; 98 99 private final String[] mGlobalSettings; 100 101 private final String[] mDeviceConfigScopes; 102 103 private final ContentResolver mContentResolver; 104 105 @VisibleForTesting SettingsToPropertiesMapper(ContentResolver contentResolver, String[] globalSettings, String[] deviceConfigScopes)106 protected SettingsToPropertiesMapper(ContentResolver contentResolver, 107 String[] globalSettings, 108 String[] deviceConfigScopes) { 109 mContentResolver = contentResolver; 110 mGlobalSettings = globalSettings; 111 mDeviceConfigScopes = deviceConfigScopes; 112 } 113 114 @VisibleForTesting updatePropertiesFromSettings()115 void updatePropertiesFromSettings() { 116 for (String globalSetting : mGlobalSettings) { 117 Uri settingUri = Settings.Global.getUriFor(globalSetting); 118 String propName = makePropertyName(GLOBAL_SETTINGS_CATEGORY, globalSetting); 119 if (settingUri == null) { 120 log("setting uri is null for globalSetting " + globalSetting); 121 continue; 122 } 123 if (propName == null) { 124 log("invalid prop name for globalSetting " + globalSetting); 125 continue; 126 } 127 128 ContentObserver co = new ContentObserver(null) { 129 @Override 130 public void onChange(boolean selfChange) { 131 updatePropertyFromSetting(globalSetting, propName); 132 } 133 }; 134 135 // only updating on starting up when no native flags reset is performed during current 136 // booting. 137 if (!isNativeFlagsResetPerformed()) { 138 updatePropertyFromSetting(globalSetting, propName); 139 } 140 mContentResolver.registerContentObserver(settingUri, false, co); 141 } 142 143 for (String deviceConfigScope : mDeviceConfigScopes) { 144 DeviceConfig.addOnPropertiesChangedListener( 145 deviceConfigScope, 146 AsyncTask.THREAD_POOL_EXECUTOR, 147 (DeviceConfig.Properties properties) -> { 148 String scope = properties.getNamespace(); 149 for (String key : properties.getKeyset()) { 150 String propertyName = makePropertyName(scope, key); 151 if (propertyName == null) { 152 log("unable to construct system property for " + scope + "/" 153 + key); 154 return; 155 } 156 setProperty(propertyName, properties.getString(key, null)); 157 } 158 }); 159 } 160 } 161 start(ContentResolver contentResolver)162 public static SettingsToPropertiesMapper start(ContentResolver contentResolver) { 163 SettingsToPropertiesMapper mapper = new SettingsToPropertiesMapper( 164 contentResolver, sGlobalSettings, sDeviceConfigScopes); 165 mapper.updatePropertiesFromSettings(); 166 return mapper; 167 } 168 169 /** 170 * If native level flags reset has been performed as an attempt to recover from a crash loop 171 * during current device booting. 172 * @return 173 */ isNativeFlagsResetPerformed()174 public static boolean isNativeFlagsResetPerformed() { 175 String value = SystemProperties.get(RESET_PERFORMED_PROPERTY); 176 return "true".equals(value); 177 } 178 179 /** 180 * return an array of native flag categories under which flags got reset during current device 181 * booting. 182 * @return 183 */ getResetNativeCategories()184 public static @NonNull String[] getResetNativeCategories() { 185 if (!isNativeFlagsResetPerformed()) { 186 return new String[0]; 187 } 188 189 String content = getResetFlagsFileContent(); 190 if (TextUtils.isEmpty(content)) { 191 return new String[0]; 192 } 193 194 String[] property_names = content.split(";"); 195 HashSet<String> categories = new HashSet<>(); 196 for (String property_name : property_names) { 197 String[] segments = property_name.split("\\."); 198 if (segments.length < 3) { 199 log("failed to extract category name from property " + property_name); 200 continue; 201 } 202 categories.add(segments[2]); 203 } 204 return categories.toArray(new String[0]); 205 } 206 207 /** 208 * system property name constructing rule: "persist.device_config.[category_name].[flag_name]". 209 * If the name contains invalid characters or substrings for system property name, 210 * will return null. 211 * @param categoryName 212 * @param flagName 213 * @return 214 */ 215 @VisibleForTesting makePropertyName(String categoryName, String flagName)216 static String makePropertyName(String categoryName, String flagName) { 217 String propertyName = SYSTEM_PROPERTY_PREFIX + categoryName + "." + flagName; 218 219 if (!propertyName.matches(SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX) 220 || propertyName.contains(SYSTEM_PROPERTY_INVALID_SUBSTRING)) { 221 return null; 222 } 223 224 return propertyName; 225 } 226 setProperty(String key, String value)227 private void setProperty(String key, String value) { 228 // Check if need to clear the property 229 if (value == null) { 230 // It's impossible to remove system property, therefore we check previous value to 231 // avoid setting an empty string if the property wasn't set. 232 if (TextUtils.isEmpty(SystemProperties.get(key))) { 233 return; 234 } 235 value = ""; 236 } else if (value.length() > SYSTEM_PROPERTY_MAX_LENGTH) { 237 log(value + " exceeds system property max length."); 238 return; 239 } 240 241 try { 242 SystemProperties.set(key, value); 243 } catch (Exception e) { 244 // Failure to set a property can be caused by SELinux denial. This usually indicates 245 // that the property wasn't allowlisted in sepolicy. 246 // No need to report it on all user devices, only on debug builds. 247 log("Unable to set property " + key + " value '" + value + "'", e); 248 } 249 } 250 log(String msg, Exception e)251 private static void log(String msg, Exception e) { 252 if (Build.IS_DEBUGGABLE) { 253 Slog.wtf(TAG, msg, e); 254 } else { 255 Slog.e(TAG, msg, e); 256 } 257 } 258 log(String msg)259 private static void log(String msg) { 260 if (Build.IS_DEBUGGABLE) { 261 Slog.wtf(TAG, msg); 262 } else { 263 Slog.e(TAG, msg); 264 } 265 } 266 267 @VisibleForTesting getResetFlagsFileContent()268 static String getResetFlagsFileContent() { 269 String content = null; 270 try { 271 File reset_flag_file = new File(RESET_RECORD_FILE_PATH); 272 BufferedReader br = new BufferedReader(new FileReader(reset_flag_file)); 273 content = br.readLine(); 274 275 br.close(); 276 } catch (IOException ioe) { 277 log("failed to read file " + RESET_RECORD_FILE_PATH, ioe); 278 } 279 return content; 280 } 281 282 @VisibleForTesting updatePropertyFromSetting(String settingName, String propName)283 void updatePropertyFromSetting(String settingName, String propName) { 284 String settingValue = Settings.Global.getString(mContentResolver, settingName); 285 setProperty(propName, settingValue); 286 } 287 } 288