Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
313 lines
10 KiB
JavaScript
Executable file
313 lines
10 KiB
JavaScript
Executable file
/* global URL, MozActivity */
|
|
/**
|
|
* Handle tone functionality in sound panel
|
|
* @module ToneManager
|
|
*/
|
|
define(function(require) {
|
|
'use strict';
|
|
|
|
var SettingsListener = require('shared/settings_listener');
|
|
var SettingsCache = require('modules/settings_cache');
|
|
var ForwardLock = require('shared/omadrm/fl');
|
|
|
|
var ToneManager = function() {
|
|
this._elements = null;
|
|
this._tones = null;
|
|
};
|
|
|
|
ToneManager.prototype = {
|
|
/**
|
|
* initialization.
|
|
*
|
|
* @access public
|
|
* @memberOf ToneManager.prototype
|
|
*/
|
|
init: function tm_init(elements) {
|
|
this._elements = elements;
|
|
this._configureTones();
|
|
this._handleTones();
|
|
|
|
this._elements.manageTones.addEventListener('click',
|
|
this._manageTonesClickHandler);
|
|
},
|
|
|
|
/**
|
|
* Initialize the ring tone and alert tone menus.
|
|
*
|
|
* @access private
|
|
* @memberOf ToneManager.prototype
|
|
*/
|
|
_configureTones: function tm_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.alertToneSelection,
|
|
desc: this._elements.alertToneSelectionDesc
|
|
}];
|
|
|
|
// 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.ringToneSelection,
|
|
desc: this._elements.ringToneSelectionDesc
|
|
});
|
|
this._elements.ringer.hidden = false;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* The button looks like a select element and holds the name of the
|
|
* currently-selected tone. Sometimes the name is an l10n ID
|
|
* and sometimes it is just text.
|
|
*
|
|
* @access private
|
|
* @memberOf ToneManager.prototype
|
|
*/
|
|
_renderToneName: function tm_renderToneName(tone, tonename) {
|
|
var l10nID = tonename && tonename.l10nID;
|
|
|
|
if (l10nID) {
|
|
tone.desc.setAttribute('data-l10n-id', l10nID);
|
|
} else {
|
|
tone.desc.removeAttribute('data-l10n-id');
|
|
tone.desc.textContent = tonename;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* render tone content
|
|
*
|
|
* @access private
|
|
* @memberOf ToneManager.prototype
|
|
* @param {Object} tone tone element
|
|
* @param {Object} result web activity result
|
|
* @param {String} secret sound is playable
|
|
* @return {Promise} A promise that resolves once the tone has been rendered
|
|
*/
|
|
_renderTone: function tm_renderTone(tone, result, secret) {
|
|
var oldRingtoneName = null;
|
|
|
|
var l10nId = tone.desc.getAttribute('data-l10n-id');
|
|
if (!l10nId) {
|
|
oldRingtoneName = tone.desc.textContent;
|
|
}
|
|
tone.desc.setAttribute('data-l10n-id', 'saving-tone');
|
|
|
|
var getBlob;
|
|
if (result.blob.type.split('/')[1] === ForwardLock.mimeSubtype) {
|
|
// If we got a locked ringtone, we have to unlock it first.
|
|
getBlob = new Promise((resolve, reject) => {
|
|
ForwardLock.unlockBlob(secret, result.blob, (unlocked) => {
|
|
resolve(unlocked);
|
|
});
|
|
});
|
|
} else {
|
|
// Otherwise we can just use the blob directly.
|
|
getBlob = Promise.resolve(result.blob);
|
|
}
|
|
|
|
var getIsPlayable = getBlob.then((blob) => {
|
|
return this._isPlayableTone(blob);
|
|
});
|
|
|
|
return Promise.all(
|
|
[getBlob, getIsPlayable]
|
|
).then(([blob, isPlayable]) => {
|
|
if (isPlayable) {
|
|
this._setRingtone(tone.settingsKey, result.l10nID,
|
|
result.name, blob, result.id);
|
|
} else {
|
|
if (l10nId) {
|
|
tone.desc.setAttribute('data-l10n-id', l10nId);
|
|
} else {
|
|
// remove temp 'saving-tone' l10nId
|
|
tone.desc.removeAttribute('data-l10n-id');
|
|
tone.desc.textContent = oldRingtoneName;
|
|
}
|
|
document.l10n.formatValue('unplayable-ringtone').then(msg =>
|
|
alert(msg));
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Call web activity to pick a tone
|
|
*
|
|
* @access private
|
|
* @memberOf ToneManager.prototype
|
|
* @param {Object} tone tone element
|
|
* @param {Number} currentToneID tone id
|
|
* @param {String} secret forwardlock secret key
|
|
*/
|
|
_pickTone: function tm_pickRingTone(tone, currentToneID, secret) {
|
|
var self = this;
|
|
var activity = new MozActivity({
|
|
name: 'pick',
|
|
data: {
|
|
type: tone.pickType,
|
|
allowNone: tone.allowNone,
|
|
currentToneID: currentToneID,
|
|
// 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 result = activity.result;
|
|
if (!result.blob) {
|
|
if (tone.allowNone) {
|
|
// If we allow a null blob, then everything is okay
|
|
self._setRingtone(tone.settingsKey, result.l10nID, result.name,
|
|
result.blob, result.id);
|
|
} 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.)
|
|
document.l10n.formatValue('unplayable-ringtone').then(msg =>
|
|
alert(msg));
|
|
}
|
|
return;
|
|
}
|
|
|
|
self._renderTone(tone, result, secret);
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Update Ringtones list.
|
|
*
|
|
* @access private
|
|
* @memberOf ToneManager.prototype
|
|
*/
|
|
_handleTones: function tm_handleTones() {
|
|
// 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';
|
|
var idKey = tone.settingsKey + '.id';
|
|
|
|
SettingsListener.observe(nameKey, '',
|
|
this._renderToneName.bind(this, tone));
|
|
|
|
var self = this;
|
|
// When the user clicks the button, we launch an activity that lets
|
|
// the user select new ringtone.
|
|
tone.button.addEventListener('click', function() {
|
|
// First, get the ID of the currently-selected tone.
|
|
SettingsCache.getSettings(function(results) {
|
|
var currentToneID = results[idKey];
|
|
|
|
// 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) {
|
|
self._pickTone(tone, currentToneID, secret);
|
|
});
|
|
});
|
|
});
|
|
}.bind(this));
|
|
},
|
|
|
|
/**
|
|
* Save the sound blob in the settings 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.
|
|
*
|
|
* @access private
|
|
* @memberOf ToneManager.prototype
|
|
* @param {String} settingsKey key string
|
|
* @param {String} l10nID element l10nID id
|
|
* @param {String} toneName element name
|
|
* @param {Blob} blob tone blob
|
|
* @param {String} id tone id
|
|
*/
|
|
_setRingtone: function tm_setRingtone(settingsKey, l10nID, toneName,
|
|
blob, id) {
|
|
var blobKey = settingsKey;
|
|
var nameKey = settingsKey + '.name';
|
|
var idKey = settingsKey + '.id';
|
|
|
|
// Update the settings database. This will cause the button
|
|
// text to change as well because of the SettingsListener above.
|
|
var values = {};
|
|
var name = l10nID ? {l10nID : l10nID} : toneName;
|
|
|
|
values[blobKey] = blob;
|
|
values[nameKey] = name || '';
|
|
values[idKey] = id;
|
|
navigator.mozSettings.createLock().set(values);
|
|
},
|
|
|
|
/**
|
|
* 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!
|
|
*
|
|
* @access private
|
|
* @memberOf ToneManager.prototype
|
|
* @param {Blob} blob tone blob
|
|
* @return {Promise} A promise that resolves to the media playable stat
|
|
*/
|
|
_isPlayableTone: function tm_isPlayableTone(blob) {
|
|
return new Promise(function(resolve, reject) {
|
|
var player = new Audio();
|
|
player.preload = 'metadata';
|
|
player.src = URL.createObjectURL(blob);
|
|
player.oncanplay = function() {
|
|
release();
|
|
resolve(true);
|
|
};
|
|
player.onerror = function() {
|
|
release();
|
|
resolve(false);
|
|
};
|
|
|
|
function release() {
|
|
URL.revokeObjectURL(player.src);
|
|
player.removeAttribute('src');
|
|
player.load();
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Call ringtone activity to manage tones.
|
|
*
|
|
* @access private
|
|
* @memberOf ToneManager.prototype
|
|
*/
|
|
_manageTonesClickHandler: function tm_manageTonesClickHandler() {
|
|
var activity = new MozActivity({
|
|
name: 'configure',
|
|
data: {
|
|
target: 'ringtone'
|
|
}
|
|
});
|
|
|
|
// We should hopefully never encounter this error, but if we do, it means
|
|
// we couldn't find the ringtone app. It also has the happy side effect of
|
|
// quieting jshint about not using the `activity` variable.
|
|
activity.onerror = function() {
|
|
console.log(this.error);
|
|
if (this.error.name === 'NO_PROVIDER') {
|
|
document.l10n.formatValue('no-ringtone-app').then(msg =>
|
|
alert(msg));
|
|
}
|
|
};
|
|
}
|
|
};
|
|
|
|
return function ctor_toneManager() {
|
|
return new ToneManager();
|
|
};
|
|
});
|