SettingsState.java revision 683914bfb13908bf380a25258cd45bcf43f13dc9
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.Slog; 27import android.util.Xml; 28import com.android.internal.annotations.GuardedBy; 29import com.android.internal.os.BackgroundThread; 30import libcore.io.IoUtils; 31import libcore.util.Objects; 32import org.xmlpull.v1.XmlPullParser; 33import org.xmlpull.v1.XmlPullParserException; 34import org.xmlpull.v1.XmlSerializer; 35 36import java.io.File; 37import java.io.FileInputStream; 38import java.io.FileNotFoundException; 39import java.io.FileOutputStream; 40import java.io.IOException; 41import java.util.ArrayList; 42import java.util.List; 43 44/** 45 * This class contains the state for one type of settings. It is responsible 46 * for saving the state asynchronously to an XML file after a mutation and 47 * loading the from an XML file on construction. 48 * <p> 49 * This class uses the same lock as the settings provider to ensure that 50 * multiple changes made by the settings provider, e,g, upgrade, bulk insert, 51 * etc, are atomically persisted since the asynchronous persistence is using 52 * the same lock to grab the current state to write to disk. 53 * </p> 54 */ 55final class SettingsState { 56 private static final boolean DEBUG = false; 57 private static final boolean DEBUG_PERSISTENCE = false; 58 59 private static final String LOG_TAG = "SettingsState"; 60 61 private static final long WRITE_SETTINGS_DELAY_MILLIS = 200; 62 private static final long MAX_WRITE_SETTINGS_DELAY_MILLIS = 2000; 63 64 public static final int MAX_BYTES_PER_APP_PACKAGE_UNLIMITED = -1; 65 public static final int MAX_BYTES_PER_APP_PACKAGE_LIMITED = 20000; 66 67 public static final String SYSTEM_PACKAGE_NAME = "android"; 68 69 public static final int VERSION_UNDEFINED = -1; 70 71 private static final String TAG_SETTINGS = "settings"; 72 private static final String TAG_SETTING = "setting"; 73 private static final String ATTR_PACKAGE = "package"; 74 75 private static final String ATTR_VERSION = "version"; 76 private static final String ATTR_ID = "id"; 77 private static final String ATTR_NAME = "name"; 78 private static final String ATTR_VALUE = "value"; 79 80 private static final String NULL_VALUE = "null"; 81 82 private final Object mLock; 83 84 private final Handler mHandler = new MyHandler(); 85 86 @GuardedBy("mLock") 87 private final ArrayMap<String, Setting> mSettings = new ArrayMap<>(); 88 89 @GuardedBy("mLock") 90 private final ArrayMap<String, Integer> mPackageToMemoryUsage; 91 92 @GuardedBy("mLock") 93 private final int mMaxBytesPerAppPackage; 94 95 @GuardedBy("mLock") 96 private final File mStatePersistFile; 97 98 public final int mKey; 99 100 @GuardedBy("mLock") 101 private int mVersion = VERSION_UNDEFINED; 102 103 @GuardedBy("mLock") 104 private long mLastNotWrittenMutationTimeMillis; 105 106 @GuardedBy("mLock") 107 private boolean mDirty; 108 109 @GuardedBy("mLock") 110 private boolean mWriteScheduled; 111 112 public SettingsState(Object lock, File file, int key, int maxBytesPerAppPackage) { 113 // It is important that we use the same lock as the settings provider 114 // to ensure multiple mutations on this state are atomicaly persisted 115 // as the async persistence should be blocked while we make changes. 116 mLock = lock; 117 mStatePersistFile = file; 118 mKey = key; 119 if (maxBytesPerAppPackage == MAX_BYTES_PER_APP_PACKAGE_LIMITED) { 120 mMaxBytesPerAppPackage = maxBytesPerAppPackage; 121 mPackageToMemoryUsage = new ArrayMap<>(); 122 } else { 123 mMaxBytesPerAppPackage = maxBytesPerAppPackage; 124 mPackageToMemoryUsage = null; 125 } 126 synchronized (mLock) { 127 readStateSyncLocked(); 128 } 129 } 130 131 // The settings provider must hold its lock when calling here. 132 public int getVersionLocked() { 133 return mVersion; 134 } 135 136 // The settings provider must hold its lock when calling here. 137 public void setVersionLocked(int version) { 138 if (version == mVersion) { 139 return; 140 } 141 mVersion = version; 142 143 scheduleWriteIfNeededLocked(); 144 } 145 146 // The settings provider must hold its lock when calling here. 147 public void onPackageRemovedLocked(String packageName) { 148 boolean removedSomething = false; 149 150 final int settingCount = mSettings.size(); 151 for (int i = settingCount - 1; i >= 0; i--) { 152 String name = mSettings.keyAt(i); 153 // Settings defined by use are never dropped. 154 if (Settings.System.PUBLIC_SETTINGS.contains(name) 155 || Settings.System.PRIVATE_SETTINGS.contains(name)) { 156 continue; 157 } 158 Setting setting = mSettings.valueAt(i); 159 if (packageName.equals(setting.packageName)) { 160 mSettings.removeAt(i); 161 removedSomething = true; 162 } 163 } 164 165 if (removedSomething) { 166 scheduleWriteIfNeededLocked(); 167 } 168 } 169 170 // The settings provider must hold its lock when calling here. 171 public List<String> getSettingNamesLocked() { 172 ArrayList<String> names = new ArrayList<>(); 173 final int settingsCount = mSettings.size(); 174 for (int i = 0; i < settingsCount; i++) { 175 String name = mSettings.keyAt(i); 176 names.add(name); 177 } 178 return names; 179 } 180 181 // The settings provider must hold its lock when calling here. 182 public Setting getSettingLocked(String name) { 183 if (TextUtils.isEmpty(name)) { 184 return null; 185 } 186 return mSettings.get(name); 187 } 188 189 // The settings provider must hold its lock when calling here. 190 public boolean updateSettingLocked(String name, String value, String packageName) { 191 if (!hasSettingLocked(name)) { 192 return false; 193 } 194 195 return insertSettingLocked(name, value, packageName); 196 } 197 198 // The settings provider must hold its lock when calling here. 199 public boolean insertSettingLocked(String name, String value, String packageName) { 200 if (TextUtils.isEmpty(name)) { 201 return false; 202 } 203 204 Setting oldState = mSettings.get(name); 205 String oldValue = (oldState != null) ? oldState.value : null; 206 207 if (oldState != null) { 208 if (!oldState.update(value, packageName)) { 209 return false; 210 } 211 } else { 212 Setting state = new Setting(name, value, packageName); 213 mSettings.put(name, state); 214 } 215 216 updateMemoryUsagePerPackageLocked(packageName, oldValue, value); 217 218 scheduleWriteIfNeededLocked(); 219 220 return true; 221 } 222 223 // The settings provider must hold its lock when calling here. 224 public void persistSyncLocked() { 225 mHandler.removeMessages(MyHandler.MSG_PERSIST_SETTINGS); 226 doWriteState(); 227 } 228 229 // The settings provider must hold its lock when calling here. 230 public boolean deleteSettingLocked(String name) { 231 if (TextUtils.isEmpty(name) || !hasSettingLocked(name)) { 232 return false; 233 } 234 235 Setting oldState = mSettings.remove(name); 236 237 updateMemoryUsagePerPackageLocked(oldState.packageName, oldState.value, null); 238 239 scheduleWriteIfNeededLocked(); 240 241 return true; 242 } 243 244 // The settings provider must hold its lock when calling here. 245 public void destroyLocked(Runnable callback) { 246 mHandler.removeMessages(MyHandler.MSG_PERSIST_SETTINGS); 247 if (callback != null) { 248 if (mDirty) { 249 // Do it without a delay. 250 mHandler.obtainMessage(MyHandler.MSG_PERSIST_SETTINGS, 251 callback).sendToTarget(); 252 return; 253 } 254 callback.run(); 255 } 256 } 257 258 private void updateMemoryUsagePerPackageLocked(String packageName, String oldValue, 259 String newValue) { 260 if (mMaxBytesPerAppPackage == MAX_BYTES_PER_APP_PACKAGE_UNLIMITED) { 261 return; 262 } 263 264 if (SYSTEM_PACKAGE_NAME.equals(packageName)) { 265 return; 266 } 267 268 final int oldValueSize = (oldValue != null) ? oldValue.length() : 0; 269 final int newValueSize = (newValue != null) ? newValue.length() : 0; 270 final int deltaSize = newValueSize - oldValueSize; 271 272 Integer currentSize = mPackageToMemoryUsage.get(packageName); 273 final int newSize = Math.max((currentSize != null) 274 ? currentSize + deltaSize : deltaSize, 0); 275 276 if (newSize > mMaxBytesPerAppPackage) { 277 throw new IllegalStateException("You are adding too many system settings. " 278 + "You should stop using system settings for app specific data."); 279 } 280 281 if (DEBUG) { 282 Slog.i(LOG_TAG, "Settings for package: " + packageName 283 + " size: " + newSize + " bytes."); 284 } 285 286 mPackageToMemoryUsage.put(packageName, newSize); 287 } 288 289 private boolean hasSettingLocked(String name) { 290 return mSettings.indexOfKey(name) >= 0; 291 } 292 293 private void scheduleWriteIfNeededLocked() { 294 // If dirty then we have a write already scheduled. 295 if (!mDirty) { 296 mDirty = true; 297 writeStateAsyncLocked(); 298 } 299 } 300 301 private void writeStateAsyncLocked() { 302 final long currentTimeMillis = SystemClock.uptimeMillis(); 303 304 if (mWriteScheduled) { 305 mHandler.removeMessages(MyHandler.MSG_PERSIST_SETTINGS); 306 307 // If enough time passed, write without holding off anymore. 308 final long timeSinceLastNotWrittenMutationMillis = currentTimeMillis 309 - mLastNotWrittenMutationTimeMillis; 310 if (timeSinceLastNotWrittenMutationMillis >= MAX_WRITE_SETTINGS_DELAY_MILLIS) { 311 mHandler.obtainMessage(MyHandler.MSG_PERSIST_SETTINGS).sendToTarget(); 312 return; 313 } 314 315 // Hold off a bit more as settings are frequently changing. 316 final long maxDelayMillis = Math.max(mLastNotWrittenMutationTimeMillis 317 + MAX_WRITE_SETTINGS_DELAY_MILLIS - currentTimeMillis, 0); 318 final long writeDelayMillis = Math.min(WRITE_SETTINGS_DELAY_MILLIS, maxDelayMillis); 319 320 Message message = mHandler.obtainMessage(MyHandler.MSG_PERSIST_SETTINGS); 321 mHandler.sendMessageDelayed(message, writeDelayMillis); 322 } else { 323 mLastNotWrittenMutationTimeMillis = currentTimeMillis; 324 Message message = mHandler.obtainMessage(MyHandler.MSG_PERSIST_SETTINGS); 325 mHandler.sendMessageDelayed(message, WRITE_SETTINGS_DELAY_MILLIS); 326 mWriteScheduled = true; 327 } 328 } 329 330 private void doWriteState() { 331 if (DEBUG_PERSISTENCE) { 332 Slog.i(LOG_TAG, "[PERSIST START]"); 333 } 334 335 AtomicFile destination = new AtomicFile(mStatePersistFile); 336 337 final int version; 338 final ArrayMap<String, Setting> settings; 339 340 synchronized (mLock) { 341 version = mVersion; 342 settings = new ArrayMap<>(mSettings); 343 mDirty = false; 344 mWriteScheduled = false; 345 } 346 347 FileOutputStream out = null; 348 try { 349 out = destination.startWrite(); 350 351 XmlSerializer serializer = Xml.newSerializer(); 352 serializer.setOutput(out, "utf-8"); 353 serializer.startDocument(null, true); 354 serializer.startTag(null, TAG_SETTINGS); 355 serializer.attribute(null, ATTR_VERSION, String.valueOf(version)); 356 357 final int settingCount = settings.size(); 358 for (int i = 0; i < settingCount; i++) { 359 Setting setting = settings.valueAt(i); 360 361 serializer.startTag(null, TAG_SETTING); 362 serializer.attribute(null, ATTR_ID, setting.getId()); 363 serializer.attribute(null, ATTR_NAME, setting.getName()); 364 serializer.attribute(null, ATTR_VALUE, packValue(setting.getValue())); 365 serializer.attribute(null, ATTR_PACKAGE, packValue(setting.getPackageName())); 366 serializer.endTag(null, TAG_SETTING); 367 368 if (DEBUG_PERSISTENCE) { 369 Slog.i(LOG_TAG, "[PERSISTED]" + setting.getName() + "=" + setting.getValue()); 370 } 371 } 372 373 serializer.endTag(null, TAG_SETTINGS); 374 serializer.endDocument(); 375 destination.finishWrite(out); 376 377 if (DEBUG_PERSISTENCE) { 378 Slog.i(LOG_TAG, "[PERSIST END]"); 379 } 380 381 } catch (IOException e) { 382 Slog.w(LOG_TAG, "Failed to write settings, restoring backup", e); 383 destination.failWrite(out); 384 } finally { 385 IoUtils.closeQuietly(out); 386 } 387 } 388 389 private void readStateSyncLocked() { 390 FileInputStream in; 391 if (!mStatePersistFile.exists()) { 392 return; 393 } 394 try { 395 in = new FileInputStream(mStatePersistFile); 396 } catch (FileNotFoundException fnfe) { 397 Slog.i(LOG_TAG, "No settings state"); 398 return; 399 } 400 try { 401 XmlPullParser parser = Xml.newPullParser(); 402 parser.setInput(in, null); 403 parseStateLocked(parser); 404 } catch (XmlPullParserException | IOException ise) { 405 throw new IllegalStateException("Failed parsing settings file: " 406 + mStatePersistFile , ise); 407 } finally { 408 IoUtils.closeQuietly(in); 409 } 410 } 411 412 private void parseStateLocked(XmlPullParser parser) 413 throws IOException, XmlPullParserException { 414 parser.next(); 415 skipEmptyTextTags(parser); 416 expect(parser, XmlPullParser.START_TAG, TAG_SETTINGS); 417 418 mVersion = Integer.parseInt(parser.getAttributeValue(null, ATTR_VERSION)); 419 420 parser.next(); 421 422 while (parseSettingLocked(parser)) { 423 parser.next(); 424 } 425 426 skipEmptyTextTags(parser); 427 expect(parser, XmlPullParser.END_TAG, TAG_SETTINGS); 428 } 429 430 private boolean parseSettingLocked(XmlPullParser parser) 431 throws IOException, XmlPullParserException { 432 skipEmptyTextTags(parser); 433 if (!accept(parser, XmlPullParser.START_TAG, TAG_SETTING)) { 434 return false; 435 } 436 437 String id = parser.getAttributeValue(null, ATTR_ID); 438 String name = parser.getAttributeValue(null, ATTR_NAME); 439 String value = parser.getAttributeValue(null, ATTR_VALUE); 440 String packageName = parser.getAttributeValue(null, ATTR_PACKAGE); 441 mSettings.put(name, new Setting(name, unpackValue(value), 442 unpackValue(packageName), id)); 443 444 if (DEBUG_PERSISTENCE) { 445 Slog.i(LOG_TAG, "[RESTORED] " + name + "=" + value); 446 } 447 448 parser.next(); 449 450 skipEmptyTextTags(parser); 451 expect(parser, XmlPullParser.END_TAG, TAG_SETTING); 452 453 return true; 454 } 455 456 private void expect(XmlPullParser parser, int type, String tag) 457 throws IOException, XmlPullParserException { 458 if (!accept(parser, type, tag)) { 459 throw new XmlPullParserException("Expected event: " + type 460 + " and tag: " + tag + " but got event: " + parser.getEventType() 461 + " and tag:" + parser.getName()); 462 } 463 } 464 465 private void skipEmptyTextTags(XmlPullParser parser) 466 throws IOException, XmlPullParserException { 467 while (accept(parser, XmlPullParser.TEXT, null) 468 && "\n".equals(parser.getText())) { 469 parser.next(); 470 } 471 } 472 473 private boolean accept(XmlPullParser parser, int type, String tag) 474 throws IOException, XmlPullParserException { 475 if (parser.getEventType() != type) { 476 return false; 477 } 478 if (tag != null) { 479 if (!tag.equals(parser.getName())) { 480 return false; 481 } 482 } else if (parser.getName() != null) { 483 return false; 484 } 485 return true; 486 } 487 488 private final class MyHandler extends Handler { 489 public static final int MSG_PERSIST_SETTINGS = 1; 490 491 public MyHandler() { 492 super(BackgroundThread.getHandler().getLooper()); 493 } 494 495 @Override 496 public void handleMessage(Message message) { 497 switch (message.what) { 498 case MSG_PERSIST_SETTINGS: { 499 Runnable callback = (Runnable) message.obj; 500 doWriteState(); 501 if (callback != null) { 502 callback.run(); 503 } 504 } 505 break; 506 } 507 } 508 } 509 510 private static String packValue(String value) { 511 if (value == null) { 512 return NULL_VALUE; 513 } 514 return value; 515 } 516 517 private static String unpackValue(String value) { 518 if (NULL_VALUE.equals(value)) { 519 return null; 520 } 521 return value; 522 } 523 524 public static final class Setting { 525 private static long sNextId; 526 527 private String name; 528 private String value; 529 private String packageName; 530 private String id; 531 532 public Setting(String name, String value, String packageName) { 533 init(name, value, packageName, String.valueOf(sNextId++)); 534 } 535 536 public Setting(String name, String value, String packageName, String id) { 537 sNextId = Math.max(sNextId, Long.valueOf(id)); 538 init(name, value, packageName, String.valueOf(sNextId)); 539 } 540 541 private void init(String name, String value, String packageName, String id) { 542 this.name = name; 543 this.value = value; 544 this.packageName = packageName; 545 this.id = id; 546 } 547 548 public String getName() { 549 return name; 550 } 551 552 public String getValue() { 553 return value; 554 } 555 556 public String getPackageName() { 557 return packageName; 558 } 559 560 public String getId() { 561 return id; 562 } 563 564 public boolean update(String value, String packageName) { 565 if (Objects.equal(value, this.value)) { 566 return false; 567 } 568 this.value = value; 569 this.packageName = packageName; 570 this.id = String.valueOf(sNextId++); 571 return true; 572 } 573 } 574} 575