/* globals Contacts, CallLogDBManager, LazyLoader, Utils, StickyHeader, KeypadManager, SimSettingsHelper, CallHandler, AccessibilityHelper, ConfirmDialog, Notification, fb, CallGroupMenu, Navigation */ 'use strict'; var CallLog = { _initialized: false, _headersInterval: null, _empty: true, _contactCache: false, init: function cl_init() { if (this._initialized) { this.becameVisible(); return; } window.performance.mark('callLogStart'); this._initialized = true; var lazyFiles = [ '/shared/style/confirm.css', '/shared/style/switches.css', '/shared/style/lists.css', '/shared/js/confirm.js', '/shared/js/dialer/utils.js', '/shared/js/sticky_header.js', '/shared/js/sim_settings_helper.js', '/shared/js/date_time_helper.js' ]; var self = this; var validContactsCachePromise = this._validateContactsCache(); LazyLoader.load(lazyFiles, function resourcesLoaded() { var mainNodes = [ 'all-filter', 'call-log-container', 'call-log-edit-mode', 'call-log-filter', 'call-log-icon-edit', 'call-log-view', 'deselect-all-threads', 'delete-button', 'edit-mode-header', 'header-edit-mode-text', 'missed-filter', 'select-all-threads', 'edit-mode' ]; mainNodes.forEach(function(id) { this[Utils.toCamelCase(id)] = document.getElementById(id); }, self); var dualSim = !!navigator.mozIccManager && navigator.mozIccManager.iccIds.length > 1; self.callLogContainer.classList.toggle('dual-sim', dualSim); validContactsCachePromise.then(function() { self.render(); window.addEventListener('timeformatchange', self._updateCallTimes.bind(self)); self.callLogIconEdit.addEventListener('click', self.showEditMode.bind(self)); self.editModeHeader.addEventListener('action', self.hideEditMode.bind(self)); self.missedFilter.addEventListener('click', self.filter.bind(self)); self.allFilter.addEventListener('click', self.unfilter.bind(self)); self.callLogContainer.addEventListener('click', self); self.callLogContainer.addEventListener('contextmenu', self); self.selectAllThreads.addEventListener('click', self.selectAll.bind(self)); self.deselectAllThreads.addEventListener('click', self.deselectAll.bind(self)); self.deleteButton.addEventListener('click', self.deleteLogGroups.bind(self)); document.addEventListener('visibilitychange', function() { if (document.hidden) { self.pauseHeaders(); } else { self.updateHeadersContinuously(); if (Navigation.currentView === 'recents') { self.becameVisible(); } } }); self.sticky = new StickyHeader(self.callLogContainer, document.getElementById('sticky')); self.becameVisible(); }); }); }, /** * Returns a promise that is fullfilled once the contact cache has been * validated. Note that the contents of the contact cache may still be stale * after the promise is fullfilled. This only guarantees that * this._contactCache contains a valid value. * * @return {Promise} A promise that is fullfilled once we've validated the * contact cache. */ _validateContactsCache: function cl_validateContactsCache() { var self = this; return new Promise(function(resolve, reject) { /* Get the latest contact cache revision and the actual Contacts API * db revision. If both values differ, we need to update the contact cache * and its revision and directly query the Contacts API to render the * appropriate information while the cache is being rebuilt. */ window.asyncStorage.getItem('contactCacheRevision', function onItem(cacheRevision) { Contacts.getRevision(function(contactsRevision) { if (cacheRevision > contactsRevision) { window.asyncStorage.setItem('contactCacheRevision', contactsRevision); self._contactCache = true; resolve(); return; } self._contactCache = (cacheRevision >= contactsRevision); if (self._contactCache) { resolve(); return; } CallLogDBManager.invalidateContactsCache(function(error) { if (!error) { self._contactCache = true; resolve(); } }); }); }); }); }, _updateCallTimes: function cl_updateCallTimes() { var logItemElts = this.callLogContainer.querySelectorAll('.log-item'); for (var i = 0; i < logItemElts.length; i++) { var logItemElt = logItemElts[i]; var timestamp = logItemElt.getAttribute('data-timestamp'); var formattedTime = Utils.prettyDate(parseInt(timestamp, 10)); var callTimeElt = logItemElt.querySelector('.call-time'); callTimeElt.textContent = formattedTime; } }, // Helper to update UI and clean notifications when we got visibility becameVisible: function cl_becameVisible() { this.updateHeaders(); this.cleanNotifications(); }, // Method for updating the time in headers based on device time updateHeaders: function cl_updateHeaders() { var headers = this.callLogContainer.getElementsByTagName('header'); for (var i = 0, l = headers.length; i < l; i++) { var current = headers[i]; Utils.setHeaderDate(current, parseInt(current.dataset.timestamp)); } }, // Method that starts updating headers every minute, based on device time updateHeadersContinuously: function cl_updateHeaders() { var updater = this.updateHeaders.bind(this); this._headersInterval = setInterval(updater, 60000); }, pauseHeaders: function cl_pauseHeaders() { clearInterval(this._headersInterval); }, // Method for rendering the whole list of logs for first time, // getting the groups from the DB and rendering them in order. render: function cl_render() { var self = this; var daysToRender = []; var chunk = []; var prevDate; var screenRendered = false; var MAX_GROUPS_FOR_FIRST_RENDER = 8; var MAX_GROUPS_TO_BATCH_RENDER = 100; var batchGroupCounter = 0; CallLogDBManager.getGroupList(function logGroupsRetrieved(cursor) { if (!cursor.value) { if (chunk.length === 0) { self.renderEmptyCallLog(); self.disableEditModeButton(); } else { daysToRender.push(chunk); self.renderSeveralDays(daysToRender); if (!screenRendered) { window.performance.mark('firstChunkReady'); } self.enableEditModeButton(); self.sticky.refresh(); self.updateHeadersContinuously(); } window.performance.measure('callLogReady', 'callLogStart'); return; } self._empty = false; var currDate = new Date(cursor.value.date); batchGroupCounter++; if (!prevDate || (currDate.getTime() == prevDate.getTime())) { chunk.push(cursor.value); } else { daysToRender.push(chunk); chunk = [cursor.value]; var renderNow = false; if (batchGroupCounter >= MAX_GROUPS_FOR_FIRST_RENDER && !screenRendered) { renderNow = true; screenRendered = true; window.performance.mark('firstChunkReady'); } else if (batchGroupCounter >= MAX_GROUPS_TO_BATCH_RENDER) { renderNow = true; } if (renderNow) { self.renderSeveralDays(daysToRender); daysToRender = []; batchGroupCounter = 0; } } prevDate = currDate; cursor.continue(); }, 'lastEntryDate', true, true); }, renderSeveralDays: function cl_renderSeveralDays(chunks) { for (var i = 0, l = chunks.length; i < l; i++) { this.renderChunk(chunks[i]); } }, renderChunk: function cl_renderChunk(chunk) { var callLogSection = this.createSectionForGroup(chunk[0]); var phoneNumbers = []; var logGroupContainer = callLogSection.getElementsByTagName('ol')[0]; for (var i = 0; i < chunk.length; i++) { var current = chunk[i]; var logGroupDOM = this.createGroup(current); logGroupContainer.appendChild(logGroupDOM); phoneNumbers.push(current.number); } this.callLogContainer.appendChild(callLogSection); // If the contacts cache is not valid, we retrieve the contacts information // directly from the contacts API and update the DOM accordingly. if (!this._contactCache) { this.updateListWithContactInfo(null, null, logGroupContainer); } }, renderEmptyCallLog: function cl_renderEmptyCallLog(isEmptyMissedCallsGroup) { this.disableEditModeButton(); // If rendering the empty call log for all calls (i.e. the // isEmptyMissedCallsGroup not set), set the _empty parameter to true if (!isEmptyMissedCallsGroup) { this._empty = true; } var noResultContainer = document.getElementById('no-result-container'); noResultContainer.hidden = false; // Get pointers to the empty call log messages var allCallsMsg1 = document.getElementById('no-result-msg1'); var allCallsMsg2 = document.getElementById('no-result-msg2'); var noMissedCallsMsg = document.getElementById('no-result-msg3'); // Set the visibility of the appropriate messages allCallsMsg1.hidden = isEmptyMissedCallsGroup || false; allCallsMsg2.hidden = isEmptyMissedCallsGroup || false; noMissedCallsMsg.hidden = !isEmptyMissedCallsGroup; // If the whole call log is cleared, remove any stale call log nodes from // the call log container if (!isEmptyMissedCallsGroup) { var noResultContainerClone = noResultContainer.cloneNode(true); this.callLogContainer.innerHTML = ''; this.callLogContainer.appendChild(noResultContainerClone); } }, // Method for appending a new group in the list. // We need to ensure where to put the group, taking into account // that the user might have changed date and time in his device. appendGroup: function cl_appendGroup(group) { if (!this._initialized) { return; } // Switch to all calls tab to avoid erroneous call filtering this.unfilter(); this.enableEditModeButton(); // Create element of logGroup var logGroupDOM = this.createGroup(group); var dayIndex = group.date; var container = this.callLogContainer; var previousLogGroup = document.getElementById(group.id); if (previousLogGroup) { // We already have a group with the same id, so just remove it // and re-insert it in the right place var parent = previousLogGroup.parentNode; parent.removeChild(previousLogGroup); this.insertInSection(logGroupDOM, parent); return logGroupDOM; } var groupSelector = '[data-timestamp="' + dayIndex + '"]'; var sectionExists = container.querySelector(groupSelector); if (sectionExists) { // We found a section to place the group, so just insert it // in the right position. var section = sectionExists.getElementsByTagName('ol')[0]; this.insertInSection(logGroupDOM, section); return logGroupDOM; } // We don't have any call for that day, so creating a new section var callLogSection = this.createSectionForGroup(group); var logGroupContainer = callLogSection.getElementsByTagName('ol')[0]; logGroupContainer.appendChild(logGroupDOM); var previousSections = container.getElementsByTagName('section'); var referenceSection; var previousSectionsLength = previousSections.length; for (var i = 0; i < previousSectionsLength; i++) { var current = previousSections[i]; if (dayIndex > current.dataset.timestamp) { referenceSection = current; break; } } if (referenceSection) { container.insertBefore(callLogSection, referenceSection); } else { if (this._empty) { // Create a copy of the empty call log message div in order to add it // back in the empty container var noResultContainer = document.getElementById('no-result-container').cloneNode(true); container.innerHTML = ''; noResultContainer.hidden = true; container.appendChild(noResultContainer); this._empty = false; } container.appendChild(callLogSection); } this.sticky.refresh(); return logGroupDOM; }, // Method that places a log group in the right place inside a section insertInSection: function cl_insertInSect(group, section) { var groups = section.getElementsByTagName('li'); var groupsLength = groups.length; var referenceGroup; var date = group.dataset.timestamp; for (var i = 0; i < groupsLength; i++) { var current = groups[i]; if (parseInt(date) > parseInt(current.dataset.timestamp)) { referenceGroup = current; break; } } if (referenceGroup) { section.insertBefore(group, referenceGroup); } else { section.appendChild(group); } }, // Method that generates the markup for each of the rows in the call log. // Example: //
  • // // // //

    // David R. Chichester // (1) //

    // //
    //
  • createGroup: function cl_createGroup(group) { var date = group.lastEntryDate; var number = group.number; var type = group.type; var emergency = group.emergency; var voicemail = group.voicemail; var status = group.status || ''; var contact = group.contact; var groupDOM = document.createElement('li'); groupDOM.classList.add('log-item'); groupDOM.dataset.timestamp = date; groupDOM.dataset.phoneNumber = number; groupDOM.dataset.type = type; groupDOM.setAttribute('role', 'option'); groupDOM.setAttribute('aria-selected', false); if (contact && contact.id) { groupDOM.dataset.contactId = contact.id; } groupDOM.id = group.id; var iconStyle = 'icon '; switch (type) { case 'dialing': case 'alerting': iconStyle += 'icon-outgoing'; break; case 'incoming': groupDOM.dataset.status = status; if (status === 'connected') { iconStyle += 'icon-incoming'; } else { iconStyle += 'icon-missed'; groupDOM.classList.add('missed-call'); } break; } if (typeof group.serviceId !== 'undefined') { var serviceClass = (parseInt(group.serviceId) === 0) ? 'first-sim' : 'second-sim'; iconStyle += ' ' + serviceClass; } var label = document.createElement('label'); label.setAttribute('aria-hidden', true); label.className = 'pack-checkbox call-log-selection danger'; var input = document.createElement('input'); input.setAttribute('type', 'checkbox'); input.value = group.id; var span = document.createElement('span'); label.appendChild(input); label.appendChild(span); var main = document.createElement('a'); main.setAttribute('role', 'presentation'); var icon = document.createElement('aside'); icon.className = 'icon call-type-icon ' + iconStyle; var primInfo = document.createElement('p'); primInfo.className = 'primary-info'; var primInfoMain = document.createElement('span'); primInfoMain.className = 'primary-info-main'; var bdi = document.createElement('bdi'); if (contact && contact.primaryInfo) { bdi.textContent = contact.primaryInfo; } else { if (number) { bdi.textContent = number; } else { bdi.setAttribute('data-l10n-id', 'withheld-number'); } } primInfoMain.appendChild(bdi); primInfo.appendChild(primInfoMain); if (group.retryCount && group.retryCount > 1) { var retryCount = document.createElement('span'); retryCount.className = 'retry-count'; retryCount.textContent = '(' + group.retryCount + ')'; primInfo.appendChild(retryCount); } var phoneNumberAdditionalInfo = ''; var phoneNumberTypeL10nId = null; if (contact && contact.matchingTel) { phoneNumberAdditionalInfo = Utils.getPhoneNumberAdditionalInfo(contact.matchingTel); } else if (voicemail || emergency) { phoneNumberAdditionalInfo = {raw: number}; phoneNumberTypeL10nId = voicemail ? 'voiceMail' : (emergency ? 'emergencyNumber' : null); } else { phoneNumberAdditionalInfo = {id: 'unknown'}; } var addInfo = document.createElement('p'); addInfo.className = 'additional-info'; addInfo.setAttribute('aria-hidden', 'true'); var typeAndCarrier = document.createElement('span'); typeAndCarrier.className = 'type-carrier'; if (phoneNumberAdditionalInfo) { if (phoneNumberAdditionalInfo.raw) { typeAndCarrier.removeAttribute('data-l10n-id'); typeAndCarrier.textContent = phoneNumberAdditionalInfo.raw; } else { navigator.mozL10n.setAttributes(typeAndCarrier, phoneNumberAdditionalInfo.id, phoneNumberAdditionalInfo.args); } } addInfo.appendChild(typeAndCarrier); var callTime = document.createElement('span'); callTime.className = 'call-time'; callTime.textContent = Utils.prettyDate(date) + ' '; addInfo.appendChild(callTime); main.appendChild(icon); main.appendChild(primInfo); main.appendChild(addInfo); if (phoneNumberTypeL10nId) { // Check if this element has a `bdi` child, and remove it if it does. var bdiPrim = primInfoMain.querySelector('bdi'); primInfoMain.removeChild(bdiPrim); primInfoMain.setAttribute('data-l10n-id', phoneNumberTypeL10nId); var primElem = primInfoMain.parentNode; var parent = primElem.parentNode; parent.insertBefore(addInfo, primElem.nextElementSibling); } groupDOM.appendChild(label); groupDOM.appendChild(main); return groupDOM; }, // Method that creates a new section for placing groups createSectionForGroup: function cl_createSectionForGroup(group) { var referenceTimestamp = group.date; var groupContainer = document.createElement('section'); groupContainer.dataset.timestamp = referenceTimestamp; var header = document.createElement('header'); Utils.setHeaderDate(header, referenceTimestamp); header.dataset.timestamp = referenceTimestamp; header.id = 'header-' + referenceTimestamp; header.dataset.update = true; var ol = document.createElement('ol'); ol.classList.add('log-group'); ol.setAttribute('role', 'listbox'); ol.setAttribute('aria-multiselectable', true); ol.id = 'group-container-' + referenceTimestamp; groupContainer.appendChild(header); groupContainer.appendChild(ol); return groupContainer; }, enableEditModeButton: function cl_enableEditModeButton() { CallLog.callLogIconEdit.removeAttribute('disabled'); }, disableEditModeButton: function cl_disableEditModeButton() { CallLog.callLogIconEdit.setAttribute('disabled', 'disabled'); }, showEditModeButton: function cl_showEditModeButton() { CallLog.callLogIconEdit.hidden = false; }, hideEditModeButton: function cl_hideEditModeButton() { CallLog.callLogIconEdit.hidden = true; }, showEditMode: function cl_showEditMode(event) { if (this.callLogIconEdit.hasAttribute('disabled')) { // Disabled does not have effect on an anchor. event.preventDefault(); event.stopPropagation(); return; } this.editMode.removeAttribute('hidden'); this.headerEditModeText.setAttribute('data-l10n-id', 'edit'); this.deleteButton.setAttribute('disabled', 'disabled'); this.selectAllThreads.removeAttribute('disabled'); this.selectAllThreads.setAttribute('data-l10n-id', 'selectAll'); this.deselectAllThreads.setAttribute('disabled', 'disabled'); document.body.classList.add('recents-edit'); }, hideEditMode: function cl_hideEditMode() { document.body.classList.remove('recents-edit'); var cont = this.callLogContainer; var inputs = cont.querySelectorAll('input[type="checkbox"]'); var logItems = cont.querySelectorAll('.log-item'); var i, l; for (i = 0, l = inputs.length; i < l; i++) { inputs[i].checked = false; } for (i = 0, l = logItems.length; i < l; i++) { logItems[i].setAttribute('aria-selected', false); } if (this.callLogContainer.classList.contains('filter')) { this.filter(); } else { this.unfilter(); } this.editMode.setAttribute('hidden', ''); }, // In case we are in edit mode, just update the counter of selected rows. handleEvent: function cl_handleEvent(evt) { var logItem = evt.target; if (document.body.classList.contains('recents-edit')) { if (logItem.dataset.phoneNumber) { // Landed on the logItem (when using the screen reader). var checkbox = logItem.getElementsByTagName('input')[0]; var toggleChecked = !checkbox.checked; checkbox.checked = toggleChecked; logItem.setAttribute('aria-selected', toggleChecked); } this.updateHeaderCount(); return; } var dataset = logItem.dataset; var phoneNumber = dataset.phoneNumber; if (!phoneNumber) { return; } if (evt.type == 'click') { if (navigator.mozIccManager && !!navigator.mozIccManager && navigator.mozIccManager.iccIds.length > 1) { KeypadManager.updatePhoneNumber(phoneNumber); Navigation.showKeypad(); } else { SimSettingsHelper.getCardIndexFrom('outgoingCall', function(ci) { CallHandler.call(phoneNumber, ci); }); } } else { evt.preventDefault(); var primaryInfo = logItem.querySelector('.primary-info-main').textContent; LazyLoader.load(['/dialer/js/call_group_menu.js'], function() { CallGroupMenu.show( primaryInfo, phoneNumber, dataset.timestamp, dataset.type, dataset.status); }); } }, filter: function cl_filter() { this.callLogContainer.classList.add('filter'); AccessibilityHelper.setAriaSelected(this.missedFilter.firstElementChild, [ this.allFilter.firstElementChild, this.missedFilter.firstElementChild]); this.callLogContainer.setAttribute('aria-labelledby', 'missed-filter-tab'); var containers = this.callLogContainer.getElementsByTagName('ol'); var totalMissedCalls = 0; for (var i = 0, l = containers.length; i < l; i++) { var noMissedCalls = containers[i].getElementsByClassName('missed-call'); if (noMissedCalls.length === 0) { containers[i].parentNode.classList.add('groupFiltered'); } totalMissedCalls += noMissedCalls.length; } // If there are no missed calls to display, show appropriate empty call log, // otherwise hide the empty call log message and enable edit mode if (totalMissedCalls === 0) { this.renderEmptyCallLog(true); } else { var noResultContainer = document.getElementById('no-result-container'); noResultContainer.hidden = true; this.enableEditModeButton(); } }, unfilter: function cl_unfilter() { // If the call log is empty display the appropriate message, otherwise hide // the empty call log message and enable edit mode var logItems = this.callLogContainer.querySelectorAll('.log-item'); if (logItems.length === 0) { this.renderEmptyCallLog(); } else { var noResultContainer = document.getElementById('no-result-container'); noResultContainer.hidden = true; this.enableEditModeButton(); } this.callLogContainer.classList.remove('filter'); AccessibilityHelper.setAriaSelected(this.allFilter.firstElementChild, [ this.allFilter.firstElementChild, this.missedFilter.firstElementChild]); this.callLogContainer.setAttribute('aria-labelledby', 'all-filter-tab'); var hiddenContainers = document.getElementsByClassName('groupFiltered'); // hiddenContainers is a live list, so let's iterate on the list in the // reverse order. for (var i = (hiddenContainers.length - 1); i >= 0; i--) { hiddenContainers[i].classList.remove('groupFiltered'); } }, updateHeaderCount: function cl_updateHeaderCount() { var selector = this.callLogContainer.classList.contains('filter') ? 'li.missed-call input[type="checkbox"]:checked' : 'input[type="checkbox"]:checked'; var allSelector = this.callLogContainer.classList.contains('filter') ? 'li.missed-call input[type="checkbox"]' : 'input[type="checkbox"]'; var selected = this.callLogContainer.querySelectorAll(selector).length; var allInputs = this.callLogContainer.querySelectorAll(allSelector).length; if (selected === 0) { this.headerEditModeText.setAttribute('data-l10n-id', 'edit'); this.selectAllThreads.removeAttribute('disabled'); this.selectAllThreads.setAttribute('data-l10n-id', 'selectAll'); this.deselectAllThreads.setAttribute('disabled', 'disabled'); this.deleteButton.setAttribute('disabled', 'disabled'); return; } navigator.mozL10n.setAttributes( this.headerEditModeText, 'edit-selected', {n: selected}); this.deleteButton.removeAttribute('disabled'); if (selected === allInputs) { this.deselectAllThreads.removeAttribute('disabled'); this.selectAllThreads.setAttribute('disabled', 'disabled'); } else { this.selectAllThreads.removeAttribute('disabled'); this.selectAllThreads.setAttribute('data-l10n-id', 'selectAll'); this.deselectAllThreads.removeAttribute('disabled'); } }, selectAll: function cl_selectAll() { var selector = this.callLogContainer.classList.contains('filter') ? 'li.missed-call input[type="checkbox"]:not(:checked)' : 'input[type="checkbox"]:not(:checked)'; var inputs = this.callLogContainer.querySelectorAll(selector); for (var i = 0, l = inputs.length; i < l; i++) { inputs[i].checked = true; } this.updateHeaderCount(); }, deselectAll: function cl_selectAll() { var selector = 'input[type="checkbox"]:checked'; var inputs = this.callLogContainer.querySelectorAll(selector); for (var i = 0, l = inputs.length; i < l; i++) { inputs[i].checked = false; } this.updateHeaderCount(); }, deleteLogGroups: function cl_deleteLogGroups() { var selector = 'input[type="checkbox"]:checked'; var inputsSelected = this.callLogContainer.querySelectorAll(selector); var self = this; var msg = {'id': 'delete-n-log?', 'args': {n: inputsSelected.length}}; var yesObject = { title: 'delete', isDanger: true, callback: function deleteLogGroup() { ConfirmDialog.hide(); var logGroupsToDelete = []; for (var i = 0, l = inputsSelected.length; i < l; i++) { var logGroup = inputsSelected[i].parentNode.parentNode; var olContainer = logGroup.parentNode; olContainer.removeChild(logGroup); if (olContainer.children.length === 0) { var section = olContainer.parentNode; self.callLogContainer.removeChild(section); } var dataset = logGroup.dataset; var toDelete = { date: parseInt(dataset.timestamp), number: dataset.phoneNumber === null ? '' : dataset.phoneNumber, type: dataset.type }; if (dataset.status) { toDelete.status = dataset.status; } logGroupsToDelete.push(toDelete); } CallLogDBManager.deleteGroupList(logGroupsToDelete, function() { self.hideEditMode(); }); } }; var noObject = { title: 'cancel', callback: function onCancel() { ConfirmDialog.hide(); } }; ConfirmDialog.show(null, msg, noObject, yesObject); }, /************************** * Contacts related methods. **************************/ // We need _updateContact and _removeContact aux functions to keep the // correct references to the log DOM element. _updateContact: function _updateContact(log, phoneNumber, contactId, updateDb) { var self = this; Contacts.findByNumber(phoneNumber, function(contact, matchingTel) { if (!contact || !matchingTel) { self._removeContact(log, contactId, updateDb); return; } // Update contact info. if (self._contactCache && updateDb) { CallLogDBManager.updateGroupContactInfo(contact, matchingTel, function(result) { if (typeof result === 'number' && result > 0) { self.updateContactInfo(log, contact, matchingTel); } }); } else { self.updateContactInfo(log, contact, matchingTel); } }); }, _removeContact: function _removeContact(log, contactId, updateDb) { // If the cache is valid, we also need to remove the contact from the // cache if (this._contactCache && updateDb) { var self = this; CallLogDBManager.removeGroupContactInfo(contactId, null, function(result) { if (typeof result === 'number' && result > 0) { self.updateContactInfo(log); } }); } else { this.updateContactInfo(log); } }, /** * Updates the whole list of groups or part of it with the appropriate * contact information. * * This function will be triggered after receiving a 'oncontactchange' event * with 'create', 'remove' or 'update' reasons or during the initial rendering * for each chunk of data *only* if we detect that the contacts cache is not * valid. * * param reason * String containing the reason of the 'oncontactchange' event or null * if the function was triggered because of an invalid contacts cache. * param contactId * Contact identifier if any. Only 'oncontactchange' events with * 'update' or 'remove' reasons will provide a contactId parameter. * param target * DOM element to be updated. We default to the whole log if no * 'target' param is provided. */ updateListWithContactInfo: function cl_updateList(reason, contactId, target) { var container = target || this.callLogContainer; if (!container) { return; } // Get the list of logs to be updated. var logs = []; switch (reason) { case 'remove': logs = container.querySelectorAll('li[data-contact-id="' + contactId + '"]'); break; /* case 'create': case 'update': */ default: logs = container.querySelectorAll('.log-item'); break; } for (var i = 0, l = logs.length; i < l; i++) { var log = logs[i]; var logInfo = log.dataset; this._updateContact(log, logInfo.phoneNumber, contactId, i === 0); } }, /** * Visually update the contact information of a group in the DOM. * * param element * DOM element containing the group information. * param contact * Object containing the contact information associated to the group * of calls. * param matchingTel * Object containing the phone number associated with the group of * calls. It contains 'type', 'carrier' and 'value' parameters. * */ updateContactInfo: function cl_updateContactInfo(element, contact, matchingTel) { var primInfoCont = element.querySelector('.primary-info-main'); var addInfo = element.getElementsByClassName('additional-info')[0]; var typeAndCarrier = addInfo.querySelector('.type-carrier'); if (!matchingTel) { if (element.dataset.contactId) { // Remove contact info. primInfoCont.textContent = ''; var bdi = document.createElement('bdi'); bdi.textContent = element.dataset.phoneNumber; primInfoCont.appendChild(bdi); typeAndCarrier.textContent = ''; typeAndCarrier.setAttribute('data-l10n-id', 'unknown'); delete element.dataset.contactId; } return; } var primaryInfo = Utils.getPhoneNumberPrimaryInfo(matchingTel, contact); if (primaryInfo) { primInfoCont.textContent = ''; var bdiPrim = document.createElement('bdi'); bdiPrim.textContent = primaryInfo; primInfoCont.appendChild(bdiPrim); } var phoneNumberAdditionalInfo = Utils.getPhoneNumberAdditionalInfo(matchingTel); navigator.mozL10n.setAttributes( typeAndCarrier, phoneNumberAdditionalInfo.id, phoneNumberAdditionalInfo.args ); if (contact) { element.dataset.contactId = contact.id; } }, cleanNotifications: function cl_cleanNotifcations() { /* On startup of call log, we clear all dialer notification except for USSD * ones as those are closed only when the user taps them. */ Notification.get() .then( function onSuccess(notifications) { for (var i = 0; i < notifications.length; i++) { if (!notifications[i].tag) { notifications[i].close(); } } }, function onError(reason) { console.debug('Call log Notification.get() promise error: ' + reason); } ); }, _getGroupFromLog: function cl_getGroupFromLog(log) { if (!log) { return; } var data = log.dataset; if (!data || !data.id) { return; } var groupId = data.id.split('-'); var group = { date: groupId[0], number: groupId[1], type: groupId[2] }; if (data.status) { group.status = data.status; } return group; } }; navigator.mozContacts.oncontactchange = function oncontactchange(event) { var reason = event.reason; var options = { filterBy: ['id'], filterOp: 'equals', filterValue: event.contactID }; /* FIXME: We should use the contact information (id, phone number, etc...) to * reduce the number of elements we try to update. */ if (reason === 'remove') { CallLog.updateListWithContactInfo('remove', event.contactID); return; } var request = navigator.mozContacts.find(options); request.onsuccess = function contactRetrieved(e) { if (!e.target.result || e.target.result.length === 0) { console.warn('Call log: No Contact Found: ', event.contactID); return; } var contact = e.target.result[0]; if (!fb.isFbContact(contact)) { CallLog.updateListWithContactInfo(reason, event.contactID); return; } var fbReq = fb.getData(contact); fbReq.onsuccess = function fbContactSuccess() { CallLog.updateListWithContactInfo(reason, event.contactID); }; fbReq.onerror = function fbContactError() { console.error('Error while querying FB: ', fbReq.error.name); CallLog.updateListWithContactInfo(reason, event.contactID); }; }; request.onerror = function errorHandler(e) { console.error('Error retrieving contact by ID ' + event.contactID); }; };