Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6ddc34e5 authored by Makoto Onuki's avatar Makoto Onuki Committed by Android (Google) Code Review
Browse files

Merge "Allow binary value in SettingsProvider" into mnc-dev

parents b633615e 3a2c3578
Loading
Loading
Loading
Loading
+24 −9
Original line number Diff line number Diff line
@@ -342,7 +342,7 @@ public class SettingsProvider extends ContentProvider {
        }

        String name = values.getAsString(Settings.Secure.NAME);
        if (TextUtils.isEmpty(name)) {
        if (!isKeyValid(name)) {
            return null;
        }

@@ -406,11 +406,10 @@ public class SettingsProvider extends ContentProvider {
            return 0;
        }

        if (TextUtils.isEmpty(args.name)) {
        if (!isKeyValid(args.name)) {
            return 0;
        }


        switch (args.table) {
            case TABLE_GLOBAL: {
                final int userId = UserHandle.getCallingUserId();
@@ -446,10 +445,11 @@ public class SettingsProvider extends ContentProvider {
            return 0;
        }

        String value = values.getAsString(Settings.Secure.VALUE);
        if (TextUtils.isEmpty(value)) {
        String name = values.getAsString(Settings.Secure.NAME);
        if (!isKeyValid(name)) {
            return 0;
        }
        String value = values.getAsString(Settings.Secure.VALUE);

        switch (args.table) {
            case TABLE_GLOBAL: {
@@ -525,13 +525,20 @@ public class SettingsProvider extends ContentProvider {
        final int valueColumnIdx = cursor.getColumnIndex(Settings.NameValueTable.VALUE);

        do {
            pw.append("_id:").append(cursor.getString(idColumnIdx));
            pw.append(" name:").append(cursor.getString(nameColumnIdx));
            pw.append(" value:").append(cursor.getString(valueColumnIdx));
            pw.append("_id:").append(toDumpString(cursor.getString(idColumnIdx)));
            pw.append(" name:").append(toDumpString(cursor.getString(nameColumnIdx)));
            pw.append(" value:").append(toDumpString(cursor.getString(valueColumnIdx)));
            pw.println();
        } while (cursor.moveToNext());
    }

    private static final String toDumpString(String s) {
        if (s != null) {
            return s;
        }
        return "{null}";
    }

    private void registerBroadcastReceivers() {
        IntentFilter userFilter = new IntentFilter();
        userFilter.addAction(Intent.ACTION_USER_REMOVED);
@@ -1280,6 +1287,10 @@ public class SettingsProvider extends ContentProvider {
        cursor.addRow(values);
    }

    private static boolean isKeyValid(String key) {
        return !(TextUtils.isEmpty(key) || SettingsState.isBinary(key));
    }

    private static final class Arguments {
        private static final Pattern WHERE_PATTERN_WITH_PARAM_NO_BRACKETS =
                Pattern.compile("[\\s]*name[\\s]*=[\\s]*\\?[\\s]*");
@@ -1812,7 +1823,7 @@ public class SettingsProvider extends ContentProvider {
        }

        private final class UpgradeController {
            private static final int SETTINGS_VERSION = 120;
            private static final int SETTINGS_VERSION = 121;

            private final int mUserId;

@@ -1940,6 +1951,10 @@ public class SettingsProvider extends ContentProvider {
                    currentVersion = 120;
                }

                // Before 121, we used a different string encoding logic.  We just bump the version
                // here; SettingsState knows how to handle pre-version 120 files.
                currentVersion = 121;

                // vXXX: Add new settings above this point.

                // Return the current version.
+130 −24
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.AtomicFile;
import android.util.Base64;
import android.util.Slog;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
@@ -59,6 +60,8 @@ final class SettingsState {

    private static final String LOG_TAG = "SettingsState";

    static final int SETTINGS_VERSOIN_NEW_ENCODING = 121;

    private static final long WRITE_SETTINGS_DELAY_MILLIS = 200;
    private static final long MAX_WRITE_SETTINGS_DELAY_MILLIS = 2000;

@@ -76,9 +79,19 @@ final class SettingsState {
    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 attribute. */
    private static final String ATTR_VALUE = "value";

    private static final String NULL_VALUE = "null";
    /**
     * 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";

    // This was used in version 120 and before.
    private static final String NULL_VALUE_OLD_STYLE = "null";

    private final Object mLock;

@@ -364,12 +377,8 @@ final class SettingsState {
            for (int i = 0; i < settingCount; i++) {
                Setting setting = settings.valueAt(i);

                serializer.startTag(null, TAG_SETTING);
                serializer.attribute(null, ATTR_ID, setting.getId());
                serializer.attribute(null, ATTR_NAME, setting.getName());
                serializer.attribute(null, ATTR_VALUE, packValue(setting.getValue()));
                serializer.attribute(null, ATTR_PACKAGE, packValue(setting.getPackageName()));
                serializer.endTag(null, TAG_SETTING);
                writeSingleSetting(mVersion, serializer, setting.getId(), setting.getName(),
                        setting.getValue(), setting.getPackageName());

                if (DEBUG_PERSISTENCE) {
                    Slog.i(LOG_TAG, "[PERSISTED]" + setting.getName() + "=" + setting.getValue());
@@ -394,6 +403,64 @@ final class SettingsState {
        }
    }

    static void writeSingleSetting(int version, XmlSerializer serializer, String id,
            String name, String value, String packageName) 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(version, serializer, value);
        serializer.attribute(null, ATTR_PACKAGE, packageName);
        serializer.endTag(null, TAG_SETTING);
    }

    static void setValueAttribute(int version, XmlSerializer serializer, String value)
            throws IOException {
        if (version >= SETTINGS_VERSOIN_NEW_ENCODING) {
            if (value == null) {
                // Null value -> No ATTR_VALUE nor ATTR_VALUE_BASE64.
            } else if (isBinary(value)) {
                serializer.attribute(null, ATTR_VALUE_BASE64, base64Encode(value));
            } else {
                serializer.attribute(null, ATTR_VALUE, value);
            }
        } else {
            // Old encoding.
            if (value == null) {
                serializer.attribute(null, ATTR_VALUE, NULL_VALUE_OLD_STYLE);
            } else {
                serializer.attribute(null, ATTR_VALUE, value);
            }
        }
    }

    private String getValueAttribute(XmlPullParser parser) {
        if (mVersion >= SETTINGS_VERSOIN_NEW_ENCODING) {
            final String value = parser.getAttributeValue(null, ATTR_VALUE);
            if (value != null) {
                return value;
            }
            final String base64 = parser.getAttributeValue(null, ATTR_VALUE_BASE64);
            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_VALUE);
            if (NULL_VALUE_OLD_STYLE.equals(stored)) {
                return null;
            } else {
                return stored;
            }
        }
    }

    private void readStateSyncLocked() {
        FileInputStream in;
        if (!mStatePersistFile.exists()) {
@@ -452,10 +519,9 @@ final class SettingsState {
            if (tagName.equals(TAG_SETTING)) {
                String id = parser.getAttributeValue(null, ATTR_ID);
                String name = parser.getAttributeValue(null, ATTR_NAME);
                String value = parser.getAttributeValue(null, ATTR_VALUE);
                String value = getValueAttribute(parser);
                String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
                mSettings.put(name, new Setting(name, unpackValue(value),
                        unpackValue(packageName), id));
                mSettings.put(name, new Setting(name, value, packageName, id));

                if (DEBUG_PERSISTENCE) {
                    Slog.i(LOG_TAG, "[RESTORED] " + name + "=" + value);
@@ -486,20 +552,6 @@ final class SettingsState {
        }
    }

    private static String packValue(String value) {
        if (value == null) {
            return NULL_VALUE;
        }
        return value;
    }

    private static String unpackValue(String value) {
        if (NULL_VALUE.equals(value)) {
            return null;
        }
        return value;
    }

    public final class Setting {
        private String name;
        private String value;
@@ -548,4 +600,58 @@ final class SettingsState {
            return true;
        }
    }

    /**
     * @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();
    }
}
+4 −1
Original line number Diff line number Diff line
@@ -2,7 +2,10 @@ LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)

LOCAL_SRC_FILES := $(call all-subdir-java-files)
# Note we statically link SettingsState to do some unit tests.  It's not accessible otherwise
# because this test is not an instrumentation test. (because the target runs in the system process.)
LOCAL_SRC_FILES := $(call all-subdir-java-files) \
    ../src/com/android/providers/settings/SettingsState.java

LOCAL_PACKAGE_NAME := SettingsProviderTest

+3 −1
Original line number Diff line number Diff line
@@ -39,8 +39,10 @@ abstract class BaseSettingsProviderTest extends AndroidTestCase {

    protected static final String FAKE_SETTING_NAME = "fake_setting_name";
    protected static final String FAKE_SETTING_NAME_1 = "fake_setting_name1";
    protected static final String FAKE_SETTING_NAME_2 = "fake_setting_name2";
    protected static final String FAKE_SETTING_VALUE = "fake_setting_value";
    protected static final String FAKE_SETTING_VALUE_1 = "fake_setting_value_1";
    protected static final String FAKE_SETTING_VALUE_1 = SettingsStateTest.CRAZY_STRING;
    protected static final String FAKE_SETTING_VALUE_2 = null;

    private static final String[] NAME_VALUE_COLUMNS = new String[] {
            Settings.NameValueTable.NAME, Settings.NameValueTable.VALUE
+15 −3
Original line number Diff line number Diff line
@@ -230,10 +230,11 @@ public class SettingsProviderTest extends BaseSettingsProviderTest {
        // Make sure we have a clean slate.
        deleteStringViaProviderApi(type, FAKE_SETTING_NAME);
        deleteStringViaProviderApi(type, FAKE_SETTING_NAME_1);
        deleteStringViaProviderApi(type, FAKE_SETTING_NAME_2);

        try {
            Uri uri = getBaseUriForType(type);
            ContentValues[] allValues = new ContentValues[2];
            ContentValues[] allValues = new ContentValues[3];

            // Insert the first setting.
            ContentValues firstValues = new ContentValues();
@@ -241,15 +242,21 @@ public class SettingsProviderTest extends BaseSettingsProviderTest {
            firstValues.put(Settings.NameValueTable.VALUE, FAKE_SETTING_VALUE);
            allValues[0] = firstValues;

            // Insert the first setting.
            // Insert the second setting.
            ContentValues secondValues = new ContentValues();
            secondValues.put(Settings.NameValueTable.NAME, FAKE_SETTING_NAME_1);
            secondValues.put(Settings.NameValueTable.VALUE, FAKE_SETTING_VALUE_1);
            allValues[1] = secondValues;

            // Insert the third setting. (null)
            ContentValues thirdValues = new ContentValues();
            thirdValues.put(Settings.NameValueTable.NAME, FAKE_SETTING_NAME_2);
            thirdValues.put(Settings.NameValueTable.VALUE, FAKE_SETTING_VALUE_2);
            allValues[2] = thirdValues;

            // Verify insertion count.
            final int insertCount = getContext().getContentResolver().bulkInsert(uri, allValues);
            assertSame("Couldn't insert both values", 2, insertCount);
            assertSame("Couldn't insert both values", 3, insertCount);

            // Make sure the first setting is there.
            String firstValue = queryStringViaProviderApi(type, FAKE_SETTING_NAME);
@@ -258,10 +265,15 @@ public class SettingsProviderTest extends BaseSettingsProviderTest {
            // Make sure the second setting is there.
            String secondValue = queryStringViaProviderApi(type, FAKE_SETTING_NAME_1);
            assertEquals("Second setting must be present", FAKE_SETTING_VALUE_1, secondValue);

            // Make sure the third setting is there.
            String thirdValue = queryStringViaProviderApi(type, FAKE_SETTING_NAME_2);
            assertEquals("Third setting must be present", FAKE_SETTING_VALUE_2, thirdValue);
        } finally {
            // Clean up.
            deleteStringViaProviderApi(type, FAKE_SETTING_NAME);
            deleteStringViaProviderApi(type, FAKE_SETTING_NAME_1);
            deleteStringViaProviderApi(type, FAKE_SETTING_NAME_2);
        }
    }

Loading