dom/telephony/gonk/TelephonyProvider.js

changeset 0
6474c204b198
     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]);

mercurial