michael@0: /* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource://gre/modules/Promise.jsm"); michael@0: michael@0: var RIL = {}; michael@0: Cu.import("resource://gre/modules/ril_consts.js", RIL); michael@0: michael@0: const GONK_TELEPHONYPROVIDER_CONTRACTID = michael@0: "@mozilla.org/telephony/gonktelephonyprovider;1"; michael@0: const GONK_TELEPHONYPROVIDER_CID = michael@0: Components.ID("{67d26434-d063-4d28-9f48-5b3189788155}"); michael@0: michael@0: const NS_XPCOM_SHUTDOWN_OBSERVER_ID = "xpcom-shutdown"; michael@0: michael@0: const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed"; michael@0: michael@0: const kPrefRilNumRadioInterfaces = "ril.numRadioInterfaces"; michael@0: const kPrefRilDebuggingEnabled = "ril.debugging.enabled"; michael@0: const kPrefDefaultServiceId = "dom.telephony.defaultServiceId"; michael@0: michael@0: const nsIAudioManager = Ci.nsIAudioManager; michael@0: const nsITelephonyProvider = Ci.nsITelephonyProvider; michael@0: michael@0: const CALL_WAKELOCK_TIMEOUT = 5000; michael@0: michael@0: // Index of the CDMA second call which isn't held in RIL but only in TelephoyProvider. michael@0: const CDMA_SECOND_CALL_INDEX = 2; michael@0: michael@0: const DIAL_ERROR_INVALID_STATE_ERROR = "InvalidStateError"; michael@0: const DIAL_ERROR_OTHER_CONNECTION_IN_USE = "OtherConnectionInUse"; michael@0: michael@0: // Should match the value we set in dom/telephony/TelephonyCommon.h michael@0: const OUTGOING_PLACEHOLDER_CALL_INDEX = 0xffffffff; michael@0: michael@0: let DEBUG; michael@0: function debug(s) { michael@0: dump("TelephonyProvider: " + s + "\n"); michael@0: } michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, "gAudioManager", function getAudioManager() { michael@0: try { michael@0: return Cc["@mozilla.org/telephony/audiomanager;1"] michael@0: .getService(nsIAudioManager); michael@0: } catch (ex) { michael@0: //TODO on the phone this should not fall back as silently. michael@0: michael@0: // Fake nsIAudioManager implementation so that we can run the telephony michael@0: // code in a non-Gonk build. michael@0: if (DEBUG) debug("Using fake audio manager."); michael@0: return { michael@0: microphoneMuted: false, michael@0: masterVolume: 1.0, michael@0: masterMuted: false, michael@0: phoneState: nsIAudioManager.PHONE_STATE_CURRENT, michael@0: _forceForUse: {}, michael@0: michael@0: setForceForUse: function(usage, force) { michael@0: this._forceForUse[usage] = force; michael@0: }, michael@0: michael@0: getForceForUse: function(usage) { michael@0: return this._forceForUse[usage] || nsIAudioManager.FORCE_NONE; michael@0: } michael@0: }; michael@0: } michael@0: }); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "gRadioInterfaceLayer", michael@0: "@mozilla.org/ril;1", michael@0: "nsIRadioInterfaceLayer"); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "gPowerManagerService", michael@0: "@mozilla.org/power/powermanagerservice;1", michael@0: "nsIPowerManagerService"); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "gSystemMessenger", michael@0: "@mozilla.org/system-message-internal;1", michael@0: "nsISystemMessagesInternal"); michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, "gPhoneNumberUtils", function() { michael@0: let ns = {}; michael@0: Cu.import("resource://gre/modules/PhoneNumberUtils.jsm", ns); michael@0: return ns.PhoneNumberUtils; michael@0: }); michael@0: michael@0: function SingleCall(options){ michael@0: this.clientId = options.clientId; michael@0: this.callIndex = options.callIndex; michael@0: this.state = options.state; michael@0: this.number = options.number; michael@0: this.isOutgoing = options.isOutgoing; michael@0: this.isEmergency = options.isEmergency; michael@0: this.isConference = options.isConference; michael@0: } michael@0: SingleCall.prototype = { michael@0: clientId: null, michael@0: callIndex: null, michael@0: state: null, michael@0: number: null, michael@0: isOutgoing: false, michael@0: isEmergency: false, michael@0: isConference: false michael@0: }; michael@0: michael@0: function ConferenceCall(state){ michael@0: this.state = state; michael@0: } michael@0: ConferenceCall.prototype = { michael@0: state: null michael@0: }; michael@0: michael@0: function TelephonyProvider() { michael@0: this._numClients = gRadioInterfaceLayer.numRadioInterfaces; michael@0: this._listeners = []; michael@0: this._currentCalls = {}; michael@0: this._updateDebugFlag(); michael@0: this.defaultServiceId = this._getDefaultServiceId(); michael@0: michael@0: Services.prefs.addObserver(kPrefRilDebuggingEnabled, this, false); michael@0: Services.prefs.addObserver(kPrefDefaultServiceId, this, false); michael@0: michael@0: Services.obs.addObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); michael@0: michael@0: for (let i = 0; i < this._numClients; ++i) { michael@0: this._enumerateCallsForClient(i); michael@0: } michael@0: } michael@0: TelephonyProvider.prototype = { michael@0: classID: GONK_TELEPHONYPROVIDER_CID, michael@0: classInfo: XPCOMUtils.generateCI({classID: GONK_TELEPHONYPROVIDER_CID, michael@0: contractID: GONK_TELEPHONYPROVIDER_CONTRACTID, michael@0: classDescription: "TelephonyProvider", michael@0: interfaces: [Ci.nsITelephonyProvider, michael@0: Ci.nsIGonkTelephonyProvider], michael@0: flags: Ci.nsIClassInfo.SINGLETON}), michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsITelephonyProvider, michael@0: Ci.nsIGonkTelephonyProvider, michael@0: Ci.nsIObserver]), michael@0: michael@0: // The following attributes/functions are used for acquiring/releasing the michael@0: // CPU wake lock when the RIL handles the incoming call. Note that we need michael@0: // a timer to bound the lock's life cycle to avoid exhausting the battery. michael@0: _callRingWakeLock: null, michael@0: _callRingWakeLockTimer: null, michael@0: michael@0: _acquireCallRingWakeLock: function() { michael@0: if (!this._callRingWakeLock) { michael@0: if (DEBUG) debug("Acquiring a CPU wake lock for handling incoming call."); michael@0: this._callRingWakeLock = gPowerManagerService.newWakeLock("cpu"); michael@0: } michael@0: if (!this._callRingWakeLockTimer) { michael@0: if (DEBUG) debug("Creating a timer for releasing the CPU wake lock."); michael@0: this._callRingWakeLockTimer = michael@0: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); michael@0: } michael@0: if (DEBUG) debug("Setting the timer for releasing the CPU wake lock."); michael@0: this._callRingWakeLockTimer michael@0: .initWithCallback(this._releaseCallRingWakeLock.bind(this), michael@0: CALL_WAKELOCK_TIMEOUT, Ci.nsITimer.TYPE_ONE_SHOT); michael@0: }, michael@0: michael@0: _releaseCallRingWakeLock: function() { michael@0: if (DEBUG) debug("Releasing the CPU wake lock for handling incoming call."); michael@0: if (this._callRingWakeLockTimer) { michael@0: this._callRingWakeLockTimer.cancel(); michael@0: } michael@0: if (this._callRingWakeLock) { michael@0: this._callRingWakeLock.unlock(); michael@0: this._callRingWakeLock = null; michael@0: } michael@0: }, michael@0: michael@0: _getClient: function(aClientId) { michael@0: return gRadioInterfaceLayer.getRadioInterface(aClientId); michael@0: }, michael@0: michael@0: // An array of nsITelephonyListener instances. michael@0: _listeners: null, michael@0: _notifyAllListeners: function(aMethodName, aArgs) { michael@0: let listeners = this._listeners.slice(); michael@0: for (let listener of listeners) { michael@0: if (this._listeners.indexOf(listener) == -1) { michael@0: // Listener has been unregistered in previous run. michael@0: continue; michael@0: } michael@0: michael@0: let handler = listener[aMethodName]; michael@0: try { michael@0: handler.apply(listener, aArgs); michael@0: } catch (e) { michael@0: debug("listener for " + aMethodName + " threw an exception: " + e); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: _matchActiveSingleCall: function(aCall) { michael@0: return this._activeCall && michael@0: this._activeCall instanceof SingleCall && michael@0: this._activeCall.clientId === aCall.clientId && michael@0: this._activeCall.callIndex === aCall.callIndex; michael@0: }, michael@0: michael@0: /** michael@0: * Track the active call and update the audio system as its state changes. michael@0: */ michael@0: _activeCall: null, michael@0: _updateCallAudioState: function(aCall, aConferenceState) { michael@0: if (aConferenceState === nsITelephonyProvider.CALL_STATE_CONNECTED) { michael@0: this._activeCall = new ConferenceCall(aConferenceState); michael@0: gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_IN_CALL; michael@0: if (this.speakerEnabled) { michael@0: gAudioManager.setForceForUse(nsIAudioManager.USE_COMMUNICATION, michael@0: nsIAudioManager.FORCE_SPEAKER); michael@0: } michael@0: if (DEBUG) { michael@0: debug("Active call, put audio system into PHONE_STATE_IN_CALL: " + michael@0: gAudioManager.phoneState); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: if (aConferenceState === nsITelephonyProvider.CALL_STATE_UNKNOWN || michael@0: aConferenceState === nsITelephonyProvider.CALL_STATE_HELD) { michael@0: if (this._activeCall instanceof ConferenceCall) { michael@0: this._activeCall = null; michael@0: gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_NORMAL; michael@0: if (DEBUG) { michael@0: debug("No active call, put audio system into PHONE_STATE_NORMAL: " + michael@0: gAudioManager.phoneState); michael@0: } michael@0: } michael@0: return; michael@0: } michael@0: michael@0: if (!aCall) { michael@0: return; michael@0: } michael@0: michael@0: if (aCall.isConference) { michael@0: if (this._matchActiveSingleCall(aCall)) { michael@0: this._activeCall = null; michael@0: } michael@0: return; michael@0: } michael@0: michael@0: switch (aCall.state) { michael@0: case nsITelephonyProvider.CALL_STATE_DIALING: // Fall through... michael@0: case nsITelephonyProvider.CALL_STATE_ALERTING: michael@0: case nsITelephonyProvider.CALL_STATE_CONNECTED: michael@0: aCall.isActive = true; michael@0: this._activeCall = new SingleCall(aCall); michael@0: gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_IN_CALL; michael@0: if (this.speakerEnabled) { michael@0: gAudioManager.setForceForUse(nsIAudioManager.USE_COMMUNICATION, michael@0: nsIAudioManager.FORCE_SPEAKER); michael@0: } michael@0: if (DEBUG) { michael@0: debug("Active call, put audio system into PHONE_STATE_IN_CALL: " + michael@0: gAudioManager.phoneState); michael@0: } michael@0: break; michael@0: michael@0: case nsITelephonyProvider.CALL_STATE_INCOMING: michael@0: aCall.isActive = false; michael@0: if (!this._activeCall) { michael@0: // We can change the phone state into RINGTONE only when there's michael@0: // no active call. michael@0: gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_RINGTONE; michael@0: if (DEBUG) { michael@0: debug("Incoming call, put audio system into PHONE_STATE_RINGTONE: " + michael@0: gAudioManager.phoneState); michael@0: } michael@0: } michael@0: break; michael@0: michael@0: case nsITelephonyProvider.CALL_STATE_HELD: // Fall through... michael@0: case nsITelephonyProvider.CALL_STATE_DISCONNECTED: michael@0: aCall.isActive = false; michael@0: if (this._matchActiveSingleCall(aCall)) { michael@0: // Previously active call is not active now. michael@0: this._activeCall = null; michael@0: } michael@0: michael@0: if (!this._activeCall) { michael@0: // No active call. Disable the audio. michael@0: gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_NORMAL; michael@0: if (DEBUG) { michael@0: debug("No active call, put audio system into PHONE_STATE_NORMAL: " + michael@0: gAudioManager.phoneState); michael@0: } michael@0: } michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: _convertRILCallState: function(aState) { michael@0: switch (aState) { michael@0: case RIL.CALL_STATE_UNKNOWN: michael@0: return nsITelephonyProvider.CALL_STATE_UNKNOWN; michael@0: case RIL.CALL_STATE_ACTIVE: michael@0: return nsITelephonyProvider.CALL_STATE_CONNECTED; michael@0: case RIL.CALL_STATE_HOLDING: michael@0: return nsITelephonyProvider.CALL_STATE_HELD; michael@0: case RIL.CALL_STATE_DIALING: michael@0: return nsITelephonyProvider.CALL_STATE_DIALING; michael@0: case RIL.CALL_STATE_ALERTING: michael@0: return nsITelephonyProvider.CALL_STATE_ALERTING; michael@0: case RIL.CALL_STATE_INCOMING: michael@0: case RIL.CALL_STATE_WAITING: michael@0: return nsITelephonyProvider.CALL_STATE_INCOMING; michael@0: default: michael@0: throw new Error("Unknown rilCallState: " + aState); michael@0: } michael@0: }, michael@0: michael@0: _convertRILSuppSvcNotification: function(aNotification) { michael@0: switch (aNotification) { michael@0: case RIL.GECKO_SUPP_SVC_NOTIFICATION_REMOTE_HELD: michael@0: return nsITelephonyProvider.NOTIFICATION_REMOTE_HELD; michael@0: case RIL.GECKO_SUPP_SVC_NOTIFICATION_REMOTE_RESUMED: michael@0: return nsITelephonyProvider.NOTIFICATION_REMOTE_RESUMED; michael@0: default: michael@0: if (DEBUG) { michael@0: debug("Unknown rilSuppSvcNotification: " + aNotification); michael@0: } michael@0: return; michael@0: } michael@0: }, michael@0: michael@0: _updateDebugFlag: function() { michael@0: try { michael@0: DEBUG = RIL.DEBUG_RIL || michael@0: Services.prefs.getBoolPref(kPrefRilDebuggingEnabled); michael@0: } catch (e) {} michael@0: }, michael@0: michael@0: _getDefaultServiceId: function() { michael@0: let id = Services.prefs.getIntPref(kPrefDefaultServiceId); michael@0: let numRil = Services.prefs.getIntPref(kPrefRilNumRadioInterfaces); michael@0: michael@0: if (id >= numRil || id < 0) { michael@0: id = 0; michael@0: } michael@0: michael@0: return id; michael@0: }, michael@0: michael@0: _currentCalls: null, michael@0: _enumerateCallsForClient: function(aClientId) { michael@0: if (DEBUG) debug("Enumeration of calls for client " + aClientId); michael@0: michael@0: this._getClient(aClientId).sendWorkerMessage("enumerateCalls", null, michael@0: (function(response) { michael@0: if (!this._currentCalls[aClientId]) { michael@0: this._currentCalls[aClientId] = {}; michael@0: } michael@0: for (let call of response.calls) { michael@0: call.clientId = aClientId; michael@0: call.state = this._convertRILCallState(call.state); michael@0: call.isActive = this._matchActiveSingleCall(call); michael@0: call.isSwitchable = true; michael@0: call.isMergeable = true; michael@0: michael@0: this._currentCalls[aClientId][call.callIndex] = call; michael@0: } michael@0: michael@0: return false; michael@0: }).bind(this)); michael@0: }, michael@0: michael@0: /** michael@0: * nsITelephonyProvider interface. michael@0: */ michael@0: michael@0: defaultServiceId: 0, michael@0: michael@0: registerListener: function(aListener) { michael@0: if (this._listeners.indexOf(aListener) >= 0) { michael@0: throw Cr.NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: this._listeners.push(aListener); michael@0: }, michael@0: michael@0: unregisterListener: function(aListener) { michael@0: let index = this._listeners.indexOf(aListener); michael@0: if (index < 0) { michael@0: throw Cr.NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: this._listeners.splice(index, 1); michael@0: }, michael@0: michael@0: enumerateCalls: function(aListener) { michael@0: if (DEBUG) debug("Requesting enumeration of calls for callback"); michael@0: michael@0: for (let cid = 0; cid < this._numClients; ++cid) { michael@0: let calls = this._currentCalls[cid]; michael@0: if (!calls) { michael@0: continue; michael@0: } michael@0: for (let i = 0, indexes = Object.keys(calls); i < indexes.length; ++i) { michael@0: let call = calls[indexes[i]]; michael@0: aListener.enumerateCallState(call.clientId, call.callIndex, michael@0: call.state, call.number, michael@0: call.isActive, call.isOutgoing, michael@0: call.isEmergency, call.isConference, michael@0: call.isSwitchable, call.isMergeable); michael@0: } michael@0: } michael@0: aListener.enumerateCallStateComplete(); michael@0: }, michael@0: michael@0: isDialing: false, michael@0: dial: function(aClientId, aNumber, aIsEmergency, aTelephonyCallback) { michael@0: if (DEBUG) debug("Dialing " + (aIsEmergency ? "emergency " : "") + aNumber); michael@0: michael@0: if (this.isDialing) { michael@0: if (DEBUG) debug("Already has a dialing call. Drop."); michael@0: aTelephonyCallback.notifyDialError(DIAL_ERROR_INVALID_STATE_ERROR); michael@0: return; michael@0: } michael@0: michael@0: function hasCallsOnOtherClient(aClientId) { michael@0: for (let cid = 0; cid < this._numClients; ++cid) { michael@0: if (cid === aClientId) { michael@0: continue; michael@0: } michael@0: if (Object.keys(this._currentCalls[cid]).length !== 0) { michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: // For DSDS, if there is aleady a call on SIM 'aClientId', we cannot place michael@0: // any new call on other SIM. michael@0: if (hasCallsOnOtherClient.call(this, aClientId)) { michael@0: if (DEBUG) debug("Already has a call on other sim. Drop."); michael@0: aTelephonyCallback.notifyDialError(DIAL_ERROR_OTHER_CONNECTION_IN_USE); michael@0: return; michael@0: } michael@0: michael@0: // All calls in the conference is regarded as one conference call. michael@0: function numCallsOnLine(aClientId) { michael@0: let numCalls = 0; michael@0: let hasConference = false; michael@0: michael@0: for (let cid in this._currentCalls[aClientId]) { michael@0: let call = this._currentCalls[aClientId][cid]; michael@0: if (call.isConference) { michael@0: hasConference = true; michael@0: } else { michael@0: numCalls++; michael@0: } michael@0: } michael@0: michael@0: return hasConference ? numCalls + 1 : numCalls; michael@0: } michael@0: michael@0: if (numCallsOnLine.call(this, aClientId) >= 2) { michael@0: if (DEBUG) debug("Has more than 2 calls on line. Drop."); michael@0: aTelephonyCallback.notifyDialError(DIAL_ERROR_INVALID_STATE_ERROR); michael@0: return; michael@0: } michael@0: michael@0: // we don't try to be too clever here, as the phone is probably in the michael@0: // locked state. Let's just check if it's a number without normalizing michael@0: if (!aIsEmergency) { michael@0: aNumber = gPhoneNumberUtils.normalize(aNumber); michael@0: } michael@0: michael@0: // Validate the number. michael@0: if (!gPhoneNumberUtils.isPlainPhoneNumber(aNumber)) { michael@0: // Note: isPlainPhoneNumber also accepts USSD and SS numbers michael@0: if (DEBUG) debug("Number '" + aNumber + "' is not viable. Drop."); michael@0: let errorMsg = RIL.RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[RIL.CALL_FAIL_UNOBTAINABLE_NUMBER]; michael@0: aTelephonyCallback.notifyDialError(errorMsg); michael@0: return; michael@0: } michael@0: michael@0: function onCdmaDialSuccess() { michael@0: let indexes = Object.keys(this._currentCalls[aClientId]); michael@0: if (indexes.length != 1 ) { michael@0: aTelephonyCallback.notifyDialSuccess(); michael@0: return; michael@0: } michael@0: michael@0: // RIL doesn't hold the 2nd call. We create one by ourselves. michael@0: let childCall = { michael@0: callIndex: CDMA_SECOND_CALL_INDEX, michael@0: state: RIL.CALL_STATE_DIALING, michael@0: number: aNumber, michael@0: isOutgoing: true, michael@0: isEmergency: false, michael@0: isConference: false, michael@0: isSwitchable: false, michael@0: isMergeable: true, michael@0: parentId: indexes[0] michael@0: }; michael@0: aTelephonyCallback.notifyDialSuccess(); michael@0: michael@0: // Manual update call state according to the request response. michael@0: this.notifyCallStateChanged(aClientId, childCall); michael@0: michael@0: childCall.state = RIL.CALL_STATE_ACTIVE; michael@0: this.notifyCallStateChanged(aClientId, childCall); michael@0: michael@0: let parentCall = this._currentCalls[aClientId][childCall.parentId]; michael@0: parentCall.childId = CDMA_SECOND_CALL_INDEX; michael@0: parentCall.state = RIL.CALL_STATE_HOLDING; michael@0: parentCall.isSwitchable = false; michael@0: parentCall.isMergeable = true; michael@0: this.notifyCallStateChanged(aClientId, parentCall); michael@0: }; michael@0: michael@0: this.isDialing = true; michael@0: this._getClient(aClientId).sendWorkerMessage("dial", { michael@0: number: aNumber, michael@0: isDialEmergency: aIsEmergency michael@0: }, (function(response) { michael@0: this.isDialing = false; michael@0: if (!response.success) { michael@0: aTelephonyCallback.notifyDialError(response.errorMsg); michael@0: return false; michael@0: } michael@0: michael@0: if (response.isCdma) { michael@0: onCdmaDialSuccess.call(this); michael@0: } else { michael@0: aTelephonyCallback.notifyDialSuccess(); michael@0: } michael@0: return false; michael@0: }).bind(this)); michael@0: }, michael@0: michael@0: hangUp: function(aClientId, aCallIndex) { michael@0: let parentId = this._currentCalls[aClientId][aCallIndex].parentId; michael@0: if (parentId) { michael@0: // Should release both, child and parent, together. Since RIL holds only michael@0: // the parent call, we send 'parentId' to RIL. michael@0: this.hangUp(aClientId, parentId); michael@0: } else { michael@0: this._getClient(aClientId).sendWorkerMessage("hangUp", { callIndex: aCallIndex }); michael@0: } michael@0: }, michael@0: michael@0: startTone: function(aClientId, aDtmfChar) { michael@0: this._getClient(aClientId).sendWorkerMessage("startTone", { dtmfChar: aDtmfChar }); michael@0: }, michael@0: michael@0: stopTone: function(aClientId) { michael@0: this._getClient(aClientId).sendWorkerMessage("stopTone"); michael@0: }, michael@0: michael@0: answerCall: function(aClientId, aCallIndex) { michael@0: this._getClient(aClientId).sendWorkerMessage("answerCall", { callIndex: aCallIndex }); michael@0: }, michael@0: michael@0: rejectCall: function(aClientId, aCallIndex) { michael@0: this._getClient(aClientId).sendWorkerMessage("rejectCall", { callIndex: aCallIndex }); michael@0: }, michael@0: michael@0: holdCall: function(aClientId, aCallIndex) { michael@0: let call = this._currentCalls[aClientId][aCallIndex]; michael@0: if (!call || !call.isSwitchable) { michael@0: // TODO: Bug 975949 - [B2G] Telephony should throw exceptions when some michael@0: // operations aren't allowed instead of simply ignoring them. michael@0: return; michael@0: } michael@0: michael@0: this._getClient(aClientId).sendWorkerMessage("holdCall", { callIndex: aCallIndex }); michael@0: }, michael@0: michael@0: resumeCall: function(aClientId, aCallIndex) { michael@0: let call = this._currentCalls[aClientId][aCallIndex]; michael@0: if (!call || !call.isSwitchable) { michael@0: // TODO: Bug 975949 - [B2G] Telephony should throw exceptions when some michael@0: // operations aren't allowed instead of simply ignoring them. michael@0: return; michael@0: } michael@0: michael@0: this._getClient(aClientId).sendWorkerMessage("resumeCall", { callIndex: aCallIndex }); michael@0: }, michael@0: michael@0: conferenceCall: function(aClientId) { michael@0: let indexes = Object.keys(this._currentCalls[aClientId]); michael@0: if (indexes.length < 2) { michael@0: // TODO: Bug 975949 - [B2G] Telephony should throw exceptions when some michael@0: // operations aren't allowed instead of simply ignoring them. michael@0: return; michael@0: } michael@0: michael@0: for (let i = 0; i < indexes.length; ++i) { michael@0: let call = this._currentCalls[aClientId][indexes[i]]; michael@0: if (!call.isMergeable) { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: function onCdmaConferenceCallSuccess() { michael@0: let indexes = Object.keys(this._currentCalls[aClientId]); michael@0: if (indexes.length < 2) { michael@0: return; michael@0: } michael@0: michael@0: for (let i = 0; i < indexes.length; ++i) { michael@0: let call = this._currentCalls[aClientId][indexes[i]]; michael@0: call.state = RIL.CALL_STATE_ACTIVE; michael@0: call.isConference = true; michael@0: this.notifyCallStateChanged(aClientId, call); michael@0: } michael@0: this.notifyConferenceCallStateChanged(RIL.CALL_STATE_ACTIVE); michael@0: }; michael@0: michael@0: this._getClient(aClientId).sendWorkerMessage("conferenceCall", null, michael@0: (function(response) { michael@0: if (!response.success) { michael@0: this._notifyAllListeners("notifyConferenceError", [response.errorName, michael@0: response.errorMsg]); michael@0: return false; michael@0: } michael@0: michael@0: if (response.isCdma) { michael@0: onCdmaConferenceCallSuccess.call(this); michael@0: } michael@0: return false; michael@0: }).bind(this)); michael@0: }, michael@0: michael@0: separateCall: function(aClientId, aCallIndex) { michael@0: let call = this._currentCalls[aClientId][aCallIndex]; michael@0: if (!call || !call.isConference) { michael@0: // TODO: Bug 975949 - [B2G] Telephony should throw exceptions when some michael@0: // operations aren't allowed instead of simply ignoring them. michael@0: return; michael@0: } michael@0: michael@0: let parentId = call.parentId; michael@0: if (parentId) { michael@0: this.separateCall(aClientId, parentId); michael@0: return; michael@0: } michael@0: michael@0: function onCdmaSeparateCallSuccess() { michael@0: // See 3gpp2, S.R0006-522-A v1.0. Table 4, XID 6S. michael@0: let call = this._currentCalls[aClientId][aCallIndex]; michael@0: if (!call || !call.isConference) { michael@0: return; michael@0: } michael@0: michael@0: let childId = call.childId; michael@0: if (!childId) { michael@0: return; michael@0: } michael@0: michael@0: let childCall = this._currentCalls[aClientId][childId]; michael@0: this.notifyCallDisconnected(aClientId, childCall); michael@0: }; michael@0: michael@0: this._getClient(aClientId).sendWorkerMessage("separateCall", { michael@0: callIndex: aCallIndex michael@0: }, (function(response) { michael@0: if (!response.success) { michael@0: this._notifyAllListeners("notifyConferenceError", [response.errorName, michael@0: response.errorMsg]); michael@0: return false; michael@0: } michael@0: michael@0: if (response.isCdma) { michael@0: onCdmaSeparateCallSuccess.call(this); michael@0: } michael@0: return false; michael@0: }).bind(this)); michael@0: }, michael@0: michael@0: holdConference: function(aClientId) { michael@0: this._getClient(aClientId).sendWorkerMessage("holdConference"); michael@0: }, michael@0: michael@0: resumeConference: function(aClientId) { michael@0: this._getClient(aClientId).sendWorkerMessage("resumeConference"); michael@0: }, michael@0: michael@0: get microphoneMuted() { michael@0: return gAudioManager.microphoneMuted; michael@0: }, michael@0: michael@0: set microphoneMuted(aMuted) { michael@0: if (aMuted == this.microphoneMuted) { michael@0: return; michael@0: } michael@0: gAudioManager.microphoneMuted = aMuted; michael@0: michael@0: if (!this._activeCall) { michael@0: gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_NORMAL; michael@0: } michael@0: }, michael@0: michael@0: get speakerEnabled() { michael@0: let force = gAudioManager.getForceForUse(nsIAudioManager.USE_COMMUNICATION); michael@0: return (force == nsIAudioManager.FORCE_SPEAKER); michael@0: }, michael@0: michael@0: set speakerEnabled(aEnabled) { michael@0: if (aEnabled == this.speakerEnabled) { michael@0: return; michael@0: } michael@0: let force = aEnabled ? nsIAudioManager.FORCE_SPEAKER : michael@0: nsIAudioManager.FORCE_NONE; michael@0: gAudioManager.setForceForUse(nsIAudioManager.USE_COMMUNICATION, force); michael@0: michael@0: if (!this._activeCall) { michael@0: gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_NORMAL; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * nsIGonkTelephonyProvider interface. michael@0: */ michael@0: michael@0: /** michael@0: * Handle call disconnects by updating our current state and the audio system. michael@0: */ michael@0: notifyCallDisconnected: function(aClientId, aCall) { michael@0: if (DEBUG) debug("handleCallDisconnected: " + JSON.stringify(aCall)); michael@0: michael@0: aCall.state = nsITelephonyProvider.CALL_STATE_DISCONNECTED; michael@0: let duration = ("started" in aCall && typeof aCall.started == "number") ? michael@0: new Date().getTime() - aCall.started : 0; michael@0: let data = { michael@0: number: aCall.number, michael@0: serviceId: aClientId, michael@0: emergency: aCall.isEmergency, michael@0: duration: duration, michael@0: direction: aCall.isOutgoing ? "outgoing" : "incoming" michael@0: }; michael@0: gSystemMessenger.broadcastMessage("telephony-call-ended", data); michael@0: michael@0: aCall.clientId = aClientId; michael@0: this._updateCallAudioState(aCall, null); michael@0: michael@0: let manualConfStateChange = false; michael@0: let childId = this._currentCalls[aClientId][aCall.callIndex].childId; michael@0: if (childId) { michael@0: // Child cannot live without parent. michael@0: let childCall = this._currentCalls[aClientId][childId]; michael@0: this.notifyCallDisconnected(aClientId, childCall); michael@0: } else { michael@0: let parentId = this._currentCalls[aClientId][aCall.callIndex].parentId; michael@0: if (parentId) { michael@0: let parentCall = this._currentCalls[aClientId][parentId]; michael@0: // The child is going to be released. michael@0: delete parentCall.childId; michael@0: if (parentCall.isConference) { michael@0: // As the child is going to be gone, the parent should be moved out michael@0: // of conference accordingly. michael@0: manualConfStateChange = true; michael@0: parentCall.isConference = false; michael@0: parentCall.isSwitchable = true; michael@0: parentCall.isMergeable = true; michael@0: aCall.isConference = false; michael@0: this.notifyCallStateChanged(aClientId, parentCall, true); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (!aCall.failCause || michael@0: aCall.failCause === RIL.GECKO_CALL_ERROR_NORMAL_CALL_CLEARING) { michael@0: this._notifyAllListeners("callStateChanged", [aClientId, michael@0: aCall.callIndex, michael@0: aCall.state, michael@0: aCall.number, michael@0: aCall.isActive, michael@0: aCall.isOutgoing, michael@0: aCall.isEmergency, michael@0: aCall.isConference, michael@0: aCall.isSwitchable, michael@0: aCall.isMergeable]); michael@0: } else { michael@0: this._notifyAllListeners("notifyError", michael@0: [aClientId, aCall.callIndex, aCall.failCause]); michael@0: } michael@0: delete this._currentCalls[aClientId][aCall.callIndex]; michael@0: michael@0: if (manualConfStateChange) { michael@0: this.notifyConferenceCallStateChanged(RIL.CALL_STATE_UNKNOWN); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Handle an incoming call. michael@0: * michael@0: * Not much is known about this call at this point, but it's enough michael@0: * to start bringing up the Phone app already. michael@0: */ michael@0: notifyCallRing: function() { michael@0: // We need to acquire a CPU wake lock to avoid the system falling into michael@0: // the sleep mode when the RIL handles the incoming call. michael@0: this._acquireCallRingWakeLock(); michael@0: michael@0: gSystemMessenger.broadcastMessage("telephony-new-call", {}); michael@0: }, michael@0: michael@0: /** michael@0: * Handle call state changes by updating our current state and the audio michael@0: * system. michael@0: */ michael@0: notifyCallStateChanged: function(aClientId, aCall, aSkipStateConversion) { michael@0: if (DEBUG) debug("handleCallStateChange: " + JSON.stringify(aCall)); michael@0: michael@0: if (!aSkipStateConversion) { michael@0: aCall.state = this._convertRILCallState(aCall.state); michael@0: } michael@0: michael@0: if (aCall.state == nsITelephonyProvider.CALL_STATE_DIALING) { michael@0: gSystemMessenger.broadcastMessage("telephony-new-call", {}); michael@0: } michael@0: michael@0: aCall.clientId = aClientId; michael@0: this._updateCallAudioState(aCall, null); michael@0: michael@0: let call = this._currentCalls[aClientId][aCall.callIndex]; michael@0: if (call) { michael@0: call.state = aCall.state; michael@0: call.isConference = aCall.isConference; michael@0: call.isEmergency = aCall.isEmergency; michael@0: call.isActive = aCall.isActive; michael@0: call.isSwitchable = aCall.isSwitchable != null ? michael@0: aCall.isSwitchable : call.isSwitchable; michael@0: call.isMergeable = aCall.isMergeable != null ? michael@0: aCall.isMergeable : call.isMergeable; michael@0: } else { michael@0: call = aCall; michael@0: call.isSwitchable = aCall.isSwitchable != null ? michael@0: aCall.isSwitchable : true; michael@0: call.isMergeable = aCall.isMergeable != null ? michael@0: aCall.isMergeable : true; michael@0: michael@0: // Get the actual call for pending outgoing call. Remove the original one. michael@0: if (this._currentCalls[aClientId][OUTGOING_PLACEHOLDER_CALL_INDEX] && michael@0: call.callIndex != OUTGOING_PLACEHOLDER_CALL_INDEX && michael@0: call.isOutgoing) { michael@0: delete this._currentCalls[aClientId][OUTGOING_PLACEHOLDER_CALL_INDEX]; michael@0: } michael@0: michael@0: this._currentCalls[aClientId][aCall.callIndex] = call; michael@0: } michael@0: michael@0: this._notifyAllListeners("callStateChanged", [aClientId, michael@0: call.callIndex, michael@0: call.state, michael@0: call.number, michael@0: call.isActive, michael@0: call.isOutgoing, michael@0: call.isEmergency, michael@0: call.isConference, michael@0: call.isSwitchable, michael@0: call.isMergeable]); michael@0: }, michael@0: michael@0: notifyCdmaCallWaiting: function(aClientId, aNumber) { michael@0: // We need to acquire a CPU wake lock to avoid the system falling into michael@0: // the sleep mode when the RIL handles the incoming call. michael@0: this._acquireCallRingWakeLock(); michael@0: michael@0: let call = this._currentCalls[aClientId][CDMA_SECOND_CALL_INDEX]; michael@0: if (call) { michael@0: // TODO: Bug 977503 - B2G RIL: [CDMA] update callNumber when a waiting michael@0: // call comes after a 3way call. michael@0: this.notifyCallDisconnected(aClientId, call); michael@0: } michael@0: this._notifyAllListeners("notifyCdmaCallWaiting", [aClientId, aNumber]); michael@0: }, michael@0: michael@0: notifySupplementaryService: function(aClientId, aCallIndex, aNotification) { michael@0: let notification = this._convertRILSuppSvcNotification(aNotification); michael@0: this._notifyAllListeners("supplementaryServiceNotification", michael@0: [aClientId, aCallIndex, notification]); michael@0: }, michael@0: michael@0: notifyConferenceCallStateChanged: function(aState) { michael@0: if (DEBUG) debug("handleConferenceCallStateChanged: " + aState); michael@0: aState = this._convertRILCallState(aState); michael@0: this._updateCallAudioState(null, aState); michael@0: michael@0: this._notifyAllListeners("conferenceCallStateChanged", [aState]); michael@0: }, michael@0: michael@0: /** michael@0: * nsIObserver interface. michael@0: */ michael@0: michael@0: observe: function(aSubject, aTopic, aData) { michael@0: switch (aTopic) { michael@0: case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID: michael@0: if (aData === kPrefRilDebuggingEnabled) { michael@0: this._updateDebugFlag(); michael@0: } else if (aData === kPrefDefaultServiceId) { michael@0: this.defaultServiceId = this._getDefaultServiceId(); michael@0: } michael@0: break; michael@0: michael@0: case NS_XPCOM_SHUTDOWN_OBSERVER_ID: michael@0: // Release the CPU wake lock for handling the incoming call. michael@0: this._releaseCallRingWakeLock(); michael@0: michael@0: Services.obs.removeObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); michael@0: break; michael@0: } michael@0: } michael@0: }; michael@0: michael@0: this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TelephonyProvider]);