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