diff --git a/apps/settings/README.md b/apps/settings/README.md
index 1c61e419f752..2aaecc407c47 100644
--- a/apps/settings/README.md
+++ b/apps/settings/README.md
@@ -180,7 +180,7 @@ If the old module called Settings.mozSettings, Use SettingsCache.getString() ins
```js
var SettingsCache = require('modules/settings_cache');
...
- SettingsCache.getSettings(function(result){
+ SettingsCache.getSettings(function(result) {
var onlineSupportTitle = result['support.onlinesupport.title'];
...
});
diff --git a/apps/settings/elements/sound.html b/apps/settings/elements/sound.html
index dce5410dd931..2fdb353a2421 100644
--- a/apps/settings/elements/sound.html
+++ b/apps/settings/elements/sound.html
@@ -106,8 +106,7 @@
Other Sounds
-
-
+
diff --git a/apps/settings/js/config/require.js b/apps/settings/js/config/require.js
index e4f93abe9141..996343cff328 100644
--- a/apps/settings/js/config/require.js
+++ b/apps/settings/js/config/require.js
@@ -9,15 +9,18 @@ require.config({
'settings': {
exports: 'Settings'
},
+ 'shared/keyboard_helper': {
+ exports: 'KeyboardHelper'
+ },
'shared/lazy_loader': {
exports: 'LazyLoader'
},
+ 'shared/omadrm/fl': {
+ exports: 'ForwardLock'
+ },
'shared/screen_layout': {
exports: 'ScreenLayout'
},
- 'shared/keyboard_helper': {
- exports: 'KeyboardHelper'
- },
'shared/settings_listener': {
exports: 'SettingsListener'
}
@@ -44,6 +47,13 @@ require.config({
{
name: 'panels/help/panel',
exclude: ['main']
+ },
+ {
+ name: 'panels/sound/panel',
+ exclude: [
+ 'main',
+ 'shared/settings_listener'
+ ]
}
]
});
diff --git a/apps/settings/js/panels/sound/panel.js b/apps/settings/js/panels/sound/panel.js
new file mode 100644
index 000000000000..f7af54fd0a3e
--- /dev/null
+++ b/apps/settings/js/panels/sound/panel.js
@@ -0,0 +1,29 @@
+/**
+ * Used to show Personalization/Sound panel
+ */
+define(function(require) {
+ 'use strict';
+
+ var SettingsPanel = require('modules/settings_panel');
+ var Sound = require('panels/sound/sound');
+
+ return function ctor_sound_panel() {
+ var sound = Sound();
+
+ return SettingsPanel({
+ onInit: function(panel) {
+ var elements = {
+ toneSelector: panel.querySelector('#touch-tone-selector'),
+ alertTone: panel.querySelector('#alert-tone-selection'),
+ ringTone: panel.querySelector('#ring-tone-selection'),
+ ringer: panel.querySelector('#ringer')
+ };
+
+ sound.init(elements);
+ },
+ onUninit: function() {
+ sound.uninit();
+ }
+ });
+ };
+});
diff --git a/apps/settings/js/panels/sound/sound.js b/apps/settings/js/panels/sound/sound.js
new file mode 100644
index 000000000000..1f836bbb1908
--- /dev/null
+++ b/apps/settings/js/panels/sound/sound.js
@@ -0,0 +1,186 @@
+/* global getSupportedNetworkInfo, URL, MozActivity */
+/**
+ * Handle sound panel functionality
+ */
+define(function(require) {
+ 'use strict';
+
+ var ForwardLock = require('shared/omadrm/fl');
+ var SettingsListener = require('shared/settings_listener');
+
+ var Sound = function() {
+ this.elements = null;
+ this.tones = null;
+ };
+
+ Sound.prototype = {
+ /**
+ * initialization
+ */
+ init: function sound_init(elements) {
+ this.elements = elements;
+
+ if (window.navigator.mozMobileConnections) {
+ var mobileConnections = window.navigator.mozMobileConnections;
+ // Show the touch tone selector if and only if we're on a CDMA network
+ var toneSelector = this.elements.toneSelector;
+ Array.prototype.forEach.call(mobileConnections,
+ function(mobileConnection) {
+ getSupportedNetworkInfo(mobileConnection, function(result) {
+ toneSelector.hidden = toneSelector.hidden && !result.cdma;
+ });
+ });
+ }
+
+ // initialize the ring tone and alert tone menus.
+ this._configureTones();
+ this._handleTones();
+ },
+
+ uninit: function sound_uninit() {
+ this.elements = null;
+ this.tones = null;
+ },
+
+ _configureTones: function sound_configureTones() {
+ /**
+ * This array has one element for each selectable tone that appears in the
+ * "Tones" section of ../elements/sound.html.
+ */
+ this.tones = [{
+ pickType: 'alerttone',
+ settingsKey: 'notification.ringtone',
+ allowNone: true, // Allow "None" as a choice for alert tones.
+ button: this.elements.alertTone
+ }];
+
+ // If we're a telephone, then show the section for ringtones, too.
+ if (navigator.mozTelephony) {
+ this.tones.push({
+ pickType: 'ringtone',
+ settingsKey: 'dialer.ringtone',
+ allowNone: false, // The ringer must always have an actual sound.
+ button: this.elements.ringTone
+ });
+ this.elements.ringer.hidden = false;
+ }
+ },
+
+ _handleTones: function sound_handleTones() {
+ var _ = navigator.mozL10n.get;
+ var self = this;
+
+ // For each kind of tone, hook up the button that will allow the user
+ // to select a sound for that kind of tone.
+ this.tones.forEach(function(tone) {
+ var namekey = tone.settingsKey + '.name';
+ // The button looks like a select element. By default it just reads
+ // "change". But we want it to display the name of the current tone.
+ // So we look up that name in the settings database.
+ SettingsListener.observe(namekey, '', function(tonename) {
+ tone.button.textContent = tonename || _('change');
+ });
+
+ // When the user clicks the button, we launch an activity that lets
+ // the user select new ringtone.
+ tone.button.onclick = function() {
+ /**
+ * Before we can start the Pick activity, we need to know if there
+ * is locked content on the phone because we don't want the user to
+ * see "Purchased Media" as a choice if there isn't any purchased
+ * media on the phone. The ForwardLock secret key is not generated
+ * until it is needed, so we can use its existance to
+ * determine whether to show the Purchased Media app.
+ */
+ ForwardLock.getKey(function(secret) {
+ var activity = new MozActivity({
+ name: 'pick',
+ data: {
+ type: tone.pickType,
+ allowNone: tone.allowNone,
+ // If we have a secret then there is locked content on the phone
+ // so include it as a choice for the user
+ includeLocked: (secret !== null)
+ }
+ });
+
+ activity.onsuccess = function() {
+ var blob = activity.result.blob; // The returned ringtone sound
+ var name = activity.result.name; // The name of this ringtone
+
+ if (!blob) {
+ if (tone.allowNone) {
+ // If we allow a null blob, then everything is okay
+ self._setRingtone(blob, name, tone.settingsKey);
+ } else {
+ // Otherwise this is an error and we should not change the
+ // current setting. (The ringtones app should never return
+ // a null blob if allowNone is false, but other apps might.)
+ alert(_('unplayable-ringtone'));
+ }
+ return;
+ }
+
+ // If we got a locked ringtone, we have to unlock it first
+ if (blob.type.split('/')[1] === ForwardLock.mimeSubtype) {
+ ForwardLock.unlockBlob(secret, blob, function(unlocked) {
+ self._checkRingtone(unlocked, name, tone);
+ });
+ } else { // Otherwise we can just use the blob directly.
+ self._checkRingtone(blob, name, tone);
+ }
+ };
+ });
+ };
+ });
+ },
+ /**
+ * Make sure that the blob we got from the activity is actually
+ * a playable audio file. It would be very bad to set an corrupt
+ * blob as a ringtone because then the phone wouldn't ring!
+ */
+ _checkRingtone: function sound_checkRingtone(blob, name, tone) {
+ var _ = navigator.mozL10n.get;
+ var oldRingtoneName = tone.button.textContent;
+ tone.button.textContent = _('savingringtone');
+
+ var player = new Audio();
+ player.preload = 'metadata';
+ player.src = URL.createObjectURL(blob);
+ player.oncanplay = function() {
+ release();
+ // this will update the button text
+ this._setRingtone(blob, name, tone.settingsKey);
+ }.bind(this);
+ player.onerror = function() {
+ release();
+ tone.button.textContent = oldRingtoneName;
+ alert(_('unplayable-ringtone'));
+ };
+
+ function release() {
+ URL.revokeObjectURL(player.src);
+ player.removeAttribute('src');
+ player.load();
+ }
+ },
+ /*
+ * Save the sound in the settings db so that other apps can
+ * use it.
+ * Also save the sound name in the db so we can display it in the future.
+ * And update the button text to the new name now.
+ */
+ _setRingtone: function sound_setRingtone(blob, name, settingsKey) {
+ // Update the settings database. This will cause the button
+ // text to change as well because of the SettingsListener above.
+ var values = {};
+ values[settingsKey] = blob;
+ values[settingsKey + '.name'] = name || '';
+ navigator.mozSettings.createLock().set(values);
+ }
+ };
+
+ return function ctor_sound() {
+ return new Sound();
+ };
+});
diff --git a/apps/settings/js/sound.js b/apps/settings/js/sound.js
deleted file mode 100644
index 096a805f07bb..000000000000
--- a/apps/settings/js/sound.js
+++ /dev/null
@@ -1,147 +0,0 @@
-/* global getSupportedNetworkInfo, SettingsListener, ForwardLock, URL,
- MozActivity */
-/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil -*- */
-/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
-
-(function() {
- 'use strict';
-
- var _ = navigator.mozL10n.get;
- (function() {
- var mobileConnections = window.navigator.mozMobileConnections;
- // Show the touch tone selector if and only if we're on a CDMA network
- var toneSelector = document.getElementById('touch-tone-selector');
- Array.prototype.forEach.call(mobileConnections, function(mobileConnection) {
- getSupportedNetworkInfo(mobileConnection, function(result) {
- toneSelector.hidden = toneSelector.hidden && !result.cdma;
- });
- });
- })();
- // Now initialize the ring tone and alert tone menus.
-
- // This array has one element for each selectable tone that appears in the
- // "Tones" section of ../elements/sound.html.
- var tones = [
- {
- pickType: 'alerttone',
- settingsKey: 'notification.ringtone',
- allowNone: true, // Allow "None" as a choice for alert tones.
- button: document.getElementById('alert-tone-selection')
- }
- ];
-
- // If we're a telephone, then show the section for ringtones, too.
- if (navigator.mozTelephony) {
- tones.push({
- pickType: 'ringtone',
- settingsKey: 'dialer.ringtone',
- allowNone: false, // The ringer must always have an actual sound.
- button: document.getElementById('ring-tone-selection')
- });
- document.getElementById('ringer').hidden = false;
- }
-
- // For each kind of tone, hook up the button that will allow the user
- // to select a sound for that kind of tone.
- tones.forEach(function(tone) {
- var namekey = tone.settingsKey + '.name';
-
- // The button looks like a select element. By default it just reads
- // "change". But we want it to display the name of the current tone.
- // So we look up that name in the settings database.
- SettingsListener.observe(namekey, '', function(tonename) {
- tone.button.textContent = tonename || _('change');
- });
-
- // When the user clicks the button, we launch an activity that lets
- // the user select new ringtone.
- tone.button.onclick = function() {
-
- // Before we can start the Pick activity, we need to know if there
- // is locked content on the phone because we don't want the user to
- // see "Purchased Media" as a choice if there isn't any purchased
- // media on the phone. The ForwardLock secret key is not generated
- // until it is needed, so we can use its existance to determine whether
- // to show the Purchased Media app.
- ForwardLock.getKey(function(secret) {
- var activity = new MozActivity({
- name: 'pick',
- data: {
- type: tone.pickType,
- allowNone: tone.allowNone,
- // If we have a secret then there is locked content on the phone
- // so include it as a choice for the user
- includeLocked: (secret !== null)
- }
- });
-
- activity.onsuccess = function() {
- var blob = activity.result.blob; // The returned ringtone sound
- var name = activity.result.name; // The name of this ringtone
-
- if (!blob) {
- if (tone.allowNone) {
- // If we allow a null blob, then everything is okay
- setRingtone(blob, name);
- }
- else {
- // Otherwise this is an error and we should not change the
- // current setting. (The ringtones app should never return
- // a null blob if allowNone is false, but other apps might.)
- alert(_('unplayable-ringtone'));
- }
- return;
- }
-
- // If we got a locked ringtone, we have to unlock it first
- if (blob.type.split('/')[1] === ForwardLock.mimeSubtype) {
- ForwardLock.unlockBlob(secret, blob, function(unlocked) {
- checkRingtone(unlocked, name);
- });
- } else { // Otherwise we can just use the blob directly.
- checkRingtone(blob, name);
- }
-
- // Make sure that the blob we got from the activity is actually
- // a playable audio file. It would be very bad to set an corrupt
- // blob as a ringtone because then the phone wouldn't ring!
- function checkRingtone(blob, name) {
- var oldRingtoneName = tone.button.textContent;
- tone.button.textContent = _('savingringtone');
-
- var player = new Audio();
- player.preload = 'metadata';
- player.src = URL.createObjectURL(blob);
- player.oncanplay = function() {
- release();
- setRingtone(blob, name); // this will update the button text
- };
- player.onerror = function() {
- release();
- tone.button.textContent = oldRingtoneName;
- alert(_('unplayable-ringtone'));
- };
-
- function release() {
- URL.revokeObjectURL(player.src);
- player.removeAttribute('src');
- player.load();
- }
- }
-
- // Save the sound in the settings db so that other apps can use it.
- // Also save the sound name in the db so we can display it in the
- // future. And update the button text to the new name now.
- function setRingtone(blob, name) {
- // Update the settings database. This will cause the button
- // text to change as well because of the SettingsListener above.
- var values = {};
- values[tone.settingsKey] = blob;
- values[namekey] = name || '';
- navigator.mozSettings.createLock().set(values);
- }
- };
- });
- };
- });
-}());
diff --git a/apps/settings/test/unit/mock_settings_listener.js b/apps/settings/test/unit/mock_settings_listener.js
new file mode 100644
index 000000000000..db2e700e5588
--- /dev/null
+++ b/apps/settings/test/unit/mock_settings_listener.js
@@ -0,0 +1,13 @@
+/* global define */
+/* SettingsListener mock for AMD panel */
+define(function() {
+ 'use strict';
+
+ var ctor = {
+ observe: function sl_observe(namekey, arg, callback) {
+ callback(namekey);
+ }
+ };
+
+ return ctor;
+});
diff --git a/apps/settings/test/unit/panels/sound/sound_test.js b/apps/settings/test/unit/panels/sound/sound_test.js
new file mode 100644
index 000000000000..6ff0ff0aec9b
--- /dev/null
+++ b/apps/settings/test/unit/panels/sound/sound_test.js
@@ -0,0 +1,183 @@
+/* global MockNavigatorMozTelephony */
+'use strict';
+mocha.globals([
+ 'MockL10n',
+ 'telephonyAddCall',
+ 'telephonyAddCdmaCall',
+ 'MockNavigatorMozTelephony',
+ 'MockMobileconnection',
+ 'MockNavigatorMozMobileConnections',
+ 'MockNavigatorSettings',
+ 'ForwardLock',
+ 'getSupportedNetworkInfo'
+]);
+
+suite('Sound > ', function() {
+ var Sound;
+ var realL10n, realTelephony, realMozSettings, realMozMobileConnections;
+
+ suiteSetup(function(done) {
+ testRequire([
+ 'shared_mocks/mock_navigator_moz_telephony',
+ 'unit/mock_l10n',
+ 'unit/mock_settings_listener',
+ 'panels/sound/sound',
+ 'shared_mocks/mock_navigator_moz_settings',
+ 'shared_mocks/mock_navigator_moz_mobile_connections'
+ ],
+ { //mock map
+ 'panels/sound/sound': {
+ 'shared/settings_listener': 'unit/mock_settings_listener'
+ }
+ },
+ function(MockNavigatorMozTelephony, MockL10n,
+ MockSettingsListener, module, MockNavigatorSettings) {
+ Sound = module;
+ // mock l10n
+ realL10n = window.navigator.mozL10n;
+ window.navigator.mozL10n = MockL10n;
+ // mock mozMobileConnections
+ realMozMobileConnections = window.navigator.mozMobileConnections;
+ window.navigator.mozMobileConnections =
+ window.MockNavigatorMozMobileConnections;
+ // mock mozTelephony
+ realTelephony = navigator.mozTelephony;
+ navigator.mozTelephony = MockNavigatorMozTelephony;
+ // mock mozSettings
+ realMozSettings = navigator.mozSettings;
+ navigator.mozSettings = MockNavigatorSettings;
+
+ done();
+ });
+ });
+
+ suiteTeardown(function() {
+ window.navigator.mozL10n = realL10n;
+ navigator.mozTelephony = realTelephony;
+ navigator.mozSettings = realMozSettings;
+ });
+
+ suite('initiation', function() {
+ var sound;
+ setup(function() {
+ sound = Sound();
+ var mock_elements = {};
+
+ this.sinon.stub(sound, '_configureTones');
+ this.sinon.stub(sound, '_handleTones');
+ window.getSupportedNetworkInfo = function() {};
+ this.sinon.stub(window, 'getSupportedNetworkInfo');
+ sound.init(mock_elements);
+ });
+
+ teardown(function() {
+ window.getSupportedNetworkInfo.restore();
+ });
+
+ test('we would call _configureTones and _handleTones in init', function() {
+ assert.ok(sound._configureTones.called);
+ assert.ok(sound._handleTones.called);
+ });
+
+ test('we would call getSupportedNetworkInfo in init', function() {
+ assert.ok(window.getSupportedNetworkInfo.called);
+ });
+ });
+
+ suite('_configureTones', function() {
+ var mock_elements;
+ var sound;
+ setup(function() {
+ sound = Sound();
+ mock_elements = {
+ ringer: document.createElement('div')
+ };
+
+ this.sinon.stub(sound, '_handleTones');
+ window.getSupportedNetworkInfo = function() {};
+ this.sinon.stub(window, 'getSupportedNetworkInfo');
+ });
+
+ teardown(function() {
+ window.getSupportedNetworkInfo.restore();
+ });
+
+ test('we would push alerttone by default in tones list', function() {
+ navigator.mozTelephony = null;
+ sound.init(mock_elements);
+ assert.equal(sound.tones.length, 1);
+ assert.equal(sound.tones[0].pickType, 'alerttone');
+ });
+
+ test('If device support telephony API, show the section for ringtones',
+ function() {
+ navigator.mozTelephony = MockNavigatorMozTelephony;
+ sound.init(mock_elements);
+ assert.equal(sound.tones.length, 2);
+ assert.equal(sound.tones[1].pickType, 'ringtone');
+ });
+ });
+
+ suite('_handleTones', function() {
+ var sound;
+ setup(function() {
+ sound = Sound();
+ sound.tones = [{
+ pickType: 'alerttone',
+ settingsKey: 'notification.ringtone',
+ allowNone: true,
+ button: document.createElement('div')
+ }];
+
+ sound._handleTones();
+ });
+
+ test('we would set button textContent in SettingsListener', function() {
+ assert.equal(sound.tones[0].button.textContent,
+ 'notification.ringtone.name');
+ });
+ });
+
+ suite('_checkRingtone', function() {
+ var sound;
+ setup(function() {
+ sound = Sound();
+ sound.tones = [{
+ pickType: 'alerttone',
+ settingsKey: 'notification.ringtone',
+ allowNone: true,
+ button: document.createElement('div')
+ }];
+
+ var blob = this.sinon.stub();
+ this.sinon.stub(window.URL, 'createObjectURL');
+ sound._checkRingtone(blob, 'mock_name', sound.tones[0]);
+ });
+
+ teardown(function() {
+ window.URL.createObjectURL.restore();
+ });
+
+ test('button textContent is changed in _checkRingtone', function() {
+ assert.equal(sound.tones[0].button.textContent, 'savingringtone');
+ });
+ });
+
+ suite('_setRingtone', function() {
+ var blob;
+ var sound;
+ setup(function() {
+ sound = Sound();
+ blob = this.sinon.stub();
+ sound._setRingtone(blob, 'aloha', 'notification.ringtone');
+ });
+
+ test('we would set ringtone value via _setRingtone', function() {
+ assert.equal(
+ navigator.mozSettings.mSettings['notification.ringtone.name'],
+ 'aloha');
+ assert.equal(navigator.mozSettings.mSettings['notification.ringtone'],
+ blob);
+ });
+ });
+});
diff --git a/shared/test/unit/mocks/mock_navigator_moz_mobile_connections.js b/shared/test/unit/mocks/mock_navigator_moz_mobile_connections.js
index 1a80ca70dbfc..ad31e79d8651 100644
--- a/shared/test/unit/mocks/mock_navigator_moz_mobile_connections.js
+++ b/shared/test/unit/mocks/mock_navigator_moz_mobile_connections.js
@@ -1,5 +1,9 @@
'use strict';
+/**
+ * Will provide 1 Mobileconnection by default. call mAddMobileConnection and
+ * mRemoveMobileConnection to add/remove extra connections.
+ */
(function() {
function MockMobileconnection() {
var props = ['voice', 'data', 'iccId', 'radioState', 'iccInfo'];