blob: 832c9d95cc606ddeda1973404280b309eea7137c [file] [log] [blame]
Svetoslav683914b2015-01-15 14:22:26 -08001/*
2 * Copyright (C) 2015 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.providers.settings;
18
Svetoslav Ganova340bfd2016-08-02 18:24:49 -070019import android.os.Build;
Svetoslav683914b2015-01-15 14:22:26 -080020import android.os.Handler;
Svetoslav Ganov92057492016-05-16 12:36:43 -070021import android.os.Looper;
Svetoslav683914b2015-01-15 14:22:26 -080022import android.os.Message;
23import android.os.SystemClock;
24import android.provider.Settings;
25import android.text.TextUtils;
26import android.util.ArrayMap;
27import android.util.AtomicFile;
Makoto Onuki3a2c35782015-06-18 11:21:58 -070028import android.util.Base64;
Svetoslav683914b2015-01-15 14:22:26 -080029import android.util.Slog;
Svetoslav Ganova340bfd2016-08-02 18:24:49 -070030import android.util.TimeUtils;
Svetoslav683914b2015-01-15 14:22:26 -080031import android.util.Xml;
32import com.android.internal.annotations.GuardedBy;
Svetoslav683914b2015-01-15 14:22:26 -080033import libcore.io.IoUtils;
34import libcore.util.Objects;
35import org.xmlpull.v1.XmlPullParser;
36import org.xmlpull.v1.XmlPullParserException;
37import org.xmlpull.v1.XmlSerializer;
38
39import java.io.File;
40import java.io.FileInputStream;
41import java.io.FileNotFoundException;
42import java.io.FileOutputStream;
43import java.io.IOException;
Svetoslav Ganova340bfd2016-08-02 18:24:49 -070044import java.io.PrintWriter;
Wojciech Staszkiewicz9e9e2e72015-05-08 14:58:46 +010045import java.nio.charset.StandardCharsets;
Svetoslav683914b2015-01-15 14:22:26 -080046import java.util.ArrayList;
47import java.util.List;
48
49/**
50 * This class contains the state for one type of settings. It is responsible
51 * for saving the state asynchronously to an XML file after a mutation and
52 * loading the from an XML file on construction.
53 * <p>
54 * This class uses the same lock as the settings provider to ensure that
55 * multiple changes made by the settings provider, e,g, upgrade, bulk insert,
56 * etc, are atomically persisted since the asynchronous persistence is using
57 * the same lock to grab the current state to write to disk.
58 * </p>
59 */
60final class SettingsState {
61 private static final boolean DEBUG = false;
62 private static final boolean DEBUG_PERSISTENCE = false;
63
64 private static final String LOG_TAG = "SettingsState";
65
Svetoslav Ganova340bfd2016-08-02 18:24:49 -070066 static final int SETTINGS_VERSION_NEW_ENCODING = 121;
Makoto Onuki3a2c35782015-06-18 11:21:58 -070067
Svetoslav683914b2015-01-15 14:22:26 -080068 private static final long WRITE_SETTINGS_DELAY_MILLIS = 200;
69 private static final long MAX_WRITE_SETTINGS_DELAY_MILLIS = 2000;
70
71 public static final int MAX_BYTES_PER_APP_PACKAGE_UNLIMITED = -1;
72 public static final int MAX_BYTES_PER_APP_PACKAGE_LIMITED = 20000;
73
74 public static final String SYSTEM_PACKAGE_NAME = "android";
75
76 public static final int VERSION_UNDEFINED = -1;
77
78 private static final String TAG_SETTINGS = "settings";
79 private static final String TAG_SETTING = "setting";
80 private static final String ATTR_PACKAGE = "package";
81
82 private static final String ATTR_VERSION = "version";
83 private static final String ATTR_ID = "id";
84 private static final String ATTR_NAME = "name";
Makoto Onuki3a2c35782015-06-18 11:21:58 -070085
86 /** Non-binary value will be written in this attribute. */
Svetoslav683914b2015-01-15 14:22:26 -080087 private static final String ATTR_VALUE = "value";
88
Makoto Onuki3a2c35782015-06-18 11:21:58 -070089 /**
90 * KXmlSerializer won't like some characters. We encode such characters in base64 and
91 * store in this attribute.
92 * NOTE: A null value will have NEITHER ATTR_VALUE nor ATTR_VALUE_BASE64.
93 */
94 private static final String ATTR_VALUE_BASE64 = "valueBase64";
95
96 // This was used in version 120 and before.
97 private static final String NULL_VALUE_OLD_STYLE = "null";
Svetoslav683914b2015-01-15 14:22:26 -080098
Svetoslav Ganova340bfd2016-08-02 18:24:49 -070099 private static final int HISTORICAL_OPERATION_COUNT = 20;
100 private static final String HISTORICAL_OPERATION_UPDATE = "update";
101 private static final String HISTORICAL_OPERATION_DELETE = "delete";
102 private static final String HISTORICAL_OPERATION_PERSIST = "persist";
103 private static final String HISTORICAL_OPERATION_INITIALIZE = "initialize";
104
Svetoslav683914b2015-01-15 14:22:26 -0800105 private final Object mLock;
106
Svet Ganova8f90262016-05-10 08:44:48 -0700107 private final Handler mHandler;
Svetoslav683914b2015-01-15 14:22:26 -0800108
109 @GuardedBy("mLock")
110 private final ArrayMap<String, Setting> mSettings = new ArrayMap<>();
111
112 @GuardedBy("mLock")
113 private final ArrayMap<String, Integer> mPackageToMemoryUsage;
114
115 @GuardedBy("mLock")
116 private final int mMaxBytesPerAppPackage;
117
118 @GuardedBy("mLock")
119 private final File mStatePersistFile;
120
Svet Ganov53a441c2016-04-19 19:38:00 -0700121 private final Setting mNullSetting = new Setting(null, null, null) {
122 @Override
123 public boolean isNull() {
124 return true;
125 }
126 };
127
128 @GuardedBy("mLock")
Svetoslav Ganova340bfd2016-08-02 18:24:49 -0700129 private final List<HistoricalOperation> mHistoricalOperations;
130
131 @GuardedBy("mLock")
Svetoslav683914b2015-01-15 14:22:26 -0800132 public final int mKey;
133
134 @GuardedBy("mLock")
135 private int mVersion = VERSION_UNDEFINED;
136
137 @GuardedBy("mLock")
138 private long mLastNotWrittenMutationTimeMillis;
139
140 @GuardedBy("mLock")
141 private boolean mDirty;
142
143 @GuardedBy("mLock")
144 private boolean mWriteScheduled;
145
Svetoslavb505ccc2015-02-17 12:41:04 -0800146 @GuardedBy("mLock")
147 private long mNextId;
148
Svetoslav Ganova340bfd2016-08-02 18:24:49 -0700149 @GuardedBy("mLock")
150 private int mNextHistoricalOpIdx;
151
Svet Ganova8f90262016-05-10 08:44:48 -0700152 public SettingsState(Object lock, File file, int key, int maxBytesPerAppPackage,
Svetoslav Ganov92057492016-05-16 12:36:43 -0700153 Looper looper) {
Svetoslav683914b2015-01-15 14:22:26 -0800154 // It is important that we use the same lock as the settings provider
155 // to ensure multiple mutations on this state are atomicaly persisted
156 // as the async persistence should be blocked while we make changes.
157 mLock = lock;
158 mStatePersistFile = file;
159 mKey = key;
Svetoslav Ganov92057492016-05-16 12:36:43 -0700160 mHandler = new MyHandler(looper);
Svetoslav683914b2015-01-15 14:22:26 -0800161 if (maxBytesPerAppPackage == MAX_BYTES_PER_APP_PACKAGE_LIMITED) {
162 mMaxBytesPerAppPackage = maxBytesPerAppPackage;
163 mPackageToMemoryUsage = new ArrayMap<>();
164 } else {
165 mMaxBytesPerAppPackage = maxBytesPerAppPackage;
166 mPackageToMemoryUsage = null;
167 }
Svetoslav Ganova340bfd2016-08-02 18:24:49 -0700168
169 mHistoricalOperations = Build.IS_DEBUGGABLE
170 ? new ArrayList<>(HISTORICAL_OPERATION_COUNT) : null;
171
Svetoslav683914b2015-01-15 14:22:26 -0800172 synchronized (mLock) {
173 readStateSyncLocked();
174 }
175 }
176
177 // The settings provider must hold its lock when calling here.
178 public int getVersionLocked() {
179 return mVersion;
180 }
181
Svetoslav Ganov83a1f7f2016-04-27 13:50:49 -0700182 public Setting getNullSetting() {
183 return mNullSetting;
184 }
185
Svetoslav683914b2015-01-15 14:22:26 -0800186 // The settings provider must hold its lock when calling here.
187 public void setVersionLocked(int version) {
188 if (version == mVersion) {
189 return;
190 }
191 mVersion = version;
192
193 scheduleWriteIfNeededLocked();
194 }
195
196 // The settings provider must hold its lock when calling here.
197 public void onPackageRemovedLocked(String packageName) {
198 boolean removedSomething = false;
199
200 final int settingCount = mSettings.size();
201 for (int i = settingCount - 1; i >= 0; i--) {
202 String name = mSettings.keyAt(i);
Svet Ganov8de34802015-04-27 09:33:40 -0700203 // Settings defined by us are never dropped.
Svetoslav683914b2015-01-15 14:22:26 -0800204 if (Settings.System.PUBLIC_SETTINGS.contains(name)
205 || Settings.System.PRIVATE_SETTINGS.contains(name)) {
206 continue;
207 }
208 Setting setting = mSettings.valueAt(i);
209 if (packageName.equals(setting.packageName)) {
210 mSettings.removeAt(i);
211 removedSomething = true;
212 }
213 }
214
215 if (removedSomething) {
216 scheduleWriteIfNeededLocked();
217 }
218 }
219
220 // The settings provider must hold its lock when calling here.
221 public List<String> getSettingNamesLocked() {
222 ArrayList<String> names = new ArrayList<>();
223 final int settingsCount = mSettings.size();
224 for (int i = 0; i < settingsCount; i++) {
225 String name = mSettings.keyAt(i);
226 names.add(name);
227 }
228 return names;
229 }
230
231 // The settings provider must hold its lock when calling here.
232 public Setting getSettingLocked(String name) {
233 if (TextUtils.isEmpty(name)) {
Svet Ganov53a441c2016-04-19 19:38:00 -0700234 return mNullSetting;
Svetoslav683914b2015-01-15 14:22:26 -0800235 }
Svet Ganov53a441c2016-04-19 19:38:00 -0700236 Setting setting = mSettings.get(name);
237 if (setting != null) {
238 return new Setting(setting);
239 }
240 return mNullSetting;
Svetoslav683914b2015-01-15 14:22:26 -0800241 }
242
243 // The settings provider must hold its lock when calling here.
244 public boolean updateSettingLocked(String name, String value, String packageName) {
245 if (!hasSettingLocked(name)) {
246 return false;
247 }
248
249 return insertSettingLocked(name, value, packageName);
250 }
251
252 // The settings provider must hold its lock when calling here.
253 public boolean insertSettingLocked(String name, String value, String packageName) {
254 if (TextUtils.isEmpty(name)) {
255 return false;
256 }
257
258 Setting oldState = mSettings.get(name);
259 String oldValue = (oldState != null) ? oldState.value : null;
Svetoslav Ganova340bfd2016-08-02 18:24:49 -0700260 Setting newState;
Svetoslav683914b2015-01-15 14:22:26 -0800261
262 if (oldState != null) {
263 if (!oldState.update(value, packageName)) {
264 return false;
265 }
Svetoslav Ganova340bfd2016-08-02 18:24:49 -0700266 newState = oldState;
Svetoslav683914b2015-01-15 14:22:26 -0800267 } else {
Svetoslav Ganova340bfd2016-08-02 18:24:49 -0700268 newState = new Setting(name, value, packageName);
269 mSettings.put(name, newState);
Svetoslav683914b2015-01-15 14:22:26 -0800270 }
271
Svetoslav Ganova340bfd2016-08-02 18:24:49 -0700272 addHistoricalOperationLocked(HISTORICAL_OPERATION_UPDATE, newState);
273
Svetoslav683914b2015-01-15 14:22:26 -0800274 updateMemoryUsagePerPackageLocked(packageName, oldValue, value);
275
276 scheduleWriteIfNeededLocked();
277
278 return true;
279 }
280
281 // The settings provider must hold its lock when calling here.
282 public void persistSyncLocked() {
283 mHandler.removeMessages(MyHandler.MSG_PERSIST_SETTINGS);
284 doWriteState();
285 }
286
287 // The settings provider must hold its lock when calling here.
288 public boolean deleteSettingLocked(String name) {
289 if (TextUtils.isEmpty(name) || !hasSettingLocked(name)) {
290 return false;
291 }
292
293 Setting oldState = mSettings.remove(name);
294
295 updateMemoryUsagePerPackageLocked(oldState.packageName, oldState.value, null);
296
Svetoslav Ganova340bfd2016-08-02 18:24:49 -0700297 addHistoricalOperationLocked(HISTORICAL_OPERATION_DELETE, oldState);
298
Svetoslav683914b2015-01-15 14:22:26 -0800299 scheduleWriteIfNeededLocked();
300
301 return true;
302 }
303
304 // The settings provider must hold its lock when calling here.
305 public void destroyLocked(Runnable callback) {
306 mHandler.removeMessages(MyHandler.MSG_PERSIST_SETTINGS);
307 if (callback != null) {
308 if (mDirty) {
309 // Do it without a delay.
310 mHandler.obtainMessage(MyHandler.MSG_PERSIST_SETTINGS,
311 callback).sendToTarget();
312 return;
313 }
314 callback.run();
315 }
316 }
317
Svetoslav Ganova340bfd2016-08-02 18:24:49 -0700318 private void addHistoricalOperationLocked(String type, Setting setting) {
319 if (mHistoricalOperations == null) {
320 return;
321 }
322 HistoricalOperation operation = new HistoricalOperation(
323 SystemClock.elapsedRealtime(), type,
324 setting != null ? new Setting(setting) : null);
325 if (mNextHistoricalOpIdx >= mHistoricalOperations.size()) {
326 mHistoricalOperations.add(operation);
327 } else {
328 mHistoricalOperations.set(mNextHistoricalOpIdx, operation);
329 }
330 mNextHistoricalOpIdx++;
331 if (mNextHistoricalOpIdx >= HISTORICAL_OPERATION_COUNT) {
332 mNextHistoricalOpIdx = 0;
333 }
334 }
335
336 public void dumpHistoricalOperations(PrintWriter pw) {
337 synchronized (mLock) {
338 if (mHistoricalOperations == null) {
339 return;
340 }
341 pw.println("Historical operations");
342 final int operationCount = mHistoricalOperations.size();
343 for (int i = 0; i < operationCount; i++) {
344 int index = mNextHistoricalOpIdx - 1 - i;
345 if (index < 0) {
346 index = operationCount + index;
347 }
348 HistoricalOperation operation = mHistoricalOperations.get(index);
349 pw.print(TimeUtils.formatForLogging(operation.mTimestamp));
350 pw.print(" ");
351 pw.print(operation.mOperation);
352 if (operation.mSetting != null) {
353 pw.print(" ");
354 pw.print(operation.mSetting);
355 }
356 pw.println();
357 }
358 pw.println();
359 pw.println();
360 }
361 }
362
Svetoslav683914b2015-01-15 14:22:26 -0800363 private void updateMemoryUsagePerPackageLocked(String packageName, String oldValue,
364 String newValue) {
365 if (mMaxBytesPerAppPackage == MAX_BYTES_PER_APP_PACKAGE_UNLIMITED) {
366 return;
367 }
368
369 if (SYSTEM_PACKAGE_NAME.equals(packageName)) {
370 return;
371 }
372
373 final int oldValueSize = (oldValue != null) ? oldValue.length() : 0;
374 final int newValueSize = (newValue != null) ? newValue.length() : 0;
375 final int deltaSize = newValueSize - oldValueSize;
376
377 Integer currentSize = mPackageToMemoryUsage.get(packageName);
378 final int newSize = Math.max((currentSize != null)
379 ? currentSize + deltaSize : deltaSize, 0);
380
381 if (newSize > mMaxBytesPerAppPackage) {
382 throw new IllegalStateException("You are adding too many system settings. "
Svetoslav28494652015-02-12 14:11:42 -0800383 + "You should stop using system settings for app specific data"
384 + " package: " + packageName);
Svetoslav683914b2015-01-15 14:22:26 -0800385 }
386
387 if (DEBUG) {
388 Slog.i(LOG_TAG, "Settings for package: " + packageName
389 + " size: " + newSize + " bytes.");
390 }
391
392 mPackageToMemoryUsage.put(packageName, newSize);
393 }
394
395 private boolean hasSettingLocked(String name) {
396 return mSettings.indexOfKey(name) >= 0;
397 }
398
399 private void scheduleWriteIfNeededLocked() {
400 // If dirty then we have a write already scheduled.
401 if (!mDirty) {
402 mDirty = true;
403 writeStateAsyncLocked();
404 }
405 }
406
407 private void writeStateAsyncLocked() {
408 final long currentTimeMillis = SystemClock.uptimeMillis();
409
410 if (mWriteScheduled) {
411 mHandler.removeMessages(MyHandler.MSG_PERSIST_SETTINGS);
412
413 // If enough time passed, write without holding off anymore.
414 final long timeSinceLastNotWrittenMutationMillis = currentTimeMillis
415 - mLastNotWrittenMutationTimeMillis;
416 if (timeSinceLastNotWrittenMutationMillis >= MAX_WRITE_SETTINGS_DELAY_MILLIS) {
417 mHandler.obtainMessage(MyHandler.MSG_PERSIST_SETTINGS).sendToTarget();
418 return;
419 }
420
421 // Hold off a bit more as settings are frequently changing.
422 final long maxDelayMillis = Math.max(mLastNotWrittenMutationTimeMillis
423 + MAX_WRITE_SETTINGS_DELAY_MILLIS - currentTimeMillis, 0);
424 final long writeDelayMillis = Math.min(WRITE_SETTINGS_DELAY_MILLIS, maxDelayMillis);
425
426 Message message = mHandler.obtainMessage(MyHandler.MSG_PERSIST_SETTINGS);
427 mHandler.sendMessageDelayed(message, writeDelayMillis);
428 } else {
429 mLastNotWrittenMutationTimeMillis = currentTimeMillis;
430 Message message = mHandler.obtainMessage(MyHandler.MSG_PERSIST_SETTINGS);
431 mHandler.sendMessageDelayed(message, WRITE_SETTINGS_DELAY_MILLIS);
432 mWriteScheduled = true;
433 }
434 }
435
436 private void doWriteState() {
437 if (DEBUG_PERSISTENCE) {
438 Slog.i(LOG_TAG, "[PERSIST START]");
439 }
440
441 AtomicFile destination = new AtomicFile(mStatePersistFile);
442
443 final int version;
444 final ArrayMap<String, Setting> settings;
445
446 synchronized (mLock) {
447 version = mVersion;
448 settings = new ArrayMap<>(mSettings);
449 mDirty = false;
450 mWriteScheduled = false;
451 }
452
453 FileOutputStream out = null;
454 try {
455 out = destination.startWrite();
456
457 XmlSerializer serializer = Xml.newSerializer();
Wojciech Staszkiewicz9e9e2e72015-05-08 14:58:46 +0100458 serializer.setOutput(out, StandardCharsets.UTF_8.name());
Svetoslavc3f56c32015-03-10 16:53:35 -0700459 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
Svetoslav683914b2015-01-15 14:22:26 -0800460 serializer.startDocument(null, true);
461 serializer.startTag(null, TAG_SETTINGS);
462 serializer.attribute(null, ATTR_VERSION, String.valueOf(version));
463
464 final int settingCount = settings.size();
465 for (int i = 0; i < settingCount; i++) {
466 Setting setting = settings.valueAt(i);
467
Makoto Onuki3a2c35782015-06-18 11:21:58 -0700468 writeSingleSetting(mVersion, serializer, setting.getId(), setting.getName(),
469 setting.getValue(), setting.getPackageName());
Svetoslav683914b2015-01-15 14:22:26 -0800470
471 if (DEBUG_PERSISTENCE) {
472 Slog.i(LOG_TAG, "[PERSISTED]" + setting.getName() + "=" + setting.getValue());
473 }
474 }
475
476 serializer.endTag(null, TAG_SETTINGS);
477 serializer.endDocument();
478 destination.finishWrite(out);
479
Svetoslav Ganova340bfd2016-08-02 18:24:49 -0700480 synchronized (mLock) {
481 addHistoricalOperationLocked(HISTORICAL_OPERATION_PERSIST, null);
482 }
483
Svetoslav683914b2015-01-15 14:22:26 -0800484 if (DEBUG_PERSISTENCE) {
485 Slog.i(LOG_TAG, "[PERSIST END]");
486 }
Svet Ganovba0821e2015-04-22 13:34:31 -0700487 } catch (Throwable t) {
488 Slog.wtf(LOG_TAG, "Failed to write settings, restoring backup", t);
Svetoslav683914b2015-01-15 14:22:26 -0800489 destination.failWrite(out);
490 } finally {
491 IoUtils.closeQuietly(out);
492 }
493 }
494
Makoto Onuki3a2c35782015-06-18 11:21:58 -0700495 static void writeSingleSetting(int version, XmlSerializer serializer, String id,
496 String name, String value, String packageName) throws IOException {
497 if (id == null || isBinary(id) || name == null || isBinary(name)
498 || packageName == null || isBinary(packageName)) {
499 // This shouldn't happen.
500 return;
501 }
502 serializer.startTag(null, TAG_SETTING);
503 serializer.attribute(null, ATTR_ID, id);
504 serializer.attribute(null, ATTR_NAME, name);
505 setValueAttribute(version, serializer, value);
506 serializer.attribute(null, ATTR_PACKAGE, packageName);
507 serializer.endTag(null, TAG_SETTING);
508 }
509
510 static void setValueAttribute(int version, XmlSerializer serializer, String value)
511 throws IOException {
Svetoslav Ganova340bfd2016-08-02 18:24:49 -0700512 if (version >= SETTINGS_VERSION_NEW_ENCODING) {
Makoto Onuki3a2c35782015-06-18 11:21:58 -0700513 if (value == null) {
514 // Null value -> No ATTR_VALUE nor ATTR_VALUE_BASE64.
515 } else if (isBinary(value)) {
516 serializer.attribute(null, ATTR_VALUE_BASE64, base64Encode(value));
517 } else {
518 serializer.attribute(null, ATTR_VALUE, value);
519 }
520 } else {
521 // Old encoding.
522 if (value == null) {
523 serializer.attribute(null, ATTR_VALUE, NULL_VALUE_OLD_STYLE);
524 } else {
525 serializer.attribute(null, ATTR_VALUE, value);
526 }
527 }
528 }
529
530 private String getValueAttribute(XmlPullParser parser) {
Svetoslav Ganova340bfd2016-08-02 18:24:49 -0700531 if (mVersion >= SETTINGS_VERSION_NEW_ENCODING) {
Makoto Onuki3a2c35782015-06-18 11:21:58 -0700532 final String value = parser.getAttributeValue(null, ATTR_VALUE);
533 if (value != null) {
534 return value;
535 }
536 final String base64 = parser.getAttributeValue(null, ATTR_VALUE_BASE64);
537 if (base64 != null) {
538 return base64Decode(base64);
539 }
540 // null has neither ATTR_VALUE nor ATTR_VALUE_BASE64.
541 return null;
542 } else {
543 // Old encoding.
544 final String stored = parser.getAttributeValue(null, ATTR_VALUE);
545 if (NULL_VALUE_OLD_STYLE.equals(stored)) {
546 return null;
547 } else {
548 return stored;
549 }
550 }
551 }
552
Svetoslav683914b2015-01-15 14:22:26 -0800553 private void readStateSyncLocked() {
554 FileInputStream in;
555 if (!mStatePersistFile.exists()) {
Svetoslav Ganova340bfd2016-08-02 18:24:49 -0700556 Slog.i(LOG_TAG, "No settings state " + mStatePersistFile);
557 addHistoricalOperationLocked(HISTORICAL_OPERATION_INITIALIZE, null);
Svetoslav683914b2015-01-15 14:22:26 -0800558 return;
559 }
560 try {
Svetoslav3dcdd372015-05-29 13:00:32 -0700561 in = new AtomicFile(mStatePersistFile).openRead();
Svetoslav683914b2015-01-15 14:22:26 -0800562 } catch (FileNotFoundException fnfe) {
Svetoslav Ganova340bfd2016-08-02 18:24:49 -0700563 String message = "No settings state " + mStatePersistFile;
564 Slog.wtf(LOG_TAG, message);
565 Slog.i(LOG_TAG, message);
Svetoslav683914b2015-01-15 14:22:26 -0800566 return;
567 }
568 try {
569 XmlPullParser parser = Xml.newPullParser();
Wojciech Staszkiewicz9e9e2e72015-05-08 14:58:46 +0100570 parser.setInput(in, StandardCharsets.UTF_8.name());
Svetoslav683914b2015-01-15 14:22:26 -0800571 parseStateLocked(parser);
Svet Ganove723e542015-04-23 11:58:26 -0700572 } catch (XmlPullParserException | IOException e) {
Svetoslav Ganova340bfd2016-08-02 18:24:49 -0700573 String message = "Failed parsing settings file: " + mStatePersistFile;
574 Slog.wtf(LOG_TAG, message);
575 throw new IllegalStateException(message , e);
Svetoslav683914b2015-01-15 14:22:26 -0800576 } finally {
577 IoUtils.closeQuietly(in);
578 }
579 }
580
581 private void parseStateLocked(XmlPullParser parser)
582 throws IOException, XmlPullParserException {
Svet Ganov8440ca32015-03-27 17:55:08 -0700583 final int outerDepth = parser.getDepth();
584 int type;
585 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
586 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
587 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
588 continue;
Svetoslav683914b2015-01-15 14:22:26 -0800589 }
Svet Ganov8440ca32015-03-27 17:55:08 -0700590
591 String tagName = parser.getName();
592 if (tagName.equals(TAG_SETTINGS)) {
593 parseSettingsLocked(parser);
594 }
Svetoslav683914b2015-01-15 14:22:26 -0800595 }
Svet Ganov8440ca32015-03-27 17:55:08 -0700596 }
597
598 private void parseSettingsLocked(XmlPullParser parser)
599 throws IOException, XmlPullParserException {
Svet Ganovc9755bc2015-03-28 13:21:22 -0700600
601 mVersion = Integer.parseInt(parser.getAttributeValue(null, ATTR_VERSION));
602
Svet Ganov8440ca32015-03-27 17:55:08 -0700603 final int outerDepth = parser.getDepth();
604 int type;
605 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
606 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
607 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
608 continue;
609 }
610
Svet Ganovc9755bc2015-03-28 13:21:22 -0700611 String tagName = parser.getName();
612 if (tagName.equals(TAG_SETTING)) {
613 String id = parser.getAttributeValue(null, ATTR_ID);
614 String name = parser.getAttributeValue(null, ATTR_NAME);
Makoto Onuki3a2c35782015-06-18 11:21:58 -0700615 String value = getValueAttribute(parser);
Svet Ganovc9755bc2015-03-28 13:21:22 -0700616 String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
Makoto Onuki3a2c35782015-06-18 11:21:58 -0700617 mSettings.put(name, new Setting(name, value, packageName, id));
Svet Ganov8440ca32015-03-27 17:55:08 -0700618
Svet Ganovc9755bc2015-03-28 13:21:22 -0700619 if (DEBUG_PERSISTENCE) {
620 Slog.i(LOG_TAG, "[RESTORED] " + name + "=" + value);
621 }
Svet Ganov8440ca32015-03-27 17:55:08 -0700622 }
623 }
Svetoslav683914b2015-01-15 14:22:26 -0800624 }
625
626 private final class MyHandler extends Handler {
627 public static final int MSG_PERSIST_SETTINGS = 1;
628
Svetoslav Ganov92057492016-05-16 12:36:43 -0700629 public MyHandler(Looper looper) {
630 super(looper);
Svetoslav683914b2015-01-15 14:22:26 -0800631 }
632
633 @Override
634 public void handleMessage(Message message) {
635 switch (message.what) {
636 case MSG_PERSIST_SETTINGS: {
637 Runnable callback = (Runnable) message.obj;
638 doWriteState();
639 if (callback != null) {
640 callback.run();
641 }
642 }
643 break;
644 }
645 }
646 }
647
Svetoslav Ganova340bfd2016-08-02 18:24:49 -0700648 private class HistoricalOperation {
649 final long mTimestamp;
650 final String mOperation;
651 final Setting mSetting;
652
653 public HistoricalOperation(long timestamp,
654 String operation, Setting setting) {
655 mTimestamp = timestamp;
656 mOperation = operation;
657 mSetting = setting;
658 }
659 }
660
Svet Ganov53a441c2016-04-19 19:38:00 -0700661 class Setting {
Svetoslav683914b2015-01-15 14:22:26 -0800662 private String name;
663 private String value;
664 private String packageName;
665 private String id;
666
Svet Ganov53a441c2016-04-19 19:38:00 -0700667 public Setting(Setting other) {
668 name = other.name;
669 value = other.value;
670 packageName = other.packageName;
671 id = other.id;
672 }
673
Svetoslav683914b2015-01-15 14:22:26 -0800674 public Setting(String name, String value, String packageName) {
Svetoslavb505ccc2015-02-17 12:41:04 -0800675 init(name, value, packageName, String.valueOf(mNextId++));
Svetoslav683914b2015-01-15 14:22:26 -0800676 }
677
678 public Setting(String name, String value, String packageName, String id) {
Tobias Thierer28532d02016-04-21 14:52:10 +0100679 mNextId = Math.max(mNextId, Long.parseLong(id) + 1);
Svetoslavb505ccc2015-02-17 12:41:04 -0800680 init(name, value, packageName, id);
Svetoslav683914b2015-01-15 14:22:26 -0800681 }
682
683 private void init(String name, String value, String packageName, String id) {
684 this.name = name;
685 this.value = value;
686 this.packageName = packageName;
687 this.id = id;
688 }
689
690 public String getName() {
691 return name;
692 }
693
Svet Ganov53a441c2016-04-19 19:38:00 -0700694 public int getkey() {
695 return mKey;
696 }
697
Svetoslav683914b2015-01-15 14:22:26 -0800698 public String getValue() {
699 return value;
700 }
701
702 public String getPackageName() {
703 return packageName;
704 }
705
706 public String getId() {
707 return id;
708 }
709
Svet Ganov53a441c2016-04-19 19:38:00 -0700710 public boolean isNull() {
711 return false;
712 }
713
Svetoslav683914b2015-01-15 14:22:26 -0800714 public boolean update(String value, String packageName) {
715 if (Objects.equal(value, this.value)) {
716 return false;
717 }
718 this.value = value;
719 this.packageName = packageName;
Svetoslavb505ccc2015-02-17 12:41:04 -0800720 this.id = String.valueOf(mNextId++);
Svetoslav683914b2015-01-15 14:22:26 -0800721 return true;
722 }
Svetoslav Ganova340bfd2016-08-02 18:24:49 -0700723
724 public String toString() {
725 return "Setting{name=" + value + " from " + packageName + "}";
726 }
Svetoslav683914b2015-01-15 14:22:26 -0800727 }
Makoto Onuki3a2c35782015-06-18 11:21:58 -0700728
729 /**
730 * @return TRUE if a string is considered "binary" from KXML's point of view. NOTE DO NOT
731 * pass null.
732 */
733 public static boolean isBinary(String s) {
734 if (s == null) {
735 throw new NullPointerException();
736 }
737 // See KXmlSerializer.writeEscaped
738 for (int i = 0; i < s.length(); i++) {
739 char c = s.charAt(i);
740 boolean allowedInXml = (c >= 0x20 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xfffd);
741 if (!allowedInXml) {
742 return true;
743 }
744 }
745 return false;
746 }
747
748 private static String base64Encode(String s) {
749 return Base64.encodeToString(toBytes(s), Base64.NO_WRAP);
750 }
751
752 private static String base64Decode(String s) {
753 return fromBytes(Base64.decode(s, Base64.DEFAULT));
754 }
755
756 // Note the followings are basically just UTF-16 encode/decode. But we want to preserve
757 // contents as-is, even if it contains broken surrogate pairs, we do it by ourselves,
758 // since I don't know how Charset would treat them.
759
760 private static byte[] toBytes(String s) {
761 final byte[] result = new byte[s.length() * 2];
762 int resultIndex = 0;
763 for (int i = 0; i < s.length(); ++i) {
764 char ch = s.charAt(i);
765 result[resultIndex++] = (byte) (ch >> 8);
766 result[resultIndex++] = (byte) ch;
767 }
768 return result;
769 }
770
771 private static String fromBytes(byte[] bytes) {
772 final StringBuffer sb = new StringBuffer(bytes.length / 2);
773
774 final int last = bytes.length - 1;
775
776 for (int i = 0; i < last; i += 2) {
777 final char ch = (char) ((bytes[i] & 0xff) << 8 | (bytes[i + 1] & 0xff));
778 sb.append(ch);
779 }
780 return sb.toString();
781 }
Svetoslav683914b2015-01-15 14:22:26 -0800782}