Bug 1063304 - 3.d/3: Gonk implementation. r=echen
☠☠ backed out by 8a7e6dc853d3 ☠ ☠
authorVicamo Yang <vyang@mozilla.com>
Tue, 16 Sep 2014 21:20:18 +0800
changeset 205537 251ec0478b7256445b8acfab26d32cc816bf15ae
parent 205536 9ff2889f236a420766dc4f60a3c7cd9ef2f6095b
child 205538 69870df1c72f1a184425d1327a26d32b06068bf2
push id27497
push userkwierso@gmail.com
push dateTue, 16 Sep 2014 23:22:24 +0000
treeherdermozilla-central@49ef7b18963d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersechen
bugs1063304
milestone35.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1063304 - 3.d/3: Gonk implementation. r=echen
dom/mobileconnection/gonk/MobileConnectionService.js
--- a/dom/mobileconnection/gonk/MobileConnectionService.js
+++ b/dom/mobileconnection/gonk/MobileConnectionService.js
@@ -14,16 +14,18 @@ Cu.import("resource://gre/modules/XPCOMU
 var RIL = {};
 Cu.import("resource://gre/modules/ril_consts.js", RIL);
 
 const GONK_MOBILECONNECTIONSERVICE_CONTRACTID =
   "@mozilla.org/mobileconnection/gonkmobileconnectionservice;1";
 
 const GONK_MOBILECONNECTIONSERVICE_CID =
   Components.ID("{05e20430-fe65-4984-8df9-a6a504b24a91}");
+const MOBILECONNECTIONINFO_CID =
+  Components.ID("{8162b3c0-664b-45f6-96cd-f07b4e193b0e}");
 const MOBILENETWORKINFO_CID =
   Components.ID("{a6c8416c-09b4-46d1-bf29-6520d677d085}");
 const MOBILECELLINFO_CID =
   Components.ID("{0635d9ab-997e-4cdf-84e7-c1883752dff3}");
 
 const NS_XPCOM_SHUTDOWN_OBSERVER_ID      = "xpcom-shutdown";
 const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID  = "nsPref:changed";
 const NS_NETWORK_ACTIVE_CHANGED_TOPIC_ID = "network-active-changed";
@@ -78,16 +80,37 @@ MobileCellInfo.prototype = {
   classID:        MOBILECELLINFO_CID,
   classInfo:      XPCOMUtils.generateCI({
     classID:          MOBILECELLINFO_CID,
     classDescription: "MobileCellInfo",
     interfaces:       [Ci.nsIMobileCellInfo]
   })
 };
 
+function MobileConnectionInfo() {}
+MobileConnectionInfo.prototype = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIMobileConnectionInfo]),
+  classID: MOBILECONNECTIONINFO_CID,
+  classInfo: XPCOMUtils.generateCI({
+    classID: MOBILECONNECTIONINFO_CID,
+    classDescription: "MobileConnectionInfo",
+    interfaces: [Ci.nsIMobileConnectionInfo]
+  }),
+
+  state: null,
+  connected: false,
+  emergencyCallsOnly: false,
+  roaming: false,
+  network: null,
+  cell: null,
+  type: null,
+  signalStrength: null,
+  relSignalStrength: null
+};
+
 function CallForwardingOptions(aOptions) {
   this.active = aOptions.active;
   this.action = aOptions.action;
   this.reason = aOptions.reason;
   this.number = aOptions.number;
   this.timeSeconds = aOptions.timeSeconds;
   this.serviceClass = aOptions.serviceClass;
 }
@@ -114,53 +137,37 @@ MMIResult.prototype = {
 function MobileConnectionProvider(aClientId, aRadioInterface) {
   this._clientId = aClientId;
   this._radioInterface = aRadioInterface;
   this._operatorInfo = {};
   // An array of nsIMobileConnectionListener instances.
   this._listeners = [];
 
   this.supportedNetworkTypes = this._getSupportedNetworkTypes();
-  // These objects implement the nsIMobileConnectionInfo interface,
-  // although the actual implementation lives in the content process. So are
-  // the child attributes `network` and `cell`, which implement
-  // nsIMobileNetworkInfo and nsIMobileCellInfo respectively.
-  this.voiceInfo = {connected: false,
-                    emergencyCallsOnly: false,
-                    roaming: false,
-                    network: null,
-                    cell: null,
-                    type: null,
-                    signalStrength: null,
-                    relSignalStrength: null};
-  this.dataInfo = {connected: false,
-                   emergencyCallsOnly: false,
-                   roaming: false,
-                   network: null,
-                   cell: null,
-                   type: null,
-                   signalStrength: null,
-                   relSignalStrength: null};
+  this.voice = new MobileConnectionInfo();
+  this.data = new MobileConnectionInfo();
 }
 MobileConnectionProvider.prototype = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIMobileConnection]),
+
   _clientId: null,
   _radioInterface: null,
   _operatorInfo: null,
   _listeners: null,
 
   /**
    * The networks that are currently trying to be selected (or "automatic").
    * This helps ensure that only one network per client is selected at a time.
    */
   _selectingNetwork: null,
 
-  voiceInfo: null,
-  dataInfo: null,
+  voice: null,
+  data: null,
   iccId: null,
-  networkSelectMode: null,
+  networkSelectionMode: null,
   radioState: null,
   lastKnownNetwork: null,
   lastKnownHomeNetwork: null,
   supportedNetworkTypes: null,
 
   /**
    * A utility function to dump debug message.
    */
@@ -207,53 +214,53 @@ MobileConnectionProvider.prototype = {
     return supportedNetworkTypes;
   },
 
   /**
    * Helper for guarding us against invalid reason values for call forwarding.
    */
   _isValidCallForwardingReason: function(aReason) {
     switch (aReason) {
-      case Ci.nsIMobileConnectionService.CALL_FORWARD_REASON_UNCONDITIONAL:
-      case Ci.nsIMobileConnectionService.CALL_FORWARD_REASON_MOBILE_BUSY:
-      case Ci.nsIMobileConnectionService.CALL_FORWARD_REASON_NO_REPLY:
-      case Ci.nsIMobileConnectionService.CALL_FORWARD_REASON_NOT_REACHABLE:
-      case Ci.nsIMobileConnectionService.CALL_FORWARD_REASON_ALL_CALL_FORWARDING:
-      case Ci.nsIMobileConnectionService.CALL_FORWARD_REASON_ALL_CONDITIONAL_CALL_FORWARDING:
+      case Ci.nsIMobileConnection.CALL_FORWARD_REASON_UNCONDITIONAL:
+      case Ci.nsIMobileConnection.CALL_FORWARD_REASON_MOBILE_BUSY:
+      case Ci.nsIMobileConnection.CALL_FORWARD_REASON_NO_REPLY:
+      case Ci.nsIMobileConnection.CALL_FORWARD_REASON_NOT_REACHABLE:
+      case Ci.nsIMobileConnection.CALL_FORWARD_REASON_ALL_CALL_FORWARDING:
+      case Ci.nsIMobileConnection.CALL_FORWARD_REASON_ALL_CONDITIONAL_CALL_FORWARDING:
         return true;
       default:
         return false;
     }
   },
 
   /**
    * Helper for guarding us against invalid action values for call forwarding.
    */
   _isValidCallForwardingAction: function(aAction) {
     switch (aAction) {
-      case Ci.nsIMobileConnectionService.CALL_FORWARD_ACTION_DISABLE:
-      case Ci.nsIMobileConnectionService.CALL_FORWARD_ACTION_ENABLE:
-      case Ci.nsIMobileConnectionService.CALL_FORWARD_ACTION_REGISTRATION:
-      case Ci.nsIMobileConnectionService.CALL_FORWARD_ACTION_ERASURE:
+      case Ci.nsIMobileConnection.CALL_FORWARD_ACTION_DISABLE:
+      case Ci.nsIMobileConnection.CALL_FORWARD_ACTION_ENABLE:
+      case Ci.nsIMobileConnection.CALL_FORWARD_ACTION_REGISTRATION:
+      case Ci.nsIMobileConnection.CALL_FORWARD_ACTION_ERASURE:
         return true;
       default:
         return false;
     }
   },
 
   /**
    * Helper for guarding us against invalid program values for call barring.
    */
   _isValidCallBarringProgram: function(aProgram) {
     switch (aProgram) {
-      case Ci.nsIMobileConnectionService.CALL_BARRING_PROGRAM_ALL_OUTGOING:
-      case Ci.nsIMobileConnectionService.CALL_BARRING_PROGRAM_OUTGOING_INTERNATIONAL:
-      case Ci.nsIMobileConnectionService.CALL_BARRING_PROGRAM_OUTGOING_INTERNATIONAL_EXCEPT_HOME:
-      case Ci.nsIMobileConnectionService.CALL_BARRING_PROGRAM_ALL_INCOMING:
-      case Ci.nsIMobileConnectionService.CALL_BARRING_PROGRAM_INCOMING_ROAMING:
+      case Ci.nsIMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING:
+      case Ci.nsIMobileConnection.CALL_BARRING_PROGRAM_OUTGOING_INTERNATIONAL:
+      case Ci.nsIMobileConnection.CALL_BARRING_PROGRAM_OUTGOING_INTERNATIONAL_EXCEPT_HOME:
+      case Ci.nsIMobileConnection.CALL_BARRING_PROGRAM_ALL_INCOMING:
+      case Ci.nsIMobileConnection.CALL_BARRING_PROGRAM_INCOMING_ROAMING:
         return true;
       default:
         return false;
     }
   },
 
   /**
    * Helper for guarding us against invalid options for call barring.
@@ -273,19 +280,19 @@ MobileConnectionProvider.prototype = {
     return true;
   },
 
   /**
    * Helper for guarding us against invalid mode for clir.
    */
   _isValidClirMode: function(aMode) {
     switch (aMode) {
-      case Ci.nsIMobileConnectionService.CLIR_DEFAULT:
-      case Ci.nsIMobileConnectionService.CLIR_INVOCATION:
-      case Ci.nsIMobileConnectionService.CLIR_SUPPRESSION:
+      case Ci.nsIMobileConnection.CLIR_DEFAULT:
+      case Ci.nsIMobileConnection.CLIR_INVOCATION:
+      case Ci.nsIMobileConnection.CLIR_SUPPRESSION:
         return true;
       default:
         return false;
     }
   },
 
   /**
    * Fix the roaming. RIL can report roaming in some case it is not
@@ -381,31 +388,31 @@ MobileConnectionProvider.prototype = {
         if (DEBUG) {
           this._debug("listener for " + aName + " threw an exception: " + e);
         }
       }
     }
   },
 
   updateVoiceInfo: function(aNewInfo, aBatch = false) {
-    let isUpdated = this._updateInfo(this.voiceInfo, aNewInfo);
+    let isUpdated = this._updateInfo(this.voice, aNewInfo);
 
     // Make sure we also reset the operator and signal strength information
     // if we drop off the network.
-    if (this.voiceInfo.state !== RIL.GECKO_MOBILE_CONNECTION_STATE_REGISTERED) {
-      this.voiceInfo.cell = null;
-      this.voiceInfo.network = null;
-      this.voiceInfo.signalStrength = null;
-      this.voiceInfo.relSignalStrength = null;
+    if (this.voice.state !== RIL.GECKO_MOBILE_CONNECTION_STATE_REGISTERED) {
+      this.voice.cell = null;
+      this.voice.network = null;
+      this.voice.signalStrength = null;
+      this.voice.relSignalStrength = null;
     } else {
-      this.voiceInfo.network = this._operatorInfo;
+      this.voice.network = this._operatorInfo;
     }
 
     // Check roaming state
-    isUpdated = this._checkRoamingBetweenOperators(this.voiceInfo) || isUpdated;
+    isUpdated = this._checkRoamingBetweenOperators(this.voice) || isUpdated;
 
     if (isUpdated && !aBatch) {
       this.deliverListenerEvent("notifyVoiceChanged");
     }
   },
 
   updateDataInfo: function(aNewInfo, aBatch = false) {
     let isUpdated = false;
@@ -415,31 +422,31 @@ MobileConnectionProvider.prototype = {
     let active = gNetworkManager.active;
     aNewInfo.connected = false;
     if (active &&
         active.type === Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE &&
         active.serviceId === this._clientId) {
       aNewInfo.connected = true;
     }
 
-    isUpdated = this._updateInfo(this.dataInfo, aNewInfo);
+    isUpdated = this._updateInfo(this.data, aNewInfo);
 
     // Make sure we also reset the operator and signal strength information
     // if we drop off the network.
-    if (this.dataInfo.state !== RIL.GECKO_MOBILE_CONNECTION_STATE_REGISTERED) {
-      this.dataInfo.cell = null;
-      this.dataInfo.network = null;
-      this.dataInfo.signalStrength = null;
-      this.dataInfo.relSignalStrength = null;
+    if (this.data.state !== RIL.GECKO_MOBILE_CONNECTION_STATE_REGISTERED) {
+      this.data.cell = null;
+      this.data.network = null;
+      this.data.signalStrength = null;
+      this.data.relSignalStrength = null;
     } else {
-      this.dataInfo.network = this._operatorInfo;
+      this.data.network = this._operatorInfo;
     }
 
     // Check roaming state
-    isUpdated = this._checkRoamingBetweenOperators(this.dataInfo) || isUpdated;
+    isUpdated = this._checkRoamingBetweenOperators(this.data) || isUpdated;
 
     if (isUpdated && !aBatch) {
       this.deliverListenerEvent("notifyDataChanged");
     }
   },
 
   updateOperatorInfo: function(aNewInfo, aBatch = false) {
     let isUpdated = this._updateInfo(this._operatorInfo, aNewInfo);
@@ -453,39 +460,39 @@ MobileConnectionProvider.prototype = {
         }
 
         this.lastKnownNetwork = network;
         this.deliverListenerEvent("notifyLastKnownNetworkChanged");
       }
     }
 
     // If the voice is unregistered, no need to send notification.
-    if (this.voiceInfo.state !== RIL.GECKO_MOBILE_CONNECTION_STATE_REGISTERED &&
+    if (this.voice.state !== RIL.GECKO_MOBILE_CONNECTION_STATE_REGISTERED &&
         isUpdated && !aBatch) {
       this.deliverListenerEvent("notifyVoiceChanged");
     }
 
     // If the data is unregistered, no need to send notification.
-    if (this.dataInfo.state !== RIL.GECKO_MOBILE_CONNECTION_STATE_REGISTERED &&
+    if (this.data.state !== RIL.GECKO_MOBILE_CONNECTION_STATE_REGISTERED &&
         isUpdated && !aBatch) {
       this.deliverListenerEvent("notifyDataChanged");
     }
   },
 
   updateSignalInfo: function(aNewInfo, aBatch = false) {
     // If the voice is not registered, no need to update signal information.
-    if (this.voiceInfo.state === RIL.GECKO_MOBILE_CONNECTION_STATE_REGISTERED) {
-      if (this._updateInfo(this.voiceInfo, aNewInfo.voice) && !aBatch) {
+    if (this.voice.state === RIL.GECKO_MOBILE_CONNECTION_STATE_REGISTERED) {
+      if (this._updateInfo(this.voice, aNewInfo.voice) && !aBatch) {
         this.deliverListenerEvent("notifyVoiceChanged");
       }
     }
 
     // If the data is not registered, no need to update signal information.
-    if (this.dataInfo.state === RIL.GECKO_MOBILE_CONNECTION_STATE_REGISTERED) {
-      if (this._updateInfo(this.dataInfo, aNewInfo.data) && !aBatch) {
+    if (this.data.state === RIL.GECKO_MOBILE_CONNECTION_STATE_REGISTERED) {
+      if (this._updateInfo(this.data, aNewInfo.data) && !aBatch) {
         this.deliverListenerEvent("notifyDataChanged");
       }
     }
   },
 
   updateIccId: function(aIccId) {
     if (this.iccId === aIccId) {
       return;
@@ -499,16 +506,21 @@ MobileConnectionProvider.prototype = {
     if (this.radioState === aRadioState) {
       return;
     }
 
     this.radioState = aRadioState;
     this.deliverListenerEvent("notifyRadioStateChanged");
   },
 
+  getSupportedNetworkTypes: function(aTypes) {
+    aTypes.value = this.supportedNetworkTypes.slice();
+    return aTypes.value.length;
+  },
+
   getNetworks: function(aCallback) {
     this._radioInterface.sendWorkerMessage("getAvailableNetworks", null,
                                            (function(aResponse) {
       if (aResponse.errorMsg) {
         aCallback.notifyError(aResponse.errorMsg);
         return false;
       }
 
@@ -992,435 +1004,120 @@ MobileConnectionService.prototype = {
       DEBUG = RIL.DEBUG_RIL ||
               Services.prefs.getBoolPref(kPrefRilDebuggingEnabled);
     } catch (e) {}
   },
 
   /**
    * nsIMobileConnectionService interface.
    */
-  registerListener: function(aClientId, aListener) {
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    provider.registerListener(aListener);
-  },
-
-  unregisterListener: function(aClientId, aListener) {
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    provider.unregisterListener(aListener);
-  },
-
-  getVoiceConnectionInfo: function(aClientId) {
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    return provider.voiceInfo;
-  },
-
-  getDataConnectionInfo: function(aClientId) {
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    return provider.dataInfo;
-  },
-
-  getIccId: function(aClientId) {
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    return provider.iccId;
-  },
-
-  getNetworkSelectionMode: function(aClientId) {
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    return provider.networkSelectMode;
-  },
-
-  getRadioState: function(aClientId) {
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    return provider.radioState;
+  get numItems() {
+    return this._providers.length;
   },
 
-  getLastKnownNetwork: function(aClientId) {
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    return provider.lastKnownNetwork;
-  },
-
-  getLastKnownHomeNetwork: function(aClientId) {
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    return provider.lastKnownHomeNetwork;
-  },
-
-  getSupportedNetworkTypes: function(aClientId) {
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    return provider.supportedNetworkTypes;
-  },
-
-  getNetworks: function(aClientId, aCallback) {
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    provider.getNetworks(aCallback);
-  },
-
-  selectNetwork: function(aClientId, aNetwork, aCallback) {
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    provider.selectNetwork(aNetwork, aCallback);
-  },
-
-  selectNetworkAutomatically: function(aClientId, aCallback) {
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    provider.selectNetworkAutomatically(aCallback);
-  },
-
-  setPreferredNetworkType: function(aClientId, aType, aCallback) {
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    provider.setPreferredNetworkType(aType, aCallback);
-  },
-
-  getPreferredNetworkType: function(aClientId, aCallback) {
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    provider.getPreferredNetworkType(aCallback);
-  },
-
-  setRoamingPreference: function(aClientId, aMode, aCallback) {
-    let provider = this._providers[aClientId];
+  getItemByServiceId: function(aServiceId) {
+    let provider = this._providers[aServiceId];
     if (!provider) {
       throw Cr.NS_ERROR_UNEXPECTED;
     }
 
-    provider.setRoamingPreference(aMode, aCallback);
-  },
-
-  getRoamingPreference: function(aClientId, aCallback) {
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    provider.getRoamingPreference(aCallback);
-  },
-
-  setVoicePrivacyMode: function(aClientId, aEnabled, aCallback) {
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    provider.setVoicePrivacyMode(aEnabled, aCallback);
-  },
-
-  getVoicePrivacyMode: function(aClientId, aCallback) {
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    provider.getVoicePrivacyMode(aCallback);
-  },
-
-  sendMMI: function(aClientId, aMmi, aCallback) {
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    provider.sendMMI(aMmi, aCallback);
-  },
-
-  cancelMMI: function(aClientId, aCallback) {
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    provider.cancelMMI(aCallback);
-  },
-
-  setCallForwarding: function(aClientId, aOptions, aCallback) {
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    provider.setCallForwarding(aOptions, aCallback);
-  },
-
-  getCallForwarding: function(aClientId, aReason, aCallback) {
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    provider.getCallForwarding(aReason, aCallback);
-  },
-
-  setCallBarring: function(aClientId, aOptions, aCallback) {
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    provider.setCallBarring(aOptions, aCallback);
-  },
-
-  getCallBarring: function(aClientId, aOptions, aCallback) {
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    provider.getCallBarring(aOptions, aCallback);
-  },
-
-  changeCallBarringPassword: function(aClientId, aOptions, aCallback) {
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    provider.changeCallBarringPassword(aOptions, aCallback);
-  },
-
-  setCallWaiting: function(aClientId, aEnabled, aCallback) {
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    provider.setCallWaiting(aEnabled, aCallback);
-  },
-
-  getCallWaiting: function(aClientId, aCallback) {
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    provider.getCallWaiting(aCallback);
-  },
-
-  setCallingLineIdRestriction: function(aClientId, aMode, aCallback) {
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    provider.setCallingLineIdRestriction(aMode, aCallback);
-  },
-
-  getCallingLineIdRestriction: function(aClientId, aCallback) {
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    provider.getCallingLineIdRestriction(aCallback);
-  },
-
-  exitEmergencyCbMode: function(aClientId, aCallback) {
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    provider.exitEmergencyCbMode(aCallback);
-  },
-
-  setRadioEnabled: function(aClientId, aEnabled, aCallback) {
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    provider.setRadioEnabled(aEnabled, aCallback);
+    return provider;
   },
 
   /**
    * nsIGonkMobileConnectionService interface.
    */
   notifyVoiceInfoChanged: function(aClientId, aVoiceInfo) {
     if (DEBUG) {
       debug("notifyVoiceInfoChanged for " + aClientId + ": " +
             JSON.stringify(aVoiceInfo));
     }
 
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    provider.updateVoiceInfo(aVoiceInfo);
+    this.getItemByServiceId(aClientId).updateVoiceInfo(aVoiceInfo);
   },
 
   notifyDataInfoChanged: function(aClientId, aDataInfo) {
     if (DEBUG) {
       debug("notifyDataInfoChanged for " + aClientId + ": " +
             JSON.stringify(aDataInfo));
     }
 
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    provider.updateDataInfo(aDataInfo);
+    this.getItemByServiceId(aClientId).updateDataInfo(aDataInfo);
   },
 
   notifyUssdReceived: function(aClientId, aMessage, aSessionEnded) {
     if (DEBUG) {
       debug("notifyUssdReceived for " + aClientId + ": " +
             aMessage + " (sessionEnded : " + aSessionEnded + ")");
     }
 
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    provider.deliverListenerEvent("notifyUssdReceived",
-                                [aMessage, aSessionEnded]);
+    this.getItemByServiceId(aClientId)
+        .deliverListenerEvent("notifyUssdReceived", [aMessage, aSessionEnded]);
 
     let info = {
       message: aMessage,
       sessionEnded: aSessionEnded,
       serviceId: aClientId
     };
 
     gSystemMessenger.broadcastMessage("ussd-received", info);
   },
 
   notifyDataError: function(aClientId, aMessage) {
     if (DEBUG) {
       debug("notifyDataError for " + aClientId + ": " + aMessage);
     }
 
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    provider.deliverListenerEvent("notifyDataError", [aMessage]);
+    this.getItemByServiceId(aClientId)
+        .deliverListenerEvent("notifyDataError", [aMessage]);
   },
 
   notifyEmergencyCallbackModeChanged: function(aClientId, aActive, aTimeoutMs) {
     if (DEBUG) {
       debug("notifyEmergencyCbModeChanged for " + aClientId + ": " +
             JSON.stringify({active: aActive, timeoutMs: aTimeoutMs}));
     }
 
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    provider.deliverListenerEvent("notifyEmergencyCbModeChanged",
-                                [aActive, aTimeoutMs]);
+    this.getItemByServiceId(aClientId)
+        .deliverListenerEvent("notifyEmergencyCbModeChanged",
+                              [aActive, aTimeoutMs]);
   },
 
   notifyOtaStatusChanged: function(aClientId, aStatus) {
     if (DEBUG) {
       debug("notifyOtaStatusChanged for " + aClientId + ": " + aStatus);
     }
 
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    provider.deliverListenerEvent("notifyOtaStatusChanged", [aStatus]);
+    this.getItemByServiceId(aClientId)
+        .deliverListenerEvent("notifyOtaStatusChanged", [aStatus]);
   },
 
   notifyIccChanged: function(aClientId, aIccId) {
     if (DEBUG) {
       debug("notifyIccChanged for " + aClientId + ": " + aIccId);
     }
 
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    provider.updateIccId(aIccId);
+    this.getItemByServiceId(aClientId).updateIccId(aIccId);
   },
 
   notifyRadioStateChanged: function(aClientId, aRadioState) {
     if (DEBUG) {
       debug("notifyRadioStateChanged for " + aClientId + ": " + aRadioState);
     }
 
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    provider.updateRadioState(aRadioState);
+    this.getItemByServiceId(aClientId).updateRadioState(aRadioState);
   },
 
   notifyNetworkInfoChanged: function(aClientId, aNetworkInfo) {
     if (DEBUG) {
       debug("notifyNetworkInfoChanged for " + aClientId + ": " +
             JSON.stringify(aNetworkInfo));
     }
 
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
+    let provider = this.getItemByServiceId(aClientId);
 
     let isVoiceUpdated = false;
     let isDataUpdated = false;
     let operatorMessage = aNetworkInfo[RIL.NETWORK_INFO_OPERATOR];
     let voiceMessage = aNetworkInfo[RIL.NETWORK_INFO_VOICE_REGISTRATION_STATE];
     let dataMessage = aNetworkInfo[RIL.NETWORK_INFO_DATA_REGISTRATION_STATE];
     let signalMessage = aNetworkInfo[RIL.NETWORK_INFO_SIGNAL];
     let selectionMessage = aNetworkInfo[RIL.NETWORK_INFO_NETWORK_SELECTION_MODE];
@@ -1456,98 +1153,77 @@ MobileConnectionService.prototype = {
   },
 
   notifySignalStrengthChanged: function(aClientId, aSignal) {
     if (DEBUG) {
       debug("notifySignalStrengthChanged for " + aClientId + ": " +
             JSON.stringify(aSignal));
     }
 
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    provider.updateSignalInfo(aSignal);
+    this.getItemByServiceId(aClientId).updateSignalInfo(aSignal);
   },
 
   notifyOperatorChanged: function(aClientId, aOperator) {
     if (DEBUG) {
       debug("notifyOperatorChanged for " + aClientId + ": " +
             JSON.stringify(aOperator));
     }
 
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    provider.updateOperatorInfo(aOperator);
+    this.getItemByServiceId(aClientId).updateOperatorInfo(aOperator);
   },
 
   notifyNetworkSelectModeChanged: function(aClientId, aMode) {
     if (DEBUG) {
       debug("notifyNetworkSelectModeChanged for " + aClientId + ": " + aMode);
     }
 
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
-    if (provider.networkSelectMode === aMode) {
+    let provider = this.getItemByServiceId(aClientId);
+    if (provider.networkSelectionMode === aMode) {
       return;
     }
 
-    provider.networkSelectMode = aMode;
+    provider.networkSelectionMode = aMode;
     provider.deliverListenerEvent("notifyNetworkSelectionModeChanged");
   },
 
   notifySpnAvailable: function(aClientId) {
     if (DEBUG) {
       debug("notifySpnAvailable for " + aClientId);
     }
 
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
+    let provider = this.getItemByServiceId(aClientId);
 
     // Update voice roaming state
     provider.updateVoiceInfo({});
 
     // Update data roaming state
     provider.updateDataInfo({});
   },
 
   notifyLastHomeNetworkChanged: function(aClientId, aNetwork) {
     if (DEBUG) {
       debug("notifyLastHomeNetworkChanged for " + aClientId + ": " + aNetwork);
     }
 
-    let provider = this._providers[aClientId];
-    if (!provider) {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-
+    let provider = this.getItemByServiceId(aClientId);
     if (provider.lastKnownHomeNetwork === aNetwork) {
       return;
     }
 
     provider.lastKnownHomeNetwork = aNetwork;
     provider.deliverListenerEvent("notifyLastKnownHomeNetworkChanged");
   },
 
   /**
    * nsIObserver interface.
    */
   observe: function(aSubject, aTopic, aData) {
     switch (aTopic) {
       case NS_NETWORK_ACTIVE_CHANGED_TOPIC_ID:
-        for (let i = 0; i < this._providers.length; i++) {
+        for (let i = 0; i < this.numItems; i++) {
           let provider = this._providers[i];
           // Update connected flag only.
           provider.updateDataInfo({});
         }
         break;
       case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID:
         if (aData === kPrefRilDebuggingEnabled) {
           this._updateDebugFlag();