Merge remote-tracking branch 'origin/master' into merge-to-aosp

Merged the following commits:
- e61717c Fix the virtual lock device
- f1fa93d Add fd event support to EventTaskRunner
- 5ca27be Removing trailing whitespace from speaker.cc
- b51b475 Adding handler for speaker device.
- fdf7515 examples: fix prerequisites for debian
- 82f215e Fixed check for pending tasks
- a627e12 Run CURL in background thread.
diff --git a/examples/daemon/README b/examples/daemon/README
index 287459f..c767420 100644
--- a/examples/daemon/README
+++ b/examples/daemon/README
@@ -8,8 +8,8 @@
         examples/prerequisites.sh
   - build daemon
         examples/build.sh
-  - binaries for daemon is at
-        out/Debug/weave_daemon
+  - binaries for daemon are in the directory
+        out/Debug/
 
 Prepare Host OS
 ---------------
@@ -50,7 +50,7 @@
         93019287-6b26-04a0-22ee-d55ad23a4226
   - go to terminal, register and start the daemon with
 
-        sudo out/Debug/weave_daemon --registration_ticket=93019287-6b26-04a0-22ee-d55ad23a4226
+        sudo out/Debug/weave_daemon_sample --registration_ticket=93019287-6b26-04a0-22ee-d55ad23a4226
 
         you should see something like:
           Publishing service
diff --git a/examples/daemon/examples.gyp b/examples/daemon/examples.gyp
index 8fee90d..2b497a5 100644
--- a/examples/daemon/examples.gyp
+++ b/examples/daemon/examples.gyp
@@ -7,7 +7,8 @@
         'sample/daemon.gyp:weave_daemon_sample',
         'light/daemon.gyp:weave_daemon_light',
         'lock/daemon.gyp:weave_daemon_lock',
-        'ledflasher/daemon.gyp:weave_daemon_ledflasher'
+        'ledflasher/daemon.gyp:weave_daemon_ledflasher',
+        'speaker/daemon.gyp:weave_daemon_speaker'
       ]
     }
   ]
diff --git a/examples/daemon/lock/lock.cc b/examples/daemon/lock/lock.cc
index e1ca2d9..3014fb1 100644
--- a/examples/daemon/lock/lock.cc
+++ b/examples/daemon/lock/lock.cc
@@ -34,11 +34,16 @@
     device_ = device;
 
     device->AddStateDefinitionsFromJson(R"({
-      "lock": {"lockedState": ["locked", "unlocked", "partiallyLocked"]}
+      "lock": {
+        "lockedState": ["locked", "unlocked", "partiallyLocked"],
+        "isLockingSupported": "boolean"}
     })");
 
     device->SetStatePropertiesFromJson(R"({
-      "lock":{"lockedState": "locked"}
+      "lock":{
+        "lockedState": "locked",
+        "isLockingSupported": true
+      }
     })",
                                        nullptr);
 
diff --git a/examples/daemon/speaker/daemon.gyp b/examples/daemon/speaker/daemon.gyp
new file mode 100644
index 0000000..3bf7a91
--- /dev/null
+++ b/examples/daemon/speaker/daemon.gyp
@@ -0,0 +1,18 @@
+# Copyright 2015 The Weave Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+{
+  'targets': [
+    {
+      'target_name': 'weave_daemon_speaker',
+      'type': 'executable',
+      'sources': [
+        'speaker.cc',
+      ],
+      'dependencies': [
+        '<@(DEPTH)/libweave_standalone.gyp:libweave',
+        '<@(DEPTH)/examples/provider/provider.gyp:libweave_provider',
+      ]
+    }
+  ]
+}
diff --git a/examples/daemon/speaker/speaker.cc b/examples/daemon/speaker/speaker.cc
new file mode 100644
index 0000000..32591f9
--- /dev/null
+++ b/examples/daemon/speaker/speaker.cc
@@ -0,0 +1,154 @@
+// Copyright 2015 The Weave 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 "examples/daemon/common/daemon.h"
+
+#include <weave/device.h>
+
+#include <base/bind.h>
+#include <base/memory/weak_ptr.h>
+
+// SpeakerHandler is a command handler example that shows
+// how to handle commands for a Weave speaker.
+class SpeakerHandler {
+ public:
+  SpeakerHandler() = default;
+  void Register(weave::Device* device) {
+    device_ = device;
+
+    device->AddStateDefinitionsFromJson(R"({
+      "onOff": {"state": ["on", "standby"]},
+      "volume": {
+        "volume": "integer",
+        "isMuted": "boolean"
+      }
+    })");
+
+    device->SetStatePropertiesFromJson(R"({
+      "onOff":{"state": "standby"},
+      "volume":{
+        "volume": 100,
+        "isMuted": false
+      }
+    })",
+                                       nullptr);
+
+    device->AddCommandDefinitionsFromJson(R"({
+      "onOff": {
+         "setConfig":{
+           "parameters": {
+             "state": ["on", "standby"]
+           }
+         }
+       },
+       "volume": {
+         "setConfig":{
+           "parameters": {
+             "volume": {
+               "type": "integer",
+               "minimum": 0,
+               "maximum": 100
+             },
+             "isMuted": "boolean"
+           }
+        }
+      }
+    })");
+    device->AddCommandHandler("onOff.setConfig",
+                              base::Bind(&SpeakerHandler::OnOnOffSetConfig,
+                                         weak_ptr_factory_.GetWeakPtr()));
+    device->AddCommandHandler("volume.setConfig",
+                              base::Bind(&SpeakerHandler::OnVolumeSetConfig,
+                                         weak_ptr_factory_.GetWeakPtr()));
+  }
+
+ private:
+  void OnVolumeSetConfig(const std::weak_ptr<weave::Command>& command) {
+    auto cmd = command.lock();
+    if (!cmd)
+      return;
+    LOG(INFO) << "received command: " << cmd->GetName();
+
+    // Handle volume parameter
+    int32_t volume_value = 0;
+    if (cmd->GetParameters()->GetInteger("volume", &volume_value)) {
+      // Display this command in terminal.
+      LOG(INFO) << cmd->GetName() << " volume: " << volume_value;
+
+      if (volume_value_ != volume_value) {
+        volume_value_ = volume_value;
+        UpdateSpeakerState();
+      }
+      cmd->Complete({}, nullptr);
+      return;
+    }
+
+    // Handle isMuted parameter
+    bool isMuted_status = false;
+    if (cmd->GetParameters()->GetBoolean("isMuted", &isMuted_status)) {
+      // Display this command in terminal.
+      LOG(INFO) << cmd->GetName() << " is "
+                << (isMuted_status ? "muted" : "not muted");
+
+      if (isMuted_status_ != isMuted_status) {
+        isMuted_status_ = isMuted_status;
+
+        LOG(INFO) << "Speaker is now: "
+                  << (isMuted_status ? "muted" : "not muted");
+        UpdateSpeakerState();
+      }
+    }
+
+    cmd->Complete({}, nullptr);
+  }
+
+  void OnOnOffSetConfig(const std::weak_ptr<weave::Command>& command) {
+    auto cmd = command.lock();
+    if (!cmd)
+      return;
+    LOG(INFO) << "received command: " << cmd->GetName();
+    std::string requested_state;
+    if (cmd->GetParameters()->GetString("state", &requested_state)) {
+      LOG(INFO) << cmd->GetName() << " state: " << requested_state;
+
+      bool new_speaker_status = requested_state == "on";
+      if (new_speaker_status != speaker_status_) {
+        speaker_status_ = new_speaker_status;
+
+        LOG(INFO) << "Speaker is now: " << (speaker_status_ ? "ON" : "OFF");
+        UpdateSpeakerState();
+      }
+    }
+    cmd->Complete({}, nullptr);
+  }
+
+  void UpdateSpeakerState() {
+    base::DictionaryValue state;
+    state.SetString("onOff.state", speaker_status_ ? "on" : "standby");
+    state.SetBoolean("volume.isMuted", isMuted_status_);
+    state.SetInteger("volume.volume", volume_value_);
+    device_->SetStateProperties(state, nullptr);
+  }
+
+  weave::Device* device_{nullptr};
+
+  // Simulate the state of the speaker.
+  bool speaker_status_;
+  bool isMuted_status_;
+  int32_t volume_value_;
+  base::WeakPtrFactory<SpeakerHandler> weak_ptr_factory_{this};
+};
+
+int main(int argc, char** argv) {
+  Daemon::Options opts;
+  if (!opts.Parse(argc, argv)) {
+    Daemon::Options::ShowUsage(argv[0]);
+    return 1;
+  }
+  Daemon daemon{opts};
+  SpeakerHandler speaker;
+  speaker.Register(daemon.GetDevice());
+  daemon.Run();
+  return 0;
+}
diff --git a/examples/prerequisites.sh b/examples/prerequisites.sh
index d1661d0..7358b71 100755
--- a/examples/prerequisites.sh
+++ b/examples/prerequisites.sh
@@ -6,10 +6,11 @@
 DIR=$(cd -P -- "$(dirname -- "$0")" && pwd -P)
 ROOT_DIR=$(cd -P -- "$(dirname -- "$0")/.." && pwd -P)
 
-sudo apt-get install ${APT_GET_OPTS} \
+sudo apt-get update && sudo apt-get install ${APT_GET_OPTS} \
   autoconf \
   automake \
   binutils \
+  g++ \
   gyp \
   hostapd \
   libavahi-client-dev \
@@ -17,6 +18,7 @@
   libexpat1-dev \
   libnl-3-dev \
   libnl-route-3-dev \
+  libssl-dev \
   libtool \
   ninja-build \
   || exit 1
@@ -57,8 +59,10 @@
 ./autogen.sh || exit 1
 ./configure --disable-shared || exit 1
 make || exit 1
-echo -e "\n\nTesting libevent...\nCan take several minutes.\n"
-make verify || exit 1
+if [ -z "$DISABLE_LIBEVENT_TEST" ]; then
+  echo -e "\n\nTesting libevent...\nCan take several minutes.\n"
+  make verify || exit 1
+fi
 cp -rf include/* $ROOT_DIR/third_party/include/ || exit 1
 cp -rf .libs/lib* $ROOT_DIR/third_party/lib/ || exit 1
 rm -rf $ROOT_DIR/third_party/libevent
diff --git a/examples/provider/curl_http_client.cc b/examples/provider/curl_http_client.cc
index b440f39..32aa4af 100644
--- a/examples/provider/curl_http_client.cc
+++ b/examples/provider/curl_http_client.cc
@@ -4,10 +4,14 @@
 
 #include "examples/provider/curl_http_client.h"
 
+#include <future>
+#include <thread>
+
 #include <base/bind.h>
+#include <base/logging.h>
 #include <curl/curl.h>
-#include <weave/provider/task_runner.h>
 #include <weave/enum_to_string.h>
+#include <weave/provider/task_runner.h>
 
 namespace weave {
 namespace examples {
@@ -30,29 +34,24 @@
   return size * nmemb;
 }
 
-}  // namespace
-
-CurlHttpClient::CurlHttpClient(provider::TaskRunner* task_runner)
-    : task_runner_{task_runner} {}
-
-void CurlHttpClient::SendRequest(Method method,
-                                 const std::string& url,
-                                 const Headers& headers,
-                                 const std::string& data,
-                                 const SendRequestCallback& callback) {
+std::pair<std::unique_ptr<CurlHttpClient::Response>, ErrorPtr>
+SendRequestBlocking(CurlHttpClient::Method method,
+                    const std::string& url,
+                    const CurlHttpClient::Headers& headers,
+                    const std::string& data) {
   std::unique_ptr<CURL, decltype(&curl_easy_cleanup)> curl{curl_easy_init(),
                                                            &curl_easy_cleanup};
   CHECK(curl);
 
   switch (method) {
-    case Method::kGet:
+    case CurlHttpClient::Method::kGet:
       CHECK_EQ(CURLE_OK, curl_easy_setopt(curl.get(), CURLOPT_HTTPGET, 1L));
       break;
-    case Method::kPost:
+    case CurlHttpClient::Method::kPost:
       CHECK_EQ(CURLE_OK, curl_easy_setopt(curl.get(), CURLOPT_HTTPPOST, 1L));
       break;
-    case Method::kPatch:
-    case Method::kPut:
+    case CurlHttpClient::Method::kPatch:
+    case CurlHttpClient::Method::kPut:
       CHECK_EQ(CURLE_OK, curl_easy_setopt(curl.get(), CURLOPT_CUSTOMREQUEST,
                                           weave::EnumToString(method).c_str()));
       break;
@@ -66,7 +65,7 @@
 
   CHECK_EQ(CURLE_OK, curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, chunk));
 
-  if (!data.empty() || method == Method::kPost) {
+  if (!data.empty() || method == CurlHttpClient::Method::kPost) {
     CHECK_EQ(CURLE_OK,
              curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDS, data.c_str()));
   }
@@ -89,8 +88,7 @@
   if (res != CURLE_OK) {
     Error::AddTo(&error, FROM_HERE, "curl", "curl_easy_perform_error",
                  curl_easy_strerror(res));
-    return task_runner_->PostDelayedTask(
-        FROM_HERE, base::Bind(callback, nullptr, base::Passed(&error)), {});
+    return {nullptr, std::move(error)};
   }
 
   const std::string kContentType = "\r\nContent-Type:";
@@ -98,8 +96,7 @@
   if (pos == std::string::npos) {
     Error::AddTo(&error, FROM_HERE, "curl", "no_content_header",
                  "Content-Type header is missing");
-    return task_runner_->PostDelayedTask(
-        FROM_HERE, base::Bind(callback, nullptr, base::Passed(&error)), {});
+    return {nullptr, std::move(error)};
   }
   pos += kContentType.size();
   auto pos_end = response->content_type.find("\r\n", pos);
@@ -112,8 +109,57 @@
   CHECK_EQ(CURLE_OK, curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE,
                                        &response->status));
 
+  return {std::move(response), nullptr};
+}
+
+}  // namespace
+
+CurlHttpClient::CurlHttpClient(provider::TaskRunner* task_runner)
+    : task_runner_{task_runner} {}
+
+void CurlHttpClient::SendRequest(Method method,
+                                 const std::string& url,
+                                 const Headers& headers,
+                                 const std::string& data,
+                                 const SendRequestCallback& callback) {
+  pending_tasks_.emplace_back(
+      std::async(std::launch::async, SendRequestBlocking, method, url, headers,
+                 data),
+      callback);
+  if (pending_tasks_.size() == 1)  // More means check is scheduled.
+    CheckTasks();
+}
+
+void CurlHttpClient::CheckTasks() {
+  VLOG(4) << "CurlHttpClient::CheckTasks, size=" << pending_tasks_.size();
+  auto ready_begin =
+      std::partition(pending_tasks_.begin(), pending_tasks_.end(),
+                     [](const decltype(pending_tasks_)::value_type& value) {
+                       return value.first.wait_for(std::chrono::seconds(0)) !=
+                              std::future_status::ready;
+                     });
+
+  for (auto it = ready_begin; it != pending_tasks_.end(); ++it) {
+    CHECK(it->first.valid());
+    auto result = it->first.get();
+    VLOG(2) << "CurlHttpClient::CheckTasks done";
+    task_runner_->PostDelayedTask(
+        FROM_HERE, base::Bind(it->second, base::Passed(&result.first),
+                              base::Passed(&result.second)),
+        {});
+  }
+
+  pending_tasks_.erase(ready_begin, pending_tasks_.end());
+
+  if (pending_tasks_.empty()) {
+    VLOG(2) << "No more CurlHttpClient tasks";
+    return;
+  }
+
   task_runner_->PostDelayedTask(
-      FROM_HERE, base::Bind(callback, base::Passed(&response), nullptr), {});
+      FROM_HERE,
+      base::Bind(&CurlHttpClient::CheckTasks, weak_ptr_factory_.GetWeakPtr()),
+      base::TimeDelta::FromMilliseconds(100));
 }
 
 }  // namespace examples
diff --git a/examples/provider/curl_http_client.h b/examples/provider/curl_http_client.h
index b2f4bca..c342076 100644
--- a/examples/provider/curl_http_client.h
+++ b/examples/provider/curl_http_client.h
@@ -5,7 +5,9 @@
 #ifndef LIBWEAVE_EXAMPLES_PROVIDER_CURL_HTTP_CLIENT_H_
 #define LIBWEAVE_EXAMPLES_PROVIDER_CURL_HTTP_CLIENT_H_
 
+#include <future>
 #include <string>
+#include <utility>
 
 #include <base/memory/weak_ptr.h>
 #include <weave/provider/http_client.h>
@@ -31,6 +33,11 @@
                    const SendRequestCallback& callback) override;
 
  private:
+  void CheckTasks();
+
+  std::vector<
+      std::pair<std::future<std::pair<std::unique_ptr<Response>, ErrorPtr>>,
+                SendRequestCallback>> pending_tasks_;
   provider::TaskRunner* task_runner_{nullptr};
 
   base::WeakPtrFactory<CurlHttpClient> weak_ptr_factory_{this};
diff --git a/examples/provider/event_deleter.h b/examples/provider/event_deleter.h
new file mode 100644
index 0000000..078c326
--- /dev/null
+++ b/examples/provider/event_deleter.h
@@ -0,0 +1,37 @@
+// Copyright 2015 The Weave 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 LIBWEAVE_EXAMPLES_PROVIDER_EVENT_DELETER_H
+#define LIBWEAVE_EXAMPLES_PROVIDER_EVENT_DELETER_H
+
+#include <memory>
+
+#include <third_party/include/event2/event.h>
+#include <third_party/include/event2/event_struct.h>
+#include <third_party/include/event2/http.h>
+
+namespace weave {
+namespace examples {
+
+// Defines overloaded deletion methods for various event_ objects
+// so we can use one unique_ptr definition for all of them
+class EventDeleter {
+ public:
+  void operator()(evhttp_uri* http_uri) { evhttp_uri_free(http_uri); }
+  void operator()(evhttp_connection* conn) { evhttp_connection_free(conn); }
+  void operator()(evhttp_request* req) { evhttp_request_free(req); }
+  void operator()(event_base* base) { event_base_free(base); }
+  void operator()(event* ev) {
+    event_del(ev);
+    event_free(ev);
+  }
+};
+
+template <typename T>
+using EventPtr = std::unique_ptr<T, EventDeleter>;
+
+}  // namespace examples
+}  // namespace weave
+
+#endif  // LIBWEAVE_EXAMPLES_PROVIDER_EVENT_DELETER_H
diff --git a/examples/provider/event_http_client.cc b/examples/provider/event_http_client.cc
index b38bd55..03da97f 100644
--- a/examples/provider/event_http_client.cc
+++ b/examples/provider/event_http_client.cc
@@ -5,15 +5,14 @@
 #include "examples/provider/event_http_client.h"
 #include "examples/provider/event_task_runner.h"
 
-#include <weave/enum_to_string.h>
-
-#include <string>
 
 #include <base/bind.h>
-
 #include <event2/bufferevent.h>
 #include <event2/buffer.h>
 #include <event2/http.h>
+#include <weave/enum_to_string.h>
+
+#include "examples/provider/event_deleter.h"
 
 // EventHttpClient based on libevent2 http-client sample
 // TODO(proppy): https
@@ -38,13 +37,6 @@
 
 namespace {
 
-class EventDeleter {
- public:
-  void operator()(evhttp_uri* http_uri) { evhttp_uri_free(http_uri); }
-  void operator()(evhttp_connection* conn) { evhttp_connection_free(conn); }
-  void operator()(evhttp_request* req) { evhttp_request_free(req); }
-};
-
 class EventHttpResponse : public weave::provider::HttpClient::Response {
  public:
   int GetStatusCode() const override { return status; }
@@ -58,8 +50,8 @@
 
 struct EventRequestState {
   TaskRunner* task_runner_;
-  std::unique_ptr<evhttp_uri, EventDeleter> http_uri_;
-  std::unique_ptr<evhttp_connection, EventDeleter> evcon_;
+  EventPtr<evhttp_uri> http_uri_;
+  EventPtr<evhttp_connection> evcon_;
   HttpClient::SendRequestCallback callback_;
 };
 
@@ -101,8 +93,7 @@
                                   const SendRequestCallback& callback) {
   evhttp_cmd_type method_id;
   CHECK(weave::StringToEnum(weave::EnumToString(method), &method_id));
-  std::unique_ptr<evhttp_uri, EventDeleter> http_uri{
-      evhttp_uri_parse(url.c_str())};
+  EventPtr<evhttp_uri> http_uri{evhttp_uri_parse(url.c_str())};
   CHECK(http_uri);
   auto host = evhttp_uri_get_host(http_uri.get());
   CHECK(host);
@@ -121,11 +112,10 @@
   auto bev = bufferevent_socket_new(task_runner_->GetEventBase(), -1,
                                     BEV_OPT_CLOSE_ON_FREE);
   CHECK(bev);
-  std::unique_ptr<evhttp_connection, EventDeleter> conn{
-      evhttp_connection_base_bufferevent_new(task_runner_->GetEventBase(), NULL,
-                                             bev, host, port)};
+  EventPtr<evhttp_connection> conn{evhttp_connection_base_bufferevent_new(
+      task_runner_->GetEventBase(), NULL, bev, host, port)};
   CHECK(conn);
-  std::unique_ptr<evhttp_request, EventDeleter> req{evhttp_request_new(
+  EventPtr<evhttp_request> req{evhttp_request_new(
       &RequestDoneCallback,
       new EventRequestState{task_runner_, std::move(http_uri), std::move(conn),
                             callback})};
diff --git a/examples/provider/event_http_client.h b/examples/provider/event_http_client.h
index 7ad117e..378c4a3 100644
--- a/examples/provider/event_http_client.h
+++ b/examples/provider/event_http_client.h
@@ -10,11 +10,11 @@
 #include <base/memory/weak_ptr.h>
 #include <weave/provider/http_client.h>
 
+#include "examples/provider/event_task_runner.h"
+
 namespace weave {
 namespace examples {
 
-class EventTaskRunner;
-
 // Basic implementation of weave::HttpClient using libevent.
 class EventHttpClient : public provider::HttpClient {
  public:
diff --git a/examples/provider/event_task_runner.cc b/examples/provider/event_task_runner.cc
index c14a934..c07e912 100644
--- a/examples/provider/event_task_runner.cc
+++ b/examples/provider/event_task_runner.cc
@@ -24,6 +24,24 @@
   queue_.emplace(std::make_pair(new_time, ++counter_), task);
 }
 
+void EventTaskRunner::AddIoCompletionTask(
+    int fd,
+    int16_t what,
+    const EventTaskRunner::IoCompletionCallback& task) {
+  int16_t flags = EV_PERSIST | EV_ET;
+  flags |= (what & kReadable) ? EV_READ : 0;
+  flags |= (what & kWriteable) ? EV_WRITE : 0;
+  flags |= (what & kClosed) ? EV_CLOSED : 0;
+  event* ioevent = event_new(base_.get(), fd, flags, FdEventHandler, this);
+  EventPtr<event> ioeventPtr{ioevent};
+  fd_task_map_.emplace(fd, std::make_pair(std::move(ioeventPtr), task));
+  event_add(ioevent, nullptr);
+}
+
+void EventTaskRunner::RemoveIoCompletionTask(int fd) {
+  fd_task_map_.erase(fd);
+}
+
 void EventTaskRunner::Run() {
   g_event_base = base_.get();
 
@@ -44,7 +62,9 @@
   event_add(task_event_.get(), &tv);
 }
 
-void EventTaskRunner::EventHandler(int, int16_t, void* runner) {
+void EventTaskRunner::EventHandler(int /* fd */,
+                                   int16_t /* what */,
+                                   void* runner) {
   static_cast<EventTaskRunner*>(runner)->Process();
 }
 
@@ -66,5 +86,17 @@
   }
 }
 
+void EventTaskRunner::FdEventHandler(int fd, int16_t what, void* runner) {
+  static_cast<EventTaskRunner*>(runner)->ProcessFd(fd, what);
+}
+
+void EventTaskRunner::ProcessFd(int fd, int16_t what) {
+  auto it = fd_task_map_.find(fd);
+  if (it != fd_task_map_.end()) {
+    const IoCompletionCallback& callback = it->second.second;
+    callback.Run(fd, what, this);
+  }
+}
+
 }  // namespace examples
 }  // namespace weave
diff --git a/examples/provider/event_task_runner.h b/examples/provider/event_task_runner.h
index 473441e..7291314 100644
--- a/examples/provider/event_task_runner.h
+++ b/examples/provider/event_task_runner.h
@@ -10,9 +10,10 @@
 #include <vector>
 
 #include <event2/event.h>
-
 #include <weave/provider/task_runner.h>
 
+#include "examples/provider/event_deleter.h"
+
 namespace weave {
 namespace examples {
 
@@ -23,6 +24,40 @@
                        const base::Closure& task,
                        base::TimeDelta delay) override;
 
+  // Defines the types of I/O completion events that the
+  // application can register to receive on a file descriptor.
+  enum IOEvent : int16_t {
+    kReadable = 0x01,
+    kWriteable = 0x02,
+    kClosed = 0x04,
+    kReadableWriteable = kReadable | kWriteable,
+    kReadableOrClosed = kReadable | kClosed,
+    kAll = kReadableOrClosed | kWriteable,
+  };
+
+  // Callback type for I/O completion events.
+  // Arguments:
+  //  fd -      file descriptor that triggered the event
+  //  what -    combination of IOEvent flags indicating
+  //            which event(s) occurred
+  //  sender -  reference to the EventTaskRunner that
+  //            called the IoCompletionCallback
+  using IoCompletionCallback =
+      base::Callback<void(int fd, int16_t what, EventTaskRunner* sender)>;
+
+  // Adds a handler for the specified IO completion events on a file
+  // descriptor. The 'what' parameter is a combination of IOEvent flags.
+  // Only one callback is allowed per file descriptor; calling this function
+  // with an fd that has already been registered will replace the previous
+  // callback with the new one.
+  void AddIoCompletionTask(int fd,
+                           int16_t what,
+                           const IoCompletionCallback& task);
+
+  // Remove the callback associated with this fd and stop listening for
+  // events related to it.
+  void RemoveIoCompletionTask(int fd);
+
   event_base* GetEventBase() const { return base_.get(); }
 
   void Run();
@@ -33,6 +68,9 @@
   static void FreeEvent(event* evnt);
   void Process();
 
+  static void FdEventHandler(int fd, int16_t what, void* runner);
+  void ProcessFd(int fd, int16_t what);
+
   using QueueItem = std::pair<std::pair<base::Time, size_t>, base::Closure>;
 
   struct Greater {
@@ -47,10 +85,12 @@
                       std::vector<QueueItem>,
                       EventTaskRunner::Greater> queue_;
 
-  std::unique_ptr<event_base, decltype(&event_base_free)> base_{
-      event_base_new(), &event_base_free};
-  std::unique_ptr<event, decltype(&FreeEvent)> task_event_{
-      event_new(base_.get(), -1, EV_TIMEOUT, &EventHandler, this), &FreeEvent};
+  EventPtr<event_base> base_{event_base_new()};
+
+  EventPtr<event> task_event_{
+      event_new(base_.get(), -1, EV_TIMEOUT, &EventHandler, this)};
+
+  std::map<int, std::pair<EventPtr<event>, IoCompletionCallback>> fd_task_map_;
 };
 
 }  // namespace examples
diff --git a/examples/provider/wifi_manager.cc b/examples/provider/wifi_manager.cc
index 6e216e4..ed6a9fd 100644
--- a/examples/provider/wifi_manager.cc
+++ b/examples/provider/wifi_manager.cc
@@ -42,7 +42,7 @@
 
 WifiImpl::WifiImpl(provider::TaskRunner* task_runner, EventNetworkImpl* network)
     : task_runner_{task_runner}, network_{network} {
-  CHECK_EQ(0, getuid())
+  CHECK_EQ(0u, getuid())
       << "WiFi manager expects root access to control WiFi capabilities";
   StopAccessPoint();
 }