From 67b424672a69d73b4bc94b354ba088b50758d482 Mon Sep 17 00:00:00 2001 From: Gabriele Svelto Date: Sat, 10 Jan 2015 12:34:12 +0100 Subject: [PATCH] Bug 1061130 - Add speed dial functionality to the Dialer. r=thills --- .../dialer/test/unit/keypad_test.js | 150 +++++++++++++++++- shared/js/dialer/keypad.js | 149 ++++++++++++++++- shared/test/unit/mocks/mock_iccmanager.js | 36 ++++- 3 files changed, 320 insertions(+), 15 deletions(-) diff --git a/apps/communications/dialer/test/unit/keypad_test.js b/apps/communications/dialer/test/unit/keypad_test.js index 121f0f2b5dcc..523b8a3ba6c7 100644 --- a/apps/communications/dialer/test/unit/keypad_test.js +++ b/apps/communications/dialer/test/unit/keypad_test.js @@ -1,9 +1,9 @@ -/* globals CallHandler, CallLogDBManager, FontSizeManager, gTonesFrequencies, - KeypadManager, MockCall, MockCallsHandler, MockIccManager, - MockNavigatorMozTelephony, MockNavigatorSettings, +/* globals CallHandler, CallLogDBManager, ConfirmDialog, FontSizeManager, + gTonesFrequencies, KeypadManager, MockCall, MockCallsHandler, + MockIccManager, MockNavigatorMozTelephony, MockNavigatorSettings, MockSettingsListener, MocksHelper, MockTonePlayer, telephonyAddCall, MockMultiSimActionButtonSingleton, MockMozL10n, CustomDialog, - MockMozActivity, CustomElementsHelper + MockMozActivity, SimSettingsHelper, CustomElementsHelper */ 'use strict'; @@ -11,15 +11,16 @@ require('/shared/js/dialer/dtmf_tone.js'); require('/shared/js/dialer/keypad.js'); +require('/contacts/test/unit/mock_confirm_dialog.js'); require('/dialer/test/unit/mock_call_handler.js'); require('/dialer/test/unit/mock_call_log_db_manager.js'); -require('/shared/test/unit/mocks/mock_confirm_dialog.js'); require('/shared/test/unit/mocks/mock_iccmanager.js'); require('/shared/test/unit/mocks/mock_lazy_loader.js'); +require('/shared/test/unit/mocks/mock_multi_sim_action_button.js'); require('/shared/test/unit/mocks/mock_navigator_moz_settings.js'); require('/shared/test/unit/mocks/mock_navigator_moz_telephony.js'); require('/shared/test/unit/mocks/mock_settings_listener.js'); -require('/shared/test/unit/mocks/mock_multi_sim_action_button.js'); +require('/shared/test/unit/mocks/mock_sim_settings_helper.js'); require('/shared/test/unit/mocks/dialer/mock_handled_call.js'); require('/shared/test/unit/mocks/dialer/mock_call.js'); require('/shared/test/unit/mocks/dialer/mock_calls_handler.js'); @@ -41,8 +42,10 @@ var mocksHelperForKeypad = new MocksHelper([ 'CallsHandler', 'CallHandler', 'CallLogDBManager', + 'ConfirmDialog', 'HandledCall', 'SettingsListener', + 'SimSettingsHelper', 'GaiaSimPicker', 'TonePlayer', 'CustomDialog', @@ -678,6 +681,141 @@ suite('dialer/keypad', function() { sinon.assert.notCalled(CallHandler.call); }); }); + + suite('Speed dial', function() { + var speedDialNum = '1#'; + + setup(function() { + subject._phoneNumber = ''; + }); + + test(speedDialNum + ' is a speed dial number', function() { + assert.isTrue(subject._isSpeedDialNumber(speedDialNum)); + }); + + test('123 is not a speed dial number', function() { + assert.isFalse(subject._isSpeedDialNumber('123')); + }); + + test('*#31# is not a speed dial number', function() { + assert.isFalse(subject._isSpeedDialNumber('*#31#')); + }); + + test('Starts speed dial upon typing ' + speedDialNum, function() { + var node; + var fakeEventStart; + var fakeEventEnd; + + node = document.createElement('div'); + fakeEventStart = { + target: node, + stopPropagation: function() {}, + type: 'touchstart' + }; + fakeEventEnd = { + target: node, + stopPropagation: function() {}, + type: 'touchend' + }; + + this.sinon.stub(KeypadManager, '_getSpeedDialNumber', function() { + return Promise.resolve('123'); + }); + + for (var i = 0, end = speedDialNum.length; i < end; i++) { + fakeEventStart.target.dataset.value = speedDialNum.charAt(i); + subject.keyHandler(fakeEventStart); + fakeEventEnd.target.dataset.value = speedDialNum.charAt(i); + subject.keyHandler(fakeEventEnd); + } + + sinon.assert.calledWith(KeypadManager._getSpeedDialNumber, 1); + }); + + suite('Getting a speed dial number', function() { + var speedDialIndex = '1'; + var numbers = [ '123', '456' ]; + + suiteSetup(function() { + navigator.mozIccManager.adnContacts = [ + { + id: numbers[1], + tel: [ { value: numbers[1] } ] + }, + { + id: numbers[0], + tel: [ { value: numbers[0] } ] + } + ]; + }); + + setup(function() { + this.sinon.spy(ConfirmDialog, 'show'); + this.sinon.spy(ConfirmDialog, 'hide'); + }); + + test('The overlay is displayed and then hidden', function(done) { + subject._getSimContactsList(0).then(function() { + sinon.assert.calledOnce(ConfirmDialog.show); + sinon.assert.calledOnce(ConfirmDialog.hide); + }).then(done, done); + }); + + test('Cancelling the overlay works', function(done) { + navigator.mozIccManager.async = true; + + var p = subject._getSimContactsList(0); + ConfirmDialog.executeNo(); + + p.then(function() { + assert.ok(false, 'Should not succeed'); + }, function() { + sinon.assert.calledOnce(ConfirmDialog.show); + sinon.assert.calledOnce(ConfirmDialog.hide); + }).then(done, done); + + navigator.mozIccManager.async = false; + }); + + test('The contacts are returned sorted by ID', function(done) { + subject._getSimContactsList(0).then(function(contacts) { + assert.equal(contacts[0].id, numbers[0]); + assert.equal(contacts[1].id, numbers[1]); + }).then(done, done); + }); + + test('The proper number is returned', function(done) { + subject._getSpeedDialNumber(speedDialIndex).then(function(number) { + assert.equal(number, numbers[0]); + }).then(done, done); + }); + + test('0# is ignored', function(done) { + subject._getSpeedDialNumber('0').then(function(number) { + assert.ok(false, 'the promise should be rejected'); + }).then(done, done); + }); + + test('The SIM picker is used when there is no default SIM', + function(done) { + var simPicker = document.getElementById('sim-picker'); + navigator.mozIccManager.iccIds[0] = 0; + navigator.mozIccManager.iccIds[1] = 1; + + this.sinon.stub(SimSettingsHelper, 'getCardIndexFrom'); + this.sinon.spy(simPicker, 'getOrPick'); + + var p = subject._getSpeedDialNumber(speedDialIndex); + SimSettingsHelper.getCardIndexFrom + .yield(SimSettingsHelper.ALWAYS_ASK_OPTION_VALUE); + simPicker.getOrPick.yield(0); + p.then(function(number) { + sinon.assert.calledOnce(simPicker.getOrPick); + assert.equal(number, numbers[0]); + }).then(done, done); + }); + }); + }); }); suite('Initializing MultiSimActionButton', function() { diff --git a/shared/js/dialer/keypad.js b/shared/js/dialer/keypad.js index ef88d1a7b39d..75bdf2e631dd 100644 --- a/shared/js/dialer/keypad.js +++ b/shared/js/dialer/keypad.js @@ -1,8 +1,9 @@ /* exported KeypadManager */ /* globals AddContactMenu, CallHandler, CallLogDBManager, CallsHandler, - CallScreen, CustomDialog, DtmfTone, FontSizeManager, LazyLoader, - LazyL10n, MultiSimActionButton, SettingsListener, TonePlayer */ + CallScreen, ConfirmDialog, CustomDialog, DtmfTone, FontSizeManager, + LazyLoader, LazyL10n, MultiSimActionButton, Promise, + SimSettingsHelper, SettingsListener, TonePlayer */ 'use strict'; @@ -436,6 +437,17 @@ var KeypadManager = { TonePlayer.stop(); } + // Handle speed dial numbers + if (this._isSpeedDialNumber(this._phoneNumber)) { + var self = this; + var index = this._phoneNumber.slice(0, -1); // Remove the trailing '#' + + this._getSpeedDialNumber(+index).then( + function(number) { + self.updatePhoneNumber(number, 'begin', false); + }); + } + // If it was a long press our work is already done if (this._longPress) { this._longPress = false; @@ -504,6 +516,139 @@ var KeypadManager = { } }, + /** + * Returns true if the number is a speed dial code as described in + * 3GPP TS 22.030 6.6.4. Speed dial codes are in the N(N)(N)# format. + */ + _isSpeedDialNumber: function(number) { + return !!number.match(/^[0-9][0-9]{0,2}\#$/); + }, + + /** + * Returns the telephony number corresponding to the specified index. Speed + * dial numbers are retrieved from the SIM contacts list and not from the + * regular contacts. + * + * @param {Integer} index The index of the speed dial number. + * @returns {Promise} A promise that resolves to the corresponding number. + */ + _getSpeedDialNumber: function(index) { + var self = this; + var cardIndex; + + index--; // Speed dial indexes are 1-based + + return new Promise(function(resolve, reject) { + LazyLoader.load(['/shared/js/sim_settings_helper.js'], function() { + SimSettingsHelper.getCardIndexFrom('outgoingCall', + function(defaultCardIndex) { + if (defaultCardIndex == SimSettingsHelper.ALWAYS_ASK_OPTION_VALUE) { + LazyLoader.load(['/shared/js/component_utils.js', + '/shared/elements/gaia_sim_picker/script.js'], + function() { + var simPicker = document.getElementById('sim-picker'); + simPicker.getOrPick(defaultCardIndex, null, + function(pickedCardIndex) { + cardIndex = pickedCardIndex; + resolve(); + }); + }); + } else { + cardIndex = defaultCardIndex; + resolve(); + } + }); + }); + }).then(function() { + return self._getSimContactsList(cardIndex).then( + function(simContactsList) { + if ((index >= 0) && (index < simContactsList.length)) { + return simContactsList[index].number; + } else { + return Promise.reject(); + } + }); + }); + }, + + /** + * Creates an array of contacts populated using the ADN contacts retrieved + * from a SIM card. Every contact will contain only the ID and first + * telephone number and the array will be sorted by ID. This array is then + * suitable to be used to pick speed dial numbers. + * + * @param {Array} contacts An array of mozContact elements. + * @returns {Array} An array of telephone numbers / ID couples sorted by ID. + */ + _createSimContactList: function(contacts) { + var numbers = new Array(contacts.length); + + for (var i = 0; i < contacts.length; i++) { + numbers[i] = { + id: contacts[i].id, + number: contacts[i].tel[0].value, + }; + } + + numbers.sort(function(a, b) { + if (a.id.length == b.id.length) { + return (a.id > b.id) ? 1 : 0; + } else { + return (a.id.length > b.id.length) ? 1 : 0; + } + }); + + return numbers; + }, + + /** + * Gets the SIM contacts list for the specified SIM card. + * + * @param {Integer} cardIndex The SIM card index. + * @returns {Promise} A promise that is resolved with the contacts list. + */ + _getSimContactsList: function(cardIndex) { + var self = this; + var canceled = false; + + return new Promise(function(resolve, reject) { + LazyLoader.load(['/shared/style/confirm.css', + '/shared/js/confirm.js', + document.getElementById('confirmation-message')], + function() { + var iccId = navigator.mozIccManager.iccIds[cardIndex]; + var icc = navigator.mozIccManager.getIccById(iccId); + var req = icc.readContacts('adn'); + + req.onsuccess = function(event) { + var adnContacts = event.target.result; + var contacts = self._createSimContactList(adnContacts); + + if (!canceled) { + ConfirmDialog.hide(); + } + + resolve(contacts); + }; + req.onerror = function(error) { + console.error('Could not retrieve the ADN contacts from SIM card ' + + cardIndex + ', got error ' + error.name); + reject(); + }; + + ConfirmDialog.show('loadingContacts', null, { + title: 'cancel', + callback: function() { + canceled = true; + ConfirmDialog.hide(); + reject(); + } + }); + } + ); + }); + }, + sanitizePhoneNumber: function(number) { return number.replace(/\s+/g, ''); }, diff --git a/shared/test/unit/mocks/mock_iccmanager.js b/shared/test/unit/mocks/mock_iccmanager.js index 0468dd76eccd..32d6b5139813 100644 --- a/shared/test/unit/mocks/mock_iccmanager.js +++ b/shared/test/unit/mocks/mock_iccmanager.js @@ -8,6 +8,7 @@ var MockIccManager = function() { this.sdnContacts = []; this.isAdnOnError = false; this.isSdnOnError = false; + this.async = false; }; MockIccManager.prototype.getIccById = function(id) { @@ -18,34 +19,55 @@ MockIccManager.prototype.getIccById = function(id) { }, 'readContacts': function(type) { if (type === 'adn') { - return new MockReadContactsRequest(self.isAdnOnError, self.adnContacts); + return new MockReadContactsRequest(self.isAdnOnError, self.adnContacts, + self.async); } if (type === 'sdn') { - return new MockReadContactsRequest(self.isSdnOnError, self.sdnContacts); + return new MockReadContactsRequest(self.isSdnOnError, self.sdnContacts, + self.async); } } }; }; -function MockReadContactsRequest(willFail, result) { +function MockReadContactsRequest(willFail, result, async) { this._result = result; this._willFail = willFail; + this._async = async; } MockReadContactsRequest.prototype = { set onsuccess(successCb) { + var self = this; + if (!this._willFail) { this.result = this._result; - (typeof successCb === 'function') && - successCb(); + if (typeof successCb === 'function') { + if (this._async) { + setTimeout(function() { + successCb({ target: self }); + }); + } else { + successCb({ target: this }); + } + } } }, set onerror(errorCb) { + var self = this; + if (this._willFail) { this.error = { name: 'error' }; - (typeof errorCb === 'function') && - errorCb(); + if (typeof errorCb === 'function') { + if (this._async) { + setTimeout(function() { + errorCb(self.error); + }); + } else { + errorCb(this.error); + } + } } } };