Bug 864485 - 3/3: Gonk backend specific. r=hsinyi
authorVicamo Yang <vyang@mozilla.com>
Sat, 07 Sep 2013 14:19:57 +0800
changeset 146087 b119d237e59c19245bd0593913052b093db9add1
parent 146086 7105ab97280117841886b4c6524126ce218ed8e6
child 146088 3d41fb8801aa0f2b902dd92f6675c04b89acf204
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewershsinyi
bugs864485
milestone26.0a1
Bug 864485 - 3/3: Gonk backend specific. r=hsinyi
b2g/installer/package-manifest.in
dom/system/gonk/RILContentHelper.js
dom/system/gonk/RadioInterfaceLayer.js
dom/system/gonk/nsIRadioInterfaceLayer.idl
dom/telephony/TelephonyFactory.cpp
dom/telephony/gonk/TelephonyProvider.js
dom/telephony/gonk/TelephonyProvider.manifest
dom/telephony/moz.build
dom/telephony/nsIGonkTelephonyProvider.idl
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -466,16 +466,18 @@
 @BINPATH@/components/WifiWorker.js
 @BINPATH@/components/WifiWorker.manifest
 @BINPATH@/components/DOMWifiManager.js
 @BINPATH@/components/DOMWifiManager.manifest
 @BINPATH@/components/NetworkStatsManager.js
 @BINPATH@/components/NetworkStatsManager.manifest
 @BINPATH@/components/NetworkInterfaceListService.manifest
 @BINPATH@/components/NetworkInterfaceListService.js
+@BINPATH@/components/TelephonyProvider.manifest
+@BINPATH@/components/TelephonyProvider.js
 #endif
 #ifdef MOZ_ENABLE_DBUS
 @BINPATH@/components/@DLL_PREFIX@dbusservice@DLL_SUFFIX@
 #endif
 @BINPATH@/components/nsINIProcessor.manifest
 @BINPATH@/components/nsINIProcessor.js
 @BINPATH@/components/nsPrompter.manifest
 @BINPATH@/components/nsPrompter.js
--- a/dom/system/gonk/RILContentHelper.js
+++ b/dom/system/gonk/RILContentHelper.js
@@ -66,27 +66,23 @@ const CELLBROADCASTETWSINFO_CID =
 const DOMMMIERROR_CID =
   Components.ID("{6b204c42-7928-4e71-89ad-f90cd82aff96}");
 
 const RIL_IPC_MSG_NAMES = [
   "RIL:CardStateChanged",
   "RIL:IccInfoChanged",
   "RIL:VoiceInfoChanged",
   "RIL:DataInfoChanged",
-  "RIL:EnumerateCalls",
   "RIL:GetAvailableNetworks",
   "RIL:NetworkSelectionModeChanged",
   "RIL:SelectNetwork",
   "RIL:SelectNetworkAuto",
-  "RIL:CallStateChanged",
   "RIL:EmergencyCbModeChanged",
   "RIL:VoicemailNotification",
   "RIL:VoicemailInfoChanged",
-  "RIL:CallError",
-  "RIL:SuppSvcNotification",
   "RIL:CardLockResult",
   "RIL:CardLockRetryCount",
   "RIL:USSDReceived",
   "RIL:SendMMI",
   "RIL:CancelMMI",
   "RIL:StkCommand",
   "RIL:StkSessionEnd",
   "RIL:DataError",
@@ -103,32 +99,26 @@ const RIL_IPC_MSG_NAMES = [
   "RIL:CfStateChanged",
   "RIL:IccOpenChannel",
   "RIL:IccCloseChannel",
   "RIL:IccExchangeAPDU",
   "RIL:ReadIccContacts",
   "RIL:UpdateIccContact",
   "RIL:SetRoamingPreference",
   "RIL:GetRoamingPreference",
-  "RIL:CdmaCallWaiting",
   "RIL:ExitEmergencyCbMode",
   "RIL:SetVoicePrivacyMode",
   "RIL:GetVoicePrivacyMode",
-  "RIL:ConferenceCallStateChanged",
   "RIL:OtaStatusChanged"
 ];
 
 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
                                    "@mozilla.org/childprocessmessagemanager;1",
                                    "nsISyncMessageSender");
 
-XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator",
-                                   "@mozilla.org/uuid-generator;1",
-                                   "nsIUUIDGenerator");
-
 function MobileIccCardLockResult(options) {
   this.lockType = options.lockType;
   this.enabled = options.enabled;
   this.retryCount = options.retryCount;
   this.success = options.success;
 }
 MobileIccCardLockResult.prototype = {
   __exposedProps__ : {lockType: 'r',
@@ -444,27 +434,25 @@ function RILContentHelper() {
 }
 
 RILContentHelper.prototype = {
   __proto__: DOMRequestIpcHelper.prototype,
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIMobileConnectionProvider,
                                          Ci.nsICellBroadcastProvider,
                                          Ci.nsIVoicemailProvider,
-                                         Ci.nsITelephonyProvider,
                                          Ci.nsIIccProvider,
                                          Ci.nsIObserver,
                                          Ci.nsISupportsWeakReference]),
   classID:   RILCONTENTHELPER_CID,
   classInfo: XPCOMUtils.generateCI({classID: RILCONTENTHELPER_CID,
                                     classDescription: "RILContentHelper",
                                     interfaces: [Ci.nsIMobileConnectionProvider,
                                                  Ci.nsICellBroadcastProvider,
                                                  Ci.nsIVoicemailProvider,
-                                                 Ci.nsITelephonyProvider,
                                                  Ci.nsIIccProvider]}),
 
   // An utility function to copy objects.
   updateInfo: function updateInfo(srcInfo, destInfo) {
     for (let key in srcInfo) {
       destInfo[key] = srcInfo[key];
     }
   },
@@ -1276,21 +1264,19 @@ RILContentHelper.prototype = {
         requestId: requestId,
       }
     });
 
     return request;
   },
 
   _mobileConnectionListeners: null,
-  _telephonyListeners: null,
   _cellBroadcastListeners: null,
   _voicemailListeners: null,
   _iccListeners: null,
-  _enumerateTelephonyCallbacks: null,
 
   voicemailStatus: null,
 
   getVoicemailInfo: function getVoicemailInfo() {
     // Get voicemail infomation by IPC only on first time.
     this.getVoicemailInfo = function getVoicemailInfo() {
       return this.voicemailInfo;
     };
@@ -1342,34 +1328,16 @@ RILContentHelper.prototype = {
     this.registerListener("_mobileConnectionListeners", listener);
     cpmm.sendAsyncMessage("RIL:RegisterMobileConnectionMsg");
   },
 
   unregisterMobileConnectionMsg: function unregisteMobileConnectionMsg(listener) {
     this.unregisterListener("_mobileConnectionListeners", listener);
   },
 
-  registerTelephonyMsg: function registerTelephonyMsg(listener) {
-    debug("Registering for telephony-related messages");
-    this.registerListener("_telephonyListeners", listener);
-    cpmm.sendAsyncMessage("RIL:RegisterTelephonyMsg");
-  },
-
-  unregisterTelephonyMsg: function unregisteTelephonyMsg(listener) {
-    this.unregisterListener("_telephonyListeners", listener);
-
-    // We also need to make sure the listener is removed from
-    // _enumerateTelephonyCallbacks.
-    let index = this._enumerateTelephonyCallbacks.indexOf(listener);
-    if (index != -1) {
-      this._enumerateTelephonyCallbacks.splice(index, 1);
-      if (DEBUG) debug("Unregistered enumerateTelephony callback: " + listener);
-    }
-  },
-
   registerVoicemailMsg: function registerVoicemailMsg(listener) {
     debug("Registering for voicemail-related messages");
     this.registerListener("_voicemailListeners", listener);
     cpmm.sendAsyncMessage("RIL:RegisterVoicemailMsg");
   },
 
   unregisterVoicemailMsg: function unregisteVoicemailMsg(listener) {
     this.unregisterListener("_voicemailListeners", listener);
@@ -1390,145 +1358,16 @@ RILContentHelper.prototype = {
     this.registerListener("_iccListeners", listener);
     cpmm.sendAsyncMessage("RIL:RegisterIccMsg");
   },
 
   unregisterIccMsg: function unregisterIccMsg(listener) {
     this.unregisterListener("_iccListeners", listener);
   },
 
-  enumerateCalls: function enumerateCalls(callback) {
-    debug("Requesting enumeration of calls for callback: " + callback);
-    // We need 'requestId' to meet the 'RILContentHelper <--> RadioInterfaceLayer'
-    // protocol.
-    let requestId = this._getRandomId();
-    cpmm.sendAsyncMessage("RIL:EnumerateCalls", {
-      clientId: 0,
-      data: {
-        requestId: requestId
-      }
-    });
-    if (!this._enumerateTelephonyCallbacks) {
-      this._enumerateTelephonyCallbacks = [];
-    }
-    this._enumerateTelephonyCallbacks.push(callback);
-  },
-
-  startTone: function startTone(dtmfChar) {
-    debug("Sending Tone for " + dtmfChar);
-    cpmm.sendAsyncMessage("RIL:StartTone", {
-      clientId: 0,
-      data: dtmfChar
-    });
-  },
-
-  stopTone: function stopTone() {
-    debug("Stopping Tone");
-    cpmm.sendAsyncMessage("RIL:StopTone", {clientId: 0});
-  },
-
-  dial: function dial(number) {
-    debug("Dialing " + number);
-    cpmm.sendAsyncMessage("RIL:Dial", {
-      clientId: 0,
-      data: number
-    });
-  },
-
-  dialEmergency: function dialEmergency(number) {
-    debug("Dialing emergency " + number);
-    cpmm.sendAsyncMessage("RIL:DialEmergency", {
-      clientId: 0,
-      data: number
-    });
-  },
-
-  hangUp: function hangUp(callIndex) {
-    debug("Hanging up call no. " + callIndex);
-    cpmm.sendAsyncMessage("RIL:HangUp", {
-      clientId: 0,
-      data: callIndex
-    });
-  },
-
-  answerCall: function answerCall(callIndex) {
-    cpmm.sendAsyncMessage("RIL:AnswerCall", {
-      clientId: 0,
-      data: callIndex
-    });
-  },
-
-  rejectCall: function rejectCall(callIndex) {
-    cpmm.sendAsyncMessage("RIL:RejectCall", {
-      clientId: 0,
-      data: callIndex
-    });
-  },
-
-  holdCall: function holdCall(callIndex) {
-    cpmm.sendAsyncMessage("RIL:HoldCall", {
-      clientId: 0,
-      data: callIndex
-    });
-  },
-
-  resumeCall: function resumeCall(callIndex) {
-    cpmm.sendAsyncMessage("RIL:ResumeCall", {
-      clientId: 0,
-      data: callIndex
-    });
-  },
-
-  conferenceCall: function conferenceCall() {
-    cpmm.sendAsyncMessage("RIL:ConferenceCall", {
-      clientId: 0
-    });
-  },
-
-  separateCall: function separateCall(callIndex) {
-    cpmm.sendAsyncMessage("RIL:SeparateCall", {
-      clientId: 0,
-      data: callIndex
-    });
-  },
-
-  holdConference: function holdConference() {
-    cpmm.sendAsyncMessage("RIL:HoldConference", {
-      clientId: 0
-    });
-  },
-
-  resumeConference: function resumeConference() {
-    cpmm.sendAsyncMessage("RIL:ResumeConference", {
-      clientId: 0
-    });
-  },
-
-  get microphoneMuted() {
-    return cpmm.sendSyncMessage("RIL:GetMicrophoneMuted", {clientId: 0})[0];
-  },
-
-  set microphoneMuted(value) {
-    cpmm.sendAsyncMessage("RIL:SetMicrophoneMuted", {
-      clientId: 0,
-      data: value
-    });
-  },
-
-  get speakerEnabled() {
-    return cpmm.sendSyncMessage("RIL:GetSpeakerEnabled", {clientId: 0})[0];
-  },
-
-  set speakerEnabled(value) {
-    cpmm.sendAsyncMessage("RIL:SetSpeakerEnabled", {
-      clientId: 0,
-      data: value
-    });
-  },
-
   // nsIObserver
 
   observe: function observe(subject, topic, data) {
     if (topic == "xpcom-shutdown") {
       this.destroyDOMRequestHelper();
       Services.obs.removeObserver(this, "xpcom-shutdown");
     }
   },
@@ -1615,62 +1454,30 @@ RILContentHelper.prototype = {
                            "notifyDataChanged",
                            null);
         break;
       case "RIL:OtaStatusChanged":
         this._deliverEvent("_mobileConnectionListeners",
                            "notifyOtaStatusChanged",
                            [msg.json.data]);
         break;
-      case "RIL:EnumerateCalls":
-        this.handleEnumerateCalls(msg.json.calls);
-        break;
       case "RIL:GetAvailableNetworks":
         this.handleGetAvailableNetworks(msg.json);
         break;
       case "RIL:NetworkSelectionModeChanged":
         this.rilContext.networkSelectionMode = msg.json.data.mode;
         break;
       case "RIL:SelectNetwork":
         this.handleSelectNetwork(msg.json,
                                  RIL.GECKO_NETWORK_SELECTION_MANUAL);
         break;
       case "RIL:SelectNetworkAuto":
         this.handleSelectNetwork(msg.json,
                                  RIL.GECKO_NETWORK_SELECTION_AUTOMATIC);
         break;
-      case "RIL:CallStateChanged": {
-        let data = msg.json.data;
-        this._deliverEvent("_telephonyListeners",
-                           "callStateChanged",
-                           [data.callIndex, data.state,
-                            data.number, data.isActive,
-                            data.isOutgoing, data.isEmergency,
-                            data.isConference]);
-        break;
-      }
-      case "RIL:ConferenceCallStateChanged": {
-        let data = msg.json.data;
-        this._deliverEvent("_telephonyListeners",
-                           "conferenceCallStateChanged",
-                           [data]);
-        break;
-      }
-      case "RIL:CallError": {
-        let data = msg.json.data;
-        this._deliverEvent("_telephonyListeners",
-                           "notifyError",
-                           [data.callIndex, data.errorMsg]);
-        break;
-      }
-      case "RIL:SuppSvcNotification":
-        this._deliverEvent("_telephonyListeners",
-                           "supplementaryServiceNotification",
-                           [msg.json.callIndex, msg.json.notification]);
-        break;
       case "RIL:VoicemailNotification":
         this.handleVoicemailNotification(msg.json.data);
         break;
       case "RIL:VoicemailInfoChanged":
         this.updateInfo(msg.json.data, this.voicemailInfo);
         break;
       case "RIL:CardLockResult":
         if (msg.json.success) {
@@ -1781,21 +1588,16 @@ RILContentHelper.prototype = {
       }
       case "RIL:SetRoamingPreference":
         this.handleSimpleRequest(msg.json.requestId, msg.json.errorMsg, null);
         break;
       case "RIL:GetRoamingPreference":
         this.handleSimpleRequest(msg.json.requestId, msg.json.errorMsg,
                                  msg.json.mode);
         break;
-      case "RIL:CdmaCallWaiting":
-        this._deliverEvent("_telephonyListeners",
-                           "notifyCdmaCallWaiting",
-                           [msg.json.data]);
-        break;
       case "RIL:ExitEmergencyCbMode":
         this.handleExitEmergencyCbMode(msg.json);
         break;
       case "RIL:EmergencyCbModeChanged":
         let data = msg.json.data;
         this._deliverEvent("_mobileConnectionListeners",
                            "notifyEmergencyCbModeChanged",
                            [data.active, data.timeoutMs]);
@@ -1805,45 +1607,16 @@ RILContentHelper.prototype = {
         break;
       case "RIL:GetVoicePrivacyMode":
         this.handleSimpleRequest(msg.json.requestId, msg.json.errorMsg,
                                  msg.json.enabled);
         break;
     }
   },
 
-  handleEnumerateCalls: function handleEnumerateCalls(calls) {
-    debug("handleEnumerateCalls: " + JSON.stringify(calls));
-    let callback = this._enumerateTelephonyCallbacks.shift();
-    if (!calls.length) {
-      callback.enumerateCallStateComplete();
-      return;
-    }
-
-    for (let i in calls) {
-      let call = calls[i];
-      let keepGoing;
-      try {
-        keepGoing =
-          callback.enumerateCallState(call.callIndex, call.state, call.number,
-                                      call.isActive, call.isOutgoing,
-                                      call.isEmergency, call.isConference);
-      } catch (e) {
-        debug("callback handler for 'enumerateCallState' threw an " +
-              " exception: " + e);
-        keepGoing = true;
-      }
-      if (!keepGoing) {
-        break;
-      }
-    }
-
-    callback.enumerateCallStateComplete();
-  },
-
   handleSimpleRequest: function handleSimpleRequest(requestId, errorMsg, result) {
     if (errorMsg) {
       this.fireRequestError(requestId, errorMsg);
     } else {
       this.fireRequestSuccess(requestId, result);
     }
   },
 
@@ -2046,20 +1819,16 @@ RILContentHelper.prototype = {
       let mmiError = new this._window.DOMMMIError(result.serviceCode,
                                                   message.errorMsg,
                                                   null,
                                                   result.additionalInformation);
       Services.DOMRequest.fireDetailedError(request, mmiError);
     }
   },
 
-  _getRandomId: function _getRandomId() {
-    return gUUIDGenerator.generateUUID().toString();
-  },
-
   _deliverEvent: function _deliverEvent(listenerType, name, args) {
     let thisListeners = this[listenerType];
     if (!thisListeners) {
       return;
     }
 
     let listeners = thisListeners.slice();
     for (let listener of listeners) {
--- a/dom/system/gonk/RadioInterfaceLayer.js
+++ b/dom/system/gonk/RadioInterfaceLayer.js
@@ -41,19 +41,16 @@ function debug(s) {
 
 const RADIOINTERFACELAYER_CID =
   Components.ID("{2d831c8d-6017-435b-a80c-e5d422810cea}");
 const RADIOINTERFACE_CID =
   Components.ID("{6a7c91f0-a2b3-4193-8562-8969296c0b54}");
 const RILNETWORKINTERFACE_CID =
   Components.ID("{3bdd52a9-3965-4130-b569-0ac5afed045e}");
 
-const nsIAudioManager = Ci.nsIAudioManager;
-const nsITelephonyProvider = Ci.nsITelephonyProvider;
-
 const kNetworkInterfaceStateChangedTopic = "network-interface-state-changed";
 const kSmsReceivedObserverTopic          = "sms-received";
 const kSilentSmsReceivedObserverTopic    = "silent-sms-received";
 const kSmsSendingObserverTopic           = "sms-sending";
 const kSmsSentObserverTopic              = "sms-sent";
 const kSmsFailedObserverTopic            = "sms-failed";
 const kSmsDeliverySuccessObserverTopic   = "sms-delivery-success";
 const kSmsDeliveryErrorObserverTopic     = "sms-delivery-error";
@@ -68,40 +65,17 @@ const kCellBroadcastDisabled            
 const kPrefenceChangedObserverTopic      = "nsPref:changed";
 const kClirModePreference                = "ril.clirMode";
 
 const DOM_MOBILE_MESSAGE_DELIVERY_RECEIVED = "received";
 const DOM_MOBILE_MESSAGE_DELIVERY_SENDING  = "sending";
 const DOM_MOBILE_MESSAGE_DELIVERY_SENT     = "sent";
 const DOM_MOBILE_MESSAGE_DELIVERY_ERROR    = "error";
 
-const CALL_WAKELOCK_TIMEOUT              = 5000;
-const RADIO_POWER_OFF_TIMEOUT            = 30000;
-
-const RIL_IPC_TELEPHONY_MSG_NAMES = [
-  "RIL:EnumerateCalls",
-  "RIL:GetMicrophoneMuted",
-  "RIL:SetMicrophoneMuted",
-  "RIL:GetSpeakerEnabled",
-  "RIL:SetSpeakerEnabled",
-  "RIL:StartTone",
-  "RIL:StopTone",
-  "RIL:Dial",
-  "RIL:DialEmergency",
-  "RIL:HangUp",
-  "RIL:AnswerCall",
-  "RIL:RejectCall",
-  "RIL:HoldCall",
-  "RIL:ResumeCall",
-  "RIL:RegisterTelephonyMsg",
-  "RIL:ConferenceCall",
-  "RIL:SeparateCall",
-  "RIL:HoldConference",
-  "RIL:ResumeConference"
-];
+const RADIO_POWER_OFF_TIMEOUT = 30000;
 
 const RIL_IPC_MOBILECONNECTION_MSG_NAMES = [
   "RIL:GetRilContext",
   "RIL:GetAvailableNetworks",
   "RIL:SelectNetwork",
   "RIL:SelectNetworkAuto",
   "RIL:SendMMI",
   "RIL:CancelMMI",
@@ -183,86 +157,32 @@ XPCOMUtils.defineLazyServiceGetter(this,
 XPCOMUtils.defineLazyServiceGetter(this, "gTimeService",
                                    "@mozilla.org/time/timeservice;1",
                                    "nsITimeService");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gSystemWorkerManager",
                                    "@mozilla.org/telephony/system-worker-manager;1",
                                    "nsISystemWorkerManager");
 
+XPCOMUtils.defineLazyServiceGetter(this, "gTelephonyProvider",
+                                   "@mozilla.org/telephony/telephonyprovider;1",
+                                   "nsIGonkTelephonyProvider");
+
 XPCOMUtils.defineLazyGetter(this, "WAP", function () {
   let wap = {};
   Cu.import("resource://gre/modules/WapPushManager.js", wap);
   return wap;
 });
 
 XPCOMUtils.defineLazyGetter(this, "PhoneNumberUtils", function () {
   let ns = {};
   Cu.import("resource://gre/modules/PhoneNumberUtils.jsm", ns);
   return ns.PhoneNumberUtils;
 });
 
-function convertRILCallState(state) {
-  switch (state) {
-    case RIL.CALL_STATE_ACTIVE:
-      return nsITelephonyProvider.CALL_STATE_CONNECTED;
-    case RIL.CALL_STATE_HOLDING:
-      return nsITelephonyProvider.CALL_STATE_HELD;
-    case RIL.CALL_STATE_DIALING:
-      return nsITelephonyProvider.CALL_STATE_DIALING;
-    case RIL.CALL_STATE_ALERTING:
-      return nsITelephonyProvider.CALL_STATE_ALERTING;
-    case RIL.CALL_STATE_INCOMING:
-    case RIL.CALL_STATE_WAITING:
-      return nsITelephonyProvider.CALL_STATE_INCOMING;
-    default:
-      throw new Error("Unknown rilCallState: " + state);
-  }
-}
-
-function convertRILSuppSvcNotification(notification) {
-  switch (notification) {
-    case RIL.GECKO_SUPP_SVC_NOTIFICATION_REMOTE_HELD:
-      return nsITelephonyProvider.NOTIFICATION_REMOTE_HELD;
-    case RIL.GECKO_SUPP_SVC_NOTIFICATION_REMOTE_RESUMED:
-      return nsITelephonyProvider.NOTIFICATION_REMOTE_RESUMED;
-    default:
-      throw new Error("Unknown rilSuppSvcNotification: " + notification);
-  }
-}
-
-/**
- * Fake nsIAudioManager implementation so that we can run the telephony
- * code in a non-Gonk build.
- */
-let FakeAudioManager = {
-  microphoneMuted: false,
-  masterVolume: 1.0,
-  masterMuted: false,
-  phoneState: nsIAudioManager.PHONE_STATE_CURRENT,
-  _forceForUse: {},
-  setForceForUse: function setForceForUse(usage, force) {
-    this._forceForUse[usage] = force;
-  },
-  getForceForUse: function setForceForUse(usage) {
-    return this._forceForUse[usage] || nsIAudioManager.FORCE_NONE;
-  }
-};
-
-XPCOMUtils.defineLazyGetter(this, "gAudioManager", function getAudioManager() {
-  try {
-    return Cc["@mozilla.org/telephony/audiomanager;1"]
-             .getService(nsIAudioManager);
-  } catch (ex) {
-    //TODO on the phone this should not fall back as silently.
-    if (DEBUG) debug("Using fake audio manager.");
-    return FakeAudioManager;
-  }
-});
-
 XPCOMUtils.defineLazyGetter(this, "gMessageManager", function () {
   return {
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIMessageListener,
                                            Ci.nsIObserver]),
 
     ril: null,
 
     // Manage message targets in terms of topic. Only the authorized and
@@ -285,38 +205,32 @@ XPCOMUtils.defineLazyGetter(this, "gMess
       this.ril = null;
 
       Services.obs.removeObserver(this, "xpcom-shutdown");
       this._unregisterMessageListeners();
     },
 
     _registerMessageListeners: function _registerMessageListeners() {
       ppmm.addMessageListener("child-process-shutdown", this);
-      for (let msgname of RIL_IPC_TELEPHONY_MSG_NAMES) {
-        ppmm.addMessageListener(msgname, this);
-      }
       for (let msgname of RIL_IPC_MOBILECONNECTION_MSG_NAMES) {
         ppmm.addMessageListener(msgname, this);
       }
       for (let msgName of RIL_IPC_ICCMANAGER_MSG_NAMES) {
         ppmm.addMessageListener(msgName, this);
       }
       for (let msgname of RIL_IPC_VOICEMAIL_MSG_NAMES) {
         ppmm.addMessageListener(msgname, this);
       }
       for (let msgname of RIL_IPC_CELLBROADCAST_MSG_NAMES) {
         ppmm.addMessageListener(msgname, this);
       }
     },
 
     _unregisterMessageListeners: function _unregisterMessageListeners() {
       ppmm.removeMessageListener("child-process-shutdown", this);
-      for (let msgname of RIL_IPC_TELEPHONY_MSG_NAMES) {
-        ppmm.removeMessageListener(msgname, this);
-      }
       for (let msgname of RIL_IPC_MOBILECONNECTION_MSG_NAMES) {
         ppmm.removeMessageListener(msgname, this);
       }
       for (let msgName of RIL_IPC_ICCMANAGER_MSG_NAMES) {
         ppmm.removeMessageListener(msgName, this);
       }
       for (let msgname of RIL_IPC_VOICEMAIL_MSG_NAMES) {
         ppmm.removeMessageListener(msgname, this);
@@ -425,25 +339,17 @@ XPCOMUtils.defineLazyGetter(this, "gMess
       if (msg.name == "child-process-shutdown") {
         // By the time we receive child-process-shutdown, the child process has
         // already forgotten its permissions so we need to unregister the target
         // for every permission.
         this._unregisterMessageTarget(null, msg.target);
         return;
       }
 
-      if (RIL_IPC_TELEPHONY_MSG_NAMES.indexOf(msg.name) != -1) {
-        if (!msg.target.assertPermission("telephony")) {
-          if (DEBUG) {
-            debug("Telephony message " + msg.name +
-                  " from a content process with no 'telephony' privileges.");
-          }
-          return null;
-        }
-      } else if (RIL_IPC_MOBILECONNECTION_MSG_NAMES.indexOf(msg.name) != -1) {
+      if (RIL_IPC_MOBILECONNECTION_MSG_NAMES.indexOf(msg.name) != -1) {
         if (!msg.target.assertPermission("mobileconnection")) {
           if (DEBUG) {
             debug("MobileConnection message " + msg.name +
                   " from a content process with no 'mobileconnection' privileges.");
           }
           return null;
         }
       } else if (RIL_IPC_ICCMANAGER_MSG_NAMES.indexOf(msg.name) != -1) {
@@ -471,19 +377,16 @@ XPCOMUtils.defineLazyGetter(this, "gMess
           return null;
         }
       } else {
         if (DEBUG) debug("Ignoring unknown message type: " + msg.name);
         return null;
       }
 
       switch (msg.name) {
-        case "RIL:RegisterTelephonyMsg":
-          this._registerMessageTarget("telephony", msg.target);
-          return;
         case "RIL:RegisterMobileConnectionMsg":
           this._registerMessageTarget("mobileconnection", msg.target);
           return;
         case "RIL:RegisterIccMsg":
           this._registerMessageTarget("icc", msg.target);
           return;
         case "RIL:RegisterVoicemailMsg":
           this._registerMessageTarget("voicemail", msg.target);
@@ -514,23 +417,16 @@ XPCOMUtils.defineLazyGetter(this, "gMess
           this._resendQueuedTargetMessage();
           break;
         case "xpcom-shutdown":
           this._shutdown();
           break;
       }
     },
 
-    sendTelephonyMessage: function sendTelephonyMessage(message, clientId, data) {
-      this._sendTargetMessage("telephony", message, {
-        clientId: clientId,
-        data: data
-      });
-    },
-
     sendMobileConnectionMessage: function sendMobileConnectionMessage(message, clientId, data) {
       this._sendTargetMessage("mobileconnection", message, {
         clientId: clientId,
         data: data
       });
     },
 
     sendVoicemailMessage: function sendVoicemailMessage(message, clientId, data) {
@@ -889,71 +785,16 @@ RadioInterface.prototype = {
   /**
    * Process a message from the content process.
    */
   receiveMessage: function receiveMessage(msg) {
     switch (msg.name) {
       case "RIL:GetRilContext":
         // This message is sync.
         return this.rilContext;
-      case "RIL:EnumerateCalls":
-        this.enumerateCalls(msg.target, msg.json.data);
-        break;
-      case "RIL:GetMicrophoneMuted":
-        // This message is sync.
-        return this.microphoneMuted;
-      case "RIL:SetMicrophoneMuted":
-        this.microphoneMuted = msg.json.data;
-        break;
-      case "RIL:GetSpeakerEnabled":
-        // This message is sync.
-        return this.speakerEnabled;
-      case "RIL:SetSpeakerEnabled":
-        this.speakerEnabled = msg.json.data;
-        break;
-      case "RIL:StartTone":
-        this.workerMessenger.send("startTone", { dtmfChar: msg.json.data });
-        break;
-      case "RIL:StopTone":
-        this.workerMessenger.send("stopTone");
-        break;
-      case "RIL:Dial":
-        this.dial(msg.json.data);
-        break;
-      case "RIL:DialEmergency":
-        this.dialEmergency(msg.json.data);
-        break;
-      case "RIL:HangUp":
-        this.workerMessenger.send("hangUp", { callIndex: msg.json.data });
-        break;
-      case "RIL:AnswerCall":
-        this.workerMessenger.send("answerCall", { callIndex: msg.json.data });
-        break;
-      case "RIL:RejectCall":
-        this.workerMessenger.send("rejectCall", { callIndex: msg.json.data });
-        break;
-      case "RIL:HoldCall":
-        this.workerMessenger.send("holdCall", { callIndex: msg.json.data });
-        break;
-      case "RIL:ResumeCall":
-        this.workerMessenger.send("resumeCall", { callIndex: msg.json.data });
-        break;
-      case "RIL:ConferenceCall":
-        this.workerMessenger.send("conferenceCall");
-        break;
-      case "RIL:SeparateCall":
-        this.workerMessenger.send("separateCall",
-                                  { callIndex: msg.json.data });
-        break;
-      case "RIL:HoldConference":
-        this.workerMessenger.send("holdConference");
-        break;
-      case "RIL:ResumeConference":
-        this.workerMessenger.send("resumeConference");
-        break;
       case "RIL:GetAvailableNetworks":
         this.workerMessenger.sendWithIPCMessage(msg, "getAvailableNetworks");
         break;
       case "RIL:SelectNetwork":
         this.workerMessenger.sendWithIPCMessage(msg, "selectNetwork");
         break;
       case "RIL:SelectNetworkAuto":
         this.workerMessenger.sendWithIPCMessage(msg, "selectNetworkAuto");
@@ -1053,38 +894,36 @@ RadioInterface.prototype = {
         this.workerMessenger.sendWithIPCMessage(msg, "queryVoicePrivacyMode");
         break;
     }
   },
 
   handleUnsolicitedWorkerMessage: function handleUnsolicitedWorkerMessage(message) {
     switch (message.rilMessageType) {
       case "callRing":
-        this.handleCallRing();
+        gTelephonyProvider.notifyCallRing();
         break;
       case "callStateChange":
-        // This one will handle its own notifications.
-        this.handleCallStateChange(message.call);
+        gTelephonyProvider.notifyCallStateChanged(message.call);
         break;
       case "callDisconnected":
-        // This one will handle its own notifications.
-        this.handleCallDisconnected(message.call);
+        gTelephonyProvider.notifyCallDisconnected(message.call);
         break;
       case "conferenceCallStateChanged":
-        this.handleConferenceCallStateChanged(message.state);
+        gTelephonyProvider.notifyConferenceCallStateChanged(message.state);
         break;
       case "cdmaCallWaiting":
-        gMessageManager.sendTelephonyMessage("RIL:CdmaCallWaiting",
-                                             this.clientId, message.number);
+        gTelephonyProvider.notifyCdmaCallWaiting(message.number);
         break;
       case "callError":
-        this.handleCallError(message);
+        gTelephonyProvider.notifyCallError(message.callIndex, message.errorMsg);
         break;
       case "suppSvcNotification":
-        this.handleSuppSvcNotification(message);
+        gTelephonyProvider.notifySupplementaryService(message.callIndex,
+                                                      message.notification);
         break;
       case "emergencyCbModeChange":
         this.handleEmergencyCbModeChange(message);
         break;
       case "networkinfochanged":
         this.updateNetworkInfo(message);
         break;
       case "networkselectionmodechange":
@@ -1723,175 +1562,16 @@ RadioInterface.prototype = {
       return;
     }
 
     if (DEBUG) this.debug("Data call settings: connect data call.");
     this.setupDataCallByType("default");
   },
 
   /**
-   * Track the active call and update the audio system as its state changes.
-   */
-  _activeCall: null,
-  updateCallAudioState: function updateCallAudioState(options) {
-    if (options.conferenceState === nsITelephonyProvider.CALL_STATE_CONNECTED) {
-      gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_IN_CALL;
-      if (this.speakerEnabled) {
-        gAudioManager.setForceForUse(nsIAudioManager.USE_COMMUNICATION,
-                                     nsIAudioManager.FORCE_SPEAKER);
-      }
-      return;
-    }
-    if (options.conferenceState === nsITelephonyProvider.CALL_STATE_UNKNOWN ||
-        options.conferenceState === nsITelephonyProvider.CALL_STATE_HELD) {
-      if (!this._activeCall) {
-        gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_NORMAL;
-      }
-      return;
-    }
-
-    if (!options.call) {
-      return;
-    }
-
-    if (options.call.isConference) {
-      if (this._activeCall && this._activeCall.callIndex == options.call.callIndex) {
-        this._activeCall = null;
-      }
-      return;
-    }
-
-    let call = options.call;
-    switch (call.state) {
-      case nsITelephonyProvider.CALL_STATE_DIALING: // Fall through...
-      case nsITelephonyProvider.CALL_STATE_ALERTING:
-      case nsITelephonyProvider.CALL_STATE_CONNECTED:
-        call.isActive = true;
-        this._activeCall = call;
-        gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_IN_CALL;
-        if (this.speakerEnabled) {
-          gAudioManager.setForceForUse(nsIAudioManager.USE_COMMUNICATION,
-                                       nsIAudioManager.FORCE_SPEAKER);
-        }
-        if (DEBUG) {
-          this.debug("Active call, put audio system into PHONE_STATE_IN_CALL: "
-                     + gAudioManager.phoneState);
-        }
-        break;
-      case nsITelephonyProvider.CALL_STATE_INCOMING:
-        call.isActive = false;
-        if (!this._activeCall) {
-          // We can change the phone state into RINGTONE only when there's
-          // no active call.
-          gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_RINGTONE;
-          if (DEBUG) {
-            this.debug("Incoming call, put audio system into " +
-                       "PHONE_STATE_RINGTONE: " + gAudioManager.phoneState);
-          }
-        }
-        break;
-      case nsITelephonyProvider.CALL_STATE_HELD: // Fall through...
-      case nsITelephonyProvider.CALL_STATE_DISCONNECTED:
-        call.isActive = false;
-        if (this._activeCall &&
-            this._activeCall.callIndex == call.callIndex) {
-          // Previously active call is not active now.
-          this._activeCall = null;
-        }
-
-        if (!this._activeCall) {
-          // No active call. Disable the audio.
-          gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_NORMAL;
-          if (DEBUG) {
-            this.debug("No active call, put audio system into " +
-                       "PHONE_STATE_NORMAL: " + gAudioManager.phoneState);
-          }
-        }
-        break;
-    }
-  },
-
-  _callRingWakeLock: null,
-  _callRingWakeLockTimer: null,
-  _cancelCallRingWakeLockTimer: function _cancelCallRingWakeLockTimer() {
-    if (this._callRingWakeLockTimer) {
-      this._callRingWakeLockTimer.cancel();
-    }
-    if (this._callRingWakeLock) {
-      this._callRingWakeLock.unlock();
-      this._callRingWakeLock = null;
-    }
-  },
-
-  /**
-   * Handle an incoming call.
-   *
-   * Not much is known about this call at this point, but it's enough
-   * to start bringing up the Phone app already.
-   */
-  handleCallRing: function handleCallRing() {
-    if (!this._callRingWakeLock) {
-      this._callRingWakeLock = gPowerManagerService.newWakeLock("cpu");
-    }
-    if (!this._callRingWakeLockTimer) {
-      this._callRingWakeLockTimer =
-        Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
-    }
-    this._callRingWakeLockTimer
-        .initWithCallback(this._cancelCallRingWakeLockTimer.bind(this),
-                          CALL_WAKELOCK_TIMEOUT, Ci.nsITimer.TYPE_ONE_SHOT);
-
-    gSystemMessenger.broadcastMessage("telephony-new-call", {});
-  },
-
-  /**
-   * Handle call state changes by updating our current state and the audio
-   * system.
-   */
-  handleCallStateChange: function handleCallStateChange(call) {
-    if (DEBUG) this.debug("handleCallStateChange: " + JSON.stringify(call));
-    call.state = convertRILCallState(call.state);
-
-    if (call.state == nsITelephonyProvider.CALL_STATE_DIALING) {
-      gSystemMessenger.broadcastMessage("telephony-new-call", {});
-    }
-    this.updateCallAudioState({call: call});
-    gMessageManager.sendTelephonyMessage("RIL:CallStateChanged",
-                                         this.clientId, call);
-  },
-
-  /**
-   * Handle call disconnects by updating our current state and the audio system.
-   */
-  handleCallDisconnected: function handleCallDisconnected(call) {
-    if (DEBUG) this.debug("handleCallDisconnected: " + JSON.stringify(call));
-    call.state = nsITelephonyProvider.CALL_STATE_DISCONNECTED;
-    let duration = ("started" in call && typeof call.started == "number") ?
-      new Date().getTime() - call.started : 0;
-    let data = {
-      number: call.number,
-      duration: duration,
-      direction: call.isOutgoing ? "outgoing" : "incoming"
-    };
-    gSystemMessenger.broadcastMessage("telephony-call-ended", data);
-    this.updateCallAudioState({call: call});
-    gMessageManager.sendTelephonyMessage("RIL:CallStateChanged",
-                                         this.clientId, call);
-  },
-
-  handleConferenceCallStateChanged: function handleConferenceCallStateChanged(state) {
-    debug("handleConferenceCallStateChanged: " + state);
-    state = state != null ? convertRILCallState(state) :
-                            nsITelephonyProvider.CALL_STATE_UNKNOWN;
-    this.updateCallAudioState({conferenceState: state});
-    gMessageManager.sendTelephonyMessage("RIL:ConferenceCallStateChanged",
-                                         this.clientId, state);
-  },
-
-  /**
    * Update network selection mode
    */
   updateNetworkSelectionMode: function updateNetworkSelectionMode(message) {
     if (DEBUG) this.debug("updateNetworkSelectionMode: " + JSON.stringify(message));
     this.rilContext.networkSelectionMode = message.mode;
     gMessageManager.sendMobileConnectionMessage("RIL:NetworkSelectionModeChanged",
                                                 this.clientId, message);
   },
@@ -1901,33 +1581,16 @@ RadioInterface.prototype = {
    */
   handleEmergencyCbModeChange: function handleEmergencyCbModeChange(message) {
     if (DEBUG) this.debug("handleEmergencyCbModeChange: " + JSON.stringify(message));
     gMessageManager.sendMobileConnectionMessage("RIL:EmergencyCbModeChanged",
                                                 this.clientId, message);
   },
 
   /**
-   * Handle call error.
-   */
-  handleCallError: function handleCallError(message) {
-    gMessageManager.sendTelephonyMessage("RIL:CallError",
-                                         this.clientId, message);
-  },
-
-  /**
-   * Handle supplementary service notification.
-   */
-  handleSuppSvcNotification: function handleSuppSvcNotification(message) {
-    message.notification = convertRILSuppSvcNotification(message.notification);
-    gMessageManager.sendTelephonyMessage("RIL:SuppSvcNotification",
-                                         this.clientId, message);
-  },
-
-  /**
    * Handle WDP port push PDU. Constructor WDP bearer information and deliver
    * to WapPushManager.
    *
    * @param message
    *        A SMS message.
    */
   handleSmsWdpPortPush: function handleSmsWdpPortPush(message) {
     if (message.encoding != RIL.PDU_DCS_MSG_CODING_8BITS_ALPHABET) {
@@ -2304,18 +1967,16 @@ RadioInterface.prototype = {
           try {
             value = Services.prefs.getBoolPref(kCellBroadcastDisabled);
           } catch(e) {}
           this.workerMessenger.send("setCellBroadcastDisabled",
                                     { disabled: value });
         }
         break;
       case "xpcom-shutdown":
-        // Cancel the timer for the call-ring wake lock.
-        this._cancelCallRingWakeLockTimer();
         // Shutdown all RIL network interfaces
         for each (let apnSetting in this.apnSettings.byAPN) {
           if (apnSetting.iface) {
             apnSetting.iface.shutdown();
           }
         }
         Services.obs.removeObserver(this, "xpcom-shutdown");
         Services.obs.removeObserver(this, kMozSettingsChangedObserverTopic);
@@ -2455,66 +2116,16 @@ RadioInterface.prototype = {
     if (DEBUG) this.debug("Setting radio power to " + value);
     this.workerMessenger.send("setRadioPower", { on: value });
   },
 
   rilContext: null,
 
   // Handle phone functions of nsIRILContentHelper
 
-  enumerateCalls: function enumerateCalls(target, message) {
-    if (DEBUG) this.debug("Requesting enumeration of calls for callback");
-    this.workerMessenger.send("enumerateCalls", message, (function(response) {
-      for (let call of response.calls) {
-        call.state = convertRILCallState(call.state);
-        call.isActive = this._activeCall ?
-          call.callIndex == this._activeCall.callIndex : false;
-      }
-      target.sendAsyncMessage("RIL:EnumerateCalls", response);
-      return false;
-    }).bind(this));
-  },
-
-  _validateNumber: function _validateNumber(number) {
-    // note: isPlainPhoneNumber also accepts USSD and SS numbers
-    if (PhoneNumberUtils.isPlainPhoneNumber(number)) {
-      return true;
-    }
-
-    this.handleCallError({
-      callIndex: -1,
-      errorMsg: RIL.RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[RIL.CALL_FAIL_UNOBTAINABLE_NUMBER]
-    });
-    if (DEBUG) {
-      this.debug("Number '" + number + "' doesn't seem to be a viable number." +
-                 " Drop.");
-    }
-
-    return false;
-  },
-
-  dial: function dial(number) {
-    if (DEBUG) this.debug("Dialing " + number);
-    number = PhoneNumberUtils.normalize(number);
-    if (this._validateNumber(number)) {
-      this.workerMessenger.send("dial", { number: number,
-                                          isDialEmergency: false });
-    }
-  },
-
-  dialEmergency: function dialEmergency(number) {
-    if (DEBUG) this.debug("Dialing emergency " + number);
-    // we don't try to be too clever here, as the phone is probably in the
-    // locked state. Let's just check if it's a number without normalizing
-    if (this._validateNumber(number)) {
-      this.workerMessenger.send("dial", { number: number,
-                                          isDialEmergency: true });
-    }
-  },
-
   _sendCfStateChanged: function _sendCfStateChanged(message) {
     gMessageManager.sendMobileConnectionMessage("RIL:CfStateChanged",
                                                 this.clientId, message);
   },
 
   _updateCallingLineIdRestrictionPref:
     function _updateCallingLineIdRestrictionPref(mode) {
     try {
@@ -2559,47 +2170,16 @@ RadioInterface.prototype = {
       if (response.success) {
         this._updateCallingLineIdRestrictionPref(response.clirMode);
       }
       target.sendAsyncMessage("RIL:SetCallingLineIdRestriction", response);
       return false;
     }).bind(this));
   },
 
-  get microphoneMuted() {
-    return gAudioManager.microphoneMuted;
-  },
-  set microphoneMuted(value) {
-    if (value == this.microphoneMuted) {
-      return;
-    }
-    gAudioManager.microphoneMuted = value;
-
-    if (!this._activeCall) {
-      gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_NORMAL;
-    }
-  },
-
-  get speakerEnabled() {
-    return (gAudioManager.getForceForUse(nsIAudioManager.USE_COMMUNICATION) ==
-            nsIAudioManager.FORCE_SPEAKER);
-  },
-  set speakerEnabled(value) {
-    if (value == this.speakerEnabled) {
-      return;
-    }
-    let force = value ? nsIAudioManager.FORCE_SPEAKER :
-                        nsIAudioManager.FORCE_NONE;
-    gAudioManager.setForceForUse(nsIAudioManager.USE_COMMUNICATION, force);
-
-    if (!this._activeCall) {
-      gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_NORMAL;
-    }
-  },
-
   /**
    * List of tuples of national language identifier pairs.
    *
    * TODO: Support static/runtime settings, see bug 733331.
    */
   enabledGsmTableTuples: [
     [RIL.PDU_NL_IDENTIFIER_DEFAULT, RIL.PDU_NL_IDENTIFIER_DEFAULT],
   ],
@@ -3357,16 +2937,23 @@ RadioInterface.prototype = {
                                                  chappap: chappap,
                                                  pdptype: pdptype });
   },
 
   deactivateDataCall: function deactivateDataCall(cid, reason) {
     this.workerMessenger.send("deactivateDataCall", { cid: cid,
                                                       reason: reason });
   },
+
+  sendWorkerMessage: function sendWorkerMessage(rilMessageType, message,
+                                                callback) {
+    this.workerMessenger.send(rilMessageType, message, function (response) {
+      return callback.handleResponse(response);
+    });
+  }
 };
 
 function RILNetworkInterface(radioInterface, apnSetting) {
   this.radioInterface = radioInterface;
   this.apnSetting = apnSetting;
 
   this.connectedTypes = [];
 }
--- a/dom/system/gonk/nsIRadioInterfaceLayer.idl
+++ b/dom/system/gonk/nsIRadioInterfaceLayer.idl
@@ -73,17 +73,23 @@ interface nsIRilContext : nsISupports
 
   readonly attribute nsIDOMMozIccInfo iccInfo;
 
   readonly attribute nsIDOMMozMobileConnectionInfo voice;
 
   readonly attribute nsIDOMMozMobileConnectionInfo data;
 };
 
-[scriptable, uuid(a50d65aa-00da-11e3-b954-7bfb233d98fc)]
+[scriptable, function, uuid(3bc96351-53b0-47a1-a888-c74c64b60f25)]
+interface nsIRilSendWorkerMessageCallback : nsISupports
+{
+  boolean handleResponse(in jsval response);
+};
+
+[scriptable, uuid(61a8ca67-6113-4cd0-b443-e045f09863ed)]
 interface nsIRadioInterface : nsISupports
 {
   readonly attribute nsIRilContext rilContext;
 
   /**
    * PDP APIs
    */
   void setupDataCallByType(in DOMString apntype);
@@ -100,16 +106,20 @@ interface nsIRadioInterface : nsISupport
    */
   void getSegmentInfoForText(in DOMString text,
                              in nsIMobileMessageCallback request);
 
   void sendSMS(in DOMString number,
                in DOMString message,
                in boolean silent,
                in nsIMobileMessageCallback request);
+
+  void sendWorkerMessage(in DOMString type,
+              [optional] in jsval message,
+              [optional] in nsIRilSendWorkerMessageCallback callback);
 };
 
 [scriptable, uuid(44b03951-1444-4c03-bd37-0bcb3a01b56f)]
 interface nsIRadioInterfaceLayer : nsISupports
 {
   readonly attribute unsigned long numRadioInterfaces;
 
   nsIRadioInterface getRadioInterface(in long clientId);
--- a/dom/telephony/TelephonyFactory.cpp
+++ b/dom/telephony/TelephonyFactory.cpp
@@ -1,23 +1,30 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/telephony/TelephonyFactory.h"
+#ifdef MOZ_WIDGET_GONK
+#include "nsIGonkTelephonyProvider.h"
+#endif
 #include "nsServiceManagerUtils.h"
 #include "nsXULAppAPI.h"
 #include "ipc/TelephonyIPCProvider.h"
 
 USING_TELEPHONY_NAMESPACE
 
 /* static */ already_AddRefed<nsITelephonyProvider>
 TelephonyFactory::CreateTelephonyProvider()
 {
   nsCOMPtr<nsITelephonyProvider> provider;
 
   if (XRE_GetProcessType() == GeckoProcessType_Content) {
     provider = new TelephonyIPCProvider();
+#ifdef MOZ_WIDGET_GONK
+  } else {
+    provider = do_CreateInstance(GONK_TELEPHONY_PROVIDER_CONTRACTID);
+#endif
   }
 
   return provider.forget();
 }
new file mode 100644
--- /dev/null
+++ b/dom/telephony/gonk/TelephonyProvider.js
@@ -0,0 +1,525 @@
+/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+var RIL = {};
+Cu.import("resource://gre/modules/ril_consts.js", RIL);
+
+const GONK_TELEPHONYPROVIDER_CONTRACTID =
+  "@mozilla.org/telephony/gonktelephonyprovider;1";
+const GONK_TELEPHONYPROVIDER_CID =
+  Components.ID("{67d26434-d063-4d28-9f48-5b3189788155}");
+
+const kPrefenceChangedObserverTopic = "nsPref:changed";
+const kXpcomShutdownObserverTopic   = "xpcom-shutdown";
+
+const nsIAudioManager = Ci.nsIAudioManager;
+const nsITelephonyProvider = Ci.nsITelephonyProvider;
+
+const CALL_WAKELOCK_TIMEOUT = 5000;
+
+let DEBUG;
+function debug(s) {
+  dump("TelephonyProvider: " + s + "\n");
+}
+
+XPCOMUtils.defineLazyGetter(this, "gAudioManager", function getAudioManager() {
+  try {
+    return Cc["@mozilla.org/telephony/audiomanager;1"]
+             .getService(nsIAudioManager);
+  } catch (ex) {
+    //TODO on the phone this should not fall back as silently.
+
+    // Fake nsIAudioManager implementation so that we can run the telephony
+    // code in a non-Gonk build.
+    if (DEBUG) debug("Using fake audio manager.");
+    return {
+      microphoneMuted: false,
+      masterVolume: 1.0,
+      masterMuted: false,
+      phoneState: nsIAudioManager.PHONE_STATE_CURRENT,
+      _forceForUse: {},
+
+      setForceForUse: function setForceForUse(usage, force) {
+        this._forceForUse[usage] = force;
+      },
+
+      getForceForUse: function setForceForUse(usage) {
+        return this._forceForUse[usage] || nsIAudioManager.FORCE_NONE;
+      }
+    };
+  }
+});
+
+XPCOMUtils.defineLazyServiceGetter(this, "gPowerManagerService",
+                                   "@mozilla.org/power/powermanagerservice;1",
+                                   "nsIPowerManagerService");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gSystemMessenger",
+                                   "@mozilla.org/system-message-internal;1",
+                                   "nsISystemMessagesInternal");
+
+XPCOMUtils.defineLazyGetter(this, "gRadioInterface", function () {
+  let ril = Cc["@mozilla.org/ril;1"].getService(Ci["nsIRadioInterfaceLayer"]);
+  // TODO: Bug 854326 - B2G Multi-SIM: support multiple SIM cards for SMS/MMS
+  return ril.getRadioInterface(0);
+});
+
+XPCOMUtils.defineLazyGetter(this, "gPhoneNumberUtils", function () {
+  let ns = {};
+  Cu.import("resource://gre/modules/PhoneNumberUtils.jsm", ns);
+  return ns.PhoneNumberUtils;
+});
+
+function TelephonyProvider() {
+  this._listeners = [];
+
+  this._updateDebugFlag();
+
+  Services.obs.addObserver(this, kPrefenceChangedObserverTopic, false);
+  Services.obs.addObserver(this, kXpcomShutdownObserverTopic, false);
+}
+TelephonyProvider.prototype = {
+  classID: GONK_TELEPHONYPROVIDER_CID,
+  classInfo: XPCOMUtils.generateCI({classID: GONK_TELEPHONYPROVIDER_CID,
+                                    contractID: GONK_TELEPHONYPROVIDER_CONTRACTID,
+                                    classDescription: "TelephonyProvider",
+                                    interfaces: [Ci.nsITelephonyProvider,
+                                                 Ci.nsIGonkTelephonyProvider],
+                                    flags: Ci.nsIClassInfo.SINGLETON}),
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsITelephonyProvider,
+                                         Ci.nsIGonkTelephonyProvider,
+                                         Ci.nsIObserver]),
+
+  _callRingWakeLock: null,
+  _callRingWakeLockTimer: null,
+  _cancelCallRingWakeLockTimer: function _cancelCallRingWakeLockTimer() {
+    if (this._callRingWakeLockTimer) {
+      this._callRingWakeLockTimer.cancel();
+    }
+    if (this._callRingWakeLock) {
+      this._callRingWakeLock.unlock();
+      this._callRingWakeLock = null;
+    }
+  },
+
+  // An array of nsITelephonyListener instances.
+  _listeners: null,
+  _notifyAllListeners: function _notifyAllListeners(aMethodName, aArgs) {
+    let listeners = this._listeners.slice();
+    for (let listener of listeners) {
+      if (this._listeners.indexOf(listener) == -1) {
+        // Listener has been unregistered in previous run.
+        continue;
+      }
+
+      let handler = listener[aMethodName];
+      try {
+        handler.apply(listener, aArgs);
+      } catch (e) {
+        debug("listener for " + aMethodName + " threw an exception: " + e);
+      }
+    }
+  },
+
+  /**
+   * Track the active call and update the audio system as its state changes.
+   */
+  _activeCall: null,
+  _updateCallAudioState: function _updateCallAudioState(aCall,
+                                                        aConferenceState) {
+    if (aConferenceState === nsITelephonyProvider.CALL_STATE_CONNECTED) {
+      gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_IN_CALL;
+      if (this.speakerEnabled) {
+        gAudioManager.setForceForUse(nsIAudioManager.USE_COMMUNICATION,
+                                     nsIAudioManager.FORCE_SPEAKER);
+      }
+      return;
+    }
+    if (aConferenceState === nsITelephonyProvider.CALL_STATE_UNKNOWN ||
+        aConferenceState === nsITelephonyProvider.CALL_STATE_HELD) {
+      if (!this._activeCall) {
+        gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_NORMAL;
+      }
+      return;
+    }
+
+    if (!aCall) {
+      return;
+    }
+
+    if (aCall.isConference) {
+      if (this._activeCall && this._activeCall.callIndex == aCall.callIndex) {
+        this._activeCall = null;
+      }
+      return;
+    }
+
+    switch (aCall.state) {
+      case nsITelephonyProvider.CALL_STATE_DIALING: // Fall through...
+      case nsITelephonyProvider.CALL_STATE_ALERTING:
+      case nsITelephonyProvider.CALL_STATE_CONNECTED:
+        aCall.isActive = true;
+        this._activeCall = aCall;
+        gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_IN_CALL;
+        if (this.speakerEnabled) {
+          gAudioManager.setForceForUse(nsIAudioManager.USE_COMMUNICATION,
+                                       nsIAudioManager.FORCE_SPEAKER);
+        }
+        if (DEBUG) {
+          debug("Active call, put audio system into PHONE_STATE_IN_CALL: " +
+                gAudioManager.phoneState);
+        }
+        break;
+
+      case nsITelephonyProvider.CALL_STATE_INCOMING:
+        aCall.isActive = false;
+        if (!this._activeCall) {
+          // We can change the phone state into RINGTONE only when there's
+          // no active call.
+          gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_RINGTONE;
+          if (DEBUG) {
+            debug("Incoming call, put audio system into PHONE_STATE_RINGTONE: " +
+                  gAudioManager.phoneState);
+          }
+        }
+        break;
+
+      case nsITelephonyProvider.CALL_STATE_HELD: // Fall through...
+      case nsITelephonyProvider.CALL_STATE_DISCONNECTED:
+        aCall.isActive = false;
+        if (this._activeCall &&
+            this._activeCall.callIndex == aCall.callIndex) {
+          // Previously active call is not active now.
+          this._activeCall = null;
+        }
+
+        if (!this._activeCall) {
+          // No active call. Disable the audio.
+          gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_NORMAL;
+          if (DEBUG) {
+            debug("No active call, put audio system into PHONE_STATE_NORMAL: " +
+                  gAudioManager.phoneState);
+          }
+        }
+        break;
+    }
+  },
+
+  _convertRILCallState: function _convertRILCallState(aState) {
+    switch (aState) {
+      case RIL.CALL_STATE_ACTIVE:
+        return nsITelephonyProvider.CALL_STATE_CONNECTED;
+      case RIL.CALL_STATE_HOLDING:
+        return nsITelephonyProvider.CALL_STATE_HELD;
+      case RIL.CALL_STATE_DIALING:
+        return nsITelephonyProvider.CALL_STATE_DIALING;
+      case RIL.CALL_STATE_ALERTING:
+        return nsITelephonyProvider.CALL_STATE_ALERTING;
+      case RIL.CALL_STATE_INCOMING:
+      case RIL.CALL_STATE_WAITING:
+        return nsITelephonyProvider.CALL_STATE_INCOMING;
+      default:
+        throw new Error("Unknown rilCallState: " + aState);
+    }
+  },
+
+  _convertRILSuppSvcNotification: function _convertRILSuppSvcNotification(aNotification) {
+    switch (aNotification) {
+      case RIL.GECKO_SUPP_SVC_NOTIFICATION_REMOTE_HELD:
+        return nsITelephonyProvider.NOTIFICATION_REMOTE_HELD;
+      case RIL.GECKO_SUPP_SVC_NOTIFICATION_REMOTE_RESUMED:
+        return nsITelephonyProvider.NOTIFICATION_REMOTE_RESUMED;
+      default:
+        throw new Error("Unknown rilSuppSvcNotification: " + aNotification);
+    }
+  },
+
+  _validateNumber: function _validateNumber(aNumber) {
+    // note: isPlainPhoneNumber also accepts USSD and SS numbers
+    if (gPhoneNumberUtils.isPlainPhoneNumber(aNumber)) {
+      return true;
+    }
+
+    let errorMsg = RIL.RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[RIL.CALL_FAIL_UNOBTAINABLE_NUMBER];
+    let currentThread = Services.tm.currentThread;
+    currentThread.dispatch(this.notifyCallError.bind(this, -1, errorMsg),
+                           Ci.nsIThread.DISPATCH_NORMAL);
+    if (DEBUG) {
+      debug("Number '" + aNumber + "' doesn't seem to be a viable number. Drop.");
+    }
+
+    return false;
+  },
+
+  _updateDebugFlag: function _updateDebugFlag() {
+    try {
+      DEBUG = RIL.DEBUG_RIL ||
+              Services.prefs.getBoolPref("ril.debugging.enabled");
+    } catch (e) {}
+  },
+
+  /**
+   * nsITelephonyProvider interface.
+   */
+
+  registerListener: function(aListener) {
+    if (this._listeners.indexOf(aListener) >= 0) {
+      throw Cr.NS_ERROR_UNEXPECTED;
+    }
+
+    this._listeners.push(aListener);
+  },
+
+  unregisterListener: function(aListener) {
+    let index = this._listeners.indexOf(aListener);
+    if (index < 0) {
+      throw Cr.NS_ERROR_UNEXPECTED;
+    }
+
+    this._listeners.splice(index, 1);
+  },
+
+  enumerateCalls: function(aListener) {
+    if (DEBUG) debug("Requesting enumeration of calls for callback");
+    gRadioInterface.sendWorkerMessage("enumerateCalls", null,
+                                      (function(response) {
+      for (let call of response.calls) {
+        call.state = this._convertRILCallState(call.state);
+        call.isActive = this._activeCall ?
+          (call.callIndex == this._activeCall.callIndex) : false;
+
+        aListener.enumerateCallState(call.callIndex, call.state, call.number,
+                                     call.isActive, call.isOutgoing,
+                                     call.isEmergency, call.isConference);
+      }
+      aListener.enumerateCallStateComplete();
+
+      return false;
+    }).bind(this));
+  },
+
+  dial: function(aNumber, aIsEmergency) {
+    if (DEBUG) debug("Dialing " + (aIsEmergency ? "emergency " : "") + aNumber);
+    // we don't try to be too clever here, as the phone is probably in the
+    // locked state. Let's just check if it's a number without normalizing
+    if (!aIsEmergency) {
+      aNumber = gPhoneNumberUtils.normalize(aNumber);
+    }
+    if (this._validateNumber(aNumber)) {
+      gRadioInterface.sendWorkerMessage("dial", { number: aNumber,
+                                                  isDialEmergency: aIsEmergency });
+    }
+  },
+
+  hangUp: function(aCallIndex) {
+    gRadioInterface.sendWorkerMessage("hangUp", { callIndex: aCallIndex });
+  },
+
+  startTone: function(aDtmfChar) {
+    gRadioInterface.sendWorkerMessage("startTone", { dtmfChar: aDtmfChar });
+  },
+
+  stopTone: function() {
+    gRadioInterface.sendWorkerMessage("stopTone");
+  },
+
+  answerCall: function(aCallIndex) {
+    gRadioInterface.sendWorkerMessage("answerCall", { callIndex: aCallIndex });
+  },
+
+  rejectCall: function(aCallIndex) {
+    gRadioInterface.sendWorkerMessage("rejectCall", { callIndex: aCallIndex });
+  },
+
+  holdCall: function(aCallIndex) {
+    gRadioInterface.sendWorkerMessage("holdCall", { callIndex: aCallIndex });
+  },
+
+  resumeCall: function(aCallIndex) {
+    gRadioInterface.sendWorkerMessage("resumeCall", { callIndex: aCallIndex });
+  },
+
+  conferenceCall: function conferenceCall() {
+    gRadioInterface.sendWorkerMessage("conferenceCall");
+  },
+
+  separateCall: function separateCall(aCallIndex) {
+    gRadioInterface.sendWorkerMessage("separateCall", { callIndex: aCallIndex });
+  },
+
+  holdConference: function holdConference() {
+    gRadioInterface.sendWorkerMessage("holdConference");
+  },
+
+  resumeConference: function resumeConference() {
+    gRadioInterface.sendWorkerMessage("resumeConference");
+  },
+
+  get microphoneMuted() {
+    return gAudioManager.microphoneMuted;
+  },
+
+  set microphoneMuted(aMuted) {
+    if (aMuted == this.microphoneMuted) {
+      return;
+    }
+    gAudioManager.microphoneMuted = aMuted;
+
+    if (!this._activeCall) {
+      gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_NORMAL;
+    }
+  },
+
+  get speakerEnabled() {
+    let force = gAudioManager.getForceForUse(nsIAudioManager.USE_COMMUNICATION);
+    return (force == nsIAudioManager.FORCE_SPEAKER);
+  },
+
+  set speakerEnabled(aEnabled) {
+    if (aEnabled == this.speakerEnabled) {
+      return;
+    }
+    let force = aEnabled ? nsIAudioManager.FORCE_SPEAKER :
+                           nsIAudioManager.FORCE_NONE;
+    gAudioManager.setForceForUse(nsIAudioManager.USE_COMMUNICATION, force);
+
+    if (!this._activeCall) {
+      gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_NORMAL;
+    }
+  },
+
+  /**
+   * nsIGonkTelephonyProvider interface.
+   */
+
+  /**
+   * Handle call disconnects by updating our current state and the audio system.
+   */
+  notifyCallDisconnected: function notifyCallDisconnected(aCall) {
+    if (DEBUG) debug("handleCallDisconnected: " + JSON.stringify(aCall));
+
+    aCall.state = nsITelephonyProvider.CALL_STATE_DISCONNECTED;
+    let duration = ("started" in aCall && typeof aCall.started == "number") ?
+      new Date().getTime() - aCall.started : 0;
+    let data = {
+      number: aCall.number,
+      duration: duration,
+      direction: aCall.isOutgoing ? "outgoing" : "incoming"
+    };
+    gSystemMessenger.broadcastMessage("telephony-call-ended", data);
+
+    this._updateCallAudioState(aCall, null);
+
+    this._notifyAllListeners("callStateChanged", [aCall.callIndex,
+                                                  aCall.state,
+                                                  aCall.number,
+                                                  aCall.isActive,
+                                                  aCall.isOutgoing,
+                                                  aCall.isEmergency,
+                                                  aCall.isConference]);
+  },
+
+  /**
+   * Handle call error.
+   */
+  notifyCallError: function notifyCallError(aCallIndex, aErrorMsg) {
+    this._notifyAllListeners("notifyError", [aCallIndex, aErrorMsg]);
+  },
+
+  /**
+   * Handle an incoming call.
+   *
+   * Not much is known about this call at this point, but it's enough
+   * to start bringing up the Phone app already.
+   */
+  notifyCallRing: function notifyCallRing() {
+    if (!this._callRingWakeLock) {
+      this._callRingWakeLock = gPowerManagerService.newWakeLock("cpu");
+    }
+    if (!this._callRingWakeLockTimer) {
+      this._callRingWakeLockTimer =
+        Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+    }
+    this._callRingWakeLockTimer
+        .initWithCallback(this._cancelCallRingWakeLockTimer.bind(this),
+                          CALL_WAKELOCK_TIMEOUT, Ci.nsITimer.TYPE_ONE_SHOT);
+
+    gSystemMessenger.broadcastMessage("telephony-new-call", {});
+  },
+
+  /**
+   * Handle call state changes by updating our current state and the audio
+   * system.
+   */
+  notifyCallStateChanged: function notifyCallStateChanged(aCall) {
+    if (DEBUG) debug("handleCallStateChange: " + JSON.stringify(aCall));
+
+    aCall.state = this._convertRILCallState(aCall.state);
+    if (aCall.state == nsITelephonyProvider.CALL_STATE_DIALING) {
+      gSystemMessenger.broadcastMessage("telephony-new-call", {});
+    }
+
+    this._updateCallAudioState(aCall, null);
+
+    this._notifyAllListeners("callStateChanged", [aCall.callIndex,
+                                                  aCall.state,
+                                                  aCall.number,
+                                                  aCall.isActive,
+                                                  aCall.isOutgoing,
+                                                  aCall.isEmergency,
+                                                  aCall.isConference]);
+  },
+
+  notifyCdmaCallWaiting: function notifyCdmaCallWaiting(aNumber) {
+    this._notifyAllListeners("notifyCdmaCallWaiting", [aNumber]);
+  },
+
+  notifySupplementaryService: function notifySupplementaryService(aCallIndex,
+                                                                  aNotification) {
+    let notification = this._convertRILSuppSvcNotification(aNotification);
+    this._notifyAllListeners("supplementaryServiceNotification",
+                             [aCallIndex, notification]);
+  },
+
+  notifyConferenceCallStateChanged: function notifyConferenceCallStateChanged(aState) {
+    if (DEBUG) debug("handleConferenceCallStateChanged: " + aState);
+    aState = aState != null ? convertRILCallState(aState) :
+                              nsITelephonyProvider.CALL_STATE_UNKNOWN;
+    this._updateCallAudioState(null, aState);
+
+    this._notifyAllListeners("conferenceCallStateChanged", [aState]);
+  },
+
+  /**
+   * nsIObserver interface.
+   */
+
+  observe: function observe(aSubject, aTopic, aData) {
+    switch (aTopic) {
+      case kPrefenceChangedObserverTopic:
+        if (aData === "ril.debugging.enabled") {
+          this._updateDebugFlag();
+        }
+        break;
+
+      case kXpcomShutdownObserverTopic:
+        // Cancel the timer for the call-ring wake lock.
+        this._cancelCallRingWakeLockTimer();
+
+        Services.obs.removeObserver(this, kPrefenceChangedObserverTopic);
+        Services.obs.removeObserver(this, kXpcomShutdownObserverTopic);
+        break;
+    }
+  }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TelephonyProvider]);
new file mode 100644
--- /dev/null
+++ b/dom/telephony/gonk/TelephonyProvider.manifest
@@ -0,0 +1,2 @@
+component {67d26434-d063-4d28-9f48-5b3189788155} TelephonyProvider.js
+contract @mozilla.org/telephony/gonktelephonyprovider;1 {67d26434-d063-4d28-9f48-5b3189788155}
--- a/dom/telephony/moz.build
+++ b/dom/telephony/moz.build
@@ -37,13 +37,22 @@ CPP_SOURCES += [
 ]
 
 IPDL_SOURCES += [
     'ipc/PTelephony.ipdl',
     'ipc/PTelephonyRequest.ipdl',
     'ipc/TelephonyTypes.ipdlh'
 ]
 
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
+    XPIDL_SOURCES += [
+        'nsIGonkTelephonyProvider.idl',
+    ]
+    EXTRA_COMPONENTS += [
+        'gonk/TelephonyProvider.js',
+        'gonk/TelephonyProvider.manifest',
+    ]
+
 FAIL_ON_WARNINGS = True
 
 LIBXUL_LIBRARY = True
 
 LIBRARY_NAME = 'domtelephony_s'
new file mode 100644
--- /dev/null
+++ b/dom/telephony/nsIGonkTelephonyProvider.idl
@@ -0,0 +1,31 @@
+/* -*- Mode: idl; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsITelephonyProvider.idl"
+
+%{C++
+#define GONK_TELEPHONY_PROVIDER_CONTRACTID \
+        "@mozilla.org/telephony/gonktelephonyprovider;1"
+%}
+
+[scriptable, uuid(0d106c7e-ba47-48ee-ba48-c92002d401b6)]
+interface nsIGonkTelephonyProvider : nsITelephonyProvider
+{
+  void notifyCallDisconnected(in jsval call);
+
+  void notifyCallError(in long callIndex,
+                       in AString error);
+
+  void notifyCallRing();
+
+  void notifyCallStateChanged(in jsval call);
+
+  void notifyCdmaCallWaiting(in AString number);
+
+  void notifySupplementaryService(in long callIndex,
+                                  in AString notification);
+
+  void notifyConferenceCallStateChanged(in unsigned short state);
+};