1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
4 * You can obtain one at http://mozilla.org/MPL/2.0/. */
8 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
10 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
11 Cu.import("resource://gre/modules/Services.jsm");
12 Cu.import("resource://gre/modules/Promise.jsm");
13 Cu.import("resource://gre/modules/systemlibs.js");
15 XPCOMUtils.defineLazyGetter(this, "RIL", function () {
17 Cu.import("resource://gre/modules/ril_consts.js", obj);
21 const GONK_TELEPHONYSERVICE_CONTRACTID =
22 "@mozilla.org/telephony/gonktelephonyservice;1";
23 const GONK_TELEPHONYSERVICE_CID =
24 Components.ID("{67d26434-d063-4d28-9f48-5b3189788155}");
26 const NS_XPCOM_SHUTDOWN_OBSERVER_ID = "xpcom-shutdown";
28 const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
30 const kPrefRilNumRadioInterfaces = "ril.numRadioInterfaces";
31 const kPrefRilDebuggingEnabled = "ril.debugging.enabled";
32 const kPrefDefaultServiceId = "dom.telephony.defaultServiceId";
34 const nsIAudioManager = Ci.nsIAudioManager;
35 const nsITelephonyService = Ci.nsITelephonyService;
37 const CALL_WAKELOCK_TIMEOUT = 5000;
39 // Index of the CDMA second call which isn't held in RIL but only in TelephoyService.
40 const CDMA_SECOND_CALL_INDEX = 2;
42 const DIAL_ERROR_INVALID_STATE_ERROR = "InvalidStateError";
43 const DIAL_ERROR_OTHER_CONNECTION_IN_USE = "OtherConnectionInUse";
44 const DIAL_ERROR_BAD_NUMBER = RIL.GECKO_CALL_ERROR_BAD_NUMBER;
46 const AUDIO_STATE_NAME = [
48 "PHONE_STATE_RINGTONE",
52 const DEFAULT_EMERGENCY_NUMBERS = ["112", "911"];
56 dump("TelephonyService: " + s + "\n");
59 XPCOMUtils.defineLazyGetter(this, "gAudioManager", function getAudioManager() {
61 return Cc["@mozilla.org/telephony/audiomanager;1"]
62 .getService(nsIAudioManager);
64 //TODO on the phone this should not fall back as silently.
66 // Fake nsIAudioManager implementation so that we can run the telephony
67 // code in a non-Gonk build.
68 if (DEBUG) debug("Using fake audio manager.");
70 microphoneMuted: false,
73 phoneState: nsIAudioManager.PHONE_STATE_CURRENT,
76 setForceForUse: function(usage, force) {
77 this._forceForUse[usage] = force;
80 getForceForUse: function(usage) {
81 return this._forceForUse[usage] || nsIAudioManager.FORCE_NONE;
87 XPCOMUtils.defineLazyServiceGetter(this, "gRadioInterfaceLayer",
89 "nsIRadioInterfaceLayer");
91 XPCOMUtils.defineLazyServiceGetter(this, "gPowerManagerService",
92 "@mozilla.org/power/powermanagerservice;1",
93 "nsIPowerManagerService");
95 XPCOMUtils.defineLazyServiceGetter(this, "gSystemMessenger",
96 "@mozilla.org/system-message-internal;1",
97 "nsISystemMessagesInternal");
99 XPCOMUtils.defineLazyGetter(this, "gPhoneNumberUtils", function() {
101 Cu.import("resource://gre/modules/PhoneNumberUtils.jsm", ns);
102 return ns.PhoneNumberUtils;
105 function TelephonyService() {
106 this._numClients = gRadioInterfaceLayer.numRadioInterfaces;
107 this._listeners = [];
108 this._currentCalls = {};
109 this._audioStates = {};
111 this._cdmaCallWaitingNumber = null;
113 // _isActiveCall[clientId][callIndex] shows the active status of the call.
114 this._isActiveCall = {};
115 this._numActiveCall = 0;
117 this._updateDebugFlag();
118 this.defaultServiceId = this._getDefaultServiceId();
120 Services.prefs.addObserver(kPrefRilDebuggingEnabled, this, false);
121 Services.prefs.addObserver(kPrefDefaultServiceId, this, false);
123 Services.obs.addObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
125 for (let i = 0; i < this._numClients; ++i) {
126 this._enumerateCallsForClient(i);
127 this._isActiveCall[i] = {};
128 this._audioStates[i] = RIL.AUDIO_STATE_NO_CALL;
131 TelephonyService.prototype = {
132 classID: GONK_TELEPHONYSERVICE_CID,
133 classInfo: XPCOMUtils.generateCI({classID: GONK_TELEPHONYSERVICE_CID,
134 contractID: GONK_TELEPHONYSERVICE_CONTRACTID,
135 classDescription: "TelephonyService",
136 interfaces: [Ci.nsITelephonyService,
137 Ci.nsIGonkTelephonyService],
138 flags: Ci.nsIClassInfo.SINGLETON}),
139 QueryInterface: XPCOMUtils.generateQI([Ci.nsITelephonyService,
140 Ci.nsIGonkTelephonyService,
143 // The following attributes/functions are used for acquiring/releasing the
144 // CPU wake lock when the RIL handles the incoming call. Note that we need
145 // a timer to bound the lock's life cycle to avoid exhausting the battery.
146 _callRingWakeLock: null,
147 _callRingWakeLockTimer: null,
149 _acquireCallRingWakeLock: function() {
150 if (!this._callRingWakeLock) {
151 if (DEBUG) debug("Acquiring a CPU wake lock for handling incoming call.");
152 this._callRingWakeLock = gPowerManagerService.newWakeLock("cpu");
154 if (!this._callRingWakeLockTimer) {
155 if (DEBUG) debug("Creating a timer for releasing the CPU wake lock.");
156 this._callRingWakeLockTimer =
157 Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
159 if (DEBUG) debug("Setting the timer for releasing the CPU wake lock.");
160 this._callRingWakeLockTimer
161 .initWithCallback(this._releaseCallRingWakeLock.bind(this),
162 CALL_WAKELOCK_TIMEOUT, Ci.nsITimer.TYPE_ONE_SHOT);
165 _releaseCallRingWakeLock: function() {
166 if (DEBUG) debug("Releasing the CPU wake lock for handling incoming call.");
167 if (this._callRingWakeLockTimer) {
168 this._callRingWakeLockTimer.cancel();
170 if (this._callRingWakeLock) {
171 this._callRingWakeLock.unlock();
172 this._callRingWakeLock = null;
176 _getClient: function(aClientId) {
177 return gRadioInterfaceLayer.getRadioInterface(aClientId);
180 _sendToRilWorker: function(aClientId, aType, aMessage, aCallback) {
181 this._getClient(aClientId).sendWorkerMessage(aType, aMessage, aCallback);
184 // An array of nsITelephonyListener instances.
186 _notifyAllListeners: function(aMethodName, aArgs) {
187 let listeners = this._listeners.slice();
188 for (let listener of listeners) {
189 if (this._listeners.indexOf(listener) == -1) {
190 // Listener has been unregistered in previous run.
194 let handler = listener[aMethodName];
196 handler.apply(listener, aArgs);
198 debug("listener for " + aMethodName + " threw an exception: " + e);
204 * Track the active call and update the audio system as its state changes.
206 _updateActiveCall: function(aCall) {
208 let incoming = false;
210 switch (aCall.state) {
211 case nsITelephonyService.CALL_STATE_DIALING: // Fall through...
212 case nsITelephonyService.CALL_STATE_ALERTING:
213 case nsITelephonyService.CALL_STATE_CONNECTED:
216 case nsITelephonyService.CALL_STATE_INCOMING:
219 case nsITelephonyService.CALL_STATE_HELD: // Fall through...
220 case nsITelephonyService.CALL_STATE_DISCONNECTED:
224 // Update active count and info.
225 let oldActive = this._isActiveCall[aCall.clientId][aCall.callIndex];
226 if (!oldActive && active) {
227 this._numActiveCall++;
228 } else if (oldActive && !active) {
229 this._numActiveCall--;
231 this._isActiveCall[aCall.clientId][aCall.callIndex] = active;
234 _updateAudioState: function(aAudioState) {
235 switch (aAudioState) {
236 case RIL.AUDIO_STATE_NO_CALL:
237 gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_NORMAL;
240 case RIL.AUDIO_STATE_INCOMING:
241 gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_RINGTONE;
244 case RIL.AUDIO_STATE_IN_CALL:
245 gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_IN_CALL;
246 if (this.speakerEnabled) {
247 gAudioManager.setForceForUse(nsIAudioManager.USE_COMMUNICATION,
248 nsIAudioManager.FORCE_SPEAKER);
254 debug("Put audio system into " + AUDIO_STATE_NAME[aAudioState] + ": " +
255 aAudioState + ", result is: " + gAudioManager.phoneState);
259 _convertRILCallState: function(aState) {
261 case RIL.CALL_STATE_UNKNOWN:
262 return nsITelephonyService.CALL_STATE_UNKNOWN;
263 case RIL.CALL_STATE_ACTIVE:
264 return nsITelephonyService.CALL_STATE_CONNECTED;
265 case RIL.CALL_STATE_HOLDING:
266 return nsITelephonyService.CALL_STATE_HELD;
267 case RIL.CALL_STATE_DIALING:
268 return nsITelephonyService.CALL_STATE_DIALING;
269 case RIL.CALL_STATE_ALERTING:
270 return nsITelephonyService.CALL_STATE_ALERTING;
271 case RIL.CALL_STATE_INCOMING:
272 case RIL.CALL_STATE_WAITING:
273 return nsITelephonyService.CALL_STATE_INCOMING;
275 throw new Error("Unknown rilCallState: " + aState);
279 _convertRILSuppSvcNotification: function(aNotification) {
280 switch (aNotification) {
281 case RIL.GECKO_SUPP_SVC_NOTIFICATION_REMOTE_HELD:
282 return nsITelephonyService.NOTIFICATION_REMOTE_HELD;
283 case RIL.GECKO_SUPP_SVC_NOTIFICATION_REMOTE_RESUMED:
284 return nsITelephonyService.NOTIFICATION_REMOTE_RESUMED;
287 debug("Unknown rilSuppSvcNotification: " + aNotification);
293 _updateDebugFlag: function() {
295 DEBUG = RIL.DEBUG_RIL ||
296 Services.prefs.getBoolPref(kPrefRilDebuggingEnabled);
300 _getDefaultServiceId: function() {
301 let id = Services.prefs.getIntPref(kPrefDefaultServiceId);
302 let numRil = Services.prefs.getIntPref(kPrefRilNumRadioInterfaces);
304 if (id >= numRil || id < 0) {
312 _enumerateCallsForClient: function(aClientId) {
313 if (DEBUG) debug("Enumeration of calls for client " + aClientId);
315 this._sendToRilWorker(aClientId, "enumerateCalls", null, response => {
316 if (!this._currentCalls[aClientId]) {
317 this._currentCalls[aClientId] = {};
319 for (let call of response.calls) {
320 call.clientId = aClientId;
321 call.state = this._convertRILCallState(call.state);
322 call.isSwitchable = true;
323 call.isMergeable = true;
325 this._currentCalls[aClientId][call.callIndex] = call;
331 * Check a given number against the list of emergency numbers provided by the
335 * The number to look up.
337 _isEmergencyNumber: function(aNumber) {
338 // Check ril provided numbers first.
339 let numbers = libcutils.property_get("ril.ecclist") ||
340 libcutils.property_get("ro.ril.ecclist");
342 numbers = numbers.split(",");
344 // No ecclist system property, so use our own list.
345 numbers = DEFAULT_EMERGENCY_NUMBERS;
347 return numbers.indexOf(aNumber) != -1;
351 * Checks whether to temporarily suppress caller id for the call.
356 _isTemporaryCLIR: function(aMmi) {
357 return (aMmi && aMmi.serviceCode === RIL.MMI_SC_CLIR) && aMmi.dialNumber;
361 * Map MMI procedure to CLIR MODE.
366 _getTemporaryCLIRMode: function(aProcedure) {
367 // In temporary mode, MMI_PROCEDURE_ACTIVATION means allowing CLI
368 // presentation, i.e. CLIR_SUPPRESSION. See TS 22.030, Annex B.
369 switch (aProcedure) {
370 case RIL.MMI_PROCEDURE_ACTIVATION:
371 return RIL.CLIR_SUPPRESSION;
372 case RIL.MMI_PROCEDURE_DEACTIVATION:
373 return RIL.CLIR_INVOCATION;
375 return RIL.CLIR_DEFAULT;
380 * nsITelephonyService interface.
385 registerListener: function(aListener) {
386 if (this._listeners.indexOf(aListener) >= 0) {
387 throw Cr.NS_ERROR_UNEXPECTED;
390 this._listeners.push(aListener);
393 unregisterListener: function(aListener) {
394 let index = this._listeners.indexOf(aListener);
396 throw Cr.NS_ERROR_UNEXPECTED;
399 this._listeners.splice(index, 1);
402 enumerateCalls: function(aListener) {
403 if (DEBUG) debug("Requesting enumeration of calls for callback");
405 for (let cid = 0; cid < this._numClients; ++cid) {
406 let calls = this._currentCalls[cid];
410 for (let i = 0, indexes = Object.keys(calls); i < indexes.length; ++i) {
411 let call = calls[indexes[i]];
412 aListener.enumerateCallState(call.clientId, call.callIndex,
413 call.state, call.number,
414 call.numberPresentation, call.name,
415 call.namePresentation, call.isOutgoing,
416 call.isEmergency, call.isConference,
417 call.isSwitchable, call.isMergeable);
420 aListener.enumerateCallStateComplete();
423 _hasCallsOnOtherClient: function(aClientId) {
424 for (let cid = 0; cid < this._numClients; ++cid) {
425 if (cid === aClientId) {
428 if (Object.keys(this._currentCalls[cid]).length !== 0) {
435 // All calls in the conference is regarded as one conference call.
436 _numCallsOnLine: function(aClientId) {
438 let hasConference = false;
440 for (let cid in this._currentCalls[aClientId]) {
441 let call = this._currentCalls[aClientId][cid];
442 if (call.isConference) {
443 hasConference = true;
449 return hasConference ? numCalls + 1 : numCalls;
453 * Get arbitrary one of active call.
455 _getOneActiveCall: function(aClientId) {
456 for (let index in this._currentCalls[aClientId]) {
457 let call = this._currentCalls[aClientId][index];
458 if (call.state === nsITelephonyService.CALL_STATE_CONNECTED) {
465 _addCdmaChildCall: function(aClientId, aNumber, aParentId) {
467 callIndex: CDMA_SECOND_CALL_INDEX,
468 state: RIL.CALL_STATE_DIALING,
478 // Manual update call state according to the request response.
479 this.notifyCallStateChanged(aClientId, childCall);
481 childCall.state = RIL.CALL_STATE_ACTIVE;
482 this.notifyCallStateChanged(aClientId, childCall);
484 let parentCall = this._currentCalls[aClientId][childCall.parentId];
485 parentCall.childId = CDMA_SECOND_CALL_INDEX;
486 parentCall.state = RIL.CALL_STATE_HOLDING;
487 parentCall.isSwitchable = false;
488 parentCall.isMergeable = true;
489 this.notifyCallStateChanged(aClientId, parentCall);
492 _composeDialRequest: function(aClientId, aNumber) {
493 return new Promise((resolve, reject) => {
494 this._sendToRilWorker(aClientId, "parseMMIFromDialNumber",
495 {number: aNumber}, response => {
497 let mmi = response.mmi;
503 } else if (this._isTemporaryCLIR(mmi)) {
505 number: mmi.dialNumber,
506 clirMode: this._getTemporaryCLIRMode(mmi.procedure)
509 reject(DIAL_ERROR_BAD_NUMBER);
515 cachedDialRequest: null,
518 dial: function(aClientId, aNumber, aIsDialEmergency, aCallback) {
519 if (DEBUG) debug("Dialing " + (aIsDialEmergency ? "emergency " : "") + aNumber);
521 if (this.isDialing) {
522 if (DEBUG) debug("Error: Already has a dialing call.");
523 aCallback.notifyError(DIAL_ERROR_INVALID_STATE_ERROR);
527 // We can only have at most two calls on the same line (client).
528 if (this._numCallsOnLine(aClientId) >= 2) {
529 if (DEBUG) debug("Error: Already has more than 2 calls on line.");
530 aCallback.notifyError(DIAL_ERROR_INVALID_STATE_ERROR);
534 // For DSDS, if there is aleady a call on SIM 'aClientId', we cannot place
535 // any new call on other SIM.
536 if (this._hasCallsOnOtherClient(aClientId)) {
537 if (DEBUG) debug("Error: Already has a call on other sim.");
538 aCallback.notifyError(DIAL_ERROR_OTHER_CONNECTION_IN_USE);
542 // We don't try to be too clever here, as the phone is probably in the
543 // locked state. Let's just check if it's a number without normalizing
544 if (!aIsDialEmergency) {
545 aNumber = gPhoneNumberUtils.normalize(aNumber);
548 // Validate the number.
549 // Note: isPlainPhoneNumber also accepts USSD and SS numbers
550 if (!gPhoneNumberUtils.isPlainPhoneNumber(aNumber)) {
551 if (DEBUG) debug("Error: Number '" + aNumber + "' is not viable. Drop.");
552 aCallback.notifyError(DIAL_ERROR_BAD_NUMBER);
556 this._composeDialRequest(aClientId, aNumber).then(options => {
557 options.isEmergency = this._isEmergencyNumber(options.number);
558 options.isDialEmergency = aIsDialEmergency;
560 if (options.isEmergency) {
561 // Automatically select a proper clientId for emergency call.
562 aClientId = gRadioInterfaceLayer.getClientIdForEmergencyCall() ;
563 if (aClientId === -1) {
564 if (DEBUG) debug("Error: No client is avaialble for emergency call.");
565 aCallback.notifyError(DIAL_ERROR_INVALID_STATE_ERROR);
570 // Before we dial, we have to hold the active call first.
571 let activeCall = this._getOneActiveCall(aClientId);
573 this._dialInternal(aClientId, options, aCallback);
575 if (DEBUG) debug("There is an active call. Hold it first before dial.");
577 this.cachedDialRequest = {
583 if (activeCall.isConference) {
584 this.holdConference(aClientId);
586 this.holdCall(aClientId, activeCall.callIndex);
590 aCallback.notifyError(DIAL_ERROR_BAD_NUMBER);
594 _dialInternal: function(aClientId, aOptions, aCallback) {
595 this.isDialing = true;
597 this._sendToRilWorker(aClientId, "dial", aOptions, response => {
598 this.isDialing = false;
600 if (!response.success) {
601 aCallback.notifyError(response.errorMsg);
605 let currentCdmaCallIndex = !response.isCdma ? null :
606 Object.keys(this._currentCalls[aClientId])[0];
608 if (currentCdmaCallIndex == null) {
609 aCallback.notifyDialCallSuccess(response.callIndex, response.number);
611 // RIL doesn't hold the 2nd call. We create one by ourselves.
612 aCallback.notifyDialCallSuccess(CDMA_SECOND_CALL_INDEX, response.number);
613 this._addCdmaChildCall(aClientId, response.number, currentCdmaCallIndex);
618 hangUp: function(aClientId, aCallIndex) {
619 let parentId = this._currentCalls[aClientId][aCallIndex].parentId;
621 // Should release both, child and parent, together. Since RIL holds only
622 // the parent call, we send 'parentId' to RIL.
623 this.hangUp(aClientId, parentId);
625 this._sendToRilWorker(aClientId, "hangUp", { callIndex: aCallIndex });
629 startTone: function(aClientId, aDtmfChar) {
630 this._sendToRilWorker(aClientId, "startTone", { dtmfChar: aDtmfChar });
633 stopTone: function(aClientId) {
634 this._sendToRilWorker(aClientId, "stopTone");
637 answerCall: function(aClientId, aCallIndex) {
638 this._sendToRilWorker(aClientId, "answerCall", { callIndex: aCallIndex });
641 rejectCall: function(aClientId, aCallIndex) {
642 this._sendToRilWorker(aClientId, "rejectCall", { callIndex: aCallIndex });
645 holdCall: function(aClientId, aCallIndex) {
646 let call = this._currentCalls[aClientId][aCallIndex];
647 if (!call || !call.isSwitchable) {
648 // TODO: Bug 975949 - [B2G] Telephony should throw exceptions when some
649 // operations aren't allowed instead of simply ignoring them.
653 this._sendToRilWorker(aClientId, "holdCall", { callIndex: aCallIndex });
656 resumeCall: function(aClientId, aCallIndex) {
657 let call = this._currentCalls[aClientId][aCallIndex];
658 if (!call || !call.isSwitchable) {
659 // TODO: Bug 975949 - [B2G] Telephony should throw exceptions when some
660 // operations aren't allowed instead of simply ignoring them.
664 this._sendToRilWorker(aClientId, "resumeCall", { callIndex: aCallIndex });
667 conferenceCall: function(aClientId) {
668 let indexes = Object.keys(this._currentCalls[aClientId]);
669 if (indexes.length < 2) {
670 // TODO: Bug 975949 - [B2G] Telephony should throw exceptions when some
671 // operations aren't allowed instead of simply ignoring them.
675 for (let i = 0; i < indexes.length; ++i) {
676 let call = this._currentCalls[aClientId][indexes[i]];
677 if (!call.isMergeable) {
682 function onCdmaConferenceCallSuccess() {
683 let indexes = Object.keys(this._currentCalls[aClientId]);
684 if (indexes.length < 2) {
688 for (let i = 0; i < indexes.length; ++i) {
689 let call = this._currentCalls[aClientId][indexes[i]];
690 call.state = RIL.CALL_STATE_ACTIVE;
691 call.isConference = true;
692 this.notifyCallStateChanged(aClientId, call);
694 this.notifyConferenceCallStateChanged(RIL.CALL_STATE_ACTIVE);
697 this._sendToRilWorker(aClientId, "conferenceCall", null, response => {
698 if (!response.success) {
699 this._notifyAllListeners("notifyConferenceError", [response.errorName,
704 if (response.isCdma) {
705 onCdmaConferenceCallSuccess.call(this);
710 separateCall: function(aClientId, aCallIndex) {
711 let call = this._currentCalls[aClientId][aCallIndex];
712 if (!call || !call.isConference) {
713 // TODO: Bug 975949 - [B2G] Telephony should throw exceptions when some
714 // operations aren't allowed instead of simply ignoring them.
718 let parentId = call.parentId;
720 this.separateCall(aClientId, parentId);
724 function onCdmaSeparateCallSuccess() {
725 // See 3gpp2, S.R0006-522-A v1.0. Table 4, XID 6S.
726 let call = this._currentCalls[aClientId][aCallIndex];
727 if (!call || !call.isConference) {
731 let childId = call.childId;
736 let childCall = this._currentCalls[aClientId][childId];
737 this.notifyCallDisconnected(aClientId, childCall);
740 this._sendToRilWorker(aClientId, "separateCall", { callIndex: aCallIndex },
742 if (!response.success) {
743 this._notifyAllListeners("notifyConferenceError", [response.errorName,
748 if (response.isCdma) {
749 onCdmaSeparateCallSuccess.call(this);
754 hangUpConference: function(aClientId, aCallback) {
755 this._sendToRilWorker(aClientId, "hangUpConference", null, response => {
756 if (!response.success) {
757 aCallback.notifyError(response.errorMsg);
759 aCallback.notifySuccess();
764 holdConference: function(aClientId) {
765 this._sendToRilWorker(aClientId, "holdConference");
768 resumeConference: function(aClientId) {
769 this._sendToRilWorker(aClientId, "resumeConference");
772 get microphoneMuted() {
773 return gAudioManager.microphoneMuted;
776 set microphoneMuted(aMuted) {
777 if (aMuted == this.microphoneMuted) {
780 gAudioManager.microphoneMuted = aMuted;
783 get speakerEnabled() {
784 let force = gAudioManager.getForceForUse(nsIAudioManager.USE_COMMUNICATION);
785 return (force == nsIAudioManager.FORCE_SPEAKER);
788 set speakerEnabled(aEnabled) {
789 if (aEnabled == this.speakerEnabled) {
792 let force = aEnabled ? nsIAudioManager.FORCE_SPEAKER :
793 nsIAudioManager.FORCE_NONE;
794 gAudioManager.setForceForUse(nsIAudioManager.USE_COMMUNICATION, force);
798 * nsIGonkTelephonyService interface.
801 notifyAudioStateChanged: function(aClientId, aState) {
802 this._audioStates[aClientId] = aState;
804 let audioState = aState;
805 for (let i = 0; i < this._numClients; ++i) {
806 audioState = Math.max(audioState, this._audioStates[i]);
809 this._updateAudioState(audioState);
813 * Handle call disconnects by updating our current state and the audio system.
815 notifyCallDisconnected: function(aClientId, aCall) {
816 if (DEBUG) debug("handleCallDisconnected: " + JSON.stringify(aCall));
818 aCall.clientId = aClientId;
819 aCall.state = nsITelephonyService.CALL_STATE_DISCONNECTED;
820 aCall.isEmergency = this._isEmergencyNumber(aCall.number);
821 let duration = ("started" in aCall && typeof aCall.started == "number") ?
822 new Date().getTime() - aCall.started : 0;
824 number: aCall.number,
825 serviceId: aClientId,
826 emergency: aCall.isEmergency,
828 direction: aCall.isOutgoing ? "outgoing" : "incoming",
829 hangUpLocal: aCall.hangUpLocal
832 if (this._cdmaCallWaitingNumber != null) {
833 data.secondNumber = this._cdmaCallWaitingNumber;
834 this._cdmaCallWaitingNumber = null;
837 gSystemMessenger.broadcastMessage("telephony-call-ended", data);
839 let manualConfStateChange = false;
840 let childId = this._currentCalls[aClientId][aCall.callIndex].childId;
842 // Child cannot live without parent.
843 let childCall = this._currentCalls[aClientId][childId];
844 this.notifyCallDisconnected(aClientId, childCall);
846 let parentId = this._currentCalls[aClientId][aCall.callIndex].parentId;
848 let parentCall = this._currentCalls[aClientId][parentId];
849 // The child is going to be released.
850 delete parentCall.childId;
851 if (parentCall.isConference) {
852 // As the child is going to be gone, the parent should be moved out
853 // of conference accordingly.
854 manualConfStateChange = true;
855 parentCall.isConference = false;
856 parentCall.isSwitchable = true;
857 parentCall.isMergeable = true;
858 aCall.isConference = false;
859 this.notifyCallStateChanged(aClientId, parentCall, true);
864 this._updateActiveCall(aCall);
866 if (!aCall.failCause ||
867 aCall.failCause === RIL.GECKO_CALL_ERROR_NORMAL_CALL_CLEARING) {
868 this._notifyAllListeners("callStateChanged", [aClientId,
872 aCall.numberPresentation,
874 aCall.namePresentation,
881 this._notifyAllListeners("notifyError",
882 [aClientId, aCall.callIndex, aCall.failCause]);
884 delete this._currentCalls[aClientId][aCall.callIndex];
886 if (manualConfStateChange) {
887 this.notifyConferenceCallStateChanged(RIL.CALL_STATE_UNKNOWN);
892 * Handle an incoming call.
894 * Not much is known about this call at this point, but it's enough
895 * to start bringing up the Phone app already.
897 notifyCallRing: function() {
898 // We need to acquire a CPU wake lock to avoid the system falling into
899 // the sleep mode when the RIL handles the incoming call.
900 this._acquireCallRingWakeLock();
902 gSystemMessenger.broadcastMessage("telephony-new-call", {});
906 * Handle call state changes by updating our current state and the audio
909 notifyCallStateChanged: function(aClientId, aCall, aSkipStateConversion) {
910 if (DEBUG) debug("handleCallStateChange: " + JSON.stringify(aCall));
912 if (!aSkipStateConversion) {
913 aCall.state = this._convertRILCallState(aCall.state);
916 if (aCall.state == nsITelephonyService.CALL_STATE_DIALING) {
917 gSystemMessenger.broadcastMessage("telephony-new-call", {});
920 aCall.clientId = aClientId;
921 this._updateActiveCall(aCall);
923 function pick(arg, defaultValue) {
924 return typeof arg !== 'undefined' ? arg : defaultValue;
927 let call = this._currentCalls[aClientId][aCall.callIndex];
929 call.state = aCall.state;
930 call.number = aCall.number;
931 call.isEmergency = this._isEmergencyNumber(aCall.number);
932 call.isConference = aCall.isConference;
933 call.isSwitchable = pick(aCall.isSwitchable, call.isSwitchable);
934 call.isMergeable = pick(aCall.isMergeable, call.isMergeable);
937 call.isEmergency = pick(aCall.isEmergency, this._isEmergencyNumber(aCall.number));
938 call.isSwitchable = pick(aCall.isSwitchable, true);
939 call.isMergeable = pick(aCall.isMergeable, true);
940 call.name = pick(aCall.name, "");
941 call.numberPresentaation = pick(aCall.numberPresentation, nsITelephonyService.CALL_PRESENTATION_ALLOWED);
942 call.namePresentaation = pick(aCall.namePresentation, nsITelephonyService.CALL_PRESENTATION_ALLOWED);
944 this._currentCalls[aClientId][aCall.callIndex] = call;
947 // Handle cached dial request.
948 if (this.cachedDialRequest && !this._getOneActiveCall()) {
949 if (DEBUG) debug("All calls held. Perform the cached dial request.");
951 let request = this.cachedDialRequest;
952 this._dialInternal(request.clientId, request.options, request.callback);
953 this.cachedDialRequest = null;
956 this._notifyAllListeners("callStateChanged", [aClientId,
960 call.numberPresentation,
962 call.namePresentation,
970 notifyCdmaCallWaiting: function(aClientId, aCall) {
971 // We need to acquire a CPU wake lock to avoid the system falling into
972 // the sleep mode when the RIL handles the incoming call.
973 this._acquireCallRingWakeLock();
975 let call = this._currentCalls[aClientId][CDMA_SECOND_CALL_INDEX];
977 // TODO: Bug 977503 - B2G RIL: [CDMA] update callNumber when a waiting
978 // call comes after a 3way call.
979 this.notifyCallDisconnected(aClientId, call);
982 this._cdmaCallWaitingNumber = aCall.number;
984 this._notifyAllListeners("notifyCdmaCallWaiting", [aClientId,
986 aCall.numberPresentation,
988 aCall.namePresentation]);
991 notifySupplementaryService: function(aClientId, aCallIndex, aNotification) {
992 let notification = this._convertRILSuppSvcNotification(aNotification);
993 this._notifyAllListeners("supplementaryServiceNotification",
994 [aClientId, aCallIndex, notification]);
997 notifyConferenceCallStateChanged: function(aState) {
998 if (DEBUG) debug("handleConferenceCallStateChanged: " + aState);
999 aState = this._convertRILCallState(aState);
1000 this._notifyAllListeners("conferenceCallStateChanged", [aState]);
1004 * nsIObserver interface.
1007 observe: function(aSubject, aTopic, aData) {
1009 case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID:
1010 if (aData === kPrefRilDebuggingEnabled) {
1011 this._updateDebugFlag();
1012 } else if (aData === kPrefDefaultServiceId) {
1013 this.defaultServiceId = this._getDefaultServiceId();
1017 case NS_XPCOM_SHUTDOWN_OBSERVER_ID:
1018 // Release the CPU wake lock for handling the incoming call.
1019 this._releaseCallRingWakeLock();
1021 Services.obs.removeObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
1027 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TelephonyService]);