/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.providers.settings;

import static android.os.Process.FIRST_APPLICATION_UID;

import android.annotation.NonNull;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.Signature;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.providers.settings.GlobalSettingsProto;
import android.providers.settings.SettingsOperationProto;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.AtomicFile;
import android.util.Base64;
import android.util.Slog;
import android.util.SparseIntArray;
import android.util.StatsLog;
import android.util.TimeUtils;
import android.util.Xml;
import android.util.proto.ProtoOutputStream;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import com.android.server.LocalServices;

import libcore.io.IoUtils;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;

This class contains the state for one type of settings. It is responsible for saving the state asynchronously to an XML file after a mutation and loading the from an XML file on construction.

This class uses the same lock as the settings provider to ensure that multiple changes made by the settings provider, e,g, upgrade, bulk insert, etc, are atomically persisted since the asynchronous persistence is using the same lock to grab the current state to write to disk.

/** * This class contains the state for one type of settings. It is responsible * for saving the state asynchronously to an XML file after a mutation and * loading the from an XML file on construction. * <p> * This class uses the same lock as the settings provider to ensure that * multiple changes made by the settings provider, e,g, upgrade, bulk insert, * etc, are atomically persisted since the asynchronous persistence is using * the same lock to grab the current state to write to disk. * </p> */
final class SettingsState { private static final boolean DEBUG = false; private static final boolean DEBUG_PERSISTENCE = false; private static final String LOG_TAG = "SettingsState"; static final String SYSTEM_PACKAGE_NAME = "android"; static final int SETTINGS_VERSION_NEW_ENCODING = 121; private static final long WRITE_SETTINGS_DELAY_MILLIS = 200; private static final long MAX_WRITE_SETTINGS_DELAY_MILLIS = 2000; public static final int MAX_BYTES_PER_APP_PACKAGE_UNLIMITED = -1; public static final int MAX_BYTES_PER_APP_PACKAGE_LIMITED = 20000; public static final int VERSION_UNDEFINED = -1; private static final String TAG_SETTINGS = "settings"; private static final String TAG_SETTING = "setting"; private static final String ATTR_PACKAGE = "package"; private static final String ATTR_DEFAULT_SYS_SET = "defaultSysSet"; private static final String ATTR_TAG = "tag"; private static final String ATTR_TAG_BASE64 = "tagBase64"; private static final String ATTR_VERSION = "version"; private static final String ATTR_ID = "id"; private static final String ATTR_NAME = "name";
Non-binary value will be written in this attributes.
/** * Non-binary value will be written in this attributes. */
private static final String ATTR_VALUE = "value"; private static final String ATTR_DEFAULT_VALUE = "defaultValue";
KXmlSerializer won't like some characters. We encode such characters in base64 and store in this attribute. NOTE: A null value will have *neither* ATTR_VALUE nor ATTR_VALUE_BASE64.
/** * KXmlSerializer won't like some characters. We encode such characters * in base64 and store in this attribute. * NOTE: A null value will have *neither* ATTR_VALUE nor ATTR_VALUE_BASE64. */
private static final String ATTR_VALUE_BASE64 = "valueBase64"; private static final String ATTR_DEFAULT_VALUE_BASE64 = "defaultValueBase64"; // This was used in version 120 and before. private static final String NULL_VALUE_OLD_STYLE = "null"; private static final int HISTORICAL_OPERATION_COUNT = 20; private static final String HISTORICAL_OPERATION_UPDATE = "update"; private static final String HISTORICAL_OPERATION_DELETE = "delete"; private static final String HISTORICAL_OPERATION_PERSIST = "persist"; private static final String HISTORICAL_OPERATION_INITIALIZE = "initialize"; private static final String HISTORICAL_OPERATION_RESET = "reset"; private static final String SHELL_PACKAGE_NAME = "com.android.shell"; private static final String ROOT_PACKAGE_NAME = "root"; private static final String NULL_VALUE = "null"; private static final Object sLock = new Object(); @GuardedBy("sLock") private static final SparseIntArray sSystemUids = new SparseIntArray(); @GuardedBy("sLock") private static Signature sSystemSignature; private final Object mWriteLock = new Object(); private final Object mLock; private final Handler mHandler; @GuardedBy("mLock") private final Context mContext; @GuardedBy("mLock") private final ArrayMap<String, Setting> mSettings = new ArrayMap<>(); @GuardedBy("mLock") private final ArrayMap<String, Integer> mPackageToMemoryUsage; @GuardedBy("mLock") private final int mMaxBytesPerAppPackage; @GuardedBy("mLock") private final File mStatePersistFile; @GuardedBy("mLock") private final String mStatePersistTag; private final Setting mNullSetting = new Setting(null, null, false, null, null) { @Override public boolean isNull() { return true; } }; @GuardedBy("mLock") private final List<HistoricalOperation> mHistoricalOperations; @GuardedBy("mLock") public final int mKey; @GuardedBy("mLock") private int mVersion = VERSION_UNDEFINED; @GuardedBy("mLock") private long mLastNotWrittenMutationTimeMillis; @GuardedBy("mLock") private boolean mDirty; @GuardedBy("mLock") private boolean mWriteScheduled; @GuardedBy("mLock") private long mNextId; @GuardedBy("mLock") private int mNextHistoricalOpIdx; public static final int SETTINGS_TYPE_GLOBAL = 0; public static final int SETTINGS_TYPE_SYSTEM = 1; public static final int SETTINGS_TYPE_SECURE = 2; public static final int SETTINGS_TYPE_SSAID = 3; public static final int SETTINGS_TYPE_MASK = 0xF0000000; public static final int SETTINGS_TYPE_SHIFT = 28; public static int makeKey(int type, int userId) { return (type << SETTINGS_TYPE_SHIFT) | userId; } public static int getTypeFromKey(int key) { return key >>> SETTINGS_TYPE_SHIFT; } public static int getUserIdFromKey(int key) { return key & ~SETTINGS_TYPE_MASK; } public static String settingTypeToString(int type) { switch (type) { case SETTINGS_TYPE_GLOBAL: { return "SETTINGS_GLOBAL"; } case SETTINGS_TYPE_SECURE: { return "SETTINGS_SECURE"; } case SETTINGS_TYPE_SYSTEM: { return "SETTINGS_SYSTEM"; } case SETTINGS_TYPE_SSAID: { return "SETTINGS_SSAID"; } default: { return "UNKNOWN"; } } } public static String keyToString(int key) { return "Key[user=" + getUserIdFromKey(key) + ";type=" + settingTypeToString(getTypeFromKey(key)) + "]"; } public SettingsState(Context context, Object lock, File file, int key, int maxBytesPerAppPackage, Looper looper) { // It is important that we use the same lock as the settings provider // to ensure multiple mutations on this state are atomicaly persisted // as the async persistence should be blocked while we make changes. mContext = context; mLock = lock; mStatePersistFile = file; mStatePersistTag = "settings-" + getTypeFromKey(key) + "-" + getUserIdFromKey(key); mKey = key; mHandler = new MyHandler(looper); if (maxBytesPerAppPackage == MAX_BYTES_PER_APP_PACKAGE_LIMITED) { mMaxBytesPerAppPackage = maxBytesPerAppPackage; mPackageToMemoryUsage = new ArrayMap<>(); } else { mMaxBytesPerAppPackage = maxBytesPerAppPackage; mPackageToMemoryUsage = null; } mHistoricalOperations = Build.IS_DEBUGGABLE ? new ArrayList<>(HISTORICAL_OPERATION_COUNT) : null; synchronized (mLock) { readStateSyncLocked(); } } // The settings provider must hold its lock when calling here. public int getVersionLocked() { return mVersion; } public Setting getNullSetting() { return mNullSetting; } // The settings provider must hold its lock when calling here. public void setVersionLocked(int version) { if (version == mVersion) { return; } mVersion = version; scheduleWriteIfNeededLocked(); } // The settings provider must hold its lock when calling here. public void onPackageRemovedLocked(String packageName) { boolean removedSomething = false; final int settingCount = mSettings.size(); for (int i = settingCount - 1; i >= 0; i--) { String name = mSettings.keyAt(i); // Settings defined by us are never dropped. if (Settings.System.PUBLIC_SETTINGS.contains(name) || Settings.System.PRIVATE_SETTINGS.contains(name)) { continue; } Setting setting = mSettings.valueAt(i); if (packageName.equals(setting.packageName)) { mSettings.removeAt(i); removedSomething = true; } } if (removedSomething) { scheduleWriteIfNeededLocked(); } } // The settings provider must hold its lock when calling here. public List<String> getSettingNamesLocked() { ArrayList<String> names = new ArrayList<>(); final int settingsCount = mSettings.size(); for (int i = 0; i < settingsCount; i++) { String name = mSettings.keyAt(i); names.add(name); } return names; } // The settings provider must hold its lock when calling here. public Setting getSettingLocked(String name) { if (TextUtils.isEmpty(name)) { return mNullSetting; } Setting setting = mSettings.get(name); if (setting != null) { return new Setting(setting); } return mNullSetting; } // The settings provider must hold its lock when calling here. public boolean updateSettingLocked(String name, String value, String tag, boolean makeValue, String packageName) { if (!hasSettingLocked(name)) { return false; } return insertSettingLocked(name, value, tag, makeValue, packageName); } // The settings provider must hold its lock when calling here. public void resetSettingDefaultValueLocked(String name) { Setting oldSetting = getSettingLocked(name); if (oldSetting != null && !oldSetting.isNull() && oldSetting.getDefaultValue() != null) { String oldValue = oldSetting.getValue(); String oldDefaultValue = oldSetting.getDefaultValue(); Setting newSetting = new Setting(name, oldSetting.getValue(), null, oldSetting.getPackageName(), oldSetting.getTag(), false, oldSetting.getId()); mSettings.put(name, newSetting); updateMemoryUsagePerPackageLocked(newSetting.getPackageName(), oldValue, newSetting.getValue(), oldDefaultValue, newSetting.getDefaultValue()); scheduleWriteIfNeededLocked(); } } // The settings provider must hold its lock when calling here. public boolean insertSettingLocked(String name, String value, String tag, boolean makeDefault, String packageName) { if (TextUtils.isEmpty(name)) { return false; } Setting oldState = mSettings.get(name); String oldValue = (oldState != null) ? oldState.value : null; String oldDefaultValue = (oldState != null) ? oldState.defaultValue : null; Setting newState; if (oldState != null) { if (!oldState.update(value, makeDefault, packageName, tag, false)) { return false; } newState = oldState; } else { newState = new Setting(name, value, makeDefault, packageName, tag); mSettings.put(name, newState); } StatsLog.write(StatsLog.SETTING_CHANGED, name, value, newState.value, oldValue, tag, makeDefault, getUserIdFromKey(mKey), StatsLog.SETTING_CHANGED__REASON__UPDATED); addHistoricalOperationLocked(HISTORICAL_OPERATION_UPDATE, newState); updateMemoryUsagePerPackageLocked(packageName, oldValue, value, oldDefaultValue, newState.getDefaultValue()); scheduleWriteIfNeededLocked(); return true; } // The settings provider must hold its lock when calling here. public void persistSyncLocked() { mHandler.removeMessages(MyHandler.MSG_PERSIST_SETTINGS); doWriteState(); } // The settings provider must hold its lock when calling here. public boolean deleteSettingLocked(String name) { if (TextUtils.isEmpty(name) || !hasSettingLocked(name)) { return false; } Setting oldState = mSettings.remove(name); StatsLog.write(StatsLog.SETTING_CHANGED, name, /* value= */ "", /* newValue= */ "", oldState.value, /* tag */ "", false, getUserIdFromKey(mKey), StatsLog.SETTING_CHANGED__REASON__DELETED); updateMemoryUsagePerPackageLocked(oldState.packageName, oldState.value, null, oldState.defaultValue, null); addHistoricalOperationLocked(HISTORICAL_OPERATION_DELETE, oldState); scheduleWriteIfNeededLocked(); return true; } // The settings provider must hold its lock when calling here. public boolean resetSettingLocked(String name) { if (TextUtils.isEmpty(name) || !hasSettingLocked(name)) { return false; } Setting setting = mSettings.get(name); Setting oldSetting = new Setting(setting); String oldValue = setting.getValue(); String oldDefaultValue = setting.getDefaultValue(); if (!setting.reset()) { return false; } String newValue = setting.getValue(); String newDefaultValue = setting.getDefaultValue(); updateMemoryUsagePerPackageLocked(setting.packageName, oldValue, newValue, oldDefaultValue, newDefaultValue); addHistoricalOperationLocked(HISTORICAL_OPERATION_RESET, oldSetting); scheduleWriteIfNeededLocked(); return true; } // The settings provider must hold its lock when calling here. public void destroyLocked(Runnable callback) { mHandler.removeMessages(MyHandler.MSG_PERSIST_SETTINGS); if (callback != null) { if (mDirty) { // Do it without a delay. mHandler.obtainMessage(MyHandler.MSG_PERSIST_SETTINGS, callback).sendToTarget(); return; } callback.run(); } } private void addHistoricalOperationLocked(String type, Setting setting) { if (mHistoricalOperations == null) { return; } HistoricalOperation operation = new HistoricalOperation( SystemClock.elapsedRealtime(), type, setting != null ? new Setting(setting) : null); if (mNextHistoricalOpIdx >= mHistoricalOperations.size()) { mHistoricalOperations.add(operation); } else { mHistoricalOperations.set(mNextHistoricalOpIdx, operation); } mNextHistoricalOpIdx++; if (mNextHistoricalOpIdx >= HISTORICAL_OPERATION_COUNT) { mNextHistoricalOpIdx = 0; } }
Dump historical operations as a proto buf.
Params:
  • proto – The proto buf stream to dump to
  • fieldId – The repeated field ID to use to save an operation to.
/** * Dump historical operations as a proto buf. * * @param proto The proto buf stream to dump to * @param fieldId The repeated field ID to use to save an operation to. */
void dumpHistoricalOperations(@NonNull ProtoOutputStream proto, long fieldId) { synchronized (mLock) { if (mHistoricalOperations == null) { return; } final int operationCount = mHistoricalOperations.size(); for (int i = 0; i < operationCount; i++) { int index = mNextHistoricalOpIdx - 1 - i; if (index < 0) { index = operationCount + index; } HistoricalOperation operation = mHistoricalOperations.get(index); final long token = proto.start(fieldId); proto.write(SettingsOperationProto.TIMESTAMP, operation.mTimestamp); proto.write(SettingsOperationProto.OPERATION, operation.mOperation); if (operation.mSetting != null) { // Only add the name of the setting, since we don't know the historical package // and values for it so they would be misleading to add here (all we could // add is what the current data is). proto.write(SettingsOperationProto.SETTING, operation.mSetting.getName()); } proto.end(token); } } } public void dumpHistoricalOperations(PrintWriter pw) { synchronized (mLock) { if (mHistoricalOperations == null) { return; } pw.println("Historical operations"); final int operationCount = mHistoricalOperations.size(); for (int i = 0; i < operationCount; i++) { int index = mNextHistoricalOpIdx - 1 - i; if (index < 0) { index = operationCount + index; } HistoricalOperation operation = mHistoricalOperations.get(index); pw.print(TimeUtils.formatForLogging(operation.mTimestamp)); pw.print(" "); pw.print(operation.mOperation); if (operation.mSetting != null) { pw.print(" "); // Only print the name of the setting, since we don't know the // historical package and values for it so they would be misleading // to print here (all we could print is what the current data is). pw.print(operation.mSetting.getName()); } pw.println(); } pw.println(); pw.println(); } } private void updateMemoryUsagePerPackageLocked(String packageName, String oldValue, String newValue, String oldDefaultValue, String newDefaultValue) { if (mMaxBytesPerAppPackage == MAX_BYTES_PER_APP_PACKAGE_UNLIMITED) { return; } if (SYSTEM_PACKAGE_NAME.equals(packageName)) { return; } final int oldValueSize = (oldValue != null) ? oldValue.length() : 0; final int newValueSize = (newValue != null) ? newValue.length() : 0; final int oldDefaultValueSize = (oldDefaultValue != null) ? oldDefaultValue.length() : 0; final int newDefaultValueSize = (newDefaultValue != null) ? newDefaultValue.length() : 0; final int deltaSize = newValueSize + newDefaultValueSize - oldValueSize - oldDefaultValueSize; Integer currentSize = mPackageToMemoryUsage.get(packageName); final int newSize = Math.max((currentSize != null) ? currentSize + deltaSize : deltaSize, 0); if (newSize > mMaxBytesPerAppPackage) { throw new IllegalStateException("You are adding too many system settings. " + "You should stop using system settings for app specific data" + " package: " + packageName); } if (DEBUG) { Slog.i(LOG_TAG, "Settings for package: " + packageName + " size: " + newSize + " bytes."); } mPackageToMemoryUsage.put(packageName, newSize); } private boolean hasSettingLocked(String name) { return mSettings.indexOfKey(name) >= 0; } private void scheduleWriteIfNeededLocked() { // If dirty then we have a write already scheduled. if (!mDirty) { mDirty = true; writeStateAsyncLocked(); } } private void writeStateAsyncLocked() { final long currentTimeMillis = SystemClock.uptimeMillis(); if (mWriteScheduled) { mHandler.removeMessages(MyHandler.MSG_PERSIST_SETTINGS); // If enough time passed, write without holding off anymore. final long timeSinceLastNotWrittenMutationMillis = currentTimeMillis - mLastNotWrittenMutationTimeMillis; if (timeSinceLastNotWrittenMutationMillis >= MAX_WRITE_SETTINGS_DELAY_MILLIS) { mHandler.obtainMessage(MyHandler.MSG_PERSIST_SETTINGS).sendToTarget(); return; } // Hold off a bit more as settings are frequently changing. final long maxDelayMillis = Math.max(mLastNotWrittenMutationTimeMillis + MAX_WRITE_SETTINGS_DELAY_MILLIS - currentTimeMillis, 0); final long writeDelayMillis = Math.min(WRITE_SETTINGS_DELAY_MILLIS, maxDelayMillis); Message message = mHandler.obtainMessage(MyHandler.MSG_PERSIST_SETTINGS); mHandler.sendMessageDelayed(message, writeDelayMillis); } else { mLastNotWrittenMutationTimeMillis = currentTimeMillis; Message message = mHandler.obtainMessage(MyHandler.MSG_PERSIST_SETTINGS); mHandler.sendMessageDelayed(message, WRITE_SETTINGS_DELAY_MILLIS); mWriteScheduled = true; } } private void doWriteState() { boolean wroteState = false; final int version; final ArrayMap<String, Setting> settings; synchronized (mLock) { version = mVersion; settings = new ArrayMap<>(mSettings); mDirty = false; mWriteScheduled = false; } synchronized (mWriteLock) { if (DEBUG_PERSISTENCE) { Slog.i(LOG_TAG, "[PERSIST START]"); } AtomicFile destination = new AtomicFile(mStatePersistFile, mStatePersistTag); FileOutputStream out = null; try { out = destination.startWrite(); XmlSerializer serializer = Xml.newSerializer(); serializer.setOutput(out, StandardCharsets.UTF_8.name()); serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); serializer.startDocument(null, true); serializer.startTag(null, TAG_SETTINGS); serializer.attribute(null, ATTR_VERSION, String.valueOf(version)); final int settingCount = settings.size(); for (int i = 0; i < settingCount; i++) { Setting setting = settings.valueAt(i); if (setting.isTransient()) { if (DEBUG_PERSISTENCE) { Slog.i(LOG_TAG, "[SKIPPED PERSISTING]" + setting.getName()); } continue; } writeSingleSetting(mVersion, serializer, setting.getId(), setting.getName(), setting.getValue(), setting.getDefaultValue(), setting.getPackageName(), setting.getTag(), setting.isDefaultFromSystem()); if (DEBUG_PERSISTENCE) { Slog.i(LOG_TAG, "[PERSISTED]" + setting.getName() + "=" + setting.getValue()); } } serializer.endTag(null, TAG_SETTINGS); serializer.endDocument(); destination.finishWrite(out); wroteState = true; if (DEBUG_PERSISTENCE) { Slog.i(LOG_TAG, "[PERSIST END]"); } } catch (Throwable t) { Slog.wtf(LOG_TAG, "Failed to write settings, restoring backup", t); destination.failWrite(out); } finally { IoUtils.closeQuietly(out); } } if (wroteState) { synchronized (mLock) { addHistoricalOperationLocked(HISTORICAL_OPERATION_PERSIST, null); } } } static void writeSingleSetting(int version, XmlSerializer serializer, String id, String name, String value, String defaultValue, String packageName, String tag, boolean defaultSysSet) throws IOException { if (id == null || isBinary(id) || name == null || isBinary(name) || packageName == null || isBinary(packageName)) { // This shouldn't happen. return; } serializer.startTag(null, TAG_SETTING); serializer.attribute(null, ATTR_ID, id); serializer.attribute(null, ATTR_NAME, name); setValueAttribute(ATTR_VALUE, ATTR_VALUE_BASE64, version, serializer, value); serializer.attribute(null, ATTR_PACKAGE, packageName); if (defaultValue != null) { setValueAttribute(ATTR_DEFAULT_VALUE, ATTR_DEFAULT_VALUE_BASE64, version, serializer, defaultValue); serializer.attribute(null, ATTR_DEFAULT_SYS_SET, Boolean.toString(defaultSysSet)); setValueAttribute(ATTR_TAG, ATTR_TAG_BASE64, version, serializer, tag); } serializer.endTag(null, TAG_SETTING); } static void setValueAttribute(String attr, String attrBase64, int version, XmlSerializer serializer, String value) throws IOException { if (version >= SETTINGS_VERSION_NEW_ENCODING) { if (value == null) { // Null value -> No ATTR_VALUE nor ATTR_VALUE_BASE64. } else if (isBinary(value)) { serializer.attribute(null, attrBase64, base64Encode(value)); } else { serializer.attribute(null, attr, value); } } else { // Old encoding. if (value == null) { serializer.attribute(null, attr, NULL_VALUE_OLD_STYLE); } else { serializer.attribute(null, attr, value); } } } private String getValueAttribute(XmlPullParser parser, String attr, String base64Attr) { if (mVersion >= SETTINGS_VERSION_NEW_ENCODING) { final String value = parser.getAttributeValue(null, attr); if (value != null) { return value; } final String base64 = parser.getAttributeValue(null, base64Attr); if (base64 != null) { return base64Decode(base64); } // null has neither ATTR_VALUE nor ATTR_VALUE_BASE64. return null; } else { // Old encoding. final String stored = parser.getAttributeValue(null, attr); if (NULL_VALUE_OLD_STYLE.equals(stored)) { return null; } else { return stored; } } } private void readStateSyncLocked() { FileInputStream in; try { in = new AtomicFile(mStatePersistFile).openRead(); } catch (FileNotFoundException fnfe) { Slog.i(LOG_TAG, "No settings state " + mStatePersistFile); addHistoricalOperationLocked(HISTORICAL_OPERATION_INITIALIZE, null); return; } try { XmlPullParser parser = Xml.newPullParser(); parser.setInput(in, StandardCharsets.UTF_8.name()); parseStateLocked(parser); } catch (XmlPullParserException | IOException e) { String message = "Failed parsing settings file: " + mStatePersistFile; Slog.wtf(LOG_TAG, message); throw new IllegalStateException(message, e); } finally { IoUtils.closeQuietly(in); } }
Uses AtomicFile to check if the file or its backup exists.
Params:
  • file – The file to check for existence
Returns:whether the original or backup exist
/** * Uses AtomicFile to check if the file or its backup exists. * @param file The file to check for existence * @return whether the original or backup exist */
public static boolean stateFileExists(File file) { AtomicFile stateFile = new AtomicFile(file); return stateFile.exists(); } private void parseStateLocked(XmlPullParser parser) throws IOException, XmlPullParserException { final int outerDepth = parser.getDepth(); int type; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { continue; } String tagName = parser.getName(); if (tagName.equals(TAG_SETTINGS)) { parseSettingsLocked(parser); } } } private void parseSettingsLocked(XmlPullParser parser) throws IOException, XmlPullParserException { mVersion = Integer.parseInt(parser.getAttributeValue(null, ATTR_VERSION)); final int outerDepth = parser.getDepth(); int type; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { continue; } String tagName = parser.getName(); if (tagName.equals(TAG_SETTING)) { String id = parser.getAttributeValue(null, ATTR_ID); String name = parser.getAttributeValue(null, ATTR_NAME); String value = getValueAttribute(parser, ATTR_VALUE, ATTR_VALUE_BASE64); String packageName = parser.getAttributeValue(null, ATTR_PACKAGE); String defaultValue = getValueAttribute(parser, ATTR_DEFAULT_VALUE, ATTR_DEFAULT_VALUE_BASE64); String tag = null; boolean fromSystem = false; if (defaultValue != null) { fromSystem = Boolean.parseBoolean(parser.getAttributeValue( null, ATTR_DEFAULT_SYS_SET)); tag = getValueAttribute(parser, ATTR_TAG, ATTR_TAG_BASE64); } mSettings.put(name, new Setting(name, value, defaultValue, packageName, tag, fromSystem, id)); if (DEBUG_PERSISTENCE) { Slog.i(LOG_TAG, "[RESTORED] " + name + "=" + value); } } } } private final class MyHandler extends Handler { public static final int MSG_PERSIST_SETTINGS = 1; public MyHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message message) { switch (message.what) { case MSG_PERSIST_SETTINGS: { Runnable callback = (Runnable) message.obj; doWriteState(); if (callback != null) { callback.run(); } } break; } } } private class HistoricalOperation { final long mTimestamp; final String mOperation; final Setting mSetting; public HistoricalOperation(long timestamp, String operation, Setting setting) { mTimestamp = timestamp; mOperation = operation; mSetting = setting; } } class Setting { private String name; private String value; private String defaultValue; private String packageName; private String id; private String tag; // Whether the default is set by the system private boolean defaultFromSystem; public Setting(Setting other) { name = other.name; value = other.value; defaultValue = other.defaultValue; packageName = other.packageName; id = other.id; defaultFromSystem = other.defaultFromSystem; tag = other.tag; } public Setting(String name, String value, boolean makeDefault, String packageName, String tag) { this.name = name; update(value, makeDefault, packageName, tag, false); } public Setting(String name, String value, String defaultValue, String packageName, String tag, boolean fromSystem, String id) { mNextId = Math.max(mNextId, Long.parseLong(id) + 1); if (NULL_VALUE.equals(value)) { value = null; } init(name, value, tag, defaultValue, packageName, fromSystem, id); } private void init(String name, String value, String tag, String defaultValue, String packageName, boolean fromSystem, String id) { this.name = name; this.value = value; this.tag = tag; this.defaultValue = defaultValue; this.packageName = packageName; this.id = id; this.defaultFromSystem = fromSystem; } public String getName() { return name; } public int getKey() { return mKey; } public String getValue() { return value; } public String getTag() { return tag; } public String getDefaultValue() { return defaultValue; } public String getPackageName() { return packageName; } public boolean isDefaultFromSystem() { return defaultFromSystem; } public String getId() { return id; } public boolean isNull() { return false; }
Returns:whether the value changed
/** @return whether the value changed */
public boolean reset() { return update(this.defaultValue, false, packageName, null, true); } public boolean isTransient() { switch (getTypeFromKey(getKey())) { case SETTINGS_TYPE_GLOBAL: return ArrayUtils.contains(Global.TRANSIENT_SETTINGS, getName()); } return false; } public boolean update(String value, boolean setDefault, String packageName, String tag, boolean forceNonSystemPackage) { if (NULL_VALUE.equals(value)) { value = null; } final boolean callerSystem = !forceNonSystemPackage && !isNull() && isSystemPackage(mContext, packageName); // Settings set by the system are always defaults. if (callerSystem) { setDefault = true; } String defaultValue = this.defaultValue; boolean defaultFromSystem = this.defaultFromSystem; if (setDefault) { if (!Objects.equals(value, this.defaultValue) && (!defaultFromSystem || callerSystem)) { defaultValue = value; // Default null means no default, so the tag is irrelevant // since it is used to reset a settings subset their defaults. // Also it is irrelevant if the system set the canonical default. if (defaultValue == null) { tag = null; defaultFromSystem = false; } } if (!defaultFromSystem && value != null) { if (callerSystem) { defaultFromSystem = true; } } } // Is something gonna change? if (Objects.equals(value, this.value) && Objects.equals(defaultValue, this.defaultValue) && Objects.equals(packageName, this.packageName) && Objects.equals(tag, this.tag) && defaultFromSystem == this.defaultFromSystem) { return false; } init(name, value, tag, defaultValue, packageName, defaultFromSystem, String.valueOf(mNextId++)); return true; } public String toString() { return "Setting{name=" + name + " value=" + value + (defaultValue != null ? " default=" + defaultValue : "") + " packageName=" + packageName + " tag=" + tag + " defaultFromSystem=" + defaultFromSystem + "}"; } }
Returns:TRUE if a string is considered "binary" from KXML's point of view. NOTE DO NOT pass null.
/** * @return TRUE if a string is considered "binary" from KXML's point of view. NOTE DO NOT * pass null. */
public static boolean isBinary(String s) { if (s == null) { throw new NullPointerException(); } // See KXmlSerializer.writeEscaped for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); boolean allowedInXml = (c >= 0x20 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xfffd); if (!allowedInXml) { return true; } } return false; } private static String base64Encode(String s) { return Base64.encodeToString(toBytes(s), Base64.NO_WRAP); } private static String base64Decode(String s) { return fromBytes(Base64.decode(s, Base64.DEFAULT)); } // Note the followings are basically just UTF-16 encode/decode. But we want to preserve // contents as-is, even if it contains broken surrogate pairs, we do it by ourselves, // since I don't know how Charset would treat them. private static byte[] toBytes(String s) { final byte[] result = new byte[s.length() * 2]; int resultIndex = 0; for (int i = 0; i < s.length(); ++i) { char ch = s.charAt(i); result[resultIndex++] = (byte) (ch >> 8); result[resultIndex++] = (byte) ch; } return result; } private static String fromBytes(byte[] bytes) { final StringBuffer sb = new StringBuffer(bytes.length / 2); final int last = bytes.length - 1; for (int i = 0; i < last; i += 2) { final char ch = (char) ((bytes[i] & 0xff) << 8 | (bytes[i + 1] & 0xff)); sb.append(ch); } return sb.toString(); } public static boolean isSystemPackage(Context context, String packageName) { return isSystemPackage(context, packageName, Binder.getCallingUid()); } public static boolean isSystemPackage(Context context, String packageName, int callingUid) { synchronized (sLock) { if (SYSTEM_PACKAGE_NAME.equals(packageName)) { return true; } // Shell and Root are not considered a part of the system if (SHELL_PACKAGE_NAME.equals(packageName) || ROOT_PACKAGE_NAME.equals(packageName)) { return false; } // Native services running as a special UID get a pass final int callingAppId = UserHandle.getAppId(callingUid); if (callingAppId < FIRST_APPLICATION_UID) { sSystemUids.put(callingAppId, callingAppId); return true; } // While some callers may have permissions to manipulate cross user // settings or some settings are stored in the parent of a managed // profile for the purpose of determining whether the other end is a // system component we need to use the user id of the caller for // pulling information about the caller from the package manager. final int callingUserId = UserHandle.getUserId(callingUid); final long identity = Binder.clearCallingIdentity(); try { final int uid; try { uid = context.getPackageManager().getPackageUidAsUser(packageName, 0, callingUserId); } catch (PackageManager.NameNotFoundException e) { return false; } // If the system or a special system UID (like telephony), done. if (UserHandle.getAppId(uid) < FIRST_APPLICATION_UID) { sSystemUids.put(uid, uid); return true; } // If already known system component, done. if (sSystemUids.indexOfKey(uid) >= 0) { return true; } // If SetupWizard, done. PackageManagerInternal packageManagerInternal = LocalServices.getService( PackageManagerInternal.class); if (packageName.equals(packageManagerInternal.getSetupWizardPackageName())) { sSystemUids.put(uid, uid); return true; } // If a persistent system app, done. PackageInfo packageInfo; try { packageInfo = context.getPackageManager().getPackageInfoAsUser( packageName, PackageManager.GET_SIGNATURES, callingUserId); if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) != 0 && (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { sSystemUids.put(uid, uid); return true; } } catch (PackageManager.NameNotFoundException e) { return false; } // Last check if system signed. if (sSystemSignature == null) { try { sSystemSignature = context.getPackageManager().getPackageInfoAsUser( SYSTEM_PACKAGE_NAME, PackageManager.GET_SIGNATURES, UserHandle.USER_SYSTEM).signatures[0]; } catch (PackageManager.NameNotFoundException e) { /* impossible */ return false; } } if (sSystemSignature.equals(packageInfo.signatures[0])) { sSystemUids.put(uid, uid); return true; } } finally { Binder.restoreCallingIdentity(identity); } return false; } } }