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