AU: Don't use network on expensive connection types

Specifically:

- FlimFlam proxy class to query the current network status and find
out if it's expensive.
- Dbus Interface, so dbus can be mocked.
- Libcurl change to redirect the URL if we are on an expensive
connection. This may seem hacky, but the reason I avoided retooling
the whole class is that we may decide that some network usage is
okay on pricy connections. Perhaps it's okay to throttle. So, for
now this is a more minimal change.

BUG=chromium-os:2397
TEST=Unit tests, tested that flimflam proxy works on the device.

Review URL: http://codereview.chromium.org/4029002

Change-Id: Ic4dcde1ca863bda890bc46a55c552e2b32d9433d
diff --git a/SConstruct b/SConstruct
index 1a73466..bd2a5d8 100644
--- a/SConstruct
+++ b/SConstruct
@@ -248,6 +248,7 @@
                    filesystem_copier_action.cc
                    filesystem_iterator.cc
                    file_writer.cc
+                   flimflam_proxy.cc
                    graph_utils.cc
                    gzip.cc
                    libcurl_http_fetcher.cc
@@ -288,6 +289,7 @@
                             file_writer_unittest.cc
                             filesystem_copier_action_unittest.cc
                             filesystem_iterator_unittest.cc
+                            flimflam_proxy_unittest.cc
                             graph_utils_unittest.cc
                             http_fetcher_unittest.cc
                             mock_http_fetcher.cc
diff --git a/dbus_interface.h b/dbus_interface.h
new file mode 100644
index 0000000..1b5c953
--- /dev/null
+++ b/dbus_interface.h
@@ -0,0 +1,74 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_PLATFORM_UPDATE_ENGINE_DBUS_INTERFACE_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_DBUS_INTERFACE_H__
+
+// This class interfaces with DBus. The interface allows it to be mocked.
+
+#include <dbus/dbus-glib.h>
+
+namespace chromeos_update_engine {
+
+class DbusGlibInterface {
+ public:
+  // wraps dbus_g_proxy_new_for_name_owner
+  virtual DBusGProxy* ProxyNewForNameOwner(DBusGConnection* connection,
+                                           const char* name,
+                                           const char* path,
+                                           const char* interface,
+                                           GError** error) = 0;
+
+  // wraps g_object_unref
+  virtual void ProxyUnref(DBusGProxy* proxy) = 0;
+
+  // wraps dbus_g_bus_get
+  virtual DBusGConnection*  BusGet(DBusBusType type, GError** error) = 0;
+  
+  // wraps dbus_g_proxy_call
+  virtual gboolean ProxyCall(DBusGProxy* proxy,
+                             const char* method,
+                             GError** error,
+                             GType first_arg_type,
+                             GType var_arg1,
+                             GHashTable** var_arg2,
+                             GType var_arg3) = 0;
+};
+
+class ConcreteDbusGlib : public DbusGlibInterface {
+  virtual DBusGProxy* ProxyNewForNameOwner(DBusGConnection* connection,
+                                           const char* name,
+                                           const char* path,
+                                           const char* interface,
+                                           GError** error) {
+    return dbus_g_proxy_new_for_name_owner(connection,
+                                           name,
+                                           path,
+                                           interface,
+                                           error);
+  }
+
+  virtual void ProxyUnref(DBusGProxy* proxy) {
+    g_object_unref(proxy);
+  }
+
+  virtual DBusGConnection*  BusGet(DBusBusType type, GError** error) {
+    return dbus_g_bus_get(type, error);
+  }
+  
+  virtual gboolean ProxyCall(DBusGProxy* proxy,
+                             const char* method,
+                             GError** error,
+                             GType first_arg_type,
+                             GType var_arg1,
+                             GHashTable** var_arg2,
+                             GType var_arg3) {
+    return dbus_g_proxy_call(
+        proxy, method, error, first_arg_type, var_arg1, var_arg2, var_arg3);
+  }
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_DBUS_INTERFACE_H__
diff --git a/flimflam_proxy.cc b/flimflam_proxy.cc
new file mode 100644
index 0000000..27be084
--- /dev/null
+++ b/flimflam_proxy.cc
@@ -0,0 +1,178 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/flimflam_proxy.h"
+
+#include <string>
+
+#include <base/string_util.h>
+#include <dbus/dbus-glib.h>
+#include <glib.h>
+
+#include "update_engine/utils.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// Gets the DbusGProxy for FlimFlam. Must be free'd with ProxyUnref()
+bool GetFlimFlamProxy(DbusGlibInterface* dbus_iface,
+                      const char* path,
+                      const char* interface,
+                      DBusGProxy** out_proxy) {
+  DBusGConnection* bus;
+  DBusGProxy* proxy;
+  GError* error = NULL;
+
+  bus = dbus_iface->BusGet(DBUS_BUS_SYSTEM, &error);
+  if (!bus) {
+    LOG(ERROR) << "Failed to get system bus";
+    return false;
+  }
+  proxy = dbus_iface->ProxyNewForNameOwner(bus,
+                                           kFlimFlamDbusService,
+                                           path,
+                                           interface,
+                                           &error);
+  if (!proxy) {
+    LOG(ERROR) << "Error getting FlimFlam proxy: "
+               << utils::GetGErrorMessage(error);
+    return false;
+  }
+  *out_proxy = proxy;
+  return true;
+}
+
+// On success, caller owns the GHashTable at out_hash_table.
+// Returns true on success.
+bool GetProperties(DbusGlibInterface* dbus_iface,
+                   const char* path,
+                   const char* interface,
+                   GHashTable** out_hash_table) {
+  DBusGProxy* proxy;
+  GError* error = NULL;
+
+  TEST_AND_RETURN_FALSE(GetFlimFlamProxy(dbus_iface,
+                                         path,
+                                         interface,
+                                         &proxy));
+
+  gboolean rc = dbus_iface->ProxyCall(proxy,
+                                      "GetProperties",
+                                      &error,
+                                      G_TYPE_INVALID,
+                                      dbus_g_type_get_map("GHashTable",
+                                                          G_TYPE_STRING,
+                                                          G_TYPE_VALUE),
+                                      out_hash_table,
+                                      G_TYPE_INVALID);
+  dbus_iface->ProxyUnref(proxy);
+  if (rc == FALSE) {
+    LOG(ERROR) << "dbus_g_proxy_call failed";
+    return false;
+  }
+
+  return true;
+}
+
+// Returns (via out_path) the default network path, or empty string if
+// there's no network up.
+// Returns true on success.
+bool GetDefaultServicePath(DbusGlibInterface* dbus_iface, string* out_path) {
+  GHashTable* hash_table = NULL;
+
+  TEST_AND_RETURN_FALSE(GetProperties(dbus_iface,
+                                      kFlimFlamDbusManagerPath,
+                                      kFlimFlamDbusManagerInterface,
+                                      &hash_table));
+
+  GValue* value = reinterpret_cast<GValue*>(g_hash_table_lookup(hash_table,
+                                                                "Services"));
+  GArray* array = NULL;
+  bool success = false;
+  if (value &&
+      (array = reinterpret_cast<GArray*>(g_value_get_boxed(value))) &&
+      (array->len > 0)) {
+    *out_path = g_array_index(array, const char*, 0);
+    success = true;
+  }
+  g_hash_table_unref(hash_table);
+  return success;
+}
+
+NetworkConnectionType ParseConnectionType(const char* type_str) {
+  if (!strcmp(type_str, kFlimFlamNetTypeEthernet)) {
+    return kNetEthernet;
+  } else if (!strcmp(type_str, kFlimFlamNetTypeWifi)) {
+    return kNetWifi;
+  } else if (!strcmp(type_str, kFlimFlamNetTypeWimax)) {
+    return kNetWimax;
+  } else if (!strcmp(type_str, kFlimFlamNetTypeBluetooth)) {
+    return kNetBluetooth;
+  } else if (!strcmp(type_str, kFlimFlamNetTypeCellular)) {
+    return kNetCellular;
+  }
+  return kNetUnknown;
+}
+
+bool GetServicePathType(DbusGlibInterface* dbus_iface,
+                        const string& path,
+                        NetworkConnectionType* out_type) {
+  GHashTable* hash_table = NULL;
+
+  TEST_AND_RETURN_FALSE(GetProperties(dbus_iface,
+                                      path.c_str(),
+                                      kFlimFlamDbusServiceInterface,
+                                      &hash_table));
+
+  GValue* value = (GValue*)g_hash_table_lookup(hash_table, "Type");
+  const char* type_str = NULL;
+  bool success = false;
+  if (value != NULL && (type_str = g_value_get_string(value)) != NULL) {
+    *out_type = ParseConnectionType(type_str);
+    success = true;
+  }
+  g_hash_table_unref(hash_table);
+  return success;
+}
+
+}  // namespace {}
+
+const char* FlimFlamProxy::StringForConnectionType(NetworkConnectionType type) {
+  static const char* const kValues[] = {kFlimFlamNetTypeEthernet,
+                                        kFlimFlamNetTypeWifi,
+                                        kFlimFlamNetTypeWimax,
+                                        kFlimFlamNetTypeBluetooth,
+                                        kFlimFlamNetTypeCellular};
+  if (type < 0 || type >= static_cast<int>(arraysize(kValues))) {
+    return "Unknown";
+  }
+  return kValues[type];
+}
+
+bool FlimFlamProxy::GetConnectionType(DbusGlibInterface* dbus_iface,
+                                      NetworkConnectionType* out_type) {
+  string default_service_path;
+  TEST_AND_RETURN_FALSE(GetDefaultServicePath(dbus_iface,
+                                              &default_service_path));
+  TEST_AND_RETURN_FALSE(GetServicePathType(dbus_iface,
+                                           default_service_path,
+                                           out_type));
+  return true;
+}
+
+const char* kFlimFlamDbusService = "org.chromium.flimflam";
+const char* kFlimFlamDbusManagerInterface = "org.chromium.flimflam.Manager";
+const char* kFlimFlamDbusManagerPath = "/";
+const char* kFlimFlamDbusServiceInterface = "org.chromium.flimflam.Service";
+
+const char* kFlimFlamNetTypeEthernet = "ethernet";
+const char* kFlimFlamNetTypeWifi = "wifi";
+const char* kFlimFlamNetTypeWimax = "wimax";
+const char* kFlimFlamNetTypeBluetooth = "bluetooth";
+const char* kFlimFlamNetTypeCellular = "cellular";
+
+}  // namespace chromeos_update_engine
diff --git a/flimflam_proxy.h b/flimflam_proxy.h
new file mode 100644
index 0000000..d5e5ff7
--- /dev/null
+++ b/flimflam_proxy.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_PLATFORM_UPDATE_ENGINE_FLIMFLAM_PROXY_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_FLIMFLAM_PROXY_H__
+
+// This class interfaces with FlimFlam to find out data about connectivity.
+
+#include <base/basictypes.h>
+
+#include "update_engine/dbus_interface.h"
+
+namespace chromeos_update_engine {
+
+extern const char* kFlimFlamDbusService;
+extern const char* kFlimFlamDbusManagerInterface;
+extern const char* kFlimFlamDbusManagerPath;
+extern const char* kFlimFlamDbusServiceInterface;
+
+extern const char* kFlimFlamNetTypeEthernet;
+extern const char* kFlimFlamNetTypeWifi;
+extern const char* kFlimFlamNetTypeWimax;
+extern const char* kFlimFlamNetTypeBluetooth;
+extern const char* kFlimFlamNetTypeCellular;
+
+enum NetworkConnectionType {
+  kNetEthernet = 0,
+  kNetWifi,
+  kNetWimax,
+  kNetBluetooth,
+  kNetCellular,
+  kNetUnknown
+};
+
+class FlimFlamProxy {
+ public:
+  static bool GetConnectionType(DbusGlibInterface* dbus_iface,
+                                NetworkConnectionType* out_type);
+
+  static bool IsExpensiveConnectionType(NetworkConnectionType type) {
+    return type == kNetWimax || type == kNetBluetooth || type == kNetCellular;
+  }
+  static const char* StringForConnectionType(NetworkConnectionType type);
+  
+ private:
+  // Should never be allocated
+  DISALLOW_IMPLICIT_CONSTRUCTORS(FlimFlamProxy);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_FLIMFLAM_PROXY_H__
diff --git a/flimflam_proxy_unittest.cc b/flimflam_proxy_unittest.cc
new file mode 100644
index 0000000..e1caa01
--- /dev/null
+++ b/flimflam_proxy_unittest.cc
@@ -0,0 +1,169 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <base/logging.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/flimflam_proxy.h"
+#include "update_engine/mock_dbus_interface.h"
+
+using ::testing::_;
+using ::testing::Return;
+using ::testing::SetArgumentPointee;
+using ::testing::StrEq;
+
+namespace chromeos_update_engine {
+
+template<typename T, void F(T)>
+class ScopedRelease {
+ public:
+  ScopedRelease(T obj) : obj_(obj) {}
+  ~ScopedRelease() {
+    F(obj_);
+  }
+
+ private:
+  T obj_;
+};
+
+class FlimFlamProxyTest : public ::testing::Test {
+ protected:
+  void TestWithServiceType(
+      const char* service_type, NetworkConnectionType expected_type);
+};
+
+void FlimFlamProxyTest::TestWithServiceType(
+    const char* service_type,
+    NetworkConnectionType expected_type) {
+  int number = 1;
+  DBusGConnection* kMockSystemBus =
+      reinterpret_cast<DBusGConnection*>(number++);
+  DBusGProxy* kMockFlimFlamManagerProxy =
+      reinterpret_cast<DBusGProxy*>(number++);
+  DBusGProxy* kMockFlimFlamServiceProxy =
+      reinterpret_cast<DBusGProxy*>(number++);
+  ASSERT_NE(kMockSystemBus, reinterpret_cast<DBusGConnection*>(NULL));
+  const char* kServicePath = "/foo/service";
+  const char kGetPropertiesMethod[] = "GetProperties";
+  
+  MockDbusGlib dbus_iface;
+  
+  EXPECT_CALL(dbus_iface,
+              ProxyNewForNameOwner(kMockSystemBus,
+                                   StrEq(kFlimFlamDbusService),
+                                   StrEq(kFlimFlamDbusManagerPath),
+                                   StrEq(kFlimFlamDbusManagerInterface),
+                                   _))
+      .WillOnce(Return(kMockFlimFlamManagerProxy));
+  EXPECT_CALL(dbus_iface,
+              ProxyNewForNameOwner(kMockSystemBus,
+                                   StrEq(kFlimFlamDbusService),
+                                   StrEq(kServicePath),
+                                   StrEq(kFlimFlamDbusServiceInterface),
+                                   _))
+      .WillOnce(Return(kMockFlimFlamServiceProxy));
+      
+  EXPECT_CALL(dbus_iface, ProxyUnref(kMockFlimFlamManagerProxy));
+  EXPECT_CALL(dbus_iface, ProxyUnref(kMockFlimFlamServiceProxy));
+
+  EXPECT_CALL(dbus_iface, BusGet(DBUS_BUS_SYSTEM, _))
+      .Times(2)
+      .WillRepeatedly(Return(kMockSystemBus));
+  
+  // Set up return value for first dbus call (to Manager object).
+  GHashTable* manager_hash_table = g_hash_table_new(g_str_hash, g_str_equal);
+  ScopedRelease<GHashTable*, g_hash_table_unref> manager_hash_table_unref(
+      manager_hash_table);
+  
+  GArray* array = g_array_new(FALSE, FALSE, sizeof(const char*));
+  ASSERT_TRUE(array != NULL);
+  
+  EXPECT_EQ(array, g_array_append_val(array, kServicePath));
+  GValue array_value = {0, {{0}}};
+  EXPECT_EQ(&array_value, g_value_init(&array_value, G_TYPE_ARRAY));
+  g_value_take_boxed(&array_value, array);
+  g_hash_table_insert(manager_hash_table,
+                      const_cast<char*>("Services"),
+                      &array_value);
+
+  // Set up return value for second dbus call (to Service object).
+
+  GHashTable* service_hash_table = g_hash_table_new(g_str_hash, g_str_equal);
+  ScopedRelease<GHashTable*, g_hash_table_unref> service_hash_table_unref(
+      service_hash_table);
+      
+  GValue service_type_value = {0, {{0}}};
+  EXPECT_EQ(&service_type_value,
+            g_value_init(&service_type_value, G_TYPE_STRING));
+  g_value_set_static_string(&service_type_value, service_type);
+  
+  g_hash_table_insert(service_hash_table,
+                      const_cast<char*>("Type"),
+                      &service_type_value);
+
+  EXPECT_CALL(dbus_iface, ProxyCall(kMockFlimFlamManagerProxy,
+                                    StrEq(kGetPropertiesMethod),
+                                    _,
+                                    G_TYPE_INVALID,
+                                    dbus_g_type_get_map("GHashTable",
+                                                        G_TYPE_STRING,
+                                                        G_TYPE_VALUE),
+                                    _,
+                                    G_TYPE_INVALID))
+      .WillOnce(DoAll(SetArgumentPointee<5>(manager_hash_table), Return(TRUE)));
+
+  EXPECT_CALL(dbus_iface, ProxyCall(kMockFlimFlamServiceProxy,
+                                    StrEq(kGetPropertiesMethod),
+                                    _,
+                                    G_TYPE_INVALID,
+                                    dbus_g_type_get_map("GHashTable",
+                                                        G_TYPE_STRING,
+                                                        G_TYPE_VALUE),
+                                    _,
+                                    G_TYPE_INVALID))
+      .WillOnce(DoAll(SetArgumentPointee<5>(service_hash_table), Return(TRUE)));
+
+  NetworkConnectionType type;
+  
+  EXPECT_TRUE(FlimFlamProxy::GetConnectionType(&dbus_iface, &type));
+  EXPECT_EQ(expected_type, type);
+}
+
+TEST_F(FlimFlamProxyTest, SimpleTest) {
+  TestWithServiceType(kFlimFlamNetTypeEthernet, kNetEthernet);
+  TestWithServiceType(kFlimFlamNetTypeWifi, kNetWifi);
+  TestWithServiceType(kFlimFlamNetTypeWimax, kNetWimax);
+  TestWithServiceType(kFlimFlamNetTypeBluetooth, kNetBluetooth);
+  TestWithServiceType(kFlimFlamNetTypeCellular, kNetCellular);
+}
+
+TEST_F(FlimFlamProxyTest, UnknownTest) {
+  TestWithServiceType("foo", kNetUnknown);
+}
+
+TEST_F(FlimFlamProxyTest, ExpensiveConnectionsTest) {
+  EXPECT_FALSE(FlimFlamProxy::IsExpensiveConnectionType(kNetEthernet));
+  EXPECT_FALSE(FlimFlamProxy::IsExpensiveConnectionType(kNetWifi));
+  EXPECT_TRUE(FlimFlamProxy::IsExpensiveConnectionType(kNetWimax));
+  EXPECT_TRUE(FlimFlamProxy::IsExpensiveConnectionType(kNetBluetooth));
+  EXPECT_TRUE(FlimFlamProxy::IsExpensiveConnectionType(kNetCellular));
+}
+
+TEST_F(FlimFlamProxyTest, StringForConnectionTypeTest) {
+  EXPECT_EQ(kFlimFlamNetTypeEthernet,
+            FlimFlamProxy::StringForConnectionType(kNetEthernet));
+  EXPECT_EQ(kFlimFlamNetTypeWifi,
+            FlimFlamProxy::StringForConnectionType(kNetWifi));
+  EXPECT_EQ(kFlimFlamNetTypeWimax,
+            FlimFlamProxy::StringForConnectionType(kNetWimax));
+  EXPECT_EQ(kFlimFlamNetTypeBluetooth,
+            FlimFlamProxy::StringForConnectionType(kNetBluetooth));
+  EXPECT_EQ(kFlimFlamNetTypeCellular,
+            FlimFlamProxy::StringForConnectionType(kNetCellular));
+  EXPECT_EQ("Unknown", FlimFlamProxy::StringForConnectionType(kNetUnknown));
+  EXPECT_EQ("Unknown", FlimFlamProxy::StringForConnectionType(
+      static_cast<NetworkConnectionType>(999999)));
+}
+
+}  // namespace chromeos_update_engine
diff --git a/http_fetcher_unittest.cc b/http_fetcher_unittest.cc
index 06cabac..bd16768 100644
--- a/http_fetcher_unittest.cc
+++ b/http_fetcher_unittest.cc
@@ -135,6 +135,7 @@
     // Speed up test execution.
     ret->set_idle_seconds(1);
     ret->set_retry_seconds(1);
+    ret->SetConnectionAsExpensive(false);
     return ret;
   }
   HttpFetcher* NewSmallFetcher() {
@@ -696,4 +697,44 @@
   }
 }
 
+namespace {
+class ExpensiveConnectionTestDelegate : public HttpFetcherDelegate {
+ public:
+  virtual void ReceivedBytes(HttpFetcher* fetcher,
+                             const char* bytes, int length) {
+    ADD_FAILURE();
+  }
+  virtual void TransferComplete(HttpFetcher* fetcher, bool successful) {
+    EXPECT_FALSE(successful);
+    g_main_loop_quit(loop_);
+  }
+  GMainLoop* loop_;
+};
+
+}  // namespace
+
+TYPED_TEST(HttpFetcherTest, ExpensiveConnectionTest) {
+  if (this->IsMock() || this->IsMulti())
+    return;
+  typename TestFixture::HttpServer server;
+  
+  ASSERT_TRUE(server.started_);
+
+  GMainLoop* loop = g_main_loop_new(g_main_context_default(), FALSE);
+  ExpensiveConnectionTestDelegate delegate;
+  delegate.loop_ = loop;
+
+  scoped_ptr<HttpFetcher> fetcher(this->NewLargeFetcher());
+  dynamic_cast<LibcurlHttpFetcher*>(
+      fetcher.get())->SetConnectionAsExpensive(true);
+  fetcher->set_delegate(&delegate);
+
+  StartTransferArgs start_xfer_args =
+      { fetcher.get(), LocalServerUrlForPath(this->SmallUrl()) };
+
+  g_timeout_add(0, StartTransfer, &start_xfer_args);
+  g_main_loop_run(loop);
+  g_main_loop_unref(loop);
+}
+
 }  // namespace chromeos_update_engine
diff --git a/libcurl_http_fetcher.cc b/libcurl_http_fetcher.cc
index c846812..9cacf86 100644
--- a/libcurl_http_fetcher.cc
+++ b/libcurl_http_fetcher.cc
@@ -3,11 +3,19 @@
 // found in the LICENSE file.
 
 #include "update_engine/libcurl_http_fetcher.h"
+
 #include <algorithm>
-#include "base/logging.h"
+#include <string>
+
+#include <base/logging.h>
+
+#include "update_engine/dbus_interface.h"
+#include "update_engine/flimflam_proxy.h"
+#include "update_engine/utils.h"
 
 using std::max;
 using std::make_pair;
+using std::string;
 
 // This is a concrete implementation of HttpFetcher that uses libcurl to do the
 // http work.
@@ -17,12 +25,24 @@
 namespace {
 const int kMaxRetriesCount = 20;
 const char kCACertificatesPath[] = "/usr/share/update_engine/ca-certificates";
-}
+}  // namespace {}
 
 LibcurlHttpFetcher::~LibcurlHttpFetcher() {
   CleanUp();
 }
 
+// On error, returns false.
+bool LibcurlHttpFetcher::ConnectionIsExpensive() const {
+  if (force_connection_type_)
+    return forced_expensive_connection_;
+  NetworkConnectionType type;
+  ConcreteDbusGlib dbus_iface;
+  TEST_AND_RETURN_FALSE(FlimFlamProxy::GetConnectionType(&dbus_iface, &type));
+  LOG(INFO) << "We are connected via "
+            << FlimFlamProxy::StringForConnectionType(type);
+  return FlimFlamProxy::IsExpensiveConnectionType(type);
+}
+
 void LibcurlHttpFetcher::ResumeTransfer(const std::string& url) {
   LOG(INFO) << "Starting/Resuming transfer";
   CHECK(!transfer_in_progress_);
@@ -54,7 +74,18 @@
   CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_WRITEDATA, this), CURLE_OK);
   CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_WRITEFUNCTION,
                             StaticLibcurlWrite), CURLE_OK);
-  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_URL, url_.c_str()), CURLE_OK);
+
+  string url_to_use(url_);
+  if (ConnectionIsExpensive()) {
+    LOG(INFO) << "Not initiating HTTP connection b/c we are on an expensive"
+              << " connection";
+    url_to_use = "";  // Sabotage the URL
+  }
+
+  CHECK_EQ(curl_easy_setopt(curl_handle_,
+                            CURLOPT_URL,
+                            url_to_use.c_str()),
+           CURLE_OK);
 
   // If the connection drops under 10 bytes/sec for 3 minutes, reconnect.
   CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_LOW_SPEED_LIMIT, 10),
diff --git a/libcurl_http_fetcher.h b/libcurl_http_fetcher.h
index 69e794f..2628ecc 100644
--- a/libcurl_http_fetcher.h
+++ b/libcurl_http_fetcher.h
@@ -33,6 +33,8 @@
         retry_count_(0),
         retry_seconds_(60),
         idle_seconds_(1),
+        force_connection_type_(false),
+        forced_expensive_connection_(false),
         in_write_callback_(false),
         terminate_requested_(false) {}
 
@@ -67,6 +69,11 @@
 
   // Sets the retry timeout. Useful for testing.
   void set_retry_seconds(int seconds) { retry_seconds_ = seconds; }
+  
+  void SetConnectionAsExpensive(bool is_expensive) {
+    force_connection_type_ = true;
+    forced_expensive_connection_ = is_expensive;
+  }
 
  private:
   // Asks libcurl for the http response code and stores it in the object.
@@ -122,6 +129,10 @@
   // curl(m) handles, io_channels_, timeout_source_.
   void CleanUp();
 
+  // Returns whether or not the current network connection is considered
+  // expensive.
+  bool ConnectionIsExpensive() const;
+
   // Handles for the libcurl library
   CURLM *curl_multi_handle_;
   CURL *curl_handle_;
@@ -157,6 +168,11 @@
 
   // Seconds to wait before asking libcurl to "perform".
   int idle_seconds_;
+  
+  // If true, assume the network is expensive or not, according to
+  // forced_expensive_connection_. (Useful for testing).
+  bool force_connection_type_;
+  bool forced_expensive_connection_;
 
   // If true, we are currently performing a write callback on the delegate.
   bool in_write_callback_;
diff --git a/mock_dbus_interface.h b/mock_dbus_interface.h
new file mode 100644
index 0000000..bf0d609
--- /dev/null
+++ b/mock_dbus_interface.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_PLATFORM_UPDATE_ENGINE_MOCK_DBUS_INTERFACE_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_MOCK_DBUS_INTERFACE_H__
+
+#include <gmock/gmock.h>
+
+#include "update_engine/dbus_interface.h"
+
+namespace chromeos_update_engine {
+
+class MockDbusGlib : public DbusGlibInterface {
+ public:
+  MOCK_METHOD5(ProxyNewForNameOwner, DBusGProxy*(DBusGConnection *connection,
+                                                 const char *name,
+                                                 const char *path,
+                                                 const char *interface,
+                                                 GError **error));
+
+  MOCK_METHOD1(ProxyUnref, void(DBusGProxy* proxy));
+
+  MOCK_METHOD2(BusGet, DBusGConnection*(DBusBusType type, GError **error));
+
+  MOCK_METHOD7(ProxyCall, gboolean(DBusGProxy *proxy,
+                                   const char *method,
+                                   GError **error,
+                                   GType first_arg_type,
+                                   GType var_arg1,
+                                   GHashTable** var_arg2,
+                                   GType var_arg3));
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_MOCK_DBUS_INTERFACE_H__