dom/telephony/gonk/TelephonyProvider.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

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

mercurial