iioservice: Add iioservice with SamplesHandler

This commit adds the first part of iioservice, which is the daemon that
provides sensor data to applications, such as Camera, Power Manager,
Chromium and ARC++/ARC_VM.

SamplesHandler is a handler which analyzes data from kernel and dispatch
samples to applications without conflicts.

BUG=chromium:1006141
TEST=builds and unit tests.

Cq-Depend: chromium:2210281
Change-Id: Ib6b1d165b5d9b9ed14055c5faffdbd054e28fa4d
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/2215941
Reviewed-by: Gwendal Grignou <gwendal@chromium.org>
Tested-by: Cheng-Hao Yang <chenghaoyang@chromium.org>
Auto-Submit: Cheng-Hao Yang <chenghaoyang@chromium.org>
Commit-Queue: Cheng-Hao Yang <chenghaoyang@chromium.org>
diff --git a/iioservice/README.md b/iioservice/README.md
index b71b499..cdc8399 100644
--- a/iioservice/README.md
+++ b/iioservice/README.md
@@ -8,3 +8,12 @@
 
 This library provides mojo interfaces to connect to sensor_dispatcher and
 iioservice, and C++ utility functions and classes to establish mojo channels.
+
+## Daemon iioservice
+
+- `/usr/sbin/iioservice`
+
+This daemon provides mojo channels that let processes connect to it. iioservice
+will dispatch devices' event data to the processes that need them. Each process
+can set the desired frequencies and IIO channels of devices without conflicts,
+as it owns all IIO devices.
diff --git a/iioservice/daemon/BUILD.gn b/iioservice/daemon/BUILD.gn
new file mode 100644
index 0000000..9da328e
--- /dev/null
+++ b/iioservice/daemon/BUILD.gn
@@ -0,0 +1,71 @@
+# Copyright 2020 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.
+
+import("//common-mk/pkg_config.gni")
+
+group("all") {
+  deps = [ ":iioservice" ]
+
+  if (use.test) {
+    deps += [ ":iioservice_testrunner" ]
+  }
+}
+
+pkg_config("target_defaults_pkg_deps") {
+  pkg_deps = [
+    "libbrillo",
+    "libchrome-${libbase_ver}",
+    "libmojo-${libbase_ver}",
+  ]
+}
+
+static_library("libiioservice") {
+  configs += [ ":target_defaults_pkg_deps" ]
+
+  sources = [ "samples_handler.cc" ]
+  public_pkg_deps = [ "libmems" ]
+
+  deps = [
+    "//iioservice/libiioservice_ipc:libiioservice_ipc",
+    "//iioservice/libiioservice_ipc:libiioservice_ipc_mojom",
+  ]
+}
+
+executable("iioservice") {
+  configs += [ ":target_defaults_pkg_deps" ]
+
+  sources = [ "main.cc" ]
+  deps = [ ":libiioservice" ]
+}
+
+if (use.test) {
+  pkg_config("iioservice_testrunner_pkg_deps") {
+    pkg_deps = [
+      "libbrillo-test",
+      "libchrome-test-${libbase_ver}",
+      "libiio",
+    ]
+  }
+
+  executable("iioservice_testrunner") {
+    configs += [
+      "//common-mk:test",
+      ":iioservice_testrunner_pkg_deps",
+      ":target_defaults_pkg_deps",
+    ]
+
+    deps = [
+      ":libiioservice",
+      "//common-mk/testrunner",
+    ]
+
+    pkg_deps = [ "libmems_test_support" ]
+
+    sources = [
+      "samples_handler_test.cc",
+      "test_fakes.cc",
+      "testrunner.cc",
+    ]
+  }
+}
diff --git a/iioservice/daemon/common_types.h b/iioservice/daemon/common_types.h
new file mode 100644
index 0000000..0868542
--- /dev/null
+++ b/iioservice/daemon/common_types.h
@@ -0,0 +1,32 @@
+// Copyright 2020 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 IIOSERVICE_DAEMON_COMMON_TYPES_H_
+#define IIOSERVICE_DAEMON_COMMON_TYPES_H_
+
+#include <set>
+#include <string>
+
+#include <mojo/public/cpp/bindings/receiver_set.h>
+
+#include <libmems/iio_device.h>
+
+#include "mojo/sensor.mojom.h"
+
+namespace iioservice {
+
+constexpr char kSamplingFrequencyAvailable[] = "sampling_frequency_available";
+
+struct ClientData {
+  mojo::ReceiverId id;
+  libmems::IioDevice* iio_device;
+  std::set<int32_t> enabled_chn_indices;
+  double frequency = -1;    // Hz
+  uint32_t timeout = 5000;  // millisecond
+  mojo::Remote<cros::mojom::SensorDeviceSamplesObserver> observer;
+};
+
+}  // namespace iioservice
+
+#endif  // IIOSERVICE_DAEMON_COMMON_TYPES_H_
diff --git a/iioservice/daemon/main.cc b/iioservice/daemon/main.cc
new file mode 100644
index 0000000..d6eb38f
--- /dev/null
+++ b/iioservice/daemon/main.cc
@@ -0,0 +1,14 @@
+// Copyright 2020 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 <brillo/daemons/daemon.h>
+
+int main() {
+  brillo::Daemon daemon;
+  daemon.Run();
+  LOG(INFO) << "Daemon stopped";
+
+  return 0;
+}
diff --git a/iioservice/daemon/samples_handler.cc b/iioservice/daemon/samples_handler.cc
new file mode 100644
index 0000000..025e4bc
--- /dev/null
+++ b/iioservice/daemon/samples_handler.cc
@@ -0,0 +1,702 @@
+// Copyright 2020 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 "iioservice/daemon/samples_handler.h"
+
+#include <algorithm>
+#include <limits>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_split.h>
+#include <base/time/time.h>
+
+#include <libmems/test_fakes.h>
+#include <libmems/common_types.h>
+#include <libmems/iio_channel.h>
+#include <libmems/iio_context.h>
+#include <libmems/iio_device.h>
+
+#include "iioservice/include/common.h"
+#include "iioservice/include/constants.h"
+
+namespace iioservice {
+
+// static
+void SamplesHandler::SamplesHandlerDeleter(SamplesHandler* handler) {
+  if (handler == nullptr)
+    return;
+
+  if (!handler->sample_task_runner_->BelongsToCurrentThread()) {
+    handler->sample_task_runner_->PostTask(
+        FROM_HERE,
+        base::BindOnce(&SamplesHandler::SamplesHandlerDeleter, handler));
+    return;
+  }
+
+  delete handler;
+}
+
+// static
+bool SamplesHandler::GetDevMinMaxFrequency(libmems::IioDevice* iio_device,
+                                           double* min_freq,
+                                           double* max_freq) {
+  auto available_opt =
+      iio_device->ReadStringAttribute(kSamplingFrequencyAvailable);
+  if (!available_opt.has_value()) {
+    LOG(ERROR) << "Failed to read attribute: " << kSamplingFrequencyAvailable;
+    return false;
+  }
+
+  std::string sampling_frequency_available = available_opt.value();
+  // Remove trailing '\0' for parsing
+  auto pos = available_opt->find_first_of('\0');
+  if (pos != std::string::npos)
+    sampling_frequency_available = available_opt->substr(0, pos);
+
+  std::vector<std::string> sampling_frequencies =
+      base::SplitString(sampling_frequency_available, " ",
+                        base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+
+  switch (sampling_frequencies.size()) {
+    case 0:
+      LOG(ERROR) << "Invalid format of " << kSamplingFrequencyAvailable << ": "
+                 << sampling_frequency_available;
+      return false;
+
+    case 1:
+      if (!base::StringToDouble(sampling_frequencies.front(), min_freq) ||
+          *min_freq < 0.0 || *min_freq < kFrequencyEpsilon) {
+        LOG(ERROR) << "Failed to parse min max sampling_frequency: "
+                   << sampling_frequency_available;
+        return false;
+      }
+
+      *max_freq = *min_freq;
+      return true;
+
+    default:
+      if (!base::StringToDouble(sampling_frequencies.back(), max_freq) ||
+          *max_freq < kFrequencyEpsilon) {
+        LOG(ERROR) << "Failed to parse max sampling_frequency: "
+                   << sampling_frequency_available;
+        return false;
+      }
+
+      if (!base::StringToDouble(sampling_frequencies.front(), min_freq) ||
+          *min_freq < 0.0) {
+        LOG(ERROR) << "Failed to parse the first sampling_frequency: "
+                   << sampling_frequency_available;
+        return false;
+      }
+
+      if (*min_freq == 0.0) {
+        if (!base::StringToDouble(sampling_frequencies[1], min_freq) ||
+            *min_freq < 0.0 || *max_freq < *min_freq) {
+          LOG(ERROR) << "Failed to parse min sampling_frequency: "
+                     << sampling_frequency_available;
+          return false;
+        }
+      }
+
+      return true;
+  }
+}
+
+// static
+SamplesHandler::ScopedSamplesHandler SamplesHandler::CreateWithFifo(
+    scoped_refptr<base::SequencedTaskRunner> ipc_task_runner,
+    scoped_refptr<base::SingleThreadTaskRunner> sample_task_runner,
+    libmems::IioDevice* iio_device,
+    OnSampleUpdatedCallback on_sample_updated_callback,
+    OnErrorOccurredCallback on_error_occurred_callback) {
+  ScopedSamplesHandler handler(nullptr, SamplesHandlerDeleter);
+
+  double min_freq, max_freq;
+  if (!GetDevMinMaxFrequency(iio_device, &min_freq, &max_freq))
+    return handler;
+
+  handler.reset(new SamplesHandler(
+      std::move(ipc_task_runner), std::move(sample_task_runner), iio_device,
+      min_freq, max_freq, std::move(on_sample_updated_callback),
+      std::move(on_error_occurred_callback)));
+  return handler;
+}
+
+// static
+SamplesHandler::ScopedSamplesHandler SamplesHandler::CreateWithoutFifo(
+    scoped_refptr<base::SequencedTaskRunner> ipc_task_runner,
+    scoped_refptr<base::SingleThreadTaskRunner> sample_task_runner,
+    libmems::IioContext* iio_context,
+    libmems::IioDevice* iio_device,
+    OnSampleUpdatedCallback on_sample_updated_callback,
+    OnErrorOccurredCallback on_error_occurred_callback) {
+  ScopedSamplesHandler handler(nullptr, SamplesHandlerDeleter);
+
+  double min_freq, max_freq;
+  if (!GetDevMinMaxFrequency(iio_device, &min_freq, &max_freq))
+    return handler;
+
+  auto trigger_device = iio_device->GetTrigger();
+  if (!trigger_device) {
+    trigger_device = iio_context->GetTriggerById(iio_device->GetId() + 1);
+    if (!trigger_device) {
+      LOGF(ERROR) << "Failed to find trigger with id: " << iio_device->GetId();
+
+      return handler;
+    }
+  }
+
+  handler.reset(new SamplesHandler(
+      std::move(ipc_task_runner), std::move(sample_task_runner), iio_device,
+      trigger_device, min_freq, max_freq, std::move(on_sample_updated_callback),
+      std::move(on_error_occurred_callback)));
+  return handler;
+}
+
+SamplesHandler::~SamplesHandler() {
+  if (requested_frequency_ > 0.0 &&
+      !iio_device_->WriteDoubleAttribute(libmems::kSamplingFrequencyAttr, 0.0))
+    LOGF(ERROR) << "Failed to set frequency";
+}
+
+void SamplesHandler::AddClient(ClientData* client_data) {
+  DCHECK_EQ(client_data->iio_device, iio_device_);
+
+  sample_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&SamplesHandler::AddClientOnThread,
+                                weak_factory_.GetWeakPtr(), client_data));
+}
+
+void SamplesHandler::RemoveClient(ClientData* client_data) {
+  DCHECK_EQ(client_data->iio_device, iio_device_);
+
+  sample_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&SamplesHandler::RemoveClientOnThread,
+                                weak_factory_.GetWeakPtr(), client_data));
+}
+void SamplesHandler::UpdateFrequency(
+    ClientData* client_data,
+    double frequency,
+    cros::mojom::SensorDevice::SetFrequencyCallback callback) {
+  DCHECK_EQ(client_data->iio_device, iio_device_);
+
+  sample_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&SamplesHandler::UpdateFrequencyOnThread,
+                                weak_factory_.GetWeakPtr(), client_data,
+                                frequency, std::move(callback)));
+}
+void SamplesHandler::UpdateChannelsEnabled(
+    ClientData* client_data,
+    const std::vector<int32_t>& iio_chn_indices,
+    bool en,
+    cros::mojom::SensorDevice::SetChannelsEnabledCallback callback) {
+  DCHECK_EQ(client_data->iio_device, iio_device_);
+
+  sample_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(&SamplesHandler::UpdateChannelsEnabledOnThread,
+                     weak_factory_.GetWeakPtr(), client_data,
+                     std::move(iio_chn_indices), en, std::move(callback)));
+}
+
+void SamplesHandler::GetChannelsEnabled(
+    ClientData* client_data,
+    const std::vector<int32_t>& iio_chn_indices,
+    cros::mojom::SensorDevice::GetChannelsEnabledCallback callback) {
+  DCHECK_EQ(client_data->iio_device, iio_device_);
+
+  sample_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(&SamplesHandler::GetChannelsEnabledOnThread,
+                     weak_factory_.GetWeakPtr(), client_data,
+                     std::move(iio_chn_indices), std::move(callback)));
+}
+
+SamplesHandler::SamplesHandler(
+    scoped_refptr<base::SequencedTaskRunner> ipc_task_runner,
+    scoped_refptr<base::SingleThreadTaskRunner> sample_task_runner,
+    libmems::IioDevice* iio_device,
+    double min_freq,
+    double max_freq,
+    OnSampleUpdatedCallback on_sample_updated_callback,
+    OnErrorOccurredCallback on_error_occurred_callback)
+    : ipc_task_runner_(std::move(ipc_task_runner)),
+      sample_task_runner_(std::move(sample_task_runner)),
+      use_fifo_(true),
+      iio_device_(iio_device),
+      trigger_device_(nullptr),
+      dev_min_frequency_(min_freq),
+      dev_max_frequency_(max_freq),
+      on_sample_updated_callback_(std::move(on_sample_updated_callback)),
+      on_error_occurred_callback_(std::move(on_error_occurred_callback)) {
+  DCHECK_GE(dev_max_frequency_, dev_min_frequency_);
+}
+
+SamplesHandler::SamplesHandler(
+    scoped_refptr<base::SequencedTaskRunner> ipc_task_runner,
+    scoped_refptr<base::SingleThreadTaskRunner> sample_task_runner,
+    libmems::IioDevice* iio_device,
+    libmems::IioDevice* trigger_device,
+    double min_freq,
+    double max_freq,
+    OnSampleUpdatedCallback on_sample_updated_callback,
+    OnErrorOccurredCallback on_error_occurred_callback)
+    : ipc_task_runner_(std::move(ipc_task_runner)),
+      sample_task_runner_(std::move(sample_task_runner)),
+      use_fifo_(false),
+      iio_device_(iio_device),
+      trigger_device_(trigger_device),
+      dev_min_frequency_(min_freq),
+      dev_max_frequency_(max_freq),
+      on_sample_updated_callback_(std::move(on_sample_updated_callback)),
+      on_error_occurred_callback_(std::move(on_error_occurred_callback)) {
+  DCHECK_GE(dev_max_frequency_, dev_min_frequency_);
+}
+
+void SamplesHandler::SetSampleWatcherOnThread() {
+  DCHECK(sample_task_runner_->BelongsToCurrentThread());
+
+  auto fd = iio_device_->GetBufferFd();
+  if (!fd.has_value()) {
+    LOGF(ERROR) << "Failed to get fd";
+    for (auto client : clients_map_) {
+      ipc_task_runner_->PostTask(
+          FROM_HERE,
+          base::BindOnce(on_error_occurred_callback_, client.first->id,
+                         cros::mojom::ObserverErrorType::GET_FD_FAILED));
+    }
+
+    return;
+  }
+
+  watcher_ = base::FileDescriptorWatcher::WatchReadable(
+      fd.value(),
+      base::BindRepeating(&SamplesHandler::OnSampleAvailableWithoutBlocking,
+                          weak_factory_.GetWeakPtr()));
+}
+
+void SamplesHandler::StopSampleWatcherOnThread() {
+  DCHECK(sample_task_runner_->BelongsToCurrentThread());
+
+  watcher_.reset();
+}
+
+void SamplesHandler::AddActiveClientOnThread(ClientData* client_data) {
+  DCHECK(sample_task_runner_->BelongsToCurrentThread());
+  DCHECK_EQ(client_data->iio_device, iio_device_);
+  DCHECK_GE(client_data->frequency, kFrequencyEpsilon);
+  DCHECK(!client_data->enabled_chn_indices.empty());
+  DCHECK(inactive_clients_.find(client_data) == inactive_clients_.end());
+  DCHECK(clients_map_.find(client_data) == clients_map_.end());
+
+  clients_map_.emplace(client_data, SampleData{});
+  clients_map_[client_data].sample_index = samples_cnt_;
+
+  if (!watcher_.get())
+    SetSampleWatcherOnThread();
+
+  SetTimeoutTaskOnThread(client_data);
+
+  if (AddFrequencyOnThread(client_data->frequency))
+    return;
+
+  ipc_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(on_error_occurred_callback_, client_data->id,
+                     cros::mojom::ObserverErrorType::SET_FREQUENCY_IO_FAILED));
+}
+void SamplesHandler::AddClientOnThread(ClientData* client_data) {
+  DCHECK(sample_task_runner_->BelongsToCurrentThread());
+  DCHECK_EQ(client_data->iio_device, iio_device_);
+
+  if (inactive_clients_.find(client_data) != inactive_clients_.end() ||
+      clients_map_.find(client_data) != clients_map_.end()) {
+    // Shouldn't happen. Users should check observer exists or not to know
+    // whether the client is already added.
+    LOGF(ERROR) << "Failed to AddClient: Already added";
+    ipc_task_runner_->PostTask(
+        FROM_HERE,
+        base::BindOnce(on_error_occurred_callback_, client_data->id,
+                       cros::mojom::ObserverErrorType::ALREADY_STARTED));
+    return;
+  }
+
+  bool active = true;
+
+  client_data->frequency = FixFrequency(client_data->frequency);
+  if (client_data->frequency == 0.0) {
+    LOGF(ERROR) << "Added an inactive client: Invalid frequency.";
+    ipc_task_runner_->PostTask(
+        FROM_HERE,
+        base::BindOnce(on_error_occurred_callback_, client_data->id,
+                       cros::mojom::ObserverErrorType::FREQUENCY_INVALID));
+    active = false;
+  }
+  if (client_data->enabled_chn_indices.empty()) {
+    LOGF(ERROR) << "Added an inactive client: No enabled channels.";
+    ipc_task_runner_->PostTask(
+        FROM_HERE,
+        base::BindOnce(on_error_occurred_callback_, client_data->id,
+                       cros::mojom::ObserverErrorType::NO_ENABLED_CHANNELS));
+    active = false;
+  }
+
+  if (!active) {
+    inactive_clients_.emplace(client_data);
+    return;
+  }
+
+  AddActiveClientOnThread(client_data);
+}
+
+void SamplesHandler::RemoveActiveClientOnThread(ClientData* client_data,
+                                                double orig_freq) {
+  DCHECK(sample_task_runner_->BelongsToCurrentThread());
+  DCHECK_EQ(client_data->iio_device, iio_device_);
+  DCHECK_GE(orig_freq, kFrequencyEpsilon);
+  DCHECK(!client_data->enabled_chn_indices.empty());
+  DCHECK(clients_map_.find(client_data) != clients_map_.end());
+
+  clients_map_.erase(client_data);
+  if (clients_map_.empty())
+    StopSampleWatcherOnThread();
+
+  if (RemoveFrequencyOnThread(orig_freq))
+    return;
+
+  ipc_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(on_error_occurred_callback_, client_data->id,
+                     cros::mojom::ObserverErrorType::SET_FREQUENCY_IO_FAILED));
+}
+void SamplesHandler::RemoveClientOnThread(ClientData* client_data) {
+  DCHECK(sample_task_runner_->BelongsToCurrentThread());
+  DCHECK_EQ(client_data->iio_device, iio_device_);
+
+  auto it = inactive_clients_.find(client_data);
+  if (it != inactive_clients_.end()) {
+    inactive_clients_.erase(it);
+    return;
+  }
+
+  if (clients_map_.find(client_data) == clients_map_.end()) {
+    LOGF(ERROR) << "Failed to RemoveClient: Client not found";
+    return;
+  }
+
+  RemoveActiveClientOnThread(client_data, client_data->frequency);
+}
+
+double SamplesHandler::FixFrequency(double frequency) {
+  if (frequency < kFrequencyEpsilon)
+    return 0.0;
+
+  if (frequency < dev_min_frequency_)
+    return dev_min_frequency_;
+
+  if (frequency > dev_max_frequency_)
+    return dev_max_frequency_;
+
+  return frequency;
+}
+
+void SamplesHandler::UpdateFrequencyOnThread(
+    ClientData* client_data,
+    double frequency,
+    cros::mojom::SensorDevice::SetFrequencyCallback callback) {
+  DCHECK(sample_task_runner_->BelongsToCurrentThread());
+  DCHECK_EQ(client_data->iio_device, iio_device_);
+
+  frequency = FixFrequency(frequency);
+  double orig_freq = client_data->frequency;
+  client_data->frequency = frequency;
+  ipc_task_runner_->PostTask(FROM_HERE,
+                             base::BindOnce(std::move(callback), frequency));
+
+  auto it = inactive_clients_.find(client_data);
+  if (it != inactive_clients_.end()) {
+    if (client_data->frequency > 0.0 &&
+        !client_data->enabled_chn_indices.empty()) {
+      // The client is now active.
+      inactive_clients_.erase(it);
+      AddActiveClientOnThread(client_data);
+    }
+
+    return;
+  }
+
+  if (clients_map_.find(client_data) == clients_map_.end())
+    return;
+
+  if (client_data->frequency == 0.0) {
+    // The client is now inactive
+    RemoveActiveClientOnThread(client_data, orig_freq);
+    inactive_clients_.emplace(client_data);
+
+    return;
+  }
+
+  // The client remains active
+  if (AddFrequencyOnThread(client_data->frequency) &&
+      RemoveFrequencyOnThread(orig_freq))
+    return;
+
+  // Failed to set device frequency
+  ipc_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(on_error_occurred_callback_, client_data->id,
+                     cros::mojom::ObserverErrorType::SET_FREQUENCY_IO_FAILED));
+}
+
+bool SamplesHandler::AddFrequencyOnThread(double frequency) {
+  DCHECK(sample_task_runner_->BelongsToCurrentThread());
+
+  frequencies_.emplace(frequency);
+  double max_freq = *frequencies_.rbegin();
+  DCHECK_GE(max_freq, requested_frequency_);
+  return UpdateRequestedFrequencyOnThread(max_freq);
+}
+bool SamplesHandler::RemoveFrequencyOnThread(double frequency) {
+  DCHECK(sample_task_runner_->BelongsToCurrentThread());
+
+  auto it = frequencies_.find(frequency);
+  DCHECK(it != frequencies_.end());
+  frequencies_.erase(it);
+  auto r_it = frequencies_.rbegin();
+  double max_freq = (r_it == frequencies_.rend()) ? 0.0 : *r_it;
+  DCHECK_LE(max_freq, requested_frequency_);
+  return UpdateRequestedFrequencyOnThread(max_freq);
+}
+
+bool SamplesHandler::UpdateRequestedFrequencyOnThread(double frequency) {
+  DCHECK(sample_task_runner_->BelongsToCurrentThread());
+
+  if (frequency == requested_frequency_)
+    return true;
+
+  requested_frequency_ = frequency;
+  if (!iio_device_->WriteDoubleAttribute(libmems::kSamplingFrequencyAttr,
+                                         frequency)) {
+    LOGF(ERROR) << "Failed to set frequency";
+    if (use_fifo_)  // ignore this error if no fifo
+      return false;
+  }
+
+  auto freq_opt =
+      iio_device_->ReadDoubleAttribute(libmems::kSamplingFrequencyAttr);
+  if (!freq_opt.has_value()) {
+    LOGF(ERROR) << "Failed to get frequency";
+    return false;
+  }
+  dev_frequency_ = freq_opt.value();
+
+  if (use_fifo_) {
+    if (dev_frequency_ < kFrequencyEpsilon)
+      return true;
+
+    if (!iio_device_->WriteDoubleAttribute(libmems::kHWFifoTimeoutAttr,
+                                           1.0 / dev_frequency_)) {
+      LOGF(ERROR) << "Failed to set fifo timeout";
+      return false;
+    }
+
+    return true;
+  }
+
+  // no fifo
+  DCHECK(trigger_device_);
+
+  if (!trigger_device_->WriteDoubleAttribute(libmems::kSamplingFrequencyAttr,
+                                             frequency)) {
+    LOGF(ERROR) << "Failed to set trigger's frequency";
+    return false;
+  }
+
+  return true;
+}
+
+void SamplesHandler::UpdateChannelsEnabledOnThread(
+    ClientData* client_data,
+    const std::vector<int32_t>& iio_chn_indices,
+    bool en,
+    cros::mojom::SensorDevice::SetChannelsEnabledCallback callback) {
+  DCHECK(sample_task_runner_->BelongsToCurrentThread());
+  DCHECK_EQ(client_data->iio_device, iio_device_);
+
+  std::vector<int32_t> failed_indices;
+
+  if (en) {
+    for (int32_t chn_index : iio_chn_indices) {
+      auto chn = iio_device_->GetChannel(chn_index);
+      if (!chn || !chn->IsEnabled()) {
+        LOG(ERROR) << "Failed to enable chn with index: " << chn_index;
+        failed_indices.push_back(chn_index);
+        continue;
+      }
+
+      client_data->enabled_chn_indices.emplace(chn_index);
+    }
+  } else {
+    for (int32_t chn_index : iio_chn_indices)
+      client_data->enabled_chn_indices.erase(chn_index);
+  }
+
+  ipc_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(std::move(callback), std::move(failed_indices)));
+
+  auto it = inactive_clients_.find(client_data);
+  if (it != inactive_clients_.end()) {
+    if (client_data->frequency > 0.0 &&
+        !client_data->enabled_chn_indices.empty()) {
+      // The client is now active.
+      inactive_clients_.erase(it);
+      AddActiveClientOnThread(client_data);
+    }
+
+    return;
+  }
+
+  if (clients_map_.find(client_data) == clients_map_.end())
+    return;
+
+  if (!client_data->enabled_chn_indices.empty()) {
+    // The client remains active
+    return;
+  }
+
+  RemoveActiveClientOnThread(client_data, client_data->frequency);
+  inactive_clients_.emplace(client_data);
+}
+
+void SamplesHandler::GetChannelsEnabledOnThread(
+    ClientData* client_data,
+    const std::vector<int32_t>& iio_chn_indices,
+    cros::mojom::SensorDevice::GetChannelsEnabledCallback callback) {
+  DCHECK(sample_task_runner_->BelongsToCurrentThread());
+  DCHECK_EQ(client_data->iio_device, iio_device_);
+
+  std::vector<bool> enabled;
+
+  for (int32_t chn_index : iio_chn_indices) {
+    enabled.push_back(client_data->enabled_chn_indices.find(chn_index) !=
+                      client_data->enabled_chn_indices.end());
+  }
+
+  ipc_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(std::move(callback), std::move(enabled)));
+}
+
+void SamplesHandler::SetTimeoutTaskOnThread(ClientData* client_data) {
+  DCHECK(sample_task_runner_->BelongsToCurrentThread());
+
+  if (client_data->timeout == 0)
+    return;
+
+  sample_task_runner_->PostDelayedTask(
+      FROM_HERE,
+      base::BindOnce(&SamplesHandler::SampleTimeout, weak_factory_.GetWeakPtr(),
+                     client_data, clients_map_[client_data].sample_index),
+      base::TimeDelta::FromMilliseconds(client_data->timeout));
+}
+void SamplesHandler::SampleTimeout(ClientData* client_data,
+                                   uint64_t sample_index) {
+  auto it = clients_map_.find(client_data);
+  if (it == clients_map_.end() || it->second.sample_index != sample_index)
+    return;
+
+  ipc_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(on_error_occurred_callback_, client_data->id,
+                                cros::mojom::ObserverErrorType::READ_TIMEOUT));
+}
+
+void SamplesHandler::OnSampleAvailableWithoutBlocking() {
+  DCHECK(sample_task_runner_->BelongsToCurrentThread());
+  DCHECK(num_read_failed_logs_ == 0 || num_read_failed_logs_recovery_ == 0);
+
+  auto sample = iio_device_->ReadSample();
+  if (!sample) {
+    AddReadFailedLog();
+    for (auto client : clients_map_) {
+      ipc_task_runner_->PostTask(
+          FROM_HERE,
+          base::BindOnce(on_error_occurred_callback_, client.first->id,
+                         cros::mojom::ObserverErrorType::READ_FAILED));
+    }
+
+    return;
+  }
+
+  if (num_read_failed_logs_ == 0) {
+    if (num_read_failed_logs_recovery_ > 0 &&
+        ++num_read_failed_logs_recovery_ >= kNumReadFailedLogsRecovery) {
+      LOGF(INFO) << "Resuming error logs";
+      num_read_failed_logs_recovery_ = 0;
+    }
+  } else {
+    --num_read_failed_logs_;
+  }
+
+  for (auto& client : clients_map_) {
+    DCHECK(client.first->frequency >= kFrequencyEpsilon);
+    DCHECK(!client.first->enabled_chn_indices.empty());
+
+    int step =
+        std::max(1, static_cast<int>(dev_frequency_ / client.first->frequency));
+
+    if (client.second.sample_index + step - 1 <= samples_cnt_) {
+      // Send a sample to the client
+      int64_t size = samples_cnt_ - client.second.sample_index + 1;
+      DCHECK_GE(size, 1);
+
+      libmems::IioDevice::IioSample client_sample;
+      for (int32_t chn_index : client.first->enabled_chn_indices) {
+        if (sample->find(chn_index) == sample->end()) {
+          LOG(ERROR) << "Missing chn: " << chn_index << " in sample";
+          continue;
+        }
+
+        client_sample[chn_index] = sample.value()[chn_index];
+      }
+
+      client.second.sample_index = samples_cnt_ + 1;
+
+      ipc_task_runner_->PostTask(
+          FROM_HERE,
+          base::BindOnce(on_sample_updated_callback_, client.first->id,
+                         std::move(client_sample)));
+      SetTimeoutTaskOnThread(client.first);
+    }
+  }
+
+  ++samples_cnt_;
+}
+
+void SamplesHandler::AddReadFailedLog() {
+  if (num_read_failed_logs_recovery_ > 0) {
+    if (++num_read_failed_logs_recovery_ >= kNumReadFailedLogsRecovery) {
+      LOGF(INFO) << "Resuming error logs";
+      num_read_failed_logs_recovery_ = 0;
+    }
+
+    return;
+  }
+
+  if (++num_read_failed_logs_ >= kNumReadFailedLogsBeforeGivingUp) {
+    LOGF(ERROR) << "Too many read failed logs: Skipping logs until "
+                << kNumReadFailedLogsRecovery << " reads are done";
+
+    num_read_failed_logs_ = 0;
+    num_read_failed_logs_recovery_ = 1;
+    return;
+  }
+
+  LOGF(ERROR) << "Failed to read a sample";
+}
+
+}  // namespace iioservice
diff --git a/iioservice/daemon/samples_handler.h b/iioservice/daemon/samples_handler.h
new file mode 100644
index 0000000..02b3649
--- /dev/null
+++ b/iioservice/daemon/samples_handler.h
@@ -0,0 +1,197 @@
+// Copyright 2020 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 IIOSERVICE_DAEMON_SAMPLES_HANDLER_H_
+#define IIOSERVICE_DAEMON_SAMPLES_HANDLER_H_
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/files/file_descriptor_watcher_posix.h>
+#include <base/memory/weak_ptr.h>
+#include <base/sequenced_task_runner.h>
+#include <base/single_thread_task_runner.h>
+#include <mojo/public/cpp/bindings/receiver_set.h>
+
+#include <libmems/iio_context.h>
+#include <libmems/iio_device.h>
+
+#include "iioservice/daemon/common_types.h"
+#include "mojo/sensor.mojom.h"
+
+namespace iioservice {
+
+// A SamplesHandler is a handler of an IioDevice's samples. The user should add,
+// remove, and update clients with frequencies and channels, and this handler
+// will dispatch samples with clients' desired frequencies and channels when
+// samples are received from the kernel.
+// The user can provide the same |sample_task_runner| to all SamplesHandler as
+// there is no blocking function in SamplesHandler and the thread would not be
+// heavily loaded.
+class SamplesHandler {
+ public:
+  static void SamplesHandlerDeleter(SamplesHandler* handler);
+  using ScopedSamplesHandler =
+      std::unique_ptr<SamplesHandler, decltype(&SamplesHandlerDeleter)>;
+
+  using OnSampleUpdatedCallback = base::RepeatingCallback<void(
+      mojo::ReceiverId, libmems::IioDevice::IioSample)>;
+  using OnErrorOccurredCallback = base::RepeatingCallback<void(
+      mojo::ReceiverId, cros::mojom::ObserverErrorType)>;
+
+  static bool GetDevMinMaxFrequency(libmems::IioDevice* iio_device,
+                                    double* min_freq,
+                                    double* max_freq);
+
+  // use fifo
+  static ScopedSamplesHandler CreateWithFifo(
+      scoped_refptr<base::SequencedTaskRunner> ipc_task_runner,
+      scoped_refptr<base::SingleThreadTaskRunner> sample_task_runner,
+      libmems::IioDevice* iio_device,
+      OnSampleUpdatedCallback on_sample_updated_callback,
+      OnErrorOccurredCallback on_error_occurred_callback);
+  // no fifo
+  static ScopedSamplesHandler CreateWithoutFifo(
+      scoped_refptr<base::SequencedTaskRunner> ipc_task_runner,
+      scoped_refptr<base::SingleThreadTaskRunner> sample_task_runner,
+      libmems::IioContext* iio_context,
+      libmems::IioDevice* iio_device,
+      OnSampleUpdatedCallback on_sample_updated_callback,
+      OnErrorOccurredCallback on_error_occurred_callback);
+
+  virtual ~SamplesHandler();
+
+  // It's the user's responsibility to maintain |client_data| before being
+  // removed or this class being destructed.
+  // |client_data.iio_device| should be the same as |iio_device_|.
+  void AddClient(ClientData* client_data);
+  void RemoveClient(ClientData* client_data);
+
+  void UpdateFrequency(
+      ClientData* client_data,
+      double frequency,
+      cros::mojom::SensorDevice::SetFrequencyCallback callback);
+  void UpdateChannelsEnabled(
+      ClientData* client_data,
+      const std::vector<int32_t>& iio_chn_indices,
+      bool en,
+      cros::mojom::SensorDevice::SetChannelsEnabledCallback callback);
+
+  void GetChannelsEnabled(
+      ClientData* client_data,
+      const std::vector<int32_t>& iio_chn_indices,
+      cros::mojom::SensorDevice::GetChannelsEnabledCallback callback);
+
+ protected:
+  struct SampleData {
+    // The starting index of the next sample.
+    uint64_t sample_index = 0;
+  };
+
+  static const uint32_t kNumReadFailedLogsBeforeGivingUp = 100;
+  static const uint32_t kNumReadFailedLogsRecovery = 10000;
+
+  // use fifo
+  SamplesHandler(scoped_refptr<base::SequencedTaskRunner> ipc_task_runner,
+                 scoped_refptr<base::SingleThreadTaskRunner> sample_task_runner,
+                 libmems::IioDevice* iio_device,
+                 double min_freq,
+                 double max_freq,
+                 OnSampleUpdatedCallback on_sample_updated_callback,
+                 OnErrorOccurredCallback on_error_occurred_callback);
+  // no fifo
+  SamplesHandler(scoped_refptr<base::SequencedTaskRunner> ipc_task_runner,
+                 scoped_refptr<base::SingleThreadTaskRunner> sample_task_runner,
+                 libmems::IioDevice* iio_device,
+                 libmems::IioDevice* trigger_device,
+                 double in_freq,
+                 double ax_freq,
+                 OnSampleUpdatedCallback on_sample_updated_callback,
+                 OnErrorOccurredCallback on_error_occurred_callback);
+
+  void SetSampleWatcherOnThread();
+  void StopSampleWatcherOnThread();
+
+  double FixFrequency(double frequency);
+
+  void AddActiveClientOnThread(ClientData* client_data);
+  void AddClientOnThread(ClientData* client_data);
+
+  void RemoveActiveClientOnThread(ClientData* client_data, double orig_freq);
+  void RemoveClientOnThread(ClientData* client_data);
+
+  void UpdateFrequencyOnThread(
+      ClientData* client_data,
+      double frequency,
+      cros::mojom::SensorDevice::SetFrequencyCallback callback);
+
+  bool AddFrequencyOnThread(double frequency);
+  bool RemoveFrequencyOnThread(double frequency);
+
+  bool UpdateRequestedFrequencyOnThread(double frequency);
+
+  void UpdateChannelsEnabledOnThread(
+      ClientData* client_data,
+      const std::vector<int32_t>& iio_chn_indices,
+      bool en,
+      cros::mojom::SensorDevice::SetChannelsEnabledCallback callback);
+
+  void GetChannelsEnabledOnThread(
+      ClientData* client_data,
+      const std::vector<int32_t>& iio_chn_indices,
+      cros::mojom::SensorDevice::GetChannelsEnabledCallback callback);
+
+  void SetTimeoutTaskOnThread(ClientData* client_data);
+  void SampleTimeout(ClientData* client_data, uint64_t sample_index);
+
+  void OnSampleAvailableWithoutBlocking();
+  void AddReadFailedLog();
+
+  scoped_refptr<base::SequencedTaskRunner> ipc_task_runner_;
+  scoped_refptr<base::SingleThreadTaskRunner> sample_task_runner_;
+  bool use_fifo_ = true;
+  libmems::IioDevice* iio_device_;
+  libmems::IioDevice* trigger_device_ = nullptr;
+
+  // Clients that either have invalid frequency or no enabled channels.
+  std::set<ClientData*> inactive_clients_;
+  // First is the active client, second is its data.
+  std::map<ClientData*, SampleData> clients_map_;
+
+  // Requested frequencies from clients.
+  std::multiset<double> frequencies_;
+  // Max frequency among |frequencies_|.
+  double requested_frequency_ = 0.0;
+  // The real device frequency. Given the kernel is requesting upsampling,
+  // |dev_frequency_| >= |requested_frequency_|.
+  double dev_frequency_ = 0.0;
+
+  double dev_min_frequency_ = 0.0;
+  double dev_max_frequency_ = 0.0;
+
+  // The next coming sample's id. 0-based.
+  // Shouldn't overflow as timestamp will overflow first.
+  uint64_t samples_cnt_ = 0;
+
+  uint32_t num_read_failed_logs_ = 0;
+  uint32_t num_read_failed_logs_recovery_ = 0;
+
+  base::RepeatingCallback<void(mojo::ReceiverId, libmems::IioDevice::IioSample)>
+      on_sample_updated_callback_;
+  base::RepeatingCallback<void(mojo::ReceiverId,
+                               cros::mojom::ObserverErrorType)>
+      on_error_occurred_callback_;
+
+  std::unique_ptr<base::FileDescriptorWatcher::Controller> watcher_;
+
+ private:
+  base::WeakPtrFactory<SamplesHandler> weak_factory_{this};
+};
+
+}  // namespace iioservice
+
+#endif  // IIOSERVICE_DAEMON_SAMPLES_HANDLER_H_
diff --git a/iioservice/daemon/samples_handler_test.cc b/iioservice/daemon/samples_handler_test.cc
new file mode 100644
index 0000000..58f5834
--- /dev/null
+++ b/iioservice/daemon/samples_handler_test.cc
@@ -0,0 +1,500 @@
+// Copyright 2020 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 <gtest/gtest.h>
+
+#include <algorithm>
+#include <limits>
+#include <queue>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include <base/rand_util.h>
+#include <base/threading/thread.h>
+#include <mojo/core/embedder/scoped_ipc_support.h>
+#include <mojo/public/cpp/bindings/receiver_set.h>
+
+#include <libmems/common_types.h>
+#include <libmems/test_fakes.h>
+
+#include "iioservice/daemon/samples_handler.h"
+#include "iioservice/daemon/test_fakes.h"
+#include "iioservice/include/constants.h"
+#include "mojo/sensor.mojom.h"
+
+namespace iioservice {
+
+namespace {
+
+constexpr double kMinFrequency = 1.25;
+constexpr double kMaxFrequency = 40.0;
+
+constexpr int kFakeTimeoutInMilliseconds = 10;
+constexpr double kFooFrequency = 20.0;
+constexpr int kNumFailures = 10;
+
+double FixFrequency(double frequency) {
+  if (frequency < kFrequencyEpsilon)
+    return 0.0;
+
+  if (frequency < kMinFrequency)
+    return kMinFrequency;
+
+  if (frequency > kMaxFrequency)
+    return kMaxFrequency;
+
+  return frequency;
+}
+
+class SamplesHandlerTestOnMinMaxFrequencyWithParam
+    : public ::testing::TestWithParam<
+          std::tuple<std::string, bool, double, double>> {
+ protected:
+  void SetUp() override {
+    device_ = std::make_unique<libmems::fakes::FakeIioDevice>(
+        nullptr, fakes::kAccelDeviceName, fakes::kAccelDeviceId);
+
+    EXPECT_TRUE(device_->WriteStringAttribute(kSamplingFrequencyAvailable,
+                                              std::get<0>(GetParam())));
+
+    result_ = SamplesHandler::GetDevMinMaxFrequency(device_.get(), &min_freq_,
+                                                    &max_freq_);
+  }
+
+  std::unique_ptr<libmems::fakes::FakeIioDevice> device_;
+  bool result_;
+  double min_freq_ = -1;
+  double max_freq_ = -1;
+};
+
+TEST_P(SamplesHandlerTestOnMinMaxFrequencyWithParam, ParseMinMaxFrequency) {
+  EXPECT_EQ(result_, std::get<1>(GetParam()));
+  if (!result_)
+    return;
+
+  EXPECT_EQ(min_freq_, std::get<2>(GetParam()));
+  EXPECT_EQ(max_freq_, std::get<3>(GetParam()));
+}
+
+INSTANTIATE_TEST_CASE_P(
+    SamplesHandlerTestOnMinMaxFrequencyWithParamRun,
+    SamplesHandlerTestOnMinMaxFrequencyWithParam,
+    ::testing::Values(
+        std::make_tuple("  ", false, 0.0, 0.0),
+        std::make_tuple("  0abc  ", false, 0.0, 0.0),
+        std::make_tuple(" 0.0001 ", false, 0.0, 0.0),
+        std::make_tuple("0.5  ", true, 0.5, 0.5),
+        std::make_tuple("  1000  ", true, 1000.0, 1000.0),
+        std::make_tuple("1.0 100.0 ", true, 1.0, 100.0),
+        std::make_tuple("1.0 10.0 100.0 ", true, 1.0, 100.0),
+        std::make_tuple("1.0 a b c 100.0 ", true, 1.0, 100.0),
+        std::make_tuple("0.0 a b c 100.0 ", false, 0.0, 0.0),
+        std::make_tuple("0.0 1.0 100.0 ", true, 1.0, 100.0),
+        std::make_tuple("0.0 2.0 a b c 100.0 ", true, 2.0, 100.0)));
+
+class SamplesHandlerTestBase {
+ protected:
+  void OnSampleUpdatedCallback(mojo::ReceiverId id,
+                               libmems::IioDevice::IioSample sample) {
+    CHECK(ipc_thread_->task_runner()->BelongsToCurrentThread());
+    CHECK_LT(id, observers_.size());
+
+    observers_[id]->OnSampleUpdated(std::move(sample));
+  }
+  void OnErrorOccurredCallback(mojo::ReceiverId id,
+                               cros::mojom::ObserverErrorType type) {
+    CHECK(ipc_thread_->task_runner()->BelongsToCurrentThread());
+    if (id >= observers_.size()) {
+      CHECK_EQ(type, cros::mojom::ObserverErrorType::FREQUENCY_INVALID);
+      return;
+    }
+
+    observers_[id]->OnErrorOccurred(type);
+  }
+
+  void SetUpBase() {
+    device_ = std::make_unique<libmems::fakes::FakeIioDevice>(
+        nullptr, fakes::kAccelDeviceName, fakes::kAccelDeviceId);
+    EXPECT_TRUE(device_->WriteStringAttribute(
+        kSamplingFrequencyAvailable, fakes::kFakeSamplingFrequencyAvailable));
+
+    for (int i = 0; i < base::size(libmems::fakes::kFakeAccelChns); ++i) {
+      device_->AddChannel(std::make_unique<libmems::fakes::FakeIioChannel>(
+          libmems::fakes::kFakeAccelChns[i], true));
+    }
+
+    EXPECT_TRUE(
+        device_->WriteDoubleAttribute(libmems::kSamplingFrequencyAttr, 0.0));
+
+    ipc_thread_ = std::make_unique<base::Thread>("IPCThread");
+    EXPECT_TRUE(ipc_thread_->StartWithOptions(
+        base::Thread::Options(base::MessageLoop::TYPE_IO, 0)));
+
+    sample_thread_ = std::make_unique<base::Thread>("SampleThread");
+    EXPECT_TRUE(sample_thread_->StartWithOptions(
+        base::Thread::Options(base::MessageLoop::TYPE_IO, 0)));
+
+    handler_ = fakes::FakeSamplesHandler::CreateWithFifo(
+        ipc_thread_->task_runner(), sample_thread_->task_runner(),
+        device_.get(),
+        base::BindRepeating(&SamplesHandlerTestBase::OnSampleUpdatedCallback,
+                            base::Unretained(this)),
+        base::BindRepeating(&SamplesHandlerTestBase::OnErrorOccurredCallback,
+                            base::Unretained(this)));
+    EXPECT_TRUE(handler_);
+  }
+
+  void TearDownBase() {
+    handler_.reset();
+    sample_thread_->Stop();
+
+    EXPECT_EQ(device_->ReadDoubleAttribute(libmems::kSamplingFrequencyAttr)
+                  .value_or(-1),
+              0.0);
+
+    observers_.clear();
+    ipc_thread_->Stop();
+  }
+
+  std::unique_ptr<libmems::fakes::FakeIioDevice> device_;
+
+  std::unique_ptr<base::Thread> ipc_thread_;
+  std::unique_ptr<base::Thread> sample_thread_;
+
+  fakes::FakeSamplesHandler::ScopedFakeSamplesHandler handler_ = {
+      nullptr, SamplesHandler::SamplesHandlerDeleter};
+  std::vector<ClientData> clients_data_;
+  std::vector<fakes::FakeSamplesObserver::ScopedFakeSamplesObserver> observers_;
+};
+
+class SamplesHandlerTest : public ::testing::Test,
+                           public SamplesHandlerTestBase {
+ protected:
+  void SetUp() override { SetUpBase(); }
+
+  void TearDown() override { TearDownBase(); }
+};
+
+// Add clients with only timestamp channel enabled, enable all other channels,
+// and disable all channels except for accel_x. Enabled channels are checked
+// after each modification.
+TEST_F(SamplesHandlerTest, UpdateChannelsEnabled) {
+  // No samples in this test
+  device_->SetPauseCallbackAtKthSamples(0, base::BindOnce([]() {}));
+
+  std::vector<double> freqs = {0.0, 10.0};
+  clients_data_.resize(freqs.size());
+  for (size_t i = 0; i < freqs.size(); ++i) {
+    ClientData& client_data = clients_data_[i];
+
+    client_data.id = i;
+    client_data.iio_device = device_.get();
+    client_data.enabled_chn_indices.emplace(3);  // timestamp
+    client_data.timeout = 0;
+    client_data.frequency = freqs[i];
+
+    handler_->AddClient(&client_data);
+
+    handler_->UpdateChannelsEnabled(
+        &client_data, std::vector<int32_t>{1, 0, 2}, true,
+        base::BindOnce([](const std::vector<int32_t>& failed_indices) {
+          EXPECT_TRUE(failed_indices.empty());
+        }));
+
+    handler_->GetChannelsEnabled(
+        &client_data, std::vector<int32_t>{1, 0, 2, 3},
+        base::BindOnce([](const std::vector<bool>& enabled) {
+          EXPECT_EQ(enabled.size(), 4);
+          EXPECT_TRUE(enabled[0] && enabled[1] && enabled[2] && enabled[3]);
+        }));
+
+    handler_->UpdateChannelsEnabled(
+        &client_data, std::vector<int32_t>{2, 1, 3}, false,
+        base::BindOnce(
+            [](ClientData* client_data, int32_t chn_index,
+               const std::vector<int32_t>& failed_indices) {
+              EXPECT_EQ(client_data->enabled_chn_indices.size(), 1);
+              EXPECT_EQ(*client_data->enabled_chn_indices.begin(), chn_index);
+              EXPECT_TRUE(failed_indices.empty());
+            },
+            &client_data, 0));
+  }
+
+  // Remove clients
+  for (auto& client_data : clients_data_)
+    handler_->RemoveClient(&client_data);
+}
+
+TEST_F(SamplesHandlerTest, BadDeviceWithNoSamples) {
+  base::MessageLoopForIO message_loop;
+
+  device_->DisableFd();
+
+  std::vector<double> freqs = {5.0, 0.0, 10.0, 100.0};
+  clients_data_.resize(freqs.size());
+  std::vector<std::unique_ptr<base::RunLoop>> loops;
+
+  std::multiset<std::pair<int, cros::mojom::ObserverErrorType>> rt_failures;
+  rt_failures.insert(
+      std::make_pair(0, cros::mojom::ObserverErrorType::READ_TIMEOUT));
+
+  size_t fd_failed_cnt = 0;
+  for (size_t i = 0; i < freqs.size(); ++i) {
+    if (freqs[i] > 0.0)
+      ++fd_failed_cnt;
+  }
+
+  for (size_t i = 0; i < freqs.size(); ++i) {
+    ClientData& client_data = clients_data_[i];
+    std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
+
+    client_data.id = i;
+    client_data.iio_device = device_.get();
+    client_data.enabled_chn_indices.emplace(3);  // timestamp
+    client_data.frequency = freqs[i];
+    client_data.timeout = kFakeTimeoutInMilliseconds;
+
+    auto failures = rt_failures;
+    if (freqs[i] == 0.0) {
+      failures.insert(
+          std::make_pair(0, cros::mojom::ObserverErrorType::FREQUENCY_INVALID));
+    } else {
+      for (size_t j = 0; j < fd_failed_cnt; ++j) {
+        failures.insert(
+            std::make_pair(0, cros::mojom::ObserverErrorType::GET_FD_FAILED));
+      }
+      --fd_failed_cnt;
+    }
+
+    // Don't care about frequencies as there would be no samples.
+    auto fake_observer = fakes::FakeSamplesObserver::Create(
+        ipc_thread_->task_runner(), run_loop->QuitClosure(), device_.get(),
+        std::move(failures), kFooFrequency, kFooFrequency, kFooFrequency,
+        kFooFrequency);
+
+    handler_->AddClient(&client_data);
+
+    loops.emplace_back(std::move(run_loop));
+    observers_.emplace_back(std::move(fake_observer));
+  }
+
+  // Wait until all observers receive the READ_TIMEOUT error
+  for (auto& run_loop : loops)
+    run_loop->Run();
+
+  // Remove clients
+  for (auto& client_data : clients_data_)
+    handler_->RemoveClient(&client_data);
+}
+
+class SamplesHandlerTestWithParam
+    : public ::testing::TestWithParam<std::vector<std::pair<double, double>>>,
+      public SamplesHandlerTestBase {
+ protected:
+  void SetUp() override { SetUpBase(); }
+
+  void TearDown() override { TearDownBase(); }
+};
+
+// Add clients with the first frequencies set, update clients with the second
+// frequencies, and remove clients. Clients' frequencies and the sample
+// handler's |max_frequency_| are checked after each modification.
+TEST_P(SamplesHandlerTestWithParam, UpdateFrequency) {
+  // No samples in this test
+  device_->SetPauseCallbackAtKthSamples(0, base::BindOnce([]() {}));
+
+  clients_data_.resize(GetParam().size());
+
+  std::multiset<double> frequencies;
+
+  // Add clients
+  for (size_t i = 0; i < GetParam().size(); ++i) {
+    ClientData& client_data = clients_data_[i];
+
+    client_data.id = i;
+    client_data.iio_device = device_.get();
+    // At least one channel enabled
+    client_data.enabled_chn_indices.emplace(3);  // timestamp
+    client_data.timeout = 0;
+    client_data.frequency = GetParam()[i].first;
+
+    handler_->AddClient(&client_data);
+
+    frequencies.emplace(FixFrequency(client_data.frequency));
+    handler_->CheckRequestedFrequency(*frequencies.rbegin());
+  }
+
+  base::MessageLoopForIO message_loop;
+
+  // Update clients' frequencies
+  for (size_t i = 0; i < GetParam().size(); ++i) {
+    ClientData& client_data = clients_data_[i];
+
+    double new_freq = GetParam()[i].second;
+    handler_->UpdateFrequency(
+        &client_data, new_freq,
+        base::BindOnce(
+            [](ClientData* client_data, double fixed_new_freq,
+               double result_freq) {
+              EXPECT_EQ(client_data->frequency, fixed_new_freq);
+              EXPECT_EQ(result_freq, fixed_new_freq);
+            },
+            &client_data, FixFrequency(new_freq)));
+
+    auto it = frequencies.find(FixFrequency(GetParam()[i].first));
+    EXPECT_TRUE(it != frequencies.end());
+    frequencies.erase(it);
+    frequencies.emplace(FixFrequency(new_freq));
+
+    handler_->CheckRequestedFrequency(*frequencies.rbegin());
+  }
+
+  // Remove clients
+  for (size_t i = 0; i < GetParam().size(); ++i) {
+    ClientData& client_data = clients_data_[i];
+
+    handler_->RemoveClient(&client_data);
+    auto it = frequencies.find(FixFrequency(GetParam()[i].second));
+    EXPECT_TRUE(it != frequencies.end());
+    frequencies.erase(it);
+
+    handler_->CheckRequestedFrequency(
+        i == GetParam().size() - 1 ? 0.0 : *frequencies.rbegin());
+  }
+}
+
+// Add all clients into the sample handler, read the first |kPauseIndex|
+// samples and pause reading, update clients' frequencies and enable accel_y
+// channel, and read the rest samples. All samples are checked when received by
+// observers.
+TEST_P(SamplesHandlerTestWithParam, ReadSamplesWithFrequency) {
+  base::MessageLoopForIO message_loop;
+
+  base::RunLoop pause_loop;
+  // Set the pause in the beginning to prevent reading samples before all
+  // clients added.
+  device_->SetPauseCallbackAtKthSamples(
+      0, base::BindOnce([](base::Closure quit_closure) { quit_closure.Run(); },
+                        pause_loop.QuitClosure()));
+
+  std::multiset<std::pair<int, cros::mojom::ObserverErrorType>> rf_failures;
+  for (int i = 0; i < kNumFailures; ++i) {
+    int k = base::RandInt(0, base::size(libmems::fakes::kFakeAccelSamples) - 1);
+
+    device_->AddFailedReadAtKthSample(k);
+    rf_failures.insert(
+        std::make_pair(k, cros::mojom::ObserverErrorType::READ_FAILED));
+  }
+
+  clients_data_.resize(GetParam().size());
+  std::vector<std::unique_ptr<base::RunLoop>> loops;
+
+  double max_freq = -1, max_freq2 = -1;
+  for (size_t i = 0; i < GetParam().size(); ++i) {
+    max_freq = std::max(max_freq, GetParam()[i].first);
+    max_freq2 = std::max(max_freq2, GetParam()[i].second);
+  }
+
+  max_freq = FixFrequency(max_freq);
+  max_freq2 = FixFrequency(max_freq2);
+
+  for (size_t i = 0; i < GetParam().size(); ++i) {
+    ClientData& client_data = clients_data_[i];
+    std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
+
+    client_data.id = i;
+    client_data.iio_device = device_.get();
+    client_data.enabled_chn_indices.emplace(0);  // accel_x
+    client_data.enabled_chn_indices.emplace(2);  // accel_y
+    client_data.enabled_chn_indices.emplace(3);  // timestamp
+    client_data.frequency = GetParam()[i].first;
+
+    auto failures = rf_failures;
+    if (GetParam()[i].first == 0.0) {
+      failures.insert(
+          std::make_pair(0, cros::mojom::ObserverErrorType::FREQUENCY_INVALID));
+    }
+
+    // The fake observer needs |max_freq| and |max_freq2| to calculate the
+    // correct values of samples
+    auto fake_observer = fakes::FakeSamplesObserver::Create(
+        ipc_thread_->task_runner(), run_loop->QuitClosure(), device_.get(),
+        std::move(failures), FixFrequency(GetParam()[i].first),
+        FixFrequency(GetParam()[i].second), max_freq, max_freq2);
+
+    handler_->AddClient(&client_data);
+
+    loops.emplace_back(std::move(run_loop));
+    observers_.emplace_back(std::move(fake_observer));
+  }
+  pause_loop.Run();
+
+  device_->SetPauseCallbackAtKthSamples(
+      fakes::kPauseIndex,
+      base::BindOnce(
+          [](fakes::FakeSamplesHandler* handler,
+             std::vector<ClientData>* clients_data,
+             libmems::fakes::FakeIioDevice* device) {
+            for (int i = 0; i < clients_data->size(); ++i) {
+              ClientData& client_data = clients_data->at(i);
+
+              // Update to the second frequency
+              handler->UpdateFrequency(
+                  &client_data, GetParam()[i].second,
+                  base::BindOnce(
+                      [](double fixed_new_freq, double result_freq) {
+                        EXPECT_EQ(result_freq, fixed_new_freq);
+                      },
+                      FixFrequency(GetParam()[i].second)));
+
+              // Enable accel_y
+              handler->UpdateChannelsEnabled(
+                  &client_data, std::vector<int32_t>{1}, true,
+                  base::BindOnce(
+                      [](const std::vector<int32_t>& failed_indices) {
+                        EXPECT_TRUE(failed_indices.empty());
+                      }));
+            }
+
+            handler->ResumeReading();  // Read the rest samples
+          },
+          handler_.get(), &clients_data_, device_.get()));
+
+  handler_->ResumeReading();  // Start reading |kPauseIndex| samples
+
+  // Wait until all observers receive all samples
+  for (auto& run_loop : loops)
+    run_loop->Run();
+
+  // Remove clients
+  for (auto& client_data : clients_data_)
+    handler_->RemoveClient(&client_data);
+}
+
+INSTANTIATE_TEST_CASE_P(
+    SamplesHandlerTestWithParamRun,
+    SamplesHandlerTestWithParam,
+    ::testing::Values(std::vector<std::pair<double, double>>(3, {10.0, 10.0}),
+                      std::vector<std::pair<double, double>>{
+                          {20.0, 50.0}, {10.0, 10.0}, {2.0, 3.0}},
+                      std::vector<std::pair<double, double>>{
+                          {10.0, 20.0}, {20.0, 30.0}, {0.0, 0.0}},
+                      std::vector<std::pair<double, double>>{
+                          {80.0, 50.0}, {10.0, 10.0}, {2.0, 3.0}},
+                      std::vector<std::pair<double, double>>{
+                          {10.0, 40.0}, {0.0, 20.0}, {2.0, 3.0}, {40.0, 10.0}},
+                      std::vector<std::pair<double, double>>{
+                          {2.0, 10.0}, {10.0, 30.0}, {80.0, 0.0}},
+                      std::vector<std::pair<double, double>>{
+                          {0.0, 10.0}, {10.0, 30.0}, {80.0, 60.0}},
+                      std::vector<std::pair<double, double>>{
+                          {2.0, 10.0}, {50.0, 30.0}, {80.0, 60.0}},
+                      std::vector<std::pair<double, double>>{{20.0, 30.0},
+                                                             {10.0, 10.0}}));
+
+}  // namespace
+
+}  // namespace iioservice
diff --git a/iioservice/daemon/test_fakes.cc b/iioservice/daemon/test_fakes.cc
new file mode 100644
index 0000000..80bcfe8
--- /dev/null
+++ b/iioservice/daemon/test_fakes.cc
@@ -0,0 +1,251 @@
+// Copyright 2020 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 "iioservice/daemon/test_fakes.h"
+
+#include <algorithm>
+#include <limits>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/logging.h>
+
+#include <libmems/common_types.h>
+#include <libmems/test_fakes.h>
+
+#include "iioservice/include/constants.h"
+
+namespace iioservice {
+
+namespace fakes {
+
+// static
+FakeSamplesHandler::ScopedFakeSamplesHandler FakeSamplesHandler::CreateWithFifo(
+    scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner,
+    scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+    libmems::fakes::FakeIioDevice* fake_iio_device,
+    OnSampleUpdatedCallback on_sample_updated_callback,
+    OnErrorOccurredCallback on_error_occurred_callback) {
+  ScopedFakeSamplesHandler handler(nullptr, SamplesHandlerDeleter);
+  double min_freq, max_freq;
+  if (!GetDevMinMaxFrequency(fake_iio_device, &min_freq, &max_freq))
+    return handler;
+
+  handler.reset(new FakeSamplesHandler(
+      std::move(ipc_task_runner), std::move(task_runner), fake_iio_device,
+      min_freq, max_freq, std::move(on_sample_updated_callback),
+      std::move(on_error_occurred_callback)));
+  return handler;
+}
+
+void FakeSamplesHandler::ResumeReading() {
+  sample_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&FakeSamplesHandler::ResumeReadingOnThread,
+                                weak_factory_.GetWeakPtr()));
+}
+
+void FakeSamplesHandler::CheckRequestedFrequency(double max_freq) {
+  sample_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(&FakeSamplesHandler::CheckRequestedFrequencyOnThread,
+                     weak_factory_.GetWeakPtr(), max_freq));
+}
+
+FakeSamplesHandler::FakeSamplesHandler(
+    scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner,
+    scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+    libmems::fakes::FakeIioDevice* fake_iio_device,
+    double min_freq,
+    double max_freq,
+    OnSampleUpdatedCallback on_sample_updated_callback,
+    OnErrorOccurredCallback on_error_occurred_callback)
+    : SamplesHandler(std::move(ipc_task_runner),
+                     std::move(task_runner),
+                     fake_iio_device,
+                     min_freq,
+                     max_freq,
+                     std::move(on_sample_updated_callback),
+                     std::move(on_error_occurred_callback)),
+      fake_iio_device_(fake_iio_device) {}
+
+void FakeSamplesHandler::ResumeReadingOnThread() {
+  CHECK(sample_task_runner_->BelongsToCurrentThread());
+
+  fake_iio_device_->ResumeReadingSamples();
+}
+
+void FakeSamplesHandler::CheckRequestedFrequencyOnThread(double max_freq) {
+  CHECK(sample_task_runner_->BelongsToCurrentThread());
+
+  CHECK_EQ(max_freq, requested_frequency_);
+}
+
+// static
+void FakeSamplesObserver::ObserverDeleter(FakeSamplesObserver* observer) {
+  if (observer == nullptr)
+    return;
+
+  if (!observer->ipc_task_runner_->BelongsToCurrentThread()) {
+    observer->ipc_task_runner_->PostTask(
+        FROM_HERE,
+        base::BindOnce(&FakeSamplesObserver::ObserverDeleter, observer));
+    return;
+  }
+
+  delete observer;
+}
+
+// static
+FakeSamplesObserver::ScopedFakeSamplesObserver FakeSamplesObserver::Create(
+    scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner,
+    base::Closure quit_closure,
+    libmems::IioDevice* device,
+    std::multiset<std::pair<int, cros::mojom::ObserverErrorType>> failures,
+    double frequency,
+    double frequency2,
+    double dev_frequency,
+    double dev_frequency2,
+    int pause_index) {
+  ScopedFakeSamplesObserver handler(
+      new FakeSamplesObserver(std::move(ipc_task_runner),
+                              std::move(quit_closure), device,
+                              std::move(failures), frequency, frequency2,
+                              dev_frequency, dev_frequency2, pause_index),
+      ObserverDeleter);
+
+  return handler;
+}
+
+void FakeSamplesObserver::OnSampleUpdated(
+    const base::flat_map<int32_t, int64_t>& sample) {
+  CHECK(ipc_task_runner_->BelongsToCurrentThread());
+  CHECK(failures_.empty() || failures_.begin()->first > sample_index_);
+
+  int step = GetStep();
+  CHECK_GE(step, 1);
+
+  CHECK_GT(base::size(libmems::fakes::kFakeAccelSamples),
+           sample_index_ + step - 1);
+
+  if (device_->GetId() == kAccelDeviceId) {
+    for (int chnIndex = 0;
+         chnIndex < base::size(libmems::fakes::kFakeAccelChns); ++chnIndex) {
+      auto it = sample.find(chnIndex);
+
+      // channel: accel_y isn't enabled before |pause_index_|
+      if (sample_index_ + step - 1 < pause_index_ && chnIndex == 1) {
+        CHECK(it == sample.end());
+        continue;
+      }
+
+      CHECK(it != sample.end());
+
+      CHECK_EQ(it->second,
+               libmems::fakes::kFakeAccelSamples[sample_index_ + step - 1]
+                                                [chnIndex]);
+    }
+  } else {
+    auto channels = device_->GetAllChannels();
+    for (size_t i = 0; i < channels.size(); ++i) {
+      auto raw_value = channels[i]->ReadNumberAttribute(libmems::kRawAttr);
+      if (!raw_value.has_value())
+        continue;
+
+      auto it = sample.find(i);
+      CHECK(it != sample.end());
+      CHECK_EQ(raw_value.value(), it->second);
+    }
+  }
+
+  sample_index_ += step;
+
+  if ((frequency2_ == 0.0 && sample_index_ + step - 1 >= pause_index_) ||
+      sample_index_ + step - 1 >= base::size(libmems::fakes::kFakeAccelSamples))
+    quit_closure_.Run();
+}
+
+void FakeSamplesObserver::OnErrorOccurred(cros::mojom::ObserverErrorType type) {
+  CHECK(ipc_task_runner_->BelongsToCurrentThread());
+
+  CHECK(!failures_.empty());
+  CHECK_LE(failures_.begin()->first, sample_index_ + GetStep());
+  CHECK_EQ(failures_.begin()->second, type);
+
+  failures_.erase(failures_.begin());
+
+  if (type == cros::mojom::ObserverErrorType::FREQUENCY_INVALID) {
+    CHECK_EQ(sample_index_, 0);
+    if (frequency2_ == 0.0) {
+      sample_index_ = base::size(libmems::fakes::kFakeAccelSamples);
+      quit_closure_.Run();
+
+      return;
+    }
+
+    while (!failures_.empty() && failures_.begin()->first < pause_index_) {
+      if (failures_.begin()->second ==
+          cros::mojom::ObserverErrorType::READ_TIMEOUT)
+        quit_closure_.Run();
+
+      failures_.erase(failures_.begin());
+    }
+
+    sample_index_ = pause_index_;
+
+    return;
+  }
+
+  if (type == cros::mojom::ObserverErrorType::READ_TIMEOUT)
+    quit_closure_.Run();
+}
+
+FakeSamplesObserver::FakeSamplesObserver(
+    scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner,
+    base::Closure quit_closure,
+    libmems::IioDevice* device,
+    std::multiset<std::pair<int, cros::mojom::ObserverErrorType>> failures,
+    double frequency,
+    double frequency2,
+    double dev_frequency,
+    double dev_frequency2,
+    int pause_index)
+    : ipc_task_runner_(ipc_task_runner),
+      quit_closure_(quit_closure),
+      device_(device),
+      failures_(std::move(failures)),
+      frequency_(frequency),
+      frequency2_(frequency2),
+      dev_frequency_(dev_frequency),
+      dev_frequency2_(dev_frequency2),
+      pause_index_(pause_index),
+      receiver_(this) {
+  CHECK_GE(frequency_, 0.0);
+  CHECK_GE(frequency2_, 0.0);
+  CHECK_GE(dev_frequency_, kFrequencyEpsilon);
+  CHECK_GE(dev_frequency2_, kFrequencyEpsilon);
+}
+
+int FakeSamplesObserver::GetStep() {
+  CHECK_GE(dev_frequency_, kFrequencyEpsilon);
+
+  int step = base::size(libmems::fakes::kFakeAccelSamples);
+  if (frequency_ >= kFrequencyEpsilon)
+    step = dev_frequency_ / frequency_;
+
+  if (sample_index_ + step - 1 < pause_index_)
+    return step;
+
+  if (frequency2_ < kFrequencyEpsilon)
+    return base::size(libmems::fakes::kFakeAccelSamples);
+
+  int step2 = dev_frequency2_ / frequency2_;
+
+  return std::max(pause_index_ - sample_index_ + 1, step2);
+}
+
+}  // namespace fakes
+
+}  // namespace iioservice
diff --git a/iioservice/daemon/test_fakes.h b/iioservice/daemon/test_fakes.h
new file mode 100644
index 0000000..6e580e6
--- /dev/null
+++ b/iioservice/daemon/test_fakes.h
@@ -0,0 +1,129 @@
+// Copyright 2020 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 IIOSERVICE_DAEMON_TEST_FAKES_H_
+#define IIOSERVICE_DAEMON_TEST_FAKES_H_
+
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+
+#include <base/callback.h>
+#include <base/single_thread_task_runner.h>
+#include <base/threading/thread.h>
+#include <mojo/public/cpp/bindings/receiver.h>
+
+#include <libmems/iio_device.h>
+#include <libmems/test_fakes.h>
+
+#include "iioservice/daemon/samples_handler.h"
+#include "mojo/sensor.mojom.h"
+
+namespace iioservice {
+
+namespace fakes {
+
+constexpr char kAccelDeviceName[] = "FakeAccelDevice";
+constexpr int kAccelDeviceId = 1;
+
+constexpr char kFakeSamplingFrequencyAvailable[] =
+    "0.000000 1.250000 40.000000";
+
+constexpr int kPauseIndex = 50;
+
+class FakeSamplesHandler : public SamplesHandler {
+ public:
+  using ScopedFakeSamplesHandler =
+      std::unique_ptr<FakeSamplesHandler, decltype(&SamplesHandlerDeleter)>;
+
+  static ScopedFakeSamplesHandler CreateWithFifo(
+      scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner,
+      scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+      libmems::fakes::FakeIioDevice* fake_iio_device,
+      OnSampleUpdatedCallback on_sample_updated_callback,
+      OnErrorOccurredCallback on_error_occurred_callback);
+
+  void ResumeReading();
+  void CheckRequestedFrequency(double max_freq);
+
+ private:
+  FakeSamplesHandler(
+      scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner,
+      scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+      libmems::fakes::FakeIioDevice* fake_iio_device,
+      double min_freq,
+      double max_freq,
+      OnSampleUpdatedCallback on_sample_updated_callback,
+      OnErrorOccurredCallback on_error_occurred_callback);
+
+  void ResumeReadingOnThread();
+  void CheckRequestedFrequencyOnThread(double max_freq);
+
+  libmems::fakes::FakeIioDevice* fake_iio_device_;
+
+  base::WeakPtrFactory<FakeSamplesHandler> weak_factory_{this};
+};
+
+class FakeSamplesObserver : public cros::mojom::SensorDeviceSamplesObserver {
+ public:
+  static void ObserverDeleter(FakeSamplesObserver* observer);
+  using ScopedFakeSamplesObserver =
+      std::unique_ptr<FakeSamplesObserver, decltype(&ObserverDeleter)>;
+
+  static ScopedFakeSamplesObserver Create(
+      scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner,
+      base::Closure quit_closure,
+      libmems::IioDevice* device,
+      std::multiset<std::pair<int, cros::mojom::ObserverErrorType>> failures,
+      double frequency,
+      double frequency2,
+      double dev_frequency,
+      double dev_frequency2,
+      int pause_index = kPauseIndex);
+
+  void OnSampleUpdated(const base::flat_map<int32_t, int64_t>& sample) override;
+
+  void OnErrorOccurred(cros::mojom::ObserverErrorType type) override;
+
+ private:
+  FakeSamplesObserver(
+      scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner,
+      base::Closure quit_closure,
+      libmems::IioDevice* device,
+      std::multiset<std::pair<int, cros::mojom::ObserverErrorType>> failures,
+      double frequency,
+      double frequency2,
+      double dev_frequency,
+      double dev_frequency2,
+      int pause_index = kPauseIndex);
+
+  int GetStep();
+
+  scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner_;
+
+  base::Closure quit_closure_;
+
+  libmems::IioDevice* device_;
+
+  std::multiset<std::pair<int, cros::mojom::ObserverErrorType>> failures_;
+
+  double frequency_;
+  double frequency2_;
+  double dev_frequency_;
+  double dev_frequency2_;
+  int pause_index_;
+
+  int sample_index_ = 0;
+
+  mojo::Receiver<cros::mojom::SensorDeviceSamplesObserver> receiver_;
+
+  base::WeakPtrFactory<FakeSamplesObserver> weak_factory_{this};
+};
+
+}  // namespace fakes
+
+}  // namespace iioservice
+
+#endif  // IIOSERVICE_DAEMON_TEST_FAKES_H_
diff --git a/iioservice/daemon/testrunner.cc b/iioservice/daemon/testrunner.cc
new file mode 100644
index 0000000..4caee59
--- /dev/null
+++ b/iioservice/daemon/testrunner.cc
@@ -0,0 +1,17 @@
+// Copyright 2020 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 <gtest/gtest.h>
+
+#include <base/at_exit.h>
+#include <mojo/core/embedder/embedder.h>
+
+int main(int argc, char** argv) {
+  ::testing::InitGoogleTest(&argc, argv);
+  base::AtExitManager at_exit;
+
+  mojo::core::Init();
+
+  return RUN_ALL_TESTS();
+}