summaryrefslogtreecommitdiffstats
path: root/mobile/android/modules/SharedPreferences.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/modules/SharedPreferences.jsm')
-rw-r--r--mobile/android/modules/SharedPreferences.jsm254
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,
+ });
+ },
+});