blob: 194549f15ecffabb43271b7f69b9990770c0fece [file] [log] [blame]
Hongyi Zhang50a4e012018-09-26 21:20:03 -07001/*
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
17package com.android.server.am;
18
bpetrivs93075f42019-02-28 12:08:12 +000019import android.annotation.NonNull;
Hongyi Zhang50a4e012018-09-26 21:20:03 -070020import android.content.ContentResolver;
21import android.database.ContentObserver;
22import android.net.Uri;
Hongyi Zhangc4aad0e2018-12-11 14:36:06 -080023import android.os.AsyncTask;
Hongyi Zhang50a4e012018-09-26 21:20:03 -070024import android.os.Build;
25import android.os.SystemProperties;
Hongyi Zhangc4aad0e2018-12-11 14:36:06 -080026import android.provider.DeviceConfig;
Hongyi Zhang50a4e012018-09-26 21:20:03 -070027import android.provider.Settings;
28import android.text.TextUtils;
29import android.util.Slog;
30
31import com.android.internal.annotations.VisibleForTesting;
32
33import java.io.BufferedReader;
34import java.io.File;
35import java.io.FileReader;
36import java.io.IOException;
37import java.util.HashSet;
38
39/**
40 * Maps system settings to system properties.
41 * <p>The properties are dynamically updated when settings change.
Hongyi Zhang78a58252019-01-18 15:10:18 -080042 * @hide
Hongyi Zhang50a4e012018-09-26 21:20:03 -070043 */
Hongyi Zhang78a58252019-01-18 15:10:18 -080044public class SettingsToPropertiesMapper {
Hongyi Zhang50a4e012018-09-26 21:20:03 -070045
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
Hongyi Zhangc4aad0e2018-12-11 14:36:06 -080068 // 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.
Hongyi Zhang50a4e012018-09-26 21:20:03 -070070 @VisibleForTesting
71 static final String[] sGlobalSettings = new String[] {
Hongyi Zhanga02118d2018-11-15 20:15:38 -080072 Settings.Global.NATIVE_FLAGS_HEALTH_CHECK_ENABLED,
Hongyi Zhang50a4e012018-09-26 21:20:03 -070073 };
74
Hongyi Zhangc4aad0e2018-12-11 14:36:06 -080075 // 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.
Hongyi Zhang50a4e012018-09-26 21:20:03 -070080 @VisibleForTesting
81 static final String[] sDeviceConfigScopes = new String[] {
Matt Papebecd5aa2019-02-28 14:08:51 -080082 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
Siarhei Vishniakou39a1a982019-01-11 09:22:32 -080083 DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT,
Matt Papebecd5aa2019-02-28 14:08:51 -080084 DeviceConfig.NAMESPACE_MEDIA_NATIVE,
chenbrucedb279e82018-12-27 17:12:31 +080085 DeviceConfig.NAMESPACE_NETD_NATIVE,
Matt Papebecd5aa2019-02-28 14:08:51 -080086 DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT,
Mathieu Chartier7f11eb92019-01-25 16:08:21 -080087 DeviceConfig.RuntimeNative.NAMESPACE,
Hongyi Zhang50a4e012018-09-26 21:20:03 -070088 };
89
90 private final String[] mGlobalSettings;
91
92 private final String[] mDeviceConfigScopes;
93
94 private final ContentResolver mContentResolver;
95
96 @VisibleForTesting
97 protected SettingsToPropertiesMapper(ContentResolver contentResolver,
98 String[] globalSettings,
99 String[] deviceConfigScopes) {
100 mContentResolver = contentResolver;
101 mGlobalSettings = globalSettings;
102 mDeviceConfigScopes = deviceConfigScopes;
103 }
104
105 @VisibleForTesting
106 void updatePropertiesFromSettings() {
107 for (String globalSetting : mGlobalSettings) {
108 Uri settingUri = Settings.Global.getUriFor(globalSetting);
109 String propName = makePropertyName(GLOBAL_SETTINGS_CATEGORY, globalSetting);
110 if (settingUri == null) {
111 log("setting uri is null for globalSetting " + globalSetting);
112 continue;
113 }
114 if (propName == null) {
115 log("invalid prop name for globalSetting " + globalSetting);
116 continue;
117 }
118
119 ContentObserver co = new ContentObserver(null) {
120 @Override
121 public void onChange(boolean selfChange) {
Hongyi Zhangc4aad0e2018-12-11 14:36:06 -0800122 updatePropertyFromSetting(globalSetting, propName);
Hongyi Zhang50a4e012018-09-26 21:20:03 -0700123 }
124 };
125
126 // only updating on starting up when no native flags reset is performed during current
127 // booting.
128 if (!isNativeFlagsResetPerformed()) {
Hongyi Zhangc4aad0e2018-12-11 14:36:06 -0800129 updatePropertyFromSetting(globalSetting, propName);
Hongyi Zhang50a4e012018-09-26 21:20:03 -0700130 }
131 mContentResolver.registerContentObserver(settingUri, false, co);
132 }
133
Hongyi Zhangc4aad0e2018-12-11 14:36:06 -0800134 for (String deviceConfigScope : mDeviceConfigScopes) {
135 DeviceConfig.addOnPropertyChangedListener(
136 deviceConfigScope,
137 AsyncTask.THREAD_POOL_EXECUTOR,
138 (String scope, String name, String value) -> {
139 String propertyName = makePropertyName(scope, name);
140 if (propertyName == null) {
141 log("unable to construct system property for " + scope + "/" + name);
142 return;
143 }
144 setProperty(propertyName, value);
145 });
146 }
Hongyi Zhang50a4e012018-09-26 21:20:03 -0700147 }
148
149 public static SettingsToPropertiesMapper start(ContentResolver contentResolver) {
150 SettingsToPropertiesMapper mapper = new SettingsToPropertiesMapper(
151 contentResolver, sGlobalSettings, sDeviceConfigScopes);
152 mapper.updatePropertiesFromSettings();
153 return mapper;
154 }
155
156 /**
157 * If native level flags reset has been performed as an attempt to recover from a crash loop
158 * during current device booting.
159 * @return
160 */
Hongyi Zhang78a58252019-01-18 15:10:18 -0800161 public static boolean isNativeFlagsResetPerformed() {
162 String value = SystemProperties.get(RESET_PERFORMED_PROPERTY);
Hongyi Zhang50a4e012018-09-26 21:20:03 -0700163 return "true".equals(value);
164 }
165
166 /**
167 * return an array of native flag categories under which flags got reset during current device
168 * booting.
169 * @return
170 */
bpetrivs93075f42019-02-28 12:08:12 +0000171 public static @NonNull String[] getResetNativeCategories() {
Hongyi Zhang50a4e012018-09-26 21:20:03 -0700172 if (!isNativeFlagsResetPerformed()) {
173 return new String[0];
174 }
175
176 String content = getResetFlagsFileContent();
177 if (TextUtils.isEmpty(content)) {
178 return new String[0];
179 }
180
181 String[] property_names = content.split(";");
182 HashSet<String> categories = new HashSet<>();
183 for (String property_name : property_names) {
184 String[] segments = property_name.split("\\.");
185 if (segments.length < 3) {
186 log("failed to extract category name from property " + property_name);
187 continue;
188 }
189 categories.add(segments[2]);
190 }
191 return categories.toArray(new String[0]);
192 }
193
194 /**
195 * system property name constructing rule: "persist.device_config.[category_name].[flag_name]".
196 * If the name contains invalid characters or substrings for system property name,
197 * will return null.
198 * @param categoryName
199 * @param flagName
200 * @return
201 */
202 @VisibleForTesting
203 static String makePropertyName(String categoryName, String flagName) {
204 String propertyName = SYSTEM_PROPERTY_PREFIX + categoryName + "." + flagName;
205
206 if (!propertyName.matches(SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX)
207 || propertyName.contains(SYSTEM_PROPERTY_INVALID_SUBSTRING)) {
208 return null;
209 }
210
211 return propertyName;
212 }
213
Hongyi Zhang50a4e012018-09-26 21:20:03 -0700214 private void setProperty(String key, String value) {
215 // Check if need to clear the property
216 if (value == null) {
217 // It's impossible to remove system property, therefore we check previous value to
218 // avoid setting an empty string if the property wasn't set.
Hongyi Zhang78a58252019-01-18 15:10:18 -0800219 if (TextUtils.isEmpty(SystemProperties.get(key))) {
Hongyi Zhang50a4e012018-09-26 21:20:03 -0700220 return;
221 }
222 value = "";
223 } else if (value.length() > SYSTEM_PROPERTY_MAX_LENGTH) {
224 log(value + " exceeds system property max length.");
225 return;
226 }
227
228 try {
Hongyi Zhang78a58252019-01-18 15:10:18 -0800229 SystemProperties.set(key, value);
Hongyi Zhang50a4e012018-09-26 21:20:03 -0700230 } catch (Exception e) {
231 // Failure to set a property can be caused by SELinux denial. This usually indicates
232 // that the property wasn't whitelisted in sepolicy.
233 // No need to report it on all user devices, only on debug builds.
234 log("Unable to set property " + key + " value '" + value + "'", e);
235 }
236 }
237
238 private static void log(String msg, Exception e) {
239 if (Build.IS_DEBUGGABLE) {
240 Slog.wtf(TAG, msg, e);
241 } else {
242 Slog.e(TAG, msg, e);
243 }
244 }
245
246 private static void log(String msg) {
247 if (Build.IS_DEBUGGABLE) {
248 Slog.wtf(TAG, msg);
249 } else {
250 Slog.e(TAG, msg);
251 }
252 }
253
254 @VisibleForTesting
Hongyi Zhang78a58252019-01-18 15:10:18 -0800255 static String getResetFlagsFileContent() {
Hongyi Zhang50a4e012018-09-26 21:20:03 -0700256 String content = null;
257 try {
258 File reset_flag_file = new File(RESET_RECORD_FILE_PATH);
259 BufferedReader br = new BufferedReader(new FileReader(reset_flag_file));
260 content = br.readLine();
261
262 br.close();
263 } catch (IOException ioe) {
264 log("failed to read file " + RESET_RECORD_FILE_PATH, ioe);
265 }
266 return content;
267 }
268
269 @VisibleForTesting
Hongyi Zhangc4aad0e2018-12-11 14:36:06 -0800270 void updatePropertyFromSetting(String settingName, String propName) {
271 String settingValue = Settings.Global.getString(mContentResolver, settingName);
Hongyi Zhang50a4e012018-09-26 21:20:03 -0700272 setProperty(propName, settingValue);
273 }
Hongyi Zhang78a58252019-01-18 15:10:18 -0800274}