Bumping manifests a=b2g-bump
[gecko.git] / dom / telephony / gonk / TelephonyService.js
blob88c71148c25161a3ef960110545f51e81db29501
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/. */
6 "use strict";
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 () {
16   let obj = {};
17   Cu.import("resource://gre/modules/ril_consts.js", obj);
18   return obj;
19 });
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 = [
47   "PHONE_STATE_NORMAL",
48   "PHONE_STATE_RINGTONE",
49   "PHONE_STATE_IN_CALL"
52 const DEFAULT_EMERGENCY_NUMBERS = ["112", "911"];
54 let DEBUG;
55 function debug(s) {
56   dump("TelephonyService: " + s + "\n");
59 XPCOMUtils.defineLazyGetter(this, "gAudioManager", function getAudioManager() {
60   try {
61     return Cc["@mozilla.org/telephony/audiomanager;1"]
62              .getService(nsIAudioManager);
63   } catch (ex) {
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.");
69     return {
70       microphoneMuted: false,
71       masterVolume: 1.0,
72       masterMuted: false,
73       phoneState: nsIAudioManager.PHONE_STATE_CURRENT,
74       _forceForUse: {},
76       setForceForUse: function(usage, force) {
77         this._forceForUse[usage] = force;
78       },
80       getForceForUse: function(usage) {
81         return this._forceForUse[usage] || nsIAudioManager.FORCE_NONE;
82       }
83     };
84   }
85 });
87 XPCOMUtils.defineLazyServiceGetter(this, "gRadioInterfaceLayer",
88                                    "@mozilla.org/ril;1",
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() {
100   let ns = {};
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;
129   }
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,
141                                          Ci.nsIObserver]),
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");
153     }
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);
158     }
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);
163   },
165   _releaseCallRingWakeLock: function() {
166     if (DEBUG) debug("Releasing the CPU wake lock for handling incoming call.");
167     if (this._callRingWakeLockTimer) {
168       this._callRingWakeLockTimer.cancel();
169     }
170     if (this._callRingWakeLock) {
171       this._callRingWakeLock.unlock();
172       this._callRingWakeLock = null;
173     }
174   },
176   _getClient: function(aClientId) {
177     return gRadioInterfaceLayer.getRadioInterface(aClientId);
178   },
180   _sendToRilWorker: function(aClientId, aType, aMessage, aCallback) {
181     this._getClient(aClientId).sendWorkerMessage(aType, aMessage, aCallback);
182   },
184   // An array of nsITelephonyListener instances.
185   _listeners: null,
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.
191         continue;
192       }
194       let handler = listener[aMethodName];
195       try {
196         handler.apply(listener, aArgs);
197       } catch (e) {
198         debug("listener for " + aMethodName + " threw an exception: " + e);
199       }
200     }
201   },
203   /**
204    * Track the active call and update the audio system as its state changes.
205    */
206   _updateActiveCall: function(aCall) {
207     let active = false;
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:
214         active = true;
215         break;
216       case nsITelephonyService.CALL_STATE_INCOMING:
217         incoming = true;
218         break;
219       case nsITelephonyService.CALL_STATE_HELD: // Fall through...
220       case nsITelephonyService.CALL_STATE_DISCONNECTED:
221         break;
222     }
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--;
230     }
231     this._isActiveCall[aCall.clientId][aCall.callIndex] = active;
232   },
234   _updateAudioState: function(aAudioState) {
235     switch (aAudioState) {
236       case RIL.AUDIO_STATE_NO_CALL:
237         gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_NORMAL;
238         break;
240       case RIL.AUDIO_STATE_INCOMING:
241         gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_RINGTONE;
242         break;
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);
249         }
250         break;
251     }
253     if (DEBUG) {
254       debug("Put audio system into " + AUDIO_STATE_NAME[aAudioState] + ": " +
255             aAudioState + ", result is: " + gAudioManager.phoneState);
256     }
257   },
259   _convertRILCallState: function(aState) {
260     switch (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;
274       default:
275         throw new Error("Unknown rilCallState: " + aState);
276     }
277   },
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;
285       default:
286         if (DEBUG) {
287           debug("Unknown rilSuppSvcNotification: " + aNotification);
288         }
289         return;
290     }
291   },
293   _updateDebugFlag: function() {
294     try {
295       DEBUG = RIL.DEBUG_RIL ||
296               Services.prefs.getBoolPref(kPrefRilDebuggingEnabled);
297     } catch (e) {}
298   },
300   _getDefaultServiceId: function() {
301     let id = Services.prefs.getIntPref(kPrefDefaultServiceId);
302     let numRil = Services.prefs.getIntPref(kPrefRilNumRadioInterfaces);
304     if (id >= numRil || id < 0) {
305       id = 0;
306     }
308     return id;
309   },
311   _currentCalls: null,
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] = {};
318       }
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;
326       }
327     });
328   },
330   /**
331    * Check a given number against the list of emergency numbers provided by the
332    * RIL.
333    *
334    * @param aNumber
335    *        The number to look up.
336    */
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");
341     if (numbers) {
342       numbers = numbers.split(",");
343     } else {
344       // No ecclist system property, so use our own list.
345       numbers = DEFAULT_EMERGENCY_NUMBERS;
346     }
347     return numbers.indexOf(aNumber) != -1;
348   },
350   /**
351    * Checks whether to temporarily suppress caller id for the call.
352    *
353    * @param aMmi
354    *        MMI full object.
355    */
356   _isTemporaryCLIR: function(aMmi) {
357     return (aMmi && aMmi.serviceCode === RIL.MMI_SC_CLIR) && aMmi.dialNumber;
358   },
360   /**
361    * Map MMI procedure to CLIR MODE.
362    *
363    * @param aProcedure
364    *        MMI procedure
365    */
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;
374       default:
375         return RIL.CLIR_DEFAULT;
376     }
377   },
379   /**
380    * nsITelephonyService interface.
381    */
383   defaultServiceId: 0,
385   registerListener: function(aListener) {
386     if (this._listeners.indexOf(aListener) >= 0) {
387       throw Cr.NS_ERROR_UNEXPECTED;
388     }
390     this._listeners.push(aListener);
391   },
393   unregisterListener: function(aListener) {
394     let index = this._listeners.indexOf(aListener);
395     if (index < 0) {
396       throw Cr.NS_ERROR_UNEXPECTED;
397     }
399     this._listeners.splice(index, 1);
400   },
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];
407       if (!calls) {
408         continue;
409       }
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);
418       }
419     }
420     aListener.enumerateCallStateComplete();
421   },
423   _hasCallsOnOtherClient: function(aClientId) {
424     for (let cid = 0; cid < this._numClients; ++cid) {
425       if (cid === aClientId) {
426         continue;
427       }
428       if (Object.keys(this._currentCalls[cid]).length !== 0) {
429         return true;
430       }
431     }
432     return false;
433   },
435   // All calls in the conference is regarded as one conference call.
436   _numCallsOnLine: function(aClientId) {
437     let numCalls = 0;
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;
444       } else {
445         numCalls++;
446       }
447     }
449     return hasConference ? numCalls + 1 : numCalls;
450   },
452   /**
453    * Get arbitrary one of active call.
454    */
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) {
459         return call;
460       }
461     }
462     return null;
463   },
465   _addCdmaChildCall: function(aClientId, aNumber, aParentId) {
466     let childCall = {
467       callIndex: CDMA_SECOND_CALL_INDEX,
468       state: RIL.CALL_STATE_DIALING,
469       number: aNumber,
470       isOutgoing: true,
471       isEmergency: false,
472       isConference: false,
473       isSwitchable: false,
474       isMergeable: true,
475       parentId: aParentId
476     };
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);
490   },
492   _composeDialRequest: function(aClientId, aNumber) {
493     return new Promise((resolve, reject) => {
494       this._sendToRilWorker(aClientId, "parseMMIFromDialNumber",
495                             {number: aNumber}, response => {
496         let options = {};
497         let mmi = response.mmi;
499         if (!mmi) {
500           resolve({
501             number: aNumber
502           });
503         } else if (this._isTemporaryCLIR(mmi)) {
504           resolve({
505             number: mmi.dialNumber,
506             clirMode: this._getTemporaryCLIRMode(mmi.procedure)
507           });
508         } else {
509           reject(DIAL_ERROR_BAD_NUMBER);
510         }
511       });
512     });
513   },
515   cachedDialRequest: null,
516   isDialing: false,
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);
524       return;
525     }
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);
531       return;
532     }
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);
539       return;
540     }
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);
546     }
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);
553       return;
554     }
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);
566           return;
567         }
568       }
570       // Before we dial, we have to hold the active call first.
571       let activeCall = this._getOneActiveCall(aClientId);
572       if (!activeCall) {
573         this._dialInternal(aClientId, options, aCallback);
574       } else {
575         if (DEBUG) debug("There is an active call. Hold it first before dial.");
577         this.cachedDialRequest = {
578           clientId: aClientId,
579           options: options,
580           callback: aCallback
581         };
583         if (activeCall.isConference) {
584           this.holdConference(aClientId);
585         } else {
586           this.holdCall(aClientId, activeCall.callIndex);
587         }
588       }
589     }, cause => {
590       aCallback.notifyError(DIAL_ERROR_BAD_NUMBER);
591     });
592   },
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);
602         return;
603       }
605       let currentCdmaCallIndex = !response.isCdma ? null :
606         Object.keys(this._currentCalls[aClientId])[0];
608       if (currentCdmaCallIndex == null) {
609         aCallback.notifyDialCallSuccess(response.callIndex, response.number);
610       } else {
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);
614       }
615     });
616   },
618   hangUp: function(aClientId, aCallIndex) {
619     let parentId = this._currentCalls[aClientId][aCallIndex].parentId;
620     if (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);
624     } else {
625       this._sendToRilWorker(aClientId, "hangUp", { callIndex: aCallIndex });
626     }
627   },
629   startTone: function(aClientId, aDtmfChar) {
630     this._sendToRilWorker(aClientId, "startTone", { dtmfChar: aDtmfChar });
631   },
633   stopTone: function(aClientId) {
634     this._sendToRilWorker(aClientId, "stopTone");
635   },
637   answerCall: function(aClientId, aCallIndex) {
638     this._sendToRilWorker(aClientId, "answerCall", { callIndex: aCallIndex });
639   },
641   rejectCall: function(aClientId, aCallIndex) {
642     this._sendToRilWorker(aClientId, "rejectCall", { callIndex: aCallIndex });
643   },
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.
650       return;
651     }
653     this._sendToRilWorker(aClientId, "holdCall", { callIndex: aCallIndex });
654   },
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.
661       return;
662     }
664     this._sendToRilWorker(aClientId, "resumeCall", { callIndex: aCallIndex });
665   },
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.
672       return;
673     }
675     for (let i = 0; i < indexes.length; ++i) {
676       let call = this._currentCalls[aClientId][indexes[i]];
677       if (!call.isMergeable) {
678         return;
679       }
680     }
682     function onCdmaConferenceCallSuccess() {
683       let indexes = Object.keys(this._currentCalls[aClientId]);
684       if (indexes.length < 2) {
685         return;
686       }
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);
693       }
694       this.notifyConferenceCallStateChanged(RIL.CALL_STATE_ACTIVE);
695     }
697     this._sendToRilWorker(aClientId, "conferenceCall", null, response => {
698       if (!response.success) {
699         this._notifyAllListeners("notifyConferenceError", [response.errorName,
700                                                            response.errorMsg]);
701         return;
702       }
704       if (response.isCdma) {
705         onCdmaConferenceCallSuccess.call(this);
706       }
707     });
708   },
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.
715       return;
716     }
718     let parentId = call.parentId;
719     if (parentId) {
720       this.separateCall(aClientId, parentId);
721       return;
722     }
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) {
728         return;
729       }
731       let childId = call.childId;
732       if (!childId) {
733         return;
734       }
736       let childCall = this._currentCalls[aClientId][childId];
737       this.notifyCallDisconnected(aClientId, childCall);
738     }
740     this._sendToRilWorker(aClientId, "separateCall", { callIndex: aCallIndex },
741                           response => {
742       if (!response.success) {
743         this._notifyAllListeners("notifyConferenceError", [response.errorName,
744                                                            response.errorMsg]);
745         return;
746       }
748       if (response.isCdma) {
749         onCdmaSeparateCallSuccess.call(this);
750       }
751     });
752   },
754   hangUpConference: function(aClientId, aCallback) {
755     this._sendToRilWorker(aClientId, "hangUpConference", null, response => {
756       if (!response.success) {
757         aCallback.notifyError(response.errorMsg);
758       } else {
759         aCallback.notifySuccess();
760       }
761     });
762   },
764   holdConference: function(aClientId) {
765     this._sendToRilWorker(aClientId, "holdConference");
766   },
768   resumeConference: function(aClientId) {
769     this._sendToRilWorker(aClientId, "resumeConference");
770   },
772   get microphoneMuted() {
773     return gAudioManager.microphoneMuted;
774   },
776   set microphoneMuted(aMuted) {
777     if (aMuted == this.microphoneMuted) {
778       return;
779     }
780     gAudioManager.microphoneMuted = aMuted;
781   },
783   get speakerEnabled() {
784     let force = gAudioManager.getForceForUse(nsIAudioManager.USE_COMMUNICATION);
785     return (force == nsIAudioManager.FORCE_SPEAKER);
786   },
788   set speakerEnabled(aEnabled) {
789     if (aEnabled == this.speakerEnabled) {
790       return;
791     }
792     let force = aEnabled ? nsIAudioManager.FORCE_SPEAKER :
793                            nsIAudioManager.FORCE_NONE;
794     gAudioManager.setForceForUse(nsIAudioManager.USE_COMMUNICATION, force);
795   },
797   /**
798    * nsIGonkTelephonyService interface.
799    */
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]);
807     }
809     this._updateAudioState(audioState);
810   },
812   /**
813    * Handle call disconnects by updating our current state and the audio system.
814    */
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;
823     let data = {
824       number: aCall.number,
825       serviceId: aClientId,
826       emergency: aCall.isEmergency,
827       duration: duration,
828       direction: aCall.isOutgoing ? "outgoing" : "incoming",
829       hangUpLocal: aCall.hangUpLocal
830     };
832     if (this._cdmaCallWaitingNumber != null) {
833       data.secondNumber = this._cdmaCallWaitingNumber;
834       this._cdmaCallWaitingNumber = null;
835     }
837     gSystemMessenger.broadcastMessage("telephony-call-ended", data);
839     let manualConfStateChange = false;
840     let childId = this._currentCalls[aClientId][aCall.callIndex].childId;
841     if (childId) {
842       // Child cannot live without parent.
843       let childCall = this._currentCalls[aClientId][childId];
844       this.notifyCallDisconnected(aClientId, childCall);
845     } else {
846       let parentId = this._currentCalls[aClientId][aCall.callIndex].parentId;
847       if (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);
860         }
861       }
862     }
864     this._updateActiveCall(aCall);
866     if (!aCall.failCause ||
867         aCall.failCause === RIL.GECKO_CALL_ERROR_NORMAL_CALL_CLEARING) {
868       this._notifyAllListeners("callStateChanged", [aClientId,
869                                                     aCall.callIndex,
870                                                     aCall.state,
871                                                     aCall.number,
872                                                     aCall.numberPresentation,
873                                                     aCall.name,
874                                                     aCall.namePresentation,
875                                                     aCall.isOutgoing,
876                                                     aCall.isEmergency,
877                                                     aCall.isConference,
878                                                     aCall.isSwitchable,
879                                                     aCall.isMergeable]);
880     } else {
881       this._notifyAllListeners("notifyError",
882                                [aClientId, aCall.callIndex, aCall.failCause]);
883     }
884     delete this._currentCalls[aClientId][aCall.callIndex];
886     if (manualConfStateChange) {
887       this.notifyConferenceCallStateChanged(RIL.CALL_STATE_UNKNOWN);
888     }
889   },
891   /**
892    * Handle an incoming call.
893    *
894    * Not much is known about this call at this point, but it's enough
895    * to start bringing up the Phone app already.
896    */
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", {});
903   },
905   /**
906    * Handle call state changes by updating our current state and the audio
907    * system.
908    */
909   notifyCallStateChanged: function(aClientId, aCall, aSkipStateConversion) {
910     if (DEBUG) debug("handleCallStateChange: " + JSON.stringify(aCall));
912     if (!aSkipStateConversion) {
913       aCall.state = this._convertRILCallState(aCall.state);
914     }
916     if (aCall.state == nsITelephonyService.CALL_STATE_DIALING) {
917       gSystemMessenger.broadcastMessage("telephony-new-call", {});
918     }
920     aCall.clientId = aClientId;
921     this._updateActiveCall(aCall);
923     function pick(arg, defaultValue) {
924       return typeof arg !== 'undefined' ? arg : defaultValue;
925     }
927     let call = this._currentCalls[aClientId][aCall.callIndex];
928     if (call) {
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);
935     } else {
936       call = aCall;
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;
945     }
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;
954     }
956     this._notifyAllListeners("callStateChanged", [aClientId,
957                                                   call.callIndex,
958                                                   call.state,
959                                                   call.number,
960                                                   call.numberPresentation,
961                                                   call.name,
962                                                   call.namePresentation,
963                                                   call.isOutgoing,
964                                                   call.isEmergency,
965                                                   call.isConference,
966                                                   call.isSwitchable,
967                                                   call.isMergeable]);
968   },
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];
976     if (call) {
977       // TODO: Bug 977503 - B2G RIL: [CDMA] update callNumber when a waiting
978       // call comes after a 3way call.
979       this.notifyCallDisconnected(aClientId, call);
980     }
982     this._cdmaCallWaitingNumber = aCall.number;
984     this._notifyAllListeners("notifyCdmaCallWaiting", [aClientId,
985                                                        aCall.number,
986                                                        aCall.numberPresentation,
987                                                        aCall.name,
988                                                        aCall.namePresentation]);
989   },
991   notifySupplementaryService: function(aClientId, aCallIndex, aNotification) {
992     let notification = this._convertRILSuppSvcNotification(aNotification);
993     this._notifyAllListeners("supplementaryServiceNotification",
994                              [aClientId, aCallIndex, notification]);
995   },
997   notifyConferenceCallStateChanged: function(aState) {
998     if (DEBUG) debug("handleConferenceCallStateChanged: " + aState);
999     aState = this._convertRILCallState(aState);
1000     this._notifyAllListeners("conferenceCallStateChanged", [aState]);
1001   },
1003   /**
1004    * nsIObserver interface.
1005    */
1007   observe: function(aSubject, aTopic, aData) {
1008     switch (aTopic) {
1009       case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID:
1010         if (aData === kPrefRilDebuggingEnabled) {
1011           this._updateDebugFlag();
1012         } else if (aData === kPrefDefaultServiceId) {
1013           this.defaultServiceId = this._getDefaultServiceId();
1014         }
1015         break;
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);
1022         break;
1023     }
1024   }
1027 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TelephonyService]);