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:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
197
gaia/apps/settings/js/activity_handler.js
Executable file
197
gaia/apps/settings/js/activity_handler.js
Executable file
|
|
@ -0,0 +1,197 @@
|
|||
/* exported ActivityHandler */
|
||||
/**
|
||||
* ActivityHandler handles activity requests and parse the information from the
|
||||
* request including section name and filter.
|
||||
*
|
||||
* @module js/activity_handler
|
||||
*/
|
||||
(function(exports) {
|
||||
'use strict';
|
||||
|
||||
var ActivityHandler = {
|
||||
/**
|
||||
* A promise indicating whether the module has received the activity
|
||||
* request.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf ActivityHandler
|
||||
* @type {Promise}
|
||||
*/
|
||||
_readyPromise: null,
|
||||
|
||||
/**
|
||||
* The target panel id retrieved from the activity request.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf ActivityHandler
|
||||
* @type {String}
|
||||
*/
|
||||
_targetPanelId: null,
|
||||
|
||||
/**
|
||||
* Possible target panel options retrieved from the activity request.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf ActivityHandler
|
||||
* @type {Object}
|
||||
*/
|
||||
_targetPanelOptions: null,
|
||||
|
||||
/**
|
||||
* The activity object received from the message handler.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf ActivityHandler
|
||||
* @type {Object}
|
||||
*/
|
||||
_currentActivity: null,
|
||||
|
||||
/**
|
||||
* An object that defines functions for handling different type of
|
||||
* activities. The handling function must return the target panel id for
|
||||
* handling the activity.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf ActivityHandler
|
||||
* @type {Object}
|
||||
*/
|
||||
_handlers: {
|
||||
'configure': function ah_configureHandler(activitySource) {
|
||||
var targetPanelId = activitySource.data.section || 'root';
|
||||
var targetPanel = document.getElementById(targetPanelId);
|
||||
|
||||
// Validate if the section exists
|
||||
if (!targetPanel || targetPanel.tagName !== 'SECTION') {
|
||||
var msg =
|
||||
'Trying to open an non-existent section: ' + targetPanelId;
|
||||
console.warn(msg);
|
||||
|
||||
// fallback to root panel
|
||||
targetPanelId = 'root';
|
||||
targetPanel = document.getElementById(targetPanelId);
|
||||
}
|
||||
|
||||
switch(targetPanelId) {
|
||||
case 'root':
|
||||
// Apply the filter
|
||||
document.body.dataset.filterBy =
|
||||
activitySource.data.filterBy || 'all';
|
||||
break;
|
||||
default:
|
||||
// Mark the desired panel as a dialog
|
||||
targetPanel.dataset.dialog = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
targetPanelId: targetPanelId,
|
||||
options: activitySource.data.options
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* It calls to corresponding handlers based on the activity name. It should
|
||||
* also set the target panel id and, optionally, target panel options.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf ActivityHandler
|
||||
* @param {Object} activitySource
|
||||
* The source object in an activity.
|
||||
*/
|
||||
_handleActivity: function ah_handler(activitySource) {
|
||||
var handle = this._handlers[activitySource.name];
|
||||
if (handle) {
|
||||
var {targetPanelId, options} = handle(activitySource);
|
||||
this._targetPanelId = targetPanelId;
|
||||
this._targetPanelOptions = options;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* It registers a handler for visibility changes so that the activity can
|
||||
* be completed when users press on the home button.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf ActivityHandler
|
||||
*/
|
||||
_registerListener: function ah_registerListener() {
|
||||
// Post result when pressing home button
|
||||
document.addEventListener('visibilitychange', function change() {
|
||||
if (document.hidden) {
|
||||
document.removeEventListener('visibilitychange', change);
|
||||
// An activity can be closed either by pressing the 'X' button
|
||||
// or by a visibility change (i.e. home button or app switch).
|
||||
// Send a result to finish this activity
|
||||
if (this._currentActivity) {
|
||||
this._currentActivity.postResult(null);
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* It returns the id of the target panel that can handle the activity.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf ActivityHandler
|
||||
* @returns {String}
|
||||
*/
|
||||
get targetPanelId() {
|
||||
return this._targetPanelId;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns possible target panel options needed to handle the activity.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf ActivityHandler
|
||||
* @returns {Object}
|
||||
*/
|
||||
get targetPanelOptions() {
|
||||
return this._targetPanelOptions;
|
||||
},
|
||||
|
||||
/**
|
||||
* It waits for the system message of an activity and initializes all
|
||||
* properties by parsing the information in an activity.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf ActivityHandler
|
||||
* @returns {Promise}
|
||||
*/
|
||||
ready: function ah_ready() {
|
||||
if (!this._readyPromise) {
|
||||
this._readyPromise = new Promise((resolve) => {
|
||||
navigator.mozSetMessageHandler('activity', (activity) => {
|
||||
this._currentActivity = activity;
|
||||
this._handleActivity(this._currentActivity.source);
|
||||
this._registerListener();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
return this._readyPromise;
|
||||
},
|
||||
|
||||
/**
|
||||
* It sends the result back to the caller of the activity and finishes the
|
||||
* activity.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf ActivityHandler
|
||||
* @param {Object} result
|
||||
* The result to be sent back to the caller.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
postResult: function ah_postResult(result) {
|
||||
return this.ready().then(() => {
|
||||
if (this._currentActivity) {
|
||||
this._currentActivity.postResult(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.ActivityHandler = ActivityHandler;
|
||||
})(window);
|
||||
450
gaia/apps/settings/js/config/require.js
Executable file
450
gaia/apps/settings/js/config/require.js
Executable file
|
|
@ -0,0 +1,450 @@
|
|||
require.config({
|
||||
baseUrl: '/js',
|
||||
paths: {
|
||||
'modules': 'modules',
|
||||
'panels': 'panels',
|
||||
'shared': '../shared/js',
|
||||
'views': '../views'
|
||||
},
|
||||
// This is the default value of the loading timeout, we will disable the
|
||||
// timeout in the production build
|
||||
waitSeconds: 7,
|
||||
// shim global object into AMD format
|
||||
// organized in alphabet order
|
||||
shim: {
|
||||
'dsds_settings': {
|
||||
exports: 'DsdsSettings'
|
||||
},
|
||||
'settings': {
|
||||
exports: 'Settings'
|
||||
},
|
||||
'shared/addons/match_pattern': {
|
||||
exports: 'MatchPattern'
|
||||
},
|
||||
'shared/airplane_mode_helper': {
|
||||
exports: 'AirplaneModeHelper'
|
||||
},
|
||||
'shared/apn_helper': {
|
||||
exports: 'ApnHelper'
|
||||
},
|
||||
'shared/async_storage': {
|
||||
exports: 'asyncStorage'
|
||||
},
|
||||
'shared/bluetooth_helper': {
|
||||
exports: 'BluetoothHelper'
|
||||
},
|
||||
'shared/device_storage/enumerate_all': {
|
||||
exports: 'enumerateAll'
|
||||
},
|
||||
'shared/download/download_formatter': {
|
||||
exports: 'DownloadFormatter'
|
||||
},
|
||||
'shared/download/download_store': {
|
||||
exports: 'DownloadStore'
|
||||
},
|
||||
'shared/download/download_ui': {
|
||||
exports: 'DownloadUI'
|
||||
},
|
||||
'shared/download/download_helper': {
|
||||
exports: 'DownloadHelper'
|
||||
},
|
||||
'shared/fxa_iac_client': {
|
||||
exports: 'FxAccountsIACHelper'
|
||||
},
|
||||
'shared/homescreens/homescreen_settings': {
|
||||
exports: 'homescreenSettings'
|
||||
},
|
||||
'shared/icc_helper': {
|
||||
exports: 'IccHelper'
|
||||
},
|
||||
'shared/keyboard_helper': {
|
||||
exports: 'KeyboardHelper',
|
||||
deps: ['shared/input_mgmt/input_app_list']
|
||||
},
|
||||
'shared/language_list': {
|
||||
exports: 'LanguageList'
|
||||
},
|
||||
'shared/lazy_loader': {
|
||||
exports: 'LazyLoader'
|
||||
},
|
||||
'shared/manifest_helper': {
|
||||
exports: 'ManifestHelper'
|
||||
},
|
||||
'shared/mime_mapper': {
|
||||
exports: 'MimeMapper'
|
||||
},
|
||||
'shared/mobile_operator': {
|
||||
exports: 'MobileOperator'
|
||||
},
|
||||
'shared/omadrm/fl': {
|
||||
exports: 'ForwardLock'
|
||||
},
|
||||
'shared/passcode_helper': {
|
||||
exports: 'PasscodeHelper'
|
||||
},
|
||||
'shared/sanitizer': {
|
||||
exports: 'Sanitizer'
|
||||
},
|
||||
'shared/screen_layout': {
|
||||
exports: 'ScreenLayout'
|
||||
},
|
||||
'shared/search_provider': {
|
||||
exports: 'SearchProvider'
|
||||
},
|
||||
'shared/settings_helper': {
|
||||
exports: 'SettingsHelper'
|
||||
},
|
||||
'shared/settings_listener': {
|
||||
exports: 'SettingsListener'
|
||||
},
|
||||
'shared/settings_url': {
|
||||
exports: 'SettingsURL'
|
||||
},
|
||||
'shared/sim_settings_helper': {
|
||||
exports: 'SimSettingsHelper'
|
||||
},
|
||||
'shared/simslot': {
|
||||
exports: 'SIMSlot'
|
||||
},
|
||||
'shared/simslot_manager': {
|
||||
exports: 'SIMSlotManager',
|
||||
deps: ['shared/simslot']
|
||||
},
|
||||
'shared/stk_helper': {
|
||||
exports: 'STKHelper'
|
||||
},
|
||||
'shared/text_normalizer': {
|
||||
exports: 'Normalizer'
|
||||
},
|
||||
'shared/toaster': {
|
||||
exports: 'Toaster'
|
||||
},
|
||||
'shared/tz_select': {
|
||||
exports: 'tzSelect',
|
||||
deps: ['shared/icc_helper']
|
||||
},
|
||||
'shared/uuid': {
|
||||
exports: 'uuid'
|
||||
},
|
||||
'shared/findmydevice_iac_api': {
|
||||
exports: 'wakeUpFindMyDevice'
|
||||
},
|
||||
'shared/wifi_helper': {
|
||||
exports: 'WifiHelper'
|
||||
},
|
||||
'vendor/jszip': {
|
||||
exports: 'JSZip'
|
||||
}
|
||||
},
|
||||
// exclude reusable file in modules
|
||||
// organized in alphabet order
|
||||
modules: [
|
||||
{
|
||||
name: 'main'
|
||||
},
|
||||
{
|
||||
name: 'modules/apn/apn_settings_manager',
|
||||
exclude: [
|
||||
'main',
|
||||
'modules/async_storage'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'modules/dialog_service',
|
||||
exclude: ['main']
|
||||
},
|
||||
{
|
||||
name: 'panels/about/panel',
|
||||
exclude: ['main']
|
||||
},
|
||||
{
|
||||
name: 'panels/about_more_info/panel',
|
||||
exclude: [
|
||||
'main',
|
||||
'modules/bluetooth/bluetooth_context'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'panels/app_permissions_detail/panel',
|
||||
exclude: ['main']
|
||||
},
|
||||
{
|
||||
name: 'panels/app_permissions_list/panel',
|
||||
exclude: [
|
||||
'main',
|
||||
'modules/apps_cache'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'panels/app_storage/panel',
|
||||
exclude: [
|
||||
'main',
|
||||
'modules/app_storage',
|
||||
'modules/storage_helper'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'views/phone/bluetooth/panel',
|
||||
exclude: [
|
||||
'main',
|
||||
'modules/mvvm/list_view',
|
||||
'modules/dialog_service',
|
||||
'modules/bluetooth/bluetooth_context'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'panels/browsing_privacy/panel',
|
||||
exclude: [
|
||||
'main',
|
||||
'modules/dialog_service'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'panels/call_barring/panel',
|
||||
exclude: ['main']
|
||||
},
|
||||
{
|
||||
name: 'panels/call_barring_passcode_change/panel',
|
||||
exclude: ['main']
|
||||
},
|
||||
{
|
||||
name: 'panels/date_time/panel',
|
||||
exclude: [
|
||||
'main',
|
||||
'modules/date_time'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'panels/developer/panel',
|
||||
exclude: [
|
||||
'main',
|
||||
'modules/dialog_service',
|
||||
'modules/apps_cache'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'panels/developer_hud/panel',
|
||||
exclude: ['main']
|
||||
},
|
||||
{
|
||||
name: 'panels/display/panel',
|
||||
exclude: ['main']
|
||||
},
|
||||
{
|
||||
name: 'panels/feedback_choose/panel',
|
||||
exclude: ['main']
|
||||
},
|
||||
{
|
||||
name: 'panels/feedback_send/panel',
|
||||
exclude: ['main']
|
||||
},
|
||||
{
|
||||
name: 'panels/findmydevice/panel',
|
||||
exclude: [
|
||||
'main',
|
||||
'modules/settings_utils'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'panels/firefox_accounts/panel',
|
||||
exclude: ['main']
|
||||
},
|
||||
{
|
||||
name: 'panels/firefox_sync/panel',
|
||||
exclude: [
|
||||
'main',
|
||||
'modules/settings_utils'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'panels/frame/panel',
|
||||
exclude: ['main']
|
||||
},
|
||||
{
|
||||
name: 'panels/help/panel',
|
||||
exclude: ['main']
|
||||
},
|
||||
{
|
||||
name: 'panels/homescreens/panel',
|
||||
exclude: [
|
||||
'main',
|
||||
'modules/apps_cache'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'panels/hotspot/panel',
|
||||
exclude: [
|
||||
'main',
|
||||
'modules/dialog_service'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'panels/hotspot_wifi_settings/panel',
|
||||
exclude: ['main']
|
||||
},
|
||||
{
|
||||
name: 'panels/keyboard/panel',
|
||||
exclude: [
|
||||
'main',
|
||||
'modules/mvvm/list_view',
|
||||
'modules/keyboard_context'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'panels/keyboard_add_layouts/panel',
|
||||
exclude: [
|
||||
'main',
|
||||
'modules/mvvm/list_view',
|
||||
'modules/keyboard_context',
|
||||
'shared/keyboard_helper'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'panels/languages/panel',
|
||||
exclude: [
|
||||
'main',
|
||||
'shared/keyboard_helper',
|
||||
'modules/date_time'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'panels/messaging/panel',
|
||||
exclude: [
|
||||
'main',
|
||||
'modules/messaging',
|
||||
'modules/settings_utils'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'panels/messaging_details/panel',
|
||||
exclude: [
|
||||
'main',
|
||||
'modules/messaging',
|
||||
'modules/settings_utils'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'panels/operator_settings/panel',
|
||||
exclude: [
|
||||
'main',
|
||||
'dsds_settings',
|
||||
'modules/defer',
|
||||
'modules/state_model',
|
||||
'modules/mvvm/list_view',
|
||||
'modules/dialog_service',
|
||||
'modules/customized_network_type_map',
|
||||
'modules/mobile/supported_network_info'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'panels/root/panel',
|
||||
exclude: [
|
||||
'main',
|
||||
'panels/root/low_priority_items',
|
||||
'modules/apps_cache',
|
||||
'modules/addon_manager',
|
||||
'modules/storage_helper'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'panels/root/low_priority_items',
|
||||
exclude: [
|
||||
'main',
|
||||
'modules/app_storage',
|
||||
'modules/battery',
|
||||
'modules/bluetooth/bluetooth_context',
|
||||
'modules/media_storage',
|
||||
'modules/sim_security',
|
||||
'modules/wifi_context'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'panels/screen_lock/panel',
|
||||
exclude: ['main']
|
||||
},
|
||||
{
|
||||
name: 'panels/screen_lock_passcode/panel',
|
||||
exclude: [
|
||||
'main',
|
||||
'modules/settings_utils'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'panels/search/panel',
|
||||
exclude: ['main']
|
||||
},
|
||||
{
|
||||
name: 'panels/simcard_manager/panel',
|
||||
exclude: ['main']
|
||||
},
|
||||
{
|
||||
name: 'panels/simpin/panel',
|
||||
exclude: [
|
||||
'main',
|
||||
'modules/sim_security'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'panels/sound/panel',
|
||||
exclude: [
|
||||
'main',
|
||||
'modules/mobile/supported_network_info'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'panels/usb_storage/panel',
|
||||
exclude: [
|
||||
'main',
|
||||
'modules/media_storage'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'panels/wifi/panel',
|
||||
exclude: [
|
||||
'main',
|
||||
'modules/dialog_service'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'panels/wifi_auth/panel',
|
||||
exclude: ['main']
|
||||
},
|
||||
{
|
||||
name: 'panels/wifi_enter_certificate_nickname/panel',
|
||||
exclude: ['main']
|
||||
},
|
||||
{
|
||||
name: 'panels/wifi_join_hidden/panel',
|
||||
exclude: ['main']
|
||||
},
|
||||
{
|
||||
name: 'panels/wifi_manage_certificates/panel',
|
||||
exclude: [
|
||||
'main',
|
||||
'modules/settings_utils'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'panels/wifi_manage_networks/panel',
|
||||
exclude: [
|
||||
'main',
|
||||
'modules/dialog_service'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'panels/wifi_select_certificate_file/panel',
|
||||
exclude: [
|
||||
'main',
|
||||
'modules/settings_utils'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'panels/wifi_status/panel',
|
||||
exclude: ['main']
|
||||
},
|
||||
{
|
||||
name: 'panels/wifi_wps/panel',
|
||||
exclude: ['main']
|
||||
}
|
||||
]
|
||||
});
|
||||
111
gaia/apps/settings/js/dsds_settings.js
Executable file
111
gaia/apps/settings/js/dsds_settings.js
Executable file
|
|
@ -0,0 +1,111 @@
|
|||
'use strict';
|
||||
|
||||
window.DsdsSettings = (function() {
|
||||
var _settings = window.navigator.mozSettings;
|
||||
var _mobileConnections = null;
|
||||
if (window.navigator.mozMobileConnections) {
|
||||
_mobileConnections = window.navigator.mozMobileConnections;
|
||||
}
|
||||
/** */
|
||||
var _iccCardIndexForCallSettings = 0;
|
||||
|
||||
/** */
|
||||
var _iccCardIndexForCellAndDataSettings = 0;
|
||||
|
||||
/**
|
||||
* Init function.
|
||||
*/
|
||||
function ds_init() {
|
||||
if (!_settings || !_mobileConnections) {
|
||||
return;
|
||||
}
|
||||
ds_handleDefaultIccCard();
|
||||
ds_handleCellAndDataSettingSimPanel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get number of ICC slots.
|
||||
*
|
||||
* @return {Numeric} Number of ICC slots.
|
||||
*/
|
||||
function ds_getNumberOfIccSlots() {
|
||||
if (_mobileConnections) {
|
||||
return _mobileConnections.length;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function ds_getIccCardIndexForCallSettings() {
|
||||
return _iccCardIndexForCallSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function ds_setIccCardIndexForCallSettings(
|
||||
iccCardIndexForCallSettings) {
|
||||
_iccCardIndexForCallSettings = iccCardIndexForCallSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function ds_getIccCardIndexForCellAndDataSettings() {
|
||||
return _iccCardIndexForCellAndDataSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find out first available iccID for default iccID
|
||||
*/
|
||||
function ds_handleDefaultIccCard() {
|
||||
for (var i = 0, len = _mobileConnections.length; i < len; i++) {
|
||||
if (_mobileConnections[i].iccId !== null) {
|
||||
ds_setIccCardIndexForCellAndDataSettings(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function ds_setIccCardIndexForCellAndDataSettings(
|
||||
iccCardIndexForCellAndDataSettings) {
|
||||
_iccCardIndexForCellAndDataSettings = iccCardIndexForCellAndDataSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide or show the cell and data settings panel in which we show the ICC
|
||||
* cards.
|
||||
*/
|
||||
function ds_handleCellAndDataSettingSimPanel() {
|
||||
var cellAndDataItem = null;
|
||||
|
||||
if (ds_getNumberOfIccSlots() > 1) {
|
||||
cellAndDataItem = document.getElementById('menuItem-cellularAndData');
|
||||
if ((_mobileConnections[0].radioState !== 'enabled') ||
|
||||
(!_mobileConnections[0].iccId &&
|
||||
!_mobileConnections[1].iccId)) {
|
||||
return;
|
||||
}
|
||||
cellAndDataItem = document.getElementById('data-connectivity');
|
||||
cellAndDataItem.removeAttribute('aria-disabled');
|
||||
}
|
||||
}
|
||||
|
||||
// Public API.
|
||||
return {
|
||||
init: ds_init,
|
||||
getNumberOfIccSlots: ds_getNumberOfIccSlots,
|
||||
getIccCardIndexForCallSettings:
|
||||
ds_getIccCardIndexForCallSettings,
|
||||
setIccCardIndexForCallSettings:
|
||||
ds_setIccCardIndexForCallSettings,
|
||||
getIccCardIndexForCellAndDataSettings:
|
||||
ds_getIccCardIndexForCellAndDataSettings,
|
||||
setIccCardIndexForCellAndDataSettings:
|
||||
ds_setIccCardIndexForCellAndDataSettings
|
||||
};
|
||||
})();
|
||||
67
gaia/apps/settings/js/firefox_accounts/menu.js
Executable file
67
gaia/apps/settings/js/firefox_accounts/menu.js
Executable file
|
|
@ -0,0 +1,67 @@
|
|||
/* global FxAccountsIACHelper, Normalizer */
|
||||
/* exported FxaMenu */
|
||||
|
||||
'use strict';
|
||||
|
||||
var FxaMenu = (function fxa_menu() {
|
||||
var fxaHelper,
|
||||
menuStatus;
|
||||
|
||||
function init(helper) {
|
||||
// allow mock to be passed in for unit testing
|
||||
fxaHelper = helper || FxAccountsIACHelper;
|
||||
menuStatus = document.getElementById('fxa-desc');
|
||||
if(window.VulpesCompat){menuStatus.removeAttribute('data-l10n-id');menuStatus.textContent=(document.documentElement.lang||navigator.language).startsWith('fr')?'Service non disponible sur ce bureau':'Service unavailable on this desktop';return;}
|
||||
|
||||
// listen for status updates
|
||||
onVisibilityChange();
|
||||
// start by asking for current status
|
||||
refreshStatus();
|
||||
document.addEventListener('visibilitychange', onVisibilityChange);
|
||||
}
|
||||
|
||||
function refreshStatus() {
|
||||
fxaHelper.getAccount(onStatusChange, onStatusError);
|
||||
}
|
||||
|
||||
// if e == null, user is logged out.
|
||||
// if e.verified, user is logged in & verified.
|
||||
// if !e.verified, user is logged in & unverified.
|
||||
function onStatusChange(e) {
|
||||
var email = e ? Normalizer.escapeHTML(e.email) : '';
|
||||
|
||||
if (!e) {
|
||||
menuStatus.setAttribute('data-l10n-id', 'fxa-invitation');
|
||||
menuStatus.removeAttribute('data-l10n-args');
|
||||
} else if (e.verified) {
|
||||
document.l10n.setAttributes(menuStatus, 'fxa-logged-in-text', {
|
||||
email: email
|
||||
});
|
||||
} else { // unverified
|
||||
document.l10n.setAttributes(menuStatus, 'fxa-confirm-email', {
|
||||
email: email
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function onStatusError(err) {
|
||||
console.error('FxaMenu: Error getting Firefox Account: ' + err.error);
|
||||
}
|
||||
|
||||
function onVisibilityChange() {
|
||||
if (document.hidden) {
|
||||
fxaHelper.removeEventListener('onlogin', refreshStatus);
|
||||
fxaHelper.removeEventListener('onverified', refreshStatus);
|
||||
fxaHelper.removeEventListener('onlogout', refreshStatus);
|
||||
} else {
|
||||
fxaHelper.addEventListener('onlogin', refreshStatus);
|
||||
fxaHelper.addEventListener('onverified', refreshStatus);
|
||||
fxaHelper.addEventListener('onlogout', refreshStatus);
|
||||
refreshStatus();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
init: init
|
||||
};
|
||||
})();
|
||||
20
gaia/apps/settings/js/firefox_accounts/menu_loader.js
Executable file
20
gaia/apps/settings/js/firefox_accounts/menu_loader.js
Executable file
|
|
@ -0,0 +1,20 @@
|
|||
/* global FxAccountsIACHelper, FxaMenu */
|
||||
|
||||
'use strict';
|
||||
|
||||
require(['shared/lazy_loader'], function(LazyLoader) {
|
||||
var idleObserver = {
|
||||
time: 4,
|
||||
onidle: function() {
|
||||
navigator.removeIdleObserver(idleObserver);
|
||||
LazyLoader.load([
|
||||
'/shared/js/fxa_iac_client.js',
|
||||
'/shared/js/text_normalizer.js',
|
||||
'js/firefox_accounts/menu.js'
|
||||
], function fxa_menu_loaded() {
|
||||
FxaMenu.init(FxAccountsIACHelper);
|
||||
});
|
||||
}
|
||||
};
|
||||
navigator.addIdleObserver(idleObserver);
|
||||
});
|
||||
102
gaia/apps/settings/js/main.js
Executable file
102
gaia/apps/settings/js/main.js
Executable file
|
|
@ -0,0 +1,102 @@
|
|||
require(['config/require'], function() {
|
||||
'use strict';
|
||||
|
||||
define('boot', function(require) {
|
||||
// The following are the scripts used by many other scripts. We load them
|
||||
// at once here.
|
||||
require('shared/settings_listener');
|
||||
require('modules/mvvm/observable');
|
||||
require('modules/mvvm/observable_array');
|
||||
require('modules/base/event_emitter');
|
||||
|
||||
var SettingsService = require('modules/settings_service');
|
||||
var ScreenLayout = require('shared/screen_layout');
|
||||
var Settings = require('settings');
|
||||
|
||||
function isInitialPanel(panel) {
|
||||
if (Settings.isTabletAndLandscape()) {
|
||||
return panel === Settings.initialPanelForTablet;
|
||||
} else {
|
||||
return panel === ('#' + window.LaunchContext.initialPanelId);
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('panelready', function onPanelReady(e) {
|
||||
if (!isInitialPanel(e.detail.current)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var initialPanelHandler = window.LaunchContext.initialPanelHandler;
|
||||
if (initialPanelHandler) {
|
||||
initialPanelHandler.release();
|
||||
var pendingTargetPanel = initialPanelHandler.pendingTargetPanel;
|
||||
// XXX: In call item,
|
||||
// we need special logic for navigating to specific panels.
|
||||
switch (pendingTargetPanel) {
|
||||
case 'call':
|
||||
var mozMobileConnections = navigator.mozMobileConnections;
|
||||
// If DSDS phone, we have to let users choose simcard
|
||||
if (mozMobileConnections && mozMobileConnections.length > 1) {
|
||||
// If the device support dsds,
|
||||
// then navigate to 'call-iccs' panel
|
||||
pendingTargetPanel = 'call-iccs';
|
||||
}
|
||||
SettingsService.navigate(pendingTargetPanel);
|
||||
break;
|
||||
default:
|
||||
if (pendingTargetPanel) {
|
||||
SettingsService.navigate(pendingTargetPanel);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
window.removeEventListener('panelready', onPanelReady);
|
||||
|
||||
// XXX: Even the panel has been displayed but the content may still not
|
||||
// stable yet. This is a estimated timing of visually complete. We
|
||||
// should implement other mechanism waiting for all content ready.
|
||||
window.performance.mark('visuallyLoaded');
|
||||
|
||||
// Activate the animation.
|
||||
document.body.dataset.ready = true;
|
||||
}, false);
|
||||
|
||||
window.addEventListener('telephony-settings-loaded',
|
||||
function onTelephonySettingsLoaded() {
|
||||
window.removeEventListener('telephony-settings-loaded',
|
||||
onTelephonySettingsLoaded);
|
||||
|
||||
// The loading of telephony settings is dependent on being idle,
|
||||
// once complete we are safe to declare the settings app as loaded
|
||||
window.performance.mark('fullyLoaded');
|
||||
});
|
||||
|
||||
/**
|
||||
* In two column layout, the root panel should not be deactivated. We pass
|
||||
* the id of the root panel to SettingsService so that it won't deactivate
|
||||
* the root panel when in two column.
|
||||
* XXX: Currently we don't separate the navigation logic of one column and
|
||||
* two column layout, so that the root panel will not be deactivated
|
||||
* in one column layout.
|
||||
*/
|
||||
SettingsService.init({
|
||||
rootPanelId: 'root',
|
||||
context: window.LaunchContext
|
||||
});
|
||||
|
||||
var options = {
|
||||
SettingsService: SettingsService,
|
||||
ScreenLayout: ScreenLayout
|
||||
};
|
||||
Settings.init(options);
|
||||
|
||||
// Tell audio channel manager that we want to adjust the notification
|
||||
// channel if the user press the volumeup/volumedown buttons in Settings.
|
||||
if (navigator.mozAudioChannelManager) {
|
||||
navigator.mozAudioChannelManager.volumeControlChannel = 'notification';
|
||||
}
|
||||
});
|
||||
|
||||
require(['boot']);
|
||||
});
|
||||
688
gaia/apps/settings/js/modules/addon_manager.js
Executable file
688
gaia/apps/settings/js/modules/addon_manager.js
Executable file
|
|
@ -0,0 +1,688 @@
|
|||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var mozApps = require('modules/navigator/mozApps');
|
||||
var App = require('modules/app');
|
||||
var AppsCache = require('modules/apps_cache');
|
||||
var ObservableArray = require('modules/mvvm/observable_array');
|
||||
var MatchPattern = require('shared/addons/match_pattern');
|
||||
|
||||
/**
|
||||
* AddonManager is a singleton with methods for querying the currently
|
||||
* installed addons, enabling, disabling, and uninstalling addons, and
|
||||
* for obtaining a list of apps targeted by a specified add-on. The changes to
|
||||
* installed addons are propagated via an observable array.
|
||||
*
|
||||
* AddonManager uses the AppsCache module for efficient access to the full
|
||||
* list of installed apps.
|
||||
*
|
||||
* @class AddonManager
|
||||
* @requires module:modules/navigator/mozApps
|
||||
* @requires module:modules/apps_cache
|
||||
* @requires module:modules/mvvm/observable_array
|
||||
* @returns {AddonManager}
|
||||
*/
|
||||
function AddonManager() {
|
||||
this._addons = ObservableArray([]);
|
||||
this._bindEvents();
|
||||
|
||||
AppsCache.apps().then((apps) => {
|
||||
apps.some((app) => {
|
||||
if (this._isAddon(app)) {
|
||||
this._addons.push(App(app));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
AddonManager.prototype = {
|
||||
/**
|
||||
* Bind to install and uninstall events.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf AddonManager.prototype
|
||||
*/
|
||||
_bindEvents: function() {
|
||||
var _boundUpdateAddons = this._updateAddons.bind(this);
|
||||
// Bug 1215546 & 1218298
|
||||
// The 'oninstall' event is fired when user started installing the
|
||||
// app, but manifest is not available yet. In order to get the
|
||||
// manifest, we need to listen to 'downloadsuccess' on the app
|
||||
// (not mozApps.mgmt), and that's when manifest will be ready.
|
||||
AppsCache.addEventListener('oninstall', (evt) => {
|
||||
var app = evt && evt.application;
|
||||
// Bug 1220195
|
||||
// If installed via WebIDE, _isAddon will return true, and we should
|
||||
// run _boundUpdateAddons immediately because 'downloadsuccess' would
|
||||
// never fire. If installed from Marketplace we need to wait for the
|
||||
// 'downloadsuccess' event and run _boundUpdateAddons later.
|
||||
if (this._isAddon(app)) {
|
||||
_boundUpdateAddons(evt);
|
||||
} else {
|
||||
app.addEventListener('downloadsuccess', _boundUpdateAddons);
|
||||
}
|
||||
});
|
||||
AppsCache.addEventListener('onuninstall', _boundUpdateAddons);
|
||||
mozApps.mgmt.addEventListener('enabledstatechange', _boundUpdateAddons);
|
||||
},
|
||||
|
||||
/**
|
||||
* We have to update the addon array based on incoming evt.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf AddonManager.prototype
|
||||
* @param {Object} evt
|
||||
*/
|
||||
_updateAddons: function(evt) {
|
||||
var app = evt && evt.application;
|
||||
var type = evt.type;
|
||||
var index;
|
||||
|
||||
if (this._isAddon(app)) {
|
||||
if ((type === 'downloadsuccess' || type === 'install') &&
|
||||
!this._alreadyExists(app)) {
|
||||
this._addons.push(App(app));
|
||||
} else if (type === 'uninstall') {
|
||||
index = this._findAddonIndex(app);
|
||||
if (index !== -1) {
|
||||
this._addons.splice(index, 1);
|
||||
}
|
||||
} else if (type === 'enabledstatechange') {
|
||||
index = this._findAddonIndex(app);
|
||||
if (index !== -1) {
|
||||
this._addons.get(index)._enabled = app.enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if an addon already exists.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf AddonManager.prototype
|
||||
* @param {DOMApplication} addon
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
_alreadyExists: function(addon) {
|
||||
return this._findAddonIndex(addon) !== -1;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the index of an addon.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf AddonManager.prototype
|
||||
* @param {DOMApplication} addon
|
||||
* @returns {Number}
|
||||
*/
|
||||
_findAddonIndex: function(addon) {
|
||||
return this._addons.array.findIndex((elem) => {
|
||||
return addon.manifestURL === elem.instance.manifestURL;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Addons can only apply to apps with the same or lower privilege. Returns
|
||||
* a bloolean indicating whether the addon can be applied on the app.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf AddonManager.prototype
|
||||
* @param {DOMApplication} addon
|
||||
* @param {DOMApplication} app
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
_privilegeCheck: function(addon, app) {
|
||||
var addonManifest = this._getManifest(addon);
|
||||
var appManifest = this._getManifest(app);
|
||||
return addonManifest.type === appManifest.type ||
|
||||
addonManifest.type === 'certified' ||
|
||||
addonManifest.type === 'privileged';
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the manifest.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf AddonManager.prototype
|
||||
* @param {DOMApplication} app
|
||||
* @returns {Object}
|
||||
*/
|
||||
_getManifest: function(app) {
|
||||
return app.manifest || app.updateManifest;
|
||||
},
|
||||
|
||||
/**
|
||||
* Check whether reboot is required when disabling the addon.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf AddonManager.prototype
|
||||
* @param {DOMApplication} app
|
||||
* @param {Array} contentScripts
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
_needsReboot: function({app, contentScripts}) {
|
||||
var manifest = this._getManifest(app);
|
||||
var role = manifest.role;
|
||||
// If the addon affects a system app
|
||||
if (role === 'system' || role === 'homescreen') {
|
||||
// and the app is customized with a script, then we need reboot
|
||||
return contentScripts.some(c => c.js.length > 0);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Check whether restart is required when disabling the addon.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf AddonManager.prototype
|
||||
* @param {DOMApplication} app
|
||||
* @param {Array} contentScripts
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
_needsRestart: function({app, contentScripts}) {
|
||||
return contentScripts.some(c => c.js.length > 0);
|
||||
},
|
||||
|
||||
/**
|
||||
* Check whether this app is an addon.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf AddonManager.prototype
|
||||
* @param {DOMApplication} app
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
_isAddon: function(app) {
|
||||
var manifest = this._getManifest(app);
|
||||
return manifest && manifest.role === 'addon';
|
||||
},
|
||||
|
||||
/**
|
||||
* This internal utility function returns an array of objects describing
|
||||
* the apps that are affected by the given addon.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf AddonManager.prototype
|
||||
* @param {DOMApplication} addon
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
_getAffectedApps: function(addon) {
|
||||
if (!this._isAddon(addon)) {
|
||||
return Promise.reject('not an addon');
|
||||
}
|
||||
|
||||
var manifest = this._getManifest(addon);
|
||||
var customizations = manifest && manifest.customizations;
|
||||
// content_scripts field is internally copied from manifest.json to
|
||||
// manifest.webapp
|
||||
var contentScripts = manifest && manifest.content_scripts;
|
||||
|
||||
// If the addon specifies customizations, then it is obsolete and does not
|
||||
// work
|
||||
if (customizations) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
// If there are no content scripts or content scripts list is empty, the
|
||||
// addon does not affect any apps.
|
||||
if (!contentScripts || contentScripts.length === 0) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
return AppsCache.apps()
|
||||
.then(apps => apps.filter(app => {
|
||||
var manifest = this._getManifest(app);
|
||||
// Ignore apps that are themselves add-ons or if the addon doesn't
|
||||
// have high enough privileges to affect this app then we can just
|
||||
// return now.
|
||||
return manifest.role !== 'addon' &&
|
||||
this._privilegeCheck(addon, app);
|
||||
}))
|
||||
// Match apps based on add-on's content sctipts.
|
||||
.then(apps => {
|
||||
var matches = [];
|
||||
apps.forEach(app => {
|
||||
var matched;
|
||||
if ((matched = this._matchContentScripts(contentScripts, app))) {
|
||||
matches.push({ app: app, contentScripts: matched });
|
||||
}
|
||||
});
|
||||
return matches;
|
||||
})
|
||||
.catch(() => []);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a list of content scripts (from an addon manifest), if available,
|
||||
* that affect the app by a given addon. We implement getAddonTargets()
|
||||
* using this information , derive the 'reboot' and 'restart' return
|
||||
* values of disableAddon() from it and also use this function to apply
|
||||
* addon filters.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf AddonManager.prototype
|
||||
* @param {DOMApplication} addon
|
||||
* @param {DOMApplication} app
|
||||
* @returns {Array?} appliedContentScripts if available
|
||||
*/
|
||||
_matchContentScripts: function(contentScripts, app) {
|
||||
var manifest = this._getManifest(app);
|
||||
// Get the URL of the app origin plus its launch path to
|
||||
// compare against each of the filters.
|
||||
// XXX: Note that we and do not check the paths used by
|
||||
// activity handlers or any other paths in the manifest.
|
||||
var launchPath = manifest.launch_path || '';
|
||||
var launchURL = new URL(launchPath, app.origin);
|
||||
|
||||
// For each content script, compile the pattern string MatchPattern
|
||||
var patterns = contentScripts.map(contentScript => {
|
||||
return {
|
||||
matches: new MatchPattern(contentScript.matches),
|
||||
excludeMatches: new MatchPattern(contentScript.exclude_matches)
|
||||
};
|
||||
});
|
||||
|
||||
var appliedContentScripts = [];
|
||||
|
||||
// Now loop through the patterns to see what content scripts are applied
|
||||
// to this app
|
||||
for(var i = 0; i < patterns.length; i++) {
|
||||
var pattern = patterns[i];
|
||||
if (!pattern.excludeMatches.matches(launchURL) &&
|
||||
pattern.matches.matches(launchURL)) {
|
||||
appliedContentScripts.push(contentScripts[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If any content scripts were applied to this app, return the list of
|
||||
// appliedContentScripts
|
||||
if (appliedContentScripts.length > 0) {
|
||||
return appliedContentScripts;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Convert exported add-on into an Array Buffer.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf AddonManager.prototype
|
||||
* @param {Blob} blob
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_blobToArrayBuffer: function(blob) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var fileReader = new FileReader();
|
||||
fileReader.onload = function() {
|
||||
resolve(fileReader.result);
|
||||
};
|
||||
fileReader.onerror = function(reason) {
|
||||
reject(reason);
|
||||
};
|
||||
fileReader.readAsArrayBuffer(blob);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Upack an add-on script.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf AddonManager.prototype
|
||||
* @param {ArrayBuffer} arrayBuffer
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_unpackScript: function(arrayBuffer) {
|
||||
return this._JSZip.then(JSZip => {
|
||||
var zip = new JSZip();
|
||||
zip.load(arrayBuffer);
|
||||
|
||||
var applicationZipFile = zip.file('application.zip');
|
||||
if (!applicationZipFile) { return; }
|
||||
|
||||
var applicationZip = new JSZip();
|
||||
applicationZip.load(applicationZipFile.asArrayBuffer());
|
||||
|
||||
var scriptFile = applicationZip.file('main.js');
|
||||
if (!scriptFile) { return; }
|
||||
|
||||
return(scriptFile.asText());
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate a new add-on.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf AddonManager.prototype
|
||||
* @param {JSON} manifest add-on manifest
|
||||
* @param {String} script add-on script
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_generate: function(manifest, script) {
|
||||
return this._JSZip.then(JSZip => {
|
||||
var id = 'addon' + Math.round(Math.random() * 100000000);
|
||||
var applicationZip = new JSZip();
|
||||
|
||||
// Ensure that we are creating an addon with a new id.
|
||||
manifest.role = 'addon';
|
||||
manifest.type = 'certified';
|
||||
manifest.origin = 'app://' + id + '.gaiamobile.org';
|
||||
manifest.activities = manifest.activities || {};
|
||||
|
||||
applicationZip.file('manifest.webapp', JSON.stringify(manifest));
|
||||
applicationZip.file('main.js', script);
|
||||
|
||||
var packageZip = new JSZip();
|
||||
packageZip.file('metadata.json', JSON.stringify({
|
||||
installOrigin: 'http://gaiamobile.org',
|
||||
manifestURL: 'app://' + id + '.gaiamobile.org/update.webapp',
|
||||
version: 1
|
||||
}));
|
||||
packageZip.file('update.webapp', JSON.stringify({
|
||||
name: manifest.name,
|
||||
package_path: '/application.zip'
|
||||
}));
|
||||
packageZip.file('application.zip',
|
||||
applicationZip.generate({ type: 'arraybuffer' }));
|
||||
|
||||
return new Blob([packageZip.generate({ type: 'arraybuffer' })],
|
||||
{ type: 'application/zip' });
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Lazily require jszip. It is currently used only for renaming Add-ons.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf AddonManager.prototype
|
||||
* @returns {Promise}
|
||||
*/
|
||||
get _JSZip() {
|
||||
return new Promise(resolve => { require(['vendor/jszip'], resolve); });
|
||||
},
|
||||
|
||||
/**
|
||||
* Install add-on, but importing it using a memory-backed blob.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf AddonManager.prototype
|
||||
* @param {Blob} blob add-on blob
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_install: function(blob) {
|
||||
return navigator.mozApps.mgmt.import(blob).then(addon => {
|
||||
// Enable the addon by default.
|
||||
var app = { instance: addon };
|
||||
this.enableAddon(app);
|
||||
return app;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Check whether an addon is enabled.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf AddonManager.prototype
|
||||
* @param {App} addon
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
isEnabled: function(addon) {
|
||||
return this._isAddon(addon.instance) && addon.instance.enabled === true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Check whether an addon can be deleted.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf AddonManager.prototype
|
||||
* @param {App} addon
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
canDelete: function(addon) {
|
||||
return this._isAddon(addon.instance) && addon.instance.removable === true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Enable an addon
|
||||
*
|
||||
* @access public
|
||||
* @memberOf AddonManager.prototype
|
||||
* @param {App} addon
|
||||
*/
|
||||
enableAddon: function(addon) {
|
||||
if (this._isAddon(addon.instance) && !addon.instance.enabled) {
|
||||
mozApps.mgmt.setEnabled(addon.instance, true);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Disable an addon. Returns a string indicating whether reboot or restart
|
||||
* required for disabling the addon completely.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf AddonManager.prototype
|
||||
* @param {App} addon
|
||||
*/
|
||||
disableAddon: function(addon) {
|
||||
if (this._isAddon(addon.instance) && addon.instance.enabled) {
|
||||
mozApps.mgmt.setEnabled(addon.instance, false);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete an addon.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf AddonManager.prototype
|
||||
* @param {App} addon
|
||||
* @returns {Promise}
|
||||
*/
|
||||
deleteAddon: function(addon) {
|
||||
if (!this._isAddon(addon.instance)) {
|
||||
return Promise.reject('not an addon');
|
||||
}
|
||||
|
||||
if (!addon.instance.removable) {
|
||||
return Promise.reject('addon is not deletable');
|
||||
}
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
var request = mozApps.mgmt.uninstall(addon.instance);
|
||||
request.onsuccess = function() {
|
||||
resolve();
|
||||
};
|
||||
request.onerror = function() {
|
||||
reject();
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Rename an addon.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf AddonManager.prototype
|
||||
* @param {App} addon
|
||||
* @param {String} name addon name
|
||||
* @returns {Promise}
|
||||
*/
|
||||
renameAddon: function(addon, name) {
|
||||
if (!name) {
|
||||
return Promise.reject('no name given');
|
||||
}
|
||||
|
||||
if (!this._isAddon(addon.instance)) {
|
||||
return Promise.reject('not an addon');
|
||||
}
|
||||
|
||||
var manifest = this._getManifest(addon.instance);
|
||||
if (name === manifest.name) {
|
||||
return Promise.reject('name is unchanged');
|
||||
}
|
||||
|
||||
var newManifest, addonBlob;
|
||||
// To rename an addon we are creating a new DOMApplication that will have
|
||||
// an updated name. Other manifest attributes are preserved.
|
||||
// Export an add-on as a blob.
|
||||
return addon.instance.export().then(blob => {
|
||||
addonBlob = blob;
|
||||
// Copy over manifest object.
|
||||
newManifest = Object.assign({}, manifest);
|
||||
newManifest.name = name;
|
||||
// Delete original add-on.
|
||||
return this.deleteAddon(addon);
|
||||
}).then(() => {
|
||||
// Convert exported add-on into an Array Buffer.
|
||||
return this._blobToArrayBuffer(addonBlob);
|
||||
}).then(this._unpackScript.bind(this)).then(script => {
|
||||
// Generate a new add-on using the original manifest and a new name.
|
||||
return this._generate(newManifest, script);
|
||||
}).then(this._install.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Share an addon.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf AddonManager.prototype
|
||||
* @param {App} addon
|
||||
* @returns {Promise}
|
||||
*/
|
||||
shareAddon: function(addon) {
|
||||
if (!this._isAddon(addon.instance)) {
|
||||
return Promise.reject('not an addon');
|
||||
}
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
var activity = new window.MozActivity({
|
||||
name: 'share',
|
||||
data: {
|
||||
type: 'app',
|
||||
app: addon.instance.manifestURL
|
||||
}
|
||||
});
|
||||
|
||||
activity.onsuccess = function() {
|
||||
resolve();
|
||||
};
|
||||
activity.onerror = function() {
|
||||
reject((activity.error && activity.error.name) || 'activity error');
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Return an array of the apps that are targeted by any of this
|
||||
* addon's content scripts. Note, however, that addons can also target
|
||||
* arbitrary web pages and hosted apps. This test only applies to
|
||||
* installed packaged apps and so may be of limited utility.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf AddonManager.prototype
|
||||
* @param {App} addon
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getAddonTargets: function(addon) {
|
||||
return this._getAffectedApps(addon.instance).then((affectedApps) => {
|
||||
return affectedApps.map((x) => x.app);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Return a string indicating whether reboot or restart is required for
|
||||
* disabling the addon.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf AddonManager.prototype
|
||||
* @param {App} addon
|
||||
* @returns {Promise.<String>}
|
||||
*/
|
||||
getAddonDisableType: function(addon) {
|
||||
return this._getAffectedApps(addon.instance).then((affectedApps) => {
|
||||
var needsReboot = affectedApps.some(this._needsReboot.bind(this));
|
||||
if (needsReboot) {
|
||||
return 'reboot';
|
||||
}
|
||||
|
||||
var needsRestart = affectedApps.some(this._needsRestart.bind(this));
|
||||
if (needsRestart) {
|
||||
return 'restart';
|
||||
}
|
||||
|
||||
return '';
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the App object that matches the given manifest url.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf AddonManager.prototype
|
||||
* @param {String} manifestURL
|
||||
* @returns {Promise.<App>}
|
||||
*/
|
||||
findAddonByManifestURL: function(manifestURL) {
|
||||
return AppsCache.apps().then(() => {
|
||||
return this._addons.array.find((addon) => {
|
||||
return addon.instance.manifestURL === manifestURL;
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a promise for a boolean flag indicating whether an addon affects
|
||||
* an application or not..
|
||||
*
|
||||
* @access public
|
||||
* @memberOf AddonManager.prototype
|
||||
* @param {DOMApplication} addon addon to check
|
||||
* @param {String} manifestURL app manifest URL to check
|
||||
* @returns {Promise.<Boolean>}
|
||||
*/
|
||||
addonAffectsApp: function(addon, manifestURL) {
|
||||
if (!manifestURL) {
|
||||
return Promise.reject('No manifestURL given for an app');
|
||||
}
|
||||
if (!this._isAddon(addon.instance)) {
|
||||
return Promise.reject('not an addon');
|
||||
}
|
||||
|
||||
var manifest = this._getManifest(addon.instance);
|
||||
var customizations = manifest && manifest.customizations;
|
||||
// content_scripts field is internally copied from manifest.json to
|
||||
// manifest.webapp
|
||||
var contentScripts = manifest && manifest.content_scripts;
|
||||
|
||||
// If the addon specifies customizations, then it is obsolete and does not
|
||||
// work
|
||||
if (customizations) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
// If there are no content scripts or content scripts list is empty, the
|
||||
// addon does not affect any apps.
|
||||
if (!contentScripts || contentScripts.length === 0) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
return AppsCache.apps()
|
||||
.then(apps => apps.find(app => app.manifestURL === manifestURL))
|
||||
.then(app => app ?
|
||||
this._matchContentScripts(contentScripts, app) : undefined)
|
||||
.then(appliedContentScripts => !!appliedContentScripts)
|
||||
.catch(() => false);
|
||||
},
|
||||
|
||||
get length() {
|
||||
return this._addons.length;
|
||||
},
|
||||
|
||||
get addons() {
|
||||
return this._addons;
|
||||
}
|
||||
};
|
||||
|
||||
return new AddonManager();
|
||||
});
|
||||
52
gaia/apps/settings/js/modules/apn/apn_const.js
Executable file
52
gaia/apps/settings/js/modules/apn/apn_const.js
Executable file
|
|
@ -0,0 +1,52 @@
|
|||
/**
|
||||
* The apn constants
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var APN_TYPES = ['default', 'mms', 'supl', 'dun', 'ims', 'fota'];
|
||||
|
||||
var CP_APN_KEY = 'ril.data.cp.apns';
|
||||
var DEFAULT_APN_KEY = 'ril.data.default.apns';
|
||||
|
||||
// Used for detecting if a new icc card has been inserted.
|
||||
var CACHED_ICCIDS_KEY = 'apn.cached.iccids';
|
||||
|
||||
var APN_LIST_KEY = 'apn.list';
|
||||
var APN_SELECTIONS_KEY = 'apn.selections';
|
||||
var APN_SETTINGS_KEY = 'ril.data.apnSettings';
|
||||
var DEFAULT_APN_SETTINGS_KEY = 'ril.data.default.apnSettings';
|
||||
|
||||
var MCC_SETTINGS_KEY = 'operatorvariant.mcc';
|
||||
var MNC_SETTINGS_KEY = 'operatorvariant.mnc';
|
||||
|
||||
var EU_ROAMING_ENABLED_KEY = 'eu-roaming.enabled';
|
||||
var EU_ROAMING_FILE_PATH = '/resources/eu-roaming.json';
|
||||
|
||||
var APN_PROPS = [
|
||||
'carrier', 'apn', 'user', 'passwd', 'httpproxyhost', 'httpproxyport',
|
||||
'mmsc', 'mmsproxy', 'mmsport', 'authtype', 'types', 'protocol',
|
||||
'roaming_protocol', 'mtu',
|
||||
/**
|
||||
* _id is an internal property for identifying apn items received via
|
||||
* client provisioning.
|
||||
*/
|
||||
'_id'
|
||||
];
|
||||
|
||||
return {
|
||||
get APN_TYPES() { return APN_TYPES; },
|
||||
get CP_APN_KEY() { return CP_APN_KEY; },
|
||||
get DEFAULT_APN_KEY() { return DEFAULT_APN_KEY; },
|
||||
get CACHED_ICCIDS_KEY() { return CACHED_ICCIDS_KEY; },
|
||||
get APN_LIST_KEY() { return APN_LIST_KEY; },
|
||||
get APN_SELECTIONS_KEY() { return APN_SELECTIONS_KEY; },
|
||||
get APN_SETTINGS_KEY() { return APN_SETTINGS_KEY; },
|
||||
get DEFAULT_APN_SETTINGS_KEY() { return DEFAULT_APN_SETTINGS_KEY; },
|
||||
get MCC_SETTINGS_KEY() { return MCC_SETTINGS_KEY; },
|
||||
get MNC_SETTINGS_KEY() { return MNC_SETTINGS_KEY; },
|
||||
get EU_ROAMING_ENABLED_KEY() { return EU_ROAMING_ENABLED_KEY; },
|
||||
get EU_ROAMING_FILE_PATH() { return EU_ROAMING_FILE_PATH; },
|
||||
get APN_PROPS() { return APN_PROPS; }
|
||||
};
|
||||
});
|
||||
46
gaia/apps/settings/js/modules/apn/apn_item.js
Executable file
46
gaia/apps/settings/js/modules/apn/apn_item.js
Executable file
|
|
@ -0,0 +1,46 @@
|
|||
/**
|
||||
* ApnItem is a wrapper of apn objects.
|
||||
*
|
||||
* @module modules/apn/apn_item
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var APN_CATEGORY = {
|
||||
PRESET: 'preset',
|
||||
CUSTOM: 'custom',
|
||||
EU: 'eu'
|
||||
};
|
||||
|
||||
/**
|
||||
* @class ApnItem
|
||||
* @params {String} id
|
||||
* @params {String} category
|
||||
* @params {Object} apn
|
||||
* @returns {ApnItem}
|
||||
*/
|
||||
function ApnItem(id, category, apn) {
|
||||
this._id = id;
|
||||
this._category = category;
|
||||
this._apn = apn;
|
||||
}
|
||||
|
||||
ApnItem.prototype = {
|
||||
get id() {
|
||||
return this._id;
|
||||
},
|
||||
get category() {
|
||||
return this._category;
|
||||
},
|
||||
get apn() {
|
||||
return this._apn;
|
||||
}
|
||||
};
|
||||
|
||||
var constructor = function ctor_apn_item(id, category, apn) {
|
||||
return new ApnItem(id, category, apn);
|
||||
};
|
||||
constructor.APN_CATEGORY = APN_CATEGORY;
|
||||
|
||||
return constructor;
|
||||
});
|
||||
266
gaia/apps/settings/js/modules/apn/apn_list.js
Executable file
266
gaia/apps/settings/js/modules/apn/apn_list.js
Executable file
|
|
@ -0,0 +1,266 @@
|
|||
/**
|
||||
* ApnList stores ApnItem objects representing apns come from the apn.json
|
||||
* database, client provisioning messages, and user's creation. Each ApnItem has
|
||||
* an id assigned upon the creation. The id is used to recored users' apn
|
||||
* selection.
|
||||
*
|
||||
* @module modules/apn/apn_list
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var AsyncStorage = require('modules/async_storage');
|
||||
var ApnUtils = require('modules/apn/apn_utils');
|
||||
var ApnItem = require('modules/apn/apn_item');
|
||||
|
||||
/**
|
||||
* @class ApnList
|
||||
* @requires module:modules/async_storage
|
||||
* @requires module:modules/apn/apn_utils
|
||||
* @requires module:modules/apn/apn_item
|
||||
* @returns {ApnList}
|
||||
*/
|
||||
function ApnList(key) {
|
||||
this._key = key;
|
||||
this._apnItems = null;
|
||||
this._pendingTaskCount = 0;
|
||||
this._promiseChain = Promise.resolve();
|
||||
}
|
||||
|
||||
ApnList.prototype = {
|
||||
/**
|
||||
* As the operations should not be performed concurrently. We use this
|
||||
* function to enusre the operations are performed one by one.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf ApnList.prototype
|
||||
* @param {Function} task
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_schedule: function al__schedule(task) {
|
||||
var that = this;
|
||||
this._promiseChain = this._promiseChain.then(function() {
|
||||
that._pendingTaskCount++;
|
||||
return task().then(function(result) {
|
||||
that._pendingTaskCount--;
|
||||
if (that._pendingTaskCount === 0) {
|
||||
return that._commit().then(function() {
|
||||
return result;
|
||||
});
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
});
|
||||
});
|
||||
return this._promiseChain;
|
||||
},
|
||||
|
||||
/**
|
||||
* _apnItems are wrapped apn items. The function unwraps the items so
|
||||
* that they can be stored to async storage.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf ApnList.prototype
|
||||
* @returns {Array}
|
||||
*/
|
||||
_export: function al__export() {
|
||||
return this._apnItems.map(function(apnItem) {
|
||||
// Convert the apn items to static objects for storing.
|
||||
var apnClone = ApnUtils.clone(apnItem.apn);
|
||||
apnClone.id = apnItem.id;
|
||||
apnClone.category = apnItem.category;
|
||||
return apnClone;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Stores the current copy of apn items to async storage.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf ApnList.prototype
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_commit: function al__commit() {
|
||||
if (!this._apnItems) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
var apns = this._export();
|
||||
return AsyncStorage.setItem(this._key, apns);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The internal add function.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf ApnList.prototype
|
||||
* @params {Object} apn
|
||||
* @params {ApnItem.APN_CATEGORY} category
|
||||
* @returns {Promise.<String>} The apn id
|
||||
*/
|
||||
_add: function al__add(apn, category) {
|
||||
return this.items().then(function(apnItems) {
|
||||
if (!apnItems) {
|
||||
// set default apn items
|
||||
this._apnItems = apnItems = [];
|
||||
}
|
||||
|
||||
// Set carrier to _custom_ if the category is custom for backward
|
||||
// compatibility
|
||||
category = category || ApnItem.APN_CATEGORY.CUSTOM;
|
||||
if (category === ApnItem.APN_CATEGORY.CUSTOM) {
|
||||
apn.carrier = '_custom_';
|
||||
}
|
||||
var apnItem = ApnItem(ApnUtils.generateId(), category, apn);
|
||||
|
||||
apnItems.unshift(apnItem);
|
||||
return apnItem.id;
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* The internal remove function.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf ApnList.prototype
|
||||
* @params {String} id
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_remove: function al__remove(id) {
|
||||
return this.items().then(function(apnItems) {
|
||||
if (!apnItems) {
|
||||
return Promise.reject('no apn items');
|
||||
}
|
||||
|
||||
var index = apnItems.findIndex((apnItem) => apnItem.id === id);
|
||||
if (index >= 0) {
|
||||
apnItems = apnItems.splice(index, 1);
|
||||
} else {
|
||||
return Promise.reject('apn not found');
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* The internal update function.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf ApnList.prototype
|
||||
* @params {String} id
|
||||
* @params {Object} apn
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_update: function al__update(id, apn) {
|
||||
return this.items().then(function(apnItems) {
|
||||
if (!apnItems) {
|
||||
return Promise.reject('no apn items');
|
||||
}
|
||||
|
||||
var index = apnItems.findIndex((apnItem) => apnItem.id === id);
|
||||
if (index >= 0) {
|
||||
var currentApn = apnItems[index].apn;
|
||||
for (var p in apn) {
|
||||
// id and category are not allowed to be changed
|
||||
if (p === 'id' || p === 'category') {
|
||||
continue;
|
||||
}
|
||||
currentApn[p] = apn[p];
|
||||
}
|
||||
} else {
|
||||
return Promise.reject('apn not found');
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all apn items of the list.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf ApnList.prototype
|
||||
* @returns {Promise.<Array.<ApnItem>>}
|
||||
*/
|
||||
items: function al_items() {
|
||||
// Because add/remove/update depend on this function so we should not
|
||||
// schedule it or we will get a dead lock. We return the current copy of
|
||||
// apn items.
|
||||
if (this._apnItems) {
|
||||
return Promise.resolve(this._apnItems);
|
||||
} else {
|
||||
return AsyncStorage.getItem(this._key).then(function(apns) {
|
||||
if (apns) {
|
||||
this._apnItems = apns.map(function(apn) {
|
||||
return ApnItem(apn.id, apn.category, apn);
|
||||
});
|
||||
return this._apnItems;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the apn item with a specified id.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf ApnList.prototype
|
||||
* @returns {Promise.<ApnItem>}
|
||||
*/
|
||||
item: function al_item(id) {
|
||||
// Return the apn item based on the current copy if apn items.
|
||||
return this.items().then(function(apnItems) {
|
||||
if (!apnItems || !id) {
|
||||
return null;
|
||||
} else {
|
||||
return apnItems.find((apnItem) => apnItem.id === id);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Add an apn to the list with specified category. Returns the id of the
|
||||
* newly added apn.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf ApnList.prototype
|
||||
* @params {Object} apn
|
||||
* @params {ApnItem.APN_CATEGORY} category
|
||||
* @returns {Promise.<String>} The apn id
|
||||
*/
|
||||
add: function al_apns(apn, category) {
|
||||
var apnClone = ApnUtils.clone(apn);
|
||||
return this._schedule(this._add.bind(this, apnClone, category));
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove an apn from the list.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf ApnList.prototype
|
||||
* @params {String} id
|
||||
* @returns {Promise}
|
||||
*/
|
||||
remove: function al_remove(id) {
|
||||
return this._schedule(this._remove.bind(this, id));
|
||||
},
|
||||
|
||||
/**
|
||||
* Update an apn. All properties expect for "id" and "category" will be
|
||||
* overwritten based on the passed apn object.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf ApnList.prototype
|
||||
* @params {String} id
|
||||
* @params {Object} apn
|
||||
* @returns {Promise}
|
||||
*/
|
||||
update: function al_update(id, apn) {
|
||||
var apnClone = ApnUtils.clone(apn);
|
||||
return this._schedule(this._update.bind(this, id, apnClone));
|
||||
}
|
||||
};
|
||||
|
||||
return function ctor_apn_list(key) {
|
||||
return new ApnList(key);
|
||||
};
|
||||
});
|
||||
192
gaia/apps/settings/js/modules/apn/apn_selections.js
Executable file
192
gaia/apps/settings/js/modules/apn/apn_selections.js
Executable file
|
|
@ -0,0 +1,192 @@
|
|||
/**
|
||||
* ApnSelections stores the id of the apn being used on each apn type. The
|
||||
* selections are provided in terms of Observable. Changes to the selection
|
||||
* can be observed and then be saved to the settings database.
|
||||
* Implementation details please refer to {@link ApnSelections}.
|
||||
*
|
||||
* @module modules/apn/apn_selections
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var Observable = require('modules/mvvm/observable');
|
||||
var SettingsCache = require('modules/settings_cache');
|
||||
var ApnConst = require('modules/apn/apn_const');
|
||||
|
||||
var ICC_COUNT = navigator.mozMobileConnections.length;
|
||||
var APN_TYPES = ApnConst.APN_TYPES;
|
||||
var APN_SELECTIONS_KEY = ApnConst.APN_SELECTIONS_KEY;
|
||||
|
||||
/**
|
||||
* @class ApnSelections
|
||||
* @requires module:modules/mvvm/observable
|
||||
* @requires module:modules/settings_cache
|
||||
* @requires module:modules/apn/apn_const
|
||||
* @returns {ApnSelections}
|
||||
*/
|
||||
function ApnSelections() {
|
||||
this._readyPromise = null;
|
||||
this._apnSelections = [];
|
||||
}
|
||||
|
||||
ApnSelections.prototype = {
|
||||
/**
|
||||
* Converts the apn selections to static objects for storing.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf ApnSelections.prototype
|
||||
* @returns {Promise.<Array>}
|
||||
*/
|
||||
_export: function as__export() {
|
||||
return this._apnSelections.map(function(apnSelection) {
|
||||
var obj = {};
|
||||
APN_TYPES.forEach(function(apnType) {
|
||||
obj[apnType] = apnSelection[apnType];
|
||||
});
|
||||
return obj;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Stores the current selection to the settings database.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf ApnSelections.prototype
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_commit: function as__commit() {
|
||||
return new Promise(function(resolve) {
|
||||
var obj = {};
|
||||
obj[APN_SELECTIONS_KEY] = this._export();
|
||||
var req = navigator.mozSettings.createLock().set(obj);
|
||||
req.onsuccess = function() { resolve(); };
|
||||
req.onerror = function() {
|
||||
console.error('write apn selections failed');
|
||||
resolve();
|
||||
};
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Registers observers so that we can save the selection when it changes.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf ApnSelections.prototype
|
||||
*/
|
||||
_observeApnSelections: function as__observeApnSelections(apnSelection) {
|
||||
APN_TYPES.forEach(function(apnType) {
|
||||
apnSelection.observe(apnType, this._commit.bind(this));
|
||||
}, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates empty selections.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf ApnSelections.prototype
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_createEmptySelections: function as__createEmptySelections() {
|
||||
var emptySelections = [];
|
||||
var emptySelection = {};
|
||||
APN_TYPES.forEach((apnType) => emptySelection[apnType] = null);
|
||||
|
||||
for (var i = 0; i < ICC_COUNT; i++) {
|
||||
emptySelections.push(emptySelection);
|
||||
}
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
var obj = {};
|
||||
obj[APN_SELECTIONS_KEY] = emptySelections;
|
||||
var req = navigator.mozSettings.createLock().set(obj);
|
||||
req.onsuccess = req.onerror =
|
||||
function() { resolve(emptySelections); };
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Initializes the selections based on the values stored in the settings
|
||||
* database.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf ApnSelections.prototype
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_ready: function as__ready() {
|
||||
// This ensures that the ready process being executed only once.
|
||||
if (!this._readyPromise) {
|
||||
var that = this;
|
||||
this._readyPromise = new Promise(function(resolve) {
|
||||
SettingsCache.getSettings(function(results) {
|
||||
resolve(results[APN_SELECTIONS_KEY]);
|
||||
});
|
||||
}).then(function(apnSelections) {
|
||||
if (apnSelections) {
|
||||
return apnSelections;
|
||||
} else {
|
||||
return that._createEmptySelections();
|
||||
}
|
||||
}).then(function(apnSelections) {
|
||||
// Turn the selections to observables
|
||||
apnSelections.forEach(function(selection, index) {
|
||||
var observableApnSelection = Observable(selection);
|
||||
that._observeApnSelections(observableApnSelection);
|
||||
that._apnSelections[index] = observableApnSelection;
|
||||
});
|
||||
|
||||
// Clear the entire apn selection states when the selections are
|
||||
// cleared by other apps (usually the wap push app).
|
||||
navigator.mozSettings.addObserver(APN_SELECTIONS_KEY,
|
||||
function(event) {
|
||||
if (event.settingValue === null) {
|
||||
that._readyPromise = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return this._readyPromise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the apn selection of a sim slot. The selection object is an
|
||||
* Observable in which the apn types and apn ids are stored as key-value
|
||||
* pairs.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf ApnSelections.prototype
|
||||
* @params {Number} serviceId
|
||||
* @returns {Promise.<Observable>}
|
||||
*/
|
||||
get: function as_get(serviceId) {
|
||||
return this._ready().then(function() {
|
||||
return this._apnSelections[serviceId];
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Reset the apn selection to null.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf ApnSelections.prototype
|
||||
* @params {Number} serviceId
|
||||
* @returns {Promise}
|
||||
*/
|
||||
clear: function as_clear(serviceId) {
|
||||
var apnSelection = this._apnSelections[serviceId];
|
||||
if (!apnSelection) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return this._ready().then(function() {
|
||||
APN_TYPES.forEach(function(apnType) {
|
||||
apnSelection[apnType] = null;
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return function() {
|
||||
return new ApnSelections();
|
||||
};
|
||||
});
|
||||
245
gaia/apps/settings/js/modules/apn/apn_settings.js
Executable file
245
gaia/apps/settings/js/modules/apn/apn_settings.js
Executable file
|
|
@ -0,0 +1,245 @@
|
|||
/**
|
||||
* ApnSettings provides functions for manipulating the apn settings in the
|
||||
* settings database.
|
||||
* Implementation details please refer to {@link ApnSettings}.
|
||||
*
|
||||
* @module modules/apn/apn_settings
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var SettingsCache = require('modules/settings_cache');
|
||||
var ApnUtils = require('modules/apn/apn_utils');
|
||||
var ApnConst = require('modules/apn/apn_const');
|
||||
|
||||
var APN_SETTINGS_KEY = ApnConst.APN_SETTINGS_KEY;
|
||||
var DEFAULT_APN_SETTINGS_KEY = ApnConst.DEFAULT_APN_SETTINGS_KEY;
|
||||
|
||||
/**
|
||||
* @class ApnSettings
|
||||
* @returns {ApnSettings}
|
||||
*/
|
||||
function ApnSettings() {
|
||||
this._isReady = false;
|
||||
this._readyPromise = null;
|
||||
this._isWritingDB = false;
|
||||
this._isDirty = false;
|
||||
this._apnSettings = null;
|
||||
this._promiseChain = Promise.resolve();
|
||||
}
|
||||
|
||||
ApnSettings.prototype = {
|
||||
/**
|
||||
* As the operations should not be performed concurrently. We use this
|
||||
* function to enusre the operations are performed one by one.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf ApnSettings.prototype
|
||||
* @param {Function} task
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_schedule: function al__schedule(task) {
|
||||
var that = this;
|
||||
this._promiseChain = this._promiseChain.then(function() {
|
||||
return task().then(function() {
|
||||
return that._commit();
|
||||
});
|
||||
});
|
||||
return this._promiseChain;
|
||||
},
|
||||
|
||||
/**
|
||||
* Stores the current copy of apn settings to the settings database.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf ApnSettings.prototype
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_commit: function al__commit() {
|
||||
if (!this._isDirty) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
var that = this;
|
||||
return new Promise(function(resolve) {
|
||||
that._isWritingDB = true;
|
||||
var obj = {};
|
||||
obj[APN_SETTINGS_KEY] = that._apnSettings;
|
||||
var req = navigator.mozSettings.createLock().set(obj);
|
||||
req.onsuccess = req.onerror = function() {
|
||||
that._isDirty = false;
|
||||
that._isWritingDB = false;
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Registers an observer on setting changes because ril.data.apnSettings
|
||||
* could be changed by other apps (system and wap).
|
||||
*
|
||||
* @access private
|
||||
* @memberOf ApnSettings.prototype
|
||||
*/
|
||||
_addObservers: function as__registerListeners() {
|
||||
var mozSettings = window.navigator.mozSettings;
|
||||
if (!mozSettings) {
|
||||
return;
|
||||
}
|
||||
mozSettings.addObserver(APN_SETTINGS_KEY, function(event) {
|
||||
if (!this._isWritingDB) {
|
||||
// Do not reflect the change during the committing.
|
||||
this._apnSettings = event.settingValue || [];
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Initializes the settings based on the values stored in the settings
|
||||
* database.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf ApnSettings.prototype
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_ready: function as__ready() {
|
||||
if (this._isReady) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
// This ensures that the ready process being executed only once.
|
||||
if (!this._readyPromise) {
|
||||
var that = this;
|
||||
this._readyPromise = new Promise(function(resolve) {
|
||||
SettingsCache.getSettings(function(results) {
|
||||
that._isReady = true;
|
||||
that._apnSettings = results[APN_SETTINGS_KEY] || [];
|
||||
that._addObservers();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
return this._readyPromise;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The internal update function.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf ApnSettings.prototype
|
||||
* @params {Number} serviceId
|
||||
* @params {String} apnType
|
||||
* @params {Object} newApn
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_update: function as__update(serviceId, apnType, newApn) {
|
||||
return this._ready().then(function() {
|
||||
var apns = this._apnSettings[serviceId];
|
||||
if (!apns) {
|
||||
apns = this._apnSettings[serviceId] = [];
|
||||
}
|
||||
|
||||
var index = apns.findIndex(function(apn) {
|
||||
return apn.types.some((type) => apnType === type);
|
||||
});
|
||||
|
||||
if (index === -1) {
|
||||
if (newApn) {
|
||||
this._isDirty = true;
|
||||
apns.push(newApn);
|
||||
}
|
||||
} else {
|
||||
if (newApn) {
|
||||
if (!ApnUtils.isMatchedApn(apns[index], newApn)) {
|
||||
this._isDirty = true;
|
||||
apns[index] = newApn;
|
||||
}
|
||||
} else {
|
||||
this._isDirty = true;
|
||||
apns.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all apns with of a sim card.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf ApnSettings.prototype
|
||||
* @params {Number} serviceId
|
||||
* @returns {Promise.<Array>}
|
||||
*/
|
||||
getAll: function as_getAll(serviceId) {
|
||||
return this._ready().then(function() {
|
||||
return this._apnSettings[serviceId];
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the apn of the specified apn type.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf ApnSettings.prototype
|
||||
* @params {Number} serviceId
|
||||
* @params {String} apnType
|
||||
* @returns {Promise.<Object>}
|
||||
*/
|
||||
get: function as_get(serviceId, apnType) {
|
||||
return this._ready().then(function() {
|
||||
var apns = this._apnSettings[serviceId];
|
||||
if (apns) {
|
||||
return apns.find((apn) => apn.types.indexOf(apnType) >= 0);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Update an apn and the change will be saved into the settings database.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf ApnSettings.prototype
|
||||
* @params {Number} serviceId
|
||||
* @params {String} apnType
|
||||
* @params {Object} apn
|
||||
* @returns {Promise}
|
||||
*/
|
||||
update: function as_update(serviceId, apnType, apn) {
|
||||
var apnClone = ApnUtils.clone(apn);
|
||||
return this._schedule(
|
||||
this._update.bind(this, serviceId, apnType, apnClone));
|
||||
},
|
||||
|
||||
/**
|
||||
* Restore the apn settings to the default value determined in
|
||||
* system/js/operator_variants.js.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf ApnSettings.prototype
|
||||
* @params {String} serviceId
|
||||
* @returns {Promise}
|
||||
*/
|
||||
restore: function as_restore(serviceId) {
|
||||
var that = this;
|
||||
return this._ready().then(function() {
|
||||
return new Promise(function(resolve) {
|
||||
SettingsCache.getSettings(function(results) {
|
||||
resolve(results[DEFAULT_APN_SETTINGS_KEY] || []);
|
||||
});
|
||||
});
|
||||
}).then(function(defaultApnSettings) {
|
||||
return that._schedule(function() {
|
||||
that._isDirty = true;
|
||||
that._apnSettings[serviceId] = defaultApnSettings[serviceId];
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return function ctor_apn_settings() {
|
||||
return new ApnSettings();
|
||||
};
|
||||
});
|
||||
482
gaia/apps/settings/js/modules/apn/apn_settings_manager.js
Executable file
482
gaia/apps/settings/js/modules/apn/apn_settings_manager.js
Executable file
|
|
@ -0,0 +1,482 @@
|
|||
/**
|
||||
* ApnSettingsManager provides functions for managing apn items. When an item
|
||||
* is selected to be used, ApnSettingsManager also helps convert the selection
|
||||
* to the real settings expected by the platform.
|
||||
* ApnSettingsManager does its task by coordinating the following objects:
|
||||
* - ApnList
|
||||
* There is an ApnList object for each sim. ApnList stores ApnItem objects
|
||||
* representing apns come from the apn.json database, client provisioning
|
||||
* messages, and user's creation. Each ApnItem has an id assigned upon
|
||||
* the creation. The id is used to recored users' apn selection.
|
||||
* - ApnSelections
|
||||
* ApnSelections stores the id of the apn being used on each apn
|
||||
* type.
|
||||
* - ApnSettings
|
||||
* ApnSettings wraps the apn settings stored in the moz settings database
|
||||
* and provides simple interface for manipulation.
|
||||
* Implementation details please refer to {@link ApnSettingsManager}.
|
||||
*
|
||||
* @module modules/apn/apn_settings_manager
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var AsyncStorage = require('modules/async_storage');
|
||||
var ApnConst = require('modules/apn/apn_const');
|
||||
var ApnUtils = require('modules/apn/apn_utils');
|
||||
var ApnItem = require('modules/apn/apn_item');
|
||||
var ApnSettings = require('modules/apn/apn_settings');
|
||||
var ApnList = require('modules/apn/apn_list');
|
||||
var ApnSelections = require('modules/apn/apn_selections');
|
||||
|
||||
var APN_TYPES = ApnConst.APN_TYPES;
|
||||
var APN_LIST_KEY = ApnConst.APN_LIST_KEY;
|
||||
var CACHED_ICCIDS_KEY = ApnConst.CACHED_ICCIDS_KEY;
|
||||
|
||||
var RESTORE_MODE = {
|
||||
ALL: 0,
|
||||
ONLY_APN_ITEMS: 1,
|
||||
};
|
||||
|
||||
/**
|
||||
* @class ApnSettingsManager
|
||||
* @requires module:modules/async_storage
|
||||
* @requires module:modules/apn/apn_const
|
||||
* @requires module:modules/apn/apn_utils
|
||||
* @requires module:modules/apn/apn_item
|
||||
* @requires module:modules/apn/apn_settings
|
||||
* @requires module:modules/apn/apn_list
|
||||
* @requires module:modules/apn/apn_selections
|
||||
* @returns {ApnSettingsManager}
|
||||
*/
|
||||
function ApnSettingsManager() {
|
||||
this._apnLists = {};
|
||||
this._apnSelections = ApnSelections();
|
||||
this._apnSettings = ApnSettings();
|
||||
|
||||
this._readyPromises = {};
|
||||
|
||||
Object.defineProperty(this, 'RESTORE_MODE', {
|
||||
configurable: false,
|
||||
get: function() {
|
||||
return RESTORE_MODE;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ApnSettingsManager.prototype = {
|
||||
/**
|
||||
* Ensures the current apn items are up-to-date. When the current apn
|
||||
* id does not equal to the cached apn id, we should restore the apn items.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf ApnSettingsManager.prototype
|
||||
* @param {Number} serviceId
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_ready: function asc_fromItems(serviceId) {
|
||||
if (!this._readyPromises[serviceId]) {
|
||||
// Check if a new icc card is detected.
|
||||
this._readyPromises[serviceId] = AsyncStorage.getItem(CACHED_ICCIDS_KEY)
|
||||
.then((cachedIccIds) => {
|
||||
var cachedIccId = cachedIccIds && cachedIccIds[serviceId];
|
||||
var curIccId = navigator.mozMobileConnections[serviceId].iccId;
|
||||
if (!curIccId || curIccId === cachedIccId) {
|
||||
return;
|
||||
}
|
||||
|
||||
cachedIccIds = cachedIccIds || {};
|
||||
cachedIccIds[serviceId] = curIccId;
|
||||
// Get the default preset apns by restoring.
|
||||
return Promise.all([
|
||||
// In this case we only restore apn items but not apn settings.
|
||||
this.restore(serviceId, RESTORE_MODE.ONLY_APN_ITEMS),
|
||||
AsyncStorage.setItem(CACHED_ICCIDS_KEY, cachedIccIds)
|
||||
]);
|
||||
});
|
||||
}
|
||||
return this._readyPromises[serviceId];
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the id of the first preset apn item.
|
||||
*
|
||||
* @param {Number} serviceId
|
||||
* @param {String} apnType
|
||||
* @returns {Promise.<String>}
|
||||
*/
|
||||
_deriveActiveApnIdFromItems: function asc_fromItems(serviceId, apnType) {
|
||||
return this._apnItems(serviceId, apnType).then(function(apnItems) {
|
||||
if (apnItems.length) {
|
||||
return apnItems.sort(ApnUtils.sortByCategory)[0].id;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the id of the apn item that matches the current apn setting of
|
||||
* the specified apn type.
|
||||
*
|
||||
* @param {Number} serviceId
|
||||
* @param {String} apnType
|
||||
* @returns {Promise.<String>}
|
||||
*/
|
||||
_deriveActiveApnIdFromSettings:
|
||||
function asc_deriveFromSettings(serviceId, apnType) {
|
||||
return Promise.all([
|
||||
this._apnItems(serviceId, apnType),
|
||||
this._apnSettings.get(serviceId, apnType)
|
||||
]).then(function(results) {
|
||||
var apnItems = results[0];
|
||||
var apnInUse = results[1];
|
||||
// apnInUse is the apn that RIL is currently using.
|
||||
if (apnInUse) {
|
||||
var matchedApnItem = apnItems.find(function(apnItem) {
|
||||
return ApnUtils.isMatchedApn(apnItem.apn, apnInUse);
|
||||
});
|
||||
if (matchedApnItem) {
|
||||
// Has matched apn in the existing apns.
|
||||
return matchedApnItem.id;
|
||||
} else {
|
||||
var category = (apnInUse.carrier === '_custom_') ?
|
||||
ApnItem.APN_CATEGORY.CUSTOM : ApnItem.APN_CATEGORY.PRESET;
|
||||
return this.addApn(serviceId, apnInUse, category);
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the apn type an apn that is actively being used for.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf ApnSettingsManager.prototype
|
||||
* @param {Number} serviceId
|
||||
* @param {String} apnId
|
||||
* id of the apn being checked.
|
||||
* @returns {Promise.<String>}
|
||||
*/
|
||||
_getApnAppliedType: function asc_getApnAppliedType(serviceId, apnId) {
|
||||
return this._apnSelections.get(serviceId).then(function(apnSelection) {
|
||||
return APN_TYPES.find((apnType) => apnSelection[apnType] === apnId);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Store the current apn selection to apn settings to the settings database.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf ApnSettingsManager.prototype
|
||||
* @param {Number} serviceId
|
||||
* @param {String} apnType
|
||||
*/
|
||||
_storeApnSettingByType: function asc_storeApnByType(serviceId, apnType) {
|
||||
return this._apnSelections.get(serviceId).then(function(apnSelection) {
|
||||
var apnList = this._apnList(serviceId);
|
||||
var apnId = apnSelection[apnType];
|
||||
if (apnId) {
|
||||
// Get the apn item of apnType.
|
||||
return apnList.item(apnId);
|
||||
}
|
||||
}.bind(this)).then(function(apnItem) {
|
||||
if (apnItem) {
|
||||
return this._apnSettings.update(serviceId, apnType, apnItem.apn);
|
||||
} else {
|
||||
return this._apnSettings.update(serviceId, apnType, null);
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the apn item list of a sim slot.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf ApnSettingsManager.prototype
|
||||
* @param {Number} serviceId
|
||||
* @returns {ApnList} The apn item list.
|
||||
*/
|
||||
_apnList: function asc_apnList(serviceId) {
|
||||
var apnList = this._apnLists[serviceId];
|
||||
if (!apnList) {
|
||||
this._apnLists[serviceId] = apnList = ApnList(APN_LIST_KEY + serviceId);
|
||||
}
|
||||
return apnList;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the apn items of an apn type for a sim slot.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf ApnSettingsManager.prototype
|
||||
* @param {Number} serviceId
|
||||
* @param {String} apnType
|
||||
* @returns {Promise.<Array.<ApnItem>>} The apn items.
|
||||
*/
|
||||
_apnItems: function asc_apnItems(serviceId, apnType) {
|
||||
return this._ready(serviceId).then(() => {
|
||||
return this._apnList(serviceId).items();
|
||||
}).then((apnItems) => {
|
||||
return apnItems &&
|
||||
apnItems.filter(ApnUtils.apnTypeFilter.bind(null, apnType)) || [];
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Restore the apn items of a category.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf ApnSettingsManager.prototype
|
||||
* @param {ApnList} apnList
|
||||
* @param {Array<Object>} apnsForRestoring
|
||||
* The function use this to restore the apn items.
|
||||
* @param {ApnItem.APN_CATEGORY} category
|
||||
* The category of the items to be resotred.
|
||||
* @returns {Promise.<Array.<String>>} The id of the restored apn items.
|
||||
*/
|
||||
_restoreApnItemsOfCategory:
|
||||
function asc_restoreApnItems(apnList, apnsForRestoring, category) {
|
||||
return apnList.items().then(function(apnItems) {
|
||||
// Remove all existing preset apns.
|
||||
apnItems = apnItems || [];
|
||||
var promises = [];
|
||||
apnItems.filter(function(apnItem) {
|
||||
if (apnItem.category === category) {
|
||||
return true;
|
||||
}
|
||||
}).forEach(function(apnItem) {
|
||||
promises.push(apnList.remove(apnItem.id));
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
}).then(function() {
|
||||
// Add default preset apns.
|
||||
var promises = [];
|
||||
apnsForRestoring.forEach(function(apns) {
|
||||
promises.push(apnList.add(apns, category));
|
||||
});
|
||||
return Promise.all(promises);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Restore the apn items and apn settings to the default. Only apn items of
|
||||
* the category ApnItem.APN_CATEGORY.PRESET and ApnItem.APN_CATEGORY.EU are
|
||||
* restored. User created apn items (custom apns) will not be affected. The
|
||||
* preset apn items are from the apn.json database and client provisioning
|
||||
* messages.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf ApnSettingsManager.prototype
|
||||
* @param {String} serviceId
|
||||
* @param {Number} mode
|
||||
* The possible values are defined in RESTORE_MODE. We
|
||||
* restore both apn items and apn settings by default. Only
|
||||
* apn items are restored when mode is
|
||||
* RESTORE_MODE.ONLY_APN_ITEMS.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
restore: function asc_restore(serviceId, mode) {
|
||||
var mobileConnection = navigator.mozMobileConnections[serviceId];
|
||||
var networkType = mobileConnection.data.type;
|
||||
var apnList = this._apnList(serviceId);
|
||||
var that = this;
|
||||
|
||||
return Promise.all([
|
||||
ApnUtils.getOperatorCode(serviceId, 'mcc'),
|
||||
ApnUtils.getOperatorCode(serviceId, 'mnc')
|
||||
]).then(function(values) {
|
||||
// Get default apns and client provisioning apns matching the mcc/mnc
|
||||
// codes.
|
||||
var mcc = values[0];
|
||||
var mnc = values[1];
|
||||
|
||||
return Promise.all([
|
||||
ApnUtils.getEuApns(),
|
||||
ApnUtils.getDefaultApns(mcc, mnc, networkType),
|
||||
ApnUtils.getCpApns(mcc, mnc, networkType)
|
||||
]);
|
||||
}).then(function(results) {
|
||||
// Restore preset and eu apns.
|
||||
var euApns = ApnUtils.separateApnsByType(results[0]);
|
||||
var presetApns = ApnUtils.separateApnsByType(
|
||||
Array.prototype.concat.apply([], results.slice(1)));
|
||||
|
||||
return that._restoreApnItemsOfCategory(
|
||||
apnList, euApns, ApnItem.APN_CATEGORY.EU)
|
||||
.then(function() {
|
||||
return that._restoreApnItemsOfCategory(
|
||||
apnList, presetApns, ApnItem.APN_CATEGORY.PRESET);
|
||||
});
|
||||
}).then(function() {
|
||||
// We simply clear the apn selections. The selections will be restored
|
||||
// based on the current apn settings when it is being queried.
|
||||
return that._apnSelections.clear(serviceId);
|
||||
}).then(function() {
|
||||
if (mode !== RESTORE_MODE.ONLY_APN_ITEMS) {
|
||||
return that._apnSettings.restore(serviceId);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Query apn items matching the mcc/mnc codes in the apn.json
|
||||
* database and the one received through client provisioning messages.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf ApnSettingsManager.prototype
|
||||
* @param {Number} serviceId
|
||||
* @param {String} apnType
|
||||
* @returns {Promise.<Array.<ApnItem>>} The apn items
|
||||
*/
|
||||
queryApns: function asc_queryApns(serviceId, apnType) {
|
||||
return this.getActiveApnId(serviceId, apnType)
|
||||
.then(function(activeApnId) {
|
||||
return this._apnItems(serviceId, apnType).then(function(items) {
|
||||
items.forEach(function(apnItem) {
|
||||
apnItem.active = (apnItem.id === activeApnId);
|
||||
});
|
||||
return items;
|
||||
});
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Add an apn to a sim slot.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf ApnSettingsManager.prototype
|
||||
* @param {Number} serviceId
|
||||
* @param {Object} apn
|
||||
* @param {ApnItem.APN_CATEGORY} category
|
||||
* @returns {Promise}
|
||||
*/
|
||||
addApn: function asc_addApn(serviceId, apn, category) {
|
||||
return this._ready(serviceId).then(() => {
|
||||
return this._apnList(serviceId).add(apn,
|
||||
category || ApnItem.APN_CATEGORY.CUSTOM);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove an apn from a sim slot.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf ApnSettingsManager.prototype
|
||||
* @param {Number} serviceId
|
||||
* @param {String} id
|
||||
* id of the apn item to be added.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
removeApn: function asc_removeApn(serviceId, id) {
|
||||
return this._ready(serviceId).then(() => {
|
||||
return this._apnList(serviceId).remove(id);
|
||||
}).then(() => {
|
||||
// check if the removed apn is actively being used.
|
||||
return this._getApnAppliedType(serviceId, id);
|
||||
}).then((matchedApnType) => {
|
||||
if (matchedApnType) {
|
||||
return this._deriveActiveApnIdFromItems(serviceId, matchedApnType)
|
||||
.then((activeApnId) => {
|
||||
return this.setActiveApnId(serviceId, matchedApnType, activeApnId);
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Update an apn item.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf ApnSettingsManager.prototype
|
||||
* @param {Number} serviceId
|
||||
* @param {String} id
|
||||
* id of the apn item to be updated.
|
||||
* @param {Object} apn
|
||||
* @returns {Promise}
|
||||
*/
|
||||
updateApn: function asc_updateApn(serviceId, id, apn) {
|
||||
return this._ready(serviceId).then(() => {
|
||||
return this._apnList(serviceId).update(id, apn);
|
||||
}).then(() => {
|
||||
// check if the updated apn is actively being used.
|
||||
return this._getApnAppliedType(serviceId, id);
|
||||
}).then((matchedApnType) => {
|
||||
if (matchedApnType) {
|
||||
return this._storeApnSettingByType(serviceId, matchedApnType);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the id of the apn that is actively being used for an apn type.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf ApnSettingsManager.prototype
|
||||
* @param {Number} serviceId
|
||||
* @param {String} apnType
|
||||
* @returns {Promise.<String>}
|
||||
*/
|
||||
getActiveApnId: function asc_getActiveApnId(serviceId, apnType) {
|
||||
var that = this;
|
||||
return this._ready(serviceId).then(function() {
|
||||
return that._apnSelections.get(serviceId);
|
||||
}).then(function(apnSelection) {
|
||||
return apnSelection && apnSelection[apnType];
|
||||
}).then(function(activeApnId) {
|
||||
if (activeApnId) {
|
||||
return activeApnId;
|
||||
} else {
|
||||
// If there is no existing active apn id, try to derive the id from
|
||||
// the current apn settings.
|
||||
return that._deriveActiveApnIdFromSettings(serviceId, apnType)
|
||||
.then(function(apnId) {
|
||||
// Set the id as the active apn id.
|
||||
that.setActiveApnId(serviceId, apnType, apnId);
|
||||
return apnId;
|
||||
});
|
||||
}
|
||||
}).then(function(activeApnId) {
|
||||
// If there is still no active apn id, means that the apn settings have
|
||||
// not been set and we need to derive a default id from the current apn
|
||||
// items (stored in the apn list).
|
||||
if (activeApnId) {
|
||||
return activeApnId;
|
||||
} else {
|
||||
return that._deriveActiveApnIdFromItems(serviceId, apnType)
|
||||
.then(function(apnId) {
|
||||
// Set the id as the active apn id.
|
||||
that.setActiveApnId(serviceId, apnType, apnId);
|
||||
return apnId;
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the id of an apn that is to be used for an apn type.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf ApnSettingsManager.prototype
|
||||
* @param {Number} serviceId
|
||||
* @param {String} apnType
|
||||
* @param {String} id
|
||||
* @returns {Promise}
|
||||
*/
|
||||
setActiveApnId: function asc_setActiveApnId(serviceId, apnType, id) {
|
||||
return this._ready(serviceId).then(() => {
|
||||
return this._apnSelections.get(serviceId);
|
||||
}).then((apnSelection) => {
|
||||
if (apnSelection[apnType] !== id) {
|
||||
apnSelection[apnType] = id;
|
||||
return this._storeApnSettingByType(serviceId, apnType);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return new ApnSettingsManager();
|
||||
});
|
||||
200
gaia/apps/settings/js/modules/apn/apn_utils.js
Executable file
200
gaia/apps/settings/js/modules/apn/apn_utils.js
Executable file
|
|
@ -0,0 +1,200 @@
|
|||
/**
|
||||
* The apn utilities
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var SettingsCache = require('modules/settings_cache');
|
||||
var ApnHelper = require('shared/apn_helper');
|
||||
var ApnConst = require('modules/apn/apn_const');
|
||||
var ApnItem = require('modules/apn/apn_item');
|
||||
var LazyLoader = require('shared/lazy_loader');
|
||||
|
||||
var CP_APN_KEY = ApnConst.CP_APN_KEY;
|
||||
var DEFAULT_APN_KEY = ApnConst.DEFAULT_APN_KEY;
|
||||
var MCC_SETTINGS_KEY = ApnConst.MCC_SETTINGS_KEY;
|
||||
var MNC_SETTINGS_KEY = ApnConst.MNC_SETTINGS_KEY;
|
||||
var APN_PROPS = ApnConst.APN_PROPS;
|
||||
var EU_ROAMING_FILE_PATH = ApnConst.EU_ROAMING_FILE_PATH;
|
||||
|
||||
function _getOperatorCode(serviceId, type) {
|
||||
var value;
|
||||
var key;
|
||||
|
||||
if (type === 'mcc') {
|
||||
value = '000';
|
||||
key = MCC_SETTINGS_KEY;
|
||||
} else if (type === 'mnc') {
|
||||
value = '00';
|
||||
key = MNC_SETTINGS_KEY;
|
||||
} else {
|
||||
return Promise.reject('invalid type');
|
||||
}
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
SettingsCache.getSettings(function(results) {
|
||||
var values = results[key];
|
||||
if (values && Array.isArray(values) && values[serviceId]) {
|
||||
value = values[serviceId];
|
||||
}
|
||||
resolve(value);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function. Filter APNs by apn type.
|
||||
*
|
||||
* @param {String} type
|
||||
* The apn type we would like to include.
|
||||
* @param {ApnItem} apnItem
|
||||
*/
|
||||
function _apnTypeFilter(type, apnItem) {
|
||||
if (!type || !apnItem || !apnItem.apn.types) {
|
||||
return false;
|
||||
} else if (type === '*') {
|
||||
return true;
|
||||
}
|
||||
return apnItem.apn.types.indexOf(type) != -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query <apn> elements matching the mcc/mnc arguments in the apn.json
|
||||
* database
|
||||
*
|
||||
* @param {String} mcc
|
||||
* @param {String} mnc
|
||||
* @param {String} networkType
|
||||
* The network type which the APN must be compatible with.
|
||||
*/
|
||||
function _getDefaultApns(mcc, mnc, networkType) {
|
||||
// XXX: should fallback to the JSON file if we don't get the apns
|
||||
return new Promise(function(resolve, reject) {
|
||||
SettingsCache.getSettings(function(results) {
|
||||
var apns = results[DEFAULT_APN_KEY] || {};
|
||||
resolve(ApnHelper.getCompatible(apns, mcc, mnc, networkType));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Query <apn> elements matching the mcc/mnc arguments in the database
|
||||
* received through client provisioning messages.
|
||||
*
|
||||
* @param {String} mcc
|
||||
* @param {String} mnc
|
||||
* @param {String} networkType
|
||||
* The network type which the APN must be compatible with.
|
||||
*/
|
||||
function _getCpApns(mcc, mnc, networkType) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
SettingsCache.getSettings(function(results) {
|
||||
var apns = results[CP_APN_KEY] || {};
|
||||
resolve(ApnHelper.getCompatible(apns, mcc, mnc, networkType));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var _euApnChecked = false;
|
||||
var _euApns = null;
|
||||
/**
|
||||
* Return the EU apns for roaming.
|
||||
*/
|
||||
function _getEuApns() {
|
||||
if (_euApnChecked) {
|
||||
return Promise.resolve(_euApns);
|
||||
} else {
|
||||
return LazyLoader.getJSON(EU_ROAMING_FILE_PATH).then(function(result) {
|
||||
_euApnChecked = true;
|
||||
// Only return eu apns when both home and foreign operators are
|
||||
// specified.
|
||||
if (result.home && result.foreign &&
|
||||
Object.keys(result.home).length > 0 &&
|
||||
Object.keys(result.foreign).length > 0) {
|
||||
_euApns = result.defaultApns;
|
||||
}
|
||||
return _euApns;
|
||||
}).catch(function() {
|
||||
_euApnChecked = true;
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function _generateId() {
|
||||
// should refine this
|
||||
return Math.random().toString(36).substr(2, 9);
|
||||
}
|
||||
|
||||
function _cloneApn(apn) {
|
||||
var newApn = {};
|
||||
for (var p in apn) {
|
||||
newApn[p] = apn[p];
|
||||
}
|
||||
return newApn;
|
||||
}
|
||||
|
||||
function _separateApnsByType(apns) {
|
||||
if (!apns) {
|
||||
return [];
|
||||
}
|
||||
return apns.reduce(function(result, apn) {
|
||||
// separate the apn by type
|
||||
apn.types.forEach(function(type) {
|
||||
var cloneApn = _cloneApn(apn);
|
||||
cloneApn.types = [type];
|
||||
result.push(cloneApn);
|
||||
});
|
||||
return result;
|
||||
}, []);
|
||||
}
|
||||
|
||||
function _isMatchedApn(apn1, apn2) {
|
||||
if (apn1 == null || apn2 == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the type of apn1 is the subset of apn2
|
||||
if (!apn1.types.every(function(type) {
|
||||
return (apn2.types.indexOf(type) !== -1);
|
||||
})) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return APN_PROPS.every(function(prop) {
|
||||
if (prop === 'types') {
|
||||
// we've already check this property
|
||||
return true;
|
||||
} else {
|
||||
return apn1[prop] === apn2[prop];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function _sortByCategory(apn1, apn2) {
|
||||
if (apn1.category === ApnItem.APN_CATEGORY.PRESET) {
|
||||
return true;
|
||||
} else if (apn2.category === ApnItem.APN_CATEGORY.PRESET) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function _clone(apn) {
|
||||
return JSON.parse(JSON.stringify(apn));
|
||||
}
|
||||
|
||||
return {
|
||||
getOperatorCode: _getOperatorCode,
|
||||
apnTypeFilter: _apnTypeFilter,
|
||||
getDefaultApns: _getDefaultApns,
|
||||
getCpApns: _getCpApns,
|
||||
getEuApns: _getEuApns,
|
||||
generateId: _generateId,
|
||||
separateApnsByType: _separateApnsByType,
|
||||
isMatchedApn: _isMatchedApn,
|
||||
sortByCategory: _sortByCategory,
|
||||
clone: _clone
|
||||
};
|
||||
});
|
||||
25
gaia/apps/settings/js/modules/app.js
Executable file
25
gaia/apps/settings/js/modules/app.js
Executable file
|
|
@ -0,0 +1,25 @@
|
|||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var Module = require('modules/base/module');
|
||||
var Observable = require('modules/mvvm/observable');
|
||||
|
||||
var App = Module.create(function App(app) {
|
||||
this.super(Observable).call(this);
|
||||
this._app = app;
|
||||
this._enabled = app.enabled;
|
||||
}).extend(Observable);
|
||||
|
||||
Observable.defineObservableProperty(App.prototype, 'enabled', {
|
||||
readonly: true
|
||||
});
|
||||
|
||||
Object.defineProperty(App.prototype, 'instance', {
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
return this._app;
|
||||
}
|
||||
});
|
||||
|
||||
return App;
|
||||
});
|
||||
92
gaia/apps/settings/js/modules/app_storage.js
Executable file
92
gaia/apps/settings/js/modules/app_storage.js
Executable file
|
|
@ -0,0 +1,92 @@
|
|||
/**
|
||||
* AppStorage is a singleton that caches app storage values for
|
||||
* app storage and root panel fast access
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var Module = require('modules/base/module');
|
||||
var Observable = require('modules/mvvm/observable');
|
||||
|
||||
/**
|
||||
* @class AppStorage
|
||||
* @requires module:modules/base/module
|
||||
* @requires module:modules/mvvm/observable
|
||||
* @returns {AppStorage}
|
||||
*/
|
||||
var AppStorage = Module.create(function AppStorage() {
|
||||
this.super(Observable).call(this);
|
||||
this._appStorage = navigator.getDeviceStorage('apps');
|
||||
|
||||
this._attachListeners();
|
||||
this._getSpaceInfo();
|
||||
}).extend(Observable);
|
||||
|
||||
Observable.defineObservableProperty(AppStorage.prototype,
|
||||
'usedPercentage', {
|
||||
readonly: true,
|
||||
value: 0
|
||||
});
|
||||
|
||||
Observable.defineObservableProperty(AppStorage.prototype,
|
||||
'totalSize', {
|
||||
readonly: true,
|
||||
value: 0
|
||||
});
|
||||
|
||||
Observable.defineObservableProperty(AppStorage.prototype,
|
||||
'usedSize', {
|
||||
readonly: true,
|
||||
value: 0
|
||||
});
|
||||
|
||||
Observable.defineObservableProperty(AppStorage.prototype,
|
||||
'freeSize', {
|
||||
readonly: true,
|
||||
value: 0
|
||||
});
|
||||
|
||||
|
||||
AppStorage.prototype.updateInfo = function as_updateInfo() {
|
||||
this._getSpaceInfo();
|
||||
};
|
||||
|
||||
AppStorage.prototype._attachListeners = function as_attachListeners() {
|
||||
this._appStorage.addEventListener('change', this);
|
||||
};
|
||||
|
||||
AppStorage.prototype.handleEvent = function as_handler(evt) {
|
||||
switch (evt.type) {
|
||||
case 'change':
|
||||
this._getSpaceInfo();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
AppStorage.prototype._getSpaceInfo = function as_getSpaceInfo() {
|
||||
var deviceStorage = this._appStorage;
|
||||
|
||||
if (!deviceStorage) {
|
||||
console.error('Cannot get DeviceStorage for: app');
|
||||
return;
|
||||
}
|
||||
deviceStorage.freeSpace().onsuccess = (e) => {
|
||||
this._freeSize = e.target.result;
|
||||
deviceStorage.usedSpace().onsuccess = (e) => {
|
||||
this._usedSize = e.target.result;
|
||||
// calculate the percentage to show a space usage bar
|
||||
this._totalSize =
|
||||
this._usedSize + this._freeSize;
|
||||
var usedPercentage = (this._totalSize === 0) ?
|
||||
0 : (this._usedSize * 100 / this._totalSize);
|
||||
if (usedPercentage > 100) {
|
||||
usedPercentage = 100;
|
||||
}
|
||||
this._usedPercentage = usedPercentage;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
return AppStorage();
|
||||
});
|
||||
|
||||
166
gaia/apps/settings/js/modules/apps_cache.js
Executable file
166
gaia/apps/settings/js/modules/apps_cache.js
Executable file
|
|
@ -0,0 +1,166 @@
|
|||
/**
|
||||
* AppsCache is a singleton that will encapsulate mozApps.mgmt inside and
|
||||
* expose neede API to callers. The reason why we need this is because
|
||||
* mozApps.mgmt.getAll() costs a lot and the better way to do so is cache
|
||||
* them inside this AppManager and automatically update it when install /
|
||||
* uninstall event is received.
|
||||
*
|
||||
* API:
|
||||
*
|
||||
* AppsCache.apps().then(function(apps) {
|
||||
* // installed apps in FxOS
|
||||
* });
|
||||
*
|
||||
* @module AppsCache
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var mozApps = window.navigator.mozApps;
|
||||
var mozAppsMgmt = mozApps && mozApps.mgmt;
|
||||
|
||||
var AppsCache = function() {
|
||||
this._initPromise = null;
|
||||
this._apps = [];
|
||||
this._eventHandlers = {};
|
||||
this._eventHandlers.oninstall = [];
|
||||
this._eventHandlers.onuninstall = [];
|
||||
};
|
||||
|
||||
AppsCache.prototype = {
|
||||
/**
|
||||
* apps is a method that would return a promise that would return
|
||||
* cached apps.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf AppsCache
|
||||
* @return {Promise}
|
||||
*/
|
||||
apps: function am_apps() {
|
||||
var self = this;
|
||||
return this._init().then(function() {
|
||||
return self._apps;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Entry point
|
||||
*
|
||||
* @access private
|
||||
* @memberOf AppsCache
|
||||
* @return {Promise}
|
||||
*/
|
||||
_init: function am__init() {
|
||||
var self = this;
|
||||
if (!this._initPromise) {
|
||||
this._initPromise = this._initApps().then(function() {
|
||||
self._initEvents();
|
||||
});
|
||||
}
|
||||
return this._initPromise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Init needed events
|
||||
*
|
||||
* @access private
|
||||
* @memberOf AppsCache
|
||||
*/
|
||||
_initEvents: function am__initEvents() {
|
||||
var self = this;
|
||||
|
||||
mozAppsMgmt.oninstall = function(evt) {
|
||||
var newApp = evt.application;
|
||||
var existing = false;
|
||||
self._apps.some((app, i) => {
|
||||
if (app.manifestURL === newApp.manifestURL) {
|
||||
self._apps[i] = newApp;
|
||||
existing = true;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
if (!existing) {
|
||||
self._apps.push(newApp);
|
||||
}
|
||||
self._eventHandlers.oninstall.forEach(function(eventHandler) {
|
||||
eventHandler(evt);
|
||||
});
|
||||
};
|
||||
|
||||
mozAppsMgmt.onuninstall = function(evt) {
|
||||
var manifestURL = evt.application.manifestURL;
|
||||
var appRemoved = false;
|
||||
self._apps = self._apps.filter(app => {
|
||||
if (app.manifestURL === manifestURL) {
|
||||
appRemoved = true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (appRemoved) {
|
||||
self._eventHandlers.onuninstall.forEach(function(eventHandler) {
|
||||
eventHandler(evt);
|
||||
});
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Init apps
|
||||
*
|
||||
* @access private
|
||||
* @memberOf AppsCache
|
||||
*/
|
||||
_initApps: function am__initApps() {
|
||||
var self = this;
|
||||
var promise = new Promise(function(resolve, reject) {
|
||||
var req = mozAppsMgmt.getAll();
|
||||
req.onsuccess = function success(evt) {
|
||||
self._apps = evt.target.result;
|
||||
resolve(self._apps);
|
||||
};
|
||||
req.onerror = function error(evt) {
|
||||
console.log('failed to get installed apps');
|
||||
reject(req.error);
|
||||
};
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Caller can add events when app is installed/uninstalled
|
||||
*
|
||||
* @access public
|
||||
* @param {String} eventName
|
||||
* @param {Function} callback
|
||||
* @memberOf AppsCache
|
||||
*/
|
||||
addEventListener: function am_addEventListener(eventName, callback) {
|
||||
if (this._eventHandlers[eventName]) {
|
||||
this._eventHandlers[eventName].push(callback);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Caller can remove events when app is installed/uninstalled
|
||||
*
|
||||
* @access public
|
||||
* @param {String} eventName
|
||||
* @param {Function} callback
|
||||
* @memberOf AppsCache
|
||||
*/
|
||||
removeEventListener: function am_removeEventListener(eventName, callback) {
|
||||
if (this._eventHandlers[eventName]) {
|
||||
var index = this._eventHandlers[eventName].indexOf(callback);
|
||||
if (index >= 0) {
|
||||
this._eventHandlers[eventName].splice(index, 1);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
var appsCache = new AppsCache();
|
||||
return appsCache;
|
||||
});
|
||||
21
gaia/apps/settings/js/modules/async_storage.js
Executable file
21
gaia/apps/settings/js/modules/async_storage.js
Executable file
|
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
* The promise version of async storage.
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var AsyncStorage = require('shared/async_storage');
|
||||
|
||||
return {
|
||||
getItem: function as_getItem(key) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
AsyncStorage.getItem(key, resolve);
|
||||
});
|
||||
},
|
||||
setItem: function as_setItem(key, value) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
AsyncStorage.setItem(key, value, resolve);
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
137
gaia/apps/settings/js/modules/base/dependency_graph.js
Executable file
137
gaia/apps/settings/js/modules/base/dependency_graph.js
Executable file
|
|
@ -0,0 +1,137 @@
|
|||
/**
|
||||
* DependencyGraph is a basic data structure that helps decide the evaluation
|
||||
* order of a certain node.
|
||||
*
|
||||
* @module modules/base/dependency_graph
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var Module = require('modules/base/module');
|
||||
|
||||
/**
|
||||
* @class DependencyGraph
|
||||
* @requires module:modules/base/module
|
||||
* @params {DependencyGraph} dpGraph
|
||||
* The newly created graph will be initialized using
|
||||
* dpGraph.
|
||||
* @returns {DependencyGraph}
|
||||
*/
|
||||
var DependencyGraph = Module.create(function DependencyGraph(dpGraph) {
|
||||
this._nodes = dpGraph ? JSON.parse(JSON.stringify(dpGraph._nodes)) : {};
|
||||
});
|
||||
|
||||
/**
|
||||
* Print the content of the nodes for debugging.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf DependencyGraph.prototype
|
||||
*/
|
||||
DependencyGraph.prototype._printNodes = function dg_printNodes() {
|
||||
Object.keys(this._nodes).forEach((name) => {
|
||||
var node = this._nodes[name];
|
||||
console.log('name: ' + name);
|
||||
console.log('children: ' +
|
||||
node.children.reduce((result, name) => {
|
||||
return result + ' ' + name;
|
||||
}, ''));
|
||||
console.log('dependent nodes: ' +
|
||||
node.dependentNodes.reduce((result, name) => {
|
||||
return result + ' ' + name;
|
||||
}, ''));
|
||||
console.log('======================');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Traverse the tree and add the dependent nodes to all nodes in the path.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf DependencyGraph.prototype
|
||||
* @param {Object} node
|
||||
* The node that we have new dependent nodes to add to.
|
||||
* @param {Array} dependentNodes
|
||||
* New dependent nodes to add.
|
||||
*/
|
||||
DependencyGraph.prototype._addDependentNodes =
|
||||
function dg_addDependentNodes(node, dependentNodes, traversedNodes) {
|
||||
traversedNodes = traversedNodes || [];
|
||||
if (traversedNodes.indexOf(node.name) >= 0) {
|
||||
traversedNodes.push(node.name);
|
||||
var error = traversedNodes.join(' -> ');
|
||||
this.throw('circular dependency detected! ' + error);
|
||||
}
|
||||
|
||||
if (dependentNodes) {
|
||||
// add all nodes in "dependendNodes" to the current node
|
||||
dependentNodes.forEach((name) => {
|
||||
if (node.dependentNodes.indexOf(name) < 0) {
|
||||
node.dependentNodes.push(name);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Nothing to add, prepare the nodes to be added to the children.
|
||||
dependentNodes = Array.prototype.slice.call(node.dependentNodes);
|
||||
}
|
||||
|
||||
dependentNodes.push(node.name);
|
||||
traversedNodes.push(node.name);
|
||||
node.children.forEach((name) => {
|
||||
this._addDependentNodes(this._nodes[name], dependentNodes,
|
||||
traversedNodes);
|
||||
});
|
||||
dependentNodes.pop();
|
||||
traversedNodes.pop();
|
||||
};
|
||||
|
||||
/**
|
||||
* Add dependency. Calling to this function adds a dependency from the first
|
||||
* parameter towards the second one.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf DependencyGraph.prototype
|
||||
* @param {String} name1
|
||||
* The name that depends on the other.
|
||||
* @param {String} name2
|
||||
* The name that is being depended on.
|
||||
*/
|
||||
DependencyGraph.prototype.addDependency = function dg_addDep(name1, name2) {
|
||||
var node1 = this._nodes[name1];
|
||||
var node2 = this._nodes[name2];
|
||||
|
||||
if (!node1) {
|
||||
node1 = this._nodes[name1] = {
|
||||
name: name1,
|
||||
children: [],
|
||||
dependentNodes: []
|
||||
};
|
||||
}
|
||||
|
||||
if (!node2) {
|
||||
node2 = this._nodes[name2] = {
|
||||
name: name2,
|
||||
children: [],
|
||||
dependentNodes: []
|
||||
};
|
||||
}
|
||||
|
||||
if (node1.children.indexOf(name2) < 0) {
|
||||
node1.children.push(name2);
|
||||
this._addDependentNodes(node1);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all dependent names of a name.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf DependencyGraph.prototype
|
||||
* @param {String} name
|
||||
*/
|
||||
DependencyGraph.prototype.getAllDependent = function dg_getAllDep(name) {
|
||||
var node = this._nodes[name];
|
||||
return node && node.dependentNodes;
|
||||
};
|
||||
|
||||
return DependencyGraph;
|
||||
});
|
||||
137
gaia/apps/settings/js/modules/base/event_emitter.js
Executable file
137
gaia/apps/settings/js/modules/base/event_emitter.js
Executable file
|
|
@ -0,0 +1,137 @@
|
|||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var Module = require('modules/base/module');
|
||||
|
||||
/**
|
||||
* EventEmitter implements basic event emitting logics. It also provides
|
||||
* function that augments the ability to other objects.
|
||||
*
|
||||
* @example
|
||||
* var eventEmitter = EventEmitter(['event1', 'event2']);
|
||||
* var eventHandler = function(event) {
|
||||
* console.log(event.detail);
|
||||
* };
|
||||
* var obj = {
|
||||
* handleEvent: function(event) {
|
||||
* console.log(event.detail);
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* eventEmitter.addEventListener('event1', eventHandler);
|
||||
* eventEmitter.addEventListener('event2', obj);
|
||||
*
|
||||
* eventEmitter._emitEvent('event1', 'value1'); // value1
|
||||
* eventEmitter._emitEvent('event2', 'value2'); // value2
|
||||
*
|
||||
* Extending from EventEmitter:
|
||||
* @example
|
||||
* var NewModule = Module.create(function() {
|
||||
* this.super(EventEmitter).call(this, ['event1', 'event2']);
|
||||
* }).extend(EventEmitter);
|
||||
*
|
||||
* var module = NewModule();
|
||||
*
|
||||
* @class EventEmitter
|
||||
* @returns {EventEmitter}
|
||||
*/
|
||||
var EventEmitter = Module.create(function EventEmitter(eventNames) {
|
||||
if (eventNames && eventNames.length) {
|
||||
this._eventListeners = eventNames.reduce((result, eventName) => {
|
||||
result[eventName] = [];
|
||||
return result;
|
||||
}, {});
|
||||
} else {
|
||||
this.throw('no valid registered events');
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Emit an event of a specific type and a given value.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf EventEmitter.prototype
|
||||
* @param {String} eventName
|
||||
* @param {Object} value
|
||||
*/
|
||||
EventEmitter.prototype._emitEvent = function(eventName, value) {
|
||||
var listeners = this._eventListeners[eventName];
|
||||
if (!listeners) {
|
||||
this.throw('invalid event name: ' + eventName);
|
||||
}
|
||||
|
||||
this.debug('_emitEvent:' + eventName + ' ' + value);
|
||||
var eventObj = {
|
||||
type: eventName,
|
||||
detail: value
|
||||
};
|
||||
listeners.forEach((listener) => {
|
||||
listener.call(this, eventObj);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Add an event listener on an event. If listener is an object,
|
||||
* object.handleEvent is called when the event emits.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf EventEmitter.prototype
|
||||
* @param {String} eventName
|
||||
* @param {(Function|Object)} listener
|
||||
*/
|
||||
EventEmitter.prototype.addEventListener = function(eventName, listener) {
|
||||
var listeners = this._eventListeners[eventName];
|
||||
if (listener && listeners) {
|
||||
switch (typeof listener) {
|
||||
case 'function':
|
||||
break;
|
||||
case 'object':
|
||||
if (typeof listener.handleEvent === 'function') {
|
||||
listener = listener.handleEvent;
|
||||
} else {
|
||||
listener = null;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
listener = null;
|
||||
break;
|
||||
}
|
||||
if (listener && listeners.indexOf(listener) < 0) {
|
||||
listeners.push(listener);
|
||||
}
|
||||
} else {
|
||||
this.error('addEventListener: invalid listener for ' + eventName);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove the event listener from an event.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf EventEmitter.prototype
|
||||
* @param {String} eventName
|
||||
* @param {(Function|Object)} listener
|
||||
*/
|
||||
EventEmitter.prototype.removeEventListener = function(eventName, listener) {
|
||||
var listeners = this._eventListeners[eventName];
|
||||
if (listener && listeners) {
|
||||
var index = -1;
|
||||
var type = typeof listener;
|
||||
switch (type) {
|
||||
case 'function':
|
||||
index = listeners.indexOf(listener);
|
||||
break;
|
||||
case 'object':
|
||||
index = listeners.indexOf(listener.handleEvent);
|
||||
break;
|
||||
}
|
||||
if (index >= 0) {
|
||||
listeners.splice(index, 1);
|
||||
}
|
||||
} else {
|
||||
this.error('removeEventListener: invalid listener for ' + eventName);
|
||||
}
|
||||
};
|
||||
|
||||
return EventEmitter;
|
||||
});
|
||||
152
gaia/apps/settings/js/modules/base/module.js
Executable file
152
gaia/apps/settings/js/modules/base/module.js
Executable file
|
|
@ -0,0 +1,152 @@
|
|||
/**
|
||||
* Module provides methods to create a new module. Any module created here has
|
||||
* the ability of being extended by other modules. Existing properties are
|
||||
* overridden.
|
||||
*
|
||||
* Creating a module
|
||||
* @example
|
||||
* var NewModule = Module.create(function() {
|
||||
* this.value = 100;
|
||||
* });
|
||||
* NewModule.prototype.print = function() { console.log(this.value); };
|
||||
*
|
||||
* var instance = NewModule();
|
||||
* instance.print(); // 100
|
||||
*
|
||||
* Extending a module
|
||||
* @example
|
||||
* var AnotherModule = Module.create();
|
||||
* AnotherModule.prototype.inc = function() { this.value++; };
|
||||
* var ExtendingModule = NewModule.extend(AnotherModule);
|
||||
*
|
||||
* var instance = ExtendingModule();
|
||||
* instance.inc();
|
||||
* instance.print(); // 101
|
||||
*
|
||||
* @module modules/base/module
|
||||
*/
|
||||
define(function() {
|
||||
'use strict';
|
||||
|
||||
const LOG_LEVEL = {
|
||||
NONE: 0,
|
||||
DEBUG: 1,
|
||||
INFO: 2,
|
||||
WARN: 3,
|
||||
ERROR: 4,
|
||||
ALL: 5
|
||||
};
|
||||
|
||||
var _constructorMap = (function() {
|
||||
var _ctorMap = new Map();
|
||||
var _getConstructor = function(Module) {
|
||||
return _ctorMap.get(Module) || function() {};
|
||||
};
|
||||
var _registerConstructor = function(Module, constructor) {
|
||||
_ctorMap.set(Module, constructor || function() {});
|
||||
};
|
||||
return {
|
||||
getConstructor: _getConstructor,
|
||||
registerConstructor: _registerConstructor
|
||||
};
|
||||
})();
|
||||
|
||||
var _emptyFunction = function() {};
|
||||
var _createLogger = function(name) {
|
||||
switch (name) {
|
||||
case 'DEBUG':
|
||||
return function(msg) {
|
||||
console.log(this._msgPrefix + msg);
|
||||
};
|
||||
case 'INFO':
|
||||
return function(msg) {
|
||||
console.info(this._msgPrefix + msg);
|
||||
};
|
||||
case 'WARN':
|
||||
return function(msg) {
|
||||
console.warn(this._msgPrefix + msg);
|
||||
};
|
||||
case 'ERROR':
|
||||
return function(msg) {
|
||||
console.error(this._msgPrefix + msg);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
var ModulePrototype = {
|
||||
get _msgPrefix() {
|
||||
return '[' + this.$name + ']: ';
|
||||
},
|
||||
debug: _emptyFunction,
|
||||
info: _emptyFunction,
|
||||
warn: _emptyFunction,
|
||||
error: _emptyFunction,
|
||||
throw: function(msg) {
|
||||
throw new Error(this._msgPrefix + msg);
|
||||
},
|
||||
set _logLevel(value) {
|
||||
Object.keys(LOG_LEVEL).forEach((name) => {
|
||||
var level = LOG_LEVEL[name];
|
||||
if (value >= level && value > 0) {
|
||||
this[name.toLowerCase()] = _createLogger(name);
|
||||
}
|
||||
});
|
||||
},
|
||||
super: _constructorMap.getConstructor
|
||||
};
|
||||
|
||||
var _extend = function() {
|
||||
switch (arguments.length) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
var Module = arguments[0];
|
||||
for (var prop in Module.prototype) {
|
||||
if (prop === '$name') {
|
||||
continue;
|
||||
}
|
||||
var pd = Object.getOwnPropertyDescriptor(Module.prototype, prop);
|
||||
if (pd) {
|
||||
Object.defineProperty(this.prototype, prop, pd);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Array.prototype.forEach.call(arguments, (Module) => {
|
||||
_extend.call(this, Module);
|
||||
});
|
||||
break;
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
// Returns a module function. The function returns an instance of the
|
||||
// module when it is called. The passed constructor is called using the
|
||||
// instance as "this".
|
||||
function _create(constructor) {
|
||||
if (constructor && typeof constructor !== 'function') {
|
||||
throw new Error('[Module]: Invalid constructor');
|
||||
}
|
||||
|
||||
var ModuleFunc = function() {};
|
||||
ModuleFunc.prototype = Object.create(ModulePrototype);
|
||||
ModuleFunc.prototype.$name = constructor && constructor.name || '';
|
||||
|
||||
var Module = function() {
|
||||
var instance = new ModuleFunc();
|
||||
if (constructor) {
|
||||
constructor.apply(instance, arguments);
|
||||
}
|
||||
return instance;
|
||||
};
|
||||
_constructorMap.registerConstructor(Module, constructor);
|
||||
Module.extend = _extend;
|
||||
Module.prototype = ModuleFunc.prototype;
|
||||
return Module;
|
||||
}
|
||||
|
||||
return {
|
||||
LOG_LEVEL: LOG_LEVEL,
|
||||
create: _create
|
||||
};
|
||||
});
|
||||
147
gaia/apps/settings/js/modules/base/task_scheduler.js
Executable file
147
gaia/apps/settings/js/modules/base/task_scheduler.js
Executable file
|
|
@ -0,0 +1,147 @@
|
|||
/**
|
||||
* TaskScheduler helps manage tasks and ensures they are executed in
|
||||
* sequential order. When a task of a certain type is enqueued, all pending
|
||||
* tasks of the same type in the queue are removed. This avoids redundant
|
||||
* queries and improves user perceived performance.
|
||||
*
|
||||
* @module modules/base/task_scheduler
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var Defer = require('modules/defer');
|
||||
var Module = require('modules/base/module');
|
||||
var Observable = require('modules/mvvm/observable');
|
||||
|
||||
/**
|
||||
* @class TaskScheduler
|
||||
* @returns {TaskScheduler}
|
||||
*/
|
||||
var TaskScheduler = Module.create(function TaskScheduler() {
|
||||
this.super(Observable).call(this);
|
||||
this.isLocked = false;
|
||||
this._tasks = [];
|
||||
}).extend(Observable);
|
||||
|
||||
Observable.defineObservableProperty(TaskScheduler.prototype, 'isLocked', {
|
||||
value: false
|
||||
});
|
||||
|
||||
const TASK_TYPE = {
|
||||
GENERAL: 0
|
||||
};
|
||||
|
||||
/**
|
||||
* We will keep some constants here as a static variable
|
||||
*
|
||||
* @access public
|
||||
* @memberOf TaskScheduler
|
||||
*/
|
||||
Object.defineProperty(TaskScheduler, 'TASK_TYPE', {
|
||||
enumerable: true,
|
||||
configurable: false,
|
||||
get: function() {
|
||||
return TASK_TYPE;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Change the internal state to make sure we are locked.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf TaskScheduler
|
||||
*/
|
||||
TaskScheduler.prototype._lock = function() {
|
||||
this.isLocked = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Change the internal state to unlocked and execute next task.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf TaskScheduler
|
||||
*/
|
||||
TaskScheduler.prototype._unlock = function() {
|
||||
this.isLocked = false;
|
||||
this._executeNextTask();
|
||||
};
|
||||
|
||||
/**
|
||||
* We will remove any redundant same-type tasks and keep the remaining ones
|
||||
* including the others which are not cancelable (general use task).
|
||||
*
|
||||
* @access private
|
||||
* @memberOf TaskScheduler
|
||||
* @param {String} type
|
||||
* @return {Array} array of tasks
|
||||
*/
|
||||
TaskScheduler.prototype._removeRedundantTasks = function(type) {
|
||||
return this._tasks.filter((task) => {
|
||||
if (!task.cancelable) {
|
||||
return true;
|
||||
} else {
|
||||
return task.type !== type;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Execute the next task when unlocked.
|
||||
*
|
||||
* @access private
|
||||
* @returns {Promise}
|
||||
* @memberOf TaskScheduler
|
||||
*/
|
||||
TaskScheduler.prototype._executeNextTask = function() {
|
||||
if (this.isLocked) {
|
||||
return;
|
||||
}
|
||||
var nextTask = this._tasks.shift();
|
||||
if (nextTask) {
|
||||
this._lock();
|
||||
return nextTask.func().then((result) => {
|
||||
nextTask.defer.resolve(result);
|
||||
this._unlock();
|
||||
}, () => {
|
||||
nextTask.defer.reject();
|
||||
this._unlock();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the only entry point for caller, and we will enqueue all tasks
|
||||
* one by one and execute them in order.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf TaskScheduler
|
||||
* @param {Object} task
|
||||
* @param {String} task.type
|
||||
* @param {Function} task.func - make sure the func would return a promise
|
||||
* @param {Boolean} task.cancelable
|
||||
* @return {Promise}
|
||||
*/
|
||||
TaskScheduler.prototype.enqueue = function(task) {
|
||||
var defer = Defer();
|
||||
|
||||
// if there is no func, let's directly resolve this task
|
||||
if (!task.func) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (!task.type) {
|
||||
// copy the task and make sure we won't change the previous one
|
||||
task = Object.assign({}, task);
|
||||
task.type = TaskScheduler.TASK_TYPE.GENERAL;
|
||||
}
|
||||
|
||||
task.defer = defer;
|
||||
this._tasks = this._removeRedundantTasks(task.type);
|
||||
this._tasks.push(task);
|
||||
this._executeNextTask();
|
||||
|
||||
return defer.promise;
|
||||
};
|
||||
|
||||
return TaskScheduler;
|
||||
});
|
||||
121
gaia/apps/settings/js/modules/battery.js
Executable file
121
gaia/apps/settings/js/modules/battery.js
Executable file
|
|
@ -0,0 +1,121 @@
|
|||
/**
|
||||
* Battery is an Observable that wraps the platform battery object. It has two
|
||||
* observable properties: level and state.
|
||||
*
|
||||
* @module modules/battery
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var NavigatorBattery = require('modules/navigator/battery');
|
||||
var Module = require('modules/base/module');
|
||||
var Observable = require('modules/mvvm/observable');
|
||||
|
||||
/**
|
||||
* @class Battery
|
||||
* @requires module:modules/navigator/battery
|
||||
* @requires module:modules/base/module
|
||||
* @requires module:modules/mvvm/observable
|
||||
* @returns {Battery}
|
||||
*/
|
||||
var Battery = Module.create(function Battery() {
|
||||
this.super(Observable).call(this);
|
||||
|
||||
this._level = this._getLevel();
|
||||
this._state = this._getState();
|
||||
this._chargingTime = this._getChargingTime();
|
||||
this._dischargingTime = this._getDischargingTime();
|
||||
NavigatorBattery.addEventListener('levelchange', () => {
|
||||
this._level = this._getLevel();
|
||||
});
|
||||
NavigatorBattery.addEventListener('chargingchange', () => {
|
||||
this._state = this._getState();
|
||||
this._chargingTime = this._getChargingTime();
|
||||
this._dischargingTime = this._getDischargingTime();
|
||||
});
|
||||
NavigatorBattery.addEventListener('chargingtimechange', () => {
|
||||
this._chargingTime = this._getChargingTime();
|
||||
});
|
||||
NavigatorBattery.addEventListener('dischargingtimechange', () => {
|
||||
this._dischargingTime = this._getDischargingTime();
|
||||
});
|
||||
}).extend(Observable);
|
||||
|
||||
/**
|
||||
* An observable property indicating the current battery level.
|
||||
*
|
||||
* @access public
|
||||
* @readonly
|
||||
* @memberOf Battery.prototype
|
||||
* @type {String}
|
||||
*/
|
||||
Observable.defineObservableProperty(Battery.prototype,
|
||||
'level', {
|
||||
readonly: true,
|
||||
value: ''
|
||||
});
|
||||
|
||||
/**
|
||||
* An observable property indicating the current battery state.
|
||||
*
|
||||
* @access public
|
||||
* @readonly
|
||||
* @memberOf Battery.prototype
|
||||
* @type {String}
|
||||
*/
|
||||
Observable.defineObservableProperty(Battery.prototype,
|
||||
'state', {
|
||||
readonly: true,
|
||||
value: ''
|
||||
});
|
||||
|
||||
/**
|
||||
* An observable property indicating the current charging time.
|
||||
*
|
||||
* @access public
|
||||
* @readonly
|
||||
* @memberOf Battery.prototype
|
||||
* @type {String}
|
||||
*/
|
||||
Observable.defineObservableProperty(Battery.prototype,
|
||||
'chargingTime', {
|
||||
readonly: true,
|
||||
value: ''
|
||||
});
|
||||
|
||||
/**
|
||||
* An observable property indicating the current discharging time.
|
||||
*
|
||||
* @access public
|
||||
* @readonly
|
||||
* @memberOf Battery.prototype
|
||||
* @type {String}
|
||||
*/
|
||||
Observable.defineObservableProperty(Battery.prototype,
|
||||
'dischargingTime', {
|
||||
readonly: true,
|
||||
value: ''
|
||||
});
|
||||
|
||||
Battery.prototype._getLevel = function() {
|
||||
return Math.min(100, Math.round(NavigatorBattery.level * 100));
|
||||
};
|
||||
|
||||
Battery.prototype._getState = function() {
|
||||
if (NavigatorBattery.charging) {
|
||||
return (this._getLevel() == 100) ? 'charged' : 'charging';
|
||||
} else {
|
||||
return 'unplugged';
|
||||
}
|
||||
};
|
||||
|
||||
Battery.prototype._getChargingTime = function() {
|
||||
return NavigatorBattery.chargingTime;
|
||||
};
|
||||
|
||||
Battery.prototype._getDischargingTime = function() {
|
||||
return NavigatorBattery.dischargingTime;
|
||||
};
|
||||
|
||||
return Battery();
|
||||
});
|
||||
111
gaia/apps/settings/js/modules/bluetooth/bluetooth_adapter_manager.js
Executable file
111
gaia/apps/settings/js/modules/bluetooth/bluetooth_adapter_manager.js
Executable file
|
|
@ -0,0 +1,111 @@
|
|||
/**
|
||||
* BluetoothAdapterManager:
|
||||
* - BluetoothAdapterManager is an Observable that wraps the platform
|
||||
* Bluetooth object for construct default adapter.
|
||||
* - It has only one observable properties: defaultAdapter
|
||||
* BluetoothAdapterManager only update state and does not involve in any UI
|
||||
* logic.
|
||||
*
|
||||
* @module BluetoothAdapterManager
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var NavigatorBluetooth = require('modules/navigator/mozBluetooth');
|
||||
var Observable = require('modules/mvvm/observable');
|
||||
|
||||
var _debug = false;
|
||||
var debug = function() {};
|
||||
if (_debug) {
|
||||
debug = function btam_debug(msg) {
|
||||
console.log('--> [BluetoothAdapterManager]: ' + msg);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @alias module:modules/bluetooth/BluetoothAdapterManager
|
||||
* @requires module:modules/navigator/mozBluetooth
|
||||
* @requires module:modules/mvvm/observable
|
||||
* @return {BluetoothAdapterManager}
|
||||
*/
|
||||
var BluetoothAdapterManager = {
|
||||
/**
|
||||
* The default adapter used to connect to the remote bluetooth devices.
|
||||
*
|
||||
* @readonly
|
||||
* @memberOf BluetoothAdapterManager
|
||||
* @type {Object}
|
||||
*/
|
||||
defaultAdapter: null,
|
||||
|
||||
/**
|
||||
* Init Bluetooth module.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothAdapterManager
|
||||
*/
|
||||
_init: function btam__init() {
|
||||
// Early return while there is no navigator.mozBluetooth module.
|
||||
if (!NavigatorBluetooth) {
|
||||
return;
|
||||
}
|
||||
|
||||
// watch BluetoothManager event
|
||||
this._watchMozBluetoothAttributechanged();
|
||||
|
||||
// init default adapter
|
||||
this._initDefaultAdapter();
|
||||
},
|
||||
|
||||
/**
|
||||
* Watch 'attributechanged' event from mozBluetooth for updating default
|
||||
* adapter information.
|
||||
*
|
||||
* 'attributechanged' event description:
|
||||
* A handler to trigger when bluetooth manager's only property
|
||||
* defaultAdapter has changed.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothAdapterManager
|
||||
*/
|
||||
_watchMozBluetoothAttributechanged:
|
||||
function btam__watchMozBluetoothAttributechanged() {
|
||||
NavigatorBluetooth.addEventListener('attributechanged', (evt) => {
|
||||
for (var i in evt.attrs) {
|
||||
switch (evt.attrs[i]) {
|
||||
case 'defaultAdapter':
|
||||
// Default adapter attribute change.
|
||||
// Usually, it means that we reach new default adapter.
|
||||
this.defaultAdapter = NavigatorBluetooth.defaultAdapter;
|
||||
debug('_watchMozBluetoothAttributechanged(): ' +
|
||||
'this.defaultAdapter = ' + this.defaultAdapter);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/* Reach default adapter from platform Bluetooth.
|
||||
*
|
||||
* If the property is accessed on platform in the first time, it will
|
||||
* trigger Gecko::Bluetooth to get default adapter from the chipset.
|
||||
* So the value would be null.
|
||||
* Then we will receive 'attributechanged' event later.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothAdapterManager
|
||||
*/
|
||||
_initDefaultAdapter: function btam__initDefaultAdapter() {
|
||||
this.defaultAdapter = NavigatorBluetooth.defaultAdapter;
|
||||
debug('_initDefaultAdapter(): this.defaultAdapter = ' +
|
||||
this.defaultAdapter);
|
||||
}
|
||||
};
|
||||
|
||||
// Create the observable object using the prototype.
|
||||
var bluetoothAdapterManager = Observable(BluetoothAdapterManager);
|
||||
bluetoothAdapterManager._init();
|
||||
return bluetoothAdapterManager;
|
||||
});
|
||||
198
gaia/apps/settings/js/modules/bluetooth/bluetooth_cod_mapper.js
Executable file
198
gaia/apps/settings/js/modules/bluetooth/bluetooth_cod_mapper.js
Executable file
|
|
@ -0,0 +1,198 @@
|
|||
/**
|
||||
* BluetoothClassOfDeviceMapper:
|
||||
* - BluetoothClassOfDeviceMapper is a mapper that helps settings/bluetooth
|
||||
* apps to decide the icon type. Since platform exposed the 'number' type
|
||||
* for class of device(Major Service Classes, Major Device Classes,
|
||||
* Minor Device Classes), the helper will map the input number to specific
|
||||
* device type. And the decision is referenced from Baseband.
|
||||
* (Assigned numbers for Baseband identifies the Inquiry Access codes
|
||||
* and Class of Device/Service (CoD) fields.)[3]
|
||||
* According to the specifications described, we define the device type in
|
||||
* Gaia side entirely. And the naming of device type we given is also
|
||||
* mapping to CSS style sheet.
|
||||
*
|
||||
* - [1] Bluetooth API v1, BluetoothDevice icon property:
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/BluetoothDevice.icon
|
||||
* - [2] Bluetooth API v2, BluetoothClassOfDevice object:
|
||||
* https://wiki.mozilla.org/B2G/Bluetooth/WebBluetooth-v2/
|
||||
* BluetoothClassOfDevice
|
||||
* - [3] Bluetooth Class of Device Reference:
|
||||
* https://www.bluetooth.org/en-us/specification/assigned-numbers/baseband
|
||||
* - We can update the mapper if the specifications Baseband is updated.
|
||||
*
|
||||
* @module BluetoothClassOfDeviceMapper
|
||||
*/
|
||||
define(function() {
|
||||
'use strict';
|
||||
|
||||
var _debug = false;
|
||||
var debug = function() {};
|
||||
if (_debug) {
|
||||
debug = function btcodm_debug(msg) {
|
||||
console.log('--> [BluetoothCODMapper]: ' + msg);
|
||||
};
|
||||
}
|
||||
|
||||
var MinorDeviceClasses = {
|
||||
Computer: {
|
||||
0: 'computer', // Uncategorized, code for device not assigned
|
||||
1: 'computer', // Desktop workstation
|
||||
2: 'computer', // Server-class computer
|
||||
3: 'computer', // Laptop
|
||||
4: 'pda', // Handheld PC/PDA (clamshell)
|
||||
5: 'pda', // Palm-size PC/PDA
|
||||
6: 'computer', // Wearable computer (watch size)
|
||||
7: 'computer', // Tablet
|
||||
reserved: 'computer' // All other values reserved. Default type
|
||||
// 'computer' given for reserved case.
|
||||
},
|
||||
|
||||
Phone: {
|
||||
0: 'phone', // Uncategorized, code for device not assigned
|
||||
1: 'phone', // Cellular
|
||||
2: 'phone', // Cordless
|
||||
3: 'phone', // Smartphone
|
||||
4: 'modem', // Wired modem or voice gateway
|
||||
5: 'phone', // Common ISDN access
|
||||
reserved: 'phone' // All other values reserved. Default type
|
||||
// 'phone' given for reserved case.
|
||||
},
|
||||
|
||||
AudioVideo: {
|
||||
0: 'audio-card', // Uncategorized, code not assigned
|
||||
1: 'audio-card', // Wearable Headset Device
|
||||
2: 'audio-card', // Hands-free Device
|
||||
4: 'audio-input-microphone', // Microphone
|
||||
5: 'audio-card', // Loudspeaker
|
||||
6: 'audio-card', // Headphones
|
||||
7: 'audio-card', // Portable Audio
|
||||
8: 'audio-card', // Car audio
|
||||
9: 'audio-card', // Set-top box
|
||||
10: 'audio-card', // HiFi Audio Device
|
||||
11: 'camera-video', // VCR
|
||||
12: 'camera-video', // Video Camera
|
||||
13: 'camera-video', // Camcorder
|
||||
14: 'video-display', // Video Monitor
|
||||
15: 'video-display', // Video Display and Loudspeaker
|
||||
16: 'video-display', // Video Conferencing
|
||||
18: 'audio-card', // Gameing/Toy
|
||||
reserved: 'audio-card' // All other values reserved. Default type
|
||||
// 'audio-card' given for reserved case.
|
||||
},
|
||||
|
||||
Peripheral: {
|
||||
1: 'input-gaming', // Joystick
|
||||
2: 'input-gaming', // Gamepad
|
||||
16: 'input-keyboard', // Keyboard
|
||||
17: 'input-keyboard', // Keyboard
|
||||
18: 'input-keyboard', // Keyboard
|
||||
19: 'input-keyboard', // Keyboard
|
||||
20: 'input-keyboard', // Keyboard
|
||||
21: 'input-keyboard', // Keyboard
|
||||
22: 'input-keyboard', // Keyboard
|
||||
23: 'input-keyboard', // Keyboard
|
||||
24: 'input-keyboard', // Keyboard
|
||||
25: 'input-keyboard', // Keyboard
|
||||
26: 'input-keyboard', // Keyboard
|
||||
27: 'input-keyboard', // Keyboard
|
||||
28: 'input-keyboard', // Keyboard
|
||||
29: 'input-keyboard', // Keyboard
|
||||
30: 'input-keyboard', // Keyboard
|
||||
31: 'input-keyboard', // Keyboard
|
||||
32: 'input-mouse', // Pointing device
|
||||
33: 'input-mouse', // Pointing device
|
||||
34: 'input-mouse', // Pointing device
|
||||
35: 'input-mouse', // Pointing device
|
||||
36: 'input-mouse', // Pointing device
|
||||
37: 'input-tablet', // Digitizer tablet
|
||||
38: 'input-mouse', // Pointing device
|
||||
39: 'input-mouse', // Pointing device
|
||||
40: 'input-mouse', // Pointing device
|
||||
41: 'input-mouse', // Pointing device
|
||||
42: 'input-mouse', // Pointing device
|
||||
43: 'input-mouse', // Pointing device
|
||||
44: 'input-mouse', // Pointing device
|
||||
45: 'input-mouse', // Pointing device
|
||||
46: 'input-mouse', // Pointing device
|
||||
47: 'input-mouse' // Pointing device
|
||||
},
|
||||
|
||||
Imaging: {
|
||||
4: 'video-display', // Display, bit: XXX1XX
|
||||
8: 'camera-photo', // Camera, bit: XX1XXX
|
||||
16: 'scanner', // Scanner, bit: X1XXXX
|
||||
32: 'printer' // Printer, bit: 1XXXXX
|
||||
}
|
||||
};
|
||||
|
||||
var MajorDeviceClasses = {
|
||||
1: MinorDeviceClasses.Computer,
|
||||
2: MinorDeviceClasses.Phone,
|
||||
3: 'network-wireless', // LAN/Network Access Point Major Class
|
||||
4: MinorDeviceClasses.AudioVideo,
|
||||
5: MinorDeviceClasses.Peripheral,
|
||||
6: MinorDeviceClasses.Imaging
|
||||
};
|
||||
|
||||
var BluetoothClassOfDeviceMapper = {
|
||||
/**
|
||||
* The string indicating which kind of icon could be used to represent
|
||||
* the device.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf BluetoothClassOfDeviceMapper
|
||||
* @param {BluetoothClassOfDevice} cod
|
||||
* @return {String}
|
||||
*/
|
||||
getDeviceType: function btcodm_getDeviceType(cod) {
|
||||
debug('cod.majorDeviceClass = ' + cod.majorDeviceClass);
|
||||
debug('cod.majorServiceClass = ' + cod.majorServiceClass);
|
||||
debug('cod.minorDeviceClass = ' + cod.minorDeviceClass);
|
||||
// Given an empty string to be default type.
|
||||
// Then, we won't show any icon for empty type.
|
||||
var deviceType = '';
|
||||
var majorDeviceClass = MajorDeviceClasses[cod.majorDeviceClass];
|
||||
debug('majorDeviceClass = ' + majorDeviceClass);
|
||||
if (typeof(majorDeviceClass) === 'object') {
|
||||
// drop in other Major Class
|
||||
deviceType = majorDeviceClass[cod.minorDeviceClass] ||
|
||||
majorDeviceClass.reserved || '';
|
||||
debug('return in minor class, type = ' + deviceType);
|
||||
return deviceType;
|
||||
} else if (typeof(majorDeviceClass) === 'string') {
|
||||
// drop in LAN/Network Access Point Major Class
|
||||
debug('return in major class, type = network-wireless');
|
||||
return majorDeviceClass;
|
||||
} else if (cod.majorServiceClass & 0x100) {
|
||||
// Not in any Major Device Class which is defined in Gaia.
|
||||
// Ex: Wearable, Toy, Health.
|
||||
|
||||
// Since there is no icon to display wearable, toy, health devices,
|
||||
// file a bug 1163479[2] to define them for specification needed.
|
||||
// But some of these devices probably service 'Audio' per Bluetooth
|
||||
// specification[1].
|
||||
// Property 'type' may be missed due to CoD of major class is TOY(0x08).
|
||||
// But we need to assign 'type' as 'audio-card' if service class
|
||||
// is 'Audio'. This is for PTS test case TC_AG_COD_BV_02_I.
|
||||
// As HFP specification defines that service class is 'Audio' can
|
||||
// be considered as HFP HF.
|
||||
// [1]: HFP_SPEC_V16.pdf: A device implementing the HF role of HFP shall
|
||||
// set the "Audio" bit in the Service Class field.
|
||||
// [2]: Bug 1163479 - [Gaia][Bluetooth] Device icon definition for
|
||||
// wearable, toy, health(Major Device Class) devices.
|
||||
// (https://bugzilla.mozilla.org/show_bug.cgi?id=1163479)
|
||||
|
||||
// Major Service Class: Audio(Speaker, Microphone, Headset service, ...)
|
||||
deviceType = 'audio-card';
|
||||
debug('return in major service class, type = audio-card');
|
||||
return deviceType;
|
||||
} else {
|
||||
// Not in any Class which is defined in Gaia.
|
||||
debug('not mapping in any class, return type = ' + deviceType);
|
||||
return deviceType;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return BluetoothClassOfDeviceMapper;
|
||||
});
|
||||
953
gaia/apps/settings/js/modules/bluetooth/bluetooth_connection_manager.js
Executable file
953
gaia/apps/settings/js/modules/bluetooth/bluetooth_connection_manager.js
Executable file
|
|
@ -0,0 +1,953 @@
|
|||
/**
|
||||
* BluetoothConnectionManager:
|
||||
* BluetoothConnectionManager only update state and does not involve in any UI
|
||||
* logic.
|
||||
*
|
||||
* @module BluetoothConnectionManager
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var AdapterManager = require('modules/bluetooth/bluetooth_adapter_manager');
|
||||
var AsyncStorage = require('shared/async_storage');
|
||||
|
||||
var _debug = false;
|
||||
var debug = function() {};
|
||||
if (_debug) {
|
||||
debug = function btam_debug(msg) {
|
||||
console.log('--> [BluetoothConnectionManager]: ' + msg);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @alias module:modules/bluetooth/BluetoothConnectionManager
|
||||
* @requires module:modules/bluetooth/bluetooth_adapter_manager
|
||||
* @requires module:shared/async_storage
|
||||
* @return {BluetoothConnectionManager}
|
||||
*/
|
||||
var BluetoothConnectionManager = {
|
||||
/**
|
||||
* The profiles of connected device that we are defined here.
|
||||
*
|
||||
* @public
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @type {Object}
|
||||
*/
|
||||
Profiles: {
|
||||
'hfp': 0x111E, // Handsfree
|
||||
'a2dp': 0x110D // Advanced Audio Distribution Devices
|
||||
},
|
||||
|
||||
/**
|
||||
* The address of device that we are trying to connect.
|
||||
*
|
||||
* @public
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @type {String}
|
||||
*/
|
||||
connectingAddress: null,
|
||||
|
||||
/**
|
||||
* A object that we cache the connected devices information(address, device,
|
||||
* connectedProfiles). It will be inited while default adapter is ready.
|
||||
* And these information is coming from profile events.
|
||||
* Each connected device is hashed by device address.
|
||||
*
|
||||
* EX:
|
||||
* _connectedDevicesInfo = {
|
||||
* 'AA:BB:CC:00:11:22': {
|
||||
* 'device': DeviceObject,
|
||||
* 'connectedProfiles': {
|
||||
* 'hfp': true,
|
||||
* 'a2dp': false
|
||||
* }
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* @private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @type {Object}
|
||||
*/
|
||||
_connectedDevicesInfo: {},
|
||||
|
||||
/**
|
||||
* An instance to maintain that we have created a promise to get connected
|
||||
* devices.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @return {Promise}
|
||||
*/
|
||||
_getConnectedDevicesPromise: null,
|
||||
|
||||
/**
|
||||
* The object maintains listeners' callback per property name.
|
||||
* Each listener would be called as following definition.
|
||||
* 'connecting' - be called when device is connecting.
|
||||
* 'connected': - be called when device is connected.
|
||||
* 'disconnected': - be called when device is disconnected.
|
||||
* 'profileChanged': - be called when profile is changed.
|
||||
*
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @type {Object}
|
||||
*/
|
||||
_listeners: {
|
||||
'connecting': [],
|
||||
'connected': [],
|
||||
'disconnected': [],
|
||||
'profileChanged': []
|
||||
},
|
||||
|
||||
/**
|
||||
* Default adapter of Bluetooth.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @type {BluetoothAdapter}
|
||||
*/
|
||||
_defaultAdapter: null,
|
||||
|
||||
/**
|
||||
* Init BluetoothConnectionManager module.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
*/
|
||||
_init: function btcm__init() {
|
||||
// Observe 'defaultAdapter' property for reaching default adapter.
|
||||
AdapterManager.observe('defaultAdapter',
|
||||
this._onDefaultAdapterChanged.bind(this));
|
||||
this._onDefaultAdapterChanged(AdapterManager.defaultAdapter);
|
||||
},
|
||||
|
||||
/**
|
||||
* 'defaultAdapter' change event handler from adapter manager for
|
||||
* updating adapter immediately.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {BluetoothAdapter} newAdapter
|
||||
* @param {BluetoothAdapter} oldAdapter
|
||||
*/
|
||||
_onDefaultAdapterChanged:
|
||||
function btcm__onDefaultAdapterChanged(newAdapter, oldAdapter) {
|
||||
debug('_onDefaultAdapterChanged(): newAdapter = ' + newAdapter);
|
||||
debug('_onDefaultAdapterChanged(): oldAdapter = ' + oldAdapter);
|
||||
|
||||
// save default adapter
|
||||
this._defaultAdapter = newAdapter;
|
||||
|
||||
if (oldAdapter) {
|
||||
// unwatch event since the old adapter is no longer usefull
|
||||
this._unwatchProfilesStatuschanged(oldAdapter);
|
||||
this._unwatchDefaultAdapterOnattributechanged(oldAdapter);
|
||||
}
|
||||
|
||||
if (newAdapter) {
|
||||
// watch event since the new adapter is ready to access
|
||||
this._watchProfilesStatuschanged(newAdapter);
|
||||
this._watchDefaultAdapterOnattributechanged(newAdapter);
|
||||
// restore connection
|
||||
if (newAdapter.state === 'enabled') {
|
||||
this._restoreConnection();
|
||||
}
|
||||
} else {
|
||||
// reset properties only
|
||||
this._resetConnectionInfo();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the cache of connected devices in ConnectionManager.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @return {Promise}
|
||||
*/
|
||||
getConnectedDevices: function btcm_getConnectedDevices() {
|
||||
if (!this._getConnectedDevicesPromise) {
|
||||
this._getConnectedDevicesPromise =
|
||||
this._initConnectedDevicesInfo().then(() => {
|
||||
debug('getConnectedDevices(): resolved with latest cache = ' +
|
||||
JSON.stringify(this._connectedDevicesInfo));
|
||||
return this._connectedDevicesInfo;
|
||||
}, (reason) => {
|
||||
debug('getConnectedDevices(): rejected with reason = ' + reason);
|
||||
this._getConnectedDevicesPromise = null;
|
||||
});
|
||||
}
|
||||
return this._getConnectedDevicesPromise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Init cache of connected device and save it in cache.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @return {Promise}
|
||||
*/
|
||||
_initConnectedDevicesInfo: function btcm__initConnectedDevicesInfo() {
|
||||
if (!this._defaultAdapter) {
|
||||
return Promise.reject('default adapter is not existed!!');
|
||||
}
|
||||
// Init connection status and profile from platform.
|
||||
// Then, save these connected device information in cache.
|
||||
return this._getConnectedDevicesFromPlatform().then(
|
||||
(connectedDevicesByProfile) => {
|
||||
this._constructDeviceItemsMap(connectedDevicesByProfile);
|
||||
}, (reason) => {
|
||||
debug('_initConnectedDevicesInfo(): rejected in ' +
|
||||
'_getConnectedDevicesFromPlatform');
|
||||
return Promise.reject(reason);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* The method will update each device item in maintaining map.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {Object} connectedDevices
|
||||
*/
|
||||
_constructDeviceItemsMap:
|
||||
function btcm__constructDeviceItemsMap(connectedDevices) {
|
||||
debug('_constructDeviceItemsMap(): connectedDevices = ' +
|
||||
JSON.stringify(connectedDevices));
|
||||
if (!connectedDevices) {
|
||||
// Return empty object while there is no any connected devices.
|
||||
debug('_constructDeviceItemsMap(): early return with empty object');
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(this.Profiles).map((profileID) => {
|
||||
connectedDevices[profileID].forEach((connectedDevice) => {
|
||||
var connectionDeviceInfo = {
|
||||
address: connectedDevice.address,
|
||||
connected: true,
|
||||
profileID: profileID,
|
||||
device: connectedDevice
|
||||
};
|
||||
debug('_constructDeviceItemsMap(): connectionDeviceInfo = ' +
|
||||
JSON.stringify(connectionDeviceInfo));
|
||||
this._initConnectedDevicesCache(connectionDeviceInfo);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Init the cache which is maintained for connection devices.
|
||||
* And the input is gotten from platform API adapter.getConnectedDevices().
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {Object} options
|
||||
* @param {String} options.address - address of the device
|
||||
* @param {Boolean} options.connected - is connected or not
|
||||
* @param {Object} options.profileID - profile ID of the connection type
|
||||
* @param {Object} options.device - connect device, Bluetooth Object
|
||||
*/
|
||||
_initConnectedDevicesCache:
|
||||
function btcm__initConnectedDevicesCache(options) {
|
||||
debug('_initConnectedDevicesCache(): options = ' +
|
||||
JSON.stringify(options));
|
||||
// hash by device address
|
||||
var info = this._connectedDevicesInfo[options.address];
|
||||
if (info) {
|
||||
// Already have profiles, update it for other profile.
|
||||
info.connectedProfiles[options.profileID] = options.connected;
|
||||
} else {
|
||||
// Not have profiles yet, create for it.
|
||||
// If options.device is existed, save the connected device.
|
||||
// Otherwise, given null in this property.
|
||||
var connectedDevice = (options.device) ? options.device : null;
|
||||
info = {
|
||||
'device': connectedDevice,
|
||||
'connectedProfiles': {}
|
||||
};
|
||||
info.connectedProfiles[options.profileID] = options.connected;
|
||||
}
|
||||
// Save the device/profile in map.
|
||||
this._connectedDevicesInfo[options.address] = info;
|
||||
|
||||
// If there is no profile connected,
|
||||
// remove the device item from cache since it is already disconnected.
|
||||
var dataToCheckConnectedProfile = {
|
||||
address: options.address,
|
||||
connectedDevices: this._connectedDevicesInfo
|
||||
};
|
||||
if (!this._hasConnectedProfileByAddress(dataToCheckConnectedProfile)) {
|
||||
delete this._connectedDevicesInfo[options.address];
|
||||
}
|
||||
// Return the latest cache which is just updated here.
|
||||
debug('_initConnectedDevicesCache(): this._connectedDevicesInfo = ' +
|
||||
JSON.stringify(this._connectedDevicesInfo));
|
||||
return this._connectedDevicesInfo;
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the cache which is maintained for connection devices.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {Object} options
|
||||
* @param {String} options.address - address of the device
|
||||
* @param {Boolean} options.connected - is connected or not
|
||||
* @param {Object} options.profileID - profile ID of the connection type
|
||||
* @param {Object} options.device - connect device, Bluetooth Object
|
||||
*/
|
||||
_updateConnectedDevices:
|
||||
function btcm__updateConnectedDevices(options) {
|
||||
return this.getConnectedDevices().then((connectedDevicesInfo) => {
|
||||
debug('_updateConnectedDevices(): connectedDevicesInfo = ' +
|
||||
JSON.stringify(connectedDevicesInfo));
|
||||
|
||||
// hash by device address
|
||||
var info =
|
||||
(connectedDevicesInfo) ? connectedDevicesInfo[options.address] : null;
|
||||
if (info) {
|
||||
// Already have profiles, update it for other profile.
|
||||
info.connectedProfiles[options.profileID] = options.connected;
|
||||
} else {
|
||||
// Not have profiles yet, create for it.
|
||||
// If options.device is existed, save the connected device.
|
||||
// Otherwise, given null in this property.
|
||||
var connectedDevice = (options.device) ? options.device : null;
|
||||
info = {
|
||||
'device': connectedDevice,
|
||||
'connectedProfiles': {}
|
||||
};
|
||||
info.connectedProfiles[options.profileID] = options.connected;
|
||||
}
|
||||
// Save the device/profile in map.
|
||||
this._connectedDevicesInfo[options.address] = info;
|
||||
|
||||
// If there is no profile connected,
|
||||
// remove the device item from cache since it is already disconnected.
|
||||
var dataToCheckConnectedProfile = {
|
||||
address: options.address,
|
||||
connectedDevices: this._connectedDevicesInfo
|
||||
};
|
||||
if (!this._hasConnectedProfileByAddress(dataToCheckConnectedProfile)) {
|
||||
delete this._connectedDevicesInfo[options.address];
|
||||
}
|
||||
// Return the latest cache which is just updated here.
|
||||
debug('_updateConnectedDevices(): this._connectedDevicesInfo = ' +
|
||||
JSON.stringify(this._connectedDevicesInfo));
|
||||
return Promise.resolve(this._connectedDevicesInfo);
|
||||
}, () => {
|
||||
debug('_updateConnectedDevices(): rejected with some exception');
|
||||
return Promise.reject('rejected with some exception');
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Only reset properties since there is no available default adapter.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
*/
|
||||
_resetConnectionInfo: function btcm__resetConnectionInfo() {
|
||||
// Reset connection status.
|
||||
this.connectingAddress = null;
|
||||
// Clean up the instance to get connected devices
|
||||
// while new adapter is ready.
|
||||
this._getConnectedDevicesPromise = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Watch 'onattributechanged' event from default adapter for watching
|
||||
* adapter enabled/disabled status.
|
||||
*
|
||||
* Description of 'onattributechanged' event:
|
||||
* A handler to trigger when one of the local bluetooth adapter's properties
|
||||
* has changed. Note access to the changed property in this event handler
|
||||
* would get the updated value.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {BluetoothAdapter} adapter
|
||||
*/
|
||||
_watchDefaultAdapterOnattributechanged:
|
||||
function btcm__watchDefaultAdapterOnattributechanged(adapter) {
|
||||
adapter.addEventListener('attributechanged',
|
||||
this._onAdapterAttributeChanged.bind(this, adapter));
|
||||
},
|
||||
|
||||
/**
|
||||
* Unwatch 'onattributechanged' event from default adapter since adapter is
|
||||
* removed.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {BluetoothAdapter} adapter
|
||||
*/
|
||||
_unwatchDefaultAdapterOnattributechanged:
|
||||
function btcm__unwatchDefaultAdapterOnattributechanged(adapter) {
|
||||
adapter.removeEventListener('attributechanged',
|
||||
this._onAdapterAttributeChanged);
|
||||
},
|
||||
|
||||
/**
|
||||
* 'onattributechanged' event handler from default adapter for reaching
|
||||
* adapter enabled/disabled status.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {BluetoothAdapter} adapter
|
||||
* @param {event} evt
|
||||
*/
|
||||
_onAdapterAttributeChanged:
|
||||
function btcm__onAdapterAttributeChanged(adapter, evt) {
|
||||
for (var i in evt.attrs) {
|
||||
debug('_onAdapterAttributeChanged(): ' + evt.attrs[i]);
|
||||
switch (evt.attrs[i]) {
|
||||
case 'state':
|
||||
if (adapter.state === 'enabled') {
|
||||
// Restore connection while default adapter state is enabled.
|
||||
this._restoreConnection();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Watch every of profile events('onhfpstatuschanged','ona2dpstatuschanged')
|
||||
* from default adapter for updating device connected status immediately.
|
||||
*
|
||||
* Description of 'onhfpstatuschanged' event:
|
||||
* Specifies an event listener to receive hfpstatuschanged events.
|
||||
* Those events occur when an HFP connection status changes.
|
||||
*
|
||||
* Description of 'ona2dpstatuschanged' event:
|
||||
* Specifies an event listener to receive a2dpstatuschanged events.
|
||||
* Those events occur when an A2DP connection status changes.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {BluetoothAdapter} adapter
|
||||
*/
|
||||
_watchProfilesStatuschanged:
|
||||
function btcm__watchProfilesStatuschanged(adapter) {
|
||||
var eventName;
|
||||
for (var profileID in this.Profiles) {
|
||||
eventName = 'on' + profileID + 'statuschanged';
|
||||
adapter[eventName] =
|
||||
this._onProfileStatuschangeHandler.bind(this, profileID);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Unwatch every of profile events('onhfpstatuschanged',
|
||||
* 'ona2dpstatuschanged') from default adapter since adapter is removed.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {BluetoothAdapter} adapter
|
||||
*/
|
||||
_unwatchProfilesStatuschanged:
|
||||
function btc__unwatchProfilesStatuschanged(adapter) {
|
||||
var eventName;
|
||||
for (var profileID in this.Profiles) {
|
||||
eventName = 'on' + profileID + 'statuschanged';
|
||||
adapter[eventName] = null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 'onhfpstatuschanged', 'ona2dpstatuschanged' events handler from
|
||||
* default adapter for updating device connected status.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {String} profileID
|
||||
* @param {event} evt
|
||||
*/
|
||||
_onProfileStatuschangeHandler:
|
||||
function btcm__onProfileStatuschangeHandler(profileID, evt) {
|
||||
debug('_onProfileStatuschangeHandler(): profileID = ' + profileID +
|
||||
', evt.address = ' + evt.address + ', evt.status = ' + evt.status);
|
||||
var options = {
|
||||
address: evt.address,
|
||||
connected: evt.status,
|
||||
profileID: profileID
|
||||
};
|
||||
// Update connection status.
|
||||
this._updateConnectionStatus(options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Query async storage to restore connection.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
*/
|
||||
_restoreConnection: function btcm__restoreConnection() {
|
||||
if (!this._defaultAdapter) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reconnect the one kept in the async storage.
|
||||
AsyncStorage.getItem('device.connected', (address) => {
|
||||
if (!address) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the restore device is already connected or not.
|
||||
this.getConnectedDevices().then((connectedDevices) => {
|
||||
if (connectedDevices && connectedDevices[address]) {
|
||||
// Do an early return since the restore device is connected already.
|
||||
debug('_restoreConnection(): early return cause connected already');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the device which is wanted to connect from paired devices.
|
||||
var restoreDevice = this._getPairedDeviceByAddress(address);
|
||||
if (restoreDevice) {
|
||||
this.connectingAddress = restoreDevice.address;
|
||||
// Fire 'connecting' event.
|
||||
var event = {
|
||||
type: 'connecting',
|
||||
detail: {
|
||||
address: restoreDevice.address
|
||||
}
|
||||
};
|
||||
this._emitEvent(event);
|
||||
this._connect(restoreDevice).then(() => {
|
||||
debug('_restoreConnection(): restore connection successfully');
|
||||
}, (reason) => {
|
||||
debug('_restoreConnection(): restore connection failed, ' +
|
||||
'reason = ' + reason);
|
||||
// No available profiles are connected. Reset connecting address.
|
||||
this.connectingAddress = null;
|
||||
// Then, fire 'disconnected' event.
|
||||
event = {
|
||||
type: 'disconnected',
|
||||
detail: {
|
||||
address: restoreDevice.address
|
||||
}
|
||||
};
|
||||
this._emitEvent(event);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Record connected device so if Bluetooth is turned off and then on
|
||||
* we can restore the connection.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {String} action - to set or remove item for recording connection
|
||||
* @param {String} address - the address of connected device
|
||||
*/
|
||||
_recordConnection: function btcm__recordConnection(action, address) {
|
||||
if (action === 'set') {
|
||||
// record connected device so if Bluetooth is turned off and then on
|
||||
// we can restore the connection
|
||||
AsyncStorage.setItem('device.connected', address);
|
||||
debug('_recordConnection(): set item');
|
||||
} else if ((action === 'remove') &&
|
||||
(this._defaultAdapter.state === 'enabled')) {
|
||||
// Clean up the connected device from async storage
|
||||
// which is recorded before.
|
||||
// Only remove the record while Bluetooth state is enabled.
|
||||
// Because the request also comes while Bluetooth is turned off.
|
||||
AsyncStorage.removeItem('device.connected');
|
||||
debug('_recordConnection(): remove item');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update connection status.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {Object} options
|
||||
* @param {String} options.address - address of the device
|
||||
* @param {Boolean} options.connected - is connected or not
|
||||
* @param {Object} options.profileID - profile ID of the connection type
|
||||
*/
|
||||
_updateConnectionStatus: function btcm___updateConnectionStatus(options) {
|
||||
debug('_updateConnectionStatus(): address = ' + options.address +
|
||||
', connected = ' + options.connected +
|
||||
', profileID = ' + options.profileID);
|
||||
this.connectingAddress = null;
|
||||
|
||||
// Update the profile in the cache of connected device info.
|
||||
this._updateConnectedDevices(options).then((connectedDevicesInfo) => {
|
||||
debug('_updateConnectionStatus(): _updateConnectedDevices() ' +
|
||||
'resolved with connectedDevicesInfo = ' +
|
||||
JSON.stringify(connectedDevicesInfo));
|
||||
// Prepare latest data to check connected profile.
|
||||
var dataToCheckConnectedProfile = {
|
||||
address: options.address,
|
||||
connectedDevices: connectedDevicesInfo
|
||||
};
|
||||
// Fire 'connected'/'disconnected' event according to
|
||||
// the connection profile. Then, record connection.
|
||||
var event;
|
||||
if (options.connected) {
|
||||
// Fire 'connected' event.
|
||||
event = {
|
||||
type: 'connected',
|
||||
detail: {
|
||||
address: options.address
|
||||
}
|
||||
};
|
||||
this._emitEvent(event);
|
||||
// Record connection.
|
||||
this._recordConnection('set', options.address);
|
||||
} else {
|
||||
// If there is no profile connected,
|
||||
// we have to remove the record connection.
|
||||
// And fire 'disconnected' event for outer modules.
|
||||
if (!this._hasConnectedProfileByAddress(
|
||||
dataToCheckConnectedProfile)) {
|
||||
// Record connection. Only remove the record while Bluetooth state
|
||||
// is enabled. Because the event also comes while Bluetooth is
|
||||
// turned off.
|
||||
if (this._defaultAdapter.state === 'enabled') {
|
||||
this._recordConnection('remove');
|
||||
}
|
||||
// Fire 'disconnected' event.
|
||||
event = {
|
||||
type: 'disconnected',
|
||||
detail: {
|
||||
address: options.address
|
||||
}
|
||||
};
|
||||
this._emitEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
// Fire 'profileChanged' event.
|
||||
var newProfiles;
|
||||
if (!this._hasConnectedProfileByAddress(dataToCheckConnectedProfile)) {
|
||||
newProfiles = null;
|
||||
} else {
|
||||
newProfiles = connectedDevicesInfo[options.address].connectedProfiles;
|
||||
}
|
||||
event = {
|
||||
type: 'profileChanged',
|
||||
detail: {
|
||||
address: options.address,
|
||||
profiles: newProfiles
|
||||
}
|
||||
};
|
||||
this._emitEvent(event);
|
||||
}, (reason) => {
|
||||
debug('_updateConnectionStatus(): miss to update in rejected case, ' +
|
||||
'reason = ' + reason);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* It provides a convenient function for panel to connect with a device.
|
||||
* And the panel no need to care about connected device currently.
|
||||
* The method will disconnect current connected device first.
|
||||
* Then, it will start connecting a paired device with the device's adapter.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {BluetoothDevice} device
|
||||
* @return {Promise}
|
||||
*/
|
||||
connect: function btcm_connect(device) {
|
||||
if (!this._defaultAdapter) {
|
||||
return Promise.reject('default adapter is not existed!!');
|
||||
}
|
||||
|
||||
// Disconnect current connected device first.
|
||||
debug('connect(): Want to connect device address = ' + device.address +
|
||||
', name = ' + device.name);
|
||||
|
||||
var connectedDevices = [];
|
||||
return this.getConnectedDevices().then((connectedDevicesInfo) => {
|
||||
for (var address in connectedDevicesInfo) {
|
||||
if (connectedDevicesInfo[address].device !== null) {
|
||||
connectedDevices.push(connectedDevicesInfo[address].device);
|
||||
debug('connect(): push device cache in queue = ' +
|
||||
JSON.stringify(connectedDevices));
|
||||
} else {
|
||||
var regetPairedDevice = this._getPairedDeviceByAddress(address);
|
||||
connectedDevices.push(regetPairedDevice);
|
||||
debug('connect(): push _getPairedDeviceByAddress in queue = ' +
|
||||
JSON.stringify(regetPairedDevice));
|
||||
}
|
||||
}
|
||||
|
||||
debug('connect(): Will disconnect these connected devices = ' +
|
||||
JSON.stringify(connectedDevices));
|
||||
|
||||
// Disconnect these connected device before
|
||||
// service to connect with new device.
|
||||
return Promise.all(connectedDevices.map((connectedDevice) => {
|
||||
return this.disconnect(connectedDevice);
|
||||
})).then(() => {
|
||||
// All connected devices is disconnected.
|
||||
// We can start to connect the new request.
|
||||
debug('connect(): Start to connect with wanted device address = ' +
|
||||
device.address);
|
||||
return this._connect(device).then(() => {
|
||||
debug('connect(): Resolved');
|
||||
}, (reason) => {
|
||||
debug('connect(): reason = ' + reason);
|
||||
return Promise.reject(reason);
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* The method will connect the input device with the device's adapter.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {BluetoothDevice} device
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_connect: function btcm__connect(device) {
|
||||
if (!this._defaultAdapter) {
|
||||
return Promise.reject('default adapter is not existed!!');
|
||||
}
|
||||
|
||||
// Save the connecting address
|
||||
this.connectingAddress = device.address;
|
||||
// Fire 'connecting' event.
|
||||
var event = {
|
||||
type: 'connecting',
|
||||
detail: {
|
||||
address: device.address
|
||||
}
|
||||
};
|
||||
this._emitEvent(event);
|
||||
debug('_connect(): before start to connect, stop discovery first');
|
||||
|
||||
// Note on Bluedroid stack, discovery has to be stopped before connect
|
||||
// (i.e., call stopDiscovery() before connect())
|
||||
// otherwise stack callbacks with connect failure.
|
||||
return this._defaultAdapter.stopDiscovery().then(() => {
|
||||
debug('_connect(): start connecting device, ' +
|
||||
'address = ' + device.address);
|
||||
return this._defaultAdapter.connect(device).then(() => {
|
||||
debug('_connect(): resolve, already connected with address = ' +
|
||||
device.address);
|
||||
}, () => {
|
||||
// No available profiles are connected. Reset connecting address.
|
||||
this.connectingAddress = null;
|
||||
// Fire 'disconnected' event.
|
||||
event = {
|
||||
type: 'disconnected',
|
||||
detail: {
|
||||
address: device.address
|
||||
}
|
||||
};
|
||||
this._emitEvent(event);
|
||||
debug('_connect(): reject with connection failed');
|
||||
return Promise.reject('connection failed');
|
||||
});
|
||||
}, () => {
|
||||
// Cannot connect with the device since stopDiscovery failed.
|
||||
this.connectingAddress = null;
|
||||
// Fire 'disconnected' event.
|
||||
event = {
|
||||
type: 'disconnected',
|
||||
detail: {
|
||||
address: device.address
|
||||
}
|
||||
};
|
||||
this._emitEvent(event);
|
||||
debug('_connect(): reject with stop discovery failed');
|
||||
return Promise.reject('stop discovery failed');
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* The method will disconnect the input device with the device's adapter.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {BluetoothDevice} device
|
||||
* @returns {Promise}
|
||||
*/
|
||||
disconnect: function btcm_disconnect(device) {
|
||||
if (!this._defaultAdapter) {
|
||||
return Promise.reject('default adapter is not existed!!');
|
||||
}
|
||||
|
||||
return this._defaultAdapter.disconnect(device).then(() => {
|
||||
debug('disconnect(): onsuccess(): resolve');
|
||||
}, () => {
|
||||
debug('disconnect(): onerror(): reject with disconnect failed');
|
||||
return Promise.reject('disconnect failed');
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* The method will get all connected devices profiles
|
||||
* which we are interested in. Profile: HFP, A2DP.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @returns {Promise} resolve: connectedDevices
|
||||
* @returns {Promise} reject: reason
|
||||
*/
|
||||
_getConnectedDevicesFromPlatform:
|
||||
function btcm__getConnectedDevicesFromPlatform() {
|
||||
// Get connected device via profiles HFP, A2DP
|
||||
return Promise.all(Object.keys(this.Profiles).map((profile) => {
|
||||
return this._getConnectedDevicesByProfile(this.Profiles[profile]);
|
||||
})).then((connectedDevices) => {
|
||||
// Update each connected devices in map.
|
||||
var collectedConnectedDevicesByProfile = {};
|
||||
Object.keys(this.Profiles).forEach((profile, index) => {
|
||||
collectedConnectedDevicesByProfile[profile] = connectedDevices[index];
|
||||
});
|
||||
debug('_getConnectedDevicesFromPlatform(): ' +
|
||||
'collectedConnectedDevicesByProfile = ' +
|
||||
JSON.stringify(collectedConnectedDevicesByProfile));
|
||||
return Promise.resolve(collectedConnectedDevicesByProfile);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* The method will get connected device by inputed profile.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {String} profileID
|
||||
* @returns {Promise} resolve: the connected devices in array
|
||||
* @returns {Promise} reject: reason
|
||||
*/
|
||||
_getConnectedDevicesByProfile:
|
||||
function btcm__getConnectedDevicesByProfile(profileID) {
|
||||
if (!this._defaultAdapter) {
|
||||
debug('_getConnectedDevicesByProfile(): reject with no adapter');
|
||||
return Promise.reject('default adapter is not existed!!');
|
||||
}
|
||||
|
||||
if (this._defaultAdapter.state === 'disabled') {
|
||||
debug('_getConnectedDevicesByProfile(): resolve with empty array ' +
|
||||
'since it is impossible to connect with any device');
|
||||
return Promise.reject('getConnectedDevices in disabled state');
|
||||
}
|
||||
|
||||
return this._defaultAdapter.getConnectedDevices(profileID).then(
|
||||
(connectedDevice) => {
|
||||
debug('_getConnectedDevicesByProfile(): resolved with ' +
|
||||
'connectedDevice = ' + JSON.stringify(connectedDevice));
|
||||
return Promise.resolve(connectedDevice || []);
|
||||
}, (reason) => {
|
||||
debug('_getConnectedDevicesByProfile(): rejected with ' +
|
||||
'reason = ' + reason);
|
||||
return Promise.reject(reason);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get device from paired devices by address.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {String} address
|
||||
*/
|
||||
_getPairedDeviceByAddress:
|
||||
function btcm__getPairedDeviceByAddress(address) {
|
||||
if (!this._defaultAdapter) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var pairedDevices = this._defaultAdapter.getPairedDevices();
|
||||
if (pairedDevices.length === 0) {
|
||||
return null;
|
||||
} else {
|
||||
for (var i in pairedDevices) {
|
||||
if (pairedDevices[i].address === address) {
|
||||
return pairedDevices[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Find out there is any profile still connected.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {Object} options
|
||||
* @param {String} options.connectedDevices - connected devices info
|
||||
* @param {String} options.address - the address of device
|
||||
*/
|
||||
_hasConnectedProfileByAddress:
|
||||
function btcm__hasConnectedProfileByAddress(options) {
|
||||
if (!options.connectedDevices[options.address]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var hasConnectedProfile = false;
|
||||
for (var profileID in this.Profiles) {
|
||||
var connectedProfiles =
|
||||
options.connectedDevices[options.address].connectedProfiles;
|
||||
if (connectedProfiles && (connectedProfiles[profileID] === true)) {
|
||||
hasConnectedProfile = true;
|
||||
}
|
||||
}
|
||||
return hasConnectedProfile;
|
||||
},
|
||||
|
||||
/**
|
||||
* A function to emit event to each registered listener by the input type.
|
||||
*
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {Object} options
|
||||
* @param {String} options.type - type of event name
|
||||
* @param {Object} options.detail - the object pass to the listener
|
||||
*/
|
||||
_emitEvent: function btcm__emitEvent(options) {
|
||||
this._listeners[options.type].forEach(function(listener) {
|
||||
listener(options);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* The method will provide event listener for outer modules to regist.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {String} eventName
|
||||
* @param {Function} callback
|
||||
*/
|
||||
addEventListener: function btcm_addEventListener(eventName, callback) {
|
||||
if (callback && (this._listeners.hasOwnProperty(eventName))) {
|
||||
this._listeners[eventName].push(callback);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The method will provide event listener for outer modules to un-regist.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {String} eventName
|
||||
* @param {Function} callback
|
||||
*/
|
||||
removeEventListener:
|
||||
function btcm_removeEventListener(eventName, callback) {
|
||||
if (callback && (this._listeners.hasOwnProperty(eventName))) {
|
||||
var index = this._listeners[eventName].indexOf(callback);
|
||||
if (index >= 0) {
|
||||
this._listeners[eventName].splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BluetoothConnectionManager._init();
|
||||
return BluetoothConnectionManager;
|
||||
});
|
||||
1043
gaia/apps/settings/js/modules/bluetooth/bluetooth_context.js
Executable file
1043
gaia/apps/settings/js/modules/bluetooth/bluetooth_context.js
Executable file
File diff suppressed because it is too large
Load diff
158
gaia/apps/settings/js/modules/bluetooth/bluetooth_device.js
Executable file
158
gaia/apps/settings/js/modules/bluetooth/bluetooth_device.js
Executable file
|
|
@ -0,0 +1,158 @@
|
|||
/**
|
||||
* BluetoothDevice:
|
||||
* - BluetoothDevice is an Observable that wraps the platform
|
||||
* BluetoothDevice object.
|
||||
* - It has some observable properties: name, paired, cod
|
||||
* BluetoothDevice only update device information and does not involve in any
|
||||
* UI logic.
|
||||
*
|
||||
* @module BluetoothDevice
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var BtClassOfDeviceMapper = require('modules/bluetooth/bluetooth_cod_mapper');
|
||||
var Observable = require('modules/mvvm/observable');
|
||||
|
||||
var _debug = false;
|
||||
var debug = function() {};
|
||||
if (_debug) {
|
||||
debug = function btd_debug(msg) {
|
||||
console.log('--> [BluetoothDevice]: ' + msg);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a function to update connection info.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf BluetoothDevice
|
||||
* @param {Object} options
|
||||
* @param {Boolean} options.connectionStatus - is connected or not
|
||||
* @param {Object} options.profiles - profiles of the connection type
|
||||
*/
|
||||
var updateConnectionInfo = function(options) {
|
||||
debug('updateConnectionInfo(): this.address = ' + this.address +
|
||||
', this.name = ' + this.name +
|
||||
', options = ' + JSON.stringify(options));
|
||||
if (options.connectionStatus) {
|
||||
this.connectionStatus = options.connectionStatus;
|
||||
}
|
||||
|
||||
if (options.profiles) {
|
||||
this.profiles = Object.create(options.profiles);
|
||||
}
|
||||
|
||||
debug('updateConnectionInfo(): this.connectionStatus = ' +
|
||||
this.connectionStatus + ', this.profiles = ' +
|
||||
JSON.stringify(this.profiles));
|
||||
};
|
||||
|
||||
/**
|
||||
* Provide a function to update description text.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf BluetoothDevice
|
||||
*/
|
||||
var updateDescriptionText = function() {
|
||||
debug('updateDescriptionText():');
|
||||
// Define description for found device.
|
||||
if (this.paired === false) {
|
||||
this.descriptionText = 'tapToConnect';
|
||||
} else if (this.paired === 'pairing') {
|
||||
this.descriptionText = 'pairing';
|
||||
} else if (this.paired === true) {
|
||||
this.descriptionText = 'paired';
|
||||
// Define description for paired device.
|
||||
if (this.connectionStatus === 'connecting') {
|
||||
this.descriptionText = 'connecting';
|
||||
} else if (this.connectionStatus === 'connected') {
|
||||
if (this.profiles) {
|
||||
var hfpConnected = this.profiles.hfp;
|
||||
var a2dpConnected = this.profiles.a2dp;
|
||||
if (hfpConnected && a2dpConnected) {
|
||||
this.descriptionText = 'connectedWithDeviceMedia';
|
||||
} else if (hfpConnected) {
|
||||
this.descriptionText = 'connectedWithDevice';
|
||||
} else if (a2dpConnected) {
|
||||
this.descriptionText = 'connectedWithMedia';
|
||||
} else {
|
||||
this.descriptionText = 'connectedWithNoProfileInfo';
|
||||
}
|
||||
} else {
|
||||
this.descriptionText = 'connectedWithNoProfileInfo';
|
||||
}
|
||||
} else if (this.connectionStatus === 'disconnected') {
|
||||
this.descriptionText = 'disconnected';
|
||||
}
|
||||
}
|
||||
debug('updateDescriptionText(): this.descriptionText = ' +
|
||||
this.descriptionText);
|
||||
};
|
||||
|
||||
/**
|
||||
* @class BluetoothDevice
|
||||
* @requires module:modules/mvvm/observable
|
||||
* @param {BluetoothDevice} device
|
||||
* @return {Observable} observableBluetoothDevice
|
||||
*/
|
||||
return function ctor_bluetooth_device(device) {
|
||||
var type = BtClassOfDeviceMapper.getDeviceType(device.cod);
|
||||
var connectionStatus = device.connectionStatus || 'disconnected';
|
||||
var observableBluetoothDevice = Observable({
|
||||
name: device.name,
|
||||
paired: device.paired,
|
||||
address: device.address,
|
||||
type: type,
|
||||
connectionStatus: connectionStatus,
|
||||
profiles: null,
|
||||
descriptionText: '',
|
||||
get data() {return device;},
|
||||
updateConnectionInfo: updateConnectionInfo,
|
||||
updateDescriptionText: updateDescriptionText
|
||||
});
|
||||
|
||||
/**
|
||||
* Observe 'paired', 'connectionStatus', and 'profiles' properties changed
|
||||
* event in init function. Once these properties changed, we can update
|
||||
* corrected description for the device.
|
||||
*/
|
||||
observableBluetoothDevice._init = function btd__init() {
|
||||
this.observe('paired', this.updateDescriptionText.bind(this));
|
||||
this.observe('connectionStatus', this.updateDescriptionText.bind(this));
|
||||
this.observe('profiles', this.updateDescriptionText.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* watch 'onattributechanged' event
|
||||
* A function to receive device which is just found via discovery handler.
|
||||
* And set a function to handle 'onattributechanged' event.
|
||||
*/
|
||||
device.onattributechanged = function btd_onDeviceAttributeChanged(evt) {
|
||||
for (var i in evt.attrs) {
|
||||
debug('onDeviceAttributeChanged(): ' + evt.attrs[i]);
|
||||
switch (evt.attrs[i]) {
|
||||
case 'name':
|
||||
observableBluetoothDevice.name = device.name;
|
||||
break;
|
||||
case 'paired':
|
||||
debug('onDeviceAttributeChanged(): ' +
|
||||
'device.paired = ' + device.paired);
|
||||
observableBluetoothDevice.paired = device.paired;
|
||||
break;
|
||||
case 'cod':
|
||||
debug('onDeviceAttributeChanged(): ' +
|
||||
'device.cod = ' + device.cod);
|
||||
observableBluetoothDevice.type =
|
||||
BtClassOfDeviceMapper.getDeviceType(device.cod);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
observableBluetoothDevice._init();
|
||||
return observableBluetoothDevice;
|
||||
};
|
||||
});
|
||||
36
gaia/apps/settings/js/modules/customized_network_type_map.js
Executable file
36
gaia/apps/settings/js/modules/customized_network_type_map.js
Executable file
|
|
@ -0,0 +1,36 @@
|
|||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
const KEY = 'operatorResources.data.icon';
|
||||
|
||||
var initialized = false;
|
||||
var map = {};
|
||||
|
||||
function getCustomizedNetworkTypeMap() {
|
||||
if (initialized) {
|
||||
return Promise.resolve(map);
|
||||
} else {
|
||||
return new Promise((resolve) => {
|
||||
var req = navigator.mozSettings.createLock().get(KEY);
|
||||
req.onsuccess = function() {
|
||||
initialized = true;
|
||||
var result = req.result[KEY];
|
||||
if (result && Object.keys(result).length) {
|
||||
map = Object.create(result);
|
||||
}
|
||||
resolve(map);
|
||||
};
|
||||
req.onerror = function() {
|
||||
initialized = true;
|
||||
console.error('Error loading ' + KEY + ' settings. ' +
|
||||
req.error && req.error.name);
|
||||
resolve(map);
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
get: getCustomizedNetworkTypeMap
|
||||
});
|
||||
});
|
||||
382
gaia/apps/settings/js/modules/date_time.js
Executable file
382
gaia/apps/settings/js/modules/date_time.js
Executable file
|
|
@ -0,0 +1,382 @@
|
|||
/**
|
||||
* DateTime is a singleton that caches date, time, timezone values for
|
||||
* Date & Time panel fast access
|
||||
*
|
||||
* @module DateTime
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var SettingsCache = require('modules/settings_cache');
|
||||
var Module = require('modules/base/module');
|
||||
var Observable = require('modules/mvvm/observable');
|
||||
var settings = window.navigator.mozSettings;
|
||||
|
||||
// keys
|
||||
var _kClockAutoEnabled = 'time.clock.automatic-update.enabled';
|
||||
var _kTimezoneAutoEnabled = 'time.timezone.automatic-update.enabled';
|
||||
var _kClockAutoAvailable = 'time.clock.automatic-update.available';
|
||||
var _kTimezoneAutoAvailable = 'time.timezone.automatic-update.available';
|
||||
var _kTimezone = 'time.timezone';
|
||||
var _kUserSelected = 'time.timezone.user-selected';
|
||||
var _kLocaleTime = 'locale.hour12';
|
||||
|
||||
// handler
|
||||
var _updateDateTimeout = null;
|
||||
var _updateTimeTimeout = null;
|
||||
|
||||
var DateTime = Module.create(function DateTime() {
|
||||
this.super(Observable).call(this);
|
||||
this._init();
|
||||
}).extend(Observable);
|
||||
|
||||
|
||||
// public observers
|
||||
/**
|
||||
* Auto setting Date & Time.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf DateTime
|
||||
* @type {Boolean}
|
||||
*/
|
||||
Observable.defineObservableProperty(DateTime.prototype, 'clockAutoEnabled', {
|
||||
readonly: true,
|
||||
value: true
|
||||
});
|
||||
|
||||
/**
|
||||
* SIM provide Auto clock function.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf DateTime
|
||||
* @type {Boolean}
|
||||
*/
|
||||
Observable.defineObservableProperty(DateTime.prototype,
|
||||
'clockAutoAvailable', {
|
||||
readonly: true,
|
||||
value: true
|
||||
});
|
||||
|
||||
/**
|
||||
* SIM provide Auto timezone function.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf DateTime
|
||||
* @type {Boolean}
|
||||
*/
|
||||
Observable.defineObservableProperty(DateTime.prototype,
|
||||
'timezoneAutoAvailable', {
|
||||
readonly: true,
|
||||
value: true
|
||||
});
|
||||
|
||||
/**
|
||||
* Current Timezone.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf DateTime
|
||||
* @type {String}
|
||||
*/
|
||||
Observable.defineObservableProperty(DateTime.prototype, 'timezone', {
|
||||
readonly: true,
|
||||
value: ''
|
||||
});
|
||||
|
||||
/**
|
||||
* User Selected Timezone.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf DateTime
|
||||
* @type {String}
|
||||
*/
|
||||
Observable.defineObservableProperty(DateTime.prototype,
|
||||
'userSelectedTimezone', {
|
||||
readonly: true,
|
||||
value: ''
|
||||
});
|
||||
|
||||
/**
|
||||
* Current Date string.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf DateTime
|
||||
* @type {String}
|
||||
*/
|
||||
Observable.defineObservableProperty(DateTime.prototype, 'date', {
|
||||
readonly: true,
|
||||
value: ''
|
||||
});
|
||||
|
||||
/**
|
||||
* Localized current time string.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf DateTime
|
||||
* @type {String}
|
||||
*/
|
||||
Observable.defineObservableProperty(DateTime.prototype, 'time', {
|
||||
readonly: true,
|
||||
value: ''
|
||||
});
|
||||
|
||||
/**
|
||||
* Current AM/PM or 24 state.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf DateTime
|
||||
* @type {Boolean}
|
||||
*/
|
||||
Observable.defineObservableProperty(DateTime.prototype, 'currentHour12', {
|
||||
readonly: true,
|
||||
value: true
|
||||
});
|
||||
|
||||
/**
|
||||
* Init DateTime module.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf DateTime
|
||||
*/
|
||||
DateTime.prototype._init = function() {
|
||||
this._mozTime = window.navigator.mozTime;
|
||||
if (!this._mozTime) {
|
||||
console.error('Could not get window.navigator.mozTime');
|
||||
return;
|
||||
}
|
||||
|
||||
this._boundSetTimeAutoEnabled = function(event) {
|
||||
this._clockAutoEnabled = event.settingValue;
|
||||
// also set timezoneAutoEnabled
|
||||
this._setTimezoneAutoEnabled(event.settingValue);
|
||||
}.bind(this);
|
||||
this._boundSetClockAutoAvailable = function(event) {
|
||||
this._clockAutoAvailable = event.settingValue;
|
||||
}.bind(this);
|
||||
this._boundSetTimezoneAutoAvailable = function(event) {
|
||||
this._timezoneAutoAvailable = event.settingValue;
|
||||
}.bind(this);
|
||||
this._boundSetTimezone = function(event) {
|
||||
this._timezone = event.settingValue;
|
||||
}.bind(this);
|
||||
this._boundUserSelectedTimezone = function(event) {
|
||||
this._userSelectedTimezone = event.settingValue;
|
||||
}.bind(this);
|
||||
this._boundCurrentHour12 = function(event) {
|
||||
this._currentHour12 = event.settingValue;
|
||||
|
||||
this._autoUpdateDateTime();
|
||||
}.bind(this);
|
||||
|
||||
this._getDefaults();
|
||||
this._attachListeners();
|
||||
};
|
||||
|
||||
DateTime.prototype._attachListeners = function() {
|
||||
window.navigator.mozSettings.addObserver(_kClockAutoEnabled,
|
||||
this._boundSetTimeAutoEnabled);
|
||||
window.navigator.mozSettings.addObserver(_kClockAutoAvailable,
|
||||
this._boundSetClockAutoAvailable);
|
||||
window.navigator.mozSettings.addObserver(_kTimezoneAutoAvailable,
|
||||
this._boundSetTimezoneAutoAvailable);
|
||||
window.navigator.mozSettings.addObserver(_kTimezone,
|
||||
this._boundSetTimezone);
|
||||
window.navigator.mozSettings.addObserver(_kUserSelected,
|
||||
this._boundUserSelectedTimezone);
|
||||
window.navigator.mozSettings.addObserver(_kLocaleTime,
|
||||
this._boundCurrentHour12);
|
||||
// Listen to 'DOMRetranslated' to get the latest l10n resource.
|
||||
document.addEventListener('DOMRetranslated', this);
|
||||
// Listen to 'moztimechange' to update clock
|
||||
window.addEventListener('moztimechange', this);
|
||||
};
|
||||
|
||||
DateTime.prototype.handleEvent = function(evt) {
|
||||
switch (evt.type) {
|
||||
case 'moztimechange':
|
||||
this._autoUpdateDateTime();
|
||||
break;
|
||||
case 'DOMRetranslated':
|
||||
var d = new Date();
|
||||
this._date = this._formatDate(d);
|
||||
this._time = this._formatTime(d);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* fill in default values to public observers
|
||||
*
|
||||
* @access private
|
||||
* @memberOf DateTime
|
||||
*/
|
||||
DateTime.prototype._getDefaults = function() {
|
||||
SettingsCache.getSettings(function(results) {
|
||||
this._clockAutoEnabled = results[_kClockAutoEnabled];
|
||||
this._clockAutoAvailable = results[_kClockAutoAvailable];
|
||||
this._timezoneAutoAvailable = results[_kTimezoneAutoAvailable];
|
||||
this._timezone = results[_kTimezone];
|
||||
this._currentHour12 = results[_kLocaleTime];
|
||||
// render date/time after get proper format
|
||||
this._autoUpdateDateTime();
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* Auto update date and time
|
||||
*
|
||||
* @access private
|
||||
* @memberOf DateTime
|
||||
*/
|
||||
DateTime.prototype._autoUpdateDateTime = function() {
|
||||
window.clearTimeout(_updateDateTimeout);
|
||||
window.clearTimeout(_updateTimeTimeout);
|
||||
this._autoUpdateDate();
|
||||
this._autoUpdateTime();
|
||||
};
|
||||
|
||||
/**
|
||||
* Update Date periodically.
|
||||
*
|
||||
* DONT call this function directly,
|
||||
* call _autoUpdateDateTime instead.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf DateTime
|
||||
*/
|
||||
DateTime.prototype._autoUpdateDate = function() {
|
||||
var d = new Date();
|
||||
this._date = this._formatDate(d);
|
||||
|
||||
var remainMillisecond = (24 - d.getHours()) * 3600 * 1000 -
|
||||
d.getMinutes() * 60 * 1000 - d.getMilliseconds();
|
||||
_updateDateTimeout = window.setTimeout(
|
||||
function updateDateTimeout() {
|
||||
this._autoUpdateDate();
|
||||
}.bind(this), remainMillisecond);
|
||||
};
|
||||
|
||||
/**
|
||||
* Update Time periodically.
|
||||
*
|
||||
* DONT call this function directly,
|
||||
* call _autoUpdateDateTime instead.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf DateTime
|
||||
*/
|
||||
DateTime.prototype._autoUpdateTime = function() {
|
||||
var d = new Date();
|
||||
this._time = this._formatTime(d);
|
||||
|
||||
var remainMillisecond = (60 - d.getSeconds()) * 1000;
|
||||
_updateTimeTimeout = window.setTimeout(
|
||||
function updateTimeTimeout() {
|
||||
this._autoUpdateTime();
|
||||
}.bind(this), remainMillisecond);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return preffered date format.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf DateTime
|
||||
* @param {Date} input as Date
|
||||
* @returns {String}
|
||||
*/
|
||||
DateTime.prototype._formatDate = function(d) {
|
||||
return d.toLocaleString(navigator.languages, {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: '2-digit',
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Return preffered time format.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf DateTime
|
||||
* @param {Date} input as Date
|
||||
* @returns {String}
|
||||
*/
|
||||
DateTime.prototype._formatTime = function(d) {
|
||||
return d.toLocaleString(navigator.languages, {
|
||||
hour12: navigator.mozHour12,
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Change system time
|
||||
*
|
||||
* @access public
|
||||
* @memberOf DateTime
|
||||
*/
|
||||
DateTime.prototype.setTime = function(type, value) {
|
||||
var newDate = new Date();
|
||||
var tokens;
|
||||
|
||||
switch (type) {
|
||||
case 'date':
|
||||
tokens = value.split('-');
|
||||
newDate.setYear(tokens[0]);
|
||||
newDate.setMonth(tokens[1] - 1);
|
||||
newDate.setDate(tokens[2]);
|
||||
break;
|
||||
|
||||
case 'time':
|
||||
tokens = value.split(':');
|
||||
newDate.setHours(parseInt(tokens[0]));
|
||||
newDate.setMinutes(parseInt(tokens[1]));
|
||||
break;
|
||||
}
|
||||
this._mozTime.set(newDate);
|
||||
};
|
||||
|
||||
/**
|
||||
* Change TimezoneAutoEnabled settings.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf DateTime
|
||||
*/
|
||||
DateTime.prototype._setTimezoneAutoEnabled = function(enabled) {
|
||||
var cset = {};
|
||||
cset[_kTimezoneAutoEnabled] = enabled;
|
||||
settings.createLock().set(cset);
|
||||
},
|
||||
|
||||
/**
|
||||
* Change UserSelectedTimezone settings.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf DateTime
|
||||
*/
|
||||
DateTime.prototype.setUserSelectedTimezone = function(selected) {
|
||||
var cset = {};
|
||||
cset[_kTimezone] = selected;
|
||||
settings.createLock().set(cset);
|
||||
};
|
||||
|
||||
/**
|
||||
* Change hour12 settings.
|
||||
* this valie is used to determine 12 or 24 time format.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf DateTime
|
||||
*/
|
||||
DateTime.prototype.setCurrentHour12 = function(selected) {
|
||||
var cset = {};
|
||||
cset[_kLocaleTime] = selected;
|
||||
settings.createLock().set(cset);
|
||||
};
|
||||
|
||||
DateTime.prototype.getHour12ForCurrentLocale = function() {
|
||||
var format = new Intl.DateTimeFormat(navigator.languages, {
|
||||
hour: 'numeric'
|
||||
});
|
||||
return format.resolvedOptions().hour12;
|
||||
};
|
||||
|
||||
return DateTime();
|
||||
});
|
||||
29
gaia/apps/settings/js/modules/defer.js
Executable file
29
gaia/apps/settings/js/modules/defer.js
Executable file
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* Defer is a factory that would create a wrapped Promise object with exposed
|
||||
* resolve / reject API so that we can easily resolve / reject promise from
|
||||
* outside.
|
||||
*
|
||||
* This is mostly useful when you are going to queue promises for later use.
|
||||
*
|
||||
* API:
|
||||
*
|
||||
* var defer = Defer();
|
||||
* defer.resolve('return value');
|
||||
* defer.reject('reason')
|
||||
*
|
||||
* @module Defer
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var Defer = function() {
|
||||
this.promise = new Promise((resolve, reject) => {
|
||||
this.resolve = resolve;
|
||||
this.reject = reject;
|
||||
});
|
||||
};
|
||||
|
||||
return function ctor_defer() {
|
||||
return new Defer();
|
||||
};
|
||||
});
|
||||
26
gaia/apps/settings/js/modules/dialog/alert_dialog.js
Executable file
26
gaia/apps/settings/js/modules/dialog/alert_dialog.js
Executable file
|
|
@ -0,0 +1,26 @@
|
|||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var BaseDialog = require('modules/dialog/base_dialog');
|
||||
|
||||
var AlertDialog = function(panelDOM, options) {
|
||||
BaseDialog.call(this, panelDOM, options);
|
||||
};
|
||||
|
||||
AlertDialog.prototype = Object.create(BaseDialog.prototype);
|
||||
AlertDialog.prototype.constructor = AlertDialog;
|
||||
AlertDialog.prototype.DIALOG_CLASS = 'alert-dialog';
|
||||
AlertDialog.prototype.TRANSITION_CLASS = 'fade';
|
||||
|
||||
AlertDialog.prototype.bindEvents = function() {
|
||||
var self = this;
|
||||
|
||||
this.getSubmitButton().onclick = function() {
|
||||
self._options.onWrapSubmit();
|
||||
};
|
||||
};
|
||||
|
||||
return function ctor_alertDialog(panelDOM, options) {
|
||||
return new AlertDialog(panelDOM, options);
|
||||
};
|
||||
});
|
||||
124
gaia/apps/settings/js/modules/dialog/base_dialog.js
Executable file
124
gaia/apps/settings/js/modules/dialog/base_dialog.js
Executable file
|
|
@ -0,0 +1,124 @@
|
|||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var BaseDialog = function(panelDOM, options) {
|
||||
this.panel = panelDOM;
|
||||
this._options = options || {};
|
||||
};
|
||||
|
||||
BaseDialog.prototype.DIALOG_CLASS = 'dialog';
|
||||
BaseDialog.prototype.TRANSITION_CLASS = 'fade';
|
||||
BaseDialog.prototype.SUBMIT_BUTTON_SELECTOR = '[type="submit"]';
|
||||
BaseDialog.prototype.CANCEL_BUTTON_SELECTOR = '[type="reset"]';
|
||||
BaseDialog.prototype.MESSAGE_SELECTOR = '.settings-dialog-message';
|
||||
BaseDialog.prototype.TITLE_SELECTOR = '.settings-dialog-title';
|
||||
|
||||
BaseDialog.prototype.init = function bd_init() {
|
||||
// We can override animation class from options
|
||||
this.TRANSITION_CLASS = this._options.transition || this.TRANSITION_CLASS;
|
||||
this.panel.classList.add(this.DIALOG_CLASS);
|
||||
this.panel.classList.add(this.TRANSITION_CLASS);
|
||||
};
|
||||
|
||||
BaseDialog.prototype.initUI = function bd_initUI() {
|
||||
var message = this._options.message;
|
||||
var title = this._options.title;
|
||||
var submitButton = this._options.submitButton;
|
||||
var cancelButton = this._options.cancelButton;
|
||||
|
||||
this._updateMessage(message);
|
||||
this._updateTitle(title);
|
||||
this._updateSubmitButton(submitButton);
|
||||
this._updateCancelButton(cancelButton);
|
||||
};
|
||||
|
||||
BaseDialog.prototype.bindEvents = function bd_bindEvent() {
|
||||
var self = this;
|
||||
|
||||
this.getSubmitButton().onclick = function() {
|
||||
self._options.onWrapSubmit();
|
||||
};
|
||||
|
||||
this.getCancelButton().onclick = function() {
|
||||
self._options.onWrapCancel();
|
||||
};
|
||||
};
|
||||
|
||||
BaseDialog.prototype._updateMessage = function bd__updateMessage(message) {
|
||||
var messageDOM = this.panel.querySelector(this.MESSAGE_SELECTOR);
|
||||
if (messageDOM && message) {
|
||||
message = this._getWrapL10nObject(message);
|
||||
document.l10n.setAttributes(messageDOM, message.id, message.args);
|
||||
}
|
||||
};
|
||||
|
||||
BaseDialog.prototype._updateTitle = function bd__updateTitle(title) {
|
||||
var titleDOM = this.panel.querySelector(this.TITLE_SELECTOR);
|
||||
if (titleDOM && title) {
|
||||
title = this._getWrapL10nObject(title);
|
||||
document.l10n.setAttributes(titleDOM, title.id, title.args);
|
||||
}
|
||||
};
|
||||
|
||||
BaseDialog.prototype._updateSubmitButton = function bd__update(options) {
|
||||
var buttonDOM = this.getSubmitButton();
|
||||
if (buttonDOM && options) {
|
||||
options = this._getWrapL10nObject(options);
|
||||
document.l10n.setAttributes(buttonDOM, options.id, options.args);
|
||||
buttonDOM.className = options.style || 'recommend';
|
||||
}
|
||||
};
|
||||
|
||||
BaseDialog.prototype._updateCancelButton = function bd__updateText(options) {
|
||||
var buttonDOM = this.getCancelButton();
|
||||
if (buttonDOM && options) {
|
||||
options = this._getWrapL10nObject(options);
|
||||
document.l10n.setAttributes(buttonDOM, options.id, options.args);
|
||||
buttonDOM.className = options.style || '';
|
||||
}
|
||||
};
|
||||
|
||||
BaseDialog.prototype._getWrapL10nObject =
|
||||
function bd__getWrapL10nObject(input) {
|
||||
if (typeof input === 'string') {
|
||||
return {id: input, args: null};
|
||||
} else if (typeof input === 'object') {
|
||||
if (typeof input.id === 'undefined') {
|
||||
throw new Error('You forgot to put l10nId - ' +
|
||||
JSON.stringify(input));
|
||||
} else {
|
||||
return {id: input.id, args: input.args || null, style: input.style};
|
||||
}
|
||||
} else {
|
||||
throw new Error('You are using the wrong L10nObject, ' +
|
||||
'please check its format again');
|
||||
}
|
||||
};
|
||||
|
||||
BaseDialog.prototype.getDOM = function bd_getDOM() {
|
||||
return this.panel;
|
||||
};
|
||||
|
||||
BaseDialog.prototype.getSubmitButton = function bd_getSubmitButton() {
|
||||
return this.panel.querySelector(this.SUBMIT_BUTTON_SELECTOR);
|
||||
};
|
||||
|
||||
BaseDialog.prototype.getCancelButton = function bd_getCancelButton() {
|
||||
return this.panel.querySelector(this.CANCEL_BUTTON_SELECTOR);
|
||||
};
|
||||
|
||||
BaseDialog.prototype.cleanup = function bd_cleanup() {
|
||||
// We only have to restore system-wise panels instead of custom panels
|
||||
if (this.DIALOG_CLASS !== 'panel-dialog') {
|
||||
this._updateTitle('settings-' + this.DIALOG_CLASS + '-header');
|
||||
this._updateSubmitButton('ok');
|
||||
this._updateCancelButton('cancel');
|
||||
}
|
||||
|
||||
// clear all added classes
|
||||
this.panel.classList.remove(this.DIALOG_CLASS);
|
||||
this.panel.classList.remove(this.TRANSITION_CLASS);
|
||||
};
|
||||
|
||||
return BaseDialog;
|
||||
});
|
||||
30
gaia/apps/settings/js/modules/dialog/confirm_dialog.js
Executable file
30
gaia/apps/settings/js/modules/dialog/confirm_dialog.js
Executable file
|
|
@ -0,0 +1,30 @@
|
|||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var BaseDialog = require('modules/dialog/base_dialog');
|
||||
|
||||
var ConfirmDialog = function(panelDOM, options) {
|
||||
BaseDialog.call(this, panelDOM, options);
|
||||
};
|
||||
|
||||
ConfirmDialog.prototype = Object.create(BaseDialog.prototype);
|
||||
ConfirmDialog.prototype.constructor = ConfirmDialog;
|
||||
ConfirmDialog.prototype.DIALOG_CLASS = 'confirm-dialog';
|
||||
ConfirmDialog.prototype.TRANSITION_CLASS = 'fade';
|
||||
|
||||
ConfirmDialog.prototype.bindEvents = function() {
|
||||
var self = this;
|
||||
|
||||
this.getSubmitButton().onclick = function() {
|
||||
self._options.onWrapSubmit();
|
||||
};
|
||||
|
||||
this.getCancelButton().onclick = function() {
|
||||
self._options.onWrapCancel();
|
||||
};
|
||||
};
|
||||
|
||||
return function ctor_confirmDialog(panelDOM, options) {
|
||||
return new ConfirmDialog(panelDOM, options);
|
||||
};
|
||||
});
|
||||
18
gaia/apps/settings/js/modules/dialog/panel_dialog.js
Executable file
18
gaia/apps/settings/js/modules/dialog/panel_dialog.js
Executable file
|
|
@ -0,0 +1,18 @@
|
|||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var BaseDialog = require('modules/dialog/base_dialog');
|
||||
|
||||
var PanelDialog = function(panelDOM, options) {
|
||||
BaseDialog.call(this, panelDOM, options);
|
||||
};
|
||||
|
||||
PanelDialog.prototype = Object.create(BaseDialog.prototype);
|
||||
PanelDialog.prototype.constructor = PanelDialog;
|
||||
PanelDialog.prototype.DIALOG_CLASS = 'panel-dialog';
|
||||
PanelDialog.prototype.TRANSITION_CLASS = 'fade';
|
||||
|
||||
return function ctor_PanelDialog(panelDOM, options) {
|
||||
return new PanelDialog(panelDOM, options);
|
||||
};
|
||||
});
|
||||
44
gaia/apps/settings/js/modules/dialog/prompt_dialog.js
Executable file
44
gaia/apps/settings/js/modules/dialog/prompt_dialog.js
Executable file
|
|
@ -0,0 +1,44 @@
|
|||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var BaseDialog = require('modules/dialog/base_dialog');
|
||||
|
||||
var PromptDialog = function(panelDOM, options) {
|
||||
BaseDialog.call(this, panelDOM, options);
|
||||
};
|
||||
|
||||
PromptDialog.prototype = Object.create(BaseDialog.prototype);
|
||||
PromptDialog.prototype.constructor = PromptDialog;
|
||||
PromptDialog.prototype.DIALOG_CLASS = 'prompt-dialog';
|
||||
PromptDialog.prototype.TRANSITION_CLASS = 'fade';
|
||||
PromptDialog.prototype.INPUT_SELECTOR = '.settings-dialog-input';
|
||||
|
||||
PromptDialog.prototype.bindEvents = function() {
|
||||
var self = this;
|
||||
|
||||
this.getSubmitButton().onclick = function() {
|
||||
self._options.onWrapSubmit();
|
||||
};
|
||||
|
||||
this.getCancelButton().onclick = function() {
|
||||
self._options.onWrapCancel();
|
||||
};
|
||||
};
|
||||
|
||||
PromptDialog.prototype.initUI = function() {
|
||||
BaseDialog.prototype.initUI.call(this);
|
||||
this.getInput().value = this._options.defaultValue || '';
|
||||
};
|
||||
|
||||
PromptDialog.prototype.getInput = function() {
|
||||
return this.panel.querySelector(this.INPUT_SELECTOR);
|
||||
};
|
||||
|
||||
PromptDialog.prototype.getResult = function() {
|
||||
return this.getInput().value;
|
||||
};
|
||||
|
||||
return function ctor_promptDialog(panelDOM, options) {
|
||||
return new PromptDialog(panelDOM, options);
|
||||
};
|
||||
});
|
||||
302
gaia/apps/settings/js/modules/dialog_manager.js
Executable file
302
gaia/apps/settings/js/modules/dialog_manager.js
Executable file
|
|
@ -0,0 +1,302 @@
|
|||
/**
|
||||
* DialogManager is a singleton that will help DialogService to load panels
|
||||
* and control any transitions on panels.
|
||||
*
|
||||
* API:
|
||||
*
|
||||
* DialogManager.open(dialog, options);
|
||||
* DialogManager.close(dialog, options);
|
||||
*
|
||||
* @module DialogManager
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var PanelCache = require('modules/panel_cache');
|
||||
var LazyLoader = require('shared/lazy_loader');
|
||||
|
||||
var DialogManager = function() {
|
||||
this.OVERLAY_SELECTOR = '.settings-dialog-overlay';
|
||||
|
||||
this._overlayDOM = document.querySelector(this.OVERLAY_SELECTOR);
|
||||
};
|
||||
|
||||
DialogManager.prototype = {
|
||||
/**
|
||||
* load panel based on passed in panelId
|
||||
*
|
||||
* @memberOf DialogManager
|
||||
* @access private
|
||||
* @param {String} panelId
|
||||
* @return {Promise}
|
||||
*/
|
||||
_loadPanel: function dm__loadPanel(panelId) {
|
||||
var promise = new Promise(function(resolve, reject) {
|
||||
var panelElement = document.getElementById(panelId);
|
||||
if (panelElement.dataset.rendered) { // already initialized
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
panelElement.dataset.rendered = true;
|
||||
|
||||
// XXX remove SubPanel loader once sub panel are modulized
|
||||
if (panelElement.dataset.requireSubPanels) {
|
||||
// load the panel and its sub-panels (dependencies)
|
||||
// (load the main panel last because it contains the scripts)
|
||||
var selector = 'section[id^="' + panelElement.id + '-"]';
|
||||
var subPanels = document.querySelectorAll(selector);
|
||||
for (var i = 0, il = subPanels.length; i < il; i++) {
|
||||
LazyLoader.load([subPanels[i]]);
|
||||
}
|
||||
LazyLoader.load([panelElement], resolve);
|
||||
} else {
|
||||
LazyLoader.load([panelElement], resolve);
|
||||
}
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* promised version of PanelCache.get()
|
||||
*
|
||||
* @memberOf DialogManager
|
||||
* @access private
|
||||
* @param {String} panelId
|
||||
* @return {Promise}
|
||||
*/
|
||||
_getPanel: function dm__getPanel(panelId) {
|
||||
var promise = new Promise(function(resolve) {
|
||||
PanelCache.get(panelId, function(panel) {
|
||||
resolve(panel);
|
||||
});
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* this is used to control visibility of overlayDOM
|
||||
*
|
||||
* @memberOf DialogManager
|
||||
* @access private
|
||||
* @param {Boolean} show
|
||||
*/
|
||||
_showOverlay: function dm__showOverlay(show) {
|
||||
this._overlayDOM.hidden = !show;
|
||||
},
|
||||
|
||||
/**
|
||||
* It is used to control the timing of transitions so that we can make sure
|
||||
* whether animation is done or not.
|
||||
*
|
||||
* @memberOf DialogManager
|
||||
* @access private
|
||||
* @param {String} method
|
||||
* @param {BaseDialog} dialog
|
||||
* @param {Object} options
|
||||
* @return {Promise}
|
||||
*/
|
||||
_transit: function dm__transit(method, dialog, options) {
|
||||
var promise = new Promise(function(resolve) {
|
||||
var panel = dialog.panel;
|
||||
|
||||
panel.addEventListener('transitionend', function paintWait(evt) {
|
||||
if ((method === 'close' || method === 'open') &&
|
||||
evt.propertyName === 'visibility') {
|
||||
// After transition, we have to `hide` the panel, otherwise
|
||||
// the panel would still exist on the layer and would block
|
||||
// the scrolling event.
|
||||
if (method === 'close') {
|
||||
panel.hidden = true;
|
||||
}
|
||||
panel.removeEventListener('transitionend', paintWait);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
|
||||
// Before transition, we have to `show` the panel, otherwise
|
||||
// the panel before applying transition class.
|
||||
if (method === 'open') {
|
||||
panel.hidden = false;
|
||||
}
|
||||
|
||||
// We need to apply class later otherwise Gecko can't apply
|
||||
// this transition and 150ms is an approximate number after doing
|
||||
// several rounds of manual tests.
|
||||
setTimeout(function() {
|
||||
if (method === 'open') {
|
||||
// Let's unhide the panel first
|
||||
panel.classList.add('current');
|
||||
} else {
|
||||
panel.classList.remove('current');
|
||||
}
|
||||
}, 150);
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Do necessary works to open panel like loading panel, doing transition
|
||||
* and call related functions.
|
||||
*
|
||||
* @memberOf DialogManager
|
||||
* @access private
|
||||
* @param {BaseDialog} dialog
|
||||
* @param {Object} options
|
||||
* @return {Promise}
|
||||
*/
|
||||
_open: function dm__open(dialog, options) {
|
||||
var self = this;
|
||||
var foundPanel;
|
||||
|
||||
return Promise.resolve()
|
||||
.then(function() {
|
||||
// 1: load panel
|
||||
return self._loadPanel(dialog.panel.id);
|
||||
})
|
||||
.then(function() {
|
||||
// 2: Get that panel
|
||||
return self._getPanel(dialog.panel.id);
|
||||
})
|
||||
.then(function(panel) {
|
||||
// 3: call beforeShow
|
||||
foundPanel = panel;
|
||||
return foundPanel.beforeShow(dialog.panel, options);
|
||||
})
|
||||
.then(function() {
|
||||
// 4. UI stuffs + transition
|
||||
dialog.init();
|
||||
dialog.initUI();
|
||||
dialog.bindEvents();
|
||||
|
||||
if (dialog.TRANSITION_CLASS === 'zoom-in-80') {
|
||||
self._showOverlay(true);
|
||||
}
|
||||
|
||||
return self._transit('open', dialog, options);
|
||||
})
|
||||
.then(function() {
|
||||
// 5. show that panel as a dialog
|
||||
return foundPanel.show(dialog.panel, options);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Do necessary works to close panel like loading panel, doing transition
|
||||
* and call related functions.
|
||||
*
|
||||
* @memberOf DialogManager
|
||||
* @access private
|
||||
* @param {BaseDialog} dialog
|
||||
* @param {Object} options
|
||||
* @return {Promise}
|
||||
*/
|
||||
_close: function dm__close(dialog, options) {
|
||||
var self = this;
|
||||
var foundPanel;
|
||||
var cachedResult;
|
||||
|
||||
return Promise.resolve()
|
||||
.then(function() {
|
||||
// 1: Get that panel
|
||||
return self._getPanel(dialog.panel.id);
|
||||
})
|
||||
.then(function(panel) {
|
||||
// 2: Let's validate to see whether we can close this dialog or not.
|
||||
foundPanel = panel;
|
||||
|
||||
var promise;
|
||||
// custom dialog - onSubmit
|
||||
if (foundPanel.onSubmit && options._type === 'submit') {
|
||||
promise = foundPanel.onSubmit();
|
||||
// custom dialog - onCancel
|
||||
} else if (foundPanel.onCancel && options._type === 'cancel') {
|
||||
promise = foundPanel.onCancel();
|
||||
// if no onSubmit & onCancel, pass directly
|
||||
} else {
|
||||
promise = Promise.resolve();
|
||||
}
|
||||
|
||||
return promise;
|
||||
})
|
||||
.then(function(result) {
|
||||
cachedResult = result;
|
||||
|
||||
// 3: call beforeHide
|
||||
return foundPanel.beforeHide();
|
||||
})
|
||||
.then(function() {
|
||||
// 4. transition
|
||||
return self._transit('close', dialog, options);
|
||||
})
|
||||
.then(function() {
|
||||
// 5. call hide
|
||||
return foundPanel.hide();
|
||||
})
|
||||
.then(function() {
|
||||
// 6. Get result and cleanup dialog
|
||||
var result;
|
||||
|
||||
// for prompt dialog, we have to get its own result from input text.
|
||||
if (dialog.DIALOG_CLASS === 'prompt-dialog') {
|
||||
result = dialog.getResult();
|
||||
} else if (cachedResult) {
|
||||
result = cachedResult;
|
||||
}
|
||||
|
||||
dialog.cleanup();
|
||||
|
||||
if (dialog.TRANSITION_CLASS === 'zoom-in-80') {
|
||||
self._showOverlay(false);
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* It is a bridge to call open or close function.
|
||||
*
|
||||
* @memberOf DialogManager
|
||||
* @access private
|
||||
* @param {String} method
|
||||
* @param {BaseDialog} dialog
|
||||
* @param {Object} options
|
||||
* @return {Promise}
|
||||
*/
|
||||
_navigate: function dm__navigate(method, dialog, options) {
|
||||
method = (method === 'open') ? '_open' : '_close';
|
||||
return this[method](dialog, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* DialogService would use this exposed API to open dialog.
|
||||
*
|
||||
* @memberOf DialogManager
|
||||
* @access public
|
||||
* @param {BaseDialog} dialog
|
||||
* @param {Object} options
|
||||
* @return {Promise}
|
||||
*/
|
||||
open: function dm_open(dialog, options) {
|
||||
return this._navigate('open', dialog, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* DialogService would use this exposed API to close dialog.
|
||||
*
|
||||
* @memberOf DialogManager
|
||||
* @access public
|
||||
* @param {BaseDialog} dialog
|
||||
* @param {Object} options
|
||||
* @return {Promise}
|
||||
*/
|
||||
close: function dm_close(dialog, type, options) {
|
||||
options._type = type;
|
||||
return this._navigate('close', dialog, options);
|
||||
}
|
||||
};
|
||||
|
||||
var dialogManager = new DialogManager();
|
||||
return dialogManager;
|
||||
});
|
||||
52
gaia/apps/settings/js/modules/dialog_panel.js
Executable file
52
gaia/apps/settings/js/modules/dialog_panel.js
Executable file
|
|
@ -0,0 +1,52 @@
|
|||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var Panel = require('modules/panel');
|
||||
|
||||
var DialogPanel = function ctor_dialogPanel(panelOptions) {
|
||||
var panel = Panel(panelOptions);
|
||||
|
||||
var _emptyFunc = function() {};
|
||||
var submitFunction = _emptyFunc;
|
||||
var cancelFunction = _emptyFunc;
|
||||
|
||||
// Because we need to bridge onWrapSubmit & onWrapCancel with panelOptions
|
||||
// together, we have to override panel.init() to make sure we can access
|
||||
// these API here.
|
||||
//
|
||||
// When init() is called, we will automatically make submitFunction &
|
||||
// cancelFunction's reference work and link to onWrapSubmit & onWrapCancel,
|
||||
// in this way, we can call `this.submit()` & `this.cancel()` in
|
||||
// panelOptions.
|
||||
var originalInit = panel.init;
|
||||
panel.init = function(panel, userOptions) {
|
||||
submitFunction = userOptions.onWrapSubmit.bind(userOptions);
|
||||
cancelFunction = userOptions.onWrapCancel.bind(userOptions);
|
||||
return originalInit.apply(panel, arguments);
|
||||
};
|
||||
|
||||
// We have to extend two more functions for it
|
||||
panel.onSubmit = panelOptions.onSubmit || _emptyFunc;
|
||||
panel.onCancel = panelOptions.onCancel || _emptyFunc;
|
||||
|
||||
// Let's rebind the scope on options
|
||||
panel.onSubmit = panel.onSubmit.bind(panelOptions);
|
||||
panel.onCancel = panel.onCancel.bind(panelOptions);
|
||||
|
||||
// Callee can use `this.submit()` to programmatically submit()
|
||||
// this dialog.
|
||||
panelOptions.submit = function() {
|
||||
submitFunction();
|
||||
};
|
||||
|
||||
// Callee can use `this.cancel()` to programmatically cancel()
|
||||
// this dialog.
|
||||
panelOptions.cancel = function() {
|
||||
cancelFunction();
|
||||
};
|
||||
|
||||
return panel;
|
||||
};
|
||||
|
||||
return DialogPanel;
|
||||
});
|
||||
256
gaia/apps/settings/js/modules/dialog_service.js
Executable file
256
gaia/apps/settings/js/modules/dialog_service.js
Executable file
|
|
@ -0,0 +1,256 @@
|
|||
/**
|
||||
* DialogService is a singleton that provides few ways for you to show/hide
|
||||
* dialogs. Here, we predefined alert/confirm/prompt dialogs to replace
|
||||
* window.alert/window.confirm/window.prompt if you want any further controls
|
||||
* of animations and UI.
|
||||
*
|
||||
* And also, there is one more dialog called panelDialog that would be used
|
||||
* when you are going to show any predefined panel in dialog way.
|
||||
*
|
||||
* API:
|
||||
*
|
||||
* 1. Alert dialog
|
||||
*
|
||||
* DialogService.alert({
|
||||
* id: 'MessageId',
|
||||
* args: {}
|
||||
* }, {
|
||||
* title: { id: 'TitleId', args: {} }
|
||||
* })
|
||||
* .then(function(result) {
|
||||
* var type = result.type;
|
||||
* });
|
||||
*
|
||||
* NOTE:
|
||||
* If there is no args in locales, you can direclty pass l10nId without args.
|
||||
*
|
||||
* DialogService.alert('MessageId', {
|
||||
* title: 'TitleId'
|
||||
* })
|
||||
* .then(function(result) {
|
||||
* var type = result.type;
|
||||
* });
|
||||
*
|
||||
* 2. Confirm dialog
|
||||
*
|
||||
* DialogService.confirm({
|
||||
* id: 'MessageId',
|
||||
* args: {}
|
||||
* }, {
|
||||
* title: { id: 'TitleId', args: {} },
|
||||
* submitButton: { id: 'SubmitButtonId', args: {}, style: 'recommend' },
|
||||
* cancelButton: { id: 'CancelButtonId', args: {} }
|
||||
* })
|
||||
* .then(function(result) {
|
||||
* var type = result.type;
|
||||
* });
|
||||
*
|
||||
* 3. Prompt dialog
|
||||
*
|
||||
* DialogService.prompt({
|
||||
* id: 'MessageId',
|
||||
* args: {}
|
||||
* }, {
|
||||
* title: { id: 'TitleId', args: {} },
|
||||
* defaultValue: 'e.g. test@mozilla.com',
|
||||
* }).then(function(result) {
|
||||
* var type = result.type;
|
||||
* var value = result.value;
|
||||
* });
|
||||
*
|
||||
* 4. Panel dialog
|
||||
*
|
||||
* DialogService.show('screen-lcok', {
|
||||
* transition: 'zoom-in',
|
||||
* }).then(function(result) {
|
||||
* // type would be submit or cancel
|
||||
* var type = result.type;
|
||||
* var value = result.value;
|
||||
* });
|
||||
*
|
||||
* NOTES:
|
||||
* We support some customized options for each dialog, please check the API
|
||||
* below to know what you can customize !
|
||||
*
|
||||
* @module DialogService
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var Settings = require('settings');
|
||||
var Defer = require('modules/defer');
|
||||
var DialogManager = require('modules/dialog_manager');
|
||||
|
||||
var PanelDialog = require('modules/dialog/panel_dialog');
|
||||
var AlertDialog = require('modules/dialog/alert_dialog');
|
||||
var ConfirmDialog = require('modules/dialog/confirm_dialog');
|
||||
var PromptDialog = require('modules/dialog/prompt_dialog');
|
||||
|
||||
var DialogService = function() {
|
||||
this._navigating = false;
|
||||
this._pendingRequests = [];
|
||||
this._settingsAlertDialogId = 'settings-alert-dialog';
|
||||
this._settingsBaseDialogId = 'settings-base-dialog';
|
||||
this._settingsConfirmDialogId = 'settings-confirm-dialog';
|
||||
this._settingsPromptDialogId = 'settings-prompt-dialog';
|
||||
};
|
||||
|
||||
DialogService.prototype = {
|
||||
/**
|
||||
* Alert dialog with more controls.
|
||||
*
|
||||
* @memberOf DialogService
|
||||
* @access public
|
||||
* @param {String} message
|
||||
* @param {Object} userOptions
|
||||
* @return {Promise}
|
||||
*/
|
||||
alert: function(message, userOptions) {
|
||||
var options = userOptions || {};
|
||||
return this.show(this._settingsAlertDialogId, {
|
||||
type: 'alert',
|
||||
message: message,
|
||||
title: options.title,
|
||||
submitButton: options.submitButton
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Confirm dialog with more controls.
|
||||
*
|
||||
* @memberOf DialogService
|
||||
* @access public
|
||||
* @param {String} message
|
||||
* @param {Object} userOptions
|
||||
* @return {Promise}
|
||||
*/
|
||||
confirm: function(message, userOptions) {
|
||||
var options = userOptions || {};
|
||||
return this.show(this._settingsConfirmDialogId, {
|
||||
type: 'confirm',
|
||||
message: message,
|
||||
title: options.title,
|
||||
submitButton: options.submitButton,
|
||||
cancelButton: options.cancelButton
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Prompt dialog with more controls.
|
||||
*
|
||||
* @memberOf DialogService
|
||||
* @access public
|
||||
* @param {String} message
|
||||
* @param {Object} userOptions
|
||||
* @return {Promise}
|
||||
*/
|
||||
prompt: function(message, userOptions) {
|
||||
var options = userOptions || {};
|
||||
return this.show(this._settingsPromptDialogId, {
|
||||
type: 'prompt',
|
||||
message: message,
|
||||
title: options.title,
|
||||
defaultValue: options.defaultValue,
|
||||
submitButton: options.submitButton,
|
||||
cancelButton: options.cancelButton
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Panel dialog. If you are going to show any panel as a dialog,
|
||||
* you have to use this method to show them.
|
||||
*
|
||||
* @memberOf DialogService
|
||||
* @access public
|
||||
* @param {String} panelId
|
||||
* @param {Object} userOptions
|
||||
* @return {Promise}
|
||||
*/
|
||||
show: function dm_show(panelId, userOptions, _pendingDefer) {
|
||||
var self = this;
|
||||
var defer;
|
||||
var dialog;
|
||||
var dialogDOM = document.getElementById(panelId);
|
||||
var currentPanel = Settings.currentPanel;
|
||||
var options = userOptions || {};
|
||||
|
||||
if (_pendingDefer) {
|
||||
defer = _pendingDefer;
|
||||
} else {
|
||||
defer = Defer();
|
||||
}
|
||||
|
||||
if (this._navigating) {
|
||||
this._pendingRequests.push({
|
||||
defer: defer,
|
||||
panelId: panelId,
|
||||
userOptions: userOptions
|
||||
});
|
||||
} else {
|
||||
if ('#' + panelId === currentPanel) {
|
||||
defer.reject('You are showing the same panel #' + panelId);
|
||||
} else {
|
||||
options.onWrapSubmit = function() {
|
||||
DialogManager.close(dialog, 'submit', options)
|
||||
.then(function(result) {
|
||||
defer.resolve({
|
||||
type: 'submit',
|
||||
value: result
|
||||
});
|
||||
self._navigating = false;
|
||||
self._execPendingRequest();
|
||||
});
|
||||
};
|
||||
|
||||
options.onWrapCancel = function() {
|
||||
DialogManager.close(dialog, 'cancel', options)
|
||||
.then(function(result) {
|
||||
defer.resolve({
|
||||
type: 'cancel',
|
||||
value: result
|
||||
});
|
||||
self._navigating = false;
|
||||
self._execPendingRequest();
|
||||
});
|
||||
};
|
||||
|
||||
switch (options.type) {
|
||||
case 'alert':
|
||||
dialog = AlertDialog(dialogDOM, options);
|
||||
break;
|
||||
case 'confirm':
|
||||
dialog = ConfirmDialog(dialogDOM, options);
|
||||
break;
|
||||
case 'prompt':
|
||||
dialog = PromptDialog(dialogDOM, options);
|
||||
break;
|
||||
default:
|
||||
dialog = PanelDialog(dialogDOM, options);
|
||||
break;
|
||||
}
|
||||
this._navigating = true;
|
||||
DialogManager.open(dialog, options);
|
||||
}
|
||||
}
|
||||
|
||||
return defer.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* This method can help us pop up any pending request and would try to
|
||||
* show it after previous request was done.
|
||||
*
|
||||
* @memberOf DialogService
|
||||
* @access private
|
||||
*/
|
||||
_execPendingRequest: function() {
|
||||
var request = this._pendingRequests.pop();
|
||||
if (request) {
|
||||
this.show(request.panelId, request.userOptions, request.defer);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var dialogService = new DialogService();
|
||||
return dialogService;
|
||||
});
|
||||
123
gaia/apps/settings/js/modules/fdn_context.js
Executable file
123
gaia/apps/settings/js/modules/fdn_context.js
Executable file
|
|
@ -0,0 +1,123 @@
|
|||
/**
|
||||
* FdnContext is a module that you can easily fetch out FDN related info.
|
||||
*
|
||||
* @module FdnContext
|
||||
*/
|
||||
define(function() {
|
||||
'use strict';
|
||||
|
||||
var FdnContext = {
|
||||
/**
|
||||
* cached fdnContacts
|
||||
*
|
||||
* @type {Array}
|
||||
*/
|
||||
_fdnContacts: [],
|
||||
|
||||
/**
|
||||
* internal function to help us clone object
|
||||
*
|
||||
* @param {Object} obj
|
||||
*/
|
||||
_cloneObject: function(obj) {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
},
|
||||
|
||||
/**
|
||||
* we can use this method to get wrapped contacts information.
|
||||
*
|
||||
* @param {Number} cardIndex
|
||||
* @type {Function}
|
||||
* @return {Promise}
|
||||
*/
|
||||
getContacts: function(cardIndex) {
|
||||
var promise = new Promise((resolve, reject) => {
|
||||
var iccId = navigator.mozMobileConnections[cardIndex].iccId;
|
||||
|
||||
if (!iccId) {
|
||||
console.log('can\'t get right iccId');
|
||||
reject();
|
||||
}
|
||||
|
||||
var icc = navigator.mozIccManager.getIccById(iccId);
|
||||
if (!icc) {
|
||||
console.log('Could not retrieve ICC object');
|
||||
reject();
|
||||
}
|
||||
|
||||
var request = icc.readContacts('fdn');
|
||||
|
||||
request.onerror = (error) => {
|
||||
console.log('we got error when reading contacts from icc');
|
||||
console.log(error);
|
||||
reject();
|
||||
};
|
||||
|
||||
request.onsuccess = () => {
|
||||
var result = this._fdnContacts[cardIndex] = request.result;
|
||||
var contacts = [];
|
||||
for (var i = 0, l = result.length; i < l; i++) {
|
||||
contacts.push({
|
||||
id: i,
|
||||
name: result[i].name || '',
|
||||
number: result[i].tel[0].value || ''
|
||||
});
|
||||
}
|
||||
resolve(contacts);
|
||||
};
|
||||
});
|
||||
|
||||
return promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* This function returns a FDN contact object matching the
|
||||
* requested action.
|
||||
*
|
||||
* mozIccManager.updateContact works like this:
|
||||
* - no id: create a new contact
|
||||
* - existing id + name and number: update a contact
|
||||
* - existing id + empty name and number: remove a contact
|
||||
*
|
||||
* @type {Function}
|
||||
* @param {String} action
|
||||
* @param {Number} options.cardIndex
|
||||
* @param {Object} options.contact
|
||||
* @return {mozContact}
|
||||
*/
|
||||
createAction: function(action, options) {
|
||||
var simContact = {};
|
||||
var cardIndex = options.cardIndex;
|
||||
var contact = options.contact;
|
||||
switch (action) {
|
||||
case 'add':
|
||||
simContact.name = [contact.name];
|
||||
simContact.tel = [{
|
||||
value: contact.number
|
||||
}];
|
||||
break;
|
||||
case 'edit':
|
||||
simContact =
|
||||
this._cloneObject(this._fdnContacts[cardIndex][contact.id]);
|
||||
simContact.name[0] = contact.name;
|
||||
simContact.tel[0].value = contact.number;
|
||||
break;
|
||||
case 'remove':
|
||||
simContact =
|
||||
this._cloneObject(this._fdnContacts[cardIndex][contact.id]);
|
||||
simContact.name[0] = '';
|
||||
simContact.tel[0].value = '';
|
||||
break;
|
||||
}
|
||||
|
||||
var result = new window.mozContact(simContact);
|
||||
if ('id' in simContact) {
|
||||
result.id = simContact.id;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
return FdnContext;
|
||||
});
|
||||
301
gaia/apps/settings/js/modules/keyboard_context.js
Executable file
301
gaia/apps/settings/js/modules/keyboard_context.js
Executable file
|
|
@ -0,0 +1,301 @@
|
|||
/**
|
||||
* KeyboardContext provides installed keyboard apps and enabled keyboard layouts
|
||||
* in terms of ObservableArrays. It listens to the events from KeyboardHelper
|
||||
* and update the ObservableArrays.
|
||||
* KeyboardHelper helps on the following things:
|
||||
* - Get all installed keyboard apps and layouts.
|
||||
* - Enable or disable keyboard layouts.
|
||||
* - Notify keyboard layout changes via the 'keyboardsrefresh' event.
|
||||
* KeyboardContext handles only data and does not involve in any UI logic.
|
||||
*
|
||||
* @module KeyboardContext
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var Observable = require('modules/mvvm/observable');
|
||||
var ObservableArray = require('modules/mvvm/observable_array');
|
||||
var KeyboardHelper = require('shared/keyboard_helper');
|
||||
var ManifestHelper = require('shared/manifest_helper');
|
||||
|
||||
// stores layout indexed by app manifestURL and layoutId
|
||||
var _layoutDict = null;
|
||||
|
||||
var _keyboards = ObservableArray([]);
|
||||
var _enabledLayouts = ObservableArray([]);
|
||||
|
||||
var _isReady = false;
|
||||
var _parsingApps = false;
|
||||
var _callbacks = [];
|
||||
var _defaultEnabledCallbacks = [];
|
||||
|
||||
/**
|
||||
* @alias module:Keyboard
|
||||
* @class Keyboard
|
||||
* @param {String} name
|
||||
The name of the keyboard.
|
||||
* @param {String} description
|
||||
The description of the keyboard.
|
||||
* @param {String} launchPath
|
||||
The launch path of the keyboard.
|
||||
* @param {Array} layouts
|
||||
All layouts included in the keyboard.
|
||||
* @param {DOMApplication} app
|
||||
The keyboard app object.
|
||||
* @returns {Keyboard}
|
||||
*/
|
||||
var Keyboard = function(name, description, launchPath, layouts, app) {
|
||||
var _observable = Observable({
|
||||
name: name,
|
||||
description: description,
|
||||
launchPath: launchPath,
|
||||
layouts: layouts,
|
||||
app: app
|
||||
});
|
||||
|
||||
return _observable;
|
||||
};
|
||||
|
||||
/**
|
||||
* @alias module:Layout
|
||||
* @class Layout
|
||||
* @param {Object} Contain the following properties
|
||||
* - id
|
||||
The id of the layout.
|
||||
* - appName
|
||||
The name of the keyboard app containing the layout.
|
||||
* - appManifestURL
|
||||
The manifest url of the keyboard app containing
|
||||
the layout.
|
||||
* - name
|
||||
The name of the layout.
|
||||
* - nameL10nId
|
||||
The l10n id of the layout name.
|
||||
* - description
|
||||
The description of the layout.
|
||||
* - types
|
||||
The supported input types of the layout.
|
||||
* - enabled
|
||||
The value indicating if the layout is enabled or not.
|
||||
* @returns {Layout}
|
||||
*/
|
||||
var Layout = function(options) {
|
||||
var _observable = Observable({
|
||||
id: options.id,
|
||||
appName: options.appName,
|
||||
name: options.name,
|
||||
nameL10nId: options.nameL10nId,
|
||||
description: options.description,
|
||||
types: options.types,
|
||||
enabled: options.enabled
|
||||
});
|
||||
|
||||
// Layout enabled changed.
|
||||
_observable.observe('enabled', function(newValue, oldValue) {
|
||||
if (!_parsingApps) {
|
||||
KeyboardHelper.setLayoutEnabled(
|
||||
options.appManifestURL, options.id, newValue);
|
||||
// only check the defaults if we disabled a checkbox
|
||||
if (!newValue) {
|
||||
KeyboardHelper.checkDefaults(function(layouts, missingTypes) {
|
||||
refreshEnabledLayouts(layouts);
|
||||
notifyDefaultEnabled(layouts, missingTypes);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return _observable;
|
||||
};
|
||||
|
||||
var _waitForLayouts;
|
||||
|
||||
function refreshEnabledLayouts(reEnabledLayouts) {
|
||||
reEnabledLayouts.forEach(function(layout) {
|
||||
var app = _layoutDict[layout.app.manifestURL];
|
||||
if (app) {
|
||||
app[layout.layoutId].enabled = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function notifyDefaultEnabled(layouts, missingTypes) {
|
||||
_defaultEnabledCallbacks.forEach(function withCallbacks(callback) {
|
||||
callback(layouts[0], missingTypes[0]);
|
||||
});
|
||||
}
|
||||
|
||||
function updateLayouts(layouts, reason) {
|
||||
function mapLayout(layout) {
|
||||
var app = _layoutDict[layout.app.manifestURL];
|
||||
if (!app) {
|
||||
app = _layoutDict[layout.app.manifestURL] = {};
|
||||
}
|
||||
if (app[layout.layoutId]) {
|
||||
app[layout.layoutId].enabled = layout.enabled;
|
||||
return app[layout.layoutId];
|
||||
}
|
||||
|
||||
app[layout.layoutId] = Layout({
|
||||
id: layout.layoutId,
|
||||
appName: layout.manifest.name,
|
||||
appManifestURL: layout.app.manifestURL,
|
||||
name: layout.inputManifest.name,
|
||||
nameL10nId: layout.inputManifest.nameL10nId,
|
||||
description: layout.inputManifest.description,
|
||||
types: layout.inputManifest.types,
|
||||
enabled: layout.enabled});
|
||||
return app[layout.layoutId];
|
||||
}
|
||||
|
||||
function reduceApps(carry, layout) {
|
||||
// if we already found this app, add it to the layouts
|
||||
if (!carry.some(function checkApp(app) {
|
||||
if (app.app === layout.app) {
|
||||
app.layouts.push(mapLayout(layout));
|
||||
return true;
|
||||
}
|
||||
})) {
|
||||
carry.push({
|
||||
app: layout.app,
|
||||
manifest: layout.manifest,
|
||||
layouts: [mapLayout(layout)]
|
||||
});
|
||||
}
|
||||
return carry;
|
||||
}
|
||||
|
||||
function mapKeyboard(app) {
|
||||
return Keyboard(app.manifest.name, app.manifest.description,
|
||||
app.manifest.launch_path, app.layouts, app.app);
|
||||
}
|
||||
|
||||
_parsingApps = true;
|
||||
|
||||
// if we changed apps
|
||||
if (reason.apps) {
|
||||
// re parse every layout
|
||||
_layoutDict = {};
|
||||
var apps = layouts.reduce(reduceApps, []);
|
||||
var keyboards = apps.map(mapKeyboard);
|
||||
_keyboards.reset(keyboards);
|
||||
}
|
||||
var enabled = layouts.filter(function filterEnabled(layout) {
|
||||
return layout.enabled;
|
||||
}).map(mapLayout);
|
||||
_enabledLayouts.reset(enabled);
|
||||
|
||||
_parsingApps = false;
|
||||
|
||||
if (_waitForLayouts) {
|
||||
_waitForLayouts();
|
||||
_waitForLayouts = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
var _init = function(callback) {
|
||||
document.addEventListener('DOMRetranslated', function() {
|
||||
// refresh keyboard and layout in _keyboards
|
||||
_keyboards.forEach(function(keyboard) {
|
||||
var keyboardAppInstance = keyboard.app;
|
||||
var keyboardManifest =
|
||||
new ManifestHelper(keyboardAppInstance.manifest);
|
||||
keyboard.name = keyboardManifest.name;
|
||||
keyboard.description = keyboardManifest.description;
|
||||
keyboard.layouts.forEach(function(layout) {
|
||||
layout.appName = keyboardManifest.name;
|
||||
});
|
||||
});
|
||||
});
|
||||
_waitForLayouts = callback;
|
||||
KeyboardHelper.stopWatching();
|
||||
KeyboardHelper.watchLayouts(updateLayouts);
|
||||
};
|
||||
|
||||
var _ready = function(callback) {
|
||||
if (!callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_isReady) {
|
||||
callback();
|
||||
} else {
|
||||
_callbacks.push(callback);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
/**
|
||||
* Reset the keyboard context. It clears all cached data of installed
|
||||
* keyboards and current enabled layouts.
|
||||
*
|
||||
* @alias module:KeyboardContext#reset
|
||||
*/
|
||||
reset: function kc_reset() {
|
||||
_layoutDict = null;
|
||||
_keyboards = ObservableArray([]);
|
||||
_enabledLayouts = ObservableArray([]);
|
||||
_isReady = false;
|
||||
_parsingApps = false;
|
||||
_callbacks = [];
|
||||
_defaultEnabledCallbacks = [];
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize the keyboard context. After the context initialized, we are
|
||||
* able to get the installed keyboards and enabled layouts.
|
||||
*
|
||||
* @alias module:KeyboardContext#init
|
||||
* @param {Function} callback
|
||||
* The callback when the context is initialized.
|
||||
*/
|
||||
init: function kc_init(callback) {
|
||||
_defaultEnabledCallbacks = [];
|
||||
_isReady = false;
|
||||
_init(function() {
|
||||
_isReady = true;
|
||||
_callbacks.forEach(function(callback) {
|
||||
callback();
|
||||
});
|
||||
});
|
||||
_ready(callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the installed keyboards in terms of an observable array.
|
||||
*
|
||||
* @alias module:KeyboardContext#keyboards
|
||||
* @param {Function} callback
|
||||
* The result is passed to the callback when ready.
|
||||
*/
|
||||
keyboards: function kc_keyboards(callback) {
|
||||
_ready(function() {
|
||||
callback(_keyboards);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the enabled layouts in terms of an observable array.
|
||||
*
|
||||
* @alias module:KeyboardContext#enabledLayouts
|
||||
* @param {Function} callback
|
||||
* The result is passed to the callback when ready.
|
||||
*/
|
||||
enabledLayouts: function kc_enabledLayouts(callback) {
|
||||
_ready(function() {
|
||||
callback(_enabledLayouts);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a callback to be triggered when the default keyboard is enabled.
|
||||
*
|
||||
* @alias module:KeyboardContext#defaultKeyboardEnabled
|
||||
* @param {Function} callback
|
||||
* The callback to be triggered.
|
||||
*/
|
||||
defaultKeyboardEnabled: function kc_defaultKeyboardEnabled(callback) {
|
||||
_defaultEnabledCallbacks.push(callback);
|
||||
}
|
||||
};
|
||||
});
|
||||
112
gaia/apps/settings/js/modules/media_storage.js
Executable file
112
gaia/apps/settings/js/modules/media_storage.js
Executable file
|
|
@ -0,0 +1,112 @@
|
|||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var Module = require('modules/base/module');
|
||||
var Observable = require('modules/mvvm/observable');
|
||||
var SettingsListener = require('shared/settings_listener');
|
||||
|
||||
/**
|
||||
* @class MediaVolume
|
||||
* @requires module:modules/base/module
|
||||
* @requires module:modules/mvvm/observable
|
||||
* @returns {MediaVolume}
|
||||
*/
|
||||
var MediaStorage = Module.create(function MediaStorage() {
|
||||
this.super(Observable).call(this);
|
||||
|
||||
this._defaultMediaVolume = null;
|
||||
this._defaultMediaVolumeKey = 'device.storage.writable.name';
|
||||
|
||||
// Monitor default media volume state
|
||||
SettingsListener.observe(this._defaultMediaVolumeKey, 'sdcard',
|
||||
this._mediaVolumeChangeHandler.bind(this));
|
||||
}).extend(Observable);
|
||||
|
||||
Observable.defineObservableProperty(MediaStorage.prototype,
|
||||
'volumeState', {
|
||||
readonly: true,
|
||||
value: 'available'
|
||||
});
|
||||
|
||||
Observable.defineObservableProperty(MediaStorage.prototype,
|
||||
'freeSize', {
|
||||
readonly: true,
|
||||
value: 0
|
||||
});
|
||||
|
||||
MediaStorage.prototype.handleEvent = function ms_handler(evt) {
|
||||
switch (evt.type) {
|
||||
case 'change':
|
||||
// we are handling storage state changes
|
||||
// possible state: available, unavailable, shared
|
||||
this._updateMediaStorageInfo();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
MediaStorage.prototype._mediaVolumeChangeHandler =
|
||||
function ms__mediaVolumeChangeHandler(defaultName) {
|
||||
if (this._defaultMediaVolume) {
|
||||
this._defaultMediaVolume.removeEventListener('change', this);
|
||||
}
|
||||
this._defaultMediaVolume = this._getDefaultVolume(defaultName);
|
||||
if (this._defaultMediaVolume) {
|
||||
this._defaultMediaVolume.addEventListener('change', this);
|
||||
}
|
||||
this._updateMediaStorageInfo();
|
||||
};
|
||||
|
||||
// Media Storage
|
||||
MediaStorage.prototype._updateMediaStorageInfo =
|
||||
function ms__updateMediaStorageInfo() {
|
||||
if (!this._defaultMediaVolume) {
|
||||
this._updateVolumeState(null, 'unavailable');
|
||||
return;
|
||||
}
|
||||
|
||||
var req = this._defaultMediaVolume.available();
|
||||
req.onsuccess = evt => {
|
||||
var state = evt.target.result;
|
||||
var firstVolume = navigator.getDeviceStorages('sdcard')[0];
|
||||
// if the default storage is unavailable, and it's not the
|
||||
// internal storage, we show the internal storage status instead.
|
||||
if (state === 'unavailable' &&
|
||||
this._defaultMediaVolume.storageName !== firstVolume.storageName) {
|
||||
firstVolume.available().onsuccess = e => {
|
||||
this._updateVolumeState(firstVolume, e.target.result);
|
||||
};
|
||||
} else {
|
||||
this._updateVolumeState(this._defaultMediaVolume, state);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
MediaStorage.prototype._updateVolumeState =
|
||||
function ms__updateVolumeState(volume, state) {
|
||||
this._volumeState = state;
|
||||
|
||||
volume.freeSpace().onsuccess = (e) => {
|
||||
this._freeSize = e.target.result;
|
||||
};
|
||||
};
|
||||
|
||||
// util function
|
||||
MediaStorage.prototype._getDefaultVolume =
|
||||
function ms__getDefaultVolume(name) {
|
||||
// Per API design, all media type return the same volumes.
|
||||
// So we use 'sdcard' here for no reason.
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=856782#c10
|
||||
var volumes = navigator.getDeviceStorages('sdcard');
|
||||
if (!name || name === '') {
|
||||
return volumes[0];
|
||||
}
|
||||
for (var i = 0; i < volumes.length; ++i) {
|
||||
if (volumes[i].storageName === name) {
|
||||
return volumes[i];
|
||||
}
|
||||
}
|
||||
return volumes[0];
|
||||
};
|
||||
|
||||
return MediaStorage();
|
||||
});
|
||||
207
gaia/apps/settings/js/modules/messaging.js
Executable file
207
gaia/apps/settings/js/modules/messaging.js
Executable file
|
|
@ -0,0 +1,207 @@
|
|||
/*
|
||||
* XXX
|
||||
* This module should be used only by `messaging` and `messaging_detail`
|
||||
* panels only.
|
||||
*/
|
||||
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
// Constants
|
||||
var CBS_KEY = 'ril.cellbroadcast.disabled';
|
||||
var CMAS_KEY = 'cmas.enabled';
|
||||
var CMAS_PREF_KEY = 'cmas.user-pref.enabled';
|
||||
|
||||
var IccHelper = require('shared/icc_helper');
|
||||
var asyncStorage = require('shared/async_storage');
|
||||
var SettingsUtils = require('modules/settings_utils');
|
||||
var SettingsListener = require('shared/settings_listener');
|
||||
var settings = window.navigator.mozSettings;
|
||||
|
||||
var Messaging = function() {
|
||||
this._cardIndex = 0;
|
||||
|
||||
this._cbs = null;
|
||||
this._cbsInput = null;
|
||||
this._cbsInit = false;
|
||||
|
||||
this._cmas = null;
|
||||
this._cmasInput = null;
|
||||
this._cmasInit = false;
|
||||
|
||||
this._boundCBSChanged = this._cbsChanged.bind(this);
|
||||
this._boundCBSInputChanged = this._cbsInputChanged.bind(this);
|
||||
this._boundCMASInputChanged = this._cmasInputChanged.bind(this);
|
||||
};
|
||||
|
||||
Messaging.prototype = {
|
||||
_disableItems: function m__disableItems(panel, disable) {
|
||||
var elementIds = [
|
||||
'menuItem-deliveryReport',
|
||||
'menuItem-readReport',
|
||||
'menuItem-autoRetrieve',
|
||||
'menuItem-wapPush',
|
||||
'menuItem-cellBroadcast',
|
||||
'menuItem-emergencyAlert'
|
||||
];
|
||||
elementIds.forEach(function(id) {
|
||||
var element = panel.querySelector(id);
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (disable) {
|
||||
element.setAttribute('aria-disabled', true);
|
||||
} else {
|
||||
element.removeAttribute('aria-disabled');
|
||||
}
|
||||
var input = element.querySelector('gaia-switch');
|
||||
if (!input) {
|
||||
input = element.querySelector('select');
|
||||
}
|
||||
input.disabled = disable;
|
||||
});
|
||||
},
|
||||
_setCMAS: function m__setCMAS(enabled) {
|
||||
var cardIndex = this._cardIndex;
|
||||
this._cmasInput.checked = enabled;
|
||||
|
||||
// get first
|
||||
var getReq = settings.createLock().get(CMAS_KEY);
|
||||
getReq.onsuccess = function() {
|
||||
var values = getReq.result[CMAS_KEY];
|
||||
values[cardIndex] = enabled;
|
||||
|
||||
var cmasset = {};
|
||||
cmasset[CMAS_KEY] = values;
|
||||
|
||||
// then set
|
||||
settings.createLock().set(cmasset);
|
||||
};
|
||||
},
|
||||
_cbsChanged: function m__cbsChanged(cbsSettings) {
|
||||
var cardIndex = this._cardIndex;
|
||||
var value = cbsSettings[cardIndex];
|
||||
this._cbsInput.checked = !value;
|
||||
|
||||
if (value) {
|
||||
// when CBS off, CMAS is also off
|
||||
this._setCMAS(false);
|
||||
} else {
|
||||
// when CBS on, retained CMAS pref
|
||||
this._restoreCMASPref();
|
||||
}
|
||||
|
||||
this._cmasInput.disabled = value; // CMAS disabled when CBS off
|
||||
|
||||
if (!this._cbsInit) {
|
||||
this._cbsInput.disabled = false;
|
||||
this._cbs.removeAttribute('aria-disabled');
|
||||
this._cbsInit = true;
|
||||
}
|
||||
},
|
||||
_cbsInputChanged: function m__cbsInputChanged() {
|
||||
var cardIndex = this._cardIndex;
|
||||
var cbsInputChecked = this._cbsInput.checked;
|
||||
|
||||
if (!cbsInputChecked) { // save CMAS pref when CBS off
|
||||
this._storeCMASPref(this._cmasInput.checked);
|
||||
}
|
||||
|
||||
// get first
|
||||
var getReq = settings.createLock().get(CBS_KEY);
|
||||
getReq.onsuccess = function() {
|
||||
var values = getReq.result[CBS_KEY];
|
||||
values[cardIndex] = !cbsInputChecked;
|
||||
|
||||
var cbsset = {};
|
||||
cbsset[CBS_KEY] = values;
|
||||
|
||||
// then set
|
||||
settings.createLock().set(cbsset);
|
||||
};
|
||||
},
|
||||
_cmasInputChanged: function m__cmasInputChanged() {
|
||||
this._setCMAS(this._cmasInput.checked);
|
||||
this._storeCMASPref(this._cmasInput.checked);
|
||||
},
|
||||
// remember last CMAS pref when CBS off
|
||||
_storeCMASPref: function m__storeCMASPref(checked) {
|
||||
var cardIndex = this._cardIndex;
|
||||
asyncStorage.getItem(CMAS_PREF_KEY, function(pref) {
|
||||
var states = pref || [true, true];
|
||||
states[cardIndex] = checked;
|
||||
asyncStorage.setItem(CMAS_PREF_KEY, states);
|
||||
});
|
||||
},
|
||||
// check local storage to bring back CMAS pref
|
||||
_restoreCMASPref: function m__restoreCMASPref() {
|
||||
var cardIndex = this._cardIndex;
|
||||
asyncStorage.getItem(CMAS_PREF_KEY, function(pref) {
|
||||
var states = pref || [true, true];
|
||||
this._setCMAS(states[cardIndex]);
|
||||
|
||||
if (!this._cmasInit) {
|
||||
this._cmasInput.disabled = false;
|
||||
this._cmas.removeAttribute('aria-disabled');
|
||||
this._cmasInit = true;
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
initCBS: function m_initCBS(panel, cardIndex) {
|
||||
this._cardIndex = cardIndex;
|
||||
|
||||
// Cell Broadcast
|
||||
this._cbs = panel.querySelector('#menuItem-cellBroadcast');
|
||||
this._cbsInput = this._cbs.querySelector('gaia-switch');
|
||||
this._cbsInit = false;
|
||||
|
||||
// Emergency Alert
|
||||
this._cmas = panel.querySelector('#menuItem-emergencyAlert');
|
||||
this._cmasInput = this._cmas.querySelector('gaia-switch');
|
||||
this._cmasInit = false;
|
||||
|
||||
// cleanup first
|
||||
this._cbsInput.removeEventListener('change', this._boundCBSInputChanged);
|
||||
this._cmasInput.removeEventListener('change',
|
||||
this._boundCMASInputChanged);
|
||||
SettingsListener.unobserve(CBS_KEY, this._boundCBSChanged);
|
||||
|
||||
// reset again
|
||||
this._cbsInput.addEventListener('change', this._boundCBSInputChanged);
|
||||
this._cmasInput.addEventListener('change', this._boundCMASInputChanged);
|
||||
SettingsListener.observe(CBS_KEY, [false, false], this._boundCBSChanged);
|
||||
},
|
||||
disableItems: function m_disableItems(panel) {
|
||||
var self = this;
|
||||
|
||||
if (!settings || !IccHelper || IccHelper.cardState !== 'ready') {
|
||||
this._disableItems(panel, true);
|
||||
return;
|
||||
}
|
||||
|
||||
IccHelper.addEventListener('cardstatechange', function() {
|
||||
self._disableItems(panel, IccHelper.cardState !== 'ready');
|
||||
});
|
||||
|
||||
this._disableItems(panel, false);
|
||||
},
|
||||
injectCBSTemplate: function m_injectCBSTemplate(containerNode) {
|
||||
var promise = new Promise(function(resolve, reject) {
|
||||
var templateId = 'template-messaging';
|
||||
SettingsUtils.loadTemplate(templateId, function(html) {
|
||||
if (!html) {
|
||||
reject('We can\'t get the template ' + templateId);
|
||||
} else {
|
||||
containerNode.innerHTML = html;
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
};
|
||||
|
||||
var messaging = new Messaging();
|
||||
return messaging;
|
||||
});
|
||||
41
gaia/apps/settings/js/modules/mobile/supported_network_info.js
Executable file
41
gaia/apps/settings/js/modules/mobile/supported_network_info.js
Executable file
|
|
@ -0,0 +1,41 @@
|
|||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var NetworkType = require('modules/mobile/supported_network_type');
|
||||
|
||||
var SupportedNetworkInfo = {
|
||||
supportedNetworkTypeHelpers: [],
|
||||
|
||||
_getMobileConnectionIndex:
|
||||
function ni_getMobileConnectionIndex(mobileConnection) {
|
||||
return Array.prototype.indexOf.call(navigator.mozMobileConnections,
|
||||
mobileConnection);
|
||||
},
|
||||
|
||||
/**
|
||||
* The function returns an object of the supporting state of category of
|
||||
* network types. The categories are 'gsm', 'cdma', and 'lte'.
|
||||
*/
|
||||
getSupportedNetworkInfo:
|
||||
function getSupportedNetworkInfo(mobileConnection, callback) {
|
||||
if (!navigator.mozMobileConnections) {
|
||||
if (typeof callback === 'function') {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
var index = this._getMobileConnectionIndex(mobileConnection);
|
||||
var helper = this.supportedNetworkTypeHelpers[index];
|
||||
if (!helper) {
|
||||
this.supportedNetworkTypeHelpers[index] = helper =
|
||||
NetworkType.SupportedNetworkTypeHelper(
|
||||
mobileConnection.supportedNetworkTypes);
|
||||
}
|
||||
if (typeof callback === 'function') {
|
||||
callback(helper);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return SupportedNetworkInfo;
|
||||
});
|
||||
158
gaia/apps/settings/js/modules/mobile/supported_network_type.js
Executable file
158
gaia/apps/settings/js/modules/mobile/supported_network_type.js
Executable file
|
|
@ -0,0 +1,158 @@
|
|||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
// The network types displayed on the list for user selection
|
||||
const NETWORK_TYPES = [
|
||||
'wcdma/gsm',
|
||||
'gsm',
|
||||
'wcdma',
|
||||
'wcdma/gsm-auto',
|
||||
'cdma/evdo',
|
||||
'cdma',
|
||||
'evdo',
|
||||
'wcdma/gsm/cdma/evdo',
|
||||
'lte/cdma/evdo',
|
||||
'lte/wcdma/gsm',
|
||||
'lte/wcdma/gsm/cdma/evdo',
|
||||
'lte',
|
||||
'lte/wcdma'
|
||||
];
|
||||
|
||||
// The string map from network types to user friendly strings, which is
|
||||
// the combination of the three categories: 'GSM', 'CDMA', and 'LTE'.
|
||||
// The mapping is determined by the hardware supported types.
|
||||
const NETWORK_TYPE_STRING_MAP = {
|
||||
'CDMA': {
|
||||
'cdma/evdo': 'operator-networkType-auto',
|
||||
'cdma': 'operator-networkType-CDMA',
|
||||
'evdo': 'operator-networkType-EVDO'
|
||||
},
|
||||
'LTE': {
|
||||
'lte': 'operator-networkType-LTE'
|
||||
},
|
||||
'GSM': {
|
||||
'wcdma/gsm': 'operator-networkType-prefer3G',
|
||||
'gsm': 'operator-networkType-2G',
|
||||
'wcdma': 'operator-networkType-3G',
|
||||
'wcdma/gsm-auto': 'operator-networkType-auto'
|
||||
},
|
||||
'GSM,LTE': {
|
||||
'lte/wcdma/gsm': 'operator-networkType-auto',
|
||||
'lte/wcdma': 'operator-networkType-auto-3G-4G',
|
||||
'gsm': 'operator-networkType-2G',
|
||||
'wcdma': 'operator-networkType-3G',
|
||||
'wcdma/gsm-auto': 'operator-networkType-auto-2G-3G',
|
||||
'wcdma/gsm': 'operator-networkType-prefer3G',
|
||||
'lte': 'operator-networkType-LTE'
|
||||
},
|
||||
'CDMA,LTE': {
|
||||
'lte/cdma/evdo': 'operator-networkType-auto',
|
||||
'cdma/evdo': 'operator-networkType-auto-CDMA-EVDO',
|
||||
'cdma': 'operator-networkType-CDMA',
|
||||
'evdo': 'operator-networkType-EVDO',
|
||||
'lte': 'operator-networkType-LTE'
|
||||
},
|
||||
'GSM,CDMA': {
|
||||
'wcdma/gsm': 'operator-networkType-preferWCDMA',
|
||||
'gsm': 'operator-networkType-GSM',
|
||||
'wcdma': 'operator-networkType-WCDMA',
|
||||
'wcdma/gsm-auto': 'operator-networkType-auto-WCDMA-GSM',
|
||||
'cdma/evdo': 'operator-networkType-auto-CDMA-EVDO',
|
||||
'cdma': 'operator-networkType-CDMA',
|
||||
'evdo': 'operator-networkType-EVDO',
|
||||
'wcdma/gsm/cdma/evdo': 'operator-networkType-auto'
|
||||
},
|
||||
'GSM,CDMA,LTE': {
|
||||
'wcdma/gsm': 'operator-networkType-preferWCDMA',
|
||||
'gsm': 'operator-networkType-GSM',
|
||||
'wcdma': 'operator-networkType-WCDMA',
|
||||
'wcdma/gsm-auto': 'operator-networkType-auto-WCDMA-GSM',
|
||||
'cdma/evdo': 'operator-networkType-auto-CDMA-EVDO',
|
||||
'cdma': 'operator-networkType-CDMA',
|
||||
'evdo': 'operator-networkType-EVDO',
|
||||
'wcdma/gsm/cdma/evdo': 'operator-networkType-auto-WCDMA-GSM-CDMA-EVDO',
|
||||
'lte': 'operator-networkType-LTE',
|
||||
'lte/wcdma/gsm': 'operator-networkType-auto-LTE-WCDMA-GSM',
|
||||
'lte/wcdma': 'operator-networkType-auto-LTE-WCDMA',
|
||||
'lte/cdma/evdo': 'operator-networkType-auto-LTE-CDMA-EVDO',
|
||||
'lte/wcdma/gsm/cdma/evdo': 'operator-networkType-auto'
|
||||
},
|
||||
// Default value, the same as 'GSM,CDMA'.
|
||||
'': {
|
||||
'wcdma/gsm': 'operator-networkType-preferWCDMA',
|
||||
'gsm': 'operator-networkType-GSM',
|
||||
'wcdma': 'operator-networkType-WCDMA',
|
||||
'wcdma/gsm-auto': 'operator-networkType-auto-WCDMA-GSM',
|
||||
'cdma/evdo': 'operator-networkType-auto-CDMA-EVDO',
|
||||
'cdma': 'operator-networkType-CDMA',
|
||||
'evdo': 'operator-networkType-EVDO',
|
||||
'wcdma/gsm/cdma/evdo': 'operator-networkType-auto'
|
||||
}
|
||||
};
|
||||
|
||||
var NetworkType = {
|
||||
/**
|
||||
* SupportedNetworkTypeHelper helps map the supported network types to user
|
||||
* friendly strings.
|
||||
*
|
||||
* @param {Array} hwSupportedTypes Array of hardware supported types. The
|
||||
* posssible values of the type are 'gsm',
|
||||
* 'cdma', 'wcdma', 'evdo', and 'lte'.
|
||||
*/
|
||||
SupportedNetworkTypeHelper: function ctor_snth(hwSupportedTypes) {
|
||||
var _hwSupportedTypeMap = {
|
||||
gsm: hwSupportedTypes.indexOf('gsm') !== -1,
|
||||
cdma: hwSupportedTypes.indexOf('cdma') !== -1,
|
||||
wcdma: hwSupportedTypes.indexOf('wcdma') !== -1,
|
||||
evdo: hwSupportedTypes.indexOf('evdo') !== -1,
|
||||
lte: hwSupportedTypes.indexOf('lte') !== -1
|
||||
};
|
||||
|
||||
// Get all supported network types based on the hardware supported types.
|
||||
var _networkTypes = NETWORK_TYPES.filter(function(type) {
|
||||
return type.split('/').every(function(subtype) {
|
||||
return _hwSupportedTypeMap[subtype.split('-')[0]];
|
||||
});
|
||||
});
|
||||
|
||||
// Compose the string for NETWORK_TYPE_STRING_MAP table lookup.
|
||||
var _stringMap = NETWORK_TYPE_STRING_MAP[
|
||||
['GSM', 'CDMA', 'LTE'].filter(function(category) {
|
||||
switch (category) {
|
||||
case 'GSM':
|
||||
return _hwSupportedTypeMap.gsm || _hwSupportedTypeMap.wcdma;
|
||||
case 'CDMA':
|
||||
return _hwSupportedTypeMap.cdma || _hwSupportedTypeMap.evdo;
|
||||
case 'LTE':
|
||||
return _hwSupportedTypeMap.lte;
|
||||
}
|
||||
}).toString()];
|
||||
|
||||
return {
|
||||
get gsm() {
|
||||
return _hwSupportedTypeMap.gsm;
|
||||
},
|
||||
get cdma() {
|
||||
return _hwSupportedTypeMap.cdma;
|
||||
},
|
||||
get wcdma() {
|
||||
return _hwSupportedTypeMap.wcdma;
|
||||
},
|
||||
get evdo() {
|
||||
return _hwSupportedTypeMap.evdo;
|
||||
},
|
||||
get lte() {
|
||||
return _hwSupportedTypeMap.lte;
|
||||
},
|
||||
get networkTypes() {
|
||||
return _networkTypes;
|
||||
},
|
||||
l10nIdForType: function(type) {
|
||||
return _stringMap[type];
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return NetworkType;
|
||||
});
|
||||
248
gaia/apps/settings/js/modules/mvvm/list_view.js
Executable file
248
gaia/apps/settings/js/modules/mvvm/list_view.js
Executable file
|
|
@ -0,0 +1,248 @@
|
|||
'use strict';
|
||||
|
||||
define(function(require) {
|
||||
var elements = new WeakMap();
|
||||
var ObservableArray = require('modules/mvvm/observable_array');
|
||||
|
||||
/*
|
||||
* A ListView takes an ObservableArray or an ordinary array, and generate/
|
||||
* manipulate the corresponding DOM elements of the content in the array using
|
||||
* the given template function. If the array is an ObservableArray, ListView
|
||||
* updates the DOM elements accordingly when the array is manipulated.
|
||||
*/
|
||||
var ListView = function(root, observableArray, templateFunc) {
|
||||
var _observableArray = null;
|
||||
var _root = root;
|
||||
var _templateFunc = templateFunc;
|
||||
var _enabled = true;
|
||||
|
||||
// keep track of observeables so we can unobserve
|
||||
var observerMap = new WeakMap();
|
||||
|
||||
function releaseObservers(element) {
|
||||
var observable = observerMap.get(element);
|
||||
if (!observable || typeof observable !== 'object') {
|
||||
return;
|
||||
}
|
||||
var observers = observerMap.get(observable);
|
||||
if (observers) {
|
||||
observers.forEach(function eachObserver(observer) {
|
||||
observable.unobserve(observer);
|
||||
});
|
||||
observerMap.delete(observable);
|
||||
observerMap.delete(element);
|
||||
}
|
||||
}
|
||||
|
||||
// helper to observe something and automatically remove old observers
|
||||
function observeAndCall(observable, map) {
|
||||
var observers = observerMap.get(observable) || [];
|
||||
if (observerMap.has(observable)) {
|
||||
observers = observerMap.get(observable);
|
||||
}
|
||||
observers = Object.keys(map).map(function eachProperty(property) {
|
||||
observable.observe(property, map[property]);
|
||||
map[property]();
|
||||
return map[property];
|
||||
});
|
||||
observerMap.set(observable, observers);
|
||||
}
|
||||
|
||||
var _helper = {
|
||||
observeAndCall: observeAndCall
|
||||
};
|
||||
|
||||
var _handleEvent = function(event) {
|
||||
// Ignore the change event when the list view is not enabled.
|
||||
if (!_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
var data = event.detail;
|
||||
switch (event.type) {
|
||||
case 'insert':
|
||||
_insert(data.index, data.items);
|
||||
break;
|
||||
case 'remove':
|
||||
_remove(data.index, data.count);
|
||||
break;
|
||||
case 'replace':
|
||||
_replace(data.index, data.newValue);
|
||||
break;
|
||||
case 'reset':
|
||||
_reset(data.items || []);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
var _insert = function(index, items) {
|
||||
if (items.length <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// add DOM elements
|
||||
var referenceElement = _root.children[index];
|
||||
for (var i = items.length - 1; i >= 0; i--) {
|
||||
var curElement = _templateFunc(items[i], undefined, _helper);
|
||||
observerMap.set(curElement, items[i]);
|
||||
_root.insertBefore(curElement, referenceElement);
|
||||
referenceElement = curElement;
|
||||
}
|
||||
};
|
||||
|
||||
var _remove = function(index, count) {
|
||||
if (count === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// remove DOM elements
|
||||
if (count >= _root.childElementCount) {
|
||||
// clear all
|
||||
while (_root.firstElementChild) {
|
||||
releaseObservers(_root.firstElementChild);
|
||||
_root.removeChild(_root.firstElementChild);
|
||||
}
|
||||
} else {
|
||||
var nextElement = _root.children[index];
|
||||
for (var i = 0; i < count; i++) {
|
||||
if (nextElement) {
|
||||
var temp = nextElement.nextElementSibling;
|
||||
releaseObservers(nextElement);
|
||||
_root.removeChild(nextElement);
|
||||
nextElement = temp;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var _replace = function(index, value) {
|
||||
var element = _root.children[index];
|
||||
if (element) {
|
||||
releaseObservers(element);
|
||||
var newElement = _templateFunc(value, element, _helper);
|
||||
observerMap.set(element, value);
|
||||
if (newElement !== element) {
|
||||
_root.insertBefore(newElement, element);
|
||||
_root.removeChild(element);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var _reset = function(items) {
|
||||
var itemCount = items.length;
|
||||
var elementCount = _root.childElementCount;
|
||||
|
||||
if (itemCount === 0) {
|
||||
_remove(0, elementCount);
|
||||
} else if (itemCount <= elementCount) {
|
||||
items.forEach(function(item, index) {
|
||||
_replace(index, item);
|
||||
});
|
||||
// remove extra elements
|
||||
_remove(itemCount, elementCount - itemCount);
|
||||
} else {
|
||||
var slicedItems = items.slice(0, elementCount);
|
||||
var remainingItems = items.slice(elementCount);
|
||||
|
||||
slicedItems.forEach(function(item, index) {
|
||||
_replace(index, item);
|
||||
});
|
||||
// add extra elements
|
||||
_insert(elementCount, remainingItems);
|
||||
}
|
||||
};
|
||||
|
||||
var _enabledChanged = function() {
|
||||
if (_enabled) {
|
||||
_reset(_observableArray.array);
|
||||
}
|
||||
};
|
||||
|
||||
var view = {
|
||||
set: function lv_set(newArray) {
|
||||
if (_observableArray) {
|
||||
_observableArray.removeEventListener('insert', _handleEvent);
|
||||
_observableArray.removeEventListener('remove', _handleEvent);
|
||||
_observableArray.removeEventListener('replace', _handleEvent);
|
||||
_observableArray.removeEventListener('reset', _handleEvent);
|
||||
}
|
||||
|
||||
if (!newArray) {
|
||||
// clear all existing items
|
||||
if (_observableArray) {
|
||||
_remove(0, _observableArray.length);
|
||||
_observableArray = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If new array is an ordinary array, convert it to an ObservableArray.
|
||||
if (Array.isArray(newArray)) {
|
||||
newArray = ObservableArray(newArray);
|
||||
}
|
||||
_observableArray = newArray;
|
||||
|
||||
// An ObservableArray fires the four events for notifying its changes.
|
||||
// Register to these events for updating UI.
|
||||
_observableArray.addEventListener('insert', _handleEvent);
|
||||
_observableArray.addEventListener('remove', _handleEvent);
|
||||
_observableArray.addEventListener('replace', _handleEvent);
|
||||
_observableArray.addEventListener('reset', _handleEvent);
|
||||
|
||||
if (this.enabled) {
|
||||
_reset(_observableArray.array);
|
||||
}
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
// unobserve from array and null everything out
|
||||
if (_observableArray) {
|
||||
_observableArray.removeEventListener('insert', _handleEvent);
|
||||
_observableArray.removeEventListener('remove', _handleEvent);
|
||||
_observableArray.removeEventListener('replace', _handleEvent);
|
||||
_observableArray.removeEventListener('reset', _handleEvent);
|
||||
}
|
||||
_remove(0, _observableArray.length);
|
||||
elements.delete(_root);
|
||||
_root = null;
|
||||
_observableArray = null;
|
||||
_templateFunc = null;
|
||||
_enabled = false;
|
||||
},
|
||||
|
||||
get element() {
|
||||
return _root;
|
||||
},
|
||||
|
||||
set enabled(value) {
|
||||
if (_enabled !== value) {
|
||||
_enabled = value;
|
||||
_enabledChanged();
|
||||
}
|
||||
},
|
||||
|
||||
get enabled() {
|
||||
return _enabled;
|
||||
}
|
||||
};
|
||||
|
||||
if (elements.has(_root)) {
|
||||
// destroy old ListView if we setup a second one
|
||||
elements.get(_root).destroy();
|
||||
}
|
||||
|
||||
elements.set(_root, view);
|
||||
|
||||
// emtpy element at creation time
|
||||
_root.innerHTML = '';
|
||||
|
||||
view.set(observableArray);
|
||||
return view;
|
||||
};
|
||||
|
||||
return ListView;
|
||||
});
|
||||
360
gaia/apps/settings/js/modules/mvvm/observable.js
Executable file
360
gaia/apps/settings/js/modules/mvvm/observable.js
Executable file
|
|
@ -0,0 +1,360 @@
|
|||
/**
|
||||
* Observable provides ways of defining properties that the value changes of
|
||||
* them can be observed. In addition to normal properties, it allows to
|
||||
* create read-only properties and dependency properties.
|
||||
*
|
||||
* Observable creation:
|
||||
* There are two ways of defining an Observable: object literal or extending
|
||||
* from Observable. Object literal is useful when defining a singleton or you
|
||||
* want to create an observable easily.
|
||||
*
|
||||
* @example
|
||||
* var observable = Observable({
|
||||
* prop: 10,
|
||||
* func: function() {}
|
||||
* });
|
||||
*
|
||||
* Extending from Observable allow you to define a class of Observable. The
|
||||
* advantage of this compared to object literal is that the accessers are
|
||||
* shared across all instances of the class. The syntax compatible with the
|
||||
* javascript prototype definition.
|
||||
*
|
||||
* @example
|
||||
* var ExtendedObservable = Module.create(function ExtendedObservable() {
|
||||
* this.super(Observable).call(this);
|
||||
* }).extend(Observable);
|
||||
* Observable.defineObservableProperty(ExtendedObservable.prototype, 'prop',
|
||||
* {
|
||||
* value: 10
|
||||
* });
|
||||
* ExtendedObservable.prototype.func = function() {};
|
||||
*
|
||||
* // Extend from NewObservable
|
||||
* var ExtendedObservable2 = Module.create(function() {
|
||||
* // constructor
|
||||
* }).extend(ExtendedObservable);
|
||||
* Observable.defineObservableProperty(
|
||||
* ExtendedObservable2.prototype, 'prop2',
|
||||
* {
|
||||
* value: 20
|
||||
* });
|
||||
*
|
||||
* var observable = ExtendedObservable2();
|
||||
* console.log(observable.prop); // 10
|
||||
* console.log(observable.prop2); // 20
|
||||
*
|
||||
* Defining a read-only property:
|
||||
* This is only supported when extending from Observable. When you define a
|
||||
* read-only property, an internal property with a '_' prefix is defined at
|
||||
* the same time so you can still change the value inside the observable.
|
||||
*
|
||||
* @example
|
||||
* Observable.defineObservableProperty(ExtendedObservable.prototype, 'prop',
|
||||
* {
|
||||
* readonly: true,
|
||||
* value: 10
|
||||
* });
|
||||
* ExtendedObservable.prototype.inc = function() {
|
||||
* this._prop = 100;
|
||||
* };
|
||||
* var observable = new ExtendedObservable();
|
||||
* observable.prop = 100; // throws an exception
|
||||
* observable.inc(); // observers on "prop" are called
|
||||
*
|
||||
* Defining a dependency property:
|
||||
* This is only supported when extending from Observable. You provide a list
|
||||
* of the dependent properties and when each of them changes, the observsers
|
||||
* on the defined property are called. Dependency properties are read-only and
|
||||
* their values are determined by the specified getter.
|
||||
*
|
||||
* @example
|
||||
* Observable.defineObservableProperty(ExtendedObservable.prototype, 'prop',
|
||||
* {
|
||||
* value: 10
|
||||
* });
|
||||
* Observable.defineObservableProperty(
|
||||
* ExtendedObservable.prototype, 'prop2',
|
||||
* {
|
||||
* value: 20
|
||||
* });
|
||||
* Observable.defineObservableProperty(
|
||||
* ExtendedObservable.prototype, 'dependencyProp',
|
||||
* {
|
||||
* dependency: ['prop', 'prop2'],
|
||||
* get: function() {
|
||||
* return this.prop + this.prop2;
|
||||
* }
|
||||
* });
|
||||
* var observable = new ExtendedObservable();
|
||||
* observable.prop = 100; // observers on "dependencyProp" are called
|
||||
* observable.prop2 = 200; // observers on "dependencyProp" are called
|
||||
* // observable.dependencyProp is 300.
|
||||
*
|
||||
* @module modules/mvvm/observable
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var Module = require('modules/base/module');
|
||||
var DependencyGraph = require('modules/base/dependency_graph');
|
||||
|
||||
var OP_PREFIX = (name) => { return '$OP_' + name; };
|
||||
|
||||
/**
|
||||
* @class Observable
|
||||
* @requires module:modules/base/module
|
||||
* @requires module:modules/base/dependency_graph
|
||||
* @returns {Observable}
|
||||
*/
|
||||
var Observable = Module.create(function Observable(object) {
|
||||
this._observers = {};
|
||||
if (object) {
|
||||
this._initWithObject(object);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Initialize the observable with a prototype object. This is not required
|
||||
* for objects that are defined using prototype as everything should be
|
||||
* defined via Observable.defineObservableProperty explicitly.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf Observable.prototype
|
||||
* @param {Object} object
|
||||
*/
|
||||
Observable.prototype._initWithObject = function o_init(object) {
|
||||
for (var name in object) {
|
||||
// If name is a function, simply add it to the observable.
|
||||
if (typeof object[name] === 'function') {
|
||||
this[name] = object[name];
|
||||
} else {
|
||||
_defineObservableProperty(this, name, {
|
||||
value: object[name]
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Notify the value change of a property.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf Observable.prototype
|
||||
* @param {String} name
|
||||
* @param {Object} newValue
|
||||
* @param {Object} oldValue
|
||||
*/
|
||||
Observable.prototype._notify = function o__notify(name, newValue, oldValue) {
|
||||
var observers = this._observers[name];
|
||||
if (observers) {
|
||||
observers.forEach(function(observer) {
|
||||
observer(newValue, oldValue);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove an observer from a property.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf Observable.prototype
|
||||
* @param {Function} observer
|
||||
* @param {String} name
|
||||
*/
|
||||
Observable.prototype._removeObserver =
|
||||
function o__removeObserver(observer, name) {
|
||||
// arguments in reverse order to support .bind(observer) for the
|
||||
// unbind from all case
|
||||
var observers = this._observers[name];
|
||||
if (observers) {
|
||||
var index = observers.indexOf(observer);
|
||||
if (index >= 0) {
|
||||
observers.splice(index, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Observe a property with an observer. The observer is called when the
|
||||
* property changes.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf Observable.prototype
|
||||
* @param {String} name
|
||||
* @param {Function} observer
|
||||
*/
|
||||
Observable.prototype.observe = function o_observe(name, observer) {
|
||||
if (typeof observer !== 'function') {
|
||||
return;
|
||||
}
|
||||
(this._observers[name] = this._observers[name] || []).push(observer);
|
||||
};
|
||||
|
||||
/**
|
||||
* Unobserve a property
|
||||
*
|
||||
* @access public
|
||||
* @memberOf Observable.prototype
|
||||
* @param {String} name
|
||||
* @param {Function} observer
|
||||
*/
|
||||
Observable.prototype.unobserve = function o_unobserve(name, observer) {
|
||||
if (typeof name === 'function') {
|
||||
// (observer) -- remove from every key in _observers
|
||||
Object.keys(this._observers).forEach(
|
||||
this._removeObserver.bind(this, name));
|
||||
} else {
|
||||
if (observer) {
|
||||
// (prop, observer) -- remove observer from the specific prop
|
||||
this._removeObserver(observer, name);
|
||||
} else if (name in this._observers) {
|
||||
// (prop) -- otherwise remove all observers for property
|
||||
this._observers[name] = [];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Static functions
|
||||
var _dependencyGraphs = new Map();
|
||||
/**
|
||||
* Each module should have its own dependency graph that decides what
|
||||
* observers to called when a property changes. The function returns the
|
||||
* dependency graph of a module. It creates one if the map does not exist.
|
||||
*
|
||||
* @param {Object} modulePrototype
|
||||
*/
|
||||
function _getDependencyGraph(modulePrototype) {
|
||||
var dependencyGraph = _dependencyGraphs.get(modulePrototype);
|
||||
if (!dependencyGraph) {
|
||||
// register a new dependency graph of the module based on the existing
|
||||
// dependency graph on the module.
|
||||
dependencyGraph = DependencyGraph(modulePrototype._dependencyGraph);
|
||||
modulePrototype._dependencyGraph = dependencyGraph;
|
||||
_dependencyGraphs.set(modulePrototype, dependencyGraph);
|
||||
}
|
||||
return dependencyGraph;
|
||||
}
|
||||
|
||||
/**
|
||||
* The function helps query the values of all dependent properties of a
|
||||
* specified property.
|
||||
*
|
||||
* @param {Observable} observable
|
||||
* @param {String} sourceProperty
|
||||
* The source property name.
|
||||
*/
|
||||
function _getAllDependentValues(observable, sourceProperty) {
|
||||
var dependentList = observable._dependencyGraph &&
|
||||
observable._dependencyGraph.getAllDependent(sourceProperty);
|
||||
if (dependentList && dependentList.length) {
|
||||
return dependentList.map((name) => {
|
||||
return {
|
||||
name: name,
|
||||
value: observable[name]
|
||||
};
|
||||
});
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function _getterTemplate(name, defaultValue) {
|
||||
return function() {
|
||||
var value = this[OP_PREFIX(name)];
|
||||
if (typeof value === 'undefined') {
|
||||
value = this[OP_PREFIX(name)] = defaultValue;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
function _setterTemplate(name) {
|
||||
return function(value) {
|
||||
var oldValue = this[name];
|
||||
if (oldValue !== value) {
|
||||
// cache the old values of all dependent
|
||||
var dependentValues = _getAllDependentValues(this, name);
|
||||
// change the value
|
||||
this[OP_PREFIX(name)] = value;
|
||||
// notify the changes
|
||||
this._notify(name, value, oldValue);
|
||||
if (dependentValues) {
|
||||
dependentValues.forEach((obj) => {
|
||||
this._notify(obj.name, this[obj.name], obj.value);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function _defineObservablePropertyCore(object, name, options) {
|
||||
var dependency = options && options.dependency;
|
||||
|
||||
// Update dependency information
|
||||
if (dependency) {
|
||||
var dependencyGraph = _getDependencyGraph(object);
|
||||
dependency.forEach((dependentName) => {
|
||||
// name depends on dependentName
|
||||
dependencyGraph.addDependency(name, dependentName);
|
||||
});
|
||||
}
|
||||
|
||||
Object.defineProperty(object, name, {
|
||||
enumerable: true,
|
||||
get: options.get,
|
||||
set: options.set
|
||||
});
|
||||
}
|
||||
|
||||
function _defineObservableProperty(object, name, options) {
|
||||
if (options && options.readonly) {
|
||||
var internalName = '_' + name;
|
||||
_defineObservablePropertyCore(object, name, {
|
||||
dependency: [internalName],
|
||||
get: function() {
|
||||
return this[internalName];
|
||||
}
|
||||
});
|
||||
_defineObservablePropertyCore(object, internalName, {
|
||||
get: _getterTemplate(internalName, options && options.value),
|
||||
set: _setterTemplate(internalName)
|
||||
});
|
||||
} else if (options && options.dependency && options.dependency.length) {
|
||||
if (typeof options.get !== 'function') {
|
||||
throw new Error('Observable: getter of ' + name + ' is invalid');
|
||||
}
|
||||
_defineObservablePropertyCore(object, name, {
|
||||
dependency: options.dependency,
|
||||
get: options.get
|
||||
});
|
||||
} else {
|
||||
_defineObservablePropertyCore(object, name, {
|
||||
get: _getterTemplate(name, options && options.value),
|
||||
set: _setterTemplate(name)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Observe a property with an observer. The observer is called when the
|
||||
* property changes.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf Observable
|
||||
* @param {Object} object
|
||||
* @param {String} name
|
||||
* @param {Object} options
|
||||
* @param {Boolean} options.readonly
|
||||
* Indicating if the property is read-only.
|
||||
* @param {Array.<String>} options.dependency
|
||||
* List of the dependent properties.
|
||||
* @param {Function} options.get
|
||||
* Getter of the property.
|
||||
*/
|
||||
Object.defineProperty(Observable, 'defineObservableProperty', {
|
||||
get: function() {
|
||||
return _defineObservableProperty;
|
||||
}
|
||||
});
|
||||
return Observable;
|
||||
});
|
||||
228
gaia/apps/settings/js/modules/mvvm/observable_array.js
Executable file
228
gaia/apps/settings/js/modules/mvvm/observable_array.js
Executable file
|
|
@ -0,0 +1,228 @@
|
|||
/**
|
||||
* ObservableArray is able to notify its change through events of four basic
|
||||
* operations including 'insert', 'remove', 'replace', 'reset'.
|
||||
* ObservableArray implements most used methods including 'push', 'pop', and
|
||||
* 'splice'. Due to the syntax limitation it provides 'get' and 'set' for
|
||||
* manuplication values stored in the array. 'reset' is also provided for
|
||||
* replacing te entire array with another one. In addition to these methods,
|
||||
* methods including 'forEach', 'map', 'every', 'some', 'indexOf',
|
||||
* 'lastIndexOf', 'reduce', and 'reduceRight'.
|
||||
*
|
||||
* ObservableArray creation:
|
||||
* It can be initialized by an ordinary array. If the array is not given, an
|
||||
* empty is used by default.
|
||||
*
|
||||
* @example
|
||||
* var observableArray = ObservableArray([1, 2, 3]);
|
||||
*
|
||||
* Events:
|
||||
* Information regarding the operation is provided in the event detail. Please
|
||||
* check the following example:
|
||||
*
|
||||
* @example
|
||||
* var observableArray = ObservableArray([1, 2, 3]);
|
||||
* var handler = {
|
||||
* handleEvent: function(event) { console.log(JSON.stringify(event)); }
|
||||
* }
|
||||
* observableArray.addEventListener('insert', handler);
|
||||
* observableArray.addEventListener('remove', handler);
|
||||
* observableArray.addEventListener('replace', handler);
|
||||
* observableArray.addEventListener('reset', handler);
|
||||
*
|
||||
* observableArray.push(4);
|
||||
* // observableArray is [1, 2, 3, 4]
|
||||
* // event.type is 'insert'
|
||||
* // event.detail is {index: 3, count: 1, items: [4]}
|
||||
*
|
||||
* observableArray.pop();
|
||||
* // observableArray is [1, 2, 3]
|
||||
* // event.type is 'remove'
|
||||
* // event.detail is {index: 3, count: 1, items: [4]}
|
||||
*
|
||||
* observableArray.set(2, 4);
|
||||
* // observableArray is [1, 2, 4]
|
||||
* // event.type is 'replace'
|
||||
* // event.detail is {index: 2, oldValue: 3, newValue: 4}
|
||||
*
|
||||
* observableArray.reset([1, 2, 3]);
|
||||
* // observableArray is [1, 2, 3]
|
||||
* // event.type is 'reset'
|
||||
* // event.detail is {items: [1, 2, 3]}
|
||||
*
|
||||
* observableArray.splice(2, 1, [4, 5, 6]);
|
||||
* // observableArray is [1, 2, 4, 5, 6]
|
||||
* // First event:
|
||||
* // event.type is 'remove'
|
||||
* // event.detail is {index: 2, count: 1, items: [3]}
|
||||
* // Second event:
|
||||
* // event.type is 'insert'
|
||||
* // event.detail is {index: 2, count: 3, items: [4, 5, 6]}
|
||||
*
|
||||
* @module modules/mvvm/observable_array
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var Module = require('modules/base/module');
|
||||
var EventEmitter = require('modules/base/event_emitter');
|
||||
var Observable = require('modules/mvvm/observable');
|
||||
|
||||
var Events = ['insert', 'remove', 'replace', 'reset'];
|
||||
var ReadOnlyMethods = ['forEach', 'map', 'every', 'some', 'indexOf',
|
||||
'lastIndexOf', 'reduce', 'reduceRight'];
|
||||
|
||||
/**
|
||||
* @class ObservableArray
|
||||
* @requires module:modules/base/module
|
||||
* @requires module:modules/base/event_emitter
|
||||
* @requires module:modules/mvvm/observable
|
||||
* @returns {ObservableArray}
|
||||
*/
|
||||
var ObservableArray = Module.create(function ObservableArray(array) {
|
||||
this.super(Observable).call(this);
|
||||
this.super(EventEmitter).call(this, Events);
|
||||
|
||||
this._array = array || [];
|
||||
this._length = this._array.length;
|
||||
}).extend(Observable, EventEmitter);
|
||||
|
||||
/**
|
||||
* An observable property indicating the length of the array.
|
||||
*
|
||||
* @access public
|
||||
* @type {Number}
|
||||
*/
|
||||
Observable.defineObservableProperty(ObservableArray.prototype, 'length', {
|
||||
readonly: true
|
||||
});
|
||||
|
||||
/**
|
||||
* An observable property representing the inner array.
|
||||
*
|
||||
* @access public
|
||||
* @type {Array}
|
||||
*/
|
||||
Observable.defineObservableProperty(ObservableArray.prototype, 'array', {
|
||||
readonly: true
|
||||
});
|
||||
|
||||
ReadOnlyMethods.forEach(function(op) {
|
||||
ObservableArray.prototype[op] = function() {
|
||||
return this._array[op].apply(this._array, arguments);
|
||||
};
|
||||
});
|
||||
|
||||
ObservableArray.prototype.push = function(item) {
|
||||
this._array.push(item);
|
||||
this._length = this._array.length;
|
||||
|
||||
this._emitEvent('insert', {
|
||||
index: this._array.length - 1,
|
||||
count: 1,
|
||||
items: [item]
|
||||
});
|
||||
};
|
||||
|
||||
ObservableArray.prototype.pop = function() {
|
||||
if (!this._array.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var item = this._array.pop();
|
||||
this._length = this._array.length;
|
||||
|
||||
this._emitEvent('remove', {
|
||||
index: this._array.length,
|
||||
count: 1,
|
||||
items: [item]
|
||||
});
|
||||
|
||||
return item;
|
||||
};
|
||||
|
||||
ObservableArray.prototype.splice = function(index, count) {
|
||||
if (arguments.length < 2) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Fix the arguments based on the behavior of the real spice function.
|
||||
if (index >= this._length) {
|
||||
index = this._length - 1;
|
||||
} else if (index < 0) {
|
||||
index = this._length + index;
|
||||
}
|
||||
if (count < 0) {
|
||||
count = 0;
|
||||
}
|
||||
|
||||
var addedItems = Array.prototype.slice.call(arguments, 2);
|
||||
var removedItems = this._array.splice.apply(this._array, arguments);
|
||||
this._length = this._array.length;
|
||||
|
||||
if (removedItems.length) {
|
||||
this._emitEvent('remove', {
|
||||
index: index,
|
||||
count: count,
|
||||
items: removedItems
|
||||
});
|
||||
}
|
||||
|
||||
if (addedItems.length) {
|
||||
this._emitEvent('insert', {
|
||||
index: index,
|
||||
count: addedItems.length,
|
||||
items: addedItems
|
||||
});
|
||||
}
|
||||
|
||||
return removedItems;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set a value to the field of a specfied index.
|
||||
*
|
||||
* @access public
|
||||
* @param {Number} index
|
||||
* @param {Object} value
|
||||
*/
|
||||
ObservableArray.prototype.set = function(index, value) {
|
||||
if (index < 0 || index >= this._array.length) {
|
||||
this.throw('set: out of range');
|
||||
}
|
||||
|
||||
var oldValue = this._array[index];
|
||||
this._array[index] = value;
|
||||
this._emitEvent('replace', {
|
||||
index: index,
|
||||
oldValue: oldValue,
|
||||
newValue: value
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the value from the field of a specfied index.
|
||||
*
|
||||
* @access public
|
||||
* @param {Number} index
|
||||
* @returns {Object}
|
||||
*/
|
||||
ObservableArray.prototype.get = function(index) {
|
||||
return this._array[index];
|
||||
};
|
||||
|
||||
/**
|
||||
* Replace the entire array with another one.
|
||||
*
|
||||
* @access public
|
||||
* @param {Array} array
|
||||
*/
|
||||
ObservableArray.prototype.reset = function(array) {
|
||||
this._array = array || [];
|
||||
this._length = this._array.length;
|
||||
this._emitEvent('reset', {
|
||||
items: this._array
|
||||
});
|
||||
};
|
||||
|
||||
return ObservableArray;
|
||||
});
|
||||
8
gaia/apps/settings/js/modules/navigator/battery.js
Executable file
8
gaia/apps/settings/js/modules/navigator/battery.js
Executable file
|
|
@ -0,0 +1,8 @@
|
|||
/**
|
||||
* Wraps navigator.battery for replacing it in unit tests more easily.
|
||||
*/
|
||||
define(function() {
|
||||
'use strict';
|
||||
|
||||
return navigator.battery;
|
||||
});
|
||||
4
gaia/apps/settings/js/modules/navigator/mozApps.js
Executable file
4
gaia/apps/settings/js/modules/navigator/mozApps.js
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
define(function() {
|
||||
'use strict';
|
||||
return window.navigator.mozApps;
|
||||
});
|
||||
12
gaia/apps/settings/js/modules/navigator/mozBluetooth.js
Executable file
12
gaia/apps/settings/js/modules/navigator/mozBluetooth.js
Executable file
|
|
@ -0,0 +1,12 @@
|
|||
/**
|
||||
* Wraps navigator.bluetooth for replacing it in unit tests more easily.
|
||||
*/
|
||||
define(function() {
|
||||
'use strict';
|
||||
|
||||
if (navigator.mozBluetooth) {
|
||||
return navigator.mozBluetooth;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
4
gaia/apps/settings/js/modules/navigator/mozPermissionSettings.js
Executable file
4
gaia/apps/settings/js/modules/navigator/mozPermissionSettings.js
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
define(function() {
|
||||
'use strict';
|
||||
return window.navigator.mozPermissionSettings;
|
||||
});
|
||||
106
gaia/apps/settings/js/modules/page_transitions.js
Executable file
106
gaia/apps/settings/js/modules/page_transitions.js
Executable file
|
|
@ -0,0 +1,106 @@
|
|||
/**
|
||||
* PageTransitions provides transition functions used when navigating panels.
|
||||
*
|
||||
* @module PageTransitions
|
||||
*/
|
||||
define(function() {
|
||||
'use strict';
|
||||
|
||||
var _sendPanelReady = function _send_panel_ready(oldPanelHash, newPanelHash) {
|
||||
var detail = {
|
||||
previous: oldPanelHash,
|
||||
current: newPanelHash
|
||||
};
|
||||
var event = new CustomEvent('panelready', {detail: detail});
|
||||
window.dispatchEvent(event);
|
||||
};
|
||||
|
||||
return {
|
||||
/**
|
||||
* Typically used with phone size device layouts.
|
||||
*
|
||||
* @alias module:PageTransitions#oneColumn
|
||||
* @param {String} oldPanel
|
||||
* @param {String} newPanel
|
||||
* @param {Function} callback
|
||||
*/
|
||||
oneColumn: function pt_one_column(oldPanel, newPanel, callback) {
|
||||
if (oldPanel === newPanel) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
// switch previous/current classes
|
||||
if (oldPanel) {
|
||||
oldPanel.className = newPanel.className ? '' : 'previous';
|
||||
}
|
||||
if (newPanel.className === 'current') {
|
||||
_sendPanelReady(oldPanel && '#' + oldPanel.id, '#' + newPanel.id);
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
newPanel.className = 'current';
|
||||
|
||||
/**
|
||||
* Most browsers now scroll content into view taking CSS transforms into
|
||||
* account. That's not what we want when moving between <section>s,
|
||||
* because the being-moved-to section is offscreen when we navigate to its
|
||||
* #hash. The transitions assume the viewport is always at document 0,0.
|
||||
* So add a hack here to make that assumption true again.
|
||||
* https://bugzilla.mozilla.org/show_bug.cgi?id=803170
|
||||
*/
|
||||
if ((window.scrollX !== 0) || (window.scrollY !== 0)) {
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
|
||||
newPanel.addEventListener('transitionend', function paintWait() {
|
||||
newPanel.removeEventListener('transitionend', paintWait);
|
||||
if (oldPanel) {
|
||||
_sendPanelReady('#' + oldPanel.id, '#' + newPanel.id);
|
||||
|
||||
if (oldPanel.className === 'current') {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
_sendPanelReady(null, '#' + newPanel.id);
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Typically used with tablet size device layouts.
|
||||
*
|
||||
* @alias module:PageTransitions#twoColumn
|
||||
* @param {String} oldPanel
|
||||
* @param {String} newPanel
|
||||
* @param {Function} callback
|
||||
*/
|
||||
twoColumn: function pt_two_column(oldPanel, newPanel, callback) {
|
||||
if (oldPanel === newPanel) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
if (oldPanel) {
|
||||
oldPanel.className = newPanel.className ? '' : 'previous';
|
||||
newPanel.className = 'current';
|
||||
_sendPanelReady('#' + oldPanel.id, '#' + newPanel.id);
|
||||
} else {
|
||||
newPanel.className = 'current';
|
||||
_sendPanelReady(null, '#' + newPanel.id);
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
125
gaia/apps/settings/js/modules/panel.js
Executable file
125
gaia/apps/settings/js/modules/panel.js
Executable file
|
|
@ -0,0 +1,125 @@
|
|||
/**
|
||||
* Panel is the basic element for navigation. Which defines Six basic
|
||||
* functions: show, hide, beforeShow, beforeHide, init, and uninit for
|
||||
* navigation. These functions are called by `SettingsService` during the
|
||||
* navigation.
|
||||
* Internal functions onShow, onHide, onBeforeShow, onBeforeHide, onInit,
|
||||
* and onUninit are called respectively in the basic functions.
|
||||
*
|
||||
* @module Panel
|
||||
*/
|
||||
define(function() {
|
||||
'use strict';
|
||||
|
||||
var _emptyFunc = function panel_emptyFunc() {};
|
||||
|
||||
/**
|
||||
* @alias module:Panel
|
||||
* @param {Object} options
|
||||
* Options are used to override the internal functions.
|
||||
* @returns {Panel}
|
||||
*/
|
||||
var Panel = function ctor_panel(options) {
|
||||
var _initialized = false;
|
||||
|
||||
options = options || {};
|
||||
options.onInit = options.onInit || _emptyFunc;
|
||||
options.onUninit = options.onUninit || _emptyFunc;
|
||||
options.onShow = options.onShow || _emptyFunc;
|
||||
options.onHide = options.onHide || _emptyFunc;
|
||||
options.onBeforeShow = options.onBeforeShow || _emptyFunc;
|
||||
options.onBeforeHide = options.onBeforeHide || _emptyFunc;
|
||||
|
||||
return {
|
||||
/**
|
||||
* Get a value that indicates whether the panel has been initialized.
|
||||
*
|
||||
* @alias module:Panel#initialized
|
||||
* @return {Boolean}
|
||||
*/
|
||||
get initialized() {
|
||||
return _initialized;
|
||||
},
|
||||
|
||||
/**
|
||||
* Called at the first time when the beforeShow function is called.
|
||||
*
|
||||
* @alias module:Panel#init
|
||||
* @param {HTMLElement} panel
|
||||
* @param {Object} initOptions
|
||||
*/
|
||||
init: function(panel, initOptions) {
|
||||
if (_initialized) {
|
||||
return;
|
||||
}
|
||||
_initialized = true;
|
||||
|
||||
return options.onInit(panel, initOptions);
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when cleanup.
|
||||
*
|
||||
* @alias module:Panel#uninit
|
||||
*/
|
||||
uninit: function() {
|
||||
if (!_initialized) {
|
||||
return;
|
||||
}
|
||||
_initialized = false;
|
||||
|
||||
options.onUninit();
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the panel is navigated into the viewport.
|
||||
*
|
||||
* @alias module:Panel#show
|
||||
* @param {HTMLElement} panel
|
||||
* @param {Object} showOptions
|
||||
*/
|
||||
show: function(panel, showOptions) {
|
||||
// Initialize at the first call to show if necessary.
|
||||
return Promise.resolve(this.init(panel, showOptions)).then(function() {
|
||||
return options.onShow(panel, showOptions);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the panel is navigated out of the viewport.
|
||||
*
|
||||
* @alias module:Panel#hide
|
||||
*/
|
||||
hide: function() {
|
||||
return options.onHide();
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the panel is about to be navigated to into the viewport.
|
||||
*
|
||||
* @alias module:Panel#beforeShow
|
||||
* @param {HTMLElement} panel
|
||||
* @param {Object} beforeShowOptions
|
||||
*/
|
||||
beforeShow: function(panel, beforeShowOptions) {
|
||||
// Initialize at the first call to beforeShow.
|
||||
return Promise.resolve(this.init(panel, beforeShowOptions)).then(
|
||||
function() {
|
||||
return options.onBeforeShow(panel, beforeShowOptions);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the panel is about to be navigated out of the viewport.
|
||||
*
|
||||
* @alias module:Panel#beforeHide
|
||||
* @param {HTMLElement} panel
|
||||
* @param {Object} beforeShowOptions
|
||||
*/
|
||||
beforeHide: function() {
|
||||
return options.onBeforeHide();
|
||||
}
|
||||
};
|
||||
};
|
||||
return Panel;
|
||||
});
|
||||
112
gaia/apps/settings/js/modules/panel_cache.js
Executable file
112
gaia/apps/settings/js/modules/panel_cache.js
Executable file
|
|
@ -0,0 +1,112 @@
|
|||
/**
|
||||
* PanelCache is a singleton that loads a panel module based on the panel id
|
||||
* and caches the loaded modules.
|
||||
*
|
||||
* @module PanelCache
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var SettingsPanel = require('modules/settings_panel');
|
||||
var LazyLoader = require('shared/lazy_loader');
|
||||
|
||||
var _panelCache = {};
|
||||
var _panelStylesheetsLoaded = false;
|
||||
|
||||
// experiment result shows
|
||||
// load all related styles once is time saver
|
||||
var _loadPanelStylesheetsIfNeeded = function loadPanelCSS() {
|
||||
if (_panelStylesheetsLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
LazyLoader.load(['shared/style/action_menu.css',
|
||||
'shared/style/confirm.css',
|
||||
'shared/style/progress_activity.css',
|
||||
'shared/elements/gaia-icons/bidi-helper.css',
|
||||
'shared/elements/gaia_buttons/script.js',
|
||||
'shared/elements/gaia_confirm/script.js',
|
||||
'style/homescreens.css',
|
||||
// Bug 1210284 - disabling achievements until they are on
|
||||
// the roadmap again or completely removed.
|
||||
// 'style/achievements.css',
|
||||
'style/apps.css',
|
||||
'style/screen_lock.css',
|
||||
'style/simcard.css',
|
||||
'style/updates.css',
|
||||
'style/downloads.css',
|
||||
'style/developer_service_workers.css'],
|
||||
function callback() {
|
||||
_panelStylesheetsLoaded = true;
|
||||
});
|
||||
};
|
||||
|
||||
// load styles in idle time after document loaded
|
||||
navigator.addIdleObserver({
|
||||
time: 3,
|
||||
onidle: _loadPanelStylesheetsIfNeeded
|
||||
});
|
||||
|
||||
return {
|
||||
// this is for unit test
|
||||
reset: function spc_reset() {
|
||||
_panelCache = {};
|
||||
_panelStylesheetsLoaded = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the panel module of a specified id. If there is no corresponding
|
||||
* panel module of the id, it returns SettingsPanel.
|
||||
*
|
||||
* @alias module:PanelCache#get
|
||||
* @param {String} panelId
|
||||
* The id of the to be loaded panel.
|
||||
* @param {Function} callback
|
||||
* The function to be called when the panel is loaded.
|
||||
*/
|
||||
get: function spc_get(panelId, callback) {
|
||||
if (!panelId && !callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (panelId !== 'root') {
|
||||
_loadPanelStylesheetsIfNeeded();
|
||||
}
|
||||
|
||||
var cachedPanel = _panelCache[panelId];
|
||||
if (cachedPanel) {
|
||||
if (callback) {
|
||||
callback(cachedPanel);
|
||||
}
|
||||
} else {
|
||||
// Get the path of the panel creation function
|
||||
var panelElement = document.getElementById(panelId);
|
||||
if (panelElement) {
|
||||
var pathElement = panelElement.querySelector('panel');
|
||||
var path = pathElement ? pathElement.dataset.path : null;
|
||||
|
||||
var panelFuncLoaded = function(panelFunc) {
|
||||
var panel = panelFunc();
|
||||
_panelCache[panelId] = panel;
|
||||
if (callback) {
|
||||
callback(panel);
|
||||
}
|
||||
};
|
||||
|
||||
if (path) {
|
||||
require([path], function(panelFunc) {
|
||||
// Create a new panel object for static panels.
|
||||
panelFuncLoaded(panelFunc ? panelFunc : SettingsPanel);
|
||||
});
|
||||
} else {
|
||||
panelFuncLoaded(SettingsPanel);
|
||||
}
|
||||
} else {
|
||||
if (callback) {
|
||||
callback(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
560
gaia/apps/settings/js/modules/panel_utils.js
Executable file
560
gaia/apps/settings/js/modules/panel_utils.js
Executable file
|
|
@ -0,0 +1,560 @@
|
|||
/**
|
||||
* PanelUtils is a singleton that defines panel related utility functions.
|
||||
*
|
||||
* @module PanelUtils
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var Settings = require('settings');
|
||||
var SettingsCache = require('modules/settings_cache');
|
||||
var LazyLoader = require('shared/lazy_loader');
|
||||
|
||||
var _settings = navigator.mozSettings;
|
||||
|
||||
/**
|
||||
* These so-called "dialog boxes" are just standard Settings panels
|
||||
* (<section role="region" />) with reset/submit buttons: these buttons both
|
||||
* return to the previous panel when clicked, and each button has its own
|
||||
* (optional) callback.
|
||||
*/
|
||||
function openDialog(dialogID, onSubmit, onReset) {
|
||||
if ('#' + dialogID === Settings.currentPanel) {
|
||||
return;
|
||||
}
|
||||
|
||||
var origin = Settings.currentPanel;
|
||||
|
||||
// Load dialog contents and show it.
|
||||
Settings.currentPanel = dialogID;
|
||||
|
||||
var dialog = document.getElementById(dialogID);
|
||||
var submit = dialog.querySelector('[type=submit]');
|
||||
if (submit) {
|
||||
submit.onclick = function onsubmit() {
|
||||
if (typeof onSubmit === 'function') {
|
||||
(onSubmit.bind(dialog))();
|
||||
}
|
||||
Settings.currentPanel = origin; // hide dialog box
|
||||
};
|
||||
}
|
||||
|
||||
var reset = dialog.querySelector('[type=reset]');
|
||||
if (reset) {
|
||||
reset.onclick = function onreset() {
|
||||
if (typeof onReset === 'function') {
|
||||
(onReset.bind(dialog))();
|
||||
}
|
||||
Settings.currentPanel = origin; // hide dialog box
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/* Open a link with a web activity */
|
||||
function openLink(url) {
|
||||
if (url.startsWith('tel:')) { // dial a phone number
|
||||
var telActivity = new window.MozActivity({
|
||||
name: 'dial',
|
||||
data: { type: 'webtelephony/number', number: url.substr(4) }
|
||||
});
|
||||
// For workaround jshint.
|
||||
telActivity.onsuccess = function() {};
|
||||
} else if (!url.startsWith('#')) { // browse a URL
|
||||
var linkActivity = new window.MozActivity({
|
||||
name: 'view',
|
||||
data: { type: 'url', url: url }
|
||||
});
|
||||
// For workaround jshint.
|
||||
linkActivity.onsuccess = function() {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the dialog of a specified id.
|
||||
*
|
||||
* @param {String} dialogID
|
||||
* The id of the dialog element.
|
||||
*/
|
||||
var _openDialog = function pu_openDialog(dialogID) {
|
||||
var dialog = document.getElementById(dialogID);
|
||||
var fields = Array.prototype.slice.call(
|
||||
dialog.querySelectorAll('[data-setting]:not([data-ignore])'));
|
||||
|
||||
var updateInput = function(lock, input) {
|
||||
var key = input.dataset.setting;
|
||||
var request = lock.get(key);
|
||||
|
||||
request.onsuccess = function() {
|
||||
switch (input.type) {
|
||||
case 'gaia-radio':
|
||||
input.checked = (input.value == request.result[key]);
|
||||
break;
|
||||
case 'checkbox':
|
||||
case 'gaia-checkbox':
|
||||
input.checked = request.result[key] || false;
|
||||
break;
|
||||
case 'select-one':
|
||||
input.value = request.result[key] || '';
|
||||
break;
|
||||
default:
|
||||
input.value = request.result[key] || '';
|
||||
break;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* In Settings dialog boxes, we don't want the input fields to be preset
|
||||
* by Settings.init() and we don't want them to set the related settings
|
||||
* without any user validation.
|
||||
*
|
||||
* So instead of assigning a `name' attribute to these inputs, a
|
||||
* `data-setting' attribute is used and the input values are set
|
||||
* explicitely when the dialog is shown. If the dialog is validated
|
||||
* (submit), their values are stored into B2G settings.
|
||||
*
|
||||
* XXX warning:
|
||||
* this only supports text/password/radio/select/radio input types.
|
||||
*/
|
||||
|
||||
// initialize all setting fields in the dialog box
|
||||
// XXX for fields being added by lazily loaded script,
|
||||
// it would have to initialize the fields again themselves.
|
||||
function reset() {
|
||||
if (_settings) {
|
||||
var lock = _settings.createLock();
|
||||
fields.forEach(updateInput.bind(null, lock));
|
||||
}
|
||||
}
|
||||
|
||||
// validate all settings in the dialog box
|
||||
function submit() {
|
||||
if (_settings) {
|
||||
// Update the fields node list to include dynamically added fields
|
||||
fields = Array.prototype.slice.call(
|
||||
dialog.querySelectorAll('[data-setting]:not([data-ignore])'));
|
||||
var cset = {}, key;
|
||||
var lock = _settings.createLock();
|
||||
|
||||
fields.forEach(function(input) {
|
||||
key = input.dataset.setting;
|
||||
switch (input.type) {
|
||||
case 'gaia-radio':
|
||||
if (input.checked) {
|
||||
cset[key] = input.value;
|
||||
}
|
||||
break;
|
||||
case 'checkbox':
|
||||
case 'gaia-checkbox':
|
||||
cset[key] = input.checked;
|
||||
break;
|
||||
default:
|
||||
cset[key] = input.value;
|
||||
break;
|
||||
}
|
||||
});
|
||||
lock.set(cset);
|
||||
}
|
||||
}
|
||||
|
||||
reset(); // preset all fields before opening the dialog
|
||||
openDialog(dialogID, submit);
|
||||
};
|
||||
|
||||
return {
|
||||
/**
|
||||
* The function parses all links in the panel and adds corresponding
|
||||
* handlers.
|
||||
* There are three types of links:
|
||||
* - a[href^="http"]: External link
|
||||
* - a[href^="tel"]: External link
|
||||
* - [data-href]: Generic dialog link and settings-specific dialog link
|
||||
*
|
||||
* @alias module:PanelUtils#activate
|
||||
* @param {HTMLElement} panel
|
||||
* The root element of the panel.
|
||||
*/
|
||||
activate: function pu_activate(panel) {
|
||||
// activate all scripts
|
||||
var scripts = panel.getElementsByTagName('script');
|
||||
var scripts_src = Array.prototype.map.call(scripts, function(script) {
|
||||
return script.getAttribute('src');
|
||||
});
|
||||
LazyLoader.load(scripts_src);
|
||||
|
||||
var _onclick = function(evt) {
|
||||
if (!this.dataset.href) {
|
||||
this.dataset.href = this.href;
|
||||
this.href = '#';
|
||||
}
|
||||
var href = this.dataset.href;
|
||||
if (!href.startsWith('#')) { // external link
|
||||
evt.target.blur();
|
||||
openLink(href);
|
||||
} else if (!href.endsWith('Settings')) { // generic dialog
|
||||
openDialog(href.substr(1));
|
||||
} else { // Settings-specific dialog box
|
||||
_openDialog(href.substr(1));
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// activate all links
|
||||
var rule = 'a[href^="http"], a[href^="tel"], [data-href]';
|
||||
var links = panel.querySelectorAll(rule);
|
||||
var i, count;
|
||||
|
||||
for (i = 0, count = links.length; i < count; i++) {
|
||||
if (links[i].tagName !== 'GAIA-HEADER') {
|
||||
links[i].addEventListener('click', _onclick);
|
||||
}
|
||||
}
|
||||
|
||||
// Setup back listener
|
||||
var backHeader = panel.querySelector('gaia-header[action="back"]');
|
||||
var href = backHeader && backHeader.dataset.href;
|
||||
if (backHeader && href) {
|
||||
backHeader.addEventListener('action', function() {
|
||||
Settings.currentPanel = this.dataset.href;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The function presets elements with the settings values.
|
||||
* The supported formats are:
|
||||
* - An input element with a "name" attribute and its value is a settings
|
||||
* key.
|
||||
* - A select element with a "name" attribute and its value is a settings
|
||||
* key.
|
||||
* - A span element with a "data-name" attribute and its value is a settings
|
||||
* key.
|
||||
*
|
||||
* @alias module:PanelUtils#preset
|
||||
* @param {HTMLElement} panel
|
||||
* The root element of the panel.
|
||||
*/
|
||||
preset: function pu_preset(panel) {
|
||||
SettingsCache.getSettings(function(result) {
|
||||
panel = panel || document;
|
||||
|
||||
// preset all checkboxes
|
||||
var rule = 'input[type="checkbox"]:not([data-ignore]), gaia-switch, ' +
|
||||
'gaia-checkbox';
|
||||
var checkboxes = panel.querySelectorAll(rule);
|
||||
var i, count, key;
|
||||
for (i = 0, count = checkboxes.length; i < count; i++) {
|
||||
key = checkboxes[i].name;
|
||||
if (key && result[key] !== undefined) {
|
||||
checkboxes[i].checked = !!result[key];
|
||||
}
|
||||
}
|
||||
|
||||
// remove initial class so the swich animation will apply
|
||||
// on these toggles if user interact with it.
|
||||
setTimeout(function() {
|
||||
for (i = 0, count = checkboxes.length; i < count; i++) {
|
||||
if (checkboxes[i].classList.contains('initial')) {
|
||||
checkboxes[i].classList.remove('initial');
|
||||
}
|
||||
}
|
||||
}, 0);
|
||||
|
||||
// preset all radio buttons
|
||||
rule = 'gaia-radio:not([data-ignore])';
|
||||
var radios = panel.querySelectorAll(rule);
|
||||
for (i = 0, count = radios.length; i < count; i++) {
|
||||
key = radios[i].name;
|
||||
if (key && result[key] !== undefined) {
|
||||
radios[i].checked = (result[key] === radios[i].value);
|
||||
}
|
||||
}
|
||||
|
||||
// preset all text inputs
|
||||
rule = 'input[type="text"]:not([data-ignore])';
|
||||
var texts = panel.querySelectorAll(rule);
|
||||
for (i = 0, count = texts.length; i < count; i++) {
|
||||
key = texts[i].name;
|
||||
if (key && result[key] !== undefined) {
|
||||
texts[i].value = result[key];
|
||||
}
|
||||
}
|
||||
|
||||
// preset all range inputs
|
||||
rule = 'input[type="range"]:not([data-ignore])';
|
||||
var ranges = panel.querySelectorAll(rule);
|
||||
for (i = 0, count = ranges.length; i < count; i++) {
|
||||
key = ranges[i].name;
|
||||
if (key && result[key] !== undefined) {
|
||||
ranges[i].value = parseFloat(result[key]);
|
||||
}
|
||||
}
|
||||
|
||||
// preset all select
|
||||
var selects = panel.querySelectorAll('select');
|
||||
for (i = 0, count = selects.length; i < count; i++) {
|
||||
var select = selects[i];
|
||||
key = select.name;
|
||||
if (key && result[key] !== undefined) {
|
||||
var value = result[key];
|
||||
var option = 'option[value="' + value + '"]';
|
||||
var selectOption = select.querySelector(option);
|
||||
if (selectOption) {
|
||||
selectOption.selected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// preset all span with data-name fields
|
||||
rule = '[data-name]:not([data-ignore])';
|
||||
var spanFields = panel.querySelectorAll(rule);
|
||||
for (i = 0, count = spanFields.length; i < count; i++) {
|
||||
key = spanFields[i].dataset.name;
|
||||
|
||||
// Mulet has no physical firmware metadata. Use the Vulpes labels
|
||||
// instead of exposing the generic 1.0.0 desktop fallback.
|
||||
if (key === 'deviceinfo.os' &&
|
||||
(!result[key] || result[key] === '1.0.0')) {
|
||||
result[key] = 'Firefox OS 2.7';
|
||||
}
|
||||
if (key === 'deviceinfo.software' &&
|
||||
(!result[key] || result[key] === '1.0.0')) {
|
||||
result[key] = 'Project Vulpes 2.7';
|
||||
}
|
||||
|
||||
// XXX intentionally checking for the string 'undefined',
|
||||
// see bug 880617
|
||||
if (key && result[key] && result[key] != 'undefined') {
|
||||
// check whether this setting comes from a select option
|
||||
// (it may be in a different panel, so query the whole document)
|
||||
rule = '[data-setting="' + key + '"] ' +
|
||||
'[value="' + result[key] + '"]';
|
||||
var option_span = document.querySelector(rule);
|
||||
if (option_span) {
|
||||
spanFields[i].setAttribute('data-l10n-id',
|
||||
option_span.getAttribute('data-l10n-id'));
|
||||
} else {
|
||||
spanFields[i].removeAttribute('data-l10n-id');
|
||||
spanFields[i].textContent = result[key];
|
||||
}
|
||||
} else { // result[key] is undefined
|
||||
switch (key) {
|
||||
//XXX bug 816899 will also provide 'deviceinfo.software' from
|
||||
// Gecko which is {os name + os version}
|
||||
case 'deviceinfo.software':
|
||||
document.l10n.setAttributes(spanFields[i],
|
||||
'deviceInfo_software',
|
||||
{ os: result['deviceinfo.os'] });
|
||||
break;
|
||||
|
||||
//XXX workaround request from bug 808892 comment 22
|
||||
// hide this field if it's undefined/empty.
|
||||
case 'deviceinfo.firmware_revision':
|
||||
spanFields[i].parentNode.hidden = true;
|
||||
break;
|
||||
|
||||
case 'deviceinfo.mac':
|
||||
spanFields[i].setAttribute('data-l10n-id', 'macUnavailable');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// unhide items according to preferences.
|
||||
rule = '[data-show-name]:not([data-ignore])';
|
||||
var hiddenItems = panel.querySelectorAll(rule);
|
||||
for (i = 0; i < hiddenItems.length; i++) {
|
||||
key = hiddenItems[i].dataset.showName;
|
||||
hiddenItems[i].hidden = !result[key];
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* When a link element is clicked, the function navigates the app to the
|
||||
* panel of the id specified by the "href" attribute of the element.
|
||||
*
|
||||
* @alias module:PanelUtils#onLinkClick
|
||||
* @param {Event} event
|
||||
*/
|
||||
onLinkClick: function pu_onLinkClick(event) {
|
||||
var target = event.target;
|
||||
var href;
|
||||
|
||||
if (target.classList.contains('icon-back')) {
|
||||
href = target.parentNode.getAttribute('href');
|
||||
} else {
|
||||
var nodeName = target.nodeName.toLowerCase();
|
||||
if (nodeName != 'a') {
|
||||
return;
|
||||
}
|
||||
href = target.getAttribute('href');
|
||||
}
|
||||
// skips the following case:
|
||||
// 1. no href, which is not panel
|
||||
// 2. href is not a hash which is not a panel
|
||||
// 3. href equals # which is translated with loadPanel function, they are
|
||||
// external links.
|
||||
if (!href || !href.startsWith('#') || href === '#') {
|
||||
return;
|
||||
}
|
||||
|
||||
Settings.currentPanel = href;
|
||||
event.preventDefault();
|
||||
},
|
||||
|
||||
/**
|
||||
* Respond to settings changes.
|
||||
* The supported formats are:
|
||||
* - An input element with a "name" attribute and its value is a settings
|
||||
* key.
|
||||
* - A select element with a "name" attribute and its value is a settings
|
||||
* key.
|
||||
* - A span element with a "data-name" attribute and its value is a settings
|
||||
* key.
|
||||
* - Elements with a "data-show-name" attribute. It hides the element when
|
||||
* the value is false and vice versa.
|
||||
*
|
||||
* @alias module:PanelUtils#onSettingsChange
|
||||
* @param {HTMLElement} panel
|
||||
* @param {Event} event
|
||||
*/
|
||||
onSettingsChange: function pu_onSettingsChange(panel, event) {
|
||||
var key = event.settingName;
|
||||
var value = event.settingValue;
|
||||
var i, count;
|
||||
|
||||
// update <span> values when the corresponding setting is changed
|
||||
var rule = '[data-name="' + key + '"]:not([data-ignore])';
|
||||
var spanField = panel.querySelector(rule);
|
||||
if (spanField) {
|
||||
// check whether this setting comes from a select option
|
||||
var options = panel.querySelector('select[data-setting="' + key + '"]');
|
||||
if (options) {
|
||||
// iterate option matching
|
||||
for (i = 0, count = options.length; i < count; i++) {
|
||||
if (options[i] && options[i].value === value) {
|
||||
spanField.dataset.l10nId = options[i].dataset.l10nId;
|
||||
spanField.textContent = options[i].textContent;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
spanField.textContent = value;
|
||||
}
|
||||
}
|
||||
|
||||
// hide or unhide items
|
||||
rule = '[data-show-name="' + key + '"]:not([data-ignore])';
|
||||
var items = document.querySelectorAll(rule);
|
||||
for (i = 0; i < items.length; i++) {
|
||||
items[i].hidden = !value;
|
||||
}
|
||||
|
||||
// update <input> values when the corresponding setting is changed
|
||||
var inputs = [].slice.call(panel.querySelectorAll(
|
||||
`input[name="${key}"],
|
||||
gaia-switch[name="${key}"],
|
||||
gaia-checkbox[name="${key}"],
|
||||
gaia-radio[name="${key}"]`));
|
||||
if (!inputs.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
inputs.forEach((input) => {
|
||||
switch (input.type) {
|
||||
case 'gaia-switch':
|
||||
case 'gaia-checkbox':
|
||||
case 'checkbox':
|
||||
case 'switch':
|
||||
value = !!value;
|
||||
if (input.checked === value) {
|
||||
return;
|
||||
}
|
||||
input.checked = value;
|
||||
break;
|
||||
case 'range':
|
||||
if (input.value === value) {
|
||||
return;
|
||||
}
|
||||
input.value = value;
|
||||
break;
|
||||
case 'select':
|
||||
for (i = 0, count = input.options.length; i < count; i++) {
|
||||
if (input.options[i].value === value) {
|
||||
input.options[i].selected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'gaia-radio':
|
||||
input.checked = (input.value === value);
|
||||
break;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Respond to settings changes.
|
||||
* The supported formats are:
|
||||
* - An input element with a "name" attribute and its value is a settings
|
||||
* key.
|
||||
* - A select element with a "name" attribute and its value is a settings
|
||||
* key.
|
||||
* - A span element with a "data-name" attribute and its value is a settings
|
||||
* key.
|
||||
* - Elements with a "data-show-name" attribute. It hides the element when
|
||||
* the value is false and vice versa.
|
||||
*
|
||||
* @alias module:PanelUtils#onInputChange
|
||||
* @param {HTMLElement} panel
|
||||
* @param {Event} event
|
||||
*/
|
||||
onInputChange: function pu_onInputChange(event) {
|
||||
var input = event.target;
|
||||
var type = input.type || input.nodeName.toLowerCase();
|
||||
var key = input.name;
|
||||
|
||||
//XXX should we check data-ignore here?
|
||||
if (!key || !_settings || event.type != 'change') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Not touching <input> with data-setting attribute here
|
||||
// because they would have to be committed with a explicit "submit"
|
||||
// of their own dialog.
|
||||
if (input.dataset.setting) {
|
||||
return;
|
||||
}
|
||||
|
||||
var value;
|
||||
switch (type) {
|
||||
case 'gaia-switch':
|
||||
case 'gaia-checkbox':
|
||||
case 'checkbox':
|
||||
case 'switch':
|
||||
value = input.checked; // boolean
|
||||
break;
|
||||
case 'range':
|
||||
// Bug 906296:
|
||||
// We parseFloat() once to be able to round to 1 digit, then
|
||||
// we parseFloat() again to make sure to store a Number and
|
||||
// not a String, otherwise this will make Gecko unable to
|
||||
// apply new settings.
|
||||
value = parseFloat(parseFloat(input.value).toFixed(1)); // float
|
||||
break;
|
||||
case 'select-one':
|
||||
case 'gaia-radio':
|
||||
case 'text':
|
||||
case 'password':
|
||||
value = input.value; // default as text
|
||||
if (input.dataset.valueType === 'integer') { // integer
|
||||
value = parseInt(value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
var cset = {}; cset[key] = value;
|
||||
_settings.createLock().set(cset);
|
||||
}
|
||||
};
|
||||
});
|
||||
147
gaia/apps/settings/js/modules/settings_cache.js
Executable file
147
gaia/apps/settings/js/modules/settings_cache.js
Executable file
|
|
@ -0,0 +1,147 @@
|
|||
/**
|
||||
* SettingsCache is a singleton that caches mozSettings values for fast
|
||||
* access.
|
||||
*
|
||||
* @module SettingsCache
|
||||
*/
|
||||
define(function() {
|
||||
'use strict';
|
||||
var _settings = window.navigator.mozSettings;
|
||||
|
||||
// Cache of all current settings values. There's some large stuff
|
||||
// in here, but not much useful can be done with the settings app
|
||||
// without these, so we keep this around most of the time.
|
||||
var _settingsCache = null;
|
||||
|
||||
// True when a request has already been made to fill the settings
|
||||
// cache. When this is true, no further get("*") requests should be
|
||||
// made; instead, pending callbacks should be added to
|
||||
// _pendingSettingsCallbacks.
|
||||
var _settingsCacheRequestSent = null;
|
||||
|
||||
// There can be race conditions in which we need settings values,
|
||||
// but haven't filled the cache yet. This array tracks those
|
||||
// listeners.
|
||||
var _pendingSettingsCallbacks = [];
|
||||
|
||||
var _callbacks = [];
|
||||
|
||||
var _onSettingsChange = function sc_onSettingsChange(event) {
|
||||
var key = event.settingName;
|
||||
var value = event.settingValue;
|
||||
|
||||
// Always update the cache if it's present, even if the DOM
|
||||
// isn't loaded yet.
|
||||
if (_settingsCache) {
|
||||
_settingsCache[key] = value;
|
||||
}
|
||||
|
||||
_callbacks.forEach(function(callback) {
|
||||
callback(event);
|
||||
});
|
||||
};
|
||||
|
||||
if (_settings) {
|
||||
_settings.onsettingchange = _onSettingsChange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event reporting that a setting value is changed.
|
||||
*
|
||||
* @event module:SettingsCache#settingsChange
|
||||
* @property {MozSettingsEvent} event
|
||||
*/
|
||||
var SettingsCache = {
|
||||
// the reset function is for unit tests
|
||||
reset: function sc_reset() {
|
||||
_settings = window.navigator.mozSettings;
|
||||
if (_settings) {
|
||||
_settings.onsettingchange = _onSettingsChange;
|
||||
}
|
||||
_settingsCache = null;
|
||||
_settingsCacheRequestSent = null;
|
||||
_pendingSettingsCallbacks = [];
|
||||
_callbacks = [];
|
||||
},
|
||||
|
||||
get cache() {
|
||||
return _settingsCache;
|
||||
},
|
||||
|
||||
/**
|
||||
* Where callback is a function to be called with a request object for a
|
||||
* successful fetch of settings values, when those values are ready.
|
||||
*
|
||||
* @alias module:SettingsCache#getSettings
|
||||
* @param {Function} callback
|
||||
*/
|
||||
getSettings: function sc_getSettings(callback) {
|
||||
if (!_settings) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_settingsCache && callback) {
|
||||
// Fast-path that we hope to always hit: our settings cache is
|
||||
// already available, so invoke the callback now.
|
||||
callback(_settingsCache);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_settingsCacheRequestSent && !_settingsCache) {
|
||||
_settingsCacheRequestSent = true;
|
||||
var lock = _settings.createLock();
|
||||
var request = lock.get('*');
|
||||
request.onsuccess = function(e) {
|
||||
var result = request.result;
|
||||
var cachedResult = {};
|
||||
for (var attr in result) {
|
||||
cachedResult[attr] = result[attr];
|
||||
}
|
||||
_settingsCache = cachedResult;
|
||||
var cbk;
|
||||
while ((cbk = _pendingSettingsCallbacks.pop())) {
|
||||
cbk(result);
|
||||
}
|
||||
};
|
||||
}
|
||||
if (callback) {
|
||||
_pendingSettingsCallbacks.push(callback);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @alias module:SettingsCache#addEventListener
|
||||
* @param {String} eventName
|
||||
* @param {Function} callback
|
||||
*/
|
||||
addEventListener: function sc_addEventListener(eventName, callback) {
|
||||
if (eventName !== 'settingsChange') {
|
||||
return;
|
||||
}
|
||||
var index = _callbacks.indexOf(callback);
|
||||
if (index === -1) {
|
||||
_callbacks.push(callback);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @alias module:SettingsCache#removeEventListener
|
||||
* @param {String} eventName
|
||||
* @param {Function} callback
|
||||
*/
|
||||
removeEventListener: function sc_removeEventListsner(eventName, callback) {
|
||||
if (eventName !== 'settingsChange') {
|
||||
return;
|
||||
}
|
||||
var index = _callbacks.indexOf(callback);
|
||||
if (index !== -1) {
|
||||
_callbacks.splice(index, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Make a request for settings to warm the cache, since we need it
|
||||
// very soon in startup after the DOM is available.
|
||||
SettingsCache.getSettings(null);
|
||||
return SettingsCache;
|
||||
});
|
||||
121
gaia/apps/settings/js/modules/settings_panel.js
Executable file
121
gaia/apps/settings/js/modules/settings_panel.js
Executable file
|
|
@ -0,0 +1,121 @@
|
|||
/**
|
||||
* SettingsPanel extends Panel with basic settings services. It presets the UI
|
||||
* elements based on the values in mozSettings and add listeners responding to
|
||||
* mozSettings changes in onReady. In onInit it parses the panel element for
|
||||
* activating links. It also removes listeners in onDone so that we can avoid
|
||||
* unwanted UI updates when the panel is outside of the viewport.
|
||||
*
|
||||
* @module SettingsPanel
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var Panel = require('modules/panel');
|
||||
var SettingsCache = require('modules/settings_cache');
|
||||
var PanelUtils = require('modules/panel_utils');
|
||||
|
||||
var _emptyFunc = function panel_emptyFunc() {};
|
||||
|
||||
/**
|
||||
* @alias module:SettingsPanel
|
||||
* @param {Object} options
|
||||
* Options are used to override the internal functions of
|
||||
* Panel.
|
||||
* @returns {SettingsPanel}
|
||||
*/
|
||||
var SettingsPanel = function ctor_SettingsPanel(options) {
|
||||
/**
|
||||
* The root element of the panel.
|
||||
*
|
||||
* @type {HTMLElement}
|
||||
*/
|
||||
var _panel = null;
|
||||
|
||||
/**
|
||||
* The handler is called when settings change.
|
||||
*
|
||||
* @param {Event} event
|
||||
*/
|
||||
var _settingsChangeHandler = function(event) {
|
||||
PanelUtils.onSettingsChange(_panel, event);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add listeners to make the panel be able to respond to setting changes
|
||||
* and user interactions.
|
||||
*
|
||||
* @param {HTMLElement} panel
|
||||
*/
|
||||
var _addListeners = function panel_addListeners(panel) {
|
||||
if (!panel) {
|
||||
return;
|
||||
}
|
||||
|
||||
SettingsCache.addEventListener('settingsChange',
|
||||
_settingsChangeHandler);
|
||||
panel.addEventListener('change', PanelUtils.onInputChange);
|
||||
panel.addEventListener('click', PanelUtils.onLinkClick);
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove all listeners.
|
||||
*
|
||||
* @param {HTMLElement} panel
|
||||
*/
|
||||
var _removeListeners = function panel_removeListeners(panel) {
|
||||
if (!panel) {
|
||||
return;
|
||||
}
|
||||
|
||||
SettingsCache.removeEventListener('settingsChange',
|
||||
_settingsChangeHandler);
|
||||
panel.removeEventListener('change', PanelUtils.onInputChange);
|
||||
panel.removeEventListener('click', PanelUtils.onLinkClick);
|
||||
};
|
||||
|
||||
options = options || {};
|
||||
options.onInit = options.onInit || _emptyFunc;
|
||||
options.onUninit = options.onUninit || _emptyFunc;
|
||||
options.onShow = options.onShow || _emptyFunc;
|
||||
options.onHide = options.onHide || _emptyFunc;
|
||||
options.onBeforeShow = options.onBeforeShow || _emptyFunc;
|
||||
options.onBeforeHide = options.onBeforeHide || _emptyFunc;
|
||||
|
||||
return Panel({
|
||||
onInit: function(panel, initOptions) {
|
||||
if (!panel) {
|
||||
return;
|
||||
}
|
||||
|
||||
_panel = panel;
|
||||
PanelUtils.activate(panel);
|
||||
|
||||
return options.onInit(panel, initOptions);
|
||||
},
|
||||
onUninit: function() {
|
||||
_removeListeners(_panel);
|
||||
_panel = null;
|
||||
options.onUninit();
|
||||
},
|
||||
onShow: function(panel, showOptions) {
|
||||
return options.onShow(panel, showOptions);
|
||||
},
|
||||
onHide: function() {
|
||||
// Remove listeners.
|
||||
_removeListeners(_panel);
|
||||
|
||||
return options.onHide();
|
||||
},
|
||||
onBeforeShow: function(panel, beforeShowOptions) {
|
||||
// Preset the panel every time when it is presented.
|
||||
PanelUtils.preset(panel);
|
||||
_addListeners(panel);
|
||||
return options.onBeforeShow(panel, beforeShowOptions);
|
||||
},
|
||||
onBeforeHide: function() {
|
||||
return options.onBeforeHide();
|
||||
}
|
||||
});
|
||||
};
|
||||
return SettingsPanel;
|
||||
});
|
||||
364
gaia/apps/settings/js/modules/settings_service.js
Executable file
364
gaia/apps/settings/js/modules/settings_service.js
Executable file
|
|
@ -0,0 +1,364 @@
|
|||
/**
|
||||
* SettingsService is a singleton that provides the navigation service. It
|
||||
* gets the corresponding panel module from PanelCache and call to its basic
|
||||
* functions when navigating.
|
||||
*
|
||||
* @module SettingsService
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var PageTransitions = require('modules/page_transitions');
|
||||
var PanelCache = require('modules/panel_cache');
|
||||
var ScreenLayout = require('shared/screen_layout');
|
||||
var LazyLoader = require('shared/lazy_loader');
|
||||
var Settings = require('settings');
|
||||
|
||||
var _rootPanelId = null;
|
||||
/**
|
||||
* _currentNavigation caches information of the current panel including id,
|
||||
* element, module, and options.
|
||||
*/
|
||||
var _currentNavigation = null;
|
||||
var _navigating = false;
|
||||
var _pendingNavigationRequest = null;
|
||||
|
||||
var _cachedNavigation = null;
|
||||
var _cachedNavigationOptions = {};
|
||||
|
||||
var _activityHandler = null;
|
||||
|
||||
var _loadModulesForSubPanelsPromise = null;
|
||||
|
||||
var _getAppNameToLink = function ss_get_app_name_to_link(panelId) {
|
||||
var reAppName = /app:(\w+)/;
|
||||
var name = reAppName.exec(panelId);
|
||||
return name && name[1];
|
||||
};
|
||||
|
||||
var _getAppInfo = function ss_get_app_info(appName) {
|
||||
// We can customize the path for specific apps
|
||||
var _supportedAppInFrame = {
|
||||
keyboard: {},
|
||||
bluetooth: {}
|
||||
};
|
||||
|
||||
var appInfo = _supportedAppInFrame[appName];
|
||||
if (!appInfo) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var prefix = 'app://' + appName + '.gaiamobile.org/';
|
||||
var defaultSrc = prefix + 'settings.html';
|
||||
var appMozapp = prefix + 'manifest.webapp';
|
||||
var appSrc = appInfo.src ? prefix + appInfo.src : defaultSrc;
|
||||
|
||||
return {
|
||||
src: appSrc,
|
||||
mozapp: appMozapp
|
||||
};
|
||||
};
|
||||
|
||||
var _isTabletAndLandscape = function ss_is_tablet_and_landscape() {
|
||||
return ScreenLayout.getCurrentLayout('tabletAndLandscaped');
|
||||
};
|
||||
|
||||
var _retriveParentPanelId = function ss_retriveParentPanelId(panelId) {
|
||||
var headerSelector = '#' + panelId + ' > gaia-header';
|
||||
var header = document.querySelector(headerSelector);
|
||||
return (header && header.dataset.href || '').replace('#', '');
|
||||
};
|
||||
|
||||
var _shallCloseActivity = function ss_shallCloseActivity(panelId) {
|
||||
// If we're handling an activity and the 'back' button is hit, close the
|
||||
// activity if the panel id to be navigated equals the parent panel id.
|
||||
|
||||
// This is for the root panel
|
||||
if (panelId === 'close') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!_currentNavigation) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the parent panel id of the current panel.
|
||||
var parentPanelId = _retriveParentPanelId(_currentNavigation.panelId);
|
||||
|
||||
// Close the activity if the current panel is the original target panel,
|
||||
// and the new panel is the parent panel of the current panel.
|
||||
return _currentNavigation.panelId === _activityHandler.targetPanelId &&
|
||||
panelId === parentPanelId;
|
||||
};
|
||||
|
||||
var _transit = function ss_transit(oldPanel, newPanel, callback) {
|
||||
var promise = new Promise(function(resolve) {
|
||||
var wrappedCallback = function() {
|
||||
if (typeof callback === 'function') {
|
||||
callback();
|
||||
}
|
||||
resolve();
|
||||
};
|
||||
|
||||
if (_isTabletAndLandscape()) {
|
||||
PageTransitions.twoColumn(oldPanel, newPanel, wrappedCallback);
|
||||
} else {
|
||||
PageTransitions.oneColumn(oldPanel, newPanel, wrappedCallback);
|
||||
}
|
||||
});
|
||||
return promise;
|
||||
};
|
||||
|
||||
var _loadPanel = function ss_loadPanel(panelId, callback) {
|
||||
var panelElement = document.getElementById(panelId);
|
||||
if (panelElement.dataset.rendered) { // already initialized
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
panelElement.dataset.rendered = true;
|
||||
|
||||
// XXX remove SubPanel loader once sub panel are modulized
|
||||
if (panelElement.dataset.requireSubPanels) {
|
||||
// load the panel and its sub-panels (dependencies)
|
||||
// (load the main panel last because it contains the scripts)
|
||||
var selector = 'section[id^="' + panelElement.id + '-"]';
|
||||
var subPanels = document.querySelectorAll(selector);
|
||||
for (var i = 0, il = subPanels.length; i < il; i++) {
|
||||
LazyLoader.load([subPanels[i]]);
|
||||
}
|
||||
LazyLoader.load([panelElement], callback);
|
||||
} else {
|
||||
LazyLoader.load([panelElement], callback);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var _loadModulesForSubPanels = function ss_loadModules(panelId) {
|
||||
if (panelId === _rootPanelId) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
if (!_loadModulesForSubPanelsPromise) {
|
||||
_loadModulesForSubPanelsPromise = new Promise(function(resolve) {
|
||||
require(['shared/async_storage'], resolve);
|
||||
});
|
||||
}
|
||||
return _loadModulesForSubPanelsPromise;
|
||||
}
|
||||
};
|
||||
|
||||
var _onVisibilityChange = function ss_onVisibilityChange() {
|
||||
_handleVisibilityChange(!document.hidden);
|
||||
};
|
||||
|
||||
/**
|
||||
* When the app becomes invisible, we should call to beforeHide and hide
|
||||
* functions of the current panel. When the app becomes visible, we should
|
||||
* call to beforeShow and show functions of the current panel with the
|
||||
* cached options.
|
||||
*/
|
||||
var _handleVisibilityChange = function ss_onVisibilityChange(visible) {
|
||||
if (!_currentNavigation) {
|
||||
return;
|
||||
}
|
||||
|
||||
var panel = _currentNavigation.panel;
|
||||
var element = _currentNavigation.panelElement;
|
||||
var options = _currentNavigation.options;
|
||||
|
||||
if (!panel) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (visible) {
|
||||
panel.beforeShow(element, options);
|
||||
panel.show(element, options);
|
||||
} else {
|
||||
panel.beforeHide();
|
||||
panel.hide();
|
||||
}
|
||||
};
|
||||
|
||||
var _navigate = function ss_navigate(panelId, options, callback) {
|
||||
// Early return if the panel to be navigated is the same as the
|
||||
// current one.
|
||||
if (_currentNavigation && _currentNavigation.panelId === panelId) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
_loadPanel(panelId, function() {
|
||||
PanelCache.get(panelId, function(panel) {
|
||||
var newPanelElement = document.getElementById(panelId);
|
||||
var currentPanelId =
|
||||
_currentNavigation && _currentNavigation.panelId;
|
||||
var currentPanelElement =
|
||||
_currentNavigation && _currentNavigation.panelElement;
|
||||
var currentPanel = _currentNavigation && _currentNavigation.panel;
|
||||
|
||||
// Keep these to make sure we can use when going back
|
||||
_cachedNavigation = _currentNavigation;
|
||||
_cachedNavigationOptions = options;
|
||||
|
||||
// Prepare options and calls to the panel object's before
|
||||
// show function.
|
||||
options = options || {};
|
||||
|
||||
// 0. start the chain
|
||||
_loadModulesForSubPanels(panelId)
|
||||
// 1. beforeHide previous panel
|
||||
.then(function() {
|
||||
// We don't deactivate the root panel.
|
||||
if (currentPanel && currentPanelId !== _rootPanelId) {
|
||||
return currentPanel.beforeHide();
|
||||
}
|
||||
})
|
||||
// 2. beforeShow next panel
|
||||
.then(function() {
|
||||
return panel.beforeShow(newPanelElement, options);
|
||||
})
|
||||
// 3. do the transition
|
||||
.then(function() {
|
||||
return _transit(currentPanelElement, newPanelElement);
|
||||
})
|
||||
// 4. hide previous panel
|
||||
.then(function() {
|
||||
// We don't deactivate the root panel.
|
||||
if (currentPanel && currentPanelId !== _rootPanelId) {
|
||||
return currentPanel.hide();
|
||||
}
|
||||
})
|
||||
// 5. show next panel
|
||||
.then(function() {
|
||||
return panel.show(newPanelElement, options);
|
||||
})
|
||||
// 6. keep information
|
||||
.then(function() {
|
||||
// Update the current navigation object
|
||||
_currentNavigation = {
|
||||
panelId: panelId,
|
||||
panelElement: newPanelElement,
|
||||
panel: panel,
|
||||
options: options
|
||||
};
|
||||
|
||||
// XXX we need to remove this line in the future
|
||||
// to make sure we won't manipulate Settings
|
||||
// directly
|
||||
Settings._currentPanel = '#' + panelId;
|
||||
callback();
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
reset: function ss_reset() {
|
||||
_rootPanelId = null;
|
||||
_currentNavigation = null;
|
||||
_cachedNavigation = null;
|
||||
_cachedNavigationOptions = {};
|
||||
_activityHandler = null;
|
||||
_navigating = false;
|
||||
_pendingNavigationRequest = null;
|
||||
window.removeEventListener('visibilitychange', _onVisibilityChange);
|
||||
},
|
||||
|
||||
/**
|
||||
* Init SettingsService.
|
||||
*
|
||||
* @alias module:SettingsService#init
|
||||
* @param {Object} options
|
||||
* @param {String} options.rootPanelId
|
||||
* Panel with the specified id is assumed to be be kept on
|
||||
* on the screen always. We don't call to its hide and
|
||||
* beforeHide functions.
|
||||
* @param {Object} options.context
|
||||
* The launch context specifying the default panel and the
|
||||
* activity handler if the app is invoked by web
|
||||
* activities.
|
||||
* @param {String} options.context.initialPanelId
|
||||
* @param {ActivityHandler} options.context.activityHandler
|
||||
*/
|
||||
init: function ss_init(options) {
|
||||
if (options) {
|
||||
_rootPanelId = options.rootPanelId || 'root';
|
||||
_activityHandler = options.context && options.context.activityHandler;
|
||||
}
|
||||
|
||||
window.addEventListener('visibilitychange', _onVisibilityChange);
|
||||
},
|
||||
|
||||
/**
|
||||
* Navigate to a panel with options. The navigation transition is
|
||||
* determined based on the current screen size and orientation.
|
||||
*
|
||||
* @alias module:SettingsService#navigate
|
||||
* @param {String} panelId
|
||||
* @param {Object} options
|
||||
* @param {Function} callback
|
||||
*/
|
||||
navigate: function ss_navigate(panelId, options, callback) {
|
||||
// Check if the app is invoked by web activity and shall post result.
|
||||
if (_activityHandler && _shallCloseActivity(panelId)) {
|
||||
_activityHandler.postResult();
|
||||
return;
|
||||
}
|
||||
|
||||
// Cache the navigation request if it is navigating.
|
||||
if (_navigating) {
|
||||
_pendingNavigationRequest = arguments;
|
||||
return;
|
||||
}
|
||||
|
||||
// If we find out the link includes information about app's name,
|
||||
// it means that we are going to embed the app into our app.
|
||||
//
|
||||
// In this way, we have to navigate to `frame` panel and embed it.
|
||||
var appName = _getAppNameToLink(panelId);
|
||||
if (appName) {
|
||||
var appInfo = _getAppInfo(appName);
|
||||
|
||||
if (!appInfo) {
|
||||
console.error('We only embed trust apps.');
|
||||
return;
|
||||
}
|
||||
|
||||
panelId = 'frame';
|
||||
options = options || {};
|
||||
options.mozapp = appInfo.mozapp;
|
||||
options.src = appInfo.src;
|
||||
}
|
||||
|
||||
_navigating = true;
|
||||
_navigate(panelId, options, (function() {
|
||||
_navigating = false;
|
||||
|
||||
// Navigate to the pending navigation if any.
|
||||
if (_pendingNavigationRequest) {
|
||||
var args = _pendingNavigationRequest;
|
||||
_pendingNavigationRequest = null;
|
||||
this.navigate.apply(this, args);
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
}).bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Go back to previous panel
|
||||
*
|
||||
* @alias module:SettingsService#back
|
||||
*/
|
||||
back: function ss_back() {
|
||||
if (_activityHandler) {
|
||||
_activityHandler.postResult();
|
||||
} else if (_cachedNavigation) {
|
||||
this.navigate(_cachedNavigation.panelId, _cachedNavigationOptions);
|
||||
_cachedNavigation = null;
|
||||
_cachedNavigationOptions = {};
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
63
gaia/apps/settings/js/modules/settings_utils.js
Executable file
63
gaia/apps/settings/js/modules/settings_utils.js
Executable file
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* SettingsUtils is a singleton that we will provide all needed methods
|
||||
* when doing panel related actions.
|
||||
*
|
||||
* @module SettingsUtils
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
// TODO
|
||||
// We need to move utils.js to SettingsUtils step by step
|
||||
var LazyLoader = require('shared/lazy_loader');
|
||||
|
||||
var SettingsUtils = {
|
||||
/**
|
||||
* We will use LazyLoader to help us load settings' used templates
|
||||
* and then return the html to callback
|
||||
*
|
||||
* @memberOf SettingsUtils
|
||||
* @param {String} panelId - ID of target panel
|
||||
* @param {Function} callback - with html as its first parameter
|
||||
*/
|
||||
loadTemplate: function su_loadTemplate(panelId, callback) {
|
||||
var templateElement = document.getElementById(panelId);
|
||||
if (!templateElement) {
|
||||
callback(null);
|
||||
} else {
|
||||
LazyLoader.load([templateElement], function() {
|
||||
callback(templateElement.innerHTML);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Re-runs the font-fit title
|
||||
* centering logic.
|
||||
*
|
||||
* The gaia-header has mutation observers
|
||||
* that listen for changes in the header
|
||||
* title and re-run the font-fit logic.
|
||||
*
|
||||
* If buttons around the title are shown/hidden
|
||||
* then these mutation observers won't be
|
||||
* triggered, but we want the font-fit logic
|
||||
* to be re-run.
|
||||
*
|
||||
* This is a deficiency of <gaia-header>. If
|
||||
* anyone knows a way to listen for changes
|
||||
* in visibility, we won't need this anymore.
|
||||
*
|
||||
* @param {GaiaHeader} header
|
||||
* @private
|
||||
*/
|
||||
runHeaderFontFit: function su_runHeaderFontFit(header) {
|
||||
var titles = header.querySelectorAll('h1');
|
||||
[].forEach.call(titles, function(title) {
|
||||
title.textContent = title.textContent;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return SettingsUtils;
|
||||
});
|
||||
108
gaia/apps/settings/js/modules/sim_security.js
Executable file
108
gaia/apps/settings/js/modules/sim_security.js
Executable file
|
|
@ -0,0 +1,108 @@
|
|||
/**
|
||||
* This is a simple model that can help us mimic the status change event
|
||||
* when enabling / disabling simcard.
|
||||
*
|
||||
* @module SimSecurity
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var Module = require('modules/base/module');
|
||||
var EventEmitter = require('modules/base/event_emitter');
|
||||
|
||||
var APIS = [
|
||||
'unlockCardLock',
|
||||
'setCardLock',
|
||||
'updateContact',
|
||||
'getCardLock',
|
||||
'getCardLockRetryCount'
|
||||
];
|
||||
|
||||
var EVENT_CHANGE_APIS = [
|
||||
'unlockCardLock',
|
||||
'setCardLock'
|
||||
];
|
||||
|
||||
var _debug = false;
|
||||
var debug = function() {};
|
||||
if (_debug) {
|
||||
debug = function ss_debug(msg) {
|
||||
console.log('--> [SimSecurity]: ' + msg);
|
||||
};
|
||||
}
|
||||
|
||||
var SimSecurity = Module.create(function() {
|
||||
this.super(EventEmitter).call(this, [
|
||||
'pin-enabled',
|
||||
'pin-disabled',
|
||||
'pin2-enabled',
|
||||
'pin2-disabled',
|
||||
'fdn-enabled',
|
||||
'fdn-disabled',
|
||||
'pin-changed',
|
||||
'pin2-changed',
|
||||
'puk2-changed'
|
||||
]);
|
||||
}).extend(EventEmitter);
|
||||
|
||||
SimSecurity.prototype._getIccByCardIndex = function(cardIndex) {
|
||||
if (cardIndex === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var iccObj;
|
||||
if (navigator.mozMobileConnections[cardIndex]) {
|
||||
var iccId = navigator.mozMobileConnections[cardIndex].iccId;
|
||||
if (iccId) {
|
||||
iccObj = navigator.mozIccManager.getIccById(iccId);
|
||||
}
|
||||
}
|
||||
|
||||
return iccObj;
|
||||
};
|
||||
|
||||
APIS.forEach(function(apiName) {
|
||||
SimSecurity.prototype[apiName] = function(cardIndex) {
|
||||
if (arguments.length < 2) {
|
||||
this.throw(
|
||||
'You are using SimSecurity API wrongly and you may forget' +
|
||||
'to put your cardIndex, please check again');
|
||||
}
|
||||
|
||||
var passedArguments = [].slice.call(arguments, 1);
|
||||
var icc = this._getIccByCardIndex(cardIndex);
|
||||
|
||||
if (!icc) {
|
||||
console.error('We can\'t find needed icc object');
|
||||
return Promise.reject();
|
||||
} else {
|
||||
var func = icc[apiName];
|
||||
var promise = func.apply(icc, passedArguments);
|
||||
return promise.then((result) => {
|
||||
if (EVENT_CHANGE_APIS.indexOf(apiName) > -1) {
|
||||
// we don't have to check passing arguments
|
||||
// because we are under Promise.then()
|
||||
var status = '';
|
||||
var lockType = passedArguments[0].lockType;
|
||||
var enabled = passedArguments[0].enabled;
|
||||
var newPin = passedArguments[0].newPin;
|
||||
|
||||
// If we have newPin, it means that we are changing pin or pin2
|
||||
if (newPin) {
|
||||
status = 'changed';
|
||||
} else {
|
||||
status = enabled ? 'enabled' : 'disabled';
|
||||
}
|
||||
debug('_emitEvent: ' + lockType + '-' + status +
|
||||
' through card ' + cardIndex);
|
||||
this._emitEvent(lockType + '-' + status, cardIndex);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
var simSecurity = new SimSecurity();
|
||||
return simSecurity;
|
||||
});
|
||||
198
gaia/apps/settings/js/modules/state_model.js
Executable file
198
gaia/apps/settings/js/modules/state_model.js
Executable file
|
|
@ -0,0 +1,198 @@
|
|||
/**
|
||||
* StateModel reduces the complexibility of making the UI and a module state
|
||||
* synchronized while keeping the UI responsive at all times. A typical use case
|
||||
* would be bound a toggle to a hardware feature that requires time for turning
|
||||
* on and off.
|
||||
*
|
||||
* StateModel was designed for the following goals:
|
||||
* - Immediate response
|
||||
* The UI is not blocked even the target module can not respond to the user
|
||||
* request immediately.
|
||||
* - Displaying the target state
|
||||
* The final state should always be the last state requested from users unless
|
||||
* the module fails to fufill the request.
|
||||
* - Displaying the real state
|
||||
* If the module fails to be set to the target state or it changes the state
|
||||
* by itself, the final state is the real module state.
|
||||
*
|
||||
* StateModel needs to be created with an object used for performing the
|
||||
* state change operations. The object should define the following three
|
||||
* functinos:
|
||||
* - onInit
|
||||
* It is called only once when the model is being initialized. The function
|
||||
* takes a function as the parameter used for reporting the state changes
|
||||
* from the module. It can be omitted if your module won't change the state
|
||||
* by itself. onInit should also return an initial state immediately.
|
||||
*
|
||||
* - onSetState
|
||||
* It is called with a target state. Do the operation required for switching
|
||||
* to the target state. Always return a promise resolved/rejected with a
|
||||
* state.
|
||||
* If the promise resolves with a state that does not equal to the
|
||||
* latest target state, StateModel will call to this function again with the
|
||||
* target state. If the promise rejects, StateModel simply changes the
|
||||
* target state to the reported state because the module fails to be changed
|
||||
* to the desired state.
|
||||
*
|
||||
* - onGetState
|
||||
* It is called for requesting the current state. Return a promise wrapping
|
||||
* the current state. StateModel fails to be initialized if the returning
|
||||
* promise rejects.
|
||||
*
|
||||
* StateModel provides three observable properties:
|
||||
* - targetState
|
||||
* This is a property representing the user desired state and it gets updated
|
||||
* to the real module state when required. Binding this property to the UI
|
||||
* should be sufficient for most of the use cases.
|
||||
* - currentState
|
||||
* This is a readonly property reporting the latest stable state.
|
||||
* - transitioning
|
||||
* This is a readonly property showing if it is in transition between states.
|
||||
*
|
||||
* @example
|
||||
* var stateModel = StateModel({
|
||||
* _state: null,
|
||||
* onInit: function(stateChange) {
|
||||
* return this._state;
|
||||
* },
|
||||
* onSetState: function(state) {
|
||||
* // A fake time consuming operation
|
||||
* return new Promise((resolve) => {
|
||||
* setTimeout(() => {
|
||||
* this._state = state;
|
||||
* resolve(this._state);
|
||||
* }, 10000);
|
||||
* });
|
||||
* },
|
||||
* onGetState: function() {
|
||||
* return Promise.resolve(this._state);
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* // Do the binding
|
||||
* checkbox.addEventListener('change', function() {
|
||||
* stateModel.targetState = checkbox.checked;
|
||||
* });
|
||||
* stateModel.observe('targetState', function(newState) {
|
||||
* checkbox.checked = newState;
|
||||
* });
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var Module = require('modules/base/module');
|
||||
var Observable = require('modules/mvvm/observable');
|
||||
|
||||
/**
|
||||
* @class StateModel
|
||||
* @param {Function} onInit
|
||||
* @param {Function} onSetState
|
||||
* @param {Function} onGetState
|
||||
* @returns {StateModel}
|
||||
*/
|
||||
var StateModel = Module.create(
|
||||
function StateModel({onInit, onGetState, onSetState}) {
|
||||
if (typeof onInit !== 'function' ||
|
||||
typeof onGetState !== 'function' ||
|
||||
typeof onSetState !== 'function') {
|
||||
this.throw('invalid arguments');
|
||||
}
|
||||
this.super(Observable).call(this);
|
||||
|
||||
var argument = arguments[0];
|
||||
this._init(onInit.bind(argument), onGetState.bind(argument),
|
||||
onSetState.bind(argument));
|
||||
}).extend(Observable);
|
||||
|
||||
/**
|
||||
* An observable property indicating the desired state.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf StateModel.prototype
|
||||
* @type {Object}
|
||||
*/
|
||||
Observable.defineObservableProperty(StateModel.prototype, 'targetState', {
|
||||
value: null
|
||||
});
|
||||
|
||||
/**
|
||||
* An observable property indicating the current state.
|
||||
*
|
||||
* @access public
|
||||
* @readonly
|
||||
* @memberOf StateModel.prototype
|
||||
* @type {Object}
|
||||
*/
|
||||
Observable.defineObservableProperty(StateModel.prototype, 'currentState', {
|
||||
readonly: true,
|
||||
value: null
|
||||
});
|
||||
|
||||
/**
|
||||
* An observable property indicating if it is in transition.
|
||||
*
|
||||
* @access public
|
||||
* @readonly
|
||||
* @memberOf StateModel.prototype
|
||||
* @type {Boolean}
|
||||
*/
|
||||
Observable.defineObservableProperty(StateModel.prototype, 'transitioning', {
|
||||
readonly: true,
|
||||
value: false
|
||||
});
|
||||
|
||||
StateModel.prototype._init = function(onInit, onGetState, onSetState) {
|
||||
this._onInit = onInit;
|
||||
this._onGetState = onGetState;
|
||||
this._onSetState = onSetState;
|
||||
|
||||
this.targetState = this._onInit((realState) => {
|
||||
if (!this._transitioning) {
|
||||
this._currentState = realState;
|
||||
this.targetState = realState;
|
||||
}
|
||||
});
|
||||
this._currentState = this.targetState;
|
||||
|
||||
// Do the initialization
|
||||
this._onGetState().then((realState) => {
|
||||
this._currentState = realState;
|
||||
if (this.targetState !== realState) {
|
||||
// If the current target state is not the real state, do set again.
|
||||
this._setState(this.targetState);
|
||||
}
|
||||
|
||||
this.observe('targetState', (newState) => {
|
||||
if (this._transitioning || newState === this._currentState) {
|
||||
return;
|
||||
}
|
||||
this._setState(newState);
|
||||
});
|
||||
}).catch((err) => {
|
||||
this.error('initialization error ' + err);
|
||||
});
|
||||
};
|
||||
|
||||
StateModel.prototype._setState = function(state) {
|
||||
this.debug('set state: ' + state);
|
||||
this._transitioning = true;
|
||||
return this._onSetState(state).then((realState) => {
|
||||
this.debug('set state completed: ' + state +
|
||||
', real state: ' + realState);
|
||||
this._currentState = realState;
|
||||
if (this.targetState !== realState) {
|
||||
// If the current target state is not the real state, do set again.
|
||||
this._setState(this.targetState);
|
||||
} else {
|
||||
this._transitioning = false;
|
||||
}
|
||||
}, (realState) => {
|
||||
this.debug('set state failed: ' + state + ', real state: ' + realState);
|
||||
this._currentState = realState;
|
||||
this.targetState = realState;
|
||||
this._transitioning = false;
|
||||
});
|
||||
};
|
||||
|
||||
return StateModel;
|
||||
});
|
||||
65
gaia/apps/settings/js/modules/storage_helper.js
Executable file
65
gaia/apps/settings/js/modules/storage_helper.js
Executable file
|
|
@ -0,0 +1,65 @@
|
|||
define(function(require) {
|
||||
'use strict';
|
||||
/**
|
||||
* Helper class for getting available/used storage
|
||||
* required by *_storage.js
|
||||
*/
|
||||
var StorageHelper = {
|
||||
/**
|
||||
* formatting file size strings.
|
||||
*
|
||||
* @private
|
||||
* @param {Number} bytes size in Bytes
|
||||
* @param {Number} digits fixed float digits
|
||||
* @returns {Object}
|
||||
*/
|
||||
_getReadableFileSize: function getReadableFileSize(bytes, digits) {
|
||||
if (bytes === undefined) {
|
||||
return {};
|
||||
}
|
||||
|
||||
var units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
var size, e;
|
||||
if (bytes) {
|
||||
e = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||
size = (bytes / Math.pow(1024, e)).toFixed(digits || 0);
|
||||
} else {
|
||||
e = 0;
|
||||
size = '0';
|
||||
}
|
||||
|
||||
return {
|
||||
size: size,
|
||||
unit: units[e]
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* attach formated size to certain element.
|
||||
*
|
||||
* @public
|
||||
* @param {Object} element html element
|
||||
* @param {String} l10nId l10n string id
|
||||
* @param {Number} sizesize in Bytes
|
||||
*/
|
||||
showFormatedSize: function showFormatedSize(element, l10nId, size) {
|
||||
if (size === undefined || isNaN(size)) {
|
||||
element.textContent = '';
|
||||
return;
|
||||
}
|
||||
|
||||
// KB - 3 KB (nearest ones), MB, GB - 1.29 MB (nearest hundredth)
|
||||
var fixedDigits = (size < 1024 * 1024) ? 0 : 2;
|
||||
var sizeInfo = this._getReadableFileSize(size, fixedDigits);
|
||||
|
||||
document.l10n.formatValue('byteUnit-' + sizeInfo.unit).then(unit => {
|
||||
document.l10n.setAttributes(element, l10nId, {
|
||||
size: sizeInfo.size,
|
||||
unit: unit
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return StorageHelper;
|
||||
});
|
||||
83
gaia/apps/settings/js/modules/sync_manager_bridge.js
Executable file
83
gaia/apps/settings/js/modules/sync_manager_bridge.js
Executable file
|
|
@ -0,0 +1,83 @@
|
|||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var SyncManagerBridge = {
|
||||
_port: null,
|
||||
_requests: new Map(),
|
||||
|
||||
set onsyncchange(callback) {
|
||||
this._onsyncchange = callback;
|
||||
},
|
||||
|
||||
connect: function() {
|
||||
if (this._port) {
|
||||
return Promise.resolve(this._port);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
navigator.mozApps.getSelf().onsuccess = event => {
|
||||
var app = event.target.result;
|
||||
app.connect('gaia-sync-management').then(ports => {
|
||||
if (!ports || !ports.length) {
|
||||
return reject();
|
||||
}
|
||||
this._port = ports[0];
|
||||
this._port.onmessage = this.onmessage.bind(this);
|
||||
resolve(this._port);
|
||||
}).catch(reject);
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
iacRequest: function(request) {
|
||||
return new Promise(resolve => {
|
||||
this.connect().then(port => {
|
||||
if (request.id) {
|
||||
this._requests.set(request.id, resolve);
|
||||
}
|
||||
port.postMessage(request);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
onmessage: function(event) {
|
||||
var message = event.data;
|
||||
if (!message) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.id && this._requests.has(message.id)) {
|
||||
this._requests.get(message.id)(message);
|
||||
this._requests.delete(message.id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.name !== 'onsyncchange') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._onsyncchange) {
|
||||
this._onsyncchange(message);
|
||||
}
|
||||
},
|
||||
|
||||
getInfo: function() {
|
||||
return this.iacRequest({
|
||||
name: 'getInfo',
|
||||
id: Date.now()
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
['enable',
|
||||
'disable',
|
||||
'sync'].forEach(requestName => {
|
||||
SyncManagerBridge[requestName] = () => {
|
||||
return SyncManagerBridge.iacRequest({
|
||||
name: requestName
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
return SyncManagerBridge;
|
||||
});
|
||||
493
gaia/apps/settings/js/modules/wifi_context.js
Executable file
493
gaia/apps/settings/js/modules/wifi_context.js
Executable file
|
|
@ -0,0 +1,493 @@
|
|||
/**
|
||||
* WifiContext is a singleton that you can easily use it to fetch
|
||||
* some shared data across different panels
|
||||
*
|
||||
* @module WifiContext
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
// modules / helpers
|
||||
var SettingsCache = require('modules/settings_cache');
|
||||
var WifiHelper = require('shared/wifi_helper');
|
||||
var wifiManager = WifiHelper.getWifiManager();
|
||||
var settings = navigator.mozSettings;
|
||||
|
||||
// observed objects
|
||||
var _currentNetwork =
|
||||
wifiManager && wifiManager.connection && wifiManager.connection.network;
|
||||
|
||||
var WifiContext = {
|
||||
/**
|
||||
* These listeners would be called when wifi is enabled
|
||||
*
|
||||
* @memberOf WifiContext
|
||||
* @type {Array}
|
||||
*/
|
||||
_wifiEnabledListeners: [],
|
||||
|
||||
/**
|
||||
* These listeners would be called when wifi is disabled
|
||||
*
|
||||
* @memberOf WifiContext
|
||||
* @type {Array}
|
||||
*/
|
||||
_wifiDisabledListeners: [],
|
||||
|
||||
/**
|
||||
* These listeners would be called when wifi is changed
|
||||
*
|
||||
* @memberOf WifiContext
|
||||
* @type {Array}
|
||||
*/
|
||||
_wifiStatusChangeListeners: [],
|
||||
|
||||
/**
|
||||
* These listeners would be called when
|
||||
* 1. localized
|
||||
* 2. wifi is enabled
|
||||
* 3. wifi is changed
|
||||
* 4. wifi is disabled
|
||||
*
|
||||
* @memberOf WifiContext
|
||||
* @type {Array}
|
||||
*/
|
||||
_wifiStatusTextChangeListeners: [],
|
||||
|
||||
/**
|
||||
* These listeners would be called when the current wifi
|
||||
* information is updated
|
||||
*
|
||||
* @memberOf WifiContext
|
||||
* @type {Array}
|
||||
*/
|
||||
_wifiConnectionInfoUpdateListeners: [],
|
||||
|
||||
/**
|
||||
* These listeners would be called when the wifi network
|
||||
* is forgotten.
|
||||
*
|
||||
* @memberOf WifiContext
|
||||
* @type {Array}
|
||||
*/
|
||||
_wifiNetworkForgottenListeners: [],
|
||||
|
||||
/**
|
||||
* These listeners would be called when
|
||||
* 1. wifi connection failed
|
||||
*
|
||||
* @memberOf WifiContext
|
||||
* @type {Array}
|
||||
*/
|
||||
_wifiWrongPasswordListeners: [],
|
||||
|
||||
/**
|
||||
* Desc about customized wifi status
|
||||
*
|
||||
* @memberOf WifiContext
|
||||
* @type {String}
|
||||
*/
|
||||
_wifiStatusText: { id: null },
|
||||
|
||||
/**
|
||||
* Mac address
|
||||
*
|
||||
* @memberOf WifiContext
|
||||
* @type {String}
|
||||
*/
|
||||
_macAddress: '',
|
||||
|
||||
/**
|
||||
* Init is used to initialize some basic stuffs
|
||||
*
|
||||
* @memberOf WifiContext
|
||||
*/
|
||||
_init: function() {
|
||||
if (!wifiManager) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set wifi.enabled so that it mirrors the state of the hardware.
|
||||
// wifi.enabled is not an ordinary user setting because the system
|
||||
// turns it on and off when wifi goes up and down.
|
||||
settings.createLock().set({'wifi.enabled': wifiManager.enabled});
|
||||
|
||||
this._bindEvents();
|
||||
|
||||
// we would call _updateWifi() when init
|
||||
this._updateWifi();
|
||||
},
|
||||
|
||||
/**
|
||||
* We will bind some default listeners here
|
||||
*
|
||||
* @memberOf WifiContext
|
||||
*/
|
||||
_bindEvents: function() {
|
||||
var self = this;
|
||||
|
||||
var _updateWifi = this._updateWifi.bind(this);
|
||||
var _updateNetworkStatus = this._updateNetworkStatus.bind(this);
|
||||
|
||||
// make sure we would update anything when wifi got changed
|
||||
this._wifiEnabledListeners.push(_updateWifi, _updateNetworkStatus);
|
||||
this._wifiDisabledListeners.push(_updateWifi);
|
||||
this._wifiStatusChangeListeners.push(_updateWifi, _updateNetworkStatus);
|
||||
|
||||
// Now register callbacks to track the state of the wifi hardware
|
||||
if (wifiManager) {
|
||||
wifiManager.onenabled = function(event) {
|
||||
self._wifiEnabled(event);
|
||||
self._wifiStatusTextChange();
|
||||
};
|
||||
|
||||
wifiManager.ondisabled = function(event) {
|
||||
self._wifiDisabled(event);
|
||||
self._wifiStatusTextChange();
|
||||
};
|
||||
|
||||
wifiManager.onstatuschange = function(event) {
|
||||
self._wifiStatusChange.call(self, event);
|
||||
self._wifiStatusTextChange();
|
||||
};
|
||||
|
||||
wifiManager.onconnectioninfoupdate =
|
||||
self._wifiConnectionInfoUpdate.bind(self);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* We will update mac address, wifiStatus when wifi is updated
|
||||
*
|
||||
* @memberOf WifiContext
|
||||
*/
|
||||
_updateWifi: function(event) {
|
||||
var self = this;
|
||||
|
||||
if (!wifiManager) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the MAC address is in the Settings database, it's already
|
||||
// displayed in all `MAC address' fields; if not, it will be set as soon
|
||||
// as the Wi-Fi is enabled (see `storeMacAddress').
|
||||
if (!this._macAddress && settings) {
|
||||
SettingsCache.getSettings(function(results) {
|
||||
self._macAddress = results['deviceinfo.mac'];
|
||||
});
|
||||
}
|
||||
|
||||
if (wifiManager.enabled) {
|
||||
this._storeMacAddress();
|
||||
}
|
||||
|
||||
// reflect latest value of wifiStatus
|
||||
this._updateWifiStatusText(event);
|
||||
},
|
||||
|
||||
/**
|
||||
* We will update mac address, wifiStatus when wifi is updated
|
||||
*
|
||||
* @memberOf WifiContext
|
||||
*/
|
||||
_updateWifiStatusText: function(event) {
|
||||
if (wifiManager.enabled) {
|
||||
var network = (event && event.network) ? event.network :
|
||||
wifiManager.connection.network;
|
||||
var status =
|
||||
event && event.status ? event.status : wifiManager.connection.status;
|
||||
|
||||
var networkProp = network ? {ssid: network.ssid} : null;
|
||||
this._wifiStatusText =
|
||||
{ id: 'full-status-' + status,
|
||||
args: networkProp };
|
||||
} else {
|
||||
this._wifiStatusText =
|
||||
{ id: 'disabled' };
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* We need to handle network status when wifi is enabled / wifistatus
|
||||
* is changed.
|
||||
*
|
||||
* _currentNetwork: user activated current network
|
||||
* wifiManager.connection.network: system activated current network
|
||||
*
|
||||
* Main difference between |_currentNetwork| and |wifiManager.connection.
|
||||
* network| is that we can't tell if network is known while connecting by
|
||||
* |wifiManager.connectiohn.network.known| because it will always be true.
|
||||
*
|
||||
* Instead, we use |_currentNetwork| to indicate if current connecting
|
||||
* network is new or known network.
|
||||
*
|
||||
* When system connect to network by user's tap on UI, |_currentNetwork|
|
||||
* will point to same network as |wifiManager.connection.network|.
|
||||
*
|
||||
* On the other hand, if system connect to network automatically under
|
||||
* conditions like wifi enable, move into AP's range, or reconnect to
|
||||
* other network after delete current connected network, |_currentNetwork|
|
||||
* will be null, while |wifiManager.connection.network| contains
|
||||
* information about current network.
|
||||
*
|
||||
* @memberOf WifiContext
|
||||
*/
|
||||
_updateNetworkStatus: function(event) {
|
||||
if (!_currentNetwork) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event && event.network &&
|
||||
_currentNetwork.ssid != event.network.ssid) {
|
||||
return;
|
||||
}
|
||||
|
||||
var networkStatus = event ? event.status : wifiManager.connection.status;
|
||||
if (networkStatus === 'connectingfailed') {
|
||||
// Since this.forgetNetwork will set _currentNetwork to null, we have
|
||||
// to save the _currentNetwork.known first.
|
||||
var isCurrentNetworkKnown = _currentNetwork.known;
|
||||
delete _currentNetwork.password;
|
||||
this.forgetNetwork(_currentNetwork);
|
||||
if (isCurrentNetworkKnown === false) {
|
||||
// Connection fail on user-activated unknown network, should be wrong
|
||||
// password, delete network and force a new authentication dialog.
|
||||
this._wifiWrongPassword();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* When wifi is enabled, we will make sure the mozSettings is sync
|
||||
* with hardward status and call all registered listeners.
|
||||
*
|
||||
* @memberOf WifiContext
|
||||
*/
|
||||
_wifiEnabled: function(event) {
|
||||
var self = this;
|
||||
this._syncWifiEnabled(true, function() {
|
||||
self._wifiEnabledListeners.forEach(function(listener) {
|
||||
listener(event);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* When wifi is disabled, we will make sure the mozSettings is sync
|
||||
* with hardward status and call all registered listeners.
|
||||
*
|
||||
* @memberOf WifiContext
|
||||
*/
|
||||
_wifiDisabled: function(event) {
|
||||
var self = this;
|
||||
this._syncWifiEnabled(false, function() {
|
||||
self._wifiDisabledListeners.forEach(function(listener) {
|
||||
listener(event);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* When wifi's status is changed, we will call all registered listeners.
|
||||
*
|
||||
* @memberOf WifiContext
|
||||
*/
|
||||
_wifiStatusChange: function(event) {
|
||||
this._wifiStatusChangeListeners.forEach(function(listener) {
|
||||
listener(event);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* When text of wifiStatus is changed, we will call all registered
|
||||
* listeners.
|
||||
*
|
||||
* @memberOf WifiContext
|
||||
*/
|
||||
_wifiStatusTextChange: function() {
|
||||
this._wifiStatusTextChangeListeners.forEach(function(listener) {
|
||||
listener();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* When wifi connection fails, we will call all registered
|
||||
* listeners.
|
||||
*
|
||||
* @memberOf WifiContext
|
||||
*/
|
||||
_wifiWrongPassword: function() {
|
||||
this._wifiWrongPasswordListeners.forEach(function(listener) {
|
||||
listener();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* When wifi's connection is updated, we will call all registered listeners.
|
||||
*
|
||||
* @memberOf WifiContext
|
||||
*/
|
||||
_wifiConnectionInfoUpdate: function(event) {
|
||||
this._wifiConnectionInfoUpdateListeners.forEach(function(listener) {
|
||||
listener(event);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* When wifi's network is forgotten, we will call all registered listeners.
|
||||
*
|
||||
* @memberOf WifiContext
|
||||
*/
|
||||
_wifiNetworkForgotten: function(event) {
|
||||
this._wifiNetworkForgottenListeners.forEach(function(listener) {
|
||||
listener(event);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Keep mac address in mozSettings
|
||||
*
|
||||
* @memberOf WifiContext
|
||||
*/
|
||||
_storeMacAddress: function() {
|
||||
if (!wifiManager) {
|
||||
return;
|
||||
}
|
||||
// Store the MAC address in the Settings database. Note: the wifiManager
|
||||
// sets macAddress to the string `undefined' when it is not available.
|
||||
if (settings && wifiManager.macAddress &&
|
||||
wifiManager.macAddress !== this._macAddress &&
|
||||
wifiManager.macAddress !== 'undefined') {
|
||||
this._macAddress = wifiManager.macAddress;
|
||||
settings.createLock().set({ 'deviceinfo.mac': this._macAddress });
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Keep the setting in sync with the hardware state. We need to do this
|
||||
* because b2g/dom/wifi/WifiWorker.js can turn the hardware on and off.
|
||||
*
|
||||
* @memberOf WifiContext
|
||||
* @param {Boolean} enabled
|
||||
* @param {Function} callback
|
||||
*/
|
||||
_syncWifiEnabled: function(enabled, callback) {
|
||||
SettingsCache.getSettings(function(results) {
|
||||
var wifiEnabled = results['wifi.enabled'];
|
||||
if (wifiEnabled !== enabled) {
|
||||
settings.createLock().set({'wifi.enabled': enabled});
|
||||
}
|
||||
callback();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* This is an internal function that can help us find out the matched
|
||||
* callback from catched listeners and remove it
|
||||
*
|
||||
* @memberOf WifiContext
|
||||
* @param {Array} listeners
|
||||
* @param {Function} callback
|
||||
*/
|
||||
_removeEventListener: function(listeners, callback) {
|
||||
var index = listeners.indexOf(callback);
|
||||
if (index >= 0) {
|
||||
listeners.splice(index, 1);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* This is a wrapper of WifiManger.associate to make sure we can handle
|
||||
* our internal status at the same time.
|
||||
*
|
||||
* @memberOf WifiContext
|
||||
* @param {Object} network
|
||||
* @param {Function} callback
|
||||
*/
|
||||
associateNetwork: function(network, cb) {
|
||||
cb = cb || function() {};
|
||||
var request = wifiManager.associate(network);
|
||||
var done = function() {
|
||||
if (!request.error) {
|
||||
_currentNetwork = network;
|
||||
}
|
||||
cb(request.error);
|
||||
};
|
||||
request.onsuccess = done;
|
||||
request.onerror = done;
|
||||
},
|
||||
/**
|
||||
* This is a wrapper of WifiManger.forget to make sure we can handle
|
||||
* our internal status at the same time.
|
||||
*
|
||||
* @memberOf WifiContext
|
||||
* @param {Object} network
|
||||
* @param {Function} callback
|
||||
*/
|
||||
forgetNetwork: function(network, cb) {
|
||||
cb = cb || function() {};
|
||||
var request = wifiManager.forget(network);
|
||||
var done = function() {
|
||||
if (!request.error) {
|
||||
_currentNetwork = null;
|
||||
WifiContext._wifiNetworkForgotten({ network: network });
|
||||
}
|
||||
cb(request.error);
|
||||
};
|
||||
request.onsuccess = done;
|
||||
request.onerror = done;
|
||||
}
|
||||
};
|
||||
|
||||
WifiContext._init();
|
||||
|
||||
return {
|
||||
addEventListener: function(eventName, callback) {
|
||||
if (eventName === 'wifiEnabled') {
|
||||
WifiContext._wifiEnabledListeners.push(callback);
|
||||
} else if (eventName === 'wifiDisabled') {
|
||||
WifiContext._wifiDisabledListeners.push(callback);
|
||||
} else if (eventName === 'wifiStatusChange') {
|
||||
WifiContext._wifiStatusChangeListeners.push(callback);
|
||||
} else if (eventName === 'wifiStatusTextChange') {
|
||||
WifiContext._wifiStatusTextChangeListeners.push(callback);
|
||||
} else if (eventName === 'wifiWrongPassword') {
|
||||
WifiContext._wifiWrongPasswordListeners.push(callback);
|
||||
} else if (eventName === 'wifiConnectionInfoUpdate') {
|
||||
WifiContext._wifiConnectionInfoUpdateListeners.push(callback);
|
||||
} else if (eventName === 'wifiNetworkForgotten') {
|
||||
WifiContext._wifiNetworkForgottenListeners.push(callback);
|
||||
}
|
||||
},
|
||||
removeEventListener: function(eventName, callback) {
|
||||
if (eventName === 'wifiEnabled') {
|
||||
WifiContext._removeEventListener(
|
||||
WifiContext._wifiEnabledListeners, callback);
|
||||
} else if (eventName === 'wifiDisabled') {
|
||||
WifiContext._removeEventListener(
|
||||
WifiContext._wifiDisabledListeners, callback);
|
||||
} else if (eventName === 'wifiStatusChange') {
|
||||
WifiContext._removeEventListener(
|
||||
WifiContext._wifiStatusChangeListeners, callback);
|
||||
} else if (eventName === 'wifiStatusTextChange') {
|
||||
WifiContext._removeEventListener(
|
||||
WifiContext._wifiStatusTextChangeListeners, callback);
|
||||
} else if (eventName === 'wifiWrongPassword') {
|
||||
WifiContext._removeEventListener(
|
||||
WifiContext._wifiWrongPasswordListeners, callback);
|
||||
} else if (eventName === 'wifiConnectionInfoUpdate') {
|
||||
WifiContext._removeEventListener(
|
||||
WifiContext._wifiConnectionInfoUpdateListeners, callback);
|
||||
} else if (eventName === 'wifiNetworkForgotten') {
|
||||
WifiContext._removeEventListener(
|
||||
WifiContext._wifiNetworkForgottenListeners, callback);
|
||||
}
|
||||
},
|
||||
get wifiStatusText() {
|
||||
return WifiContext._wifiStatusText;
|
||||
},
|
||||
get currentNetwork() {
|
||||
return _currentNetwork;
|
||||
},
|
||||
forgetNetwork: WifiContext.forgetNetwork,
|
||||
associateNetwork: WifiContext.associateNetwork
|
||||
};
|
||||
});
|
||||
345
gaia/apps/settings/js/modules/wifi_utils.js
Executable file
345
gaia/apps/settings/js/modules/wifi_utils.js
Executable file
|
|
@ -0,0 +1,345 @@
|
|||
/**
|
||||
* WifiUtils is a utils-box that keeps wifi-operations needed utils.
|
||||
*
|
||||
* @module WifiUtils
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var WifiHelper = require('shared/wifi_helper');
|
||||
var wifiManager = WifiHelper.getWifiManager();
|
||||
|
||||
var WifiUtils = {
|
||||
/**
|
||||
* Create an explanatory list item
|
||||
*
|
||||
* @memberOf WifiUtils
|
||||
* @param {String} message
|
||||
* @returns {HTMLLIElement}
|
||||
*/
|
||||
newExplanationItem: function(message) {
|
||||
var li = document.createElement('li');
|
||||
li.className = 'explanation';
|
||||
li.setAttribute('data-l10n-id', message);
|
||||
return li;
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a network list item
|
||||
*
|
||||
* @memberOf WifiUtils
|
||||
* @param {Object} options
|
||||
* @returns {HTMLLIElement}
|
||||
*/
|
||||
newListItem: function(options) {
|
||||
/**
|
||||
* A Wi-Fi list item has the following HTML structure:
|
||||
* <li>
|
||||
* <aside class="pack-end wifi-icon level-[?] [secured]"></aside>
|
||||
* <a>
|
||||
* <span> Network SSID </span>
|
||||
* <small> Network Security </small>
|
||||
* </a>
|
||||
* </li>
|
||||
*/
|
||||
var network = options.network;
|
||||
var showNotInRange = options.showNotInRange || false;
|
||||
var onClick = options.onClick || function() {};
|
||||
|
||||
// icon
|
||||
var icon = document.createElement('aside');
|
||||
icon.classList.add('pack-end');
|
||||
icon.classList.add('wifi-icon');
|
||||
var level = Math.min(Math.floor(network.relSignalStrength / 20), 4);
|
||||
icon.classList.add('level-' + level);
|
||||
|
||||
// ssid
|
||||
var ssid = document.createElement('span');
|
||||
ssid.dir = 'auto';
|
||||
ssid.textContent = network.ssid;
|
||||
|
||||
// supported authentication methods
|
||||
var small = document.createElement('small');
|
||||
var keys = WifiHelper.getSecurity(network);
|
||||
var networkNotInRange = (network.known && level === 0);
|
||||
var hasSecurity = (keys && keys.length);
|
||||
|
||||
if (hasSecurity) {
|
||||
if (showNotInRange && networkNotInRange) {
|
||||
small.setAttribute('data-l10n-id', 'notInRange');
|
||||
} else {
|
||||
document.l10n.setAttributes(small, 'securedBy',
|
||||
{ capabilities: keys.join(', ') });
|
||||
}
|
||||
icon.classList.add('secured');
|
||||
} else {
|
||||
if (showNotInRange && networkNotInRange) {
|
||||
small.setAttribute('data-l10n-id', 'notInRange');
|
||||
} else {
|
||||
small.setAttribute('data-l10n-id', 'securityOpen');
|
||||
}
|
||||
}
|
||||
|
||||
var a = document.createElement('a');
|
||||
a.appendChild(ssid);
|
||||
a.appendChild(small);
|
||||
|
||||
// create list item
|
||||
var li = document.createElement('li');
|
||||
li.dataset.ssid = network.ssid;
|
||||
li.appendChild(icon);
|
||||
li.appendChild(a);
|
||||
|
||||
// Show connection status
|
||||
icon.classList.add('wifi-signal');
|
||||
|
||||
var networkStatus = WifiHelper.getNetworkStatus(network);
|
||||
var kActiveStatuses = ['associated', 'connecting', 'connected'];
|
||||
|
||||
if (-1 !== kActiveStatuses.indexOf(networkStatus)) {
|
||||
li.classList.add('active');
|
||||
|
||||
if ('connected' === networkStatus) {
|
||||
icon.classList.add('connected');
|
||||
} else {
|
||||
icon.classList.add('connecting');
|
||||
}
|
||||
|
||||
small.setAttribute('data-l10n-id', 'short-status-' + networkStatus);
|
||||
}
|
||||
|
||||
// bind connection callback
|
||||
li.onclick = function() {
|
||||
onClick(network);
|
||||
};
|
||||
return li;
|
||||
},
|
||||
|
||||
/**
|
||||
* Change dialog layout based on dialogId and security
|
||||
*
|
||||
* @memberOf WifiUtils
|
||||
* @param {HTMLElement} panel
|
||||
* @param {String} security
|
||||
*/
|
||||
changeDisplay: function(panel, security) {
|
||||
var eap = panel.querySelector('li.eap select');
|
||||
var identity = panel.querySelector('input[name=identity]');
|
||||
var password = panel.querySelector('input[name=password]');
|
||||
var authPhase2 = panel.querySelector('li.auth-phase2 select');
|
||||
var certificate = panel.querySelector('li.server-certificate select');
|
||||
var description =
|
||||
panel.querySelector('li.server-certificate-description');
|
||||
|
||||
if (security === 'WEP' || security === 'WPA-PSK') {
|
||||
identity.parentNode.style.display = 'none';
|
||||
password.parentNode.style.display = 'block';
|
||||
authPhase2.parentNode.parentNode.style.display = 'none';
|
||||
certificate.parentNode.parentNode.style.display = 'none';
|
||||
description.style.display = 'none';
|
||||
} else if (security === 'WPA-EAP') {
|
||||
if (eap) {
|
||||
switch (eap.value) {
|
||||
case 'SIM':
|
||||
identity.parentNode.style.display = 'none';
|
||||
password.parentNode.style.display = 'none';
|
||||
authPhase2.parentNode.parentNode.style.display = 'none';
|
||||
certificate.parentNode.parentNode.style.display = 'none';
|
||||
description.style.display = 'none';
|
||||
break;
|
||||
case 'PEAP':
|
||||
case 'TLS':
|
||||
case 'TTLS':
|
||||
identity.parentNode.style.display = 'block';
|
||||
password.parentNode.style.display = 'block';
|
||||
authPhase2.parentNode.parentNode.style.display = 'block';
|
||||
certificate.parentNode.parentNode.style.display = 'block';
|
||||
description.style.display = 'block';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
identity.parentNode.style.display = 'none';
|
||||
password.parentNode.style.display = 'none';
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* This is used to help us do some initialization works if the panel
|
||||
* is auth related.
|
||||
*
|
||||
* @memberOf WifiUtils
|
||||
* @param {String} panel
|
||||
* @param {Object} network
|
||||
*/
|
||||
initializeAuthFields: function(panel, network) {
|
||||
var ssid = panel.querySelector('input[name=ssid]');
|
||||
var identity = panel.querySelector('input[name=identity]');
|
||||
var password = panel.querySelector('input[name=password]');
|
||||
var showPassword = panel.querySelector('gaia-checkbox[name=show-pwd]');
|
||||
var eap = panel.querySelector('li.eap select');
|
||||
var certificate = panel.querySelector('li.server-certificate select');
|
||||
var submitButton = panel.querySelector('button[type=submit]');
|
||||
|
||||
// load needed certificates first
|
||||
this.loadImportedCertificateOptions(certificate);
|
||||
|
||||
identity.value = network.identity || '';
|
||||
|
||||
password.type = 'password';
|
||||
password.value = network.password || '';
|
||||
|
||||
showPassword.checked = false;
|
||||
showPassword.onchange = function() {
|
||||
password.type = this.checked ? 'text' : 'password';
|
||||
};
|
||||
|
||||
var checkPassword = function() {
|
||||
var key = WifiHelper.getKeyManagement(network);
|
||||
var isDisabled = !WifiHelper.isValidInput(
|
||||
key, password.value, identity.value, eap.value);
|
||||
if (ssid) {
|
||||
isDisabled = isDisabled || !WifiHelper.isSSIDValid(ssid.value);
|
||||
}
|
||||
submitButton.disabled = isDisabled;
|
||||
};
|
||||
|
||||
eap.onchange = function() {
|
||||
var key = WifiHelper.getKeyManagement(network);
|
||||
checkPassword();
|
||||
WifiUtils.changeDisplay(panel, key);
|
||||
};
|
||||
|
||||
if (ssid) {
|
||||
ssid.oninput = checkPassword;
|
||||
}
|
||||
|
||||
password.oninput = checkPassword;
|
||||
identity.oninput = checkPassword;
|
||||
checkPassword();
|
||||
},
|
||||
|
||||
/**
|
||||
* This is an inner function that used to inject certificates options
|
||||
* into select element.
|
||||
*
|
||||
* @memberOf WifiUtils
|
||||
* @param {HTMLSelectElement} select
|
||||
*/
|
||||
loadImportedCertificateOptions: function(select) {
|
||||
if (!wifiManager.getImportedCerts) {
|
||||
return;
|
||||
}
|
||||
|
||||
var certRequest = wifiManager.getImportedCerts();
|
||||
|
||||
certRequest.onsuccess = function() {
|
||||
var i;
|
||||
var certList = certRequest.result;
|
||||
// save the imported server certificates
|
||||
var certificateList = certList.ServerCert;
|
||||
|
||||
// reset the option to be <option value="none">--</option> only
|
||||
var originLengthOfOptions = select.options.length;
|
||||
for (i = 0; i < originLengthOfOptions - 1; i++) {
|
||||
select.remove(1);
|
||||
}
|
||||
|
||||
for (i = 0; i < certificateList.length; i++) {
|
||||
var option = document.createElement('option');
|
||||
option.text = certificateList[i];
|
||||
option.value = certificateList[i];
|
||||
select.add(option, null);
|
||||
}
|
||||
};
|
||||
|
||||
certRequest.onerror = function() {
|
||||
console.warn('getImportedCerts failed');
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the icon of the given network
|
||||
*
|
||||
* @memberOf WifiUtils
|
||||
* @param {Object} network
|
||||
* @param {Integer} networkSignal
|
||||
*/
|
||||
updateNetworkSignal: function(network, networkSignal) {
|
||||
var li = document.querySelector('li[data-ssid="' + network.ssid + '"]');
|
||||
var icon = li.querySelector('aside');
|
||||
// Clean previous state
|
||||
icon.className = icon.className.replace(/level-\w*/, '');
|
||||
var level = Math.min(Math.floor(networkSignal / 20), 4);
|
||||
icon.classList.add('level-' + level);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get concated networkKey which can be used as identifier
|
||||
*
|
||||
* @memberOf WifiUtils
|
||||
* @param {Object} network
|
||||
* @return {String} concated network identifier
|
||||
*/
|
||||
getNetworkKey: function(network) {
|
||||
if (!network) {
|
||||
return '';
|
||||
} else {
|
||||
var key =
|
||||
network.ssid + '+' + WifiHelper.getSecurity(network).join('+');
|
||||
return key;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Reflect incoming network status on related listItem (show different UI)
|
||||
*
|
||||
* @memberOf WifiUtils
|
||||
* @param {Object} options
|
||||
* @param {Object} options.listItems - listItems with DOM elements
|
||||
* @param {Object} options.activeItemDOM - DOM element for active item
|
||||
* @param {Object} options.network - network object
|
||||
* @param {Object} options.networkStatus - current status for network
|
||||
*/
|
||||
updateListItemStatus: function(options) {
|
||||
options = options || {};
|
||||
var listItems = options.listItems;
|
||||
var activeItemDOM = options.activeItemDOM;
|
||||
var network = options.network;
|
||||
var networkStatus = options.networkStatus;
|
||||
|
||||
if (!network || !networkStatus || !listItems) {
|
||||
console.log('Please check passing options for updateListItemStatus');
|
||||
return;
|
||||
}
|
||||
|
||||
var key = this.getNetworkKey(network);
|
||||
var listItemDOM = listItems[key];
|
||||
|
||||
if (activeItemDOM && activeItemDOM != listItemDOM) {
|
||||
activeItemDOM.classList.remove('active');
|
||||
activeItemDOM.querySelector('small').
|
||||
setAttribute('data-l10n-id', 'short-status-disconnected');
|
||||
activeItemDOM.querySelector('aside').classList.remove('connecting');
|
||||
activeItemDOM.querySelector('aside').classList.remove('connected');
|
||||
}
|
||||
|
||||
if (listItemDOM) {
|
||||
listItemDOM.classList.add('active');
|
||||
listItemDOM.querySelector('small').
|
||||
setAttribute('data-l10n-id', 'short-status-' + networkStatus);
|
||||
if (networkStatus === 'connecting' || networkStatus === 'associated') {
|
||||
listItemDOM.querySelector('aside').classList.remove('connected');
|
||||
listItemDOM.querySelector('aside').classList.add('connecting');
|
||||
} else if (networkStatus === 'connected') {
|
||||
listItemDOM.querySelector('aside').classList.remove('connecting');
|
||||
listItemDOM.querySelector('aside').classList.add('connected');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return WifiUtils;
|
||||
});
|
||||
80
gaia/apps/settings/js/panels/about/factory_reset.js
Executable file
80
gaia/apps/settings/js/panels/about/factory_reset.js
Executable file
|
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
* Handle factory reset functionality
|
||||
*
|
||||
* @module about/FactoryReset
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @alias module:about/FactoryReset
|
||||
* @class FactoryReset
|
||||
* @returns {FactoryReset}
|
||||
*/
|
||||
var FactoryReset = function() {
|
||||
this._elements = null;
|
||||
};
|
||||
|
||||
FactoryReset.prototype = {
|
||||
/**
|
||||
* initialization
|
||||
*
|
||||
* @access public
|
||||
* @memberOf FactoryReset.prototype
|
||||
* @param {HTMLElement} elements
|
||||
*/
|
||||
init: function fr_init(elements) {
|
||||
this._elements = elements;
|
||||
if (navigator.mozPower) {
|
||||
this._elements.resetButton.disabled = false;
|
||||
this._elements.resetButton.addEventListener('click',
|
||||
this._resetClick.bind(this));
|
||||
} else {
|
||||
// disable button if mozPower is undefined or can't be used
|
||||
this._elements.resetButton.disabled = true;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* click handler.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf FactoryReset.prototype
|
||||
*/
|
||||
_resetClick: function fr__resetClick() {
|
||||
this._elements.resetDialog.hidden = false;
|
||||
this._elements.resetCancel.onclick = function() {
|
||||
this._elements.resetDialog.hidden = true;
|
||||
}.bind(this);
|
||||
this._elements.resetConfirm.onclick = function() {
|
||||
this._factoryReset();
|
||||
this._elements.resetDialog.hidden = true;
|
||||
}.bind(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* call mozPower API to reset device
|
||||
*
|
||||
* @access private
|
||||
* @memberOf FactoryReset.prototype
|
||||
*/
|
||||
_factoryReset: function fr__factoryReset() {
|
||||
var power = navigator.mozPower;
|
||||
if (!power) {
|
||||
console.error('Cannot get mozPower');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!power.factoryReset) {
|
||||
console.error('Cannot invoke mozPower.factoryReset()');
|
||||
return;
|
||||
}
|
||||
|
||||
power.factoryReset();
|
||||
}
|
||||
};
|
||||
|
||||
return function ctor_factoryReset() {
|
||||
return new FactoryReset();
|
||||
};
|
||||
});
|
||||
121
gaia/apps/settings/js/panels/about/hardware_info.js
Executable file
121
gaia/apps/settings/js/panels/about/hardware_info.js
Executable file
|
|
@ -0,0 +1,121 @@
|
|||
/**
|
||||
* Handle HardwareInfo related functionality
|
||||
*
|
||||
* @module about/HardwareInfo
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @alias module:about/HardwareInfo
|
||||
* @class HardwareInfo
|
||||
* @returns {HardwareInfo}
|
||||
*/
|
||||
var HardwareInfo = function() {
|
||||
this._elements = null;
|
||||
};
|
||||
|
||||
HardwareInfo.prototype = {
|
||||
/**
|
||||
* initialization.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf HardwareInfo.prototype
|
||||
* @param {HTMLElement} elements
|
||||
*/
|
||||
init: function hi_init(elements) {
|
||||
this._elements = elements;
|
||||
|
||||
this._loadHardwareInfo();
|
||||
},
|
||||
|
||||
/**
|
||||
* Load hardware related informations.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf HardwareInfo.prototype
|
||||
*/
|
||||
_loadHardwareInfo: function hi__loadHardwareInfo() {
|
||||
var _conns = navigator.mozMobileConnections;
|
||||
if (!_conns) {
|
||||
this._elements.deviceInfoPhoneNum.hidden = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var _isMultiSim = _conns.length > 1;
|
||||
// Only show the list item when there are valid iccinfos.
|
||||
var _hideListItem = true;
|
||||
|
||||
// update msisdns
|
||||
while (this._elements.deviceInfoMsisdns.hasChildNodes()) {
|
||||
this._elements.deviceInfoMsisdns.removeChild(
|
||||
this._elements.deviceInfoMsisdns.lastChild);
|
||||
}
|
||||
|
||||
Array.prototype.forEach.call(_conns, function(conn, index) {
|
||||
var iccId = conn.iccId;
|
||||
if (!iccId) {
|
||||
return;
|
||||
}
|
||||
|
||||
var iccObj = navigator.mozIccManager.getIccById(iccId);
|
||||
if (!iccObj) {
|
||||
return;
|
||||
}
|
||||
|
||||
var iccInfo = iccObj.iccInfo;
|
||||
if (!iccInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
_hideListItem = false;
|
||||
var span = this._renderPhoneNumberElement(iccInfo, index, _isMultiSim);
|
||||
this._elements.deviceInfoMsisdns.appendChild(span);
|
||||
}.bind(this));
|
||||
|
||||
this._elements.deviceInfoPhoneNum.hidden = _hideListItem;
|
||||
},
|
||||
|
||||
/**
|
||||
* render phone number element based on SIM card info.
|
||||
*
|
||||
* If the icc card is gsm card, the phone number is in msisdn.
|
||||
* Otherwise, the phone number is in mdn.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf HardwareInfo.prototype
|
||||
* @param {Object} iccInfo iccInfo data
|
||||
* @param {Number} index index number
|
||||
* @param {Boolean} isMultiSim has multiple SIM
|
||||
* @return {HTMLElement} span element with number info
|
||||
*/
|
||||
_renderPhoneNumberElement: function hi__renderPhoneNumberElement(
|
||||
iccInfo, index, isMultiSim) {
|
||||
var span = document.createElement('span');
|
||||
var msisdn = iccInfo.msisdn || iccInfo.mdn;
|
||||
if (msisdn) {
|
||||
if (isMultiSim) {
|
||||
document.l10n.setAttributes(span,
|
||||
'deviceInfo-MSISDN-with-index', {
|
||||
index: index + 1,
|
||||
msisdn: msisdn
|
||||
});
|
||||
} else {
|
||||
span.textContent = msisdn;
|
||||
}
|
||||
} else {
|
||||
if (isMultiSim) {
|
||||
document.l10n.setAttributes(span,
|
||||
'unknown-phoneNumber-sim', { index: index + 1 });
|
||||
} else {
|
||||
span.setAttribute('data-l10n-id', 'unknown-phoneNumber');
|
||||
}
|
||||
}
|
||||
return span;
|
||||
}
|
||||
};
|
||||
|
||||
return function ctor_hardwareInfo() {
|
||||
return new HardwareInfo();
|
||||
};
|
||||
});
|
||||
41
gaia/apps/settings/js/panels/about/panel.js
Executable file
41
gaia/apps/settings/js/panels/about/panel.js
Executable file
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* Used to show Device/Information panel
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var SettingsPanel = require('modules/settings_panel');
|
||||
var HardwareInfo = require('panels/about/hardware_info');
|
||||
var UpdateCheck = require('panels/about/update_check');
|
||||
var FactoryReset = require('panels/about/factory_reset');
|
||||
|
||||
return function ctor_support_panel() {
|
||||
var hardwareInfo = HardwareInfo();
|
||||
var factoryReset = FactoryReset();
|
||||
var updateCheck = UpdateCheck();
|
||||
|
||||
return SettingsPanel({
|
||||
onInit: function(panel) {
|
||||
hardwareInfo.init({
|
||||
deviceInfoPhoneNum: panel.querySelector('.deviceinfo-phone-num'),
|
||||
deviceInfoMsisdns: panel.querySelector('.deviceInfo-msisdns')
|
||||
});
|
||||
|
||||
updateCheck.init({
|
||||
checkUpdateNow: panel.querySelector('.check-update-now'),
|
||||
lastUpdateDate: panel.querySelector('.last-update-date'),
|
||||
updateStatus: panel.querySelector('.update-status'),
|
||||
systemStatus: panel.querySelector('.system-update-status'),
|
||||
addonUpdateNotify: panel.querySelector('.addon-update-notify')
|
||||
});
|
||||
|
||||
factoryReset.init({
|
||||
resetButton: panel.querySelector('.reset-phone'),
|
||||
resetDialog: panel.querySelector('.reset-phone-dialog'),
|
||||
resetConfirm: panel.querySelector('.confirm-reset-phone'),
|
||||
resetCancel: panel.querySelector('.cancel-reset-phone')
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
246
gaia/apps/settings/js/panels/about/update_check.js
Executable file
246
gaia/apps/settings/js/panels/about/update_check.js
Executable file
|
|
@ -0,0 +1,246 @@
|
|||
/**
|
||||
* Handle Update check related functionality
|
||||
*
|
||||
* @module about/UpdateCheck
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @alias module:about/UpdateCheck
|
||||
* @class UpdateCheck
|
||||
* @returns {UpdateCheck}
|
||||
*/
|
||||
var UpdateCheck = function() {
|
||||
this._elements = null;
|
||||
this._settings = window.navigator.mozSettings;
|
||||
this._checkStatus = {
|
||||
'gecko.updateStatus': {},
|
||||
'apps.updateStatus': {}
|
||||
};
|
||||
};
|
||||
|
||||
UpdateCheck.prototype = {
|
||||
/**
|
||||
* initialization.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf UpdateCheck.prototype
|
||||
* @param {HTMLElement} elements
|
||||
*/
|
||||
init: function uc_init(elements) {
|
||||
this._elements = elements;
|
||||
|
||||
this._loadLastUpdated();
|
||||
|
||||
this._displayNotifyCheckbox();
|
||||
|
||||
this._elements.checkUpdateNow.addEventListener('click',
|
||||
this._checkForUpdates.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows and hides the "notify me" checkbox depending on whether auto
|
||||
* updates are enabled.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf UpdateCheck.prototype
|
||||
*/
|
||||
_displayNotifyCheckbox: function uc__displayNotifyCheckBox() {
|
||||
var key = 'addons.auto_update';
|
||||
var request = this._settings.createLock().get(key);
|
||||
|
||||
request.onsuccess = () => {
|
||||
this._elements.addonUpdateNotify
|
||||
.classList.toggle('hidden', !request.result[key]);
|
||||
|
||||
this._settings.addObserver(key, e => {
|
||||
this._elements.addonUpdateNotify
|
||||
.classList.toggle('hidden', !e.settingValue);
|
||||
});
|
||||
};
|
||||
|
||||
request.onerror = err => {
|
||||
console.error('Failed to fetch ', key, err);
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Show last update date.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf UpdateCheck.prototype
|
||||
*/
|
||||
_loadLastUpdated: function uc__loadLastUpdated() {
|
||||
var key = 'deviceinfo.last_updated';
|
||||
var request = this._settings.createLock().get(key);
|
||||
|
||||
request.onsuccess = function() {
|
||||
var lastUpdated = request.result[key];
|
||||
if (!lastUpdated) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._elements.lastUpdateDate.textContent =
|
||||
new Date(lastUpdated).toLocaleString(navigator.languages, {
|
||||
hour12: navigator.mozHour12,
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
day: 'numeric',
|
||||
month: 'numeric',
|
||||
year: 'numeric'
|
||||
});
|
||||
}.bind(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* update result based on return states
|
||||
*
|
||||
* @access private
|
||||
* @memberOf UpdateCheck.prototype
|
||||
*/
|
||||
_statusCompleteUpdater: function uc__statusCompleteUpdater() {
|
||||
var hasAllCheckComplete =
|
||||
Object.keys(this._checkStatus).some((setting) =>
|
||||
this._checkStatus[setting].value === 'check-complete'
|
||||
);
|
||||
|
||||
var hasAllResponses =
|
||||
Object.keys(this._checkStatus).every((setting) =>
|
||||
!!this._checkStatus[setting].value
|
||||
);
|
||||
|
||||
if (hasAllCheckComplete) {
|
||||
this._startClearUpdateStatus();
|
||||
}
|
||||
|
||||
// On no-updates we should also remove the checking class.
|
||||
var hasNoUpdatesResult =
|
||||
Object.keys(this._checkStatus).some((setting) =>
|
||||
this._checkStatus[setting].value === 'no-updates'
|
||||
);
|
||||
|
||||
if (hasAllResponses || hasNoUpdatesResult) {
|
||||
this._elements.updateStatus.classList.remove('checking');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* handler for update status.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf UpdateCheck.prototype
|
||||
* @param {String} setting gecko or app setting
|
||||
* @param {Object} event event contains SettingValue
|
||||
*/
|
||||
_onUpdateStatus: function uc__onUpdateStatus(setting, event) {
|
||||
var value = event.settingValue;
|
||||
this._checkStatus[setting].value = value;
|
||||
|
||||
/**
|
||||
* possible return values:
|
||||
*
|
||||
* - for system updates:
|
||||
* - no-updates
|
||||
* - already-latest-version
|
||||
* - check-complete
|
||||
* - retry-when-online
|
||||
* - check-error-$nsresult
|
||||
* - check-error-http-$code
|
||||
*
|
||||
* - for apps updates:
|
||||
* - check-complete
|
||||
*
|
||||
* use
|
||||
* http://mxr.mozilla.org/mozilla-central/ident?i=setUpdateStatus&tree=mozilla-central&filter=&strict=1
|
||||
* to check if this is still current
|
||||
*/
|
||||
|
||||
var l10nValues = [
|
||||
'no-updates', 'already-latest-version', 'retry-when-online'];
|
||||
|
||||
if (value !== 'check-complete') {
|
||||
var id = l10nValues.indexOf(value) !== -1 ? value : 'check-error';
|
||||
this._elements.systemStatus.setAttribute('data-l10n-id', id);
|
||||
if (id === 'check-error') {
|
||||
console.error('Error checking for system update:', value);
|
||||
}
|
||||
}
|
||||
|
||||
this._statusCompleteUpdater();
|
||||
|
||||
this._settings.removeObserver(setting, this._checkStatus[setting].cb);
|
||||
this._checkStatus[setting].cb = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Timer to keep track of displayed update status
|
||||
*
|
||||
* @access private
|
||||
* @memberOf UpdateCheck.prototype
|
||||
*/
|
||||
_clearUpdateStatusTimer: null,
|
||||
|
||||
/**
|
||||
* Start timer to hide the update status, ensuring it is displayed
|
||||
* for a certain period of time.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf UpdateCheck.prototype
|
||||
*/
|
||||
_startClearUpdateStatus: function uc__clearUpdateStatus() {
|
||||
if (this._clearUpdateStatusTimer) {
|
||||
clearTimeout(this._clearUpdateStatusTimer);
|
||||
}
|
||||
this._clearUpdateStatusTimer =
|
||||
window.setTimeout(this._doClearUpdateStatus.bind(this), 5000);
|
||||
},
|
||||
|
||||
/**
|
||||
* Actually hide the update status
|
||||
*
|
||||
* @access private
|
||||
* @memberOf UpdateCheck.prototype
|
||||
*/
|
||||
_doClearUpdateStatus: function uc__clearUpdateStatus() {
|
||||
this._elements.updateStatus.classList.remove('visible');
|
||||
this._elements.systemStatus.textContent = '';
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if there's any update.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf UpdateCheck.prototype
|
||||
*/
|
||||
_checkForUpdates: function uc__checkForUpdates() {
|
||||
if (!navigator.onLine) {
|
||||
this._elements.checkUpdateNow.setAttribute('disabled', 'disabled');
|
||||
document.l10n.formatValue('no-network-when-update').then(msg => {
|
||||
alert(msg);
|
||||
this._elements.checkUpdateNow.removeAttribute('disabled');
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this._elements.updateStatus.classList.add('checking', 'visible');
|
||||
|
||||
/* remove whatever was there before */
|
||||
this._elements.systemStatus.textContent = '';
|
||||
|
||||
for (var setting in this._checkStatus) {
|
||||
this._checkStatus[setting].cb =
|
||||
this._onUpdateStatus.bind(this, setting);
|
||||
this._settings.addObserver(setting, this._checkStatus[setting].cb);
|
||||
}
|
||||
|
||||
this._settings.createLock().set({
|
||||
'gaia.system.checkForUpdates': true
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return function ctor_updateCheck() {
|
||||
return new UpdateCheck();
|
||||
};
|
||||
});
|
||||
92
gaia/apps/settings/js/panels/about_more_info/commit_info.js
Executable file
92
gaia/apps/settings/js/panels/about_more_info/commit_info.js
Executable file
|
|
@ -0,0 +1,92 @@
|
|||
/**
|
||||
* Show software informations
|
||||
*
|
||||
* @module abou_more_info/commitInfo
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @alias module:abou_more_info/commitInfo
|
||||
* @class commitInfo
|
||||
* @returns {commitInfo}
|
||||
*/
|
||||
var commitInfo = function() {
|
||||
this._elements = {};
|
||||
};
|
||||
|
||||
commitInfo.prototype = {
|
||||
/**
|
||||
* initialization.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf commitInfo.prototype
|
||||
* @param {HTMLElement} elements
|
||||
*/
|
||||
init: function mi_init(elements) {
|
||||
this._elements = elements;
|
||||
|
||||
this._loadGaiaCommit();
|
||||
},
|
||||
|
||||
/**
|
||||
* convert date to UTC format.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf commitInfo.prototype
|
||||
*/
|
||||
_dateToUTC: function mi__dateToUTC(d) {
|
||||
var arr = [];
|
||||
[
|
||||
d.getUTCFullYear(), (d.getUTCMonth() + 1), d.getUTCDate(),
|
||||
d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds()
|
||||
].forEach(function(n) {
|
||||
arr.push((n >= 10) ? n : '0' + n);
|
||||
});
|
||||
return arr.splice(0, 3).join('-') + ' ' + arr.join(':');
|
||||
},
|
||||
|
||||
/**
|
||||
* show Gaia commit number.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf commitInfo.prototype
|
||||
*/
|
||||
_loadGaiaCommit: function mi__loadGaiaCommit() {
|
||||
const GAIA_COMMIT = 'resources/gaia_commit.txt';
|
||||
|
||||
if (this._elements.dispHash.textContent) {
|
||||
return; // `gaia-commit.txt' has already been loaded
|
||||
}
|
||||
|
||||
var req = new XMLHttpRequest();
|
||||
req.onreadystatechange = (function(e) {
|
||||
if (req.readyState === 4) {
|
||||
if (req.status === 0 || req.status === 200) {
|
||||
var data = req.responseText.split('\n');
|
||||
|
||||
/**
|
||||
* XXX it would be great to pop a link to the github page
|
||||
* showing the commit, but there doesn't seem to be any way to
|
||||
* tell the browser to do it.
|
||||
*/
|
||||
|
||||
var d = new Date(parseInt(data[1] + '000', 10));
|
||||
this._elements.dispDate.textContent = this._dateToUTC(d);
|
||||
this._elements.dispHash.textContent = data[0].substr(0, 8);
|
||||
} else {
|
||||
console.error('Failed to fetch gaia commit: ', req.statusText);
|
||||
}
|
||||
}
|
||||
}).bind(this);
|
||||
|
||||
req.open('GET', GAIA_COMMIT, true); // async
|
||||
req.responseType = 'text';
|
||||
req.send();
|
||||
}
|
||||
};
|
||||
|
||||
return function ctor_commitInfo() {
|
||||
return new commitInfo();
|
||||
};
|
||||
});
|
||||
188
gaia/apps/settings/js/panels/about_more_info/device_info.js
Executable file
188
gaia/apps/settings/js/panels/about_more_info/device_info.js
Executable file
|
|
@ -0,0 +1,188 @@
|
|||
/**
|
||||
* Show misc informations
|
||||
*
|
||||
* @module abou_more_info/DeviceInfo
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
/** MMI control code used for retrieving a devices's IMEI code. */
|
||||
const GET_IMEI_COMMAND = '*#06#';
|
||||
|
||||
/**
|
||||
* @alias module:abou_more_info/DeviceInfo
|
||||
* @class DeviceInfo
|
||||
* @returns {DeviceInfo}
|
||||
*/
|
||||
var DeviceInfo = function() {
|
||||
this._elements = {};
|
||||
};
|
||||
|
||||
DeviceInfo.prototype = {
|
||||
/**
|
||||
* initialization.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf DeviceInfo.prototype
|
||||
* @param {HTMLElement} elements
|
||||
*/
|
||||
init: function mi_init(elements) {
|
||||
this._elements = elements;
|
||||
|
||||
this._loadImei();
|
||||
this._loadIccId();
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves the IMEI code corresponding with the specified SIM card slot.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf DeviceInfo.prototype
|
||||
* @param {Integer} simSlotIndex The slot whose IMEI code
|
||||
* we want to retrieve.
|
||||
* @return {Promise} A promise that resolves to the IMEI code or rejects
|
||||
* if an error occurred.
|
||||
*/
|
||||
_getImeiCode: function mi__getImeiCode(simSlotIndex) {
|
||||
var dialPromise = navigator.mozTelephony.dial(GET_IMEI_COMMAND,
|
||||
simSlotIndex);
|
||||
|
||||
return dialPromise.then(function about_dialImeiPromise(call) {
|
||||
return call.result.then(function(result) {
|
||||
if (result && result.success &&
|
||||
(result.serviceCode === 'scImei')) {
|
||||
return result.statusMessage;
|
||||
} else {
|
||||
var errorMsg = 'Could not retrieve the IMEI code for SIM ' +
|
||||
simSlotIndex;
|
||||
console.log(errorMsg);
|
||||
return Promise.reject(
|
||||
new Error(errorMsg)
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Populate the IMEI information entry with the provided list of IMEI codes.
|
||||
* If the code is not given or if it's empty then the entry will be marked
|
||||
* as unavailable.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf DeviceInfo.prototype
|
||||
* @param {Array} imeis An array of IMEI codes.
|
||||
*/
|
||||
_createImeiField: function mi__createImeiField(imeis) {
|
||||
while (this._elements.deviceInfoImeis.hasChildNodes()) {
|
||||
this._elements.deviceInfoImeis.removeChild(
|
||||
this._elements.deviceInfoImeis.lastChild);
|
||||
}
|
||||
|
||||
if (!imeis || imeis.length === 0) {
|
||||
var span = document.createElement('span');
|
||||
|
||||
span.setAttribute('data-l10n-id', 'unavailable');
|
||||
this._elements.deviceInfoImeis.appendChild(span);
|
||||
} else {
|
||||
imeis.forEach(function(imei, index) {
|
||||
var span = document.createElement('span');
|
||||
|
||||
if (imeis.length > 1) {
|
||||
document.l10n.setAttributes(span,
|
||||
'deviceInfo-IMEI-with-index', {
|
||||
index: index + 1,
|
||||
imei: imei
|
||||
});
|
||||
} else {
|
||||
span.textContent = imei;
|
||||
}
|
||||
|
||||
span.dataset.slot = index;
|
||||
this._elements.deviceInfoImeis.appendChild(span);
|
||||
}.bind(this));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Loads all the device's IMEI code in the corresponding entry.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf DeviceInfo.prototype
|
||||
* @return {Promise} A promise that is resolved when the container has been
|
||||
* fully populated.
|
||||
*/
|
||||
_loadImei: function mi__loadImei() {
|
||||
var conns = navigator.mozMobileConnections;
|
||||
|
||||
if (!navigator.mozTelephony || !conns) {
|
||||
this._elements.listImeis.hidden = true;
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// Retrieve all IMEI codes.
|
||||
var promises = [];
|
||||
for (var i = 0; i < conns.length; i++) {
|
||||
promises.push(this._getImeiCode(i));
|
||||
}
|
||||
|
||||
var self = this;
|
||||
return Promise.all(promises).then(function(imeis) {
|
||||
self._createImeiField(imeis);
|
||||
}, function() {
|
||||
self._createImeiField(null);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* show icc id.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf DeviceInfo.prototype
|
||||
*/
|
||||
_loadIccId: function mi__loadIccId() {
|
||||
var conns = navigator.mozMobileConnections;
|
||||
|
||||
if (!navigator.mozTelephony || !conns) {
|
||||
this._elements.listIccIds.hidden = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var multiSim = conns.length > 1;
|
||||
|
||||
// update iccids
|
||||
while (this._elements.deviceInfoIccIds.hasChildNodes()) {
|
||||
this._elements.deviceInfoIccIds.removeChild(
|
||||
this._elements.deviceInfoIccIds.lastChild);
|
||||
}
|
||||
Array.prototype.forEach.call(conns, function(conn, index) {
|
||||
var span = document.createElement('span');
|
||||
if (conn.iccId) {
|
||||
if (multiSim) {
|
||||
document.l10n.setAttributes(span,
|
||||
'deviceInfo-ICCID-with-index', {
|
||||
index: index + 1,
|
||||
iccid: conn.iccId
|
||||
});
|
||||
} else {
|
||||
span.textContent = conn.iccId;
|
||||
}
|
||||
} else {
|
||||
if (multiSim) {
|
||||
document.l10n.setAttributes(span,
|
||||
'deviceInfo-ICCID-unavailable-sim', {
|
||||
index: index + 1
|
||||
});
|
||||
} else {
|
||||
span.setAttribute('data-l10n-id', 'unavailable');
|
||||
}
|
||||
}
|
||||
this._elements.deviceInfoIccIds.appendChild(span);
|
||||
}.bind(this));
|
||||
}
|
||||
};
|
||||
|
||||
return function ctor_deviceInfo() {
|
||||
return new DeviceInfo();
|
||||
};
|
||||
});
|
||||
80
gaia/apps/settings/js/panels/about_more_info/hardware_info.js
Executable file
80
gaia/apps/settings/js/panels/about_more_info/hardware_info.js
Executable file
|
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
* Show hardware informations
|
||||
*
|
||||
* @module about_more_info/hardwareInfo
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var SettingsListener = require('shared/settings_listener');
|
||||
var BtContext = require('modules/bluetooth/bluetooth_context');
|
||||
|
||||
/**
|
||||
* @alias module:about_more_info/HardwareInfo
|
||||
* @class HardwareInfo
|
||||
* @returns {HardwareInfo}
|
||||
*/
|
||||
var HardwareInfo = function() {
|
||||
this._elements = {};
|
||||
};
|
||||
|
||||
HardwareInfo.prototype = {
|
||||
/**
|
||||
* initialization.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf HardwareInfo.prototype
|
||||
* @param {HTMLElement} elements
|
||||
*/
|
||||
init: function mi_init(elements) {
|
||||
this._elements = elements;
|
||||
|
||||
this._loadMacAddress();
|
||||
this._loadBluetoothAddress();
|
||||
},
|
||||
|
||||
/**
|
||||
* observe and show MacAddress.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf HardwareInfo.prototype
|
||||
*/
|
||||
_loadMacAddress: function mi__loadMacAddress() {
|
||||
SettingsListener.observe('deviceinfo.mac', '', (macAddress) =>
|
||||
this._elements.deviceInfoMac.textContent = macAddress);
|
||||
},
|
||||
|
||||
/**
|
||||
* refreshing the address field only.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf HardwareInfo.prototype
|
||||
* @param {String} address Bluetooth address
|
||||
*/
|
||||
_refreshBluetoothAddress: function mi__refreshBluetoothAddress(address) {
|
||||
// update btAddr
|
||||
if (address == null || address === '') {
|
||||
this._elements.btAddr.setAttribute('data-l10n-id',
|
||||
'bluetooth-address-unavailable');
|
||||
} else {
|
||||
this._elements.btAddr.removeAttribute('data-l10n-id');
|
||||
this._elements.btAddr.textContent = address;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* load Bluetooth address.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf HardwareInfo.prototype
|
||||
*/
|
||||
_loadBluetoothAddress: function about_loadBluetoothAddress() {
|
||||
BtContext.observe('address', this._refreshBluetoothAddress.bind(this));
|
||||
this._refreshBluetoothAddress(BtContext.address);
|
||||
}
|
||||
};
|
||||
|
||||
return function ctor_hardwareInfo() {
|
||||
return new HardwareInfo();
|
||||
};
|
||||
});
|
||||
38
gaia/apps/settings/js/panels/about_more_info/panel.js
Executable file
38
gaia/apps/settings/js/panels/about_more_info/panel.js
Executable file
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* Used to show Device/Information/More Information panel
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var SettingsPanel = require('modules/settings_panel');
|
||||
var CommitInfo = require('panels/about_more_info/commit_info');
|
||||
var HardwareInfo = require('panels/about_more_info/hardware_info');
|
||||
var DeviceInfo = require('panels/about_more_info/device_info');
|
||||
|
||||
return function ctor_support_panel() {
|
||||
var commitInfo = CommitInfo();
|
||||
var hardwareInfo = HardwareInfo();
|
||||
var deviceInfo = DeviceInfo();
|
||||
|
||||
return SettingsPanel({
|
||||
onInit: function(panel) {
|
||||
deviceInfo.init({
|
||||
listImeis: panel.querySelector('.list-imeis'),
|
||||
listIccIds: panel.querySelector('.list-iccids'),
|
||||
deviceInfoImeis: panel.querySelector('.deviceInfo-imeis'),
|
||||
deviceInfoIccIds: panel.querySelector('.deviceInfo-iccids')
|
||||
});
|
||||
|
||||
commitInfo.init({
|
||||
dispDate: panel.querySelector('.gaia-commit-date'),
|
||||
dispHash: panel.querySelector('.gaia-commit-hash')
|
||||
});
|
||||
|
||||
hardwareInfo.init({
|
||||
deviceInfoMac: panel.querySelector('[data-name="deviceinfo.mac"]'),
|
||||
btAddr: panel.querySelector('[data-name="deviceinfo.bt_address"]')
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
41
gaia/apps/settings/js/panels/accessibility/panel.js
Executable file
41
gaia/apps/settings/js/panels/accessibility/panel.js
Executable file
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* The accessibility panel
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var SettingsListener = require('shared/settings_listener');
|
||||
var SettingsPanel = require('modules/settings_panel');
|
||||
|
||||
return function ctor_accessibilityPanel() {
|
||||
var _screenReaderDesc;
|
||||
var _colorFilterDesc;
|
||||
var _audioDesc;
|
||||
|
||||
return SettingsPanel({
|
||||
onInit: function accessibilityPanel_onInit(rootElement) {
|
||||
_screenReaderDesc = rootElement.querySelector('#screenReader-desc');
|
||||
_colorFilterDesc = rootElement.querySelector('#colorFilter-desc');
|
||||
_audioDesc = rootElement.querySelector('#audio-desc');
|
||||
|
||||
SettingsListener.observe('accessibility.screenreader', false,
|
||||
function(enabled) {
|
||||
_screenReaderDesc.setAttribute('data-l10n-id',
|
||||
enabled ? 'enabled' : 'disabled');
|
||||
});
|
||||
|
||||
SettingsListener.observe('accessibility.colors.enable', false,
|
||||
function(enabled) {
|
||||
_colorFilterDesc.setAttribute('data-l10n-id',
|
||||
enabled ? 'enabled' : 'disabled');
|
||||
});
|
||||
|
||||
SettingsListener.observe('accessibility.monoaudio.enable', false,
|
||||
function(enabled) {
|
||||
_audioDesc.setAttribute('data-l10n-id',
|
||||
enabled ? 'mono-enabled' : 'mono-disabled');
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
41
gaia/apps/settings/js/panels/accessibility_screenreader/confirm_dialog.js
Executable file
41
gaia/apps/settings/js/panels/accessibility_screenreader/confirm_dialog.js
Executable file
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* The screen reader confirmation dialog
|
||||
*/
|
||||
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var SettingsListener = require('shared/settings_listener');
|
||||
|
||||
var _elements;
|
||||
var _enable;
|
||||
|
||||
var ConfirmDialog = {
|
||||
init: function ConfirmDialog_init(elements, onConfirmDialog) {
|
||||
_elements = elements;
|
||||
|
||||
_elements.container.onclick = function() {
|
||||
_elements.container.hidden = true;
|
||||
};
|
||||
|
||||
_elements.confirmButton.onclick = function(e) {
|
||||
SettingsListener.getSettingsLock().set(
|
||||
{ 'accessibility.screenreader' : _enable });
|
||||
};
|
||||
},
|
||||
|
||||
show: function ConfirmDialog_show(enable) {
|
||||
_enable = enable;
|
||||
var startStop = enable ? 'start' : 'stop';
|
||||
_elements.heading.setAttribute(
|
||||
'data-l10n-id', 'screenReader-confirm-title-' + startStop);
|
||||
_elements.text.setAttribute(
|
||||
'data-l10n-id', 'screenReader-confirm-description-' + startStop);
|
||||
_elements.confirmButton.setAttribute(
|
||||
'data-l10n-id', 'screenReader-confirm-button-' + startStop);
|
||||
_elements.container.hidden = false;
|
||||
}
|
||||
};
|
||||
|
||||
return ConfirmDialog;
|
||||
});
|
||||
44
gaia/apps/settings/js/panels/accessibility_screenreader/panel.js
Executable file
44
gaia/apps/settings/js/panels/accessibility_screenreader/panel.js
Executable file
|
|
@ -0,0 +1,44 @@
|
|||
/**
|
||||
* The accessibility screen reader panel
|
||||
*/
|
||||
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var SettingsListener = require('shared/settings_listener');
|
||||
var SettingsPanel = require('modules/settings_panel');
|
||||
var SettingsCache = require('modules/settings_cache');
|
||||
var ConfirmDialog = require(
|
||||
'panels/accessibility_screenreader/confirm_dialog');
|
||||
|
||||
var kEnabledKey = 'accessibility.screenreader';
|
||||
|
||||
return function ctor_accessibility_screenreader_panel() {
|
||||
var _screenreaderSwitch;
|
||||
return SettingsPanel({
|
||||
onInit: function asr_onInit(rootElement) {
|
||||
_screenreaderSwitch =
|
||||
rootElement.querySelector('.screenreader-enable');
|
||||
var dialogContainer = rootElement.querySelector(
|
||||
'#screenreader-confirm-dialog');
|
||||
|
||||
ConfirmDialog.init({
|
||||
container: dialogContainer,
|
||||
confirmButton: dialogContainer.querySelector('button.danger'),
|
||||
heading: dialogContainer.querySelector('h1'),
|
||||
text: dialogContainer.querySelector('p')});
|
||||
|
||||
_screenreaderSwitch.addEventListener('click', event => {
|
||||
event.preventDefault();
|
||||
SettingsCache.getSettings(function(results) {
|
||||
ConfirmDialog.show(!results[kEnabledKey]);
|
||||
});
|
||||
});
|
||||
|
||||
SettingsListener.observe(kEnabledKey, false, function(value) {
|
||||
_screenreaderSwitch.checked = value;
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
31
gaia/apps/settings/js/panels/achievements/achievements_list.js
Executable file
31
gaia/apps/settings/js/panels/achievements/achievements_list.js
Executable file
|
|
@ -0,0 +1,31 @@
|
|||
/**
|
||||
* UI for Achievements panel's functionality.
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var ListView = require('modules/mvvm/list_view');
|
||||
var ObservableArray = require('modules/mvvm/observable_array');
|
||||
var AchievementsTemplate = require(
|
||||
'panels/achievements/achievements_template');
|
||||
|
||||
function AchievementsList(root) {
|
||||
this._achievements = ObservableArray([]);
|
||||
this._listView = ListView(root, this._achievements, AchievementsTemplate);
|
||||
}
|
||||
|
||||
AchievementsList.prototype = {
|
||||
get achievements() {
|
||||
return this._achievements.array;
|
||||
},
|
||||
|
||||
set achievements(achievements) {
|
||||
achievements = achievements || [];
|
||||
this._achievements.reset(achievements);
|
||||
}
|
||||
};
|
||||
|
||||
return function ctor_achievements_list(panel) {
|
||||
return new AchievementsList(panel);
|
||||
};
|
||||
});
|
||||
54
gaia/apps/settings/js/panels/achievements/achievements_template.js
Executable file
54
gaia/apps/settings/js/panels/achievements/achievements_template.js
Executable file
|
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
* The template function for generating an UI element for an Addon item.
|
||||
*
|
||||
* @module achievements/achievements_template
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
return function achievementsTemplate(achievement, recycled) {
|
||||
var container, span, small, icon, details;
|
||||
var name = achievement.criteria.split('achievements/')[1];
|
||||
|
||||
if (recycled) {
|
||||
container = recycled;
|
||||
details = container.querySelector('span');
|
||||
span = details.querySelector('span');
|
||||
small = details.querySelector('small');
|
||||
icon = container.querySelector('img');
|
||||
} else {
|
||||
container = document.createElement('li');
|
||||
details = document.createElement('span');
|
||||
span = document.createElement('span');
|
||||
small = document.createElement('small');
|
||||
icon = document.createElement('img');
|
||||
|
||||
container.classList.add('achievements-item');
|
||||
details.classList.add('details');
|
||||
details.setAttribute('role', 'presentation');
|
||||
small.classList.add('description');
|
||||
icon.classList.add('icon');
|
||||
icon.setAttribute('aria-hidden', true);
|
||||
|
||||
container.appendChild(icon);
|
||||
details.appendChild(span);
|
||||
details.appendChild(small);
|
||||
container.appendChild(details);
|
||||
}
|
||||
|
||||
icon.src = `../style/images/achievements/${name}.png`;
|
||||
|
||||
span.setAttribute('data-l10n-id', `achievements-${name}-name`);
|
||||
small.setAttribute('data-l10n-id', `achievements-${name}-description`);
|
||||
|
||||
if (achievement.evidence) {
|
||||
container.classList.add('rewarded');
|
||||
container.removeAttribute('aria-disabled');
|
||||
} else {
|
||||
container.classList.remove('rewarded');
|
||||
container.setAttribute('aria-disabled', true);
|
||||
}
|
||||
|
||||
return container;
|
||||
};
|
||||
});
|
||||
28
gaia/apps/settings/js/panels/achievements/panel.js
Executable file
28
gaia/apps/settings/js/panels/achievements/panel.js
Executable file
|
|
@ -0,0 +1,28 @@
|
|||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var SettingsPanel = require('modules/settings_panel');
|
||||
var AchievementsList = require('panels/achievements/achievements_list');
|
||||
var SettingsListener = require('shared/settings_listener');
|
||||
|
||||
return function ctor_achievements_panel() {
|
||||
var achievementsList;
|
||||
var update = achievements => achievementsList.achievements = achievements;
|
||||
|
||||
return SettingsPanel({
|
||||
onInit: function(panel) {
|
||||
var listElement = panel.querySelector('.achievements-list');
|
||||
achievementsList = AchievementsList(listElement);
|
||||
},
|
||||
|
||||
onBeforeShow: function() {
|
||||
SettingsListener.observe(
|
||||
'achievements', achievementsList.achievements, update);
|
||||
},
|
||||
|
||||
onHide: function() {
|
||||
SettingsListener.unobserve('achievements', update);
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
143
gaia/apps/settings/js/panels/addon_details/addon_details.js
Executable file
143
gaia/apps/settings/js/panels/addon_details/addon_details.js
Executable file
|
|
@ -0,0 +1,143 @@
|
|||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var ManifestHelper = require('shared/manifest_helper');
|
||||
var AddonManager = require('modules/addon_manager');
|
||||
|
||||
const PREFERRED_ICON_SIZE = 64 * (window.devicePixelRatio || 1);
|
||||
|
||||
/**
|
||||
* This module renders the information of an addon to the UI elements.
|
||||
*
|
||||
* @class AddonDetails
|
||||
* @requires module:shared/manifest_helper
|
||||
* @requires module:modules/addon_manager
|
||||
* @returns {AddonDetails}
|
||||
*/
|
||||
function AddonDetails(elements) {
|
||||
this._boundUpdateEnabledState = this._updateEnabledState.bind(this);
|
||||
this._elements = elements;
|
||||
this._curApp = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the enabled state of an addon.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf AddonDetails.prototype
|
||||
* @param {Boolean} newState
|
||||
*/
|
||||
AddonDetails.prototype._updateEnabledState = function _update(newState) {
|
||||
this._elements.enabledState
|
||||
.setAttribute('data-l10n-id', newState ? 'enabled' : 'disabled');
|
||||
};
|
||||
|
||||
/**
|
||||
* Render details of an app to the UI elements.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf AddonDetails.prototype
|
||||
* @param {Object} app Add-on to render
|
||||
*/
|
||||
AddonDetails.prototype.render = function render(app) {
|
||||
if (this._curApp !== null) {
|
||||
this._curApp.unobserve('enabled', this._boundUpdateEnabledState);
|
||||
}
|
||||
this._curApp = app;
|
||||
if (this._curApp) {
|
||||
this._curApp.observe('enabled', this._boundUpdateEnabledState);
|
||||
this._updateEnabledState(this._curApp.enabled);
|
||||
}
|
||||
|
||||
var l10n = document.l10n;
|
||||
var manifest =
|
||||
new ManifestHelper(app.instance.manifest || app.instance.updateManifest);
|
||||
|
||||
this.updateNames(manifest);
|
||||
|
||||
// Put an icon next to the description
|
||||
navigator.mozApps.mgmt.getIcon(app, PREFERRED_ICON_SIZE).then((blob) => {
|
||||
this._elements.icon.src = URL.createObjectURL(blob);
|
||||
}).catch(() => {
|
||||
this._elements.icon.src = '../style/images/default.png';
|
||||
});
|
||||
|
||||
// Display the add-on version
|
||||
if (manifest.version) {
|
||||
this._elements.version.textContent = 'v' + manifest.version;
|
||||
}
|
||||
|
||||
// Display the add-on description if there is one
|
||||
if (manifest.description) {
|
||||
// If we have a description, the ManifestHelper class returned the
|
||||
// best localized version from the manifest file.
|
||||
this._elements.description.removeAttribute('data-l10n-id');
|
||||
this._elements.description.textContent = manifest.description;
|
||||
} else {
|
||||
// If there is no description, then display "no description" or similar.
|
||||
this._elements.description
|
||||
.setAttribute('data-l10n-id', 'addon-no-description');
|
||||
}
|
||||
|
||||
// Display the add-on developer name if there is one
|
||||
var developerName = manifest.developer && manifest.developer.name;
|
||||
l10n.setAttributes(this._elements.developer, 'addon-developer-name', {
|
||||
developerName: developerName || ''
|
||||
});
|
||||
|
||||
// Old add-on format had 'customizations' field, it is now moved to
|
||||
// 'content_scripts' field
|
||||
var oldAddonFormat = !!manifest.customizations;
|
||||
if (oldAddonFormat) {
|
||||
l10n.setAttributes(this._elements.obsoleteStatusInfo,
|
||||
AddonManager.canDelete(app) ? 'addon-obsolete-can-delete' :
|
||||
'addon-obsolete');
|
||||
}
|
||||
this._elements.obsoleteStatus.hidden = !oldAddonFormat;
|
||||
|
||||
// Get the list of targeted apps
|
||||
this._elements.targetsList.textContent = ''; // Clear any old content here
|
||||
AddonManager.getAddonTargets(app).then((targets) => {
|
||||
var names = [];
|
||||
targets.forEach(function(target) {
|
||||
var manifest =
|
||||
new ManifestHelper(target.manifest || target.updateManifest);
|
||||
names.push(manifest.displayName);
|
||||
});
|
||||
|
||||
names.sort();
|
||||
|
||||
if (names.length < 1) {
|
||||
this._elements.targetsList
|
||||
.setAttribute('data-l10n-id', 'addon-no-targets');
|
||||
} else {
|
||||
this._elements.targetsList.textContent = names.join(', ');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Render app names where appropriate.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf AddonDetails.prototype
|
||||
* @param {JSON} manifest
|
||||
*/
|
||||
AddonDetails.prototype.updateNames = function _updateNames(manifest) {
|
||||
// Display the name of the add-on in the panel header
|
||||
var appnameArgs = { appName: manifest.name };
|
||||
var l10n = document.l10n;
|
||||
|
||||
l10n.setAttributes(
|
||||
this._elements.header, 'addon-details-header1', appnameArgs);
|
||||
l10n.setAttributes(
|
||||
this._elements.name, 'addon-details-header1', appnameArgs);
|
||||
// Put text description for an icon
|
||||
l10n.setAttributes(
|
||||
this._elements.icon, 'accessibility-app-icon', appnameArgs);
|
||||
};
|
||||
|
||||
return function ctor_addon_details(panel) {
|
||||
return new AddonDetails(panel);
|
||||
};
|
||||
});
|
||||
130
gaia/apps/settings/js/panels/addon_details/panel.js
Executable file
130
gaia/apps/settings/js/panels/addon_details/panel.js
Executable file
|
|
@ -0,0 +1,130 @@
|
|||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var SettingsPanel = require('modules/settings_panel');
|
||||
var AddonManager = require('modules/addon_manager');
|
||||
var AddonDetails = require('panels/addon_details/addon_details');
|
||||
var SettingsService = require('modules/settings_service');
|
||||
var Toaster = require('shared/toaster');
|
||||
|
||||
return function ctor_addon_details_panel() {
|
||||
return SettingsPanel({
|
||||
onInit: function(panel) {
|
||||
this._elements = {
|
||||
body: panel.querySelector('.addon-details-body'),
|
||||
header: panel.querySelector('.addon-details-header'),
|
||||
gaiaHeader: panel.querySelector('gaia-header'),
|
||||
enabledState: panel.querySelector('.addon-enabled details'),
|
||||
name: panel.querySelector('.addon-details-name'),
|
||||
icon: panel.querySelector('.addon-details-icon'),
|
||||
version: panel.querySelector('.addon-details-version'),
|
||||
description: panel.querySelector('.addon-description-text'),
|
||||
developer: panel.querySelector('.addon-developer'),
|
||||
targetsList: panel.querySelector('.addon-targets'),
|
||||
toggle: panel.querySelector('.addon-enabled'),
|
||||
deleteButton: panel.querySelector('.addon-delete'),
|
||||
obsoleteStatus: panel.querySelector('.addon-obsolete-status'),
|
||||
obsoleteStatusInfo: panel.querySelector('.addon-obsolete-info')
|
||||
};
|
||||
this._details = AddonDetails(this._elements);
|
||||
this._boundOnAppEnabledChange = this._onAppEnabledChange.bind(this);
|
||||
|
||||
// Hook up the enable/disable toggle button
|
||||
this._elements.toggle.onchange = this._onToggleChange.bind(this);
|
||||
// Hook up the delete button
|
||||
this._elements.deleteButton.onclick = this._onDelete.bind(this);
|
||||
},
|
||||
|
||||
onBeforeShow: function(panel, options) {
|
||||
return Promise.resolve((() => {
|
||||
if (options.addon) {
|
||||
return options.addon;
|
||||
} else if (options.manifestURL) {
|
||||
return AddonManager.findAddonByManifestURL(options.manifestURL);
|
||||
}
|
||||
})()).then((addon) => {
|
||||
this._curAddon = addon;
|
||||
|
||||
// set initial state
|
||||
if (this._curAddon) {
|
||||
this._details.render(this._curAddon);
|
||||
this._elements.toggle.checked =
|
||||
AddonManager.isEnabled(this._curAddon);
|
||||
this._curAddon.observe('enabled', this._boundOnAppEnabledChange);
|
||||
this._elements.deleteButton.disabled =
|
||||
!AddonManager.canDelete(this._curAddon);
|
||||
this._elements.body.hidden = false;
|
||||
} else {
|
||||
console.error('No valid add-on');
|
||||
this._elements.body.hidden = true;
|
||||
}
|
||||
|
||||
this._elements.gaiaHeader.hidden = false;
|
||||
});
|
||||
},
|
||||
|
||||
onHide: function() {
|
||||
// Scroll back to the top
|
||||
this._elements.body.scrollTop = 0;
|
||||
|
||||
if (this._curAddon) {
|
||||
this._curAddon.unobserve('enabled', this._boundOnAppEnabledChange);
|
||||
this._curAddon = null;
|
||||
}
|
||||
},
|
||||
|
||||
_showStepsRequired(type) {
|
||||
// If an addon injected a script then we have to restart any
|
||||
// affected apps. And if those apps are system apps, then we
|
||||
// need to reboot the device.
|
||||
if (type === 'reboot') {
|
||||
// XXX We could do a confirm dialog here asking the user if
|
||||
// they want to go ahead and reboot the device now.
|
||||
Toaster.showToast({
|
||||
messageL10nId: 'addon-reboot-required',
|
||||
latency: 3000,
|
||||
useTransition: true
|
||||
});
|
||||
} else if (type === 'restart') {
|
||||
Toaster.showToast({
|
||||
messageL10nId: 'addon-restart-apps-to-disable',
|
||||
latency: 3000,
|
||||
useTransition: true
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_onAppEnabledChange: function(enabled) {
|
||||
this._elements.toggle.checked = enabled;
|
||||
if (!enabled) {
|
||||
AddonManager.getAddonDisableType(this._curAddon).then(
|
||||
this._showStepsRequired);
|
||||
}
|
||||
},
|
||||
|
||||
_onToggleChange: function() {
|
||||
if (this._elements.toggle.checked) {
|
||||
AddonManager.enableAddon(this._curAddon);
|
||||
} else {
|
||||
AddonManager.disableAddon(this._curAddon);
|
||||
}
|
||||
},
|
||||
|
||||
_onDelete: function() {
|
||||
this._elements.deleteButton.disabled = true; // don't delete it twice!
|
||||
var disableType;
|
||||
AddonManager.getAddonDisableType(this._curAddon).then(type => {
|
||||
disableType = type;
|
||||
return AddonManager.deleteAddon(this._curAddon);
|
||||
}).then(() => {
|
||||
this._showStepsRequired(disableType);
|
||||
SettingsService.navigate('addons'); /* go back*/
|
||||
}).catch(reason => {
|
||||
console.error('Addon deletion failed:', reason);
|
||||
// If the user cancelled deletion, we need to reenable the button
|
||||
this._elements.deleteButton.disabled = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
75
gaia/apps/settings/js/panels/addons/addons_list.js
Executable file
75
gaia/apps/settings/js/panels/addons/addons_list.js
Executable file
|
|
@ -0,0 +1,75 @@
|
|||
/**
|
||||
* UI for Addons panel's functionality.
|
||||
*
|
||||
* @module AddonsList
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var SettingsService = require('modules/settings_service');
|
||||
var ListView = require('modules/mvvm/list_view');
|
||||
var AddonItemTemplateFactory = require('panels/addons/addons_template');
|
||||
var ObservableArray = require('modules/mvvm/observable_array');
|
||||
|
||||
function AddonsList(root, manager) {
|
||||
this._enabled = false;
|
||||
this._listRoot = root;
|
||||
this._addonManager = manager;
|
||||
this._addons = ObservableArray(this._sortByName(
|
||||
this._addonManager.addons.array));
|
||||
|
||||
['insert', 'remove'].forEach(e =>
|
||||
this._addonManager.addons.addEventListener(e, this._update.bind(this)));
|
||||
|
||||
var addonTemplate = AddonItemTemplateFactory(function onClick(addon) {
|
||||
SettingsService.navigate('addon-details', { addon: addon });
|
||||
});
|
||||
|
||||
this._listView = ListView(this._listRoot, this._addons, addonTemplate);
|
||||
}
|
||||
|
||||
AddonsList.prototype = {
|
||||
set enabled(value) {
|
||||
if (value !== this._enabled) {
|
||||
this._listView.enabled = this._enabled = value;
|
||||
}
|
||||
},
|
||||
|
||||
_sortByName: function(addons) {
|
||||
return addons.sort((a, b) => {
|
||||
return a._app.manifest.name.localeCompare(b._app.manifest.name);
|
||||
});
|
||||
},
|
||||
|
||||
_update: function() {
|
||||
return this._filter ?
|
||||
Promise.all(this._addonManager.addons.array.map(addon =>
|
||||
this._addonManager.addonAffectsApp(addon, this._filter).then(
|
||||
affects => affects ? addon : undefined))).then(
|
||||
addons => this._addons.reset(this._sortByName(
|
||||
addons.filter(addon => addon)))) :
|
||||
Promise.resolve(this._addons.reset(this._sortByName(
|
||||
this._addonManager.addons.array)));
|
||||
},
|
||||
|
||||
setFilter: function(manifestURL) {
|
||||
if (!manifestURL) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
this._filter = manifestURL;
|
||||
return this._update();
|
||||
},
|
||||
|
||||
unsetFilter: function() {
|
||||
if (!this._filter) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
delete this._filter;
|
||||
return this._update();
|
||||
}
|
||||
};
|
||||
|
||||
return function ctor_addons_list(root, manager) {
|
||||
return new AddonsList(root, manager);
|
||||
};
|
||||
});
|
||||
64
gaia/apps/settings/js/panels/addons/addons_template.js
Executable file
64
gaia/apps/settings/js/panels/addons/addons_template.js
Executable file
|
|
@ -0,0 +1,64 @@
|
|||
/**
|
||||
* The template function for generating an UI element for an Addon item.
|
||||
*
|
||||
* @module addons/addons_template
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var ManifestHelper = require('shared/manifest_helper');
|
||||
|
||||
const PREFERRED_ICON_SIZE = 30 * (window.devicePixelRatio || 1);
|
||||
|
||||
function addonsTemplate(onClick, addon, recycled) {
|
||||
var manifest = new ManifestHelper(addon.instance.manifest ||
|
||||
addon.instance.updateManifest);
|
||||
var container = null;
|
||||
var link, span, small, icon;
|
||||
if (recycled) {
|
||||
container = recycled;
|
||||
link = container.querySelector('a');
|
||||
span = container.querySelector('span');
|
||||
small = container.querySelector('small');
|
||||
icon = container.querySelector('img');
|
||||
} else {
|
||||
container = document.createElement('li');
|
||||
link = document.createElement('a');
|
||||
span = document.createElement('span');
|
||||
small = document.createElement('small');
|
||||
icon = document.createElement('img');
|
||||
|
||||
link.href = '#';
|
||||
link.classList.add('menu-item');
|
||||
small.classList.add('menu-item-desc');
|
||||
|
||||
link.appendChild(icon);
|
||||
link.appendChild(span);
|
||||
link.appendChild(small);
|
||||
container.appendChild(link);
|
||||
}
|
||||
|
||||
navigator.mozApps.mgmt
|
||||
.getIcon(addon.instance, PREFERRED_ICON_SIZE).then((blob) => {
|
||||
icon.src = URL.createObjectURL(blob);
|
||||
}).catch(() => {
|
||||
icon.src = '../style/images/default.png';
|
||||
});
|
||||
|
||||
span.textContent = manifest.displayName;
|
||||
small.setAttribute('data-l10n-id', addon.enabled ? 'enabled' : 'disabled');
|
||||
addon.observe('enabled', function(enabled) {
|
||||
small.setAttribute('data-l10n-id', enabled ? 'enabled' : 'disabled');
|
||||
});
|
||||
|
||||
link.onclick = () => {
|
||||
onClick(addon);
|
||||
};
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
return function ctor_addonsTemplate(onClick) {
|
||||
return addonsTemplate.bind(null, onClick);
|
||||
};
|
||||
});
|
||||
44
gaia/apps/settings/js/panels/addons/panel.js
Executable file
44
gaia/apps/settings/js/panels/addons/panel.js
Executable file
|
|
@ -0,0 +1,44 @@
|
|||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var SettingsPanel = require('modules/settings_panel');
|
||||
var AddonManager = require('modules/addon_manager');
|
||||
var AddonsList = require('panels/addons/addons_list');
|
||||
|
||||
return function ctor_addons_panel() {
|
||||
var addonsList;
|
||||
|
||||
return SettingsPanel({
|
||||
onInit: function(panel) {
|
||||
var listElement = panel.querySelector('.addon-list');
|
||||
var moreLink = panel.querySelector('.get-more-addons');
|
||||
|
||||
addonsList = AddonsList(listElement, AddonManager);
|
||||
moreLink.addEventListener('click', function() {
|
||||
moreLink.blur();
|
||||
// The addons list panel will update itself if new addon is installed.
|
||||
var activity = new window.MozActivity({
|
||||
name: 'marketplace-category',
|
||||
data: { slug: 'addons' }
|
||||
});
|
||||
// For workaround jshint.
|
||||
activity.onsuccess = function() {};
|
||||
});
|
||||
},
|
||||
|
||||
onBeforeShow: function(panel, options) {
|
||||
if (options && options.manifestURL) {
|
||||
return addonsList.setFilter(options.manifestURL).then(() =>
|
||||
addonsList.enabled = true);
|
||||
} else {
|
||||
addonsList.enabled = true;
|
||||
}
|
||||
},
|
||||
|
||||
onBeforeHide: function() {
|
||||
return addonsList.unsetFilter().then(() =>
|
||||
addonsList.enabled = false);
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
53
gaia/apps/settings/js/panels/apn_editor/apn_editor.js
Executable file
53
gaia/apps/settings/js/panels/apn_editor/apn_editor.js
Executable file
|
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* The apn editor module
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var ApnEditorConst = require('panels/apn_editor/apn_editor_const');
|
||||
var ApnEditorSession = require('panels/apn_editor/apn_editor_session');
|
||||
|
||||
var APN_PROPERTIES = ApnEditorConst.APN_PROPERTIES;
|
||||
var APN_PROPERTY_DEFAULTS = ApnEditorConst.APN_PROPERTY_DEFAULTS;
|
||||
var VALUE_CONVERTERS = ApnEditorConst.VALUE_CONVERTERS;
|
||||
|
||||
function ApnEditor(rootElement) {
|
||||
this._inputElements = {};
|
||||
APN_PROPERTIES.forEach(function(name) {
|
||||
this._inputElements[name] = rootElement.querySelector('.' + name);
|
||||
}, this);
|
||||
}
|
||||
|
||||
ApnEditor.prototype = {
|
||||
_convertValue: function ae_convertValue(value, converter) {
|
||||
if (converter) {
|
||||
return converter(value);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
},
|
||||
_fillInputElements: function ae_fillInputElements(inputElements, apn) {
|
||||
APN_PROPERTIES.forEach(function(name) {
|
||||
var inputElement = inputElements[name];
|
||||
if (inputElement) {
|
||||
var value = (apn && apn[name.toLowerCase()]) ||
|
||||
APN_PROPERTY_DEFAULTS[name];
|
||||
inputElement.value =
|
||||
this._convertValue(value, VALUE_CONVERTERS.TO_STRING[name]);
|
||||
}
|
||||
}, this);
|
||||
},
|
||||
createApn: function ae_createApn(serviceId, apnItem) {
|
||||
this._fillInputElements(this._inputElements, apnItem.apn);
|
||||
return ApnEditorSession(serviceId, 'new', this._inputElements, apnItem);
|
||||
},
|
||||
editApn: function ae_editApn(serviceId, apnItem) {
|
||||
this._fillInputElements(this._inputElements, apnItem.apn);
|
||||
return ApnEditorSession(serviceId, 'edit', this._inputElements, apnItem);
|
||||
}
|
||||
};
|
||||
|
||||
return function ctor_apn_editor(rootElement) {
|
||||
return new ApnEditor(rootElement);
|
||||
};
|
||||
});
|
||||
47
gaia/apps/settings/js/panels/apn_editor/apn_editor_const.js
Executable file
47
gaia/apps/settings/js/panels/apn_editor/apn_editor_const.js
Executable file
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* The apn editor const
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var APN_PROPERTY_DEFAULTS = {
|
||||
'apn': '',
|
||||
'user': '',
|
||||
'password': '',
|
||||
'proxy': '',
|
||||
'port': '',
|
||||
'mmsc': '',
|
||||
'mmsproxy': '',
|
||||
'mmsport': '',
|
||||
'authtype': 'notDefined',
|
||||
'types': ['default'],
|
||||
'protocol': 'notDefined',
|
||||
'roaming_protocol': 'notDefined',
|
||||
'mtu' : ''
|
||||
};
|
||||
|
||||
var APN_PROPERTIES = Object.keys(APN_PROPERTY_DEFAULTS);
|
||||
|
||||
var VALUE_CONVERTERS = {
|
||||
'TO_STRING': {
|
||||
'types': function(types) {
|
||||
if (types && Array.isArray(types) && types.length) {
|
||||
return types.join(', ');
|
||||
} else {
|
||||
return 'default';
|
||||
}
|
||||
}
|
||||
},
|
||||
'TO_DATA': {
|
||||
'types': function(string) {
|
||||
return string.split(',').map((str) => str.trim());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
get APN_PROPERTIES() { return APN_PROPERTIES; },
|
||||
get APN_PROPERTY_DEFAULTS() { return APN_PROPERTY_DEFAULTS; },
|
||||
get VALUE_CONVERTERS() { return VALUE_CONVERTERS; }
|
||||
};
|
||||
});
|
||||
99
gaia/apps/settings/js/panels/apn_editor/apn_editor_session.js
Executable file
99
gaia/apps/settings/js/panels/apn_editor/apn_editor_session.js
Executable file
|
|
@ -0,0 +1,99 @@
|
|||
/**
|
||||
* The apn editor session module
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var ApnSettingsManager = require('modules/apn/apn_settings_manager');
|
||||
var ApnEditorConst = require('panels/apn_editor/apn_editor_const');
|
||||
var ApnUtils = require('modules/apn/apn_utils');
|
||||
|
||||
var APN_PROPERTIES = ApnEditorConst.APN_PROPERTIES;
|
||||
var VALUE_CONVERTERS = ApnEditorConst.VALUE_CONVERTERS;
|
||||
|
||||
function ApnEditorSession(serviceId, mode, inputElements, apnItem) {
|
||||
this._serviceId = serviceId;
|
||||
this._mode = mode;
|
||||
this._inputElements = inputElements;
|
||||
this._apnItem = apnItem;
|
||||
}
|
||||
|
||||
ApnEditorSession.prototype = {
|
||||
_convertValue: function ae_convertValue(value, converter) {
|
||||
if (converter) {
|
||||
return converter(value);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
},
|
||||
_exportApnSetting: function aes_exportApnSetting(inputElements) {
|
||||
var newApnSetting = {};
|
||||
APN_PROPERTIES.forEach(function(name) {
|
||||
var inputElement = inputElements[name];
|
||||
if (inputElement && !inputElement.hidden && inputElement.value) {
|
||||
newApnSetting[name.toLowerCase()] = this._convertValue(
|
||||
inputElement.value, VALUE_CONVERTERS.TO_DATA[name]);
|
||||
}
|
||||
}, this);
|
||||
return newApnSetting;
|
||||
},
|
||||
_commitNew: function aes_commitNew() {
|
||||
var promises = [];
|
||||
var newApnSetting = this._exportApnSetting(this._inputElements);
|
||||
newApnSetting.types.slice().forEach(function(type) {
|
||||
// Clone new settings to make sure the promises do not all
|
||||
// point to the very last one
|
||||
var settingClone = ApnUtils.clone(newApnSetting);
|
||||
settingClone.types = [type];
|
||||
promises.push(
|
||||
ApnSettingsManager.addApn(this._serviceId, settingClone));
|
||||
}, this);
|
||||
return Promise.all(promises);
|
||||
},
|
||||
_commitEdit: function aes_commitEdit() {
|
||||
var promises = [];
|
||||
var newApnSetting = this._exportApnSetting(this._inputElements);
|
||||
if (newApnSetting.types.length === 1) {
|
||||
promises.push(ApnSettingsManager.updateApn(this._serviceId,
|
||||
this._apnItem.id, newApnSetting));
|
||||
} else {
|
||||
newApnSetting.types.forEach(function(type) {
|
||||
var settingClone = ApnUtils.clone(newApnSetting);
|
||||
settingClone.types = [type];
|
||||
if (type === this._apnItem.apn.types[0]) {
|
||||
promises.push(ApnSettingsManager.updateApn(this._serviceId,
|
||||
this._apnItem.id, settingClone));
|
||||
} else {
|
||||
promises.push(ApnSettingsManager.addApn(this._serviceId,
|
||||
settingClone));
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
return Promise.all(promises);
|
||||
},
|
||||
commit: function aes_commit() {
|
||||
switch (this._mode) {
|
||||
case 'new':
|
||||
return this._commitNew();
|
||||
case 'edit':
|
||||
return this._commitEdit();
|
||||
default:
|
||||
console.error('invalid mode');
|
||||
return Promise.resolve();
|
||||
}
|
||||
},
|
||||
cancel: function aes_cancel() {
|
||||
APN_PROPERTIES.forEach(function(name) {
|
||||
this._inputElements[name].value = '';
|
||||
}, this);
|
||||
this._apnItem = null;
|
||||
},
|
||||
get mode() {
|
||||
return this._mode;
|
||||
}
|
||||
};
|
||||
|
||||
return function ctor_apnESession(serviceId, mode, inputElements, apnItem) {
|
||||
return new ApnEditorSession(serviceId, mode, inputElements, apnItem);
|
||||
};
|
||||
});
|
||||
155
gaia/apps/settings/js/panels/apn_editor/panel.js
Executable file
155
gaia/apps/settings/js/panels/apn_editor/panel.js
Executable file
|
|
@ -0,0 +1,155 @@
|
|||
/**
|
||||
* The apn editor panel
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var SettingsService = require('modules/settings_service');
|
||||
var SettingsPanel = require('modules/settings_panel');
|
||||
var SettingsCache = require('modules/settings_cache');
|
||||
var ApnSettingsManager = require('modules/apn/apn_settings_manager');
|
||||
var ApnEditor = require('panels/apn_editor/apn_editor');
|
||||
|
||||
var _back = function() {
|
||||
SettingsService.navigate('apn-list');
|
||||
};
|
||||
|
||||
var _getDialogService = function() {
|
||||
return new Promise(function(resolve) {
|
||||
require(['modules/dialog_service'], resolve);
|
||||
});
|
||||
};
|
||||
|
||||
var _showChangeApnWarning = function() {
|
||||
return _getDialogService().then((DialogService) => {
|
||||
return DialogService.confirm('change-apn-warning-message1', {
|
||||
title: 'apnSettings',
|
||||
submitButton: {
|
||||
id: 'yes',
|
||||
style: 'recommend'
|
||||
},
|
||||
cancelButton: 'cancel'
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var _showDeleteApnWarning = function() {
|
||||
return _getDialogService().then((DialogService) => {
|
||||
return DialogService.confirm('delete-apn-confirm', {
|
||||
title: 'confirmation',
|
||||
submitButton: {
|
||||
id: 'delete-apn',
|
||||
style: 'danger'
|
||||
},
|
||||
cancelButton: 'cancel'
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return function ctor_apnEditorPanel() {
|
||||
var _rootElement;
|
||||
var _elements;
|
||||
|
||||
var _apnEditor;
|
||||
var _editorSession;
|
||||
|
||||
var _apnItem;
|
||||
var _apnType;
|
||||
var _serviceId;
|
||||
|
||||
var _leftApp = false;
|
||||
|
||||
return SettingsPanel({
|
||||
onInit: function ae_onInit(rootElement) {
|
||||
_rootElement = rootElement;
|
||||
_apnEditor = new ApnEditor(rootElement);
|
||||
_elements = {
|
||||
panel: rootElement.querySelector('.panel'),
|
||||
okBtn: rootElement.querySelector('button.ok'),
|
||||
deteleItem: rootElement.querySelector('.apnSettings-btns li.delete'),
|
||||
deleteBtn:
|
||||
rootElement.querySelector('.apnSettings-btns li.delete button')
|
||||
};
|
||||
|
||||
_elements.okBtn.onclick = function() {
|
||||
if (!_editorSession) {
|
||||
_back();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_editorSession.mode === 'new') {
|
||||
_editorSession.commit().then(_back);
|
||||
_editorSession = null;
|
||||
} else if (_editorSession.mode === 'edit') {
|
||||
// Display the warning only when Data roaming is turned on and it is
|
||||
// the current APN in use that’s being edited.
|
||||
Promise.all([
|
||||
ApnSettingsManager.getActiveApnId(_serviceId, _apnType),
|
||||
new Promise((resolve) => {
|
||||
SettingsCache.getSettings(function(results) {
|
||||
resolve(results['ril.data.roaming_enabled']);
|
||||
});
|
||||
})
|
||||
]).then(function(results) {
|
||||
var activeApnId = results[0];
|
||||
var dataRoamingEnabled = results[1];
|
||||
if (activeApnId === _apnItem.id && dataRoamingEnabled) {
|
||||
return _showChangeApnWarning();
|
||||
}
|
||||
}).then(function(result) {
|
||||
if (!result || (result && result.type === 'submit')) {
|
||||
_editorSession.commit().then(_back);
|
||||
_editorSession = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
_elements.deleteBtn.onclick = function() {
|
||||
_showDeleteApnWarning().then((result) => {
|
||||
if (result.type === 'submit') {
|
||||
ApnSettingsManager.removeApn(_serviceId, _apnItem.id).then(_back);
|
||||
}
|
||||
});
|
||||
};
|
||||
},
|
||||
onBeforeShow: function ae_onBeforeShow(rootElement, options) {
|
||||
// If this flag has been set, which means that users have been left
|
||||
// the app before so we should keep the original state instead of
|
||||
// refreshing it.
|
||||
if (_leftApp) {
|
||||
_leftApp = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var mode = options.mode || 'new';
|
||||
_apnItem = options.item || {};
|
||||
_apnType = options.type || 'default';
|
||||
_serviceId = options.serviceId;
|
||||
|
||||
switch (mode) {
|
||||
case 'new':
|
||||
_elements.deteleItem.hidden = true;
|
||||
var defaultApnItem = {
|
||||
apn: {
|
||||
types: [_apnType]
|
||||
}
|
||||
};
|
||||
_editorSession = _apnEditor.createApn(_serviceId, defaultApnItem);
|
||||
break;
|
||||
case 'edit':
|
||||
_elements.deteleItem.hidden = false;
|
||||
_editorSession = _apnEditor.editApn(_serviceId, _apnItem);
|
||||
break;
|
||||
}
|
||||
_elements.panel.scrollTop = 0;
|
||||
},
|
||||
onHide: function ae_onHide() {
|
||||
_leftApp = document.hidden;
|
||||
if (!_leftApp && _editorSession) {
|
||||
_editorSession.cancel();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
67
gaia/apps/settings/js/panels/apn_list/apn_template_factory.js
Executable file
67
gaia/apps/settings/js/panels/apn_list/apn_template_factory.js
Executable file
|
|
@ -0,0 +1,67 @@
|
|||
/**
|
||||
* The template function for generating an UI element for an APN item.
|
||||
*
|
||||
* @module apn_list/apn_template_factory
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
function apnTemplate(apnType, onItemclick, onRadioClick, item) {
|
||||
var rawApn = item.apn;
|
||||
|
||||
// create an <gaia-radio> element
|
||||
var radio = document.createElement('gaia-radio');
|
||||
radio.className = 'split';
|
||||
radio.checked = item.active;
|
||||
radio.name = apnType;
|
||||
|
||||
// include the radio button element in a list item
|
||||
var radioLabel = document.createElement('label');
|
||||
|
||||
radioLabel.classList.add('name');
|
||||
if (!rawApn.carrier || rawApn.carrier === '_custom_') {
|
||||
radioLabel.textContent = rawApn.apn;
|
||||
} else {
|
||||
radioLabel.textContent = rawApn.carrier;
|
||||
}
|
||||
radio.appendChild(radioLabel);
|
||||
|
||||
var li = document.createElement('li');
|
||||
li.classList.add('apn-item');
|
||||
li.appendChild(radio);
|
||||
|
||||
// Keep track if we clicked the label or not.
|
||||
// This is needed because you can't stop the event from propagating to the
|
||||
// shadow DOM from the label currently.
|
||||
var labelClicked = false;
|
||||
|
||||
// Register the handler for click event.
|
||||
// Must listen to click, which matches the event in <gaia-radio>.
|
||||
// Use event capturing because we are overriding the default functionality.
|
||||
if (typeof onRadioClick === 'function') {
|
||||
radio.addEventListener('click', event => {
|
||||
if (!labelClicked && !radio.checked) {
|
||||
onRadioClick(item, radio);
|
||||
}
|
||||
labelClicked = false;
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}, true);
|
||||
}
|
||||
|
||||
// Register the handler for a mouseup event.
|
||||
// Use mouseup so that it wouldn't be stopped by stopPropagation.
|
||||
if (typeof onItemclick === 'function') {
|
||||
radioLabel.addEventListener('mouseup', () => {
|
||||
labelClicked = true;
|
||||
onItemclick(item);
|
||||
});
|
||||
}
|
||||
|
||||
return li;
|
||||
}
|
||||
|
||||
return function ctor_apnTemplate(apnType, onItemclick, onRadioClick) {
|
||||
return apnTemplate.bind(null, apnType, onItemclick, onRadioClick);
|
||||
};
|
||||
});
|
||||
151
gaia/apps/settings/js/panels/apn_list/panel.js
Executable file
151
gaia/apps/settings/js/panels/apn_list/panel.js
Executable file
|
|
@ -0,0 +1,151 @@
|
|||
/**
|
||||
* The apn list panel
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var SettingsService = require('modules/settings_service');
|
||||
var SettingsPanel = require('modules/settings_panel');
|
||||
var SettingsCache = require('modules/settings_cache');
|
||||
var ListView = require('modules/mvvm/list_view');
|
||||
var ApnSettingsManager = require('modules/apn/apn_settings_manager');
|
||||
var ApnTemplateFactory = require('panels/apn_list/apn_template_factory');
|
||||
|
||||
var HEADER_L10N_MAP = {
|
||||
'default': 'dataSettings-header',
|
||||
'mms': 'messageSettings-header',
|
||||
'ims': 'imsSettings-header2',
|
||||
'supl': 'suplSettings-header',
|
||||
'dun': 'dunSettings-header',
|
||||
'fota': 'fotaSettings-header'
|
||||
};
|
||||
|
||||
var _getDialogService = function() {
|
||||
return new Promise(function(resolve) {
|
||||
require(['modules/dialog_service'], resolve);
|
||||
});
|
||||
};
|
||||
|
||||
var _showChangeApnWarning = function() {
|
||||
return _getDialogService().then((DialogService) => {
|
||||
return DialogService.confirm('change-apn-warning-message1', {
|
||||
title: 'apnSettings',
|
||||
submitButton: {
|
||||
id: 'yes',
|
||||
style: 'recommend'
|
||||
},
|
||||
cancelButton: 'cancel'
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return function ctor_apn_settings_panel() {
|
||||
var _rootElement;
|
||||
var _mainHeader;
|
||||
var _header;
|
||||
var _apnListViewRoot;
|
||||
var _apnListView;
|
||||
var _addApnBtn;
|
||||
|
||||
var _role;
|
||||
|
||||
var _apnType = 'default';
|
||||
var _serviceId = 0;
|
||||
|
||||
var _onApnItemClick = function(serviceId, apnType, apnItem) {
|
||||
SettingsService.navigate('apn-editor',
|
||||
{
|
||||
mode: 'edit',
|
||||
serviceId: serviceId,
|
||||
type: apnType,
|
||||
item: apnItem
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
var _onRadioClick = function(serviceId, apnType, apnItem, radio) {
|
||||
var setActive = function() {
|
||||
apnItem.active = true;
|
||||
radio.checked = true;
|
||||
ApnSettingsManager.setActiveApnId(serviceId, apnType, apnItem.id);
|
||||
};
|
||||
|
||||
SettingsCache.getSettings(function(results) {
|
||||
if (results['ril.data.roaming_enabled'] === true) {
|
||||
// Only display the warning when roaming is enabled.
|
||||
_showChangeApnWarning().then((result) => {
|
||||
if (result.type === 'submit') {
|
||||
setActive();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// XXX: We need to make this to the next tick to the UI gets updated
|
||||
// because we've prevented the default behavior in the handler.
|
||||
setTimeout(function() {
|
||||
setActive();
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var _onAddApnBtnClick = function(serviceId, apnType) {
|
||||
SettingsService.navigate('apn-editor',
|
||||
{
|
||||
mode: 'new',
|
||||
serviceId: serviceId,
|
||||
type: apnType
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
var _onBackBtnClick = function() {
|
||||
if (_role === 'activity') {
|
||||
_role = null;
|
||||
Settings.finishActivityRequest();
|
||||
} else {
|
||||
SettingsService.navigate('apn-settings');
|
||||
}
|
||||
};
|
||||
|
||||
return SettingsPanel({
|
||||
onInit: function bp_onInit(rootElement) {
|
||||
_rootElement = rootElement;
|
||||
_mainHeader = rootElement.querySelector('gaia-header');
|
||||
_header = _mainHeader.querySelector('h1');
|
||||
_apnListViewRoot = rootElement.querySelector('.apn-list');
|
||||
_addApnBtn = rootElement.querySelector('button.add-apn');
|
||||
|
||||
_mainHeader.addEventListener('action', _onBackBtnClick);
|
||||
},
|
||||
onBeforeShow: function bp_onBeforeShow(rootElement, options) {
|
||||
_role = options.role || _role;
|
||||
|
||||
// When back from apn editor, there is no type and serviceId specified
|
||||
// so that we use the original type and service id.
|
||||
_apnType = options.type || _apnType;
|
||||
_serviceId = (options.serviceId === undefined) ?
|
||||
_serviceId : options.serviceId;
|
||||
|
||||
_header.setAttribute('data-l10n-id', HEADER_L10N_MAP[_apnType]);
|
||||
|
||||
var apnTemplate =
|
||||
ApnTemplateFactory(_apnType,
|
||||
_onApnItemClick.bind(null, _serviceId, _apnType),
|
||||
_onRadioClick.bind(null, _serviceId, _apnType));
|
||||
|
||||
_addApnBtn.onclick = _onAddApnBtnClick.bind(null, _serviceId, _apnType);
|
||||
|
||||
ApnSettingsManager.queryApns(_serviceId, _apnType)
|
||||
.then(function(apnItems) {
|
||||
_apnListView = ListView(_apnListViewRoot, apnItems, apnTemplate);
|
||||
});
|
||||
},
|
||||
onHide: function bp_onBeforeHide() {
|
||||
if (_apnListView) {
|
||||
_apnListView.destroy();
|
||||
_apnListView = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
71
gaia/apps/settings/js/panels/apn_settings/panel.js
Executable file
71
gaia/apps/settings/js/panels/apn_settings/panel.js
Executable file
|
|
@ -0,0 +1,71 @@
|
|||
/* global DsdsSettings */
|
||||
/**
|
||||
* The apn settings panel
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var SettingsService = require('modules/settings_service');
|
||||
var SettingsPanel = require('modules/settings_panel');
|
||||
var ApnSettingsManager = require('modules/apn/apn_settings_manager');
|
||||
var Toaster = require('shared/toaster');
|
||||
var DialogService = require('modules/dialog_service');
|
||||
|
||||
return function ctor_apn_settings_panel() {
|
||||
var _serviceId = 0;
|
||||
|
||||
var _rootElement;
|
||||
|
||||
var _browseApnItems = function(apnType) {
|
||||
SettingsService.navigate(
|
||||
'apn-list',
|
||||
{
|
||||
type: apnType,
|
||||
serviceId: _serviceId
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
var _resetApn = function() {
|
||||
DialogService.confirm('reset-apn-warning-message', {
|
||||
title: 'reset-apn-warning-title',
|
||||
submitButton: { id: 'reset', style: 'danger' },
|
||||
cancelButton: 'cancel'
|
||||
}).then((result) => {
|
||||
if (result.type === 'submit') {
|
||||
ApnSettingsManager.restore(_serviceId).then(function() {
|
||||
var toast = {
|
||||
messageL10nId: 'apnSettings-reset',
|
||||
latency: 3000,
|
||||
useTransition: true
|
||||
};
|
||||
Toaster.showToast(toast);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return SettingsPanel({
|
||||
onInit: function asp_onInit(rootElement) {
|
||||
_rootElement = rootElement;
|
||||
var apnBtns = rootElement.querySelectorAll('a[data-apn-type]');
|
||||
var resetBtn = rootElement.querySelector('button.reset-apn');
|
||||
var header = rootElement.querySelector('gaia-header');
|
||||
|
||||
if (DsdsSettings.getNumberOfIccSlots() > 1) {
|
||||
header.dataset.href = '#carrier-detail';
|
||||
} else {
|
||||
header.dataset.href = '#carrier';
|
||||
}
|
||||
|
||||
Array.prototype.forEach.call(apnBtns, function(btn) {
|
||||
btn.onclick = _browseApnItems.bind(null, btn.dataset.apnType);
|
||||
});
|
||||
resetBtn.onclick = _resetApn;
|
||||
},
|
||||
onBeforeShow: function asp_onBeforeShow() {
|
||||
_serviceId = DsdsSettings.getIccCardIndexForCellAndDataSettings();
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
218
gaia/apps/settings/js/panels/app_permissions_detail/app_permissions_detail.js
Executable file
218
gaia/apps/settings/js/panels/app_permissions_detail/app_permissions_detail.js
Executable file
|
|
@ -0,0 +1,218 @@
|
|||
/**
|
||||
* Handle app_permissions_detail panel's functionality.
|
||||
*/
|
||||
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var ManifestHelper = require('shared/manifest_helper');
|
||||
var SettingsService = require('modules/settings_service');
|
||||
var mozApps = require('modules/navigator/mozApps');
|
||||
var mozPerms = require('modules/navigator/mozPermissionSettings');
|
||||
|
||||
var PermissionsDetail = function pd() {
|
||||
this._elements = null;
|
||||
this._permissionsTable = null;
|
||||
this._app = null;
|
||||
};
|
||||
|
||||
PermissionsDetail.prototype = {
|
||||
/**
|
||||
* initialization
|
||||
*/
|
||||
init: function pd_init(elements, permissionsTable) {
|
||||
this._elements = elements;
|
||||
this._permissionsTable = permissionsTable;
|
||||
|
||||
// only go back to previous when application is uninstalled
|
||||
window.addEventListener('applicationuninstall', this.back);
|
||||
},
|
||||
|
||||
/**
|
||||
* Back to app_permissions_list panel.
|
||||
*/
|
||||
back: function pd_back() {
|
||||
SettingsService.navigate('appPermissions');
|
||||
},
|
||||
|
||||
/**
|
||||
* Show app detail page.
|
||||
*/
|
||||
showAppDetails: function pd_show_app_details(app, verbose) {
|
||||
this._isValidPerm = verbose ? this._isValidVerbosePerm
|
||||
: this._isExplicitPerm;
|
||||
this._app = app;
|
||||
var table = this._permissionsTable;
|
||||
var elements = this._elements;
|
||||
var manifest = new ManifestHelper(app.manifest ?
|
||||
app.manifest : app.updateManifest);
|
||||
var developer = manifest.developer;
|
||||
elements.detailTitle.textContent = manifest.displayName;
|
||||
elements.uninstallButton.disabled = !app.removable;
|
||||
if (!developer || !('name' in developer)) {
|
||||
elements.developerInfos.hidden = true;
|
||||
elements.developerHeader.hidden = true;
|
||||
} else {
|
||||
elements.developerName.textContent = developer.name;
|
||||
elements.developerInfos.hidden = false;
|
||||
elements.developerHeader.hidden = false;
|
||||
if (!developer.url) {
|
||||
delete elements.developerLink.href;
|
||||
elements.developerUrl.hidden = true;
|
||||
} else {
|
||||
elements.developerUrl.hidden = false;
|
||||
elements.developerLink.href = developer.url;
|
||||
elements.developerUrl.textContent = developer.url;
|
||||
}
|
||||
}
|
||||
if (!mozPerms) {
|
||||
elements.list.hidden = true;
|
||||
return;
|
||||
} else {
|
||||
elements.list.hidden = false;
|
||||
elements.list.innerHTML = '';
|
||||
}
|
||||
|
||||
table.plainPermissions.forEach(function(perm) {
|
||||
var value = mozPerms.get(perm, app.manifestURL,
|
||||
app.origin, false);
|
||||
if (this._isValidPerm(app, perm, value)) {
|
||||
this._insertPermissionSelect(perm, value);
|
||||
}
|
||||
}, this);
|
||||
|
||||
table.composedPermissions.forEach(function appIterator(perm) {
|
||||
var value = null;
|
||||
var display = table.accessModes.some(function modeIterator(mode) {
|
||||
var composedPerm = perm + '-' + mode;
|
||||
value = mozPerms.get(composedPerm, app.manifestURL, app.origin,
|
||||
false);
|
||||
if (this._isValidPerm(app, composedPerm, value)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, this);
|
||||
|
||||
if (display) {
|
||||
this._insertPermissionSelect(perm, value);
|
||||
}
|
||||
}, this);
|
||||
|
||||
elements.header.hidden = !elements.list.children.length;
|
||||
},
|
||||
|
||||
_isExplicitPerm: function pd_shouldDisplayPerm(app, perm, value) {
|
||||
var isExplicit = mozPerms.isExplicit(perm, app.manifestURL,
|
||||
app.origin, false);
|
||||
return (isExplicit && value !== 'unknown');
|
||||
},
|
||||
|
||||
_isValidVerbosePerm: function pd_displayPermVerbose(app, perm, value) {
|
||||
if (app.manifest.type !== 'certified') {
|
||||
return (value !== 'unknown');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Detect event from user's selection of the permission.
|
||||
*/
|
||||
selectValueChanged: function pd_select_value_changed(evt) {
|
||||
var select = evt.target;
|
||||
select.setAttribute('value', select.value);
|
||||
this._changePermission(select.dataset.perm, select.value);
|
||||
},
|
||||
|
||||
/**
|
||||
* Change permission of the app.
|
||||
*/
|
||||
_changePermission: function pd__change_permission(perm, value) {
|
||||
if (!mozPerms) {
|
||||
return;
|
||||
}
|
||||
|
||||
var table = this._permissionsTable;
|
||||
// We edit the composed permission for all the access modes
|
||||
if (table.composedPermissions.indexOf(perm) !== -1) {
|
||||
table.accessModes.forEach(function modeIterator(mode) {
|
||||
var composedPerm = perm + '-' + mode;
|
||||
|
||||
try {
|
||||
mozPerms.set(composedPerm, value, this._app.manifestURL,
|
||||
this._app.origin, false);
|
||||
} catch (e) {
|
||||
console.warn('Failed to set the ' + composedPerm + 'permission.');
|
||||
}
|
||||
}, this);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
mozPerms.set(perm, value, this._app.manifestURL,
|
||||
this._app.origin, false);
|
||||
} catch (e) {
|
||||
console.warn('Failed to set the ' + perm + 'permission.');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Show available selection option of permission in app detail dialog.
|
||||
*/
|
||||
_insertPermissionSelect:
|
||||
function pd__insert_permission_select(perm, value) {
|
||||
var item = document.createElement('li');
|
||||
var content = document.createElement('p');
|
||||
var contentL10nId = 'perm-' + perm.replace(':', '-');
|
||||
content.setAttribute('data-l10n-id', contentL10nId);
|
||||
|
||||
var fakeSelect = document.createElement('span');
|
||||
fakeSelect.classList.add('button', 'icon', 'icon-dialog');
|
||||
|
||||
var select = document.createElement('select');
|
||||
select.dataset.perm = perm;
|
||||
|
||||
var askOpt = document.createElement('option');
|
||||
askOpt.value = 'prompt';
|
||||
askOpt.setAttribute('data-l10n-id', 'ask');
|
||||
select.add(askOpt);
|
||||
|
||||
var allowOpt = document.createElement('option');
|
||||
allowOpt.value = 'allow';
|
||||
allowOpt.setAttribute('data-l10n-id', 'allow2');
|
||||
select.add(allowOpt);
|
||||
|
||||
var denyOpt = document.createElement('option');
|
||||
denyOpt.value = 'deny';
|
||||
denyOpt.setAttribute('data-l10n-id', 'block');
|
||||
select.add(denyOpt);
|
||||
|
||||
var opt = select.querySelector('[value="' + value + '"]');
|
||||
opt.setAttribute('selected', true);
|
||||
|
||||
select.value = select.options[select.selectedIndex].textContent;
|
||||
select.setAttribute('value', value);
|
||||
select.onchange = this.selectValueChanged.bind(this);
|
||||
|
||||
item.onclick = function focusSelect() {
|
||||
select.focus();
|
||||
};
|
||||
|
||||
fakeSelect.appendChild(select);
|
||||
item.appendChild(content);
|
||||
item.appendChild(fakeSelect);
|
||||
this._elements.list.appendChild(item);
|
||||
},
|
||||
|
||||
/**
|
||||
* Uninstall the choosed app.
|
||||
*/
|
||||
uninstall: function pd_uninstall() {
|
||||
mozApps.mgmt.uninstall(this._app).onsuccess = () => {
|
||||
this.back();
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return function ctor_app_permissions_detail() {
|
||||
return new PermissionsDetail();
|
||||
};
|
||||
});
|
||||
58
gaia/apps/settings/js/panels/app_permissions_detail/panel.js
Executable file
58
gaia/apps/settings/js/panels/app_permissions_detail/panel.js
Executable file
|
|
@ -0,0 +1,58 @@
|
|||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var SettingsListener = require('shared/settings_listener');
|
||||
var SettingsPanel = require('modules/settings_panel');
|
||||
var PermissionDetail =
|
||||
require('panels/app_permissions_detail/app_permissions_detail');
|
||||
|
||||
return function ctor_app_permissions_detail_panel() {
|
||||
var elements = {};
|
||||
var permissionDetailModule = PermissionDetail();
|
||||
var uninstall =
|
||||
permissionDetailModule.uninstall.bind(permissionDetailModule);
|
||||
var back = permissionDetailModule.back.bind(permissionDetailModule);
|
||||
|
||||
function bindEvents(doms) {
|
||||
doms.uninstallButton.addEventListener('click', uninstall);
|
||||
doms.panelHeader.addEventListener('action', back);
|
||||
}
|
||||
|
||||
function unbindEvents(doms) {
|
||||
doms.uninstallButton.removeEventListener('click', uninstall);
|
||||
doms.panelHeader.removeEventListener('action', back);
|
||||
}
|
||||
|
||||
return SettingsPanel({
|
||||
onInit: function(panel, options) {
|
||||
this._verbose = null;
|
||||
elements = {
|
||||
panelHeader: panel.querySelector('gaia-header'),
|
||||
uninstallButton: panel.querySelector('.uninstall-app > button'),
|
||||
list: panel.querySelector('.permissionsListHeader + ul'),
|
||||
header: panel.querySelector('.permissionsListHeader'),
|
||||
developerLink: panel.querySelector('.developer-infos > a'),
|
||||
developerName: panel.querySelector('.developer-infos > a > span'),
|
||||
developerUrl: panel.querySelector('.developer-infos > a > small'),
|
||||
developerInfos: panel.querySelector('.developer-infos'),
|
||||
developerHeader: panel.querySelector('.developer-header'),
|
||||
detailTitle: panel.querySelector('.detail-title')
|
||||
};
|
||||
SettingsListener.observe('debug.verbose_app_permissions', false,
|
||||
function(enabled) {
|
||||
this._verbose = enabled;
|
||||
}.bind(this));
|
||||
permissionDetailModule.init(elements, options.permissionsTable);
|
||||
},
|
||||
|
||||
onBeforeShow: function(panel, options) {
|
||||
permissionDetailModule.showAppDetails(options.app, this._verbose);
|
||||
bindEvents(elements);
|
||||
},
|
||||
|
||||
onBeforeHide: function() {
|
||||
unbindEvents(elements);
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
363
gaia/apps/settings/js/panels/app_permissions_list/app_permissions_list.js
Executable file
363
gaia/apps/settings/js/panels/app_permissions_list/app_permissions_list.js
Executable file
|
|
@ -0,0 +1,363 @@
|
|||
/**
|
||||
* Handle app_permissions_list panel's functionality.
|
||||
*
|
||||
* @module PermissionsList
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var SettingsService = require('modules/settings_service');
|
||||
var ManifestHelper = require('shared/manifest_helper');
|
||||
var AppsCache = require('modules/apps_cache');
|
||||
var mozApps = require('modules/navigator/mozApps');
|
||||
var mozPerms = require('modules/navigator/mozPermissionSettings');
|
||||
|
||||
var PermissionsList = function pl() {
|
||||
this._listRoot = null;
|
||||
this._permissionsTable = null;
|
||||
this._permissionTableHaveProcessed = false;
|
||||
this._apps = null;
|
||||
this._enabled = false;
|
||||
};
|
||||
|
||||
PermissionsList.prototype = {
|
||||
/**
|
||||
* initialization
|
||||
*
|
||||
* @memberOf PermissionsList
|
||||
* @param {HTMLElement} listRoot
|
||||
* @access public
|
||||
*/
|
||||
init: function pl_init(listRoot) {
|
||||
this._listRoot = listRoot;
|
||||
|
||||
this._boundOnAppChoose = this._onAppChoose.bind(this);
|
||||
this._boundOnApplicationInstall = this._onApplicationInstall.bind(this);
|
||||
this._boundOnApplicationUninstall =
|
||||
this._onApplicationUninstall.bind(this);
|
||||
},
|
||||
|
||||
set enabled(value) {
|
||||
if (value !== this._enabled) {
|
||||
this._enabled = value;
|
||||
if (this._enabled) {
|
||||
this._bindEvents();
|
||||
} else {
|
||||
this._unbindEvents();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_bindEvents: function pl__bindEvents() {
|
||||
this._listRoot.addEventListener('click', this._boundOnAppChoose);
|
||||
AppsCache.addEventListener('oninstall', this._boundOnApplicationInstall);
|
||||
AppsCache.addEventListener('onuninstall',
|
||||
this._boundOnApplicationUninstall);
|
||||
},
|
||||
|
||||
_unbindEvents: function pl__unbindEvents() {
|
||||
this._listRoot.removeEventListener('click', this._boundOnAppChoose);
|
||||
AppsCache.removeEventListener('oninstall',
|
||||
this._boundOnApplicationInstall);
|
||||
AppsCache.removeEventListener('onuninstall',
|
||||
this._boundOnApplicationUninstall);
|
||||
},
|
||||
|
||||
/**
|
||||
* Set /resources/permissions_table.json.
|
||||
*
|
||||
* @memberOf PermissionsList
|
||||
* @param {Object} permissionTable
|
||||
* @access public
|
||||
*/
|
||||
setPermissionsTable: function pl_setPermissionsTable(permissionTable) {
|
||||
this._permissionTable = permissionTable;
|
||||
},
|
||||
|
||||
/**
|
||||
* Refresh the app list when we enter into panel.
|
||||
*
|
||||
* @memberOf PermissionsList
|
||||
* @access public
|
||||
* @return {Promise}
|
||||
*/
|
||||
refresh: function pl_refresh() {
|
||||
var self = this;
|
||||
this._apps = [];
|
||||
if (this._permissionTableHaveProcessed) {
|
||||
return this.loadApps();
|
||||
} else {
|
||||
return mozApps.getSelf().then(function(app) {
|
||||
return self._initExplicitPermissionsTable(app);
|
||||
}).then(function() {
|
||||
return self.loadApps();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Go to app_permissions_detail panel when user select an app.
|
||||
*
|
||||
* @memberOf PermissionsList
|
||||
* @param {Event} evt
|
||||
* @access public
|
||||
*/
|
||||
_onAppChoose: function pl__onAppChoose(evt) {
|
||||
if (evt.target.dataset && evt.target.dataset.appIndex) {
|
||||
SettingsService.navigate('appPermissions-details', {
|
||||
app: this._apps[evt.target.dataset.appIndex],
|
||||
permissionsTable: this._permissionTable
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* When new application is installed, we push the app to list, sort them and
|
||||
* rerender the app list.
|
||||
*
|
||||
* @memberOf PermissionsList
|
||||
* @param {Event} evt
|
||||
* @access public
|
||||
*/
|
||||
_onApplicationInstall: function pl__onApplicationInstall(evt) {
|
||||
var app = evt.application;
|
||||
this._apps.push(app);
|
||||
this._sortApps();
|
||||
this.renderList();
|
||||
},
|
||||
|
||||
/**
|
||||
* When application is uninstalled, we remove it from list and rerender the
|
||||
* app list.
|
||||
*
|
||||
* @memberOf PermissionsList
|
||||
* @param {Event} evt
|
||||
* @access public
|
||||
*/
|
||||
_onApplicationUninstall: function pl__onApplicationUninstall(evt) {
|
||||
var app;
|
||||
var appIndex;
|
||||
this._apps.some(function findApp(anApp, index) {
|
||||
if (anApp.origin === evt.application.origin) {
|
||||
app = anApp;
|
||||
appIndex = index;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (!app) {
|
||||
return;
|
||||
}
|
||||
SettingsService.navigate('appPermissions');
|
||||
this._apps.splice(appIndex, 1);
|
||||
this.renderList();
|
||||
},
|
||||
|
||||
/**
|
||||
* Sort the applist by the name of its manifest.
|
||||
*
|
||||
* @memberOf PermissionsList
|
||||
* @access private
|
||||
*/
|
||||
_sortApps: function pl__sortApps() {
|
||||
this._apps.sort(function alphabeticalSort(app, otherApp) {
|
||||
var manifest = new ManifestHelper(app.manifest ?
|
||||
app.manifest : app.updateManifest);
|
||||
var otherManifest = new ManifestHelper(otherApp.manifest ?
|
||||
otherApp.manifest : otherApp.updateManifest);
|
||||
return manifest.name > otherManifest.name;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Genrate UI template of app item.
|
||||
*
|
||||
* @memberOf PermissionsList
|
||||
* @access private
|
||||
* @param {Object} itemData
|
||||
* @return {HTMLDivElement}
|
||||
*/
|
||||
_genAppItemTemplate: function pl__genAppItemTemplate(itemData) {
|
||||
var icon = document.createElement('img');
|
||||
var item = document.createElement('li');
|
||||
var link = document.createElement('a');
|
||||
var span = document.createElement('span');
|
||||
span.textContent = itemData.name;
|
||||
span.dir = 'auto';
|
||||
icon.src = itemData.iconSrc;
|
||||
link.dataset.appIndex = itemData.index;
|
||||
link.href = '#';
|
||||
link.classList.add('menu-item');
|
||||
link.appendChild(icon);
|
||||
link.appendChild(span);
|
||||
item.appendChild(link);
|
||||
return item;
|
||||
},
|
||||
|
||||
/**
|
||||
* Genrate UI template of app item.
|
||||
*
|
||||
* @memberOf PermissionsList
|
||||
* @access public
|
||||
*/
|
||||
renderList: function pl_renderList() {
|
||||
this._listRoot.innerHTML = '';
|
||||
var listFragment = document.createDocumentFragment();
|
||||
this._apps.forEach(function appIterator(app, index) {
|
||||
var manifest = new ManifestHelper(app.manifest ?
|
||||
app.manifest : app.updateManifest);
|
||||
var li = this._genAppItemTemplate({
|
||||
name: manifest.displayName,
|
||||
index: index,
|
||||
iconSrc: this._getBestIconURL(app, manifest.icons)
|
||||
});
|
||||
listFragment.appendChild(li);
|
||||
}.bind(this));
|
||||
this._listRoot.appendChild(listFragment);
|
||||
},
|
||||
|
||||
/**
|
||||
* Genrate explicitCertifiedPermissions table from plainPermissions table
|
||||
* table and "composedPermissions + accessModes" table.
|
||||
*
|
||||
* @memberOf PermissionsList
|
||||
* @param {Event} evt
|
||||
* @access private
|
||||
*/
|
||||
_initExplicitPermissionsTable:
|
||||
function pl__initExplicitPermissionsTable(app) {
|
||||
this._currentApp = app;
|
||||
|
||||
var table = this._permissionTable;
|
||||
table.explicitCertifiedPermissions = [];
|
||||
table.plainPermissions.forEach(function plainPermIterator(perm) {
|
||||
if (this._isExplicitPerm(perm)) {
|
||||
table.explicitCertifiedPermissions.push({
|
||||
explicitPermission: perm,
|
||||
permission: perm
|
||||
});
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
table.composedPermissions.forEach(function permIterator(perm) {
|
||||
table.accessModes.forEach(function modeIterator(mode) {
|
||||
var composedPerm = perm + '-' + mode;
|
||||
if (this._isExplicitPerm(composedPerm)) {
|
||||
table.explicitCertifiedPermissions.push({
|
||||
explicitPermission: composedPerm,
|
||||
permission: perm
|
||||
});
|
||||
}
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
this._permissionTableHaveProcessed = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Identify the permission whether is explict or not.
|
||||
*
|
||||
* @memberOf PermissionsList
|
||||
* @access private
|
||||
* @return {Bool}
|
||||
*/
|
||||
_isExplicitPerm: function pl_isExplicitPerm(perm) {
|
||||
return mozPerms.isExplicit(perm, this._currentApp.manifestURL,
|
||||
this._currentApp.origin, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filter explicit apps from moz apps, sort them, and render to screen.
|
||||
*
|
||||
* @memberOf PermissionsList
|
||||
* @access public
|
||||
* @return {Promise}
|
||||
*/
|
||||
loadApps: function pl_loadApps() {
|
||||
var self = this;
|
||||
return AppsCache.apps().then(function(apps) {
|
||||
self._loadApps(apps);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Iterate internal apps and render them on UI.
|
||||
*
|
||||
* @memberOf PermissionsList
|
||||
* @param {Object[]} apps
|
||||
* @access private
|
||||
*/
|
||||
_loadApps: function pl__loadApps(apps) {
|
||||
var table = this._permissionTable;
|
||||
apps.forEach(function(app) {
|
||||
var manifest = app.manifest ? app.manifest : app.updateManifest;
|
||||
if (manifest.type != 'certified') {
|
||||
this._apps.push(app);
|
||||
return;
|
||||
}
|
||||
|
||||
var display = table.explicitCertifiedPermissions
|
||||
.some(function iterator(perm) {
|
||||
var permInfo = mozPerms.get(perm.explicitPermission,
|
||||
app.manifestURL, app.origin, false);
|
||||
return permInfo != 'unknown';
|
||||
}.bind(this));
|
||||
|
||||
if (display) {
|
||||
this._apps.push(app);
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
this._sortApps();
|
||||
this.renderList();
|
||||
},
|
||||
|
||||
/**
|
||||
* Get icon URL.
|
||||
*
|
||||
* @memberOf PermissionsList
|
||||
* @param {Object} app
|
||||
* @param {Object} icons
|
||||
* @access private
|
||||
*/
|
||||
_getBestIconURL: function pl__getBestIconURL(app, icons) {
|
||||
if (!icons || !Object.keys(icons).length) {
|
||||
return '../style/images/default.png';
|
||||
}
|
||||
|
||||
// The preferred size is 30 by the default. If we use HDPI device, we may
|
||||
// use the image larger than 30 * 1.5 = 45 pixels.
|
||||
var preferredIconSize = 30 * (window.devicePixelRatio || 1);
|
||||
var preferredSize = Number.MAX_VALUE;
|
||||
var max = 0;
|
||||
|
||||
for (var size in icons) {
|
||||
size = parseInt(size, 10);
|
||||
if (size > max) {
|
||||
max = size;
|
||||
}
|
||||
|
||||
if (size >= preferredIconSize && size < preferredSize) {
|
||||
preferredSize = size;
|
||||
}
|
||||
}
|
||||
// If there is an icon matching the preferred size, we return the result,
|
||||
// if there isn't, we will return the maximum available size.
|
||||
if (preferredSize === Number.MAX_VALUE) {
|
||||
preferredSize = max;
|
||||
}
|
||||
|
||||
var url = icons[preferredSize];
|
||||
|
||||
if (url) {
|
||||
return !(/^(http|https|data):/.test(url)) ? app.origin + url : url;
|
||||
} else {
|
||||
return '../style/images/default.png';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return function ctor_permissions_list() {
|
||||
return new PermissionsList();
|
||||
};
|
||||
});
|
||||
43
gaia/apps/settings/js/panels/app_permissions_list/panel.js
Executable file
43
gaia/apps/settings/js/panels/app_permissions_list/panel.js
Executable file
|
|
@ -0,0 +1,43 @@
|
|||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var SettingsPanel = require('modules/settings_panel');
|
||||
var PermissionList =
|
||||
require('panels/app_permissions_list/app_permissions_list');
|
||||
var LazyLoader = require('shared/lazy_loader');
|
||||
|
||||
return function ctor_app_permissions_list_panel() {
|
||||
// We use this flag to identify permissions_table.json has been loaded or
|
||||
// not.
|
||||
var permissionsTableHasBeenLoaded = false;
|
||||
var elements = {};
|
||||
var permissionListModule = PermissionList();
|
||||
|
||||
return SettingsPanel({
|
||||
onInit: function(panel) {
|
||||
elements = {
|
||||
list: panel.querySelector('.app-list')
|
||||
};
|
||||
permissionListModule.init(elements.list);
|
||||
},
|
||||
|
||||
onBeforeShow: function() {
|
||||
if (permissionsTableHasBeenLoaded) {
|
||||
permissionListModule.refresh();
|
||||
} else {
|
||||
LazyLoader.getJSON('/resources/permissions_table.json')
|
||||
.then(function(data) {
|
||||
permissionsTableHasBeenLoaded = true;
|
||||
permissionListModule.setPermissionsTable(data);
|
||||
permissionListModule.refresh();
|
||||
});
|
||||
}
|
||||
permissionListModule.enabled = true;
|
||||
},
|
||||
|
||||
onBeforeHide: function() {
|
||||
permissionListModule.enabled = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
68
gaia/apps/settings/js/panels/app_storage/panel.js
Executable file
68
gaia/apps/settings/js/panels/app_storage/panel.js
Executable file
|
|
@ -0,0 +1,68 @@
|
|||
/**
|
||||
* Used to show Storage/App Storage panel
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var SettingsPanel = require('modules/settings_panel');
|
||||
var AppStorage = require('modules/app_storage');
|
||||
var StorageHelper = require('modules/storage_helper');
|
||||
|
||||
return function ctor_app_storage_panel() {
|
||||
var _spaceBarElement = null;
|
||||
var _totalSpaceText = null;
|
||||
var _usedSpaceText = null;
|
||||
var _freeSpaceText = null;
|
||||
|
||||
var _updateUsePercentage = function() {
|
||||
_spaceBarElement.value = AppStorage.usedPercentage;
|
||||
};
|
||||
|
||||
var _refreshText = function(element, size) {
|
||||
StorageHelper.showFormatedSize(element,
|
||||
'storageSize', size);
|
||||
};
|
||||
var _updateTotalSize = function() {
|
||||
_refreshText(_totalSpaceText, AppStorage.totalSize);
|
||||
};
|
||||
var _updateUsedSize = function() {
|
||||
_refreshText(_usedSpaceText, AppStorage.usedSize);
|
||||
};
|
||||
var _updateFreeSize = function() {
|
||||
_refreshText(_freeSpaceText, AppStorage.freeSize);
|
||||
};
|
||||
|
||||
var _updateElements = function() {
|
||||
_updateUsePercentage();
|
||||
_updateTotalSize();
|
||||
_updateUsedSize();
|
||||
_updateFreeSize();
|
||||
};
|
||||
|
||||
return SettingsPanel({
|
||||
onInit: function(panel) {
|
||||
_spaceBarElement = panel.querySelector('.apps-space-bar');
|
||||
_totalSpaceText = panel.querySelector('.apps-total-space');
|
||||
_usedSpaceText = panel.querySelector('.apps-used-space');
|
||||
_freeSpaceText = panel.querySelector('.apps-free-space');
|
||||
},
|
||||
|
||||
onBeforeShow: function() {
|
||||
AppStorage.observe('usedPercentage', _updateUsePercentage);
|
||||
AppStorage.observe('totalSize', _updateTotalSize);
|
||||
AppStorage.observe('usedSize', _updateUsedSize);
|
||||
AppStorage.observe('freeSize', _updateFreeSize);
|
||||
|
||||
_updateElements();
|
||||
AppStorage.updateInfo();
|
||||
},
|
||||
|
||||
onHide: function() {
|
||||
AppStorage.unobserve('usedPercentage', _updateUsePercentage);
|
||||
AppStorage.unobserve('totalSize', _updateTotalSize);
|
||||
AppStorage.unobserve('usedSize', _updateUsedSize);
|
||||
AppStorage.unobserve('freeSize', _updateFreeSize);
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
73
gaia/apps/settings/js/panels/battery/panel.js
Executable file
73
gaia/apps/settings/js/panels/battery/panel.js
Executable file
|
|
@ -0,0 +1,73 @@
|
|||
/**
|
||||
* The battery panel displays battery information provided by Battery.
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var SettingsPanel = require('modules/settings_panel');
|
||||
var Battery = require('modules/battery');
|
||||
|
||||
return function ctor_battery_panel() {
|
||||
var _batteryLevelText = null;
|
||||
var _batteryTimeText = null;
|
||||
var _getBatteryTimeL10n = function(type) {
|
||||
type = type === 'charging' ? 'charging': 'discharging';
|
||||
|
||||
var time = Battery[type + 'Time'];
|
||||
var l10nAttrs = {
|
||||
id: 'battery-' + type + '-calculating',
|
||||
args: {}
|
||||
};
|
||||
|
||||
if (time !== Infinity && time > 0) {
|
||||
var timeLeft = new Date(0, 0, 0, 0, 0, time);
|
||||
l10nAttrs.args.hours = timeLeft.getHours();
|
||||
l10nAttrs.args.minutes = timeLeft.getMinutes();
|
||||
|
||||
if (l10nAttrs.args.hours > 0) {
|
||||
l10nAttrs.id = 'battery-' + type + '-hours';
|
||||
} else {
|
||||
l10nAttrs.id = 'battery-' + type + '-minutes';
|
||||
}
|
||||
} else if (time !== Infinity && time === 0) {
|
||||
l10nAttrs.id = 'battery-' + type + '-complete';
|
||||
}
|
||||
|
||||
return l10nAttrs;
|
||||
};
|
||||
var _refreshText = function() {
|
||||
document.l10n.setAttributes(_batteryLevelText,
|
||||
'battery-current-level', { level: Battery.level });
|
||||
|
||||
if (Battery.state === 'unplugged') {
|
||||
var l10nDischarging = _getBatteryTimeL10n('discharging');
|
||||
document.l10n.setAttributes(_batteryTimeText,
|
||||
l10nDischarging.id, l10nDischarging.args);
|
||||
}
|
||||
else {
|
||||
var l10nCharging = _getBatteryTimeL10n('charging');
|
||||
document.l10n.setAttributes(_batteryTimeText,
|
||||
l10nCharging.id, l10nCharging.args);
|
||||
}
|
||||
};
|
||||
|
||||
return SettingsPanel({
|
||||
onInit: function bp_onInit(rootElement) {
|
||||
_batteryLevelText = rootElement.querySelector(
|
||||
'#battery-level');
|
||||
_batteryTimeText = rootElement.querySelector(
|
||||
'#battery-time');
|
||||
},
|
||||
onBeforeShow: function bp_onBeforeShow(rootElement) {
|
||||
Battery.observe('level', _refreshText);
|
||||
Battery.observe('state', _refreshText);
|
||||
Battery.observe('chargingTime', _refreshText);
|
||||
Battery.observe('dischargingTime', _refreshText);
|
||||
_refreshText();
|
||||
},
|
||||
onBeforeHide: function bp_onBeforeHide() {
|
||||
Battery.unobserve(_refreshText);
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
25
gaia/apps/settings/js/panels/browsing_privacy/browsing_privacy.js
Executable file
25
gaia/apps/settings/js/panels/browsing_privacy/browsing_privacy.js
Executable file
|
|
@ -0,0 +1,25 @@
|
|||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
function BrowsingPrivacy() {}
|
||||
|
||||
/**
|
||||
* Clear browser history.
|
||||
*/
|
||||
BrowsingPrivacy.prototype.clearHistory = function() {
|
||||
navigator.mozSettings.createLock().set({'clear.browser.history': true});
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear browser private data.
|
||||
*/
|
||||
BrowsingPrivacy.prototype.clearPrivateData = function() {
|
||||
navigator.mozSettings.createLock().set({
|
||||
'clear.browser.private-data': true
|
||||
});
|
||||
};
|
||||
|
||||
return function() {
|
||||
return new BrowsingPrivacy();
|
||||
};
|
||||
});
|
||||
64
gaia/apps/settings/js/panels/browsing_privacy/panel.js
Executable file
64
gaia/apps/settings/js/panels/browsing_privacy/panel.js
Executable file
|
|
@ -0,0 +1,64 @@
|
|||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var SettingsPanel = require('modules/settings_panel');
|
||||
var BrowsingPrivacy = require('panels/browsing_privacy/browsing_privacy');
|
||||
var DialogService = require('modules/dialog_service');
|
||||
|
||||
var browsingPrivacy = BrowsingPrivacy();
|
||||
var clearHistoryButton, clearPrivateDataButton;
|
||||
|
||||
function onInit(panel) {
|
||||
clearHistoryButton = panel.querySelector('.clear-history-button');
|
||||
clearPrivateDataButton = panel.querySelector('.clear-private-data-button');
|
||||
|
||||
clearHistoryButton.addEventListener('click',
|
||||
handleClearHistoryClick);
|
||||
clearPrivateDataButton.addEventListener('click',
|
||||
handleClearPrivateDataClick);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle clear history button click.
|
||||
*/
|
||||
function handleClearHistoryClick() {
|
||||
DialogService.confirm('confirm-clear-history-desc', {
|
||||
title: 'confirm-clear-history-title',
|
||||
submitButton: { id: 'clear', style: 'danger' },
|
||||
cancelButton: 'cancel'
|
||||
}).then((result) => {
|
||||
if (result.type === 'submit') {
|
||||
return browsingPrivacy.clearHistory();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle clear private data button click.
|
||||
*/
|
||||
function handleClearPrivateDataClick() {
|
||||
DialogService.confirm('confirm-clear-cookies-cache-desc', {
|
||||
title: 'confirm-clear-cookies-cache-title',
|
||||
submitButton: { id: 'delete', style: 'danger' },
|
||||
cancelButton: 'cancel'
|
||||
}).then((result) => {
|
||||
if (result.type === 'submit') {
|
||||
return browsingPrivacy.clearPrivateData();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function onUninit() {
|
||||
clearHistoryButton.removeEventListener('click',
|
||||
handleClearHistoryClick);
|
||||
clearPrivateDataButton.removeEventListener('click',
|
||||
handleClearPrivateDataClick);
|
||||
}
|
||||
|
||||
return function() {
|
||||
return SettingsPanel({
|
||||
onInit: onInit,
|
||||
onUninit: onUninit
|
||||
});
|
||||
};
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue