| Index: build/android/pylib/device/shared_prefs.py
|
| diff --git a/build/android/pylib/device/shared_prefs.py b/build/android/pylib/device/shared_prefs.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..32cef4b535b87ce87171323fe70793ac2bf23ab6
|
| --- /dev/null
|
| +++ b/build/android/pylib/device/shared_prefs.py
|
| @@ -0,0 +1,391 @@
|
| +# Copyright 2015 The Chromium Authors. All rights reserved.
|
| +# Use of this source code is governed by a BSD-style license that can be
|
| +# found in the LICENSE file.
|
| +
|
| +"""Helper object to read and modify Shared Preferences from Android apps.
|
| +
|
| +See e.g.:
|
| + http://developer.android.com/reference/android/content/SharedPreferences.html
|
| +"""
|
| +
|
| +import collections
|
| +import logging
|
| +import posixpath
|
| +
|
| +from xml.etree import ElementTree
|
| +
|
| +
|
| +_XML_DECLARATION = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
|
| +
|
| +
|
| +class BasePref(object):
|
| + """Base class for getting/setting the value of a specific preference type.
|
| +
|
| + Should not be instantiated directly. The SharedPrefs collection will
|
| + instantiate the appropriate subclasses, which directly manipulate the
|
| + underlying xml document, to parse and serialize values according to their
|
| + type.
|
| +
|
| + Args:
|
| + elem: An xml ElementTree object holding the preference data.
|
| +
|
| + Properties:
|
| + tag_name: A string with the tag that must be used for this preference type.
|
| + """
|
| + tag_name = None
|
| +
|
| + def __init__(self, elem):
|
| + if elem.tag != type(self).tag_name:
|
| + raise TypeError('Property %r has type %r, but trying to access as %r' %
|
| + (elem.get('name'), elem.tag, type(self).tag_name))
|
| + self._elem = elem
|
| +
|
| + def __str__(self):
|
| + """Get the underlying xml element as a string."""
|
| + return ElementTree.tostring(self._elem)
|
| +
|
| + def get(self):
|
| + """Get the value of this preference."""
|
| + return self._elem.get('value')
|
| +
|
| + def set(self, value):
|
| + """Set from a value casted as a string."""
|
| + self._elem.set('value', str(value))
|
| +
|
| + @property
|
| + def has_value(self):
|
| + """Check whether the element has a value."""
|
| + return self._elem.get('value') is not None
|
| +
|
| +
|
| +class BooleanPref(BasePref):
|
| + """Class for getting/setting a preference with a boolean value.
|
| +
|
| + The underlying xml element has the form, e.g.:
|
| + <boolean name="featureEnabled" value="false" />
|
| + """
|
| + tag_name = 'boolean'
|
| + VALUES = {'true': True, 'false': False}
|
| +
|
| + def get(self):
|
| + """Get the value as a Python bool."""
|
| + return type(self).VALUES[super(BooleanPref, self).get()]
|
| +
|
| + def set(self, value):
|
| + """Set from a value casted as a bool."""
|
| + super(BooleanPref, self).set('true' if value else 'false')
|
| +
|
| +
|
| +class FloatPref(BasePref):
|
| + """Class for getting/setting a preference with a float value.
|
| +
|
| + The underlying xml element has the form, e.g.:
|
| + <float name="someMetric" value="4.7" />
|
| + """
|
| + tag_name = 'float'
|
| +
|
| + def get(self):
|
| + """Get the value as a Python float."""
|
| + return float(super(FloatPref, self).get())
|
| +
|
| +
|
| +class IntPref(BasePref):
|
| + """Class for getting/setting a preference with an int value.
|
| +
|
| + The underlying xml element has the form, e.g.:
|
| + <int name="aCounter" value="1234" />
|
| + """
|
| + tag_name = 'int'
|
| +
|
| + def get(self):
|
| + """Get the value as a Python int."""
|
| + return int(super(IntPref, self).get())
|
| +
|
| +
|
| +class LongPref(IntPref):
|
| + """Class for getting/setting a preference with a long value.
|
| +
|
| + The underlying xml element has the form, e.g.:
|
| + <long name="aLongCounter" value="1234" />
|
| +
|
| + We use the same implementation from IntPref.
|
| + """
|
| + tag_name = 'long'
|
| +
|
| +
|
| +class StringPref(BasePref):
|
| + """Class for getting/setting a preference with a string value.
|
| +
|
| + The underlying xml element has the form, e.g.:
|
| + <string name="someHashValue">249b3e5af13d4db2</string>
|
| + """
|
| + tag_name = 'string'
|
| +
|
| + def get(self):
|
| + """Get the value as a Python string."""
|
| + return self._elem.text
|
| +
|
| + def set(self, value):
|
| + """Set from a value casted as a string."""
|
| + self._elem.text = str(value)
|
| +
|
| +
|
| +class StringSetPref(StringPref):
|
| + """Class for getting/setting a preference with a set of string values.
|
| +
|
| + The underlying xml element has the form, e.g.:
|
| + <set name="managed_apps">
|
| + <string>com.mine.app1</string>
|
| + <string>com.mine.app2</string>
|
| + <string>com.mine.app3</string>
|
| + </set>
|
| + """
|
| + tag_name = 'set'
|
| +
|
| + def get(self):
|
| + """Get a list with the string values contained."""
|
| + value = []
|
| + for child in self._elem:
|
| + assert child.tag == 'string'
|
| + value.append(child.text)
|
| + return value
|
| +
|
| + def set(self, value):
|
| + """Set from a sequence of values, each casted as a string."""
|
| + for child in list(self._elem):
|
| + self._elem.remove(child)
|
| + for item in value:
|
| + ElementTree.SubElement(self._elem, 'string').text = str(item)
|
| +
|
| +
|
| +_PREF_TYPES = {c.tag_name: c for c in [BooleanPref, FloatPref, IntPref,
|
| + LongPref, StringPref, StringSetPref]}
|
| +
|
| +
|
| +class SharedPrefs(object):
|
| + def __init__(self, device, package, filename):
|
| + """Helper object to read and update "Shared Prefs" of Android apps.
|
| +
|
| + Such files typically look like, e.g.:
|
| +
|
| + <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
|
| + <map>
|
| + <int name="databaseVersion" value="107" />
|
| + <boolean name="featureEnabled" value="false" />
|
| + <string name="someHashValue">249b3e5af13d4db2</string>
|
| + </map>
|
| +
|
| + Example usage:
|
| +
|
| + prefs = shared_prefs.SharedPrefs(device, 'com.my.app', 'my_prefs.xml')
|
| + prefs.Load()
|
| + prefs.GetString('someHashValue') # => '249b3e5af13d4db2'
|
| + prefs.SetInt('databaseVersion', 42)
|
| + prefs.Remove('featureEnabled')
|
| + prefs.Commit()
|
| +
|
| + The object may also be used as a context manager to automatically load and
|
| + commit, respectively, upon entering and leaving the context.
|
| +
|
| + Args:
|
| + device: A DeviceUtils object.
|
| + package: A string with the package name of the app that owns the shared
|
| + preferences file.
|
| + filename: A string with the name of the preferences file to read/write.
|
| + """
|
| + self._device = device
|
| + self._xml = None
|
| + self._package = package
|
| + self._filename = filename
|
| + self._path = '/data/data/%s/shared_prefs/%s' % (package, filename)
|
| + self._changed = False
|
| +
|
| + def __repr__(self):
|
| + """Get a useful printable representation of the object."""
|
| + return '<{cls} file {filename} for {package} on {device}>'.format(
|
| + cls=type(self).__name__, filename=self.filename, package=self.package,
|
| + device=str(self._device))
|
| +
|
| + def __str__(self):
|
| + """Get the underlying xml document as a string."""
|
| + return _XML_DECLARATION + ElementTree.tostring(self.xml)
|
| +
|
| + @property
|
| + def package(self):
|
| + """Get the package name of the app that owns the shared preferences."""
|
| + return self._package
|
| +
|
| + @property
|
| + def filename(self):
|
| + """Get the filename of the shared preferences file."""
|
| + return self._filename
|
| +
|
| + @property
|
| + def path(self):
|
| + """Get the full path to the shared preferences file on the device."""
|
| + return self._path
|
| +
|
| + @property
|
| + def changed(self):
|
| + """True if properties have changed and a commit would be needed."""
|
| + return self._changed
|
| +
|
| + @property
|
| + def xml(self):
|
| + """Get the underlying xml document as an ElementTree object."""
|
| + if self._xml is None:
|
| + self._xml = ElementTree.Element('map')
|
| + return self._xml
|
| +
|
| + def Load(self):
|
| + """Load the shared preferences file from the device.
|
| +
|
| + A empty xml document, which may be modified and saved on |commit|, is
|
| + created if the file does not already exist.
|
| + """
|
| + if self._device.FileExists(self.path):
|
| + self._xml = ElementTree.fromstring(
|
| + self._device.ReadFile(self.path, as_root=True))
|
| + assert self._xml.tag == 'map'
|
| + else:
|
| + self._xml = None
|
| + self._changed = False
|
| +
|
| + def Clear(self):
|
| + """Clear all of the preferences contained in this object."""
|
| + if self._xml is not None and len(self): # only clear if not already empty
|
| + self._xml = None
|
| + self._changed = True
|
| +
|
| + def Commit(self):
|
| + """Save the current set of preferences to the device.
|
| +
|
| + Only actually saves if some preferences have been modified.
|
| + """
|
| + if not self.changed:
|
| + return
|
| + self._device.RunShellCommand(
|
| + ['mkdir', '-p', posixpath.dirname(self.path)],
|
| + as_root=True, check_return=True)
|
| + self._device.WriteFile(self.path, str(self), as_root=True)
|
| + self._device.KillAll(self.package, as_root=True, quiet=True)
|
| + self._changed = False
|
| +
|
| + def __len__(self):
|
| + """Get the number of preferences in this collection."""
|
| + return len(self.xml)
|
| +
|
| + def PropertyType(self, key):
|
| + """Get the type (i.e. tag name) of a property in the collection."""
|
| + return self._GetChild(key).tag
|
| +
|
| + def HasProperty(self, key):
|
| + try:
|
| + self._GetChild(key)
|
| + return True
|
| + except KeyError:
|
| + return False
|
| +
|
| + def GetBoolean(self, key):
|
| + """Get a boolean property."""
|
| + return BooleanPref(self._GetChild(key)).get()
|
| +
|
| + def SetBoolean(self, key, value):
|
| + """Set a boolean property."""
|
| + self._SetPrefValue(key, value, BooleanPref)
|
| +
|
| + def GetFloat(self, key):
|
| + """Get a float property."""
|
| + return FloatPref(self._GetChild(key)).get()
|
| +
|
| + def SetFloat(self, key, value):
|
| + """Set a float property."""
|
| + self._SetPrefValue(key, value, FloatPref)
|
| +
|
| + def GetInt(self, key):
|
| + """Get an int property."""
|
| + return IntPref(self._GetChild(key)).get()
|
| +
|
| + def SetInt(self, key, value):
|
| + """Set an int property."""
|
| + self._SetPrefValue(key, value, IntPref)
|
| +
|
| + def GetLong(self, key):
|
| + """Get a long property."""
|
| + return LongPref(self._GetChild(key)).get()
|
| +
|
| + def SetLong(self, key, value):
|
| + """Set a long property."""
|
| + self._SetPrefValue(key, value, LongPref)
|
| +
|
| + def GetString(self, key):
|
| + """Get a string property."""
|
| + return StringPref(self._GetChild(key)).get()
|
| +
|
| + def SetString(self, key, value):
|
| + """Set a string property."""
|
| + self._SetPrefValue(key, value, StringPref)
|
| +
|
| + def GetStringSet(self, key):
|
| + """Get a string set property."""
|
| + return StringSetPref(self._GetChild(key)).get()
|
| +
|
| + def SetStringSet(self, key, value):
|
| + """Set a string set property."""
|
| + self._SetPrefValue(key, value, StringSetPref)
|
| +
|
| + def Remove(self, key):
|
| + """Remove a preference from the collection."""
|
| + self.xml.remove(self._GetChild(key))
|
| +
|
| + def AsDict(self):
|
| + """Return the properties and their values as a dictionary."""
|
| + d = {}
|
| + for child in self.xml:
|
| + pref = _PREF_TYPES[child.tag](child)
|
| + d[child.get('name')] = pref.get()
|
| + return d
|
| +
|
| + def __enter__(self):
|
| + """Load preferences file from the device when entering a context."""
|
| + self.Load()
|
| + return self
|
| +
|
| + def __exit__(self, exc_type, _exc_value, _traceback):
|
| + """Save preferences file to the device when leaving a context."""
|
| + if not exc_type:
|
| + self.Commit()
|
| +
|
| + def _GetChild(self, key):
|
| + """Get the underlying xml node that holds the property of a given key.
|
| +
|
| + Raises:
|
| + KeyError when the key is not found in the collection.
|
| + """
|
| + for child in self.xml:
|
| + if child.get('name') == key:
|
| + return child
|
| + raise KeyError(key)
|
| +
|
| + def _SetPrefValue(self, key, value, pref_cls):
|
| + """Set the value of a property.
|
| +
|
| + Args:
|
| + key: The key of the property to set.
|
| + value: The new value of the property.
|
| + pref_cls: A subclass of BasePref used to access the property.
|
| +
|
| + Raises:
|
| + TypeError when the key already exists but with a different type.
|
| + """
|
| + try:
|
| + pref = pref_cls(self._GetChild(key))
|
| + old_value = pref.get()
|
| + except KeyError:
|
| + pref = pref_cls(ElementTree.SubElement(
|
| + self.xml, pref_cls.tag_name, {'name': key}))
|
| + old_value = None
|
| + if old_value != value:
|
| + pref.set(value)
|
| + self._changed = True
|
| + logging.info('Setting property: %s', pref)
|
|
|