dom/telephony/gonk/TelephonyProvider.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial