Bumping manifests a=b2g-bump
[gecko.git] / dom / telephony / gonk / TelephonyService.js
blob1490d87b925dae075ef0cd678da4cd087b33dc3e
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(aClientId, response.callIndex,
610                                         response.number);
611       } else {
612         // RIL doesn't hold the 2nd call. We create one by ourselves.
613         aCallback.notifyDialCallSuccess(aClientId, CDMA_SECOND_CALL_INDEX,
614                                         response.number);
615         this._addCdmaChildCall(aClientId, response.number, currentCdmaCallIndex);
616       }
617     });
618   },
620   hangUp: function(aClientId, aCallIndex) {
621     let parentId = this._currentCalls[aClientId][aCallIndex].parentId;
622     if (parentId) {
623       // Should release both, child and parent, together. Since RIL holds only
624       // the parent call, we send 'parentId' to RIL.
625       this.hangUp(aClientId, parentId);
626     } else {
627       this._sendToRilWorker(aClientId, "hangUp", { callIndex: aCallIndex });
628     }
629   },
631   startTone: function(aClientId, aDtmfChar) {
632     this._sendToRilWorker(aClientId, "startTone", { dtmfChar: aDtmfChar });
633   },
635   stopTone: function(aClientId) {
636     this._sendToRilWorker(aClientId, "stopTone");
637   },
639   answerCall: function(aClientId, aCallIndex) {
640     this._sendToRilWorker(aClientId, "answerCall", { callIndex: aCallIndex });
641   },
643   rejectCall: function(aClientId, aCallIndex) {
644     this._sendToRilWorker(aClientId, "rejectCall", { callIndex: aCallIndex });
645   },
647   holdCall: function(aClientId, aCallIndex) {
648     let call = this._currentCalls[aClientId][aCallIndex];
649     if (!call || !call.isSwitchable) {
650       // TODO: Bug 975949 - [B2G] Telephony should throw exceptions when some
651       // operations aren't allowed instead of simply ignoring them.
652       return;
653     }
655     this._sendToRilWorker(aClientId, "holdCall", { callIndex: aCallIndex });
656   },
658   resumeCall: function(aClientId, aCallIndex) {
659     let call = this._currentCalls[aClientId][aCallIndex];
660     if (!call || !call.isSwitchable) {
661       // TODO: Bug 975949 - [B2G] Telephony should throw exceptions when some
662       // operations aren't allowed instead of simply ignoring them.
663       return;
664     }
666     this._sendToRilWorker(aClientId, "resumeCall", { callIndex: aCallIndex });
667   },
669   conferenceCall: function(aClientId) {
670     let indexes = Object.keys(this._currentCalls[aClientId]);
671     if (indexes.length < 2) {
672       // TODO: Bug 975949 - [B2G] Telephony should throw exceptions when some
673       // operations aren't allowed instead of simply ignoring them.
674       return;
675     }
677     for (let i = 0; i < indexes.length; ++i) {
678       let call = this._currentCalls[aClientId][indexes[i]];
679       if (!call.isMergeable) {
680         return;
681       }
682     }
684     function onCdmaConferenceCallSuccess() {
685       let indexes = Object.keys(this._currentCalls[aClientId]);
686       if (indexes.length < 2) {
687         return;
688       }
690       for (let i = 0; i < indexes.length; ++i) {
691         let call = this._currentCalls[aClientId][indexes[i]];
692         call.state = RIL.CALL_STATE_ACTIVE;
693         call.isConference = true;
694         this.notifyCallStateChanged(aClientId, call);
695       }
696       this.notifyConferenceCallStateChanged(RIL.CALL_STATE_ACTIVE);
697     }
699     this._sendToRilWorker(aClientId, "conferenceCall", null, response => {
700       if (!response.success) {
701         this._notifyAllListeners("notifyConferenceError", [response.errorName,
702                                                            response.errorMsg]);
703         return;
704       }
706       if (response.isCdma) {
707         onCdmaConferenceCallSuccess.call(this);
708       }
709     });
710   },
712   separateCall: function(aClientId, aCallIndex) {
713     let call = this._currentCalls[aClientId][aCallIndex];
714     if (!call || !call.isConference) {
715       // TODO: Bug 975949 - [B2G] Telephony should throw exceptions when some
716       // operations aren't allowed instead of simply ignoring them.
717       return;
718     }
720     let parentId = call.parentId;
721     if (parentId) {
722       this.separateCall(aClientId, parentId);
723       return;
724     }
726     function onCdmaSeparateCallSuccess() {
727       // See 3gpp2, S.R0006-522-A v1.0. Table 4, XID 6S.
728       let call = this._currentCalls[aClientId][aCallIndex];
729       if (!call || !call.isConference) {
730         return;
731       }
733       let childId = call.childId;
734       if (!childId) {
735         return;
736       }
738       let childCall = this._currentCalls[aClientId][childId];
739       this.notifyCallDisconnected(aClientId, childCall);
740     }
742     this._sendToRilWorker(aClientId, "separateCall", { callIndex: aCallIndex },
743                           response => {
744       if (!response.success) {
745         this._notifyAllListeners("notifyConferenceError", [response.errorName,
746                                                            response.errorMsg]);
747         return;
748       }
750       if (response.isCdma) {
751         onCdmaSeparateCallSuccess.call(this);
752       }
753     });
754   },
756   hangUpConference: function(aClientId, aCallback) {
757     this._sendToRilWorker(aClientId, "hangUpConference", null, response => {
758       if (!response.success) {
759         aCallback.notifyError(response.errorMsg);
760       } else {
761         aCallback.notifySuccess();
762       }
763     });
764   },
766   holdConference: function(aClientId) {
767     this._sendToRilWorker(aClientId, "holdConference");
768   },
770   resumeConference: function(aClientId) {
771     this._sendToRilWorker(aClientId, "resumeConference");
772   },
774   get microphoneMuted() {
775     return gAudioManager.microphoneMuted;
776   },
778   set microphoneMuted(aMuted) {
779     if (aMuted == this.microphoneMuted) {
780       return;
781     }
782     gAudioManager.microphoneMuted = aMuted;
783   },
785   get speakerEnabled() {
786     let force = gAudioManager.getForceForUse(nsIAudioManager.USE_COMMUNICATION);
787     return (force == nsIAudioManager.FORCE_SPEAKER);
788   },
790   set speakerEnabled(aEnabled) {
791     if (aEnabled == this.speakerEnabled) {
792       return;
793     }
794     let force = aEnabled ? nsIAudioManager.FORCE_SPEAKER :
795                            nsIAudioManager.FORCE_NONE;
796     gAudioManager.setForceForUse(nsIAudioManager.USE_COMMUNICATION, force);
797   },
799   /**
800    * nsIGonkTelephonyService interface.
801    */
803   notifyAudioStateChanged: function(aClientId, aState) {
804     this._audioStates[aClientId] = aState;
806     let audioState = aState;
807     for (let i = 0; i < this._numClients; ++i) {
808       audioState = Math.max(audioState, this._audioStates[i]);
809     }
811     this._updateAudioState(audioState);
812   },
814   /**
815    * Handle call disconnects by updating our current state and the audio system.
816    */
817   notifyCallDisconnected: function(aClientId, aCall) {
818     if (DEBUG) debug("handleCallDisconnected: " + JSON.stringify(aCall));
820     aCall.clientId = aClientId;
821     aCall.state = nsITelephonyService.CALL_STATE_DISCONNECTED;
822     aCall.isEmergency = this._isEmergencyNumber(aCall.number);
823     let duration = ("started" in aCall && typeof aCall.started == "number") ?
824       new Date().getTime() - aCall.started : 0;
825     let data = {
826       number: aCall.number,
827       serviceId: aClientId,
828       emergency: aCall.isEmergency,
829       duration: duration,
830       direction: aCall.isOutgoing ? "outgoing" : "incoming",
831       hangUpLocal: aCall.hangUpLocal
832     };
834     if (this._cdmaCallWaitingNumber != null) {
835       data.secondNumber = this._cdmaCallWaitingNumber;
836       this._cdmaCallWaitingNumber = null;
837     }
839     gSystemMessenger.broadcastMessage("telephony-call-ended", data);
841     let manualConfStateChange = false;
842     let childId = this._currentCalls[aClientId][aCall.callIndex].childId;
843     if (childId) {
844       // Child cannot live without parent.
845       let childCall = this._currentCalls[aClientId][childId];
846       this.notifyCallDisconnected(aClientId, childCall);
847     } else {
848       let parentId = this._currentCalls[aClientId][aCall.callIndex].parentId;
849       if (parentId) {
850         let parentCall = this._currentCalls[aClientId][parentId];
851         // The child is going to be released.
852         delete parentCall.childId;
853         if (parentCall.isConference) {
854           // As the child is going to be gone, the parent should be moved out
855           // of conference accordingly.
856           manualConfStateChange = true;
857           parentCall.isConference = false;
858           parentCall.isSwitchable = true;
859           parentCall.isMergeable = true;
860           aCall.isConference = false;
861           this.notifyCallStateChanged(aClientId, parentCall, true);
862         }
863       }
864     }
866     this._updateActiveCall(aCall);
868     if (!aCall.failCause ||
869         aCall.failCause === RIL.GECKO_CALL_ERROR_NORMAL_CALL_CLEARING) {
870       this._notifyAllListeners("callStateChanged", [aClientId,
871                                                     aCall.callIndex,
872                                                     aCall.state,
873                                                     aCall.number,
874                                                     aCall.numberPresentation,
875                                                     aCall.name,
876                                                     aCall.namePresentation,
877                                                     aCall.isOutgoing,
878                                                     aCall.isEmergency,
879                                                     aCall.isConference,
880                                                     aCall.isSwitchable,
881                                                     aCall.isMergeable]);
882     } else {
883       this._notifyAllListeners("notifyError",
884                                [aClientId, aCall.callIndex, aCall.failCause]);
885     }
886     delete this._currentCalls[aClientId][aCall.callIndex];
888     if (manualConfStateChange) {
889       this.notifyConferenceCallStateChanged(RIL.CALL_STATE_UNKNOWN);
890     }
891   },
893   /**
894    * Handle an incoming call.
895    *
896    * Not much is known about this call at this point, but it's enough
897    * to start bringing up the Phone app already.
898    */
899   notifyCallRing: function() {
900     // We need to acquire a CPU wake lock to avoid the system falling into
901     // the sleep mode when the RIL handles the incoming call.
902     this._acquireCallRingWakeLock();
904     gSystemMessenger.broadcastMessage("telephony-new-call", {});
905   },
907   /**
908    * Handle call state changes by updating our current state and the audio
909    * system.
910    */
911   notifyCallStateChanged: function(aClientId, aCall, aSkipStateConversion) {
912     if (DEBUG) debug("handleCallStateChange: " + JSON.stringify(aCall));
914     if (!aSkipStateConversion) {
915       aCall.state = this._convertRILCallState(aCall.state);
916     }
918     if (aCall.state == nsITelephonyService.CALL_STATE_DIALING) {
919       gSystemMessenger.broadcastMessage("telephony-new-call", {});
920     }
922     aCall.clientId = aClientId;
923     this._updateActiveCall(aCall);
925     function pick(arg, defaultValue) {
926       return typeof arg !== 'undefined' ? arg : defaultValue;
927     }
929     let call = this._currentCalls[aClientId][aCall.callIndex];
930     if (call) {
931       call.state = aCall.state;
932       call.number = aCall.number;
933       call.isEmergency = this._isEmergencyNumber(aCall.number);
934       call.isConference = aCall.isConference;
935       call.isSwitchable = pick(aCall.isSwitchable, call.isSwitchable);
936       call.isMergeable = pick(aCall.isMergeable, call.isMergeable);
937     } else {
938       call = aCall;
939       call.isEmergency = pick(aCall.isEmergency, this._isEmergencyNumber(aCall.number));
940       call.isSwitchable = pick(aCall.isSwitchable, true);
941       call.isMergeable = pick(aCall.isMergeable, true);
942       call.name = pick(aCall.name, "");
943       call.numberPresentaation = pick(aCall.numberPresentation, nsITelephonyService.CALL_PRESENTATION_ALLOWED);
944       call.namePresentaation = pick(aCall.namePresentation, nsITelephonyService.CALL_PRESENTATION_ALLOWED);
946       this._currentCalls[aClientId][aCall.callIndex] = call;
947     }
949     // Handle cached dial request.
950     if (this.cachedDialRequest && !this._getOneActiveCall()) {
951       if (DEBUG) debug("All calls held. Perform the cached dial request.");
953       let request = this.cachedDialRequest;
954       this._dialInternal(request.clientId, request.options, request.callback);
955       this.cachedDialRequest = null;
956     }
958     this._notifyAllListeners("callStateChanged", [aClientId,
959                                                   call.callIndex,
960                                                   call.state,
961                                                   call.number,
962                                                   call.numberPresentation,
963                                                   call.name,
964                                                   call.namePresentation,
965                                                   call.isOutgoing,
966                                                   call.isEmergency,
967                                                   call.isConference,
968                                                   call.isSwitchable,
969                                                   call.isMergeable]);
970   },
972   notifyCdmaCallWaiting: function(aClientId, aCall) {
973     // We need to acquire a CPU wake lock to avoid the system falling into
974     // the sleep mode when the RIL handles the incoming call.
975     this._acquireCallRingWakeLock();
977     let call = this._currentCalls[aClientId][CDMA_SECOND_CALL_INDEX];
978     if (call) {
979       // TODO: Bug 977503 - B2G RIL: [CDMA] update callNumber when a waiting
980       // call comes after a 3way call.
981       this.notifyCallDisconnected(aClientId, call);
982     }
984     this._cdmaCallWaitingNumber = aCall.number;
986     this._notifyAllListeners("notifyCdmaCallWaiting", [aClientId,
987                                                        aCall.number,
988                                                        aCall.numberPresentation,
989                                                        aCall.name,
990                                                        aCall.namePresentation]);
991   },
993   notifySupplementaryService: function(aClientId, aCallIndex, aNotification) {
994     let notification = this._convertRILSuppSvcNotification(aNotification);
995     this._notifyAllListeners("supplementaryServiceNotification",
996                              [aClientId, aCallIndex, notification]);
997   },
999   notifyConferenceCallStateChanged: function(aState) {
1000     if (DEBUG) debug("handleConferenceCallStateChanged: " + aState);
1001     aState = this._convertRILCallState(aState);
1002     this._notifyAllListeners("conferenceCallStateChanged", [aState]);
1003   },
1005   /**
1006    * nsIObserver interface.
1007    */
1009   observe: function(aSubject, aTopic, aData) {
1010     switch (aTopic) {
1011       case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID:
1012         if (aData === kPrefRilDebuggingEnabled) {
1013           this._updateDebugFlag();
1014         } else if (aData === kPrefDefaultServiceId) {
1015           this.defaultServiceId = this._getDefaultServiceId();
1016         }
1017         break;
1019       case NS_XPCOM_SHUTDOWN_OBSERVER_ID:
1020         // Release the CPU wake lock for handling the incoming call.
1021         this._releaseCallRingWakeLock();
1023         Services.obs.removeObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
1024         break;
1025     }
1026   }
1029 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TelephonyService]);