diff options
Diffstat (limited to 'mobile/android/modules/SharedPreferences.jsm')
-rw-r--r-- | mobile/android/modules/SharedPreferences.jsm | 254 |
1 files changed, 254 insertions, 0 deletions
diff --git a/mobile/android/modules/SharedPreferences.jsm b/mobile/android/modules/SharedPreferences.jsm new file mode 100644 index 000000000..3f32df6ea --- /dev/null +++ b/mobile/android/modules/SharedPreferences.jsm @@ -0,0 +1,254 @@ +// -*- indent-tabs-mode: nil; js-indent-level: 2 -*- +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +this.EXPORTED_SYMBOLS = ["SharedPreferences"]; + +const { classes: Cc, interfaces: Ci, utils: Cu } = Components; + +// For adding observers. +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/Messaging.jsm"); + +var Scope = Object.freeze({ + APP: "app", + PROFILE: "profile", + GLOBAL: "global" +}); + +/** + * Public API to getting a SharedPreferencesImpl instance. These scopes mirror GeckoSharedPrefs. + */ +var SharedPreferences = { + forApp: function() { + return new SharedPreferencesImpl({ scope: Scope.APP }); + }, + + forProfile: function() { + return new SharedPreferencesImpl({ scope: Scope.PROFILE }); + }, + + /** + * Get SharedPreferences for the named profile; if the profile name is null, + * returns the preferences for the current profile (just like |forProfile|). + */ + forProfileName: function(profileName) { + return new SharedPreferencesImpl({ scope: Scope.PROFILE, profileName: profileName }); + }, + + /** + * Get SharedPreferences for the given Android branch; if the branch is null, + * returns the default preferences branch for the application, which is the + * output of |PreferenceManager.getDefaultSharedPreferences|. + */ + forAndroid: function(branch) { + return new SharedPreferencesImpl({ scope: Scope.GLOBAL, branch: branch }); + } +}; + +/** + * Create an interface to an Android SharedPreferences branch. + * + * options {Object} with the following valid keys: + * - scope {String} (required) specifies the scope of preferences that should be accessed. + * - branch {String} (only when using Scope.GLOBAL) should be a string describing a preferences branch, + * like "UpdateService" or "background.data", or null to access the + * default preferences branch for the application. + * - profileName {String} (optional, only valid when using Scope.PROFILE) + */ +function SharedPreferencesImpl(options = {}) { + if (!(this instanceof SharedPreferencesImpl)) { + return new SharedPreferencesImpl(options); + } + + if (options.scope == null || options.scope == undefined) { + throw "Shared Preferences must specifiy a scope."; + } + + this._scope = options.scope; + this._profileName = options.profileName; + this._branch = options.branch; + this._observers = {}; +} + +SharedPreferencesImpl.prototype = Object.freeze({ + _set: function _set(prefs) { + Messaging.sendRequest({ + type: "SharedPreferences:Set", + preferences: prefs, + scope: this._scope, + profileName: this._profileName, + branch: this._branch, + }); + }, + + _setOne: function _setOne(prefName, value, type) { + let prefs = []; + prefs.push({ + name: prefName, + value: value, + type: type, + }); + this._set(prefs); + }, + + setBoolPref: function setBoolPref(prefName, value) { + this._setOne(prefName, value, "bool"); + }, + + setCharPref: function setCharPref(prefName, value) { + this._setOne(prefName, value, "string"); + }, + + setIntPref: function setIntPref(prefName, value) { + this._setOne(prefName, value, "int"); + }, + + _get: function _get(prefs, callback) { + let result = null; + Messaging.sendRequestForResult({ + type: "SharedPreferences:Get", + preferences: prefs, + scope: this._scope, + profileName: this._profileName, + branch: this._branch, + }).then((data) => { + result = data.values; + }); + + let thread = Services.tm.currentThread; + while (result == null) + thread.processNextEvent(true); + + return result; + }, + + _getOne: function _getOne(prefName, type) { + let prefs = []; + prefs.push({ + name: prefName, + type: type, + }); + let values = this._get(prefs); + if (values.length != 1) { + throw new Error("Got too many values: " + values.length); + } + return values[0].value; + }, + + getBoolPref: function getBoolPref(prefName) { + return this._getOne(prefName, "bool"); + }, + + getCharPref: function getCharPref(prefName) { + return this._getOne(prefName, "string"); + }, + + getIntPref: function getIntPref(prefName) { + return this._getOne(prefName, "int"); + }, + + /** + * Invoke `observer` after a change to the preference `domain` in + * the current branch. + * + * `observer` should implement the nsIObserver.observe interface. + */ + addObserver: function addObserver(domain, observer, holdWeak) { + if (!domain) + throw new Error("domain must not be null"); + if (!observer) + throw new Error("observer must not be null"); + if (holdWeak) + throw new Error("Weak references not yet implemented."); + + if (!this._observers.hasOwnProperty(domain)) + this._observers[domain] = []; + if (this._observers[domain].indexOf(observer) > -1) + return; + + this._observers[domain].push(observer); + + this._updateAndroidListener(); + }, + + /** + * Do not invoke `observer` after a change to the preference + * `domain` in the current branch. + */ + removeObserver: function removeObserver(domain, observer) { + if (!this._observers.hasOwnProperty(domain)) + return; + let index = this._observers[domain].indexOf(observer); + if (index < 0) + return; + + this._observers[domain].splice(index, 1); + if (this._observers[domain].length < 1) + delete this._observers[domain]; + + this._updateAndroidListener(); + }, + + _updateAndroidListener: function _updateAndroidListener() { + if (this._listening && Object.keys(this._observers).length < 1) + this._uninstallAndroidListener(); + if (!this._listening && Object.keys(this._observers).length > 0) + this._installAndroidListener(); + }, + + _installAndroidListener: function _installAndroidListener() { + if (this._listening) + return; + this._listening = true; + + Services.obs.addObserver(this, "SharedPreferences:Changed", false); + Messaging.sendRequest({ + type: "SharedPreferences:Observe", + enable: true, + scope: this._scope, + profileName: this._profileName, + branch: this._branch, + }); + }, + + observe: function observe(subject, topic, data) { + if (topic != "SharedPreferences:Changed") { + return; + } + + let msg = JSON.parse(data); + if (msg.scope !== this._scope || + ((this._scope === Scope.PROFILE) && (msg.profileName !== this._profileName)) || + ((this._scope === Scope.GLOBAL) && (msg.branch !== this._branch))) { + return; + } + + if (!this._observers.hasOwnProperty(msg.key)) { + return; + } + + let observers = this._observers[msg.key]; + for (let obs of observers) { + obs.observe(obs, msg.key, msg.value); + } + }, + + _uninstallAndroidListener: function _uninstallAndroidListener() { + if (!this._listening) + return; + this._listening = false; + + Services.obs.removeObserver(this, "SharedPreferences:Changed"); + Messaging.sendRequest({ + type: "SharedPreferences:Observe", + enable: false, + scope: this._scope, + profileName: this._profileName, + branch: this._branch, + }); + }, +}); |