2 * Copyright (C) 2015 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.providers.settings;
19 import android.os.Handler;
20 import android.os.Looper;
21 import android.os.Message;
22 import android.os.SystemClock;
23 import android.provider.Settings;
24 import android.text.TextUtils;
25 import android.util.ArrayMap;
26 import android.util.AtomicFile;
27 import android.util.Base64;
28 import android.util.Slog;
29 import android.util.Xml;
30 import com.android.internal.annotations.GuardedBy;
31 import libcore.io.IoUtils;
32 import libcore.util.Objects;
33 import org.xmlpull.v1.XmlPullParser;
34 import org.xmlpull.v1.XmlPullParserException;
35 import org.xmlpull.v1.XmlSerializer;
38 import java.io.FileInputStream;
39 import java.io.FileNotFoundException;
40 import java.io.FileOutputStream;
41 import java.io.IOException;
42 import java.nio.charset.StandardCharsets;
43 import java.util.ArrayList;
44 import java.util.List;
47 * This class contains the state for one type of settings. It is responsible
48 * for saving the state asynchronously to an XML file after a mutation and
49 * loading the from an XML file on construction.
51 * This class uses the same lock as the settings provider to ensure that
52 * multiple changes made by the settings provider, e,g, upgrade, bulk insert,
53 * etc, are atomically persisted since the asynchronous persistence is using
54 * the same lock to grab the current state to write to disk.
57 final class SettingsState {
58 private static final boolean DEBUG = false;
59 private static final boolean DEBUG_PERSISTENCE = false;
61 private static final String LOG_TAG = "SettingsState";
63 static final int SETTINGS_VERSOIN_NEW_ENCODING = 121;
65 private static final long WRITE_SETTINGS_DELAY_MILLIS = 200;
66 private static final long MAX_WRITE_SETTINGS_DELAY_MILLIS = 2000;
68 public static final int MAX_BYTES_PER_APP_PACKAGE_UNLIMITED = -1;
69 public static final int MAX_BYTES_PER_APP_PACKAGE_LIMITED = 20000;
71 public static final String SYSTEM_PACKAGE_NAME = "android";
73 public static final int VERSION_UNDEFINED = -1;
75 private static final String TAG_SETTINGS = "settings";
76 private static final String TAG_SETTING = "setting";
77 private static final String ATTR_PACKAGE = "package";
79 private static final String ATTR_VERSION = "version";
80 private static final String ATTR_ID = "id";
81 private static final String ATTR_NAME = "name";
83 /** Non-binary value will be written in this attribute. */
84 private static final String ATTR_VALUE = "value";
87 * KXmlSerializer won't like some characters. We encode such characters in base64 and
88 * store in this attribute.
89 * NOTE: A null value will have NEITHER ATTR_VALUE nor ATTR_VALUE_BASE64.
91 private static final String ATTR_VALUE_BASE64 = "valueBase64";
93 // This was used in version 120 and before.
94 private static final String NULL_VALUE_OLD_STYLE = "null";
96 private final Object mLock;
98 private final Handler mHandler;
101 private final ArrayMap<String, Setting> mSettings = new ArrayMap<>();
104 private final ArrayMap<String, Integer> mPackageToMemoryUsage;
107 private final int mMaxBytesPerAppPackage;
110 private final File mStatePersistFile;
112 private final Setting mNullSetting = new Setting(null, null, null) {
114 public boolean isNull() {
120 public final int mKey;
123 private int mVersion = VERSION_UNDEFINED;
126 private long mLastNotWrittenMutationTimeMillis;
129 private boolean mDirty;
132 private boolean mWriteScheduled;
135 private long mNextId;
137 public SettingsState(Object lock, File file, int key, int maxBytesPerAppPackage,
139 // It is important that we use the same lock as the settings provider
140 // to ensure multiple mutations on this state are atomicaly persisted
141 // as the async persistence should be blocked while we make changes.
143 mStatePersistFile = file;
145 mHandler = new MyHandler(looper);
146 if (maxBytesPerAppPackage == MAX_BYTES_PER_APP_PACKAGE_LIMITED) {
147 mMaxBytesPerAppPackage = maxBytesPerAppPackage;
148 mPackageToMemoryUsage = new ArrayMap<>();
150 mMaxBytesPerAppPackage = maxBytesPerAppPackage;
151 mPackageToMemoryUsage = null;
153 synchronized (mLock) {
154 readStateSyncLocked();
158 // The settings provider must hold its lock when calling here.
159 public int getVersionLocked() {
163 public Setting getNullSetting() {
167 // The settings provider must hold its lock when calling here.
168 public void setVersionLocked(int version) {
169 if (version == mVersion) {
174 scheduleWriteIfNeededLocked();
177 // The settings provider must hold its lock when calling here.
178 public void onPackageRemovedLocked(String packageName) {
179 boolean removedSomething = false;
181 final int settingCount = mSettings.size();
182 for (int i = settingCount - 1; i >= 0; i--) {
183 String name = mSettings.keyAt(i);
184 // Settings defined by us are never dropped.
185 if (Settings.System.PUBLIC_SETTINGS.contains(name)
186 || Settings.System.PRIVATE_SETTINGS.contains(name)) {
189 Setting setting = mSettings.valueAt(i);
190 if (packageName.equals(setting.packageName)) {
191 mSettings.removeAt(i);
192 removedSomething = true;
196 if (removedSomething) {
197 scheduleWriteIfNeededLocked();
201 // The settings provider must hold its lock when calling here.
202 public List<String> getSettingNamesLocked() {
203 ArrayList<String> names = new ArrayList<>();
204 final int settingsCount = mSettings.size();
205 for (int i = 0; i < settingsCount; i++) {
206 String name = mSettings.keyAt(i);
212 // The settings provider must hold its lock when calling here.
213 public Setting getSettingLocked(String name) {
214 if (TextUtils.isEmpty(name)) {
217 Setting setting = mSettings.get(name);
218 if (setting != null) {
219 return new Setting(setting);
224 // The settings provider must hold its lock when calling here.
225 public boolean updateSettingLocked(String name, String value, String packageName) {
226 if (!hasSettingLocked(name)) {
230 return insertSettingLocked(name, value, packageName);
233 // The settings provider must hold its lock when calling here.
234 public boolean insertSettingLocked(String name, String value, String packageName) {
235 if (TextUtils.isEmpty(name)) {
239 Setting oldState = mSettings.get(name);
240 String oldValue = (oldState != null) ? oldState.value : null;
242 if (oldState != null) {
243 if (!oldState.update(value, packageName)) {
247 Setting state = new Setting(name, value, packageName);
248 mSettings.put(name, state);
251 updateMemoryUsagePerPackageLocked(packageName, oldValue, value);
253 scheduleWriteIfNeededLocked();
258 // The settings provider must hold its lock when calling here.
259 public void persistSyncLocked() {
260 mHandler.removeMessages(MyHandler.MSG_PERSIST_SETTINGS);
264 // The settings provider must hold its lock when calling here.
265 public boolean deleteSettingLocked(String name) {
266 if (TextUtils.isEmpty(name) || !hasSettingLocked(name)) {
270 Setting oldState = mSettings.remove(name);
272 updateMemoryUsagePerPackageLocked(oldState.packageName, oldState.value, null);
274 scheduleWriteIfNeededLocked();
279 // The settings provider must hold its lock when calling here.
280 public void destroyLocked(Runnable callback) {
281 mHandler.removeMessages(MyHandler.MSG_PERSIST_SETTINGS);
282 if (callback != null) {
284 // Do it without a delay.
285 mHandler.obtainMessage(MyHandler.MSG_PERSIST_SETTINGS,
286 callback).sendToTarget();
293 private void updateMemoryUsagePerPackageLocked(String packageName, String oldValue,
295 if (mMaxBytesPerAppPackage == MAX_BYTES_PER_APP_PACKAGE_UNLIMITED) {
299 if (SYSTEM_PACKAGE_NAME.equals(packageName)) {
303 final int oldValueSize = (oldValue != null) ? oldValue.length() : 0;
304 final int newValueSize = (newValue != null) ? newValue.length() : 0;
305 final int deltaSize = newValueSize - oldValueSize;
307 Integer currentSize = mPackageToMemoryUsage.get(packageName);
308 final int newSize = Math.max((currentSize != null)
309 ? currentSize + deltaSize : deltaSize, 0);
311 if (newSize > mMaxBytesPerAppPackage) {
312 throw new IllegalStateException("You are adding too many system settings. "
313 + "You should stop using system settings for app specific data"
314 + " package: " + packageName);
318 Slog.i(LOG_TAG, "Settings for package: " + packageName
319 + " size: " + newSize + " bytes.");
322 mPackageToMemoryUsage.put(packageName, newSize);
325 private boolean hasSettingLocked(String name) {
326 return mSettings.indexOfKey(name) >= 0;
329 private void scheduleWriteIfNeededLocked() {
330 // If dirty then we have a write already scheduled.
333 writeStateAsyncLocked();
337 private void writeStateAsyncLocked() {
338 final long currentTimeMillis = SystemClock.uptimeMillis();
340 if (mWriteScheduled) {
341 mHandler.removeMessages(MyHandler.MSG_PERSIST_SETTINGS);
343 // If enough time passed, write without holding off anymore.
344 final long timeSinceLastNotWrittenMutationMillis = currentTimeMillis
345 - mLastNotWrittenMutationTimeMillis;
346 if (timeSinceLastNotWrittenMutationMillis >= MAX_WRITE_SETTINGS_DELAY_MILLIS) {
347 mHandler.obtainMessage(MyHandler.MSG_PERSIST_SETTINGS).sendToTarget();
351 // Hold off a bit more as settings are frequently changing.
352 final long maxDelayMillis = Math.max(mLastNotWrittenMutationTimeMillis
353 + MAX_WRITE_SETTINGS_DELAY_MILLIS - currentTimeMillis, 0);
354 final long writeDelayMillis = Math.min(WRITE_SETTINGS_DELAY_MILLIS, maxDelayMillis);
356 Message message = mHandler.obtainMessage(MyHandler.MSG_PERSIST_SETTINGS);
357 mHandler.sendMessageDelayed(message, writeDelayMillis);
359 mLastNotWrittenMutationTimeMillis = currentTimeMillis;
360 Message message = mHandler.obtainMessage(MyHandler.MSG_PERSIST_SETTINGS);
361 mHandler.sendMessageDelayed(message, WRITE_SETTINGS_DELAY_MILLIS);
362 mWriteScheduled = true;
366 private void doWriteState() {
367 if (DEBUG_PERSISTENCE) {
368 Slog.i(LOG_TAG, "[PERSIST START]");
371 AtomicFile destination = new AtomicFile(mStatePersistFile);
374 final ArrayMap<String, Setting> settings;
376 synchronized (mLock) {
378 settings = new ArrayMap<>(mSettings);
380 mWriteScheduled = false;
383 FileOutputStream out = null;
385 out = destination.startWrite();
387 XmlSerializer serializer = Xml.newSerializer();
388 serializer.setOutput(out, StandardCharsets.UTF_8.name());
389 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
390 serializer.startDocument(null, true);
391 serializer.startTag(null, TAG_SETTINGS);
392 serializer.attribute(null, ATTR_VERSION, String.valueOf(version));
394 final int settingCount = settings.size();
395 for (int i = 0; i < settingCount; i++) {
396 Setting setting = settings.valueAt(i);
398 writeSingleSetting(mVersion, serializer, setting.getId(), setting.getName(),
399 setting.getValue(), setting.getPackageName());
401 if (DEBUG_PERSISTENCE) {
402 Slog.i(LOG_TAG, "[PERSISTED]" + setting.getName() + "=" + setting.getValue());
406 serializer.endTag(null, TAG_SETTINGS);
407 serializer.endDocument();
408 destination.finishWrite(out);
410 if (DEBUG_PERSISTENCE) {
411 Slog.i(LOG_TAG, "[PERSIST END]");
413 } catch (Throwable t) {
414 Slog.wtf(LOG_TAG, "Failed to write settings, restoring backup", t);
415 destination.failWrite(out);
417 IoUtils.closeQuietly(out);
421 static void writeSingleSetting(int version, XmlSerializer serializer, String id,
422 String name, String value, String packageName) throws IOException {
423 if (id == null || isBinary(id) || name == null || isBinary(name)
424 || packageName == null || isBinary(packageName)) {
425 // This shouldn't happen.
428 serializer.startTag(null, TAG_SETTING);
429 serializer.attribute(null, ATTR_ID, id);
430 serializer.attribute(null, ATTR_NAME, name);
431 setValueAttribute(version, serializer, value);
432 serializer.attribute(null, ATTR_PACKAGE, packageName);
433 serializer.endTag(null, TAG_SETTING);
436 static void setValueAttribute(int version, XmlSerializer serializer, String value)
438 if (version >= SETTINGS_VERSOIN_NEW_ENCODING) {
440 // Null value -> No ATTR_VALUE nor ATTR_VALUE_BASE64.
441 } else if (isBinary(value)) {
442 serializer.attribute(null, ATTR_VALUE_BASE64, base64Encode(value));
444 serializer.attribute(null, ATTR_VALUE, value);
449 serializer.attribute(null, ATTR_VALUE, NULL_VALUE_OLD_STYLE);
451 serializer.attribute(null, ATTR_VALUE, value);
456 private String getValueAttribute(XmlPullParser parser) {
457 if (mVersion >= SETTINGS_VERSOIN_NEW_ENCODING) {
458 final String value = parser.getAttributeValue(null, ATTR_VALUE);
462 final String base64 = parser.getAttributeValue(null, ATTR_VALUE_BASE64);
463 if (base64 != null) {
464 return base64Decode(base64);
466 // null has neither ATTR_VALUE nor ATTR_VALUE_BASE64.
470 final String stored = parser.getAttributeValue(null, ATTR_VALUE);
471 if (NULL_VALUE_OLD_STYLE.equals(stored)) {
479 private void readStateSyncLocked() {
481 if (!mStatePersistFile.exists()) {
485 in = new AtomicFile(mStatePersistFile).openRead();
486 } catch (FileNotFoundException fnfe) {
487 Slog.i(LOG_TAG, "No settings state");
491 XmlPullParser parser = Xml.newPullParser();
492 parser.setInput(in, StandardCharsets.UTF_8.name());
493 parseStateLocked(parser);
495 } catch (XmlPullParserException | IOException e) {
496 throw new IllegalStateException("Failed parsing settings file: "
497 + mStatePersistFile , e);
499 IoUtils.closeQuietly(in);
503 private void parseStateLocked(XmlPullParser parser)
504 throws IOException, XmlPullParserException {
505 final int outerDepth = parser.getDepth();
507 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
508 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
509 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
513 String tagName = parser.getName();
514 if (tagName.equals(TAG_SETTINGS)) {
515 parseSettingsLocked(parser);
520 private void parseSettingsLocked(XmlPullParser parser)
521 throws IOException, XmlPullParserException {
523 mVersion = Integer.parseInt(parser.getAttributeValue(null, ATTR_VERSION));
525 final int outerDepth = parser.getDepth();
527 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
528 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
529 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
533 String tagName = parser.getName();
534 if (tagName.equals(TAG_SETTING)) {
535 String id = parser.getAttributeValue(null, ATTR_ID);
536 String name = parser.getAttributeValue(null, ATTR_NAME);
537 String value = getValueAttribute(parser);
538 String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
539 mSettings.put(name, new Setting(name, value, packageName, id));
541 if (DEBUG_PERSISTENCE) {
542 Slog.i(LOG_TAG, "[RESTORED] " + name + "=" + value);
548 private final class MyHandler extends Handler {
549 public static final int MSG_PERSIST_SETTINGS = 1;
551 public MyHandler(Looper looper) {
556 public void handleMessage(Message message) {
557 switch (message.what) {
558 case MSG_PERSIST_SETTINGS: {
559 Runnable callback = (Runnable) message.obj;
561 if (callback != null) {
572 private String value;
573 private String packageName;
576 public Setting(Setting other) {
579 packageName = other.packageName;
583 public Setting(String name, String value, String packageName) {
584 init(name, value, packageName, String.valueOf(mNextId++));
587 public Setting(String name, String value, String packageName, String id) {
588 mNextId = Math.max(mNextId, Long.valueOf(id) + 1);
589 init(name, value, packageName, id);
592 private void init(String name, String value, String packageName, String id) {
595 this.packageName = packageName;
599 public String getName() {
603 public int getkey() {
607 public String getValue() {
611 public String getPackageName() {
615 public String getId() {
619 public boolean isNull() {
623 public boolean update(String value, String packageName) {
624 if (Objects.equal(value, this.value)) {
628 this.packageName = packageName;
629 this.id = String.valueOf(mNextId++);
635 * @return TRUE if a string is considered "binary" from KXML's point of view. NOTE DO NOT
638 public static boolean isBinary(String s) {
640 throw new NullPointerException();
642 // See KXmlSerializer.writeEscaped
643 for (int i = 0; i < s.length(); i++) {
644 char c = s.charAt(i);
645 boolean allowedInXml = (c >= 0x20 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xfffd);
653 private static String base64Encode(String s) {
654 return Base64.encodeToString(toBytes(s), Base64.NO_WRAP);
657 private static String base64Decode(String s) {
658 return fromBytes(Base64.decode(s, Base64.DEFAULT));
661 // Note the followings are basically just UTF-16 encode/decode. But we want to preserve
662 // contents as-is, even if it contains broken surrogate pairs, we do it by ourselves,
663 // since I don't know how Charset would treat them.
665 private static byte[] toBytes(String s) {
666 final byte[] result = new byte[s.length() * 2];
668 for (int i = 0; i < s.length(); ++i) {
669 char ch = s.charAt(i);
670 result[resultIndex++] = (byte) (ch >> 8);
671 result[resultIndex++] = (byte) ch;
676 private static String fromBytes(byte[] bytes) {
677 final StringBuffer sb = new StringBuffer(bytes.length / 2);
679 final int last = bytes.length - 1;
681 for (int i = 0; i < last; i += 2) {
682 final char ch = (char) ((bytes[i] & 0xff) << 8 | (bytes[i + 1] & 0xff));
685 return sb.toString();