1cef7893435aa41160dd1255c43cb8498279738ccChris Craik# Copyright 2015 The Chromium Authors. All rights reserved.
2cef7893435aa41160dd1255c43cb8498279738ccChris Craik# Use of this source code is governed by a BSD-style license that can be
3cef7893435aa41160dd1255c43cb8498279738ccChris Craik# found in the LICENSE file.
4cef7893435aa41160dd1255c43cb8498279738ccChris Craik
5cef7893435aa41160dd1255c43cb8498279738ccChris Craik"""Helper object to read and modify Shared Preferences from Android apps.
6cef7893435aa41160dd1255c43cb8498279738ccChris Craik
7cef7893435aa41160dd1255c43cb8498279738ccChris CraikSee e.g.:
8cef7893435aa41160dd1255c43cb8498279738ccChris Craik  http://developer.android.com/reference/android/content/SharedPreferences.html
9cef7893435aa41160dd1255c43cb8498279738ccChris Craik"""
10cef7893435aa41160dd1255c43cb8498279738ccChris Craik
11cef7893435aa41160dd1255c43cb8498279738ccChris Craikimport logging
12cef7893435aa41160dd1255c43cb8498279738ccChris Craikimport posixpath
13cef7893435aa41160dd1255c43cb8498279738ccChris Craik
14cef7893435aa41160dd1255c43cb8498279738ccChris Craikfrom xml.etree import ElementTree
15cef7893435aa41160dd1255c43cb8498279738ccChris Craik
16cef7893435aa41160dd1255c43cb8498279738ccChris Craik
17cef7893435aa41160dd1255c43cb8498279738ccChris Craik_XML_DECLARATION = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
18cef7893435aa41160dd1255c43cb8498279738ccChris Craik
19cef7893435aa41160dd1255c43cb8498279738ccChris Craik
20cef7893435aa41160dd1255c43cb8498279738ccChris Craikclass BasePref(object):
21cef7893435aa41160dd1255c43cb8498279738ccChris Craik  """Base class for getting/setting the value of a specific preference type.
22cef7893435aa41160dd1255c43cb8498279738ccChris Craik
23cef7893435aa41160dd1255c43cb8498279738ccChris Craik  Should not be instantiated directly. The SharedPrefs collection will
24cef7893435aa41160dd1255c43cb8498279738ccChris Craik  instantiate the appropriate subclasses, which directly manipulate the
25cef7893435aa41160dd1255c43cb8498279738ccChris Craik  underlying xml document, to parse and serialize values according to their
26cef7893435aa41160dd1255c43cb8498279738ccChris Craik  type.
27cef7893435aa41160dd1255c43cb8498279738ccChris Craik
28cef7893435aa41160dd1255c43cb8498279738ccChris Craik  Args:
29cef7893435aa41160dd1255c43cb8498279738ccChris Craik    elem: An xml ElementTree object holding the preference data.
30cef7893435aa41160dd1255c43cb8498279738ccChris Craik
31cef7893435aa41160dd1255c43cb8498279738ccChris Craik  Properties:
32cef7893435aa41160dd1255c43cb8498279738ccChris Craik    tag_name: A string with the tag that must be used for this preference type.
33cef7893435aa41160dd1255c43cb8498279738ccChris Craik  """
34cef7893435aa41160dd1255c43cb8498279738ccChris Craik  tag_name = None
35cef7893435aa41160dd1255c43cb8498279738ccChris Craik
36cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def __init__(self, elem):
37cef7893435aa41160dd1255c43cb8498279738ccChris Craik    if elem.tag != type(self).tag_name:
38cef7893435aa41160dd1255c43cb8498279738ccChris Craik      raise TypeError('Property %r has type %r, but trying to access as %r' %
39cef7893435aa41160dd1255c43cb8498279738ccChris Craik                      (elem.get('name'), elem.tag, type(self).tag_name))
40cef7893435aa41160dd1255c43cb8498279738ccChris Craik    self._elem = elem
41cef7893435aa41160dd1255c43cb8498279738ccChris Craik
42cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def __str__(self):
43cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Get the underlying xml element as a string."""
44cef7893435aa41160dd1255c43cb8498279738ccChris Craik    return ElementTree.tostring(self._elem)
45cef7893435aa41160dd1255c43cb8498279738ccChris Craik
46cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def get(self):
47cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Get the value of this preference."""
48cef7893435aa41160dd1255c43cb8498279738ccChris Craik    return self._elem.get('value')
49cef7893435aa41160dd1255c43cb8498279738ccChris Craik
50cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def set(self, value):
51cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Set from a value casted as a string."""
52cef7893435aa41160dd1255c43cb8498279738ccChris Craik    self._elem.set('value', str(value))
53cef7893435aa41160dd1255c43cb8498279738ccChris Craik
54cef7893435aa41160dd1255c43cb8498279738ccChris Craik  @property
55cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def has_value(self):
56cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Check whether the element has a value."""
57cef7893435aa41160dd1255c43cb8498279738ccChris Craik    return self._elem.get('value') is not None
58cef7893435aa41160dd1255c43cb8498279738ccChris Craik
59cef7893435aa41160dd1255c43cb8498279738ccChris Craik
60cef7893435aa41160dd1255c43cb8498279738ccChris Craikclass BooleanPref(BasePref):
61cef7893435aa41160dd1255c43cb8498279738ccChris Craik  """Class for getting/setting a preference with a boolean value.
62cef7893435aa41160dd1255c43cb8498279738ccChris Craik
63cef7893435aa41160dd1255c43cb8498279738ccChris Craik  The underlying xml element has the form, e.g.:
64cef7893435aa41160dd1255c43cb8498279738ccChris Craik      <boolean name="featureEnabled" value="false" />
65cef7893435aa41160dd1255c43cb8498279738ccChris Craik  """
66cef7893435aa41160dd1255c43cb8498279738ccChris Craik  tag_name = 'boolean'
67cef7893435aa41160dd1255c43cb8498279738ccChris Craik  VALUES = {'true': True, 'false': False}
68cef7893435aa41160dd1255c43cb8498279738ccChris Craik
69cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def get(self):
70cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Get the value as a Python bool."""
71cef7893435aa41160dd1255c43cb8498279738ccChris Craik    return type(self).VALUES[super(BooleanPref, self).get()]
72cef7893435aa41160dd1255c43cb8498279738ccChris Craik
73cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def set(self, value):
74cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Set from a value casted as a bool."""
75cef7893435aa41160dd1255c43cb8498279738ccChris Craik    super(BooleanPref, self).set('true' if value else 'false')
76cef7893435aa41160dd1255c43cb8498279738ccChris Craik
77cef7893435aa41160dd1255c43cb8498279738ccChris Craik
78cef7893435aa41160dd1255c43cb8498279738ccChris Craikclass FloatPref(BasePref):
79cef7893435aa41160dd1255c43cb8498279738ccChris Craik  """Class for getting/setting a preference with a float value.
80cef7893435aa41160dd1255c43cb8498279738ccChris Craik
81cef7893435aa41160dd1255c43cb8498279738ccChris Craik  The underlying xml element has the form, e.g.:
82cef7893435aa41160dd1255c43cb8498279738ccChris Craik      <float name="someMetric" value="4.7" />
83cef7893435aa41160dd1255c43cb8498279738ccChris Craik  """
84cef7893435aa41160dd1255c43cb8498279738ccChris Craik  tag_name = 'float'
85cef7893435aa41160dd1255c43cb8498279738ccChris Craik
86cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def get(self):
87cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Get the value as a Python float."""
88cef7893435aa41160dd1255c43cb8498279738ccChris Craik    return float(super(FloatPref, self).get())
89cef7893435aa41160dd1255c43cb8498279738ccChris Craik
90cef7893435aa41160dd1255c43cb8498279738ccChris Craik
91cef7893435aa41160dd1255c43cb8498279738ccChris Craikclass IntPref(BasePref):
92cef7893435aa41160dd1255c43cb8498279738ccChris Craik  """Class for getting/setting a preference with an int value.
93cef7893435aa41160dd1255c43cb8498279738ccChris Craik
94cef7893435aa41160dd1255c43cb8498279738ccChris Craik  The underlying xml element has the form, e.g.:
95cef7893435aa41160dd1255c43cb8498279738ccChris Craik      <int name="aCounter" value="1234" />
96cef7893435aa41160dd1255c43cb8498279738ccChris Craik  """
97cef7893435aa41160dd1255c43cb8498279738ccChris Craik  tag_name = 'int'
98cef7893435aa41160dd1255c43cb8498279738ccChris Craik
99cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def get(self):
100cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Get the value as a Python int."""
101cef7893435aa41160dd1255c43cb8498279738ccChris Craik    return int(super(IntPref, self).get())
102cef7893435aa41160dd1255c43cb8498279738ccChris Craik
103cef7893435aa41160dd1255c43cb8498279738ccChris Craik
104cef7893435aa41160dd1255c43cb8498279738ccChris Craikclass LongPref(IntPref):
105cef7893435aa41160dd1255c43cb8498279738ccChris Craik  """Class for getting/setting a preference with a long value.
106cef7893435aa41160dd1255c43cb8498279738ccChris Craik
107cef7893435aa41160dd1255c43cb8498279738ccChris Craik  The underlying xml element has the form, e.g.:
108cef7893435aa41160dd1255c43cb8498279738ccChris Craik      <long name="aLongCounter" value="1234" />
109cef7893435aa41160dd1255c43cb8498279738ccChris Craik
110cef7893435aa41160dd1255c43cb8498279738ccChris Craik  We use the same implementation from IntPref.
111cef7893435aa41160dd1255c43cb8498279738ccChris Craik  """
112cef7893435aa41160dd1255c43cb8498279738ccChris Craik  tag_name = 'long'
113cef7893435aa41160dd1255c43cb8498279738ccChris Craik
114cef7893435aa41160dd1255c43cb8498279738ccChris Craik
115cef7893435aa41160dd1255c43cb8498279738ccChris Craikclass StringPref(BasePref):
116cef7893435aa41160dd1255c43cb8498279738ccChris Craik  """Class for getting/setting a preference with a string value.
117cef7893435aa41160dd1255c43cb8498279738ccChris Craik
118cef7893435aa41160dd1255c43cb8498279738ccChris Craik  The underlying xml element has the form, e.g.:
119cef7893435aa41160dd1255c43cb8498279738ccChris Craik      <string name="someHashValue">249b3e5af13d4db2</string>
120cef7893435aa41160dd1255c43cb8498279738ccChris Craik  """
121cef7893435aa41160dd1255c43cb8498279738ccChris Craik  tag_name = 'string'
122cef7893435aa41160dd1255c43cb8498279738ccChris Craik
123cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def get(self):
124cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Get the value as a Python string."""
125cef7893435aa41160dd1255c43cb8498279738ccChris Craik    return self._elem.text
126cef7893435aa41160dd1255c43cb8498279738ccChris Craik
127cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def set(self, value):
128cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Set from a value casted as a string."""
129cef7893435aa41160dd1255c43cb8498279738ccChris Craik    self._elem.text = str(value)
130cef7893435aa41160dd1255c43cb8498279738ccChris Craik
131cef7893435aa41160dd1255c43cb8498279738ccChris Craik
132cef7893435aa41160dd1255c43cb8498279738ccChris Craikclass StringSetPref(StringPref):
133cef7893435aa41160dd1255c43cb8498279738ccChris Craik  """Class for getting/setting a preference with a set of string values.
134cef7893435aa41160dd1255c43cb8498279738ccChris Craik
135cef7893435aa41160dd1255c43cb8498279738ccChris Craik  The underlying xml element has the form, e.g.:
136cef7893435aa41160dd1255c43cb8498279738ccChris Craik      <set name="managed_apps">
137cef7893435aa41160dd1255c43cb8498279738ccChris Craik          <string>com.mine.app1</string>
138cef7893435aa41160dd1255c43cb8498279738ccChris Craik          <string>com.mine.app2</string>
139cef7893435aa41160dd1255c43cb8498279738ccChris Craik          <string>com.mine.app3</string>
140cef7893435aa41160dd1255c43cb8498279738ccChris Craik      </set>
141cef7893435aa41160dd1255c43cb8498279738ccChris Craik  """
142cef7893435aa41160dd1255c43cb8498279738ccChris Craik  tag_name = 'set'
143cef7893435aa41160dd1255c43cb8498279738ccChris Craik
144cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def get(self):
145cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Get a list with the string values contained."""
146cef7893435aa41160dd1255c43cb8498279738ccChris Craik    value = []
147cef7893435aa41160dd1255c43cb8498279738ccChris Craik    for child in self._elem:
148cef7893435aa41160dd1255c43cb8498279738ccChris Craik      assert child.tag == 'string'
149cef7893435aa41160dd1255c43cb8498279738ccChris Craik      value.append(child.text)
150cef7893435aa41160dd1255c43cb8498279738ccChris Craik    return value
151cef7893435aa41160dd1255c43cb8498279738ccChris Craik
152cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def set(self, value):
153cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Set from a sequence of values, each casted as a string."""
154cef7893435aa41160dd1255c43cb8498279738ccChris Craik    for child in list(self._elem):
155cef7893435aa41160dd1255c43cb8498279738ccChris Craik      self._elem.remove(child)
156cef7893435aa41160dd1255c43cb8498279738ccChris Craik    for item in value:
157cef7893435aa41160dd1255c43cb8498279738ccChris Craik      ElementTree.SubElement(self._elem, 'string').text = str(item)
158cef7893435aa41160dd1255c43cb8498279738ccChris Craik
159cef7893435aa41160dd1255c43cb8498279738ccChris Craik
160cef7893435aa41160dd1255c43cb8498279738ccChris Craik_PREF_TYPES = {c.tag_name: c for c in [BooleanPref, FloatPref, IntPref,
161cef7893435aa41160dd1255c43cb8498279738ccChris Craik                                       LongPref, StringPref, StringSetPref]}
162cef7893435aa41160dd1255c43cb8498279738ccChris Craik
163cef7893435aa41160dd1255c43cb8498279738ccChris Craik
164cef7893435aa41160dd1255c43cb8498279738ccChris Craikclass SharedPrefs(object):
165cef7893435aa41160dd1255c43cb8498279738ccChris Craik
166cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def __init__(self, device, package, filename):
167cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Helper object to read and update "Shared Prefs" of Android apps.
168cef7893435aa41160dd1255c43cb8498279738ccChris Craik
169cef7893435aa41160dd1255c43cb8498279738ccChris Craik    Such files typically look like, e.g.:
170cef7893435aa41160dd1255c43cb8498279738ccChris Craik
171cef7893435aa41160dd1255c43cb8498279738ccChris Craik        <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
172cef7893435aa41160dd1255c43cb8498279738ccChris Craik        <map>
173cef7893435aa41160dd1255c43cb8498279738ccChris Craik          <int name="databaseVersion" value="107" />
174cef7893435aa41160dd1255c43cb8498279738ccChris Craik          <boolean name="featureEnabled" value="false" />
175cef7893435aa41160dd1255c43cb8498279738ccChris Craik          <string name="someHashValue">249b3e5af13d4db2</string>
176cef7893435aa41160dd1255c43cb8498279738ccChris Craik        </map>
177cef7893435aa41160dd1255c43cb8498279738ccChris Craik
178cef7893435aa41160dd1255c43cb8498279738ccChris Craik    Example usage:
179cef7893435aa41160dd1255c43cb8498279738ccChris Craik
180cef7893435aa41160dd1255c43cb8498279738ccChris Craik        prefs = shared_prefs.SharedPrefs(device, 'com.my.app', 'my_prefs.xml')
181cef7893435aa41160dd1255c43cb8498279738ccChris Craik        prefs.Load()
182cef7893435aa41160dd1255c43cb8498279738ccChris Craik        prefs.GetString('someHashValue') # => '249b3e5af13d4db2'
183cef7893435aa41160dd1255c43cb8498279738ccChris Craik        prefs.SetInt('databaseVersion', 42)
184cef7893435aa41160dd1255c43cb8498279738ccChris Craik        prefs.Remove('featureEnabled')
185cef7893435aa41160dd1255c43cb8498279738ccChris Craik        prefs.Commit()
186cef7893435aa41160dd1255c43cb8498279738ccChris Craik
187cef7893435aa41160dd1255c43cb8498279738ccChris Craik    The object may also be used as a context manager to automatically load and
188cef7893435aa41160dd1255c43cb8498279738ccChris Craik    commit, respectively, upon entering and leaving the context.
189cef7893435aa41160dd1255c43cb8498279738ccChris Craik
190cef7893435aa41160dd1255c43cb8498279738ccChris Craik    Args:
191cef7893435aa41160dd1255c43cb8498279738ccChris Craik      device: A DeviceUtils object.
192cef7893435aa41160dd1255c43cb8498279738ccChris Craik      package: A string with the package name of the app that owns the shared
193cef7893435aa41160dd1255c43cb8498279738ccChris Craik        preferences file.
194cef7893435aa41160dd1255c43cb8498279738ccChris Craik      filename: A string with the name of the preferences file to read/write.
195cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """
196cef7893435aa41160dd1255c43cb8498279738ccChris Craik    self._device = device
197cef7893435aa41160dd1255c43cb8498279738ccChris Craik    self._xml = None
198cef7893435aa41160dd1255c43cb8498279738ccChris Craik    self._package = package
199cef7893435aa41160dd1255c43cb8498279738ccChris Craik    self._filename = filename
200cef7893435aa41160dd1255c43cb8498279738ccChris Craik    self._path = '/data/data/%s/shared_prefs/%s' % (package, filename)
201cef7893435aa41160dd1255c43cb8498279738ccChris Craik    self._changed = False
202cef7893435aa41160dd1255c43cb8498279738ccChris Craik
203cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def __repr__(self):
204cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Get a useful printable representation of the object."""
205cef7893435aa41160dd1255c43cb8498279738ccChris Craik    return '<{cls} file {filename} for {package} on {device}>'.format(
206cef7893435aa41160dd1255c43cb8498279738ccChris Craik      cls=type(self).__name__, filename=self.filename, package=self.package,
207cef7893435aa41160dd1255c43cb8498279738ccChris Craik      device=str(self._device))
208cef7893435aa41160dd1255c43cb8498279738ccChris Craik
209cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def __str__(self):
210cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Get the underlying xml document as a string."""
211cef7893435aa41160dd1255c43cb8498279738ccChris Craik    return _XML_DECLARATION + ElementTree.tostring(self.xml)
212cef7893435aa41160dd1255c43cb8498279738ccChris Craik
213cef7893435aa41160dd1255c43cb8498279738ccChris Craik  @property
214cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def package(self):
215cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Get the package name of the app that owns the shared preferences."""
216cef7893435aa41160dd1255c43cb8498279738ccChris Craik    return self._package
217cef7893435aa41160dd1255c43cb8498279738ccChris Craik
218cef7893435aa41160dd1255c43cb8498279738ccChris Craik  @property
219cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def filename(self):
220cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Get the filename of the shared preferences file."""
221cef7893435aa41160dd1255c43cb8498279738ccChris Craik    return self._filename
222cef7893435aa41160dd1255c43cb8498279738ccChris Craik
223cef7893435aa41160dd1255c43cb8498279738ccChris Craik  @property
224cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def path(self):
225cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Get the full path to the shared preferences file on the device."""
226cef7893435aa41160dd1255c43cb8498279738ccChris Craik    return self._path
227cef7893435aa41160dd1255c43cb8498279738ccChris Craik
228cef7893435aa41160dd1255c43cb8498279738ccChris Craik  @property
229cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def changed(self):
230cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """True if properties have changed and a commit would be needed."""
231cef7893435aa41160dd1255c43cb8498279738ccChris Craik    return self._changed
232cef7893435aa41160dd1255c43cb8498279738ccChris Craik
233cef7893435aa41160dd1255c43cb8498279738ccChris Craik  @property
234cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def xml(self):
235cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Get the underlying xml document as an ElementTree object."""
236cef7893435aa41160dd1255c43cb8498279738ccChris Craik    if self._xml is None:
237cef7893435aa41160dd1255c43cb8498279738ccChris Craik      self._xml = ElementTree.Element('map')
238cef7893435aa41160dd1255c43cb8498279738ccChris Craik    return self._xml
239cef7893435aa41160dd1255c43cb8498279738ccChris Craik
240cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def Load(self):
241cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Load the shared preferences file from the device.
242cef7893435aa41160dd1255c43cb8498279738ccChris Craik
243cef7893435aa41160dd1255c43cb8498279738ccChris Craik    A empty xml document, which may be modified and saved on |commit|, is
244cef7893435aa41160dd1255c43cb8498279738ccChris Craik    created if the file does not already exist.
245cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """
246cef7893435aa41160dd1255c43cb8498279738ccChris Craik    if self._device.FileExists(self.path):
247cef7893435aa41160dd1255c43cb8498279738ccChris Craik      self._xml = ElementTree.fromstring(
248cef7893435aa41160dd1255c43cb8498279738ccChris Craik          self._device.ReadFile(self.path, as_root=True))
249cef7893435aa41160dd1255c43cb8498279738ccChris Craik      assert self._xml.tag == 'map'
250cef7893435aa41160dd1255c43cb8498279738ccChris Craik    else:
251cef7893435aa41160dd1255c43cb8498279738ccChris Craik      self._xml = None
252cef7893435aa41160dd1255c43cb8498279738ccChris Craik    self._changed = False
253cef7893435aa41160dd1255c43cb8498279738ccChris Craik
254cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def Clear(self):
255cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Clear all of the preferences contained in this object."""
256cef7893435aa41160dd1255c43cb8498279738ccChris Craik    if self._xml is not None and len(self):  # only clear if not already empty
257cef7893435aa41160dd1255c43cb8498279738ccChris Craik      self._xml = None
258cef7893435aa41160dd1255c43cb8498279738ccChris Craik      self._changed = True
259cef7893435aa41160dd1255c43cb8498279738ccChris Craik
260cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def Commit(self):
261cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Save the current set of preferences to the device.
262cef7893435aa41160dd1255c43cb8498279738ccChris Craik
263cef7893435aa41160dd1255c43cb8498279738ccChris Craik    Only actually saves if some preferences have been modified.
264cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """
265cef7893435aa41160dd1255c43cb8498279738ccChris Craik    if not self.changed:
266cef7893435aa41160dd1255c43cb8498279738ccChris Craik      return
267cef7893435aa41160dd1255c43cb8498279738ccChris Craik    self._device.RunShellCommand(
268cef7893435aa41160dd1255c43cb8498279738ccChris Craik        ['mkdir', '-p', posixpath.dirname(self.path)],
269cef7893435aa41160dd1255c43cb8498279738ccChris Craik        as_root=True, check_return=True)
270cef7893435aa41160dd1255c43cb8498279738ccChris Craik    self._device.WriteFile(self.path, str(self), as_root=True)
271cef7893435aa41160dd1255c43cb8498279738ccChris Craik    self._device.KillAll(self.package, exact=True, as_root=True, quiet=True)
272cef7893435aa41160dd1255c43cb8498279738ccChris Craik    self._changed = False
273cef7893435aa41160dd1255c43cb8498279738ccChris Craik
274cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def __len__(self):
275cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Get the number of preferences in this collection."""
276cef7893435aa41160dd1255c43cb8498279738ccChris Craik    return len(self.xml)
277cef7893435aa41160dd1255c43cb8498279738ccChris Craik
278cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def PropertyType(self, key):
279cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Get the type (i.e. tag name) of a property in the collection."""
280cef7893435aa41160dd1255c43cb8498279738ccChris Craik    return self._GetChild(key).tag
281cef7893435aa41160dd1255c43cb8498279738ccChris Craik
282cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def HasProperty(self, key):
283cef7893435aa41160dd1255c43cb8498279738ccChris Craik    try:
284cef7893435aa41160dd1255c43cb8498279738ccChris Craik      self._GetChild(key)
285cef7893435aa41160dd1255c43cb8498279738ccChris Craik      return True
286cef7893435aa41160dd1255c43cb8498279738ccChris Craik    except KeyError:
287cef7893435aa41160dd1255c43cb8498279738ccChris Craik      return False
288cef7893435aa41160dd1255c43cb8498279738ccChris Craik
289cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def GetBoolean(self, key):
290cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Get a boolean property."""
291cef7893435aa41160dd1255c43cb8498279738ccChris Craik    return BooleanPref(self._GetChild(key)).get()
292cef7893435aa41160dd1255c43cb8498279738ccChris Craik
293cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def SetBoolean(self, key, value):
294cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Set a boolean property."""
295cef7893435aa41160dd1255c43cb8498279738ccChris Craik    self._SetPrefValue(key, value, BooleanPref)
296cef7893435aa41160dd1255c43cb8498279738ccChris Craik
297cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def GetFloat(self, key):
298cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Get a float property."""
299cef7893435aa41160dd1255c43cb8498279738ccChris Craik    return FloatPref(self._GetChild(key)).get()
300cef7893435aa41160dd1255c43cb8498279738ccChris Craik
301cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def SetFloat(self, key, value):
302cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Set a float property."""
303cef7893435aa41160dd1255c43cb8498279738ccChris Craik    self._SetPrefValue(key, value, FloatPref)
304cef7893435aa41160dd1255c43cb8498279738ccChris Craik
305cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def GetInt(self, key):
306cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Get an int property."""
307cef7893435aa41160dd1255c43cb8498279738ccChris Craik    return IntPref(self._GetChild(key)).get()
308cef7893435aa41160dd1255c43cb8498279738ccChris Craik
309cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def SetInt(self, key, value):
310cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Set an int property."""
311cef7893435aa41160dd1255c43cb8498279738ccChris Craik    self._SetPrefValue(key, value, IntPref)
312cef7893435aa41160dd1255c43cb8498279738ccChris Craik
313cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def GetLong(self, key):
314cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Get a long property."""
315cef7893435aa41160dd1255c43cb8498279738ccChris Craik    return LongPref(self._GetChild(key)).get()
316cef7893435aa41160dd1255c43cb8498279738ccChris Craik
317cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def SetLong(self, key, value):
318cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Set a long property."""
319cef7893435aa41160dd1255c43cb8498279738ccChris Craik    self._SetPrefValue(key, value, LongPref)
320cef7893435aa41160dd1255c43cb8498279738ccChris Craik
321cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def GetString(self, key):
322cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Get a string property."""
323cef7893435aa41160dd1255c43cb8498279738ccChris Craik    return StringPref(self._GetChild(key)).get()
324cef7893435aa41160dd1255c43cb8498279738ccChris Craik
325cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def SetString(self, key, value):
326cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Set a string property."""
327cef7893435aa41160dd1255c43cb8498279738ccChris Craik    self._SetPrefValue(key, value, StringPref)
328cef7893435aa41160dd1255c43cb8498279738ccChris Craik
329cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def GetStringSet(self, key):
330cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Get a string set property."""
331cef7893435aa41160dd1255c43cb8498279738ccChris Craik    return StringSetPref(self._GetChild(key)).get()
332cef7893435aa41160dd1255c43cb8498279738ccChris Craik
333cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def SetStringSet(self, key, value):
334cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Set a string set property."""
335cef7893435aa41160dd1255c43cb8498279738ccChris Craik    self._SetPrefValue(key, value, StringSetPref)
336cef7893435aa41160dd1255c43cb8498279738ccChris Craik
337cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def Remove(self, key):
338cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Remove a preference from the collection."""
339cef7893435aa41160dd1255c43cb8498279738ccChris Craik    self.xml.remove(self._GetChild(key))
340cef7893435aa41160dd1255c43cb8498279738ccChris Craik
341cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def AsDict(self):
342cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Return the properties and their values as a dictionary."""
343cef7893435aa41160dd1255c43cb8498279738ccChris Craik    d = {}
344cef7893435aa41160dd1255c43cb8498279738ccChris Craik    for child in self.xml:
345cef7893435aa41160dd1255c43cb8498279738ccChris Craik      pref = _PREF_TYPES[child.tag](child)
346cef7893435aa41160dd1255c43cb8498279738ccChris Craik      d[child.get('name')] = pref.get()
347cef7893435aa41160dd1255c43cb8498279738ccChris Craik    return d
348cef7893435aa41160dd1255c43cb8498279738ccChris Craik
349cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def __enter__(self):
350cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Load preferences file from the device when entering a context."""
351cef7893435aa41160dd1255c43cb8498279738ccChris Craik    self.Load()
352cef7893435aa41160dd1255c43cb8498279738ccChris Craik    return self
353cef7893435aa41160dd1255c43cb8498279738ccChris Craik
354cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def __exit__(self, exc_type, _exc_value, _traceback):
355cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Save preferences file to the device when leaving a context."""
356cef7893435aa41160dd1255c43cb8498279738ccChris Craik    if not exc_type:
357cef7893435aa41160dd1255c43cb8498279738ccChris Craik      self.Commit()
358cef7893435aa41160dd1255c43cb8498279738ccChris Craik
359cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def _GetChild(self, key):
360cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Get the underlying xml node that holds the property of a given key.
361cef7893435aa41160dd1255c43cb8498279738ccChris Craik
362cef7893435aa41160dd1255c43cb8498279738ccChris Craik    Raises:
363cef7893435aa41160dd1255c43cb8498279738ccChris Craik      KeyError when the key is not found in the collection.
364cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """
365cef7893435aa41160dd1255c43cb8498279738ccChris Craik    for child in self.xml:
366cef7893435aa41160dd1255c43cb8498279738ccChris Craik      if child.get('name') == key:
367cef7893435aa41160dd1255c43cb8498279738ccChris Craik        return child
368cef7893435aa41160dd1255c43cb8498279738ccChris Craik    raise KeyError(key)
369cef7893435aa41160dd1255c43cb8498279738ccChris Craik
370cef7893435aa41160dd1255c43cb8498279738ccChris Craik  def _SetPrefValue(self, key, value, pref_cls):
371cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """Set the value of a property.
372cef7893435aa41160dd1255c43cb8498279738ccChris Craik
373cef7893435aa41160dd1255c43cb8498279738ccChris Craik    Args:
374cef7893435aa41160dd1255c43cb8498279738ccChris Craik      key: The key of the property to set.
375cef7893435aa41160dd1255c43cb8498279738ccChris Craik      value: The new value of the property.
376cef7893435aa41160dd1255c43cb8498279738ccChris Craik      pref_cls: A subclass of BasePref used to access the property.
377cef7893435aa41160dd1255c43cb8498279738ccChris Craik
378cef7893435aa41160dd1255c43cb8498279738ccChris Craik    Raises:
379cef7893435aa41160dd1255c43cb8498279738ccChris Craik      TypeError when the key already exists but with a different type.
380cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """
381cef7893435aa41160dd1255c43cb8498279738ccChris Craik    try:
382cef7893435aa41160dd1255c43cb8498279738ccChris Craik      pref = pref_cls(self._GetChild(key))
383cef7893435aa41160dd1255c43cb8498279738ccChris Craik      old_value = pref.get()
384cef7893435aa41160dd1255c43cb8498279738ccChris Craik    except KeyError:
385cef7893435aa41160dd1255c43cb8498279738ccChris Craik      pref = pref_cls(ElementTree.SubElement(
386cef7893435aa41160dd1255c43cb8498279738ccChris Craik          self.xml, pref_cls.tag_name, {'name': key}))
387cef7893435aa41160dd1255c43cb8498279738ccChris Craik      old_value = None
388cef7893435aa41160dd1255c43cb8498279738ccChris Craik    if old_value != value:
389cef7893435aa41160dd1255c43cb8498279738ccChris Craik      pref.set(value)
390cef7893435aa41160dd1255c43cb8498279738ccChris Craik      self._changed = True
391cef7893435aa41160dd1255c43cb8498279738ccChris Craik      logging.info('Setting property: %s', pref)
392