Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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");
14 var RIL = {};
15 Cu.import("resource://gre/modules/ril_consts.js", RIL);
17 const GONK_TELEPHONYPROVIDER_CONTRACTID =
18 "@mozilla.org/telephony/gonktelephonyprovider;1";
19 const GONK_TELEPHONYPROVIDER_CID =
20 Components.ID("{67d26434-d063-4d28-9f48-5b3189788155}");
22 const NS_XPCOM_SHUTDOWN_OBSERVER_ID = "xpcom-shutdown";
24 const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
26 const kPrefRilNumRadioInterfaces = "ril.numRadioInterfaces";
27 const kPrefRilDebuggingEnabled = "ril.debugging.enabled";
28 const kPrefDefaultServiceId = "dom.telephony.defaultServiceId";
30 const nsIAudioManager = Ci.nsIAudioManager;
31 const nsITelephonyProvider = Ci.nsITelephonyProvider;
33 const CALL_WAKELOCK_TIMEOUT = 5000;
35 // Index of the CDMA second call which isn't held in RIL but only in TelephoyProvider.
36 const CDMA_SECOND_CALL_INDEX = 2;
38 const DIAL_ERROR_INVALID_STATE_ERROR = "InvalidStateError";
39 const DIAL_ERROR_OTHER_CONNECTION_IN_USE = "OtherConnectionInUse";
41 // Should match the value we set in dom/telephony/TelephonyCommon.h
42 const OUTGOING_PLACEHOLDER_CALL_INDEX = 0xffffffff;
44 let DEBUG;
45 function debug(s) {
46 dump("TelephonyProvider: " + s + "\n");
47 }
49 XPCOMUtils.defineLazyGetter(this, "gAudioManager", function getAudioManager() {
50 try {
51 return Cc["@mozilla.org/telephony/audiomanager;1"]
52 .getService(nsIAudioManager);
53 } catch (ex) {
54 //TODO on the phone this should not fall back as silently.
56 // Fake nsIAudioManager implementation so that we can run the telephony
57 // code in a non-Gonk build.
58 if (DEBUG) debug("Using fake audio manager.");
59 return {
60 microphoneMuted: false,
61 masterVolume: 1.0,
62 masterMuted: false,
63 phoneState: nsIAudioManager.PHONE_STATE_CURRENT,
64 _forceForUse: {},
66 setForceForUse: function(usage, force) {
67 this._forceForUse[usage] = force;
68 },
70 getForceForUse: function(usage) {
71 return this._forceForUse[usage] || nsIAudioManager.FORCE_NONE;
72 }
73 };
74 }
75 });
77 XPCOMUtils.defineLazyServiceGetter(this, "gRadioInterfaceLayer",
78 "@mozilla.org/ril;1",
79 "nsIRadioInterfaceLayer");
81 XPCOMUtils.defineLazyServiceGetter(this, "gPowerManagerService",
82 "@mozilla.org/power/powermanagerservice;1",
83 "nsIPowerManagerService");
85 XPCOMUtils.defineLazyServiceGetter(this, "gSystemMessenger",
86 "@mozilla.org/system-message-internal;1",
87 "nsISystemMessagesInternal");
89 XPCOMUtils.defineLazyGetter(this, "gPhoneNumberUtils", function() {
90 let ns = {};
91 Cu.import("resource://gre/modules/PhoneNumberUtils.jsm", ns);
92 return ns.PhoneNumberUtils;
93 });
95 function SingleCall(options){
96 this.clientId = options.clientId;
97 this.callIndex = options.callIndex;
98 this.state = options.state;
99 this.number = options.number;
100 this.isOutgoing = options.isOutgoing;
101 this.isEmergency = options.isEmergency;
102 this.isConference = options.isConference;
103 }
104 SingleCall.prototype = {
105 clientId: null,
106 callIndex: null,
107 state: null,
108 number: null,
109 isOutgoing: false,
110 isEmergency: false,
111 isConference: false
112 };
114 function ConferenceCall(state){
115 this.state = state;
116 }
117 ConferenceCall.prototype = {
118 state: null
119 };
121 function TelephonyProvider() {
122 this._numClients = gRadioInterfaceLayer.numRadioInterfaces;
123 this._listeners = [];
124 this._currentCalls = {};
125 this._updateDebugFlag();
126 this.defaultServiceId = this._getDefaultServiceId();
128 Services.prefs.addObserver(kPrefRilDebuggingEnabled, this, false);
129 Services.prefs.addObserver(kPrefDefaultServiceId, this, false);
131 Services.obs.addObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
133 for (let i = 0; i < this._numClients; ++i) {
134 this._enumerateCallsForClient(i);
135 }
136 }
137 TelephonyProvider.prototype = {
138 classID: GONK_TELEPHONYPROVIDER_CID,
139 classInfo: XPCOMUtils.generateCI({classID: GONK_TELEPHONYPROVIDER_CID,
140 contractID: GONK_TELEPHONYPROVIDER_CONTRACTID,
141 classDescription: "TelephonyProvider",
142 interfaces: [Ci.nsITelephonyProvider,
143 Ci.nsIGonkTelephonyProvider],
144 flags: Ci.nsIClassInfo.SINGLETON}),
145 QueryInterface: XPCOMUtils.generateQI([Ci.nsITelephonyProvider,
146 Ci.nsIGonkTelephonyProvider,
147 Ci.nsIObserver]),
149 // The following attributes/functions are used for acquiring/releasing the
150 // CPU wake lock when the RIL handles the incoming call. Note that we need
151 // a timer to bound the lock's life cycle to avoid exhausting the battery.
152 _callRingWakeLock: null,
153 _callRingWakeLockTimer: null,
155 _acquireCallRingWakeLock: function() {
156 if (!this._callRingWakeLock) {
157 if (DEBUG) debug("Acquiring a CPU wake lock for handling incoming call.");
158 this._callRingWakeLock = gPowerManagerService.newWakeLock("cpu");
159 }
160 if (!this._callRingWakeLockTimer) {
161 if (DEBUG) debug("Creating a timer for releasing the CPU wake lock.");
162 this._callRingWakeLockTimer =
163 Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
164 }
165 if (DEBUG) debug("Setting the timer for releasing the CPU wake lock.");
166 this._callRingWakeLockTimer
167 .initWithCallback(this._releaseCallRingWakeLock.bind(this),
168 CALL_WAKELOCK_TIMEOUT, Ci.nsITimer.TYPE_ONE_SHOT);
169 },
171 _releaseCallRingWakeLock: function() {
172 if (DEBUG) debug("Releasing the CPU wake lock for handling incoming call.");
173 if (this._callRingWakeLockTimer) {
174 this._callRingWakeLockTimer.cancel();
175 }
176 if (this._callRingWakeLock) {
177 this._callRingWakeLock.unlock();
178 this._callRingWakeLock = null;
179 }
180 },
182 _getClient: function(aClientId) {
183 return gRadioInterfaceLayer.getRadioInterface(aClientId);
184 },
186 // An array of nsITelephonyListener instances.
187 _listeners: null,
188 _notifyAllListeners: function(aMethodName, aArgs) {
189 let listeners = this._listeners.slice();
190 for (let listener of listeners) {
191 if (this._listeners.indexOf(listener) == -1) {
192 // Listener has been unregistered in previous run.
193 continue;
194 }
196 let handler = listener[aMethodName];
197 try {
198 handler.apply(listener, aArgs);
199 } catch (e) {
200 debug("listener for " + aMethodName + " threw an exception: " + e);
201 }
202 }
203 },
205 _matchActiveSingleCall: function(aCall) {
206 return this._activeCall &&
207 this._activeCall instanceof SingleCall &&
208 this._activeCall.clientId === aCall.clientId &&
209 this._activeCall.callIndex === aCall.callIndex;
210 },
212 /**
213 * Track the active call and update the audio system as its state changes.
214 */
215 _activeCall: null,
216 _updateCallAudioState: function(aCall, aConferenceState) {
217 if (aConferenceState === nsITelephonyProvider.CALL_STATE_CONNECTED) {
218 this._activeCall = new ConferenceCall(aConferenceState);
219 gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_IN_CALL;
220 if (this.speakerEnabled) {
221 gAudioManager.setForceForUse(nsIAudioManager.USE_COMMUNICATION,
222 nsIAudioManager.FORCE_SPEAKER);
223 }
224 if (DEBUG) {
225 debug("Active call, put audio system into PHONE_STATE_IN_CALL: " +
226 gAudioManager.phoneState);
227 }
228 return;
229 }
231 if (aConferenceState === nsITelephonyProvider.CALL_STATE_UNKNOWN ||
232 aConferenceState === nsITelephonyProvider.CALL_STATE_HELD) {
233 if (this._activeCall instanceof ConferenceCall) {
234 this._activeCall = null;
235 gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_NORMAL;
236 if (DEBUG) {
237 debug("No active call, put audio system into PHONE_STATE_NORMAL: " +
238 gAudioManager.phoneState);
239 }
240 }
241 return;
242 }
244 if (!aCall) {
245 return;
246 }
248 if (aCall.isConference) {
249 if (this._matchActiveSingleCall(aCall)) {
250 this._activeCall = null;
251 }
252 return;
253 }
255 switch (aCall.state) {
256 case nsITelephonyProvider.CALL_STATE_DIALING: // Fall through...
257 case nsITelephonyProvider.CALL_STATE_ALERTING:
258 case nsITelephonyProvider.CALL_STATE_CONNECTED:
259 aCall.isActive = true;
260 this._activeCall = new SingleCall(aCall);
261 gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_IN_CALL;
262 if (this.speakerEnabled) {
263 gAudioManager.setForceForUse(nsIAudioManager.USE_COMMUNICATION,
264 nsIAudioManager.FORCE_SPEAKER);
265 }
266 if (DEBUG) {
267 debug("Active call, put audio system into PHONE_STATE_IN_CALL: " +
268 gAudioManager.phoneState);
269 }
270 break;
272 case nsITelephonyProvider.CALL_STATE_INCOMING:
273 aCall.isActive = false;
274 if (!this._activeCall) {
275 // We can change the phone state into RINGTONE only when there's
276 // no active call.
277 gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_RINGTONE;
278 if (DEBUG) {
279 debug("Incoming call, put audio system into PHONE_STATE_RINGTONE: " +
280 gAudioManager.phoneState);
281 }
282 }
283 break;
285 case nsITelephonyProvider.CALL_STATE_HELD: // Fall through...
286 case nsITelephonyProvider.CALL_STATE_DISCONNECTED:
287 aCall.isActive = false;
288 if (this._matchActiveSingleCall(aCall)) {
289 // Previously active call is not active now.
290 this._activeCall = null;
291 }
293 if (!this._activeCall) {
294 // No active call. Disable the audio.
295 gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_NORMAL;
296 if (DEBUG) {
297 debug("No active call, put audio system into PHONE_STATE_NORMAL: " +
298 gAudioManager.phoneState);
299 }
300 }
301 break;
302 }
303 },
305 _convertRILCallState: function(aState) {
306 switch (aState) {
307 case RIL.CALL_STATE_UNKNOWN:
308 return nsITelephonyProvider.CALL_STATE_UNKNOWN;
309 case RIL.CALL_STATE_ACTIVE:
310 return nsITelephonyProvider.CALL_STATE_CONNECTED;
311 case RIL.CALL_STATE_HOLDING:
312 return nsITelephonyProvider.CALL_STATE_HELD;
313 case RIL.CALL_STATE_DIALING:
314 return nsITelephonyProvider.CALL_STATE_DIALING;
315 case RIL.CALL_STATE_ALERTING:
316 return nsITelephonyProvider.CALL_STATE_ALERTING;
317 case RIL.CALL_STATE_INCOMING:
318 case RIL.CALL_STATE_WAITING:
319 return nsITelephonyProvider.CALL_STATE_INCOMING;
320 default:
321 throw new Error("Unknown rilCallState: " + aState);
322 }
323 },
325 _convertRILSuppSvcNotification: function(aNotification) {
326 switch (aNotification) {
327 case RIL.GECKO_SUPP_SVC_NOTIFICATION_REMOTE_HELD:
328 return nsITelephonyProvider.NOTIFICATION_REMOTE_HELD;
329 case RIL.GECKO_SUPP_SVC_NOTIFICATION_REMOTE_RESUMED:
330 return nsITelephonyProvider.NOTIFICATION_REMOTE_RESUMED;
331 default:
332 if (DEBUG) {
333 debug("Unknown rilSuppSvcNotification: " + aNotification);
334 }
335 return;
336 }
337 },
339 _updateDebugFlag: function() {
340 try {
341 DEBUG = RIL.DEBUG_RIL ||
342 Services.prefs.getBoolPref(kPrefRilDebuggingEnabled);
343 } catch (e) {}
344 },
346 _getDefaultServiceId: function() {
347 let id = Services.prefs.getIntPref(kPrefDefaultServiceId);
348 let numRil = Services.prefs.getIntPref(kPrefRilNumRadioInterfaces);
350 if (id >= numRil || id < 0) {
351 id = 0;
352 }
354 return id;
355 },
357 _currentCalls: null,
358 _enumerateCallsForClient: function(aClientId) {
359 if (DEBUG) debug("Enumeration of calls for client " + aClientId);
361 this._getClient(aClientId).sendWorkerMessage("enumerateCalls", null,
362 (function(response) {
363 if (!this._currentCalls[aClientId]) {
364 this._currentCalls[aClientId] = {};
365 }
366 for (let call of response.calls) {
367 call.clientId = aClientId;
368 call.state = this._convertRILCallState(call.state);
369 call.isActive = this._matchActiveSingleCall(call);
370 call.isSwitchable = true;
371 call.isMergeable = true;
373 this._currentCalls[aClientId][call.callIndex] = call;
374 }
376 return false;
377 }).bind(this));
378 },
380 /**
381 * nsITelephonyProvider interface.
382 */
384 defaultServiceId: 0,
386 registerListener: function(aListener) {
387 if (this._listeners.indexOf(aListener) >= 0) {
388 throw Cr.NS_ERROR_UNEXPECTED;
389 }
391 this._listeners.push(aListener);
392 },
394 unregisterListener: function(aListener) {
395 let index = this._listeners.indexOf(aListener);
396 if (index < 0) {
397 throw Cr.NS_ERROR_UNEXPECTED;
398 }
400 this._listeners.splice(index, 1);
401 },
403 enumerateCalls: function(aListener) {
404 if (DEBUG) debug("Requesting enumeration of calls for callback");
406 for (let cid = 0; cid < this._numClients; ++cid) {
407 let calls = this._currentCalls[cid];
408 if (!calls) {
409 continue;
410 }
411 for (let i = 0, indexes = Object.keys(calls); i < indexes.length; ++i) {
412 let call = calls[indexes[i]];
413 aListener.enumerateCallState(call.clientId, call.callIndex,
414 call.state, call.number,
415 call.isActive, call.isOutgoing,
416 call.isEmergency, call.isConference,
417 call.isSwitchable, call.isMergeable);
418 }
419 }
420 aListener.enumerateCallStateComplete();
421 },
423 isDialing: false,
424 dial: function(aClientId, aNumber, aIsEmergency, aTelephonyCallback) {
425 if (DEBUG) debug("Dialing " + (aIsEmergency ? "emergency " : "") + aNumber);
427 if (this.isDialing) {
428 if (DEBUG) debug("Already has a dialing call. Drop.");
429 aTelephonyCallback.notifyDialError(DIAL_ERROR_INVALID_STATE_ERROR);
430 return;
431 }
433 function hasCallsOnOtherClient(aClientId) {
434 for (let cid = 0; cid < this._numClients; ++cid) {
435 if (cid === aClientId) {
436 continue;
437 }
438 if (Object.keys(this._currentCalls[cid]).length !== 0) {
439 return true;
440 }
441 }
442 return false;
443 }
445 // For DSDS, if there is aleady a call on SIM 'aClientId', we cannot place
446 // any new call on other SIM.
447 if (hasCallsOnOtherClient.call(this, aClientId)) {
448 if (DEBUG) debug("Already has a call on other sim. Drop.");
449 aTelephonyCallback.notifyDialError(DIAL_ERROR_OTHER_CONNECTION_IN_USE);
450 return;
451 }
453 // All calls in the conference is regarded as one conference call.
454 function numCallsOnLine(aClientId) {
455 let numCalls = 0;
456 let hasConference = false;
458 for (let cid in this._currentCalls[aClientId]) {
459 let call = this._currentCalls[aClientId][cid];
460 if (call.isConference) {
461 hasConference = true;
462 } else {
463 numCalls++;
464 }
465 }
467 return hasConference ? numCalls + 1 : numCalls;
468 }
470 if (numCallsOnLine.call(this, aClientId) >= 2) {
471 if (DEBUG) debug("Has more than 2 calls on line. Drop.");
472 aTelephonyCallback.notifyDialError(DIAL_ERROR_INVALID_STATE_ERROR);
473 return;
474 }
476 // we don't try to be too clever here, as the phone is probably in the
477 // locked state. Let's just check if it's a number without normalizing
478 if (!aIsEmergency) {
479 aNumber = gPhoneNumberUtils.normalize(aNumber);
480 }
482 // Validate the number.
483 if (!gPhoneNumberUtils.isPlainPhoneNumber(aNumber)) {
484 // Note: isPlainPhoneNumber also accepts USSD and SS numbers
485 if (DEBUG) debug("Number '" + aNumber + "' is not viable. Drop.");
486 let errorMsg = RIL.RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[RIL.CALL_FAIL_UNOBTAINABLE_NUMBER];
487 aTelephonyCallback.notifyDialError(errorMsg);
488 return;
489 }
491 function onCdmaDialSuccess() {
492 let indexes = Object.keys(this._currentCalls[aClientId]);
493 if (indexes.length != 1 ) {
494 aTelephonyCallback.notifyDialSuccess();
495 return;
496 }
498 // RIL doesn't hold the 2nd call. We create one by ourselves.
499 let childCall = {
500 callIndex: CDMA_SECOND_CALL_INDEX,
501 state: RIL.CALL_STATE_DIALING,
502 number: aNumber,
503 isOutgoing: true,
504 isEmergency: false,
505 isConference: false,
506 isSwitchable: false,
507 isMergeable: true,
508 parentId: indexes[0]
509 };
510 aTelephonyCallback.notifyDialSuccess();
512 // Manual update call state according to the request response.
513 this.notifyCallStateChanged(aClientId, childCall);
515 childCall.state = RIL.CALL_STATE_ACTIVE;
516 this.notifyCallStateChanged(aClientId, childCall);
518 let parentCall = this._currentCalls[aClientId][childCall.parentId];
519 parentCall.childId = CDMA_SECOND_CALL_INDEX;
520 parentCall.state = RIL.CALL_STATE_HOLDING;
521 parentCall.isSwitchable = false;
522 parentCall.isMergeable = true;
523 this.notifyCallStateChanged(aClientId, parentCall);
524 };
526 this.isDialing = true;
527 this._getClient(aClientId).sendWorkerMessage("dial", {
528 number: aNumber,
529 isDialEmergency: aIsEmergency
530 }, (function(response) {
531 this.isDialing = false;
532 if (!response.success) {
533 aTelephonyCallback.notifyDialError(response.errorMsg);
534 return false;
535 }
537 if (response.isCdma) {
538 onCdmaDialSuccess.call(this);
539 } else {
540 aTelephonyCallback.notifyDialSuccess();
541 }
542 return false;
543 }).bind(this));
544 },
546 hangUp: function(aClientId, aCallIndex) {
547 let parentId = this._currentCalls[aClientId][aCallIndex].parentId;
548 if (parentId) {
549 // Should release both, child and parent, together. Since RIL holds only
550 // the parent call, we send 'parentId' to RIL.
551 this.hangUp(aClientId, parentId);
552 } else {
553 this._getClient(aClientId).sendWorkerMessage("hangUp", { callIndex: aCallIndex });
554 }
555 },
557 startTone: function(aClientId, aDtmfChar) {
558 this._getClient(aClientId).sendWorkerMessage("startTone", { dtmfChar: aDtmfChar });
559 },
561 stopTone: function(aClientId) {
562 this._getClient(aClientId).sendWorkerMessage("stopTone");
563 },
565 answerCall: function(aClientId, aCallIndex) {
566 this._getClient(aClientId).sendWorkerMessage("answerCall", { callIndex: aCallIndex });
567 },
569 rejectCall: function(aClientId, aCallIndex) {
570 this._getClient(aClientId).sendWorkerMessage("rejectCall", { callIndex: aCallIndex });
571 },
573 holdCall: function(aClientId, aCallIndex) {
574 let call = this._currentCalls[aClientId][aCallIndex];
575 if (!call || !call.isSwitchable) {
576 // TODO: Bug 975949 - [B2G] Telephony should throw exceptions when some
577 // operations aren't allowed instead of simply ignoring them.
578 return;
579 }
581 this._getClient(aClientId).sendWorkerMessage("holdCall", { callIndex: aCallIndex });
582 },
584 resumeCall: function(aClientId, aCallIndex) {
585 let call = this._currentCalls[aClientId][aCallIndex];
586 if (!call || !call.isSwitchable) {
587 // TODO: Bug 975949 - [B2G] Telephony should throw exceptions when some
588 // operations aren't allowed instead of simply ignoring them.
589 return;
590 }
592 this._getClient(aClientId).sendWorkerMessage("resumeCall", { callIndex: aCallIndex });
593 },
595 conferenceCall: function(aClientId) {
596 let indexes = Object.keys(this._currentCalls[aClientId]);
597 if (indexes.length < 2) {
598 // TODO: Bug 975949 - [B2G] Telephony should throw exceptions when some
599 // operations aren't allowed instead of simply ignoring them.
600 return;
601 }
603 for (let i = 0; i < indexes.length; ++i) {
604 let call = this._currentCalls[aClientId][indexes[i]];
605 if (!call.isMergeable) {
606 return;
607 }
608 }
610 function onCdmaConferenceCallSuccess() {
611 let indexes = Object.keys(this._currentCalls[aClientId]);
612 if (indexes.length < 2) {
613 return;
614 }
616 for (let i = 0; i < indexes.length; ++i) {
617 let call = this._currentCalls[aClientId][indexes[i]];
618 call.state = RIL.CALL_STATE_ACTIVE;
619 call.isConference = true;
620 this.notifyCallStateChanged(aClientId, call);
621 }
622 this.notifyConferenceCallStateChanged(RIL.CALL_STATE_ACTIVE);
623 };
625 this._getClient(aClientId).sendWorkerMessage("conferenceCall", null,
626 (function(response) {
627 if (!response.success) {
628 this._notifyAllListeners("notifyConferenceError", [response.errorName,
629 response.errorMsg]);
630 return false;
631 }
633 if (response.isCdma) {
634 onCdmaConferenceCallSuccess.call(this);
635 }
636 return false;
637 }).bind(this));
638 },
640 separateCall: function(aClientId, aCallIndex) {
641 let call = this._currentCalls[aClientId][aCallIndex];
642 if (!call || !call.isConference) {
643 // TODO: Bug 975949 - [B2G] Telephony should throw exceptions when some
644 // operations aren't allowed instead of simply ignoring them.
645 return;
646 }
648 let parentId = call.parentId;
649 if (parentId) {
650 this.separateCall(aClientId, parentId);
651 return;
652 }
654 function onCdmaSeparateCallSuccess() {
655 // See 3gpp2, S.R0006-522-A v1.0. Table 4, XID 6S.
656 let call = this._currentCalls[aClientId][aCallIndex];
657 if (!call || !call.isConference) {
658 return;
659 }
661 let childId = call.childId;
662 if (!childId) {
663 return;
664 }
666 let childCall = this._currentCalls[aClientId][childId];
667 this.notifyCallDisconnected(aClientId, childCall);
668 };
670 this._getClient(aClientId).sendWorkerMessage("separateCall", {
671 callIndex: aCallIndex
672 }, (function(response) {
673 if (!response.success) {
674 this._notifyAllListeners("notifyConferenceError", [response.errorName,
675 response.errorMsg]);
676 return false;
677 }
679 if (response.isCdma) {
680 onCdmaSeparateCallSuccess.call(this);
681 }
682 return false;
683 }).bind(this));
684 },
686 holdConference: function(aClientId) {
687 this._getClient(aClientId).sendWorkerMessage("holdConference");
688 },
690 resumeConference: function(aClientId) {
691 this._getClient(aClientId).sendWorkerMessage("resumeConference");
692 },
694 get microphoneMuted() {
695 return gAudioManager.microphoneMuted;
696 },
698 set microphoneMuted(aMuted) {
699 if (aMuted == this.microphoneMuted) {
700 return;
701 }
702 gAudioManager.microphoneMuted = aMuted;
704 if (!this._activeCall) {
705 gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_NORMAL;
706 }
707 },
709 get speakerEnabled() {
710 let force = gAudioManager.getForceForUse(nsIAudioManager.USE_COMMUNICATION);
711 return (force == nsIAudioManager.FORCE_SPEAKER);
712 },
714 set speakerEnabled(aEnabled) {
715 if (aEnabled == this.speakerEnabled) {
716 return;
717 }
718 let force = aEnabled ? nsIAudioManager.FORCE_SPEAKER :
719 nsIAudioManager.FORCE_NONE;
720 gAudioManager.setForceForUse(nsIAudioManager.USE_COMMUNICATION, force);
722 if (!this._activeCall) {
723 gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_NORMAL;
724 }
725 },
727 /**
728 * nsIGonkTelephonyProvider interface.
729 */
731 /**
732 * Handle call disconnects by updating our current state and the audio system.
733 */
734 notifyCallDisconnected: function(aClientId, aCall) {
735 if (DEBUG) debug("handleCallDisconnected: " + JSON.stringify(aCall));
737 aCall.state = nsITelephonyProvider.CALL_STATE_DISCONNECTED;
738 let duration = ("started" in aCall && typeof aCall.started == "number") ?
739 new Date().getTime() - aCall.started : 0;
740 let data = {
741 number: aCall.number,
742 serviceId: aClientId,
743 emergency: aCall.isEmergency,
744 duration: duration,
745 direction: aCall.isOutgoing ? "outgoing" : "incoming"
746 };
747 gSystemMessenger.broadcastMessage("telephony-call-ended", data);
749 aCall.clientId = aClientId;
750 this._updateCallAudioState(aCall, null);
752 let manualConfStateChange = false;
753 let childId = this._currentCalls[aClientId][aCall.callIndex].childId;
754 if (childId) {
755 // Child cannot live without parent.
756 let childCall = this._currentCalls[aClientId][childId];
757 this.notifyCallDisconnected(aClientId, childCall);
758 } else {
759 let parentId = this._currentCalls[aClientId][aCall.callIndex].parentId;
760 if (parentId) {
761 let parentCall = this._currentCalls[aClientId][parentId];
762 // The child is going to be released.
763 delete parentCall.childId;
764 if (parentCall.isConference) {
765 // As the child is going to be gone, the parent should be moved out
766 // of conference accordingly.
767 manualConfStateChange = true;
768 parentCall.isConference = false;
769 parentCall.isSwitchable = true;
770 parentCall.isMergeable = true;
771 aCall.isConference = false;
772 this.notifyCallStateChanged(aClientId, parentCall, true);
773 }
774 }
775 }
777 if (!aCall.failCause ||
778 aCall.failCause === RIL.GECKO_CALL_ERROR_NORMAL_CALL_CLEARING) {
779 this._notifyAllListeners("callStateChanged", [aClientId,
780 aCall.callIndex,
781 aCall.state,
782 aCall.number,
783 aCall.isActive,
784 aCall.isOutgoing,
785 aCall.isEmergency,
786 aCall.isConference,
787 aCall.isSwitchable,
788 aCall.isMergeable]);
789 } else {
790 this._notifyAllListeners("notifyError",
791 [aClientId, aCall.callIndex, aCall.failCause]);
792 }
793 delete this._currentCalls[aClientId][aCall.callIndex];
795 if (manualConfStateChange) {
796 this.notifyConferenceCallStateChanged(RIL.CALL_STATE_UNKNOWN);
797 }
798 },
800 /**
801 * Handle an incoming call.
802 *
803 * Not much is known about this call at this point, but it's enough
804 * to start bringing up the Phone app already.
805 */
806 notifyCallRing: function() {
807 // We need to acquire a CPU wake lock to avoid the system falling into
808 // the sleep mode when the RIL handles the incoming call.
809 this._acquireCallRingWakeLock();
811 gSystemMessenger.broadcastMessage("telephony-new-call", {});
812 },
814 /**
815 * Handle call state changes by updating our current state and the audio
816 * system.
817 */
818 notifyCallStateChanged: function(aClientId, aCall, aSkipStateConversion) {
819 if (DEBUG) debug("handleCallStateChange: " + JSON.stringify(aCall));
821 if (!aSkipStateConversion) {
822 aCall.state = this._convertRILCallState(aCall.state);
823 }
825 if (aCall.state == nsITelephonyProvider.CALL_STATE_DIALING) {
826 gSystemMessenger.broadcastMessage("telephony-new-call", {});
827 }
829 aCall.clientId = aClientId;
830 this._updateCallAudioState(aCall, null);
832 let call = this._currentCalls[aClientId][aCall.callIndex];
833 if (call) {
834 call.state = aCall.state;
835 call.isConference = aCall.isConference;
836 call.isEmergency = aCall.isEmergency;
837 call.isActive = aCall.isActive;
838 call.isSwitchable = aCall.isSwitchable != null ?
839 aCall.isSwitchable : call.isSwitchable;
840 call.isMergeable = aCall.isMergeable != null ?
841 aCall.isMergeable : call.isMergeable;
842 } else {
843 call = aCall;
844 call.isSwitchable = aCall.isSwitchable != null ?
845 aCall.isSwitchable : true;
846 call.isMergeable = aCall.isMergeable != null ?
847 aCall.isMergeable : true;
849 // Get the actual call for pending outgoing call. Remove the original one.
850 if (this._currentCalls[aClientId][OUTGOING_PLACEHOLDER_CALL_INDEX] &&
851 call.callIndex != OUTGOING_PLACEHOLDER_CALL_INDEX &&
852 call.isOutgoing) {
853 delete this._currentCalls[aClientId][OUTGOING_PLACEHOLDER_CALL_INDEX];
854 }
856 this._currentCalls[aClientId][aCall.callIndex] = call;
857 }
859 this._notifyAllListeners("callStateChanged", [aClientId,
860 call.callIndex,
861 call.state,
862 call.number,
863 call.isActive,
864 call.isOutgoing,
865 call.isEmergency,
866 call.isConference,
867 call.isSwitchable,
868 call.isMergeable]);
869 },
871 notifyCdmaCallWaiting: function(aClientId, aNumber) {
872 // We need to acquire a CPU wake lock to avoid the system falling into
873 // the sleep mode when the RIL handles the incoming call.
874 this._acquireCallRingWakeLock();
876 let call = this._currentCalls[aClientId][CDMA_SECOND_CALL_INDEX];
877 if (call) {
878 // TODO: Bug 977503 - B2G RIL: [CDMA] update callNumber when a waiting
879 // call comes after a 3way call.
880 this.notifyCallDisconnected(aClientId, call);
881 }
882 this._notifyAllListeners("notifyCdmaCallWaiting", [aClientId, aNumber]);
883 },
885 notifySupplementaryService: function(aClientId, aCallIndex, aNotification) {
886 let notification = this._convertRILSuppSvcNotification(aNotification);
887 this._notifyAllListeners("supplementaryServiceNotification",
888 [aClientId, aCallIndex, notification]);
889 },
891 notifyConferenceCallStateChanged: function(aState) {
892 if (DEBUG) debug("handleConferenceCallStateChanged: " + aState);
893 aState = this._convertRILCallState(aState);
894 this._updateCallAudioState(null, aState);
896 this._notifyAllListeners("conferenceCallStateChanged", [aState]);
897 },
899 /**
900 * nsIObserver interface.
901 */
903 observe: function(aSubject, aTopic, aData) {
904 switch (aTopic) {
905 case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID:
906 if (aData === kPrefRilDebuggingEnabled) {
907 this._updateDebugFlag();
908 } else if (aData === kPrefDefaultServiceId) {
909 this.defaultServiceId = this._getDefaultServiceId();
910 }
911 break;
913 case NS_XPCOM_SHUTDOWN_OBSERVER_ID:
914 // Release the CPU wake lock for handling the incoming call.
915 this._releaseCallRingWakeLock();
917 Services.obs.removeObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
918 break;
919 }
920 }
921 };
923 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TelephonyProvider]);