SettingsState.java revision 41f3c49cce2505f0e15890bb2c8476e0bf9425a4
1/* 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 19import android.os.Handler; 20import android.os.Message; 21import android.os.SystemClock; 22import android.provider.Settings; 23import android.text.TextUtils; 24import android.util.ArrayMap; 25import android.util.AtomicFile; 26import android.util.Base64; 27import android.util.Slog; 28import android.util.Xml; 29import com.android.internal.annotations.GuardedBy; 30import com.android.internal.os.BackgroundThread; 31import libcore.io.IoUtils; 32import libcore.util.Objects; 33import org.xmlpull.v1.XmlPullParser; 34import org.xmlpull.v1.XmlPullParserException; 35import org.xmlpull.v1.XmlSerializer; 36 37import java.io.File; 38import java.io.FileInputStream; 39import java.io.FileNotFoundException; 40import java.io.FileOutputStream; 41import java.io.IOException; 42import java.nio.charset.StandardCharsets; 43import java.util.ArrayList; 44import java.util.List; 45 46/** 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. 50 * <p> 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. 55 * </p> 56 */ 57final class SettingsState { 58 private static final boolean DEBUG = false; 59 private static final boolean DEBUG_PERSISTENCE = false; 60 61 private static final String LOG_TAG = "SettingsState"; 62 63 static final int SETTINGS_VERSOIN_NEW_ENCODING = 121; 64 65 private static final long WRITE_SETTINGS_DELAY_MILLIS = 200; 66 private static final long MAX_WRITE_SETTINGS_DELAY_MILLIS = 2000; 67 68 public static final int MAX_BYTES_PER_APP_PACKAGE_UNLIMITED = -1; 69 public static final int MAX_BYTES_PER_APP_PACKAGE_LIMITED = 20000; 70 71 public static final String SYSTEM_PACKAGE_NAME = "android"; 72 73 public static final int VERSION_UNDEFINED = -1; 74 75 private static final String TAG_SETTINGS = "settings"; 76 private static final String TAG_SETTING = "setting"; 77 private static final String ATTR_PACKAGE = "package"; 78 79 private static final String ATTR_VERSION = "version"; 80 private static final String ATTR_ID = "id"; 81 private static final String ATTR_NAME = "name"; 82 83 /** Non-binary value will be written in this attribute. */ 84 private static final String ATTR_VALUE = "value"; 85 86 /** 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. 90 */ 91 private static final String ATTR_VALUE_BASE64 = "valueBase64"; 92 93 // This was used in version 120 and before. 94 private static final String NULL_VALUE_OLD_STYLE = "null"; 95 96 private final Object mLock; 97 98 private final Handler mHandler = new MyHandler(); 99 100 @GuardedBy("mLock") 101 private final ArrayMap<String, Setting> mSettings = new ArrayMap<>(); 102 103 @GuardedBy("mLock") 104 private final ArrayMap<String, Integer> mPackageToMemoryUsage; 105 106 @GuardedBy("mLock") 107 private final int mMaxBytesPerAppPackage; 108 109 @GuardedBy("mLock") 110 private final File mStatePersistFile; 111 112 public final int mKey; 113 114 @GuardedBy("mLock") 115 private int mVersion = VERSION_UNDEFINED; 116 117 @GuardedBy("mLock") 118 private long mLastNotWrittenMutationTimeMillis; 119 120 @GuardedBy("mLock") 121 private boolean mDirty; 122 123 @GuardedBy("mLock") 124 private boolean mWriteScheduled; 125 126 @GuardedBy("mLock") 127 private long mNextId; 128 129 public SettingsState(Object lock, File file, int key, int maxBytesPerAppPackage) { 130 // It is important that we use the same lock as the settings provider 131 // to ensure multiple mutations on this state are atomicaly persisted 132 // as the async persistence should be blocked while we make changes. 133 mLock = lock; 134 mStatePersistFile = file; 135 mKey = key; 136 if (maxBytesPerAppPackage == MAX_BYTES_PER_APP_PACKAGE_LIMITED) { 137 mMaxBytesPerAppPackage = maxBytesPerAppPackage; 138 mPackageToMemoryUsage = new ArrayMap<>(); 139 } else { 140 mMaxBytesPerAppPackage = maxBytesPerAppPackage; 141 mPackageToMemoryUsage = null; 142 } 143 synchronized (mLock) { 144 readStateSyncLocked(); 145 } 146 } 147 148 // The settings provider must hold its lock when calling here. 149 public int getVersionLocked() { 150 return mVersion; 151 } 152 153 // The settings provider must hold its lock when calling here. 154 public void setVersionLocked(int version) { 155 if (version == mVersion) { 156 return; 157 } 158 mVersion = version; 159 160 scheduleWriteIfNeededLocked(); 161 } 162 163 // The settings provider must hold its lock when calling here. 164 public void onPackageRemovedLocked(String packageName) { 165 boolean removedSomething = false; 166 167 final int settingCount = mSettings.size(); 168 for (int i = settingCount - 1; i >= 0; i--) { 169 String name = mSettings.keyAt(i); 170 // Settings defined by us are never dropped. 171 if (Settings.System.PUBLIC_SETTINGS.contains(name) 172 || Settings.System.PRIVATE_SETTINGS.contains(name)) { 173 continue; 174 } 175 Setting setting = mSettings.valueAt(i); 176 if (packageName.equals(setting.packageName)) { 177 mSettings.removeAt(i); 178 removedSomething = true; 179 } 180 } 181 182 if (removedSomething) { 183 scheduleWriteIfNeededLocked(); 184 } 185 } 186 187 // The settings provider must hold its lock when calling here. 188 public List<String> getSettingNamesLocked() { 189 ArrayList<String> names = new ArrayList<>(); 190 final int settingsCount = mSettings.size(); 191 for (int i = 0; i < settingsCount; i++) { 192 String name = mSettings.keyAt(i); 193 names.add(name); 194 } 195 return names; 196 } 197 198 // The settings provider must hold its lock when calling here. 199 public Setting getSettingLocked(String name) { 200 if (TextUtils.isEmpty(name)) { 201 return null; 202 } 203 return mSettings.get(name); 204 } 205 206 // The settings provider must hold its lock when calling here. 207 public boolean updateSettingLocked(String name, String value, String packageName) { 208 if (!hasSettingLocked(name)) { 209 return false; 210 } 211 212 return insertSettingLocked(name, value, packageName); 213 } 214 215 // The settings provider must hold its lock when calling here. 216 public boolean insertSettingLocked(String name, String value, String packageName) { 217 if (TextUtils.isEmpty(name)) { 218 return false; 219 } 220 221 Setting oldState = mSettings.get(name); 222 String oldValue = (oldState != null) ? oldState.value : null; 223 224 if (oldState != null) { 225 if (!oldState.update(value, packageName)) { 226 return false; 227 } 228 } else { 229 Setting state = new Setting(name, value, packageName); 230 mSettings.put(name, state); 231 } 232 233 updateMemoryUsagePerPackageLocked(packageName, oldValue, value); 234 235 scheduleWriteIfNeededLocked(); 236 237 return true; 238 } 239 240 // The settings provider must hold its lock when calling here. 241 public void persistSyncLocked() { 242 mHandler.removeMessages(MyHandler.MSG_PERSIST_SETTINGS); 243 doWriteState(); 244 } 245 246 // The settings provider must hold its lock when calling here. 247 public boolean deleteSettingLocked(String name) { 248 if (TextUtils.isEmpty(name) || !hasSettingLocked(name)) { 249 return false; 250 } 251 252 Setting oldState = mSettings.remove(name); 253 254 updateMemoryUsagePerPackageLocked(oldState.packageName, oldState.value, null); 255 256 scheduleWriteIfNeededLocked(); 257 258 return true; 259 } 260 261 // The settings provider must hold its lock when calling here. 262 public void destroyLocked(Runnable callback) { 263 mHandler.removeMessages(MyHandler.MSG_PERSIST_SETTINGS); 264 if (callback != null) { 265 if (mDirty) { 266 // Do it without a delay. 267 mHandler.obtainMessage(MyHandler.MSG_PERSIST_SETTINGS, 268 callback).sendToTarget(); 269 return; 270 } 271 callback.run(); 272 } 273 } 274 275 private void updateMemoryUsagePerPackageLocked(String packageName, String oldValue, 276 String newValue) { 277 if (mMaxBytesPerAppPackage == MAX_BYTES_PER_APP_PACKAGE_UNLIMITED) { 278 return; 279 } 280 281 if (SYSTEM_PACKAGE_NAME.equals(packageName)) { 282 return; 283 } 284 285 final int oldValueSize = (oldValue != null) ? oldValue.length() : 0; 286 final int newValueSize = (newValue != null) ? newValue.length() : 0; 287 final int deltaSize = newValueSize - oldValueSize; 288 289 Integer currentSize = mPackageToMemoryUsage.get(packageName); 290 final int newSize = Math.max((currentSize != null) 291 ? currentSize + deltaSize : deltaSize, 0); 292 293 if (newSize > mMaxBytesPerAppPackage) { 294 throw new IllegalStateException("You are adding too many system settings. " 295 + "You should stop using system settings for app specific data" 296 + " package: " + packageName); 297 } 298 299 if (DEBUG) { 300 Slog.i(LOG_TAG, "Settings for package: " + packageName 301 + " size: " + newSize + " bytes."); 302 } 303 304 mPackageToMemoryUsage.put(packageName, newSize); 305 } 306 307 private boolean hasSettingLocked(String name) { 308 return mSettings.indexOfKey(name) >= 0; 309 } 310 311 private void scheduleWriteIfNeededLocked() { 312 // If dirty then we have a write already scheduled. 313 if (!mDirty) { 314 mDirty = true; 315 writeStateAsyncLocked(); 316 } 317 } 318 319 private void writeStateAsyncLocked() { 320 final long currentTimeMillis = SystemClock.uptimeMillis(); 321 322 if (mWriteScheduled) { 323 mHandler.removeMessages(MyHandler.MSG_PERSIST_SETTINGS); 324 325 // If enough time passed, write without holding off anymore. 326 final long timeSinceLastNotWrittenMutationMillis = currentTimeMillis 327 - mLastNotWrittenMutationTimeMillis; 328 if (timeSinceLastNotWrittenMutationMillis >= MAX_WRITE_SETTINGS_DELAY_MILLIS) { 329 mHandler.obtainMessage(MyHandler.MSG_PERSIST_SETTINGS).sendToTarget(); 330 return; 331 } 332 333 // Hold off a bit more as settings are frequently changing. 334 final long maxDelayMillis = Math.max(mLastNotWrittenMutationTimeMillis 335 + MAX_WRITE_SETTINGS_DELAY_MILLIS - currentTimeMillis, 0); 336 final long writeDelayMillis = Math.min(WRITE_SETTINGS_DELAY_MILLIS, maxDelayMillis); 337 338 Message message = mHandler.obtainMessage(MyHandler.MSG_PERSIST_SETTINGS); 339 mHandler.sendMessageDelayed(message, writeDelayMillis); 340 } else { 341 mLastNotWrittenMutationTimeMillis = currentTimeMillis; 342 Message message = mHandler.obtainMessage(MyHandler.MSG_PERSIST_SETTINGS); 343 mHandler.sendMessageDelayed(message, WRITE_SETTINGS_DELAY_MILLIS); 344 mWriteScheduled = true; 345 } 346 } 347 348 private void doWriteState() { 349 if (DEBUG_PERSISTENCE) { 350 Slog.i(LOG_TAG, "[PERSIST START]"); 351 } 352 353 AtomicFile destination = new AtomicFile(mStatePersistFile); 354 355 final int version; 356 final ArrayMap<String, Setting> settings; 357 358 synchronized (mLock) { 359 version = mVersion; 360 settings = new ArrayMap<>(mSettings); 361 mDirty = false; 362 mWriteScheduled = false; 363 } 364 365 FileOutputStream out = null; 366 try { 367 out = destination.startWrite(); 368 369 XmlSerializer serializer = Xml.newSerializer(); 370 serializer.setOutput(out, StandardCharsets.UTF_8.name()); 371 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 372 serializer.startDocument(null, true); 373 serializer.startTag(null, TAG_SETTINGS); 374 serializer.attribute(null, ATTR_VERSION, String.valueOf(version)); 375 376 final int settingCount = settings.size(); 377 for (int i = 0; i < settingCount; i++) { 378 Setting setting = settings.valueAt(i); 379 380 writeSingleSetting(mVersion, serializer, setting.getId(), setting.getName(), 381 setting.getValue(), setting.getPackageName()); 382 383 if (DEBUG_PERSISTENCE) { 384 Slog.i(LOG_TAG, "[PERSISTED]" + setting.getName() + "=" + setting.getValue()); 385 } 386 } 387 388 serializer.endTag(null, TAG_SETTINGS); 389 serializer.endDocument(); 390 destination.finishWrite(out); 391 392 if (DEBUG_PERSISTENCE) { 393 Slog.i(LOG_TAG, "[PERSIST END]"); 394 } 395 } catch (Throwable t) { 396 Slog.wtf(LOG_TAG, "Failed to write settings, restoring backup", t); 397 destination.failWrite(out); 398 } finally { 399 IoUtils.closeQuietly(out); 400 } 401 } 402 403 static void writeSingleSetting(int version, XmlSerializer serializer, String id, 404 String name, String value, String packageName) throws IOException { 405 if (id == null || isBinary(id) || name == null || isBinary(name) 406 || packageName == null || isBinary(packageName)) { 407 // This shouldn't happen. 408 return; 409 } 410 serializer.startTag(null, TAG_SETTING); 411 serializer.attribute(null, ATTR_ID, id); 412 serializer.attribute(null, ATTR_NAME, name); 413 setValueAttribute(version, serializer, value); 414 serializer.attribute(null, ATTR_PACKAGE, packageName); 415 serializer.endTag(null, TAG_SETTING); 416 } 417 418 static void setValueAttribute(int version, XmlSerializer serializer, String value) 419 throws IOException { 420 if (version >= SETTINGS_VERSOIN_NEW_ENCODING) { 421 if (value == null) { 422 // Null value -> No ATTR_VALUE nor ATTR_VALUE_BASE64. 423 } else if (isBinary(value)) { 424 serializer.attribute(null, ATTR_VALUE_BASE64, base64Encode(value)); 425 } else { 426 serializer.attribute(null, ATTR_VALUE, value); 427 } 428 } else { 429 // Old encoding. 430 if (value == null) { 431 serializer.attribute(null, ATTR_VALUE, NULL_VALUE_OLD_STYLE); 432 } else { 433 serializer.attribute(null, ATTR_VALUE, value); 434 } 435 } 436 } 437 438 private String getValueAttribute(XmlPullParser parser) { 439 if (mVersion >= SETTINGS_VERSOIN_NEW_ENCODING) { 440 final String value = parser.getAttributeValue(null, ATTR_VALUE); 441 if (value != null) { 442 return value; 443 } 444 final String base64 = parser.getAttributeValue(null, ATTR_VALUE_BASE64); 445 if (base64 != null) { 446 return base64Decode(base64); 447 } 448 // null has neither ATTR_VALUE nor ATTR_VALUE_BASE64. 449 return null; 450 } else { 451 // Old encoding. 452 final String stored = parser.getAttributeValue(null, ATTR_VALUE); 453 if (NULL_VALUE_OLD_STYLE.equals(stored)) { 454 return null; 455 } else { 456 return stored; 457 } 458 } 459 } 460 461 private void readStateSyncLocked() { 462 FileInputStream in; 463 if (!mStatePersistFile.exists()) { 464 return; 465 } 466 try { 467 in = new AtomicFile(mStatePersistFile).openRead(); 468 } catch (FileNotFoundException fnfe) { 469 Slog.i(LOG_TAG, "No settings state"); 470 return; 471 } 472 try { 473 XmlPullParser parser = Xml.newPullParser(); 474 parser.setInput(in, StandardCharsets.UTF_8.name()); 475 parseStateLocked(parser); 476 477 } catch (XmlPullParserException | IOException e) { 478 throw new IllegalStateException("Failed parsing settings file: " 479 + mStatePersistFile , e); 480 } finally { 481 IoUtils.closeQuietly(in); 482 } 483 } 484 485 private void parseStateLocked(XmlPullParser parser) 486 throws IOException, XmlPullParserException { 487 final int outerDepth = parser.getDepth(); 488 int type; 489 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 490 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 491 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { 492 continue; 493 } 494 495 String tagName = parser.getName(); 496 if (tagName.equals(TAG_SETTINGS)) { 497 parseSettingsLocked(parser); 498 } 499 } 500 } 501 502 private void parseSettingsLocked(XmlPullParser parser) 503 throws IOException, XmlPullParserException { 504 505 mVersion = Integer.parseInt(parser.getAttributeValue(null, ATTR_VERSION)); 506 507 final int outerDepth = parser.getDepth(); 508 int type; 509 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 510 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 511 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { 512 continue; 513 } 514 515 String tagName = parser.getName(); 516 if (tagName.equals(TAG_SETTING)) { 517 String id = parser.getAttributeValue(null, ATTR_ID); 518 String name = parser.getAttributeValue(null, ATTR_NAME); 519 String value = getValueAttribute(parser); 520 String packageName = parser.getAttributeValue(null, ATTR_PACKAGE); 521 mSettings.put(name, new Setting(name, value, packageName, id)); 522 523 if (DEBUG_PERSISTENCE) { 524 Slog.i(LOG_TAG, "[RESTORED] " + name + "=" + value); 525 } 526 } 527 } 528 } 529 530 private final class MyHandler extends Handler { 531 public static final int MSG_PERSIST_SETTINGS = 1; 532 533 public MyHandler() { 534 super(BackgroundThread.getHandler().getLooper()); 535 } 536 537 @Override 538 public void handleMessage(Message message) { 539 switch (message.what) { 540 case MSG_PERSIST_SETTINGS: { 541 Runnable callback = (Runnable) message.obj; 542 doWriteState(); 543 if (callback != null) { 544 callback.run(); 545 } 546 } 547 break; 548 } 549 } 550 } 551 552 public final class Setting { 553 private String name; 554 private String value; 555 private String packageName; 556 private String id; 557 558 public Setting(String name, String value, String packageName) { 559 init(name, value, packageName, String.valueOf(mNextId++)); 560 } 561 562 public Setting(String name, String value, String packageName, String id) { 563 mNextId = Math.max(mNextId, Long.valueOf(id) + 1); 564 init(name, value, packageName, id); 565 } 566 567 private void init(String name, String value, String packageName, String id) { 568 this.name = name; 569 this.value = value; 570 this.packageName = packageName; 571 this.id = id; 572 } 573 574 public String getName() { 575 return name; 576 } 577 578 public String getValue() { 579 return value; 580 } 581 582 public String getPackageName() { 583 return packageName; 584 } 585 586 public String getId() { 587 return id; 588 } 589 590 public boolean update(String value, String packageName) { 591 if (Objects.equal(value, this.value)) { 592 return false; 593 } 594 this.value = value; 595 this.packageName = packageName; 596 this.id = String.valueOf(mNextId++); 597 return true; 598 } 599 } 600 601 /** 602 * @return TRUE if a string is considered "binary" from KXML's point of view. NOTE DO NOT 603 * pass null. 604 */ 605 public static boolean isBinary(String s) { 606 if (s == null) { 607 throw new NullPointerException(); 608 } 609 // See KXmlSerializer.writeEscaped 610 for (int i = 0; i < s.length(); i++) { 611 char c = s.charAt(i); 612 boolean allowedInXml = (c >= 0x20 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xfffd); 613 if (!allowedInXml) { 614 return true; 615 } 616 } 617 return false; 618 } 619 620 private static String base64Encode(String s) { 621 return Base64.encodeToString(toBytes(s), Base64.NO_WRAP); 622 } 623 624 private static String base64Decode(String s) { 625 return fromBytes(Base64.decode(s, Base64.DEFAULT)); 626 } 627 628 // Note the followings are basically just UTF-16 encode/decode. But we want to preserve 629 // contents as-is, even if it contains broken surrogate pairs, we do it by ourselves, 630 // since I don't know how Charset would treat them. 631 632 private static byte[] toBytes(String s) { 633 final byte[] result = new byte[s.length() * 2]; 634 int resultIndex = 0; 635 for (int i = 0; i < s.length(); ++i) { 636 char ch = s.charAt(i); 637 result[resultIndex++] = (byte) (ch >> 8); 638 result[resultIndex++] = (byte) ch; 639 } 640 return result; 641 } 642 643 private static String fromBytes(byte[] bytes) { 644 final StringBuffer sb = new StringBuffer(bytes.length / 2); 645 646 final int last = bytes.length - 1; 647 648 for (int i = 0; i < last; i += 2) { 649 final char ch = (char) ((bytes[i] & 0xff) << 8 | (bytes[i + 1] & 0xff)); 650 sb.append(ch); 651 } 652 return sb.toString(); 653 } 654} 655