1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/telephony/gonk/TelephonyProvider.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,923 @@ 1.4 +/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.7 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +"use strict"; 1.10 + 1.11 +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; 1.12 + 1.13 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.14 +Cu.import("resource://gre/modules/Services.jsm"); 1.15 +Cu.import("resource://gre/modules/Promise.jsm"); 1.16 + 1.17 +var RIL = {}; 1.18 +Cu.import("resource://gre/modules/ril_consts.js", RIL); 1.19 + 1.20 +const GONK_TELEPHONYPROVIDER_CONTRACTID = 1.21 + "@mozilla.org/telephony/gonktelephonyprovider;1"; 1.22 +const GONK_TELEPHONYPROVIDER_CID = 1.23 + Components.ID("{67d26434-d063-4d28-9f48-5b3189788155}"); 1.24 + 1.25 +const NS_XPCOM_SHUTDOWN_OBSERVER_ID = "xpcom-shutdown"; 1.26 + 1.27 +const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed"; 1.28 + 1.29 +const kPrefRilNumRadioInterfaces = "ril.numRadioInterfaces"; 1.30 +const kPrefRilDebuggingEnabled = "ril.debugging.enabled"; 1.31 +const kPrefDefaultServiceId = "dom.telephony.defaultServiceId"; 1.32 + 1.33 +const nsIAudioManager = Ci.nsIAudioManager; 1.34 +const nsITelephonyProvider = Ci.nsITelephonyProvider; 1.35 + 1.36 +const CALL_WAKELOCK_TIMEOUT = 5000; 1.37 + 1.38 +// Index of the CDMA second call which isn't held in RIL but only in TelephoyProvider. 1.39 +const CDMA_SECOND_CALL_INDEX = 2; 1.40 + 1.41 +const DIAL_ERROR_INVALID_STATE_ERROR = "InvalidStateError"; 1.42 +const DIAL_ERROR_OTHER_CONNECTION_IN_USE = "OtherConnectionInUse"; 1.43 + 1.44 +// Should match the value we set in dom/telephony/TelephonyCommon.h 1.45 +const OUTGOING_PLACEHOLDER_CALL_INDEX = 0xffffffff; 1.46 + 1.47 +let DEBUG; 1.48 +function debug(s) { 1.49 + dump("TelephonyProvider: " + s + "\n"); 1.50 +} 1.51 + 1.52 +XPCOMUtils.defineLazyGetter(this, "gAudioManager", function getAudioManager() { 1.53 + try { 1.54 + return Cc["@mozilla.org/telephony/audiomanager;1"] 1.55 + .getService(nsIAudioManager); 1.56 + } catch (ex) { 1.57 + //TODO on the phone this should not fall back as silently. 1.58 + 1.59 + // Fake nsIAudioManager implementation so that we can run the telephony 1.60 + // code in a non-Gonk build. 1.61 + if (DEBUG) debug("Using fake audio manager."); 1.62 + return { 1.63 + microphoneMuted: false, 1.64 + masterVolume: 1.0, 1.65 + masterMuted: false, 1.66 + phoneState: nsIAudioManager.PHONE_STATE_CURRENT, 1.67 + _forceForUse: {}, 1.68 + 1.69 + setForceForUse: function(usage, force) { 1.70 + this._forceForUse[usage] = force; 1.71 + }, 1.72 + 1.73 + getForceForUse: function(usage) { 1.74 + return this._forceForUse[usage] || nsIAudioManager.FORCE_NONE; 1.75 + } 1.76 + }; 1.77 + } 1.78 +}); 1.79 + 1.80 +XPCOMUtils.defineLazyServiceGetter(this, "gRadioInterfaceLayer", 1.81 + "@mozilla.org/ril;1", 1.82 + "nsIRadioInterfaceLayer"); 1.83 + 1.84 +XPCOMUtils.defineLazyServiceGetter(this, "gPowerManagerService", 1.85 + "@mozilla.org/power/powermanagerservice;1", 1.86 + "nsIPowerManagerService"); 1.87 + 1.88 +XPCOMUtils.defineLazyServiceGetter(this, "gSystemMessenger", 1.89 + "@mozilla.org/system-message-internal;1", 1.90 + "nsISystemMessagesInternal"); 1.91 + 1.92 +XPCOMUtils.defineLazyGetter(this, "gPhoneNumberUtils", function() { 1.93 + let ns = {}; 1.94 + Cu.import("resource://gre/modules/PhoneNumberUtils.jsm", ns); 1.95 + return ns.PhoneNumberUtils; 1.96 +}); 1.97 + 1.98 +function SingleCall(options){ 1.99 + this.clientId = options.clientId; 1.100 + this.callIndex = options.callIndex; 1.101 + this.state = options.state; 1.102 + this.number = options.number; 1.103 + this.isOutgoing = options.isOutgoing; 1.104 + this.isEmergency = options.isEmergency; 1.105 + this.isConference = options.isConference; 1.106 +} 1.107 +SingleCall.prototype = { 1.108 + clientId: null, 1.109 + callIndex: null, 1.110 + state: null, 1.111 + number: null, 1.112 + isOutgoing: false, 1.113 + isEmergency: false, 1.114 + isConference: false 1.115 +}; 1.116 + 1.117 +function ConferenceCall(state){ 1.118 + this.state = state; 1.119 +} 1.120 +ConferenceCall.prototype = { 1.121 + state: null 1.122 +}; 1.123 + 1.124 +function TelephonyProvider() { 1.125 + this._numClients = gRadioInterfaceLayer.numRadioInterfaces; 1.126 + this._listeners = []; 1.127 + this._currentCalls = {}; 1.128 + this._updateDebugFlag(); 1.129 + this.defaultServiceId = this._getDefaultServiceId(); 1.130 + 1.131 + Services.prefs.addObserver(kPrefRilDebuggingEnabled, this, false); 1.132 + Services.prefs.addObserver(kPrefDefaultServiceId, this, false); 1.133 + 1.134 + Services.obs.addObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); 1.135 + 1.136 + for (let i = 0; i < this._numClients; ++i) { 1.137 + this._enumerateCallsForClient(i); 1.138 + } 1.139 +} 1.140 +TelephonyProvider.prototype = { 1.141 + classID: GONK_TELEPHONYPROVIDER_CID, 1.142 + classInfo: XPCOMUtils.generateCI({classID: GONK_TELEPHONYPROVIDER_CID, 1.143 + contractID: GONK_TELEPHONYPROVIDER_CONTRACTID, 1.144 + classDescription: "TelephonyProvider", 1.145 + interfaces: [Ci.nsITelephonyProvider, 1.146 + Ci.nsIGonkTelephonyProvider], 1.147 + flags: Ci.nsIClassInfo.SINGLETON}), 1.148 + QueryInterface: XPCOMUtils.generateQI([Ci.nsITelephonyProvider, 1.149 + Ci.nsIGonkTelephonyProvider, 1.150 + Ci.nsIObserver]), 1.151 + 1.152 + // The following attributes/functions are used for acquiring/releasing the 1.153 + // CPU wake lock when the RIL handles the incoming call. Note that we need 1.154 + // a timer to bound the lock's life cycle to avoid exhausting the battery. 1.155 + _callRingWakeLock: null, 1.156 + _callRingWakeLockTimer: null, 1.157 + 1.158 + _acquireCallRingWakeLock: function() { 1.159 + if (!this._callRingWakeLock) { 1.160 + if (DEBUG) debug("Acquiring a CPU wake lock for handling incoming call."); 1.161 + this._callRingWakeLock = gPowerManagerService.newWakeLock("cpu"); 1.162 + } 1.163 + if (!this._callRingWakeLockTimer) { 1.164 + if (DEBUG) debug("Creating a timer for releasing the CPU wake lock."); 1.165 + this._callRingWakeLockTimer = 1.166 + Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); 1.167 + } 1.168 + if (DEBUG) debug("Setting the timer for releasing the CPU wake lock."); 1.169 + this._callRingWakeLockTimer 1.170 + .initWithCallback(this._releaseCallRingWakeLock.bind(this), 1.171 + CALL_WAKELOCK_TIMEOUT, Ci.nsITimer.TYPE_ONE_SHOT); 1.172 + }, 1.173 + 1.174 + _releaseCallRingWakeLock: function() { 1.175 + if (DEBUG) debug("Releasing the CPU wake lock for handling incoming call."); 1.176 + if (this._callRingWakeLockTimer) { 1.177 + this._callRingWakeLockTimer.cancel(); 1.178 + } 1.179 + if (this._callRingWakeLock) { 1.180 + this._callRingWakeLock.unlock(); 1.181 + this._callRingWakeLock = null; 1.182 + } 1.183 + }, 1.184 + 1.185 + _getClient: function(aClientId) { 1.186 + return gRadioInterfaceLayer.getRadioInterface(aClientId); 1.187 + }, 1.188 + 1.189 + // An array of nsITelephonyListener instances. 1.190 + _listeners: null, 1.191 + _notifyAllListeners: function(aMethodName, aArgs) { 1.192 + let listeners = this._listeners.slice(); 1.193 + for (let listener of listeners) { 1.194 + if (this._listeners.indexOf(listener) == -1) { 1.195 + // Listener has been unregistered in previous run. 1.196 + continue; 1.197 + } 1.198 + 1.199 + let handler = listener[aMethodName]; 1.200 + try { 1.201 + handler.apply(listener, aArgs); 1.202 + } catch (e) { 1.203 + debug("listener for " + aMethodName + " threw an exception: " + e); 1.204 + } 1.205 + } 1.206 + }, 1.207 + 1.208 + _matchActiveSingleCall: function(aCall) { 1.209 + return this._activeCall && 1.210 + this._activeCall instanceof SingleCall && 1.211 + this._activeCall.clientId === aCall.clientId && 1.212 + this._activeCall.callIndex === aCall.callIndex; 1.213 + }, 1.214 + 1.215 + /** 1.216 + * Track the active call and update the audio system as its state changes. 1.217 + */ 1.218 + _activeCall: null, 1.219 + _updateCallAudioState: function(aCall, aConferenceState) { 1.220 + if (aConferenceState === nsITelephonyProvider.CALL_STATE_CONNECTED) { 1.221 + this._activeCall = new ConferenceCall(aConferenceState); 1.222 + gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_IN_CALL; 1.223 + if (this.speakerEnabled) { 1.224 + gAudioManager.setForceForUse(nsIAudioManager.USE_COMMUNICATION, 1.225 + nsIAudioManager.FORCE_SPEAKER); 1.226 + } 1.227 + if (DEBUG) { 1.228 + debug("Active call, put audio system into PHONE_STATE_IN_CALL: " + 1.229 + gAudioManager.phoneState); 1.230 + } 1.231 + return; 1.232 + } 1.233 + 1.234 + if (aConferenceState === nsITelephonyProvider.CALL_STATE_UNKNOWN || 1.235 + aConferenceState === nsITelephonyProvider.CALL_STATE_HELD) { 1.236 + if (this._activeCall instanceof ConferenceCall) { 1.237 + this._activeCall = null; 1.238 + gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_NORMAL; 1.239 + if (DEBUG) { 1.240 + debug("No active call, put audio system into PHONE_STATE_NORMAL: " + 1.241 + gAudioManager.phoneState); 1.242 + } 1.243 + } 1.244 + return; 1.245 + } 1.246 + 1.247 + if (!aCall) { 1.248 + return; 1.249 + } 1.250 + 1.251 + if (aCall.isConference) { 1.252 + if (this._matchActiveSingleCall(aCall)) { 1.253 + this._activeCall = null; 1.254 + } 1.255 + return; 1.256 + } 1.257 + 1.258 + switch (aCall.state) { 1.259 + case nsITelephonyProvider.CALL_STATE_DIALING: // Fall through... 1.260 + case nsITelephonyProvider.CALL_STATE_ALERTING: 1.261 + case nsITelephonyProvider.CALL_STATE_CONNECTED: 1.262 + aCall.isActive = true; 1.263 + this._activeCall = new SingleCall(aCall); 1.264 + gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_IN_CALL; 1.265 + if (this.speakerEnabled) { 1.266 + gAudioManager.setForceForUse(nsIAudioManager.USE_COMMUNICATION, 1.267 + nsIAudioManager.FORCE_SPEAKER); 1.268 + } 1.269 + if (DEBUG) { 1.270 + debug("Active call, put audio system into PHONE_STATE_IN_CALL: " + 1.271 + gAudioManager.phoneState); 1.272 + } 1.273 + break; 1.274 + 1.275 + case nsITelephonyProvider.CALL_STATE_INCOMING: 1.276 + aCall.isActive = false; 1.277 + if (!this._activeCall) { 1.278 + // We can change the phone state into RINGTONE only when there's 1.279 + // no active call. 1.280 + gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_RINGTONE; 1.281 + if (DEBUG) { 1.282 + debug("Incoming call, put audio system into PHONE_STATE_RINGTONE: " + 1.283 + gAudioManager.phoneState); 1.284 + } 1.285 + } 1.286 + break; 1.287 + 1.288 + case nsITelephonyProvider.CALL_STATE_HELD: // Fall through... 1.289 + case nsITelephonyProvider.CALL_STATE_DISCONNECTED: 1.290 + aCall.isActive = false; 1.291 + if (this._matchActiveSingleCall(aCall)) { 1.292 + // Previously active call is not active now. 1.293 + this._activeCall = null; 1.294 + } 1.295 + 1.296 + if (!this._activeCall) { 1.297 + // No active call. Disable the audio. 1.298 + gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_NORMAL; 1.299 + if (DEBUG) { 1.300 + debug("No active call, put audio system into PHONE_STATE_NORMAL: " + 1.301 + gAudioManager.phoneState); 1.302 + } 1.303 + } 1.304 + break; 1.305 + } 1.306 + }, 1.307 + 1.308 + _convertRILCallState: function(aState) { 1.309 + switch (aState) { 1.310 + case RIL.CALL_STATE_UNKNOWN: 1.311 + return nsITelephonyProvider.CALL_STATE_UNKNOWN; 1.312 + case RIL.CALL_STATE_ACTIVE: 1.313 + return nsITelephonyProvider.CALL_STATE_CONNECTED; 1.314 + case RIL.CALL_STATE_HOLDING: 1.315 + return nsITelephonyProvider.CALL_STATE_HELD; 1.316 + case RIL.CALL_STATE_DIALING: 1.317 + return nsITelephonyProvider.CALL_STATE_DIALING; 1.318 + case RIL.CALL_STATE_ALERTING: 1.319 + return nsITelephonyProvider.CALL_STATE_ALERTING; 1.320 + case RIL.CALL_STATE_INCOMING: 1.321 + case RIL.CALL_STATE_WAITING: 1.322 + return nsITelephonyProvider.CALL_STATE_INCOMING; 1.323 + default: 1.324 + throw new Error("Unknown rilCallState: " + aState); 1.325 + } 1.326 + }, 1.327 + 1.328 + _convertRILSuppSvcNotification: function(aNotification) { 1.329 + switch (aNotification) { 1.330 + case RIL.GECKO_SUPP_SVC_NOTIFICATION_REMOTE_HELD: 1.331 + return nsITelephonyProvider.NOTIFICATION_REMOTE_HELD; 1.332 + case RIL.GECKO_SUPP_SVC_NOTIFICATION_REMOTE_RESUMED: 1.333 + return nsITelephonyProvider.NOTIFICATION_REMOTE_RESUMED; 1.334 + default: 1.335 + if (DEBUG) { 1.336 + debug("Unknown rilSuppSvcNotification: " + aNotification); 1.337 + } 1.338 + return; 1.339 + } 1.340 + }, 1.341 + 1.342 + _updateDebugFlag: function() { 1.343 + try { 1.344 + DEBUG = RIL.DEBUG_RIL || 1.345 + Services.prefs.getBoolPref(kPrefRilDebuggingEnabled); 1.346 + } catch (e) {} 1.347 + }, 1.348 + 1.349 + _getDefaultServiceId: function() { 1.350 + let id = Services.prefs.getIntPref(kPrefDefaultServiceId); 1.351 + let numRil = Services.prefs.getIntPref(kPrefRilNumRadioInterfaces); 1.352 + 1.353 + if (id >= numRil || id < 0) { 1.354 + id = 0; 1.355 + } 1.356 + 1.357 + return id; 1.358 + }, 1.359 + 1.360 + _currentCalls: null, 1.361 + _enumerateCallsForClient: function(aClientId) { 1.362 + if (DEBUG) debug("Enumeration of calls for client " + aClientId); 1.363 + 1.364 + this._getClient(aClientId).sendWorkerMessage("enumerateCalls", null, 1.365 + (function(response) { 1.366 + if (!this._currentCalls[aClientId]) { 1.367 + this._currentCalls[aClientId] = {}; 1.368 + } 1.369 + for (let call of response.calls) { 1.370 + call.clientId = aClientId; 1.371 + call.state = this._convertRILCallState(call.state); 1.372 + call.isActive = this._matchActiveSingleCall(call); 1.373 + call.isSwitchable = true; 1.374 + call.isMergeable = true; 1.375 + 1.376 + this._currentCalls[aClientId][call.callIndex] = call; 1.377 + } 1.378 + 1.379 + return false; 1.380 + }).bind(this)); 1.381 + }, 1.382 + 1.383 + /** 1.384 + * nsITelephonyProvider interface. 1.385 + */ 1.386 + 1.387 + defaultServiceId: 0, 1.388 + 1.389 + registerListener: function(aListener) { 1.390 + if (this._listeners.indexOf(aListener) >= 0) { 1.391 + throw Cr.NS_ERROR_UNEXPECTED; 1.392 + } 1.393 + 1.394 + this._listeners.push(aListener); 1.395 + }, 1.396 + 1.397 + unregisterListener: function(aListener) { 1.398 + let index = this._listeners.indexOf(aListener); 1.399 + if (index < 0) { 1.400 + throw Cr.NS_ERROR_UNEXPECTED; 1.401 + } 1.402 + 1.403 + this._listeners.splice(index, 1); 1.404 + }, 1.405 + 1.406 + enumerateCalls: function(aListener) { 1.407 + if (DEBUG) debug("Requesting enumeration of calls for callback"); 1.408 + 1.409 + for (let cid = 0; cid < this._numClients; ++cid) { 1.410 + let calls = this._currentCalls[cid]; 1.411 + if (!calls) { 1.412 + continue; 1.413 + } 1.414 + for (let i = 0, indexes = Object.keys(calls); i < indexes.length; ++i) { 1.415 + let call = calls[indexes[i]]; 1.416 + aListener.enumerateCallState(call.clientId, call.callIndex, 1.417 + call.state, call.number, 1.418 + call.isActive, call.isOutgoing, 1.419 + call.isEmergency, call.isConference, 1.420 + call.isSwitchable, call.isMergeable); 1.421 + } 1.422 + } 1.423 + aListener.enumerateCallStateComplete(); 1.424 + }, 1.425 + 1.426 + isDialing: false, 1.427 + dial: function(aClientId, aNumber, aIsEmergency, aTelephonyCallback) { 1.428 + if (DEBUG) debug("Dialing " + (aIsEmergency ? "emergency " : "") + aNumber); 1.429 + 1.430 + if (this.isDialing) { 1.431 + if (DEBUG) debug("Already has a dialing call. Drop."); 1.432 + aTelephonyCallback.notifyDialError(DIAL_ERROR_INVALID_STATE_ERROR); 1.433 + return; 1.434 + } 1.435 + 1.436 + function hasCallsOnOtherClient(aClientId) { 1.437 + for (let cid = 0; cid < this._numClients; ++cid) { 1.438 + if (cid === aClientId) { 1.439 + continue; 1.440 + } 1.441 + if (Object.keys(this._currentCalls[cid]).length !== 0) { 1.442 + return true; 1.443 + } 1.444 + } 1.445 + return false; 1.446 + } 1.447 + 1.448 + // For DSDS, if there is aleady a call on SIM 'aClientId', we cannot place 1.449 + // any new call on other SIM. 1.450 + if (hasCallsOnOtherClient.call(this, aClientId)) { 1.451 + if (DEBUG) debug("Already has a call on other sim. Drop."); 1.452 + aTelephonyCallback.notifyDialError(DIAL_ERROR_OTHER_CONNECTION_IN_USE); 1.453 + return; 1.454 + } 1.455 + 1.456 + // All calls in the conference is regarded as one conference call. 1.457 + function numCallsOnLine(aClientId) { 1.458 + let numCalls = 0; 1.459 + let hasConference = false; 1.460 + 1.461 + for (let cid in this._currentCalls[aClientId]) { 1.462 + let call = this._currentCalls[aClientId][cid]; 1.463 + if (call.isConference) { 1.464 + hasConference = true; 1.465 + } else { 1.466 + numCalls++; 1.467 + } 1.468 + } 1.469 + 1.470 + return hasConference ? numCalls + 1 : numCalls; 1.471 + } 1.472 + 1.473 + if (numCallsOnLine.call(this, aClientId) >= 2) { 1.474 + if (DEBUG) debug("Has more than 2 calls on line. Drop."); 1.475 + aTelephonyCallback.notifyDialError(DIAL_ERROR_INVALID_STATE_ERROR); 1.476 + return; 1.477 + } 1.478 + 1.479 + // we don't try to be too clever here, as the phone is probably in the 1.480 + // locked state. Let's just check if it's a number without normalizing 1.481 + if (!aIsEmergency) { 1.482 + aNumber = gPhoneNumberUtils.normalize(aNumber); 1.483 + } 1.484 + 1.485 + // Validate the number. 1.486 + if (!gPhoneNumberUtils.isPlainPhoneNumber(aNumber)) { 1.487 + // Note: isPlainPhoneNumber also accepts USSD and SS numbers 1.488 + if (DEBUG) debug("Number '" + aNumber + "' is not viable. Drop."); 1.489 + let errorMsg = RIL.RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[RIL.CALL_FAIL_UNOBTAINABLE_NUMBER]; 1.490 + aTelephonyCallback.notifyDialError(errorMsg); 1.491 + return; 1.492 + } 1.493 + 1.494 + function onCdmaDialSuccess() { 1.495 + let indexes = Object.keys(this._currentCalls[aClientId]); 1.496 + if (indexes.length != 1 ) { 1.497 + aTelephonyCallback.notifyDialSuccess(); 1.498 + return; 1.499 + } 1.500 + 1.501 + // RIL doesn't hold the 2nd call. We create one by ourselves. 1.502 + let childCall = { 1.503 + callIndex: CDMA_SECOND_CALL_INDEX, 1.504 + state: RIL.CALL_STATE_DIALING, 1.505 + number: aNumber, 1.506 + isOutgoing: true, 1.507 + isEmergency: false, 1.508 + isConference: false, 1.509 + isSwitchable: false, 1.510 + isMergeable: true, 1.511 + parentId: indexes[0] 1.512 + }; 1.513 + aTelephonyCallback.notifyDialSuccess(); 1.514 + 1.515 + // Manual update call state according to the request response. 1.516 + this.notifyCallStateChanged(aClientId, childCall); 1.517 + 1.518 + childCall.state = RIL.CALL_STATE_ACTIVE; 1.519 + this.notifyCallStateChanged(aClientId, childCall); 1.520 + 1.521 + let parentCall = this._currentCalls[aClientId][childCall.parentId]; 1.522 + parentCall.childId = CDMA_SECOND_CALL_INDEX; 1.523 + parentCall.state = RIL.CALL_STATE_HOLDING; 1.524 + parentCall.isSwitchable = false; 1.525 + parentCall.isMergeable = true; 1.526 + this.notifyCallStateChanged(aClientId, parentCall); 1.527 + }; 1.528 + 1.529 + this.isDialing = true; 1.530 + this._getClient(aClientId).sendWorkerMessage("dial", { 1.531 + number: aNumber, 1.532 + isDialEmergency: aIsEmergency 1.533 + }, (function(response) { 1.534 + this.isDialing = false; 1.535 + if (!response.success) { 1.536 + aTelephonyCallback.notifyDialError(response.errorMsg); 1.537 + return false; 1.538 + } 1.539 + 1.540 + if (response.isCdma) { 1.541 + onCdmaDialSuccess.call(this); 1.542 + } else { 1.543 + aTelephonyCallback.notifyDialSuccess(); 1.544 + } 1.545 + return false; 1.546 + }).bind(this)); 1.547 + }, 1.548 + 1.549 + hangUp: function(aClientId, aCallIndex) { 1.550 + let parentId = this._currentCalls[aClientId][aCallIndex].parentId; 1.551 + if (parentId) { 1.552 + // Should release both, child and parent, together. Since RIL holds only 1.553 + // the parent call, we send 'parentId' to RIL. 1.554 + this.hangUp(aClientId, parentId); 1.555 + } else { 1.556 + this._getClient(aClientId).sendWorkerMessage("hangUp", { callIndex: aCallIndex }); 1.557 + } 1.558 + }, 1.559 + 1.560 + startTone: function(aClientId, aDtmfChar) { 1.561 + this._getClient(aClientId).sendWorkerMessage("startTone", { dtmfChar: aDtmfChar }); 1.562 + }, 1.563 + 1.564 + stopTone: function(aClientId) { 1.565 + this._getClient(aClientId).sendWorkerMessage("stopTone"); 1.566 + }, 1.567 + 1.568 + answerCall: function(aClientId, aCallIndex) { 1.569 + this._getClient(aClientId).sendWorkerMessage("answerCall", { callIndex: aCallIndex }); 1.570 + }, 1.571 + 1.572 + rejectCall: function(aClientId, aCallIndex) { 1.573 + this._getClient(aClientId).sendWorkerMessage("rejectCall", { callIndex: aCallIndex }); 1.574 + }, 1.575 + 1.576 + holdCall: function(aClientId, aCallIndex) { 1.577 + let call = this._currentCalls[aClientId][aCallIndex]; 1.578 + if (!call || !call.isSwitchable) { 1.579 + // TODO: Bug 975949 - [B2G] Telephony should throw exceptions when some 1.580 + // operations aren't allowed instead of simply ignoring them. 1.581 + return; 1.582 + } 1.583 + 1.584 + this._getClient(aClientId).sendWorkerMessage("holdCall", { callIndex: aCallIndex }); 1.585 + }, 1.586 + 1.587 + resumeCall: function(aClientId, aCallIndex) { 1.588 + let call = this._currentCalls[aClientId][aCallIndex]; 1.589 + if (!call || !call.isSwitchable) { 1.590 + // TODO: Bug 975949 - [B2G] Telephony should throw exceptions when some 1.591 + // operations aren't allowed instead of simply ignoring them. 1.592 + return; 1.593 + } 1.594 + 1.595 + this._getClient(aClientId).sendWorkerMessage("resumeCall", { callIndex: aCallIndex }); 1.596 + }, 1.597 + 1.598 + conferenceCall: function(aClientId) { 1.599 + let indexes = Object.keys(this._currentCalls[aClientId]); 1.600 + if (indexes.length < 2) { 1.601 + // TODO: Bug 975949 - [B2G] Telephony should throw exceptions when some 1.602 + // operations aren't allowed instead of simply ignoring them. 1.603 + return; 1.604 + } 1.605 + 1.606 + for (let i = 0; i < indexes.length; ++i) { 1.607 + let call = this._currentCalls[aClientId][indexes[i]]; 1.608 + if (!call.isMergeable) { 1.609 + return; 1.610 + } 1.611 + } 1.612 + 1.613 + function onCdmaConferenceCallSuccess() { 1.614 + let indexes = Object.keys(this._currentCalls[aClientId]); 1.615 + if (indexes.length < 2) { 1.616 + return; 1.617 + } 1.618 + 1.619 + for (let i = 0; i < indexes.length; ++i) { 1.620 + let call = this._currentCalls[aClientId][indexes[i]]; 1.621 + call.state = RIL.CALL_STATE_ACTIVE; 1.622 + call.isConference = true; 1.623 + this.notifyCallStateChanged(aClientId, call); 1.624 + } 1.625 + this.notifyConferenceCallStateChanged(RIL.CALL_STATE_ACTIVE); 1.626 + }; 1.627 + 1.628 + this._getClient(aClientId).sendWorkerMessage("conferenceCall", null, 1.629 + (function(response) { 1.630 + if (!response.success) { 1.631 + this._notifyAllListeners("notifyConferenceError", [response.errorName, 1.632 + response.errorMsg]); 1.633 + return false; 1.634 + } 1.635 + 1.636 + if (response.isCdma) { 1.637 + onCdmaConferenceCallSuccess.call(this); 1.638 + } 1.639 + return false; 1.640 + }).bind(this)); 1.641 + }, 1.642 + 1.643 + separateCall: function(aClientId, aCallIndex) { 1.644 + let call = this._currentCalls[aClientId][aCallIndex]; 1.645 + if (!call || !call.isConference) { 1.646 + // TODO: Bug 975949 - [B2G] Telephony should throw exceptions when some 1.647 + // operations aren't allowed instead of simply ignoring them. 1.648 + return; 1.649 + } 1.650 + 1.651 + let parentId = call.parentId; 1.652 + if (parentId) { 1.653 + this.separateCall(aClientId, parentId); 1.654 + return; 1.655 + } 1.656 + 1.657 + function onCdmaSeparateCallSuccess() { 1.658 + // See 3gpp2, S.R0006-522-A v1.0. Table 4, XID 6S. 1.659 + let call = this._currentCalls[aClientId][aCallIndex]; 1.660 + if (!call || !call.isConference) { 1.661 + return; 1.662 + } 1.663 + 1.664 + let childId = call.childId; 1.665 + if (!childId) { 1.666 + return; 1.667 + } 1.668 + 1.669 + let childCall = this._currentCalls[aClientId][childId]; 1.670 + this.notifyCallDisconnected(aClientId, childCall); 1.671 + }; 1.672 + 1.673 + this._getClient(aClientId).sendWorkerMessage("separateCall", { 1.674 + callIndex: aCallIndex 1.675 + }, (function(response) { 1.676 + if (!response.success) { 1.677 + this._notifyAllListeners("notifyConferenceError", [response.errorName, 1.678 + response.errorMsg]); 1.679 + return false; 1.680 + } 1.681 + 1.682 + if (response.isCdma) { 1.683 + onCdmaSeparateCallSuccess.call(this); 1.684 + } 1.685 + return false; 1.686 + }).bind(this)); 1.687 + }, 1.688 + 1.689 + holdConference: function(aClientId) { 1.690 + this._getClient(aClientId).sendWorkerMessage("holdConference"); 1.691 + }, 1.692 + 1.693 + resumeConference: function(aClientId) { 1.694 + this._getClient(aClientId).sendWorkerMessage("resumeConference"); 1.695 + }, 1.696 + 1.697 + get microphoneMuted() { 1.698 + return gAudioManager.microphoneMuted; 1.699 + }, 1.700 + 1.701 + set microphoneMuted(aMuted) { 1.702 + if (aMuted == this.microphoneMuted) { 1.703 + return; 1.704 + } 1.705 + gAudioManager.microphoneMuted = aMuted; 1.706 + 1.707 + if (!this._activeCall) { 1.708 + gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_NORMAL; 1.709 + } 1.710 + }, 1.711 + 1.712 + get speakerEnabled() { 1.713 + let force = gAudioManager.getForceForUse(nsIAudioManager.USE_COMMUNICATION); 1.714 + return (force == nsIAudioManager.FORCE_SPEAKER); 1.715 + }, 1.716 + 1.717 + set speakerEnabled(aEnabled) { 1.718 + if (aEnabled == this.speakerEnabled) { 1.719 + return; 1.720 + } 1.721 + let force = aEnabled ? nsIAudioManager.FORCE_SPEAKER : 1.722 + nsIAudioManager.FORCE_NONE; 1.723 + gAudioManager.setForceForUse(nsIAudioManager.USE_COMMUNICATION, force); 1.724 + 1.725 + if (!this._activeCall) { 1.726 + gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_NORMAL; 1.727 + } 1.728 + }, 1.729 + 1.730 + /** 1.731 + * nsIGonkTelephonyProvider interface. 1.732 + */ 1.733 + 1.734 + /** 1.735 + * Handle call disconnects by updating our current state and the audio system. 1.736 + */ 1.737 + notifyCallDisconnected: function(aClientId, aCall) { 1.738 + if (DEBUG) debug("handleCallDisconnected: " + JSON.stringify(aCall)); 1.739 + 1.740 + aCall.state = nsITelephonyProvider.CALL_STATE_DISCONNECTED; 1.741 + let duration = ("started" in aCall && typeof aCall.started == "number") ? 1.742 + new Date().getTime() - aCall.started : 0; 1.743 + let data = { 1.744 + number: aCall.number, 1.745 + serviceId: aClientId, 1.746 + emergency: aCall.isEmergency, 1.747 + duration: duration, 1.748 + direction: aCall.isOutgoing ? "outgoing" : "incoming" 1.749 + }; 1.750 + gSystemMessenger.broadcastMessage("telephony-call-ended", data); 1.751 + 1.752 + aCall.clientId = aClientId; 1.753 + this._updateCallAudioState(aCall, null); 1.754 + 1.755 + let manualConfStateChange = false; 1.756 + let childId = this._currentCalls[aClientId][aCall.callIndex].childId; 1.757 + if (childId) { 1.758 + // Child cannot live without parent. 1.759 + let childCall = this._currentCalls[aClientId][childId]; 1.760 + this.notifyCallDisconnected(aClientId, childCall); 1.761 + } else { 1.762 + let parentId = this._currentCalls[aClientId][aCall.callIndex].parentId; 1.763 + if (parentId) { 1.764 + let parentCall = this._currentCalls[aClientId][parentId]; 1.765 + // The child is going to be released. 1.766 + delete parentCall.childId; 1.767 + if (parentCall.isConference) { 1.768 + // As the child is going to be gone, the parent should be moved out 1.769 + // of conference accordingly. 1.770 + manualConfStateChange = true; 1.771 + parentCall.isConference = false; 1.772 + parentCall.isSwitchable = true; 1.773 + parentCall.isMergeable = true; 1.774 + aCall.isConference = false; 1.775 + this.notifyCallStateChanged(aClientId, parentCall, true); 1.776 + } 1.777 + } 1.778 + } 1.779 + 1.780 + if (!aCall.failCause || 1.781 + aCall.failCause === RIL.GECKO_CALL_ERROR_NORMAL_CALL_CLEARING) { 1.782 + this._notifyAllListeners("callStateChanged", [aClientId, 1.783 + aCall.callIndex, 1.784 + aCall.state, 1.785 + aCall.number, 1.786 + aCall.isActive, 1.787 + aCall.isOutgoing, 1.788 + aCall.isEmergency, 1.789 + aCall.isConference, 1.790 + aCall.isSwitchable, 1.791 + aCall.isMergeable]); 1.792 + } else { 1.793 + this._notifyAllListeners("notifyError", 1.794 + [aClientId, aCall.callIndex, aCall.failCause]); 1.795 + } 1.796 + delete this._currentCalls[aClientId][aCall.callIndex]; 1.797 + 1.798 + if (manualConfStateChange) { 1.799 + this.notifyConferenceCallStateChanged(RIL.CALL_STATE_UNKNOWN); 1.800 + } 1.801 + }, 1.802 + 1.803 + /** 1.804 + * Handle an incoming call. 1.805 + * 1.806 + * Not much is known about this call at this point, but it's enough 1.807 + * to start bringing up the Phone app already. 1.808 + */ 1.809 + notifyCallRing: function() { 1.810 + // We need to acquire a CPU wake lock to avoid the system falling into 1.811 + // the sleep mode when the RIL handles the incoming call. 1.812 + this._acquireCallRingWakeLock(); 1.813 + 1.814 + gSystemMessenger.broadcastMessage("telephony-new-call", {}); 1.815 + }, 1.816 + 1.817 + /** 1.818 + * Handle call state changes by updating our current state and the audio 1.819 + * system. 1.820 + */ 1.821 + notifyCallStateChanged: function(aClientId, aCall, aSkipStateConversion) { 1.822 + if (DEBUG) debug("handleCallStateChange: " + JSON.stringify(aCall)); 1.823 + 1.824 + if (!aSkipStateConversion) { 1.825 + aCall.state = this._convertRILCallState(aCall.state); 1.826 + } 1.827 + 1.828 + if (aCall.state == nsITelephonyProvider.CALL_STATE_DIALING) { 1.829 + gSystemMessenger.broadcastMessage("telephony-new-call", {}); 1.830 + } 1.831 + 1.832 + aCall.clientId = aClientId; 1.833 + this._updateCallAudioState(aCall, null); 1.834 + 1.835 + let call = this._currentCalls[aClientId][aCall.callIndex]; 1.836 + if (call) { 1.837 + call.state = aCall.state; 1.838 + call.isConference = aCall.isConference; 1.839 + call.isEmergency = aCall.isEmergency; 1.840 + call.isActive = aCall.isActive; 1.841 + call.isSwitchable = aCall.isSwitchable != null ? 1.842 + aCall.isSwitchable : call.isSwitchable; 1.843 + call.isMergeable = aCall.isMergeable != null ? 1.844 + aCall.isMergeable : call.isMergeable; 1.845 + } else { 1.846 + call = aCall; 1.847 + call.isSwitchable = aCall.isSwitchable != null ? 1.848 + aCall.isSwitchable : true; 1.849 + call.isMergeable = aCall.isMergeable != null ? 1.850 + aCall.isMergeable : true; 1.851 + 1.852 + // Get the actual call for pending outgoing call. Remove the original one. 1.853 + if (this._currentCalls[aClientId][OUTGOING_PLACEHOLDER_CALL_INDEX] && 1.854 + call.callIndex != OUTGOING_PLACEHOLDER_CALL_INDEX && 1.855 + call.isOutgoing) { 1.856 + delete this._currentCalls[aClientId][OUTGOING_PLACEHOLDER_CALL_INDEX]; 1.857 + } 1.858 + 1.859 + this._currentCalls[aClientId][aCall.callIndex] = call; 1.860 + } 1.861 + 1.862 + this._notifyAllListeners("callStateChanged", [aClientId, 1.863 + call.callIndex, 1.864 + call.state, 1.865 + call.number, 1.866 + call.isActive, 1.867 + call.isOutgoing, 1.868 + call.isEmergency, 1.869 + call.isConference, 1.870 + call.isSwitchable, 1.871 + call.isMergeable]); 1.872 + }, 1.873 + 1.874 + notifyCdmaCallWaiting: function(aClientId, aNumber) { 1.875 + // We need to acquire a CPU wake lock to avoid the system falling into 1.876 + // the sleep mode when the RIL handles the incoming call. 1.877 + this._acquireCallRingWakeLock(); 1.878 + 1.879 + let call = this._currentCalls[aClientId][CDMA_SECOND_CALL_INDEX]; 1.880 + if (call) { 1.881 + // TODO: Bug 977503 - B2G RIL: [CDMA] update callNumber when a waiting 1.882 + // call comes after a 3way call. 1.883 + this.notifyCallDisconnected(aClientId, call); 1.884 + } 1.885 + this._notifyAllListeners("notifyCdmaCallWaiting", [aClientId, aNumber]); 1.886 + }, 1.887 + 1.888 + notifySupplementaryService: function(aClientId, aCallIndex, aNotification) { 1.889 + let notification = this._convertRILSuppSvcNotification(aNotification); 1.890 + this._notifyAllListeners("supplementaryServiceNotification", 1.891 + [aClientId, aCallIndex, notification]); 1.892 + }, 1.893 + 1.894 + notifyConferenceCallStateChanged: function(aState) { 1.895 + if (DEBUG) debug("handleConferenceCallStateChanged: " + aState); 1.896 + aState = this._convertRILCallState(aState); 1.897 + this._updateCallAudioState(null, aState); 1.898 + 1.899 + this._notifyAllListeners("conferenceCallStateChanged", [aState]); 1.900 + }, 1.901 + 1.902 + /** 1.903 + * nsIObserver interface. 1.904 + */ 1.905 + 1.906 + observe: function(aSubject, aTopic, aData) { 1.907 + switch (aTopic) { 1.908 + case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID: 1.909 + if (aData === kPrefRilDebuggingEnabled) { 1.910 + this._updateDebugFlag(); 1.911 + } else if (aData === kPrefDefaultServiceId) { 1.912 + this.defaultServiceId = this._getDefaultServiceId(); 1.913 + } 1.914 + break; 1.915 + 1.916 + case NS_XPCOM_SHUTDOWN_OBSERVER_ID: 1.917 + // Release the CPU wake lock for handling the incoming call. 1.918 + this._releaseCallRingWakeLock(); 1.919 + 1.920 + Services.obs.removeObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); 1.921 + break; 1.922 + } 1.923 + } 1.924 +}; 1.925 + 1.926 +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TelephonyProvider]);