diff options
Diffstat (limited to 'apps/system/js/statusbar.js')
-rw-r--r-- | apps/system/js/statusbar.js | 618 |
1 files changed, 618 insertions, 0 deletions
diff --git a/apps/system/js/statusbar.js b/apps/system/js/statusbar.js new file mode 100644 index 0000000..1d95d99 --- /dev/null +++ b/apps/system/js/statusbar.js @@ -0,0 +1,618 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ + +'use strict'; + +var StatusBar = { + /* all elements that are children nodes of the status bar */ + ELEMENTS: ['notification', 'time', + 'battery', 'wifi', 'data', 'flight-mode', 'signal', 'network-activity', + 'tethering', 'alarm', 'bluetooth', 'mute', 'headphones', + 'recording', 'sms', 'geolocation', 'usb', 'label', 'system-downloads', + 'call-forwarding'], + + /* Timeout for 'recently active' indicators */ + kActiveIndicatorTimeout: 60 * 1000, + + /* Whether or not status bar is actively updating or not */ + active: true, + + /* Some values that sync from mozSettings */ + settingValues: {}, + + /* Keep the DOM element references here */ + icons: {}, + + /* A mapping table between technology names + we would get from API v.s. the icon we want to show. */ + mobileDataIconTypes: { + 'lte': '4G', // 4G LTE + 'ehrpd': '4G', // 4G CDMA + 'hspa+': 'H+', // 3.5G HSPA+ + 'hsdpa': 'H', 'hsupa': 'H', 'hspa': 'H', // 3.5G HSDPA + 'evdo0': '3G', 'evdoa': '3G', 'evdob': '3G', '1xrtt': '3G', // 3G CDMA + 'umts': '3G', // 3G + 'edge': 'E', // EDGE + 'is95a': '2G', 'is95b': '2G', // 2G CDMA + 'gprs': '2G' + }, + + geolocationActive: false, + geolocationTimer: null, + + recordingActive: false, + recordingTimer: null, + + umsActive: false, + + headphonesActive: false, + + /** + * this keeps how many current installs/updates we do + * it triggers the icon "systemDownloads" + */ + systemDownloadsCount: 0, + + /* For other modules to acquire */ + get height() { + if (this.screen.classList.contains('fullscreen-app') || + document.mozFullScreen) { + return 0; + } else if (this.screen.classList.contains('active-statusbar')) { + return this.attentionBar.offsetHeight; + } else { + return this._cacheHeight || + (this._cacheHeight = this.element.getBoundingClientRect().height); + } + }, + + init: function sb_init() { + this.getAllElements(); + + var settings = { + 'ril.radio.disabled': ['signal', 'data'], + 'ril.data.enabled': ['data'], + 'wifi.enabled': ['wifi'], + 'bluetooth.enabled': ['bluetooth'], + 'tethering.usb.enabled': ['tethering'], + 'tethering.wifi.enabled': ['tethering'], + 'tethering.wifi.connectedClients': ['tethering'], + 'tethering.usb.connectedClients': ['tethering'], + 'ring.enabled': ['mute'], + 'alarm.enabled': ['alarm'], + 'vibration.enabled': ['vibration'], + 'ril.cf.enabled': ['callForwarding'] + }; + + var self = this; + for (var settingKey in settings) { + (function sb_setSettingsListener(settingKey) { + SettingsListener.observe(settingKey, false, + function sb_settingUpdate(value) { + self.settingValues[settingKey] = value; + settings[settingKey].forEach( + function sb_callUpdate(name) { + self.update[name].call(self); + } + ); + } + ); + self.settingValues[settingKey] = false; + })(settingKey); + } + + // Listen to 'screenchange' from screen_manager.js + window.addEventListener('screenchange', this); + + // Listen to 'geolocation-status' and 'recording-status' mozChromeEvent + window.addEventListener('mozChromeEvent', this); + + // Listen to 'bluetoothconnectionchange' from bluetooth.js + window.addEventListener('bluetoothconnectionchange', this); + + // Listen to 'moztimechange' + window.addEventListener('moztimechange', this); + + this.systemDownloadsCount = 0; + this.setActive(true); + }, + + handleEvent: function sb_handleEvent(evt) { + switch (evt.type) { + case 'screenchange': + this.setActive(evt.detail.screenEnabled); + break; + + case 'chargingchange': + case 'levelchange': + case 'statuschange': + this.update.battery.call(this); + break; + + case 'voicechange': + this.update.signal.call(this); + this.update.label.call(this); + break; + + case 'cardstatechange': + this.update.signal.call(this); + this.update.label.call(this); + this.update.data.call(this); + break; + + case 'callschanged': + this.update.signal.call(this); + break; + + case 'iccinfochange': + this.update.label.call(this); + break; + + case 'datachange': + this.update.data.call(this); + break; + + case 'bluetoothconnectionchange': + this.update.bluetooth.call(this); + break; + + case 'moztimechange': + this.update.time.call(this); + break; + + case 'mozChromeEvent': + switch (evt.detail.type) { + case 'geolocation-status': + this.geolocationActive = evt.detail.active; + this.update.geolocation.call(this); + break; + + case 'recording-status': + this.recordingActive = evt.detail.active; + this.update.recording.call(this); + break; + + case 'volume-state-changed': + this.umsActive = evt.detail.active; + this.update.usb.call(this); + break; + + case 'headphones-status-changed': + this.headphonesActive = (evt.detail.state != 'off'); + this.update.headphones.call(this); + break; + } + + break; + + case 'moznetworkupload': + case 'moznetworkdownload': + this.update.networkActivity.call(this); + break; + } + }, + + setActive: function sb_setActive(active) { + this.active = active; + if (active) { + this.update.time.call(this); + + var battery = window.navigator.battery; + if (battery) { + battery.addEventListener('chargingchange', this); + battery.addEventListener('levelchange', this); + battery.addEventListener('statuschange', this); + this.update.battery.call(this); + } + + var conn = window.navigator.mozMobileConnection; + if (conn) { + conn.addEventListener('voicechange', this); + conn.addEventListener('iccinfochange', this); + conn.addEventListener('datachange', this); + this.update.signal.call(this); + this.update.data.call(this); + } + + window.addEventListener('wifi-statuschange', + this.update.wifi.bind(this)); + this.update.wifi.call(this); + + window.addEventListener('moznetworkupload', this); + window.addEventListener('moznetworkdownload', this); + } else { + clearTimeout(this._clockTimer); + + var battery = window.navigator.battery; + if (battery) { + battery.removeEventListener('chargingchange', this); + battery.removeEventListener('levelchange', this); + battery.removeEventListener('statuschange', this); + } + + var conn = window.navigator.mozMobileConnection; + if (conn) { + conn.removeEventListener('voicechange', this); + conn.removeEventListener('iccinfochange', this); + conn.removeEventListener('datachange', this); + } + + window.removeEventListener('moznetworkupload', this); + window.removeEventListener('moznetworkdownload', this); + } + }, + + update: { + label: function sb_updateLabel() { + var conn = window.navigator.mozMobileConnection; + var label = this.icons.label; + var l10nArgs = JSON.parse(label.dataset.l10nArgs || '{}'); + + if (!conn || !conn.voice || !conn.voice.connected || + conn.voice.emergencyCallsOnly) { + delete l10nArgs.operator; + label.dataset.l10nArgs = JSON.stringify(l10nArgs); + + label.dataset.l10nId = ''; + label.textContent = l10nArgs.date; + + return; + } + + var operatorInfos = MobileOperator.userFacingInfo(conn); + l10nArgs.operator = operatorInfos.operator; + + if (operatorInfos.region) { + l10nArgs.operator += ' ' + operatorInfos.region; + } + + label.dataset.l10nArgs = JSON.stringify(l10nArgs); + + label.dataset.l10nId = 'statusbarLabel'; + label.textContent = navigator.mozL10n.get('statusbarLabel', l10nArgs); + }, + + time: function sb_updateTime() { + // Schedule another clock update when a new minute rolls around + var _ = navigator.mozL10n.get; + var f = new navigator.mozL10n.DateTimeFormat(); + var now = new Date(); + var sec = now.getSeconds(); + if (this._clockTimer) + window.clearTimeout(this._clockTimer); + this._clockTimer = + window.setTimeout((this.update.time).bind(this), (59 - sec) * 1000); + + var formated = f.localeFormat(now, _('shortTimeFormat')); + formated = formated.replace(/\s?(AM|PM)\s?/i, '<span>$1</span>'); + this.icons.time.innerHTML = formated; + + var label = this.icons.label; + var l10nArgs = JSON.parse(label.dataset.l10nArgs || '{}'); + l10nArgs.date = f.localeFormat(now, _('statusbarDateFormat')); + label.dataset.l10nArgs = JSON.stringify(l10nArgs); + this.update.label.call(this); + }, + + battery: function sb_updateBattery() { + var battery = window.navigator.battery; + if (!battery) + return; + + var icon = this.icons.battery; + + icon.hidden = false; + icon.dataset.charging = battery.charging; + icon.dataset.level = Math.floor(battery.level * 10) * 10; + }, + + networkActivity: function sb_updateNetworkActivity() { + // Each time we receive an update, make network activity indicator + // show up for 500ms. + + var icon = this.icons.networkActivity; + + clearTimeout(this._networkActivityTimer); + icon.hidden = false; + + this._networkActivityTimer = setTimeout(function hideNetActivityIcon() { + icon.hidden = true; + }, 500); + }, + + signal: function sb_updateSignal() { + var conn = window.navigator.mozMobileConnection; + if (!conn || !conn.voice) + return; + + var voice = conn.voice; + var icon = this.icons.signal; + var flightModeIcon = this.icons.flightMode; + var _ = navigator.mozL10n.get; + + if (this.settingValues['ril.radio.disabled']) { + // "Airplane Mode" + icon.hidden = true; + flightModeIcon.hidden = false; + return; + } + + flightModeIcon.hidden = true; + icon.hidden = false; + + if (conn.cardState === 'absent') { + // no SIM + delete icon.dataset.level; + delete icon.dataset.emergency; + delete icon.dataset.searching; + delete icon.dataset.roaming; + } else if (voice.connected || this.hasActiveCall()) { + // "Carrier" / "Carrier (Roaming)" + icon.dataset.level = Math.ceil(voice.relSignalStrength / 20); // 0-5 + icon.dataset.roaming = voice.roaming; + + delete icon.dataset.emergency; + delete icon.dataset.searching; + } else { + // "No Network" / "Emergency Calls Only (REASON)" / trying to connect + icon.dataset.level = -1; + // logically, we should have "&& !voice.connected" as well but we + // already know this. + icon.dataset.searching = (!voice.emergencyCallsOnly && + voice.state !== 'notSearching'); + icon.dataset.emergency = (voice.emergencyCallsOnly); + delete icon.dataset.roaming; + } + + if (voice.emergencyCallsOnly) { + this.addCallListener(); + } else { + this.removeCallListener(); + } + + }, + + data: function sb_updateSignal() { + var conn = window.navigator.mozMobileConnection; + if (!conn || !conn.data) + return; + + var data = conn.data; + var icon = this.icons.data; + + if (this.settingValues['ril.radio.disabled'] || + !this.settingValues['ril.data.enabled'] || + !this.icons.wifi.hidden || !data.connected) { + icon.hidden = true; + + return; + } + + icon.hidden = false; + icon.dataset.type = + this.mobileDataIconTypes[data.type] || 'circle'; + }, + + + wifi: function sb_updateWifi() { + var wifiManager = window.navigator.mozWifiManager; + if (!wifiManager) + return; + + var icon = this.icons.wifi; + var wasHidden = icon.hidden; + + if (!this.settingValues['wifi.enabled']) { + icon.hidden = true; + if (!wasHidden) + this.update.data.call(this); + + return; + } + + switch (wifiManager.connection.status) { + case 'disconnected': + icon.hidden = true; + + break; + + case 'connecting': + case 'associated': + icon.hidden = false; + icon.dataset.connecting = true; + icon.dataset.level = 0; + + break; + + case 'connected': + icon.hidden = false; + + var relSignalStrength = + wifiManager.connectionInformation.relSignalStrength; + icon.dataset.level = Math.floor(relSignalStrength / 25); + + break; + } + + if (icon.hidden !== wasHidden) + this.update.data.call(this); + }, + + tethering: function sb_updateTethering() { + var icon = this.icons.tethering; + icon.hidden = !(this.settingValues['tethering.usb.enabled'] || + this.settingValues['tethering.wifi.enabled']); + + icon.dataset.active = + (this.settingValues['tethering.wifi.connectedClients'] !== 0) || + (this.settingValues['tethering.usb.connectedClients'] !== 0); + }, + + bluetooth: function sb_updateBluetooth() { + var icon = this.icons.bluetooth; + + icon.hidden = !this.settingValues['bluetooth.enabled']; + icon.dataset.active = Bluetooth.connected; + }, + + alarm: function sb_updateAlarm() { + this.icons.alarm.hidden = !this.settingValues['alarm.enabled']; + }, + + mute: function sb_updateMute() { + this.icons.mute.hidden = + (this.settingValues['ring.enabled'] == true); + }, + + vibration: function sb_vibration() { + var vibrate = (this.settingValues['vibration.enabled'] == true); + if (vibrate) { + this.icons.mute.classList.add('vibration'); + } else { + this.icons.mute.classList.remove('vibration'); + } + }, + + recording: function sb_updateRecording() { + window.clearTimeout(this.recordingTimer); + + var icon = this.icons.recording; + icon.dataset.active = this.recordingActive; + + if (this.recordingActive) { + // Geolocation is currently active, show the active icon. + icon.hidden = false; + return; + } + + // Geolocation is currently inactive. + // Show the inactive icon and hide it after kActiveIndicatorTimeout + this.recordingTimer = window.setTimeout(function hideGeoIcon() { + icon.hidden = true; + }, this.kActiveIndicatorTimeout); + }, + + sms: function sb_updateSms() { + // We are not going to show this for v1 + + // this.icon.sms.hidden = ? + // this.icon.sms.dataset.num = ?; + }, + + geolocation: function sb_updateGeolocation() { + window.clearTimeout(this.geolocationTimer); + + var icon = this.icons.geolocation; + icon.dataset.active = this.geolocationActive; + + if (this.geolocationActive) { + // Geolocation is currently active, show the active icon. + icon.hidden = false; + return; + } + + // Geolocation is currently inactive. + // Show the inactive icon and hide it after kActiveIndicatorTimeout + this.geolocationTimer = window.setTimeout(function hideGeoIcon() { + icon.hidden = true; + }, this.kActiveIndicatorTimeout); + }, + + usb: function sb_updateUsb() { + var icon = this.icons.usb; + icon.hidden = !this.umsActive; + }, + + headphones: function sb_updateHeadphones() { + var icon = this.icons.headphones; + icon.hidden = !this.headphonesActive; + }, + + systemDownloads: function sb_updatesystemDownloads() { + var icon = this.icons.systemDownloads; + icon.hidden = (this.systemDownloadsCount === 0); + }, + + callForwarding: function sb_updateCallForwarding() { + var icon = this.icons.callForwarding; + icon.hidden = !this.settingValues['ril.cf.enabled']; + } + }, + + hasActiveCall: function sb_hasActiveCall() { + var telephony = navigator.mozTelephony; + + // will return true as soon as we begin dialing + return !!(telephony && telephony.active); + }, + + addCallListener: function sb_addCallListener() { + var telephony = navigator.mozTelephony; + if (telephony) { + telephony.addEventListener('callschanged', this); + } + }, + + removeCallListener: function sb_addCallListener() { + var telephony = navigator.mozTelephony; + if (telephony) { + telephony.removeEventListener('callschanged', this); + } + }, + + updateNotification: function sb_updateNotification(count) { + var icon = this.icons.notification; + if (!count) { + icon.hidden = true; + return; + } + + icon.hidden = false; + icon.dataset.num = count; + }, + + updateNotificationUnread: function sb_updateNotificationUnread(unread) { + this.icons.notification.dataset.unread = unread; + }, + + incSystemDownloads: function sb_incSystemDownloads() { + this.systemDownloadsCount++; + this.update.systemDownloads.call(this); + }, + + decSystemDownloads: function sb_decSystemDownloads() { + if (--this.systemDownloadsCount < 0) { + this.systemDownloadsCount = 0; + } + + this.update.systemDownloads.call(this); + }, + + getAllElements: function sb_getAllElements() { + // ID of elements to create references + + var toCamelCase = function toCamelCase(str) { + return str.replace(/\-(.)/g, function replacer(str, p1) { + return p1.toUpperCase(); + }); + }; + + this.ELEMENTS.forEach((function createElementRef(name) { + this.icons[toCamelCase(name)] = + document.getElementById('statusbar-' + name); + }).bind(this)); + + this.element = document.getElementById('statusbar'); + this.screen = document.getElementById('screen'); + this.attentionBar = document.getElementById('attention-bar'); + } +}; + +if (navigator.mozL10n.readyState == 'complete' || + navigator.mozL10n.readyState == 'interactive') { + StatusBar.init(); +} else { + window.addEventListener('localized', StatusBar.init.bind(StatusBar)); +} + + |