Add support for initial delay of modem switch operation.

This reintroduces CL:175741 (but fixes a missing header inclusion) that
adds an 'initial_delay_ms' parameter in the mist configuration file for
specifying the initial delay, in milliseconds, that mist should wait
before starting the modem switch operation. Such a delay is required to
reliably switch some dongles (e.g. Micromax MMX 353G) to the modem mode.

BUG=chromium:313211
TEST=Tested the following:
1. Build and run unit tests.
2. Run remote trybots on {x86,amd64,arm-generic}-release builders.
3. Verify on daisy_spring and peppy that mist successfully switches the
   Micromax MMX 353G dongle from the mass storage mode to the modem
   mode.

Change-Id: Ie82bfe6777b4495b05d304fd78e8d5961041830e
Reviewed-on: https://chromium-review.googlesource.com/175859
Reviewed-by: Ben Chan <benchan@chromium.org>
Commit-Queue: Ben Chan <benchan@chromium.org>
Tested-by: Ben Chan <benchan@chromium.org>
diff --git a/config_loader_unittest.cc b/config_loader_unittest.cc
index 988c26e..efe10ff 100644
--- a/config_loader_unittest.cc
+++ b/config_loader_unittest.cc
@@ -40,6 +40,7 @@
     "  usb_message: \"fedcba9877654210\"\n"
     "  usb_message: \"1234\"\n"
     "  expect_response: true\n"
+    "  initial_delay_ms: 2500\n"
     "}\n";
 
 }  // namespace
@@ -84,6 +85,7 @@
   EXPECT_EQ(0, usb_modem_info1->final_usb_id_size());
   EXPECT_EQ(0, usb_modem_info1->usb_message_size());
   EXPECT_FALSE(usb_modem_info1->expect_response());
+  EXPECT_EQ(0, usb_modem_info1->initial_delay_ms());
 
   const UsbModemInfo* usb_modem_info2 =
       config_loader_.GetUsbModemInfo(0x1234, 0xabcd);
@@ -100,6 +102,7 @@
   EXPECT_EQ("fedcba9877654210", usb_modem_info2->usb_message(1));
   EXPECT_EQ("1234", usb_modem_info2->usb_message(2));
   EXPECT_TRUE(usb_modem_info2->expect_response());
+  EXPECT_EQ(2500, usb_modem_info2->initial_delay_ms());
 }
 
 TEST_F(ConfigLoaderTest, LoadEmptyConfigFile) {
diff --git a/default.conf b/default.conf
index 7b36aaf..f613823 100644
--- a/default.conf
+++ b/default.conf
@@ -150,6 +150,7 @@
   initial_usb_id { vendor_id: 0x1c9e product_id: 0xf000 }
   final_usb_id { vendor_id: 0x1c9e product_id: 0x9605 }
   usb_message: "55534243123456788000000080000606f50402527000000000000000000000"
+  initial_delay_ms: 1000
 }
 
 # Micromax MMX 377G
diff --git a/proto/usb_modem_info.proto b/proto/usb_modem_info.proto
index 2b16d6e..ed70f30 100644
--- a/proto/usb_modem_info.proto
+++ b/proto/usb_modem_info.proto
@@ -25,4 +25,7 @@
   // If true, a response is expected from the mass storage interface after
   // sending each USB message to the interface.
   optional bool expect_response = 4;
+  // Initial delay, in milliseconds, that mist should wait before starting
+  // the modem switch operation.
+  optional uint32 initial_delay_ms = 5;
 }
diff --git a/usb_modem_switch_operation.cc b/usb_modem_switch_operation.cc
index c60ebee..0f93d28 100644
--- a/usb_modem_switch_operation.cc
+++ b/usb_modem_switch_operation.cc
@@ -94,10 +94,16 @@
   VLOG(1) << "Start modem switch operation for device '"
           << switch_context_->sys_path() << "'.";
 
-  // Defer the execution of the first task as multiple UsbModemSwitchOperation
-  // objects may be created and started in a tight loop.
-  ScheduleTask(
-      &UsbModemSwitchOperation::OpenDeviceAndClaimMassStorageInterface);
+  // Schedule the execution of the first task using the message loop, even when
+  // the initial delay is 0, as multiple UsbModemSwitchOperation objects may be
+  // created and started in a tight loop.
+  base::TimeDelta initial_delay = base::TimeDelta::FromMilliseconds(
+      switch_context_->modem_info()->initial_delay_ms());
+  LOG(INFO) << "Starting the modem switch operation in "
+            << initial_delay.InMilliseconds() << " ms.";
+  ScheduleDelayedTask(
+      &UsbModemSwitchOperation::OpenDeviceAndClaimMassStorageInterface,
+      initial_delay);
 }
 
 void UsbModemSwitchOperation::Cancel() {
@@ -114,6 +120,14 @@
   context_->event_dispatcher()->PostTask(pending_task_.callback());
 }
 
+void UsbModemSwitchOperation::ScheduleDelayedTask(
+    Task task,
+    const base::TimeDelta& delay) {
+  pending_task_.Reset(Bind(task, Unretained(this)));
+  context_->event_dispatcher()->PostDelayedTask(pending_task_.callback(),
+                                                delay);
+}
+
 void UsbModemSwitchOperation::Complete(bool success) {
   CHECK(!completion_callback_.is_null());
 
diff --git a/usb_modem_switch_operation.h b/usb_modem_switch_operation.h
index 8fbd278..4fe6917 100644
--- a/usb_modem_switch_operation.h
+++ b/usb_modem_switch_operation.h
@@ -13,6 +13,7 @@
 #include <base/compiler_specific.h>
 #include <base/memory/scoped_ptr.h>
 #include <base/memory/weak_ptr.h>
+#include <base/time.h>
 
 #include "mist/usb_device_event_observer.h"
 
@@ -74,10 +75,18 @@
   typedef void (UsbModemSwitchOperation::*UsbTransferCompletionHandler)(
       UsbTransfer* transfer);
 
-  // Schedules the next task in the message loop for execution. At most one
-  // pending task is allowed at any time.
+  // Schedules the specified |task| in the message loop for execution. At most
+  // one pending task is allowed, so any pending task previously scheduled by
+  // ScheduleTask() or ScheduleDelayedTask() is cancelled before |task| is
+  // scheduled.
   void ScheduleTask(Task task);
 
+  // Schedules the specified |task| in the message loop for execution after the
+  // specified |delay|. At most one pending task is allowed, so any pending
+  // task previously scheduled by ScheduleTask() or ScheduleDelayedTask() is
+  // cancelled before |task| is scheduled.
+  void ScheduleDelayedTask(Task task, const base::TimeDelta& delay);
+
   // Completes the operation, which invokes the completion callback with the
   // status of the operation as |success|. The completion callback may delete
   // this object, so this object should not be accessed after this method