Bug 909688 - B2G RIL - handle data connection state changes properly. r=vicamo, a=leo+
authorJessica Jong <jjong@mozilla.com>
Wed, 18 Sep 2013 18:07:14 +0800
changeset 160305 cd1fef972e2d8ae6dfd1b7913b0829b3b47512fc
parent 160304 2e560c5ce351909b0d39fa9c3b75481fb07301e5
child 160306 30aa92ae05796c25d2204aab4da5a988a38685e6
push id2961
push userlsblakk@mozilla.com
push dateMon, 28 Oct 2013 21:59:28 +0000
treeherdermozilla-beta@73ef4f13486f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvicamo, leo
bugs909688
milestone26.0a2
Bug 909688 - B2G RIL - handle data connection state changes properly. r=vicamo, a=leo+
dom/system/gonk/NetworkManager.js
dom/system/gonk/RadioInterfaceLayer.js
dom/system/gonk/net_worker.js
dom/system/gonk/ril_worker.js
--- a/dom/system/gonk/NetworkManager.js
+++ b/dom/system/gonk/NetworkManager.js
@@ -228,16 +228,17 @@ NetworkManager.prototype = {
         let network = subject.QueryInterface(Ci.nsINetworkInterface);
         debug("Network " + network.name + " changed state to " + network.state);
         switch (network.state) {
           case Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED:
             // Add host route for data calls
             if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE ||
                 network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_MMS ||
                 network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_SUPL) {
+              this.removeHostRoutes(network.name);
               this.addHostRoute(network);
             }
             // Add extra host route. For example, mms proxy or mmsc.
             this.setExtraHostRoute(network);
             // Remove pre-created default route and let setAndConfigureActive()
             // to set default route only on preferred network
             this.removeDefaultRoute(network.name);
             this.setAndConfigureActive();
@@ -635,16 +636,25 @@ NetworkManager.prototype = {
       cmd: "removeHostRoute",
       ifname: network.name,
       gateway: network.gateway,
       hostnames: [network.dns1, network.dns2, network.httpProxyHost]
     };
     this.worker.postMessage(options);
   },
 
+  removeHostRoutes: function removeHostRoutes(ifname) {
+    debug("Going to remove all host routes on " + ifname);
+    let options = {
+      cmd: "removeHostRoutes",
+      ifname: ifname,
+    };
+    this.worker.postMessage(options);
+  },
+
   resolveHostname: function resolveHostname(hosts) {
     let retval = [];
 
     for (let hostname of hosts) {
       try {
         let uri = Services.io.newURI(hostname, null, null);
         hostname = uri.host;
       } catch (e) {}
--- a/dom/system/gonk/RadioInterfaceLayer.js
+++ b/dom/system/gonk/RadioInterfaceLayer.js
@@ -3237,17 +3237,40 @@ RILNetworkInterface.prototype = {
       }
     }
     // In current design, we don't update status of secondary APN if it shares
     // same APN name with the default APN.  In this condition, this.cid will
     // not be set and we don't want to update its status.
     if (this.cid == null) {
       return;
     }
+
     if (this.state == datacall.state) {
+      if (datacall.state != GECKO_NETWORK_STATE_CONNECTED) {
+        return;
+      }
+      // State remains connected, check for minor changes.
+      let changed = false;
+      if (this.gateway != datacall.gw) {
+        this.gateway = datacall.gw;
+        changed = true;
+      }
+      if (datacall.dns &&
+          (this.dns1 != datacall.dns[0] ||
+           this.dns2 != datacall.dns[1])) {
+        this.dns1 = datacall.dns[0];
+        this.dns2 = datacall.dns[1];
+        changed = true;
+      }
+      if (changed) {
+        if (DEBUG) this.debug("Notify for data call minor changes.");
+        Services.obs.notifyObservers(this,
+                                     kNetworkInterfaceStateChangedTopic,
+                                     null);
+      }
       return;
     }
 
     this.state = datacall.state;
 
     // In case the data setting changed while the datacall was being started or
     // ended, let's re-check the setting and potentially adjust the datacall
     // state again.
--- a/dom/system/gonk/net_worker.js
+++ b/dom/system/gonk/net_worker.js
@@ -270,16 +270,23 @@ function addHostRoute(options) {
  * Remove host route for given network interface.
  */
 function removeHostRoute(options) {
   for (let i = 0; i < options.hostnames.length; i++) {
     libnetutils.ifc_remove_route(options.ifname, options.hostnames[i], 32, options.gateway);
   }
 }
 
+/**
+ * Remove the routes associated with the named interface.
+ */
+function removeHostRoutes(options) {
+  libnetutils.ifc_remove_host_routes(options.ifname);
+}
+
 function removeNetworkRoute(options) {
   let ipvalue = netHelpers.stringToIP(options.ip);
   let netmaskvalue = netHelpers.stringToIP(options.netmask);
   let subnet = netmaskvalue & ipvalue;
   let dst = netHelpers.ipToString(subnet);
   let prefixLength = netHelpers.getMaskLength(netmaskvalue);
   let gateway = "0.0.0.0";
 
--- a/dom/system/gonk/ril_worker.js
+++ b/dom/system/gonk/ril_worker.js
@@ -3672,16 +3672,37 @@ let RIL = {
     if (errorCode == ERROR_GENERIC_FAILURE) {
       message.errorMsg = RIL_ERROR_TO_GECKO_ERROR[errorCode];
     } else {
       message.errorMsg = RIL_DATACALL_FAILCAUSE_TO_GECKO_DATACALL_ERROR[errorCode];
     }
     this.sendChromeMessage(message);
   },
 
+  _compareDataCallLink: function _compareDataCallLink(source, target) {
+    if (source.ifname != target.ifname ||
+        source.ipaddr != target.ipaddr ||
+        source.gw != target.gw) {
+      return false;
+    }
+
+    // Compare <datacall>.dns.
+    let sdns = source.dns, tdns = target.dns;
+    if (sdns.length != tdns.length) {
+      return false;
+    }
+    for (let i = 0; i < sdns.length; i++) {
+      if (sdns[i] != tdns[i]) {
+        return false;
+      }
+    }
+
+    return true;
+  },
+
   _processDataCallList: function _processDataCallList(datacalls, newDataCallOptions) {
     // Check for possible PDP errors: We check earlier because the datacall
     // can be removed if is the same as the current one.
     for each (let newDataCall in datacalls) {
       if (newDataCall.status != DATACALL_FAIL_NONE) {
         if (newDataCallOptions) {
           newDataCall.apn = newDataCallOptions.apn;
         }
@@ -3695,16 +3716,17 @@ let RIL = {
         updatedDataCall = datacalls[currentDataCall.cid];
         delete datacalls[currentDataCall.cid];
       }
 
       if (!updatedDataCall) {
         // If datacalls list is coming from REQUEST_SETUP_DATA_CALL response,
         // we do not change state for any currentDataCalls not in datacalls list.
         if (!newDataCallOptions) {
+          delete this.currentDataCalls[currentDataCall.cid];
           currentDataCall.state = GECKO_NETWORK_STATE_DISCONNECTED;
           currentDataCall.rilMessageType = "datacallstatechange";
           this.sendChromeMessage(currentDataCall);
         }
         continue;
       }
 
       if (updatedDataCall && !updatedDataCall.ifname) {
@@ -3712,41 +3734,70 @@ let RIL = {
         currentDataCall.state = GECKO_NETWORK_STATE_UNKNOWN;
         currentDataCall.rilMessageType = "datacallstatechange";
         this.sendChromeMessage(currentDataCall);
         continue;
       }
 
       this._setDataCallGeckoState(updatedDataCall);
       if (updatedDataCall.state != currentDataCall.state) {
+        if (updatedDataCall.state == GECKO_NETWORK_STATE_DISCONNECTED) {
+          delete this.currentDataCalls[currentDataCall.cid];
+        }
         currentDataCall.status = updatedDataCall.status;
         currentDataCall.active = updatedDataCall.active;
         currentDataCall.state = updatedDataCall.state;
         currentDataCall.rilMessageType = "datacallstatechange";
         this.sendChromeMessage(currentDataCall);
-      }
+        continue;
+      }
+
+      // State not changed, now check links.
+      if (this._compareDataCallLink(updatedDataCall, currentDataCall)) {
+        if(DEBUG) debug("No changes in data call.");
+        continue;
+      }
+      if ((updatedDataCall.ifname != currentDataCall.ifname) ||
+          (updatedDataCall.ipaddr != currentDataCall.ipaddr)) {
+        if(DEBUG) debug("Data link changed, cleanup.");
+        this.deactivateDataCall(currentDataCall);
+        continue;
+      }
+      // Minor change, just update and notify.
+      if(DEBUG) debug("Data link minor change, just update and notify.");
+      currentDataCall.gw = updatedDataCall.gw;
+      if (updatedDataCall.dns) {
+        currentDataCall.dns[0] = updatedDataCall.dns[0];
+        currentDataCall.dns[1] = updatedDataCall.dns[1];
+      }
+      currentDataCall.rilMessageType = "datacallstatechange";
+      this.sendChromeMessage(currentDataCall);
     }
 
     for each (let newDataCall in datacalls) {
       if (!newDataCall.ifname) {
         continue;
       }
+
+      if (!newDataCallOptions) {
+        if (DEBUG) debug("Unexpected new data call: " + JSON.stringify(newDataCall));
+        continue;
+      }
+
       this.currentDataCalls[newDataCall.cid] = newDataCall;
       this._setDataCallGeckoState(newDataCall);
-      if (newDataCallOptions) {
-        newDataCall.radioTech = newDataCallOptions.radioTech;
-        newDataCall.apn = newDataCallOptions.apn;
-        newDataCall.user = newDataCallOptions.user;
-        newDataCall.passwd = newDataCallOptions.passwd;
-        newDataCall.chappap = newDataCallOptions.chappap;
-        newDataCall.pdptype = newDataCallOptions.pdptype;
-        newDataCallOptions = null;
-      } else if (DEBUG) {
-        debug("Unexpected new data call: " + JSON.stringify(newDataCall));
-      }
+
+      newDataCall.radioTech = newDataCallOptions.radioTech;
+      newDataCall.apn = newDataCallOptions.apn;
+      newDataCall.user = newDataCallOptions.user;
+      newDataCall.passwd = newDataCallOptions.passwd;
+      newDataCall.chappap = newDataCallOptions.chappap;
+      newDataCall.pdptype = newDataCallOptions.pdptype;
+      newDataCallOptions = null;
+
       newDataCall.rilMessageType = "datacallstatechange";
       this.sendChromeMessage(newDataCall);
     }
   },
 
   _setDataCallGeckoState: function _setDataCallGeckoState(datacall) {
     switch (datacall.active) {
       case DATACALL_INACTIVE: