Publish Vulpes Preview 15 sources for desktop, Android and Tundra

Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

907
gaia/apps/fl/js/download.js Executable file
View file

@ -0,0 +1,907 @@
/*
* This is an implementation of the OMA Download specification:
* http://technical.openmobilealliance.org/Technical/release_program/docs/Download/V1_0-20040625-A/OMA-Download-OTA-V1_0-20040625-A.pdf
*
* It downloads and installs locked wallpaper, ringtones and songs
* roughly following these steps:
*
* 1) Download a download descriptor from the specified URL
* - onerror: display a message to the user and abort the activity
*
* 2) Validate and parse the download descriptor
* - extract all the required and optional tags from the descriptor document
* - if it doesn't contain the tags we expect, display an error message
* and abort the activity.
* - if the version number is wrong, display an error, report the failure,
* and abort the activity.
*
* 3) Check types
* - If we can't handle any of the types specified in the descriptor,
* report a failure to the server, display an error to the user and abort.
* - For audio files, ask the user if they want to use them as a ringtone
* or as a song
*
* 4) Confirm download and use of media
* - Display name, vendor, type, size, etc. to the user, remind them
* that the content will be locked (and that they may be charged for
* it) and ask them to confirm that they want to download.
* - If the user cancels, report that to the server and abort the activity.
*
* 5) Download content
* - Display a progress meter and cancel button
* - If the user clicks cancel, report to server and abort the activity.
*
* 6) Extract the content, if necessary
* - If the content type is a DRM message, then it is in multi-part
* MIME format and we have to extract the first part, using the boundary
* string from the response headers.
* - If extracting the content fails, notify the server and user and abort
*
* 7) Allow the user to preview the downloaded files
* - if the file cannot be loaded or played, abort with an error
* - otherwise, show a small image, or play part of the audio
* to the user and allow them to click Cancel or Install.
*
* 8) Install the content
* - For wallpapers and ringtones, save to the database
* - For music, encrypt the file and save to device storage
* - If this fails, report errors and abort
* - Report success to the server
* - For ringtones and wallpaper, set them as the default
*/
'use strict';
/* global
_,
$,
BinaryStringView,
debug,
DOWNLOAD_ERROR,
ERR_BAD_AUDIO,
ERR_BAD_DESCRIPTOR,
ERR_BAD_DRM_MESSAGE,
ERR_BAD_IMAGE,
ERR_BAD_TYPE,
ERR_CONTENT_DOWNLOAD_FAILED,
ERR_DS_SAVE_FAILURE,
ERR_DB_STORE_FAILURE,
ERR_DESCRIPTOR_DOWNLOAD_FAILED,
ERR_NO_SDCARD,
ERR_NO_SPACE,
ERR_SDCARD_IN_USE,
ERR_TOO_BIG,
ERR_UNSUPPORTED_TYPE,
ForwardLock,
getStorageIfAvailable,
getUnusedFilename,
INSTALL_ERROR,
MAX_DOWNLOAD_SIZE,
MediaUtils,
MimeTypeAliases,
objectStore,
OMADownloadStatus,
RINGTONE,
RINGTONE_KEY,
RINGTONE_NAME_KEY,
RINGTONE_ID_KEY,
showDialog,
SONG,
SUCCESS_RINGTONE,
SUCCESS_SONG,
SUCCESS_WALLPAPER,
SupportedAudioTypes,
SupportedImageTypes,
systemXHR,
WALLPAPER,
WALLPAPER_KEY
*/
// Wait until we're loaded, localized, and get an activity request
window.addEventListener('load', function() {
navigator.mozL10n.once(function() {
navigator.mozSetMessageHandler('activity', view);
});
});
//
// This function is invoked with:
//
// * a view activity for MIME type application/vnd.oma.dd+xml and
// activity.source.data.url set to the url of the download descriptor ".dd"
// file.
//
// or
//
// * a view activity for MIME type application/vnd.oma.drm.message and
// activity.source.data.url set to the url of the download ".dm" file.
//
function view(activity) {
debug('view() invoked for activity', activity.source.name,
'with data', JSON.stringify(activity.source.data));
var descriptor = {}; // The parsed form of the download descriptor
var mimeType; // Set by checkType() to the primary media type
var isImage = false; // These are also set by checkType()
var isAudio = false;
var installType; // Wallpaper, ringtone or song.
var hasDescriptor; // To acommodate direct download without a descriptor
//We start the proccess
// First a little bit of cleanup. Sometimes we get a content-type like
// Content-type: a/type; boundary=somethingelse
// And we don't really want anything after the ;
var activityType = normalizeMimeType(activity.source.data.type);
if (activityType === 'application/vnd.oma.drm.message') {
hasDescriptor = false;
var url = (activity.source.data.url || '');
descriptor.name = getFileNameFromURL(url);
descriptor.size = 0;
descriptor.types = ['application/vnd.oma.drm.message'];
descriptor.objectURI = url;
displayDownloadProgress();
} else {
hasDescriptor = true;
// We start by downloading the url passed with the activity request
downloadDescriptor(activity.source.data.url);
}
// We call this function on all mime type values we read from .dm or .dd
// files or we get in XHR headers. This strips off trailing parameters,
// converts to lower case, and fixes incorrect types (like audio/mp3).
function normalizeMimeType(type) {
// Strip off any mime type parameters
type = type.split(';')[0];
// convert to lower case
type = type.toLowerCase();
// Map bad types like "audio/mp3" to the correct type like "audio/mpeg"
if (type in MimeTypeAliases) {
type = MimeTypeAliases[type];
}
return type;
}
function askSongOrRingtone(aCallback) {
showDialog({
message: _('ringtone-or-song-question'),
okText: _('ringtone-response'),
cancelText: _('song-response'),
okCallback: aCallback.bind(undefined, RINGTONE),
cancelCallback: aCallback.bind(undefined, SONG)
});
}
function getFileNameFromURL(aUri) {
var name = '';
if (aUri) {
name = aUri.split('/').pop();
// Slice off the .dm suffix
if (name.endsWith('.dm')) {
name = name.slice(0, -3);
}
if (name.length > 18) {
name = name.substring(0, 15) + '\u2026';
}
}
return name;
}
// Gets the filename from a header.
function getFileNameFromHeader(aHeader) {
// This is not exactly RFC compliant since it will cut
// quoted-strings with quoted-pairs by the first "
var regexp = /filename="([^"]*)"/i;
var match = regexp.exec(aHeader);
var name = match && match[1];
if (name) {
name = getFileNameFromURL(name);
}
return name;
}
function reportError(type, errorID, downloadStatus, details) {
// Report to the console
console.error(type, errorID, downloadStatus, details);
// Report to the server, if necessary
if (downloadStatus) {
sendStatus(downloadStatus);
}
details = '[' + (details ? errorID + ': ' + details : errorID) + ']';
// Display error to the user
showDialog({
title: _(type) || type,
message: _(errorID),
details: details,
okCallback: function() {
// Cancel the activity after the user has seen the error message.
activity.postError(errorID);
}
});
}
function reportSuccess(successID) {
// This is a "well-intentioned attempt" to send the success code.
sendStatus(OMADownloadStatus.success);
showDialog({
message: _(successID) || successID,
okCallback: function() {
// Return from the activity after the user has seen the error message.
activity.postResult({ success: true });
}
});
}
function sendStatus(status, callback) {
// If the descriptor contained an installNotifyURI, then we need to
// report errors by posting to that URL
if (descriptor.installNotifyURI) {
var xhr = new XMLHttpRequest(systemXHR);
xhr.open('POST', descriptor.installNotifyURI);
xhr.timeout = 1000; // wait up to a second for a response
xhr.send(status);
// If there is a callback, call it on success, error or timeout
if (callback) {
xhr.onloadend = function() { callback(); };
}
} else if (callback) {
callback();
}
}
// Step 1: download the download descriptor
function downloadDescriptor(descriptorURL) {
debug('Step 1: downloading descriptor from', descriptorURL);
var xhr = new XMLHttpRequest(systemXHR);
xhr.open('GET', descriptorURL);
xhr.responseType = 'document'; // Parse it as an XML document
xhr.send();
xhr.onerror = function(e) {
reportError(DOWNLOAD_ERROR, ERR_DESCRIPTOR_DOWNLOAD_FAILED, null,
xhr.status + ' ' + xhr.statusText);
};
xhr.onload = function() {
// When we get the descriptor, go to step 2
// The setTimeout() may not be necessary here. The intent is to ensure
// that this XHR is complete before we begin downloading the .dm file.
setTimeout(function() {
parse(xhr.response);
});
};
}
// Step 2: Parse and validate the download descriptor document
//
// The format of the download descriptor is defined in
// http://technical.openmobilealliance.org/Technical/release_program/docs/
// Download/V1_0-20040625-A/OMA-Download-OTA-V1_0-20040625-A.pdf
//
function parse(document) {
// Get all the types listed for this content
var types = document.getElementsByTagName('type');
if (types) {
descriptor.types = [];
for (var i = 0; i < types.length; i++) {
descriptor.types.push(normalizeMimeType(types[i].textContent.trim()));
}
}
// A utility function for parsing the rest of the descriptor
function get(tag) {
var elts = document.getElementsByTagName(tag);
if (elts && elts.length) {
descriptor[tag] = elts[0].textContent.trim();
}
}
// Get the other properties, ignoring nextURL, infoURL, iconURI,
// and installParam (which are all optional)
get('size');
get('objectURI');
get('installNotifyURI');
get('DDVersion');
get('name');
get('description');
get('vendor');
debug('Step 2: parse descriptor:', JSON.stringify(descriptor));
// If any of the required attributes are missing, then report an error
if (!descriptor.types || descriptor.types.length === 0 ||
!descriptor.size || !descriptor.objectURI) {
reportError(DOWNLOAD_ERROR, ERR_BAD_DESCRIPTOR,
OMADownloadStatus.INVALID_DESCRIPTOR);
return;
}
// If the size is too big, just abort now so we don't run out of
// memory when we download the .dm file later
if (descriptor.size > MAX_DOWNLOAD_SIZE) {
MediaUtils.getLocalizedSize(descriptor.size).then((msg) => {
reportError(DOWNLOAD_ERROR, ERR_TOO_BIG,
OMADownloadStatus.INSUFFICIENT_MEMORY,
msg);
});
return;
}
// If the descriptor did not include a name, derive one from the URL
if (!descriptor.name) {
descriptor.name = getFileNameFromURL(descriptor.objectURI);
}
// If the version number is too large, abort
if (descriptor.DDVersion && parseInt(descriptor.DDVersion) > 1) {
reportError(DOWNLOAD_ERROR, ERR_BAD_DESCRIPTOR,
OMADownloadStatus.INVALID_DDVERSION);
return;
}
// Otherwise, move on to step 3
checkTypes();
}
// Step 3: check that we can handle the types listed in the download
// descriptor and abort with an error if we can't
function checkTypes() {
debug('Step 3: check types');
var types = descriptor.types;
for (var i = 0; i < types.length; i++) {
var type = types[i];
if (type === 'application/vnd.oma.drm.message') {
// This type just indicates that the download will be a multi-part
// MIME message body rather than the content directly.
// We support it.
continue;
}
mimeType = type;
if (type in SupportedImageTypes) {
isImage = true;
} else if (type in SupportedAudioTypes) {
isAudio = true;
} else {
mimeType = undefined;
reportError(DOWNLOAD_ERROR, ERR_UNSUPPORTED_TYPE,
OMADownloadStatus.NON_ACCEPTABLE_CONTENT,
type);
return;
}
}
// We expect one media type plus one option DRM message ".dm" MIME
// type. If we didn't see a media type at all, that is an
// error. And if we had more than two types, then the content is
// some kind of composite thing that we don't know how to handle.
if (types.length < 1 || types.length > 2 || // wrong number of types
(isAudio && isImage) || // both audio and image
(!isAudio && !isImage)) { // neither audio nor image
reportError(DOWNLOAD_ERROR, ERR_BAD_TYPE,
OMADownloadStatus.NON_ACCEPTABLE_CONTENT);
return;
}
// If this is an image, then we know we're downloading wallpaper
// and can move on to the confirmation screen. For audio, we need
// to ask the user if they want a ringtone or a song
if (isImage) {
installType = WALLPAPER;
confirmDownload();
} else {
askSongOrRingtone(function(response) {
installType = response;
confirmDownload();
});
}
}
// Step 4: Let the user confirm or cancel the download
function confirmDownload() {
debug('Step 4: confirm download');
// Customize the dialog title based on the installType
var titleElement = $('download-confirmation-title');
titleElement.textContent = _('download-confirmation-title-' + installType);
// Display details from the descriptor
$('download-confirmation-name').textContent = descriptor.name;
$('download-confirmation-type').textContent = mimeType;
MediaUtils.getLocalizedSizeTokens(descriptor.size).then((args) => {
navigator.mozL10n.setAttributes(
$('download-confirmation-size'), 'fileSize', args);
});
// XXX: this might be too big to fit on the screen.
$('download-confirmation-description').textContent = descriptor.description;
// Display the Locked Content Advisory
var lca;
if (descriptor.vendor) {
lca = _('locked-content-advisory', {
vendor: descriptor.vendor
});
} else {
lca = _('locked-content-advisory-generic');
}
$('locked-content-advisory-text').textContent = lca;
// Hook up the Download button: if the user clicks it, go on to step 5
$('download-confirm').onclick = function(e) {
e.target.disabled = true; // Don't let the user click twice
displayDownloadProgress();
};
// Hook up the Cancel button: if the user clicks it, report this to
// the server and abort without displaying an error message to the user.
$('download-cancel').onclick = function(e) {
debug('download cancelled');
$('download-confirmation').hidden = true;
sendStatus(OMADownloadStatus.USER_CANCELLED, function() {
activity.postError('cancelled');
});
};
// Finally, make the confirmation screen visible
$('download-confirmation').hidden = false;
}
// Step 5: download the content and display progress
function displayDownloadProgress() {
debug('Step 5: download content and display progress');
var bar;
var contentLength;
if (!hasDescriptor) {
$('direct-download').hidden = false;
bar = $('direct-download-progress');
// We might get or not the Content-Length header. We defer the max size
// setting until the first progress call.
bar.max = 100;
} else {
// Change the dialog title from Download? to Downloading...
var titleElement = $('download-confirmation-title');
titleElement.textContent = _('downloading-' + installType);
$('download-confirmation').hidden = false;
bar = $('download-progress');
bar.max = descriptor.size;
}
bar.style.visibility = 'visible';
bar.value = 0;
debug('starting download for', descriptor.objectURI);
var download = new XMLHttpRequest(systemXHR);
download.open('GET', descriptor.objectURI);
download.responseType = 'arraybuffer';
download.send();
download.onprogress = function(e) {
if (contentLength === undefined) {
contentLength = download.getResponseHeader('Content-Length');
bar.max = contentLength && parseInt(contentLength) || bar.max;
}
if (descriptor.size || contentLength) {
bar.value = e.loaded;
} else {
// We didn't get a download size, let's just do a percent increase
bar.value = (bar.value + 10) % bar.max;
}
};
// XXX This onerror function is being called with status 0 and statusText OK
// It only happens sometimes. A caching thing? A bug in gecko?
// There is no response body when this happends
download.onerror = function() {
$('download-confirmation').hidden = true;
reportError(DOWNLOAD_ERROR, ERR_CONTENT_DOWNLOAD_FAILED,
OMADownloadStatus.LOADER_ERROR,
download.status + ' ' + download.statusText);
};
download.onload = function() {
// We're done downloading, so we can't cancel it anymore
$('download-cancel').disabled = true;
$('download-confirmation').hidden = true;
var cdHeader =
download.getResponseHeader('Content-Disposition');
if (cdHeader && !hasDescriptor) {
descriptor.name = getFileNameFromHeader(cdHeader) || descriptor.name;
}
// Move on to step 6
var type = normalizeMimeType(download.getResponseHeader('Content-Type'));
extractContent(type, download.response);
};
// Add another cancel button handler to abort the download if the
// user clicks cancel
$('download-cancel').addEventListener('click', function() {
download.abort();
});
}
// Step 6: extract the downloaded content from the array buffer
function extractContent(type, buffer) {
debug('Step 6a: extractContent', type, buffer.byteLength);
if (type !== 'application/vnd.oma.drm.message') {
// In this case, we already have the content and don't need to
// extract anything. Move on to step 7
previewContent(type, buffer);
return;
}
// View the array buffer as if it was a binary string
var dataString = new BinaryStringView(buffer);
// If we're here, then dataString is a multipart message with a
// boundary string at the start and end. There should be only one part
// but we still must strip off the separators and extract the real
// content type. The type argument should specify the boundary string
// but in practice it seems like it doesn't so we find it by examining
// the end of the string.
// Make sure there are no trailing newlines
dataString = dataString.trim();
function malformed(why) {
if (why) {
debug('malformed drm message:', why);
}
reportError(DOWNLOAD_ERROR, ERR_BAD_DRM_MESSAGE,
OMADownloadStatus.ATTRIBUTE_MISMATCH);
}
// The last two characters should be "--". If they are not, then
// the content is malformed
if (dataString.slice(-2).toString() !== '--') {
return malformed('missing trailing --');
}
// Find the last newline.
var end = dataString.lastIndexOf('\r\n');
if (end === -1) {
return malformed('missing final \r\n');
}
// The boundary string begins right after that newline
var boundaryString = dataString.slice(end + 2, -2).toString() + '\r\n';
dataString = dataString.slice(0, end);
debug('Boundary string is', boundaryString);
// The first occurance of the boundary string is the start of
// the content headers
var headerStart =
dataString.indexOf(boundaryString) + boundaryString.length;
if (headerStart === -1) {
return malformed('missing boundary string at start');
}
// If there are any more occurrances of the boundary string then
// this is probably a .dm object for "combined delivery" and we
// must reject it since we support only forward lock.
if (dataString.indexOf(boundaryString, headerStart) !== -1) {
return malformed('more than one part');
}
var contentStart = dataString.indexOf('\r\n\r\n', headerStart);
if (contentStart === -1) {
return malformed('missing header separator');
}
var headers = dataString.slice(headerStart,
contentStart).toString().split('\r\n');
var content = dataString.slice(contentStart + 4);
debug('got first part headers:', JSON.stringify(headers));
// find the content-type header for the content
var contentType;
for (var i = 0; i < headers.length; i++) {
var header = headers[i].toLowerCase();
if (header.startsWith('content-type:')) {
contentType = normalizeMimeType(header.substring(13).trim());
}
}
if (!contentType) {
return malformed('no content-type header found in first part');
}
debug('Found content in drm message file', contentType, content.length);
previewContent(contentType, content.toArrayBuffer());
}
// Step 7
function previewContent(type, bytes) {
if (!hasDescriptor) {
mimeType = type;
if (type in SupportedImageTypes) {
isImage = true;
} else if (type in SupportedAudioTypes) {
isAudio = true;
} else {
mimeType = undefined;
reportError(DOWNLOAD_ERROR, ERR_UNSUPPORTED_TYPE,
OMADownloadStatus.NON_ACCEPTABLE_CONTENT,
type);
return;
}
} else {
// If the content we found in the DRM message (.dm) file has a different
// Content-Type than what we were expecting based on the .dd file
// report an error.
if (type !== mimeType) {
reportError(DOWNLOAD_ERROR, ERR_BAD_DRM_MESSAGE,
OMADownloadStatus.ATTRIBUTE_MISMATCH);
return;
}
}
if (isImage) {
installType = WALLPAPER;
previewImage(type, bytes);
} else {
if (!hasDescriptor) {
askSongOrRingtone(function(response) {
installType = response;
previewAudio(type, bytes);
});
} else {
// We already asked how to install the audio before
// downloading
previewAudio(type, bytes);
}
}
}
function previewImage(type, bytes) {
// Hide the confirmation screen and show the preview screen
$('download-confirmation').hidden = true;
$('preview-image-screen').hidden = false;
// Load the image into an off-screen image
var blob = new Blob([bytes], { type: type });
var url = URL.createObjectURL(blob);
var image = new Image();
image.src = url;
// If we can't load the image, abort with an error
image.onerror = function() {
URL.revokeObjectURL(url);
reportError(INSTALL_ERROR, ERR_BAD_IMAGE,
OMADownloadStatus.NON_ACCEPTABLE_CONTENT);
return;
};
// If the image loads successfully, use it as the background image
// for the preview. And enable the install button
image.onload = function() {
var preview = $('preview-image');
preview.style.backgroundImage = 'url(' + url + ')';
$('preview-image-install').disabled = false;
};
// If the user clicks Install, move on to step 8
$('preview-image-install').onclick = function() {
URL.revokeObjectURL(url);
installContent(bytes);
};
// If the user clicks cancel, return to the browser without installing
$('preview-image-cancel').onclick = function() {
URL.revokeObjectURL(url);
sendStatus(OMADownloadStatus.USER_CANCELLED, function() {
activity.postError('cancelled');
});
};
}
function previewAudio(type, bytes) {
// Hide the confirmation screen and show the preview screen
$('download-confirmation').hidden = true;
$('preview-audio-screen').hidden = false;
// Create a blob and a blob: url that we can play
var blob = new Blob([bytes], { type: type });
var url = URL.createObjectURL(blob);
var player = $('preview-audio');
player.src = url;
// If we can't play the audio, abort with an error
player.onerror = function() {
cleanup();
reportError(INSTALL_ERROR,
ERR_BAD_AUDIO, OMADownloadStatus.NON_ACCEPTABLE_CONTENT);
return;
};
// If we can play the music, enable the Install button
player.oncanplay = function() {
$('preview-audio-install').disabled = false;
};
// Don't allow the user to play more than 50% of the audio, or
// 5 seconds, whichever is longer
player.ontimeupdate = player.ondurationchange = function() {
if (player.currentTime > Math.max(5, player.duration / 2)) {
player.pause();
player.currentTime = 0;
}
};
// If the user clicks Install, move on to step 8
$('preview-audio-install').onclick = function() {
cleanup();
installContent(bytes);
};
// If the user clicks cancel, return to the browser without installing
$('preview-audio-cancel').onclick = function() {
cleanup();
sendStatus(OMADownloadStatus.USER_CANCELLED, function() {
activity.postError('cancelled');
});
};
function cleanup() {
player.pause();
player.removeAttribute('src');
player.load();
URL.revokeObjectURL(url);
}
}
// Step 8
function installContent(bytes) {
switch (installType) {
case SONG:
installSong(bytes);
return;
case RINGTONE:
case WALLPAPER:
install(installType, bytes, descriptor);
return;
}
}
function installSong(buffer) {
debug('installSong');
ForwardLock.getOrCreateKey(function(secret) {
debug('got secret key');
var blob = ForwardLock.lockBuffer(secret, buffer, mimeType, {
vendor: descriptor.vendor,
name: descriptor.name,
description: descriptor.description
});
debug('encrypted song');
getStorageIfAvailable('music', blob.size,
storageSuccess, storageError);
function storageError(err) {
debug('storageError:', err);
if (err === 'unavailable') {
reportError(INSTALL_ERROR, ERR_NO_SDCARD,
OMADownloadStatus.INSUFFICIENT_MEMORY);
} else if (err === 'shared') {
reportError(INSTALL_ERROR, ERR_SDCARD_IN_USE,
OMADownloadStatus.INSUFFICIENT_MEMORY);
} else {
reportError(INSTALL_ERROR, ERR_NO_SPACE,
OMADownloadStatus.INSUFFICIENT_MEMORY, err);
}
}
function storageSuccess(storage) {
debug('got music storage');
var filename = 'locked/';
if (descriptor.vendor) {
filename += fixPath(descriptor.vendor) + '/';
}
filename += fixPath(descriptor.name) + '.lcka';
debug('trying filename', filename);
// Remove characters in s that might be significant in a filename
function fixPath(s) { return s.replace(/[\/\\\.\,\?\*\:]/g, ''); }
getUnusedFilename(storage, filename, function(filename) {
debug('using filename', filename);
var addreq = storage.addNamed(blob, filename);
addreq.onerror = function() {
// We don't expect this to fail, since we've already checked
// available storage space.
reportError(INSTALL_ERROR, ERR_DS_SAVE_FAILURE,
OMADownloadStatus.LOADER_ERROR,
addreq.error.name);
};
addreq.onsuccess = function() {
// The song is installed and ready to be played, so
// send a success notification, tell the user that the
// song was installed and return to the invoking application.
reportSuccess(SUCCESS_SONG);
};
});
}
});
}
function install(installType, buffer, descriptor) {
// We don't need the secret key here, but we need to create one
// if it doesn't already exist
ForwardLock.getOrCreateKey();
var objectStoreName = installType === RINGTONE ? 'ringtones' : 'wallpapers';
var blob = new Blob([buffer], { type: mimeType });
objectStore.readwrite(objectStoreName, function(store) {
var req = store.add({ blob: blob, descriptor: descriptor });
req.onerror = function() {
reportError(INSTALL_ERROR, ERR_DB_STORE_FAILURE,
OMADownloadStatus.LOADER_ERROR,
req.error.name);
};
req.onsuccess = function() {
// Report that we've successfully saved the media.
sendStatus(OMADownloadStatus.SUCCESS);
// Install this blob as the default ringtone or wallpaper
// We assume that if the user is downloading this media they
// want to use it right away. Otherwise we could put a checkbox
// on the confirmation screen.
var lock = navigator.mozSettings.createLock();
var settings = {};
if (installType === RINGTONE) {
settings[RINGTONE_KEY] = blob;
settings[RINGTONE_NAME_KEY] = descriptor.name;
settings[RINGTONE_ID_KEY] = req.result;
}
else {
settings[WALLPAPER_KEY] = blob;
}
lock.set(settings).onsuccess = function() {
var msgid;
if (installType === RINGTONE) {
msgid = SUCCESS_RINGTONE;
} else {
msgid = SUCCESS_WALLPAPER;
}
showDialog({
message: _(msgid),
okCallback: function() {
// Return from the activity after the user has seen the message.
activity.postResult({ success: true });
}
});
};
};
});
}
}