Commit 4bbb1de3 authored by Harvey Yang's avatar Harvey Yang Committed by Commit Bot

Chromium: Add Sensors in chromeos/components

This commit adds Sensor Hal Dispatcher, sensor mojo interfaces, and
basic usages of them to connect clients to iioservice.

BUG=chromium:1006141, b:162154663
TEST=builds, unit tests and test on octopus: Chromium connects to
iioservice as a DBus client, and invites iioservice into the mojo
network. Chromium then waits for iioservice_client (test executable) to
register and establish a connection between iioservice and
iioservice_client. All re-connections between Chromium and iioservice
work well.

Change-Id: I9f9ed4267093191ad306c25216300f850dc42006
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2332056
Commit-Queue: Cheng-Hao Yang <chenghaoyang@chromium.org>
Reviewed-by: default avatarOksana Zhuravlova <oksamyt@chromium.org>
Reviewed-by: default avatarRyo Hashimoto <hashimoto@chromium.org>
Cr-Commit-Position: refs/heads/master@{#805973}
parent 9bfdb97e
......@@ -28,6 +28,7 @@ test("chromeos_components_unittests") {
"//chromeos/components/proximity_auth:unit_tests",
"//chromeos/components/quick_answers:unit_tests",
"//chromeos/components/security_token_pin:unit_tests",
"//chromeos/components/sensors:unit_tests",
"//chromeos/components/smbfs:unit_tests",
"//chromeos/components/string_matching:unit_tests",
"//chromeos/components/sync_wifi:unit_tests",
......
# Copyright 2020 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
assert(is_chromeos, "Non-ChromeOS builds cannot depend on //chromeos")
component("sensors") {
output_name = "chromeos_sensors"
defines = [ "IS_CHROMEOS_SENSORS_IMPL" ]
sources = [
"sensor_hal_dispatcher.cc",
"sensor_hal_dispatcher.h",
]
deps = [
"//base",
"//chromeos/components/sensors/mojom",
"//mojo/public/cpp/bindings",
]
}
source_set("test_support") {
testonly = true
sources = [
"fake_sensor_hal_client.cc",
"fake_sensor_hal_client.h",
"fake_sensor_hal_server.cc",
"fake_sensor_hal_server.h",
]
deps = [
":sensors",
"//base",
"//chromeos/components/sensors/mojom",
"//mojo/public/cpp/bindings",
]
}
source_set("unit_tests") {
testonly = true
sources = [ "sensor_hal_dispatcher_unittest.cc" ]
deps = [
":sensors",
":test_support",
"//base/test:test_support",
"//chromeos/components/sensors/mojom",
"//mojo/public/cpp/bindings",
"//testing/gtest",
]
}
include_rules = [
"+mojo/public/cpp/bindings",
]
chenghaoyang@chromium.org
gwendal@chromium.org
// Copyright 2020 The Chromium 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 "chromeos/components/sensors/fake_sensor_hal_client.h"
namespace chromeos {
namespace sensors {
FakeSensorHalClient::FakeSensorHalClient() {}
FakeSensorHalClient::~FakeSensorHalClient() = default;
void FakeSensorHalClient::SetUpChannel(
mojo::PendingRemote<mojom::SensorService> sensor_service) {
DCHECK(!SensorServiceIsValid());
sensor_service_ = std::move(sensor_service);
}
mojo::PendingRemote<mojom::SensorHalClient> FakeSensorHalClient::PassRemote() {
CHECK(!receiver_.is_bound());
return receiver_.BindNewPipeAndPassRemote();
}
bool FakeSensorHalClient::SensorServiceIsValid() {
return sensor_service_.is_valid();
}
void FakeSensorHalClient::ResetSensorService() {
sensor_service_.reset();
}
} // namespace sensors
} // namespace chromeos
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROMEOS_COMPONENTS_SENSORS_FAKE_SENSOR_HAL_CLIENT_H_
#define CHROMEOS_COMPONENTS_SENSORS_FAKE_SENSOR_HAL_CLIENT_H_
#include "chromeos/components/sensors/mojom/cros_sensor_service.mojom.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
namespace chromeos {
namespace sensors {
class FakeSensorHalClient : public mojom::SensorHalClient {
public:
FakeSensorHalClient();
FakeSensorHalClient(const FakeSensorHalClient&) = delete;
FakeSensorHalClient& operator=(const FakeSensorHalClient&) = delete;
~FakeSensorHalClient() override;
void SetUpChannel(
mojo::PendingRemote<mojom::SensorService> sensor_service) override;
mojo::PendingRemote<mojom::SensorHalClient> PassRemote();
bool SensorServiceIsValid();
void ResetSensorService();
private:
mojo::PendingRemote<mojom::SensorService> sensor_service_;
mojo::Receiver<mojom::SensorHalClient> receiver_{this};
};
} // namespace sensors
} // namespace chromeos
#endif // CHROMEOS_COMPONENTS_SENSORS_FAKE_SENSOR_HAL_CLIENT_H_
// Copyright 2020 The Chromium 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 "chromeos/components/sensors/fake_sensor_hal_server.h"
namespace chromeos {
namespace sensors {
FakeSensorHalServer::FakeSensorHalServer() {}
FakeSensorHalServer::~FakeSensorHalServer() = default;
void FakeSensorHalServer::CreateChannel(
mojo::PendingReceiver<mojom::SensorService> sensor_service_receiver) {
DCHECK(!SensorServiceIsValid());
sensor_service_receiver_ = std::move(sensor_service_receiver);
}
mojo::PendingRemote<mojom::SensorHalServer> FakeSensorHalServer::PassRemote() {
CHECK(!receiver_.is_bound());
return receiver_.BindNewPipeAndPassRemote();
}
bool FakeSensorHalServer::SensorServiceIsValid() {
return sensor_service_receiver_.is_valid();
}
void FakeSensorHalServer::ResetSensorService() {
sensor_service_receiver_.reset();
}
} // namespace sensors
} // namespace chromeos
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROMEOS_COMPONENTS_SENSORS_FAKE_SENSOR_HAL_SERVER_H_
#define CHROMEOS_COMPONENTS_SENSORS_FAKE_SENSOR_HAL_SERVER_H_
#include "chromeos/components/sensors/mojom/cros_sensor_service.mojom.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
namespace chromeos {
namespace sensors {
class FakeSensorHalServer : public mojom::SensorHalServer {
public:
FakeSensorHalServer();
FakeSensorHalServer(const FakeSensorHalServer&) = delete;
FakeSensorHalServer& operator=(const FakeSensorHalServer&) = delete;
~FakeSensorHalServer() override;
void CreateChannel(mojo::PendingReceiver<mojom::SensorService>
sensor_service_receiver) override;
mojo::PendingRemote<mojom::SensorHalServer> PassRemote();
bool SensorServiceIsValid();
void ResetSensorService();
private:
mojo::PendingReceiver<mojom::SensorService> sensor_service_receiver_;
mojo::Receiver<mojom::SensorHalServer> receiver_{this};
};
} // namespace sensors
} // namespace chromeos
#endif // CHROMEOS_COMPONENTS_SENSORS_FAKE_SENSOR_HAL_SERVER_H_
# Copyright 2020 The Chromium 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("//mojo/public/tools/bindings/mojom.gni")
mojom("mojom") {
sources = [
"cros_sensor_service.mojom",
"sensor.mojom",
]
}
per-file *.mojom=set noparent
per-file *.mojom=file://ipc/SECURITY_OWNERS
per-file *.mojom=file://chromeos/SECURITY_OWNERS
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Next min version: 1
// NOTE: This mojom exists in two places and must be kept in sync:
// Chromium: //chromeos/components/sensors/mojom/
// Chrome OS: src/platform2/iioservice/mojo/
// Note: Other repos downstream of Chromium might also use this mojom.
// Example: A backwards-compatible mojom change (and corresponding
// implementation change) can be made in Chrome OS first, then replicated to the
// clients (Chromium, other downstream repos) later.
module chromeos.sensors.mojom;
// NOTE: The base directory for 'import' statements is expected to differ
// between Chromium and Chrome OS versions of this file.
import "chromeos/components/sensors/mojom/sensor.mojom";
// The CrOS sensor HAL Mojo server.
//
// Next method ID: 1
interface SensorHalServer {
// A caller calls CreateChannel to create a new Mojo channel to the sensor
// HAL adapter. Upon successfully binding of |sensor_context_request|, the
// caller will have an established Mojo channel to the sensor HAL adapter
// process.
CreateChannel@0(pending_receiver<SensorService> sensor_service_request);
};
// The CrOS sensor HAL Mojo client.
//
// Next method ID: 1
interface SensorHalClient {
// A caller calls SetUpChannel to dispatch the established Mojo channel
// |sensor_context_ptr| to the client. The SensorHalClient can create a
// Mojo channel to the sensor HAL adapter process with |sensor_context_ptr|.
// SetUpChannel may be called multiple times. In cases such as the
// SensorHalServer which holds the original Mojo channel crashes,
// SensorHalDispatcher will call SetUpChannel again once a new SensorHalServer
// reconnects.
SetUpChannel@0(pending_remote<SensorService> sensor_service_ptr);
};
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Next min version: 1
// NOTE: This mojom exists in two places and must be kept in sync:
// Chromium: //chromeos/components/sensors/mojom/
// Chrome OS: src/platform2/iioservice/mojo/
// Note: Other repos downstream of Chromium might also use this mojom.
// Example: A backwards-compatible mojom change (and corresponding
// implementation change) can be made in Chrome OS first, then replicated to the
// clients (Chromium, other downstream repos) later.
module chromeos.sensors.mojom;
enum DeviceType {
NONE = 0, // invalid device type
ACCEL = 1,
ANGLVEL = 2,
LIGHT = 3,
COUNT = 4,
MAGN = 5,
ANGL = 6,
ACPI_ALS = 7,
BARO = 8,
};
enum ObserverErrorType {
ALREADY_STARTED = 0,
SET_FREQUENCY_IO_FAILED = 1,
FREQUENCY_INVALID = 2,
NO_ENABLED_CHANNELS = 3,
GET_FD_FAILED = 4,
READ_FAILED = 5,
READ_TIMEOUT = 6,
};
// SensorService, an interface to search and get SensorDevices. A User can get
// multiple isolated SensorDevices for one physical device, if it wants
// different frequencies of that device's samples.
//
// Next method ID: 3
interface SensorService {
// Gets device ids as a vector of int given the device's type. Only devices
// with id having "iio:device" as the prefix would be available.
// Gets an empty vector if no device can be found.
GetDeviceIds@0(DeviceType type) => (array<int32> iio_device_ids);
// Gets all device ids and their types. Only devices with id having
// "iio:device" as the prefix would be available. For combo sensors, there are
// multiple types in the array of types.
// Gets an empty vector if no device can be found.
GetAllDeviceIds@1() => (map<int32, array<DeviceType>> iio_device_ids_types);
// Creates a new Mojo channel to the iioservice as an isolated client. Upon
// successfully binding of |device_request|, the caller will have an
// established Mojo channel to iioservice.
// If failed, the request won't be bound and will be destroyed directly.
GetDevice@2(int32 iio_device_id,
pending_receiver<SensorDevice> device_request);
};
// SensorDevice, an interface sending requests for a physical device
// (libiio:iio_device). It is an isolated client in iioservice's point of view.
//
// Next method ID: 9
interface SensorDevice {
// Sets |timeout| in milliseconds for I/O operations, mainly for reading
// samples. Sets |timeout| as 0 to specify that no timeout should occur.
// Default: 5000 milliseconds.
SetTimeout@0(uint32 timeout);
// Gets the |attr_name| attribute of this device into |value| in string.
// Returns base::nullopt if the attribute cannot be read.
GetAttribute@1(string attr_name) => (string? value);
// Sets the frequency of the device before starting to read samples.
// |result_freq| would be the frequency set by iioservice for the client.
SetFrequency@2(double frequency) => (double result_freq);
// Starts reading samples with the frequency set by |SetFrequency|. Samples or
// errors will be pushed to the observer. It's the user's responsibility to
// keep |observer| active while reading.
StartReadingSamples@3(pending_remote<SensorDeviceSamplesObserver> observer);
// Stops reading samples in this device. Reading can be restarted by calling
// |StartReadingSamples| again.
StopReadingSamples@4();
// Gets all channels' ids as a vector of string. The ids are ordered and the
// user should use the indices of channels to send further requests and
// receive samples.
GetAllChannelIds@5() => (array<string> iio_chn_ids);
// Sets channels' enabled status to |en| with channel indices
// |iio_chn_indices|. Returns an array of channel indices |failed_indices| if
// there are some invalid or duplicate indices.
SetChannelsEnabled @6(array<int32> iio_chn_indices, bool en)
=> (array<int32> failed_indices);
// Returns an array of bool indicating if channels are enabled.
GetChannelsEnabled@7(array<int32> iio_chn_indices) => (array<bool> enabled);
// Gets the |attr_name| attribute of channels as a group into |values| in
// string.
// Returns base::nullopt if the attribute in the channel cannot be read.
GetChannelsAttributes@8(array<int32> iio_chn_indices, string attr_name)
=> (array<string?> values);
};
// One observer is created to track one specific device's samples, using
// SensorDevice::StartReadingSamples to register the observer.
//
// Next method ID: 2
interface SensorDeviceSamplesObserver {
// |sample| arrives and is sent to the client as a map from iio_chn_indices to
// data (64 bit integer).
OnSampleUpdated@0(map<int32, int64> sample);
// An error occurs and is sent to the client as an enum type.
OnErrorOccurred@1(ObserverErrorType type);
};
// Copyright 2020 The Chromium 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 "chromeos/components/sensors/sensor_hal_dispatcher.h"
#include <utility>
#include "base/bind.h"
#include "base/no_destructor.h"
namespace chromeos {
namespace sensors {
namespace {
SensorHalDispatcher* g_sensor_hal_dispatcher = nullptr;
}
// static
void SensorHalDispatcher::Initialize() {
if (g_sensor_hal_dispatcher) {
LOG(WARNING) << "SensorHalDispatcher was already initialized";
return;
}
g_sensor_hal_dispatcher = new SensorHalDispatcher();
}
// static
void SensorHalDispatcher::Shutdown() {
if (!g_sensor_hal_dispatcher) {
LOG(WARNING)
<< "SensorHalDispatcher::Shutdown() called with null dispatcher";
return;
}
delete g_sensor_hal_dispatcher;
g_sensor_hal_dispatcher = nullptr;
}
// static
SensorHalDispatcher* SensorHalDispatcher::GetInstance() {
return g_sensor_hal_dispatcher;
}
void SensorHalDispatcher::RegisterServer(
mojo::PendingRemote<mojom::SensorHalServer> remote) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
sensor_hal_server_.Bind(std::move(remote));
sensor_hal_server_.set_disconnect_handler(
base::BindOnce(&SensorHalDispatcher::OnSensorHalServerDisconnect,
base::Unretained(this)));
// Set up the Mojo channels for clients which registered before the server
// registers.
for (auto& client : sensor_hal_clients_)
EstablishMojoChannel(client);
}
void SensorHalDispatcher::RegisterClient(
mojo::PendingRemote<mojom::SensorHalClient> remote) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto client = mojo::Remote<mojom::SensorHalClient>(std::move(remote));
if (sensor_hal_server_)
EstablishMojoChannel(client);
sensor_hal_clients_.Add(std::move(client));
}
SensorHalDispatcher::SensorHalDispatcher() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
sensor_hal_clients_.set_disconnect_handler(
base::BindRepeating(&SensorHalDispatcher::OnSensorHalClientDisconnect,
base::Unretained(this)));
}
SensorHalDispatcher::~SensorHalDispatcher() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void SensorHalDispatcher::EstablishMojoChannel(
const mojo::Remote<mojom::SensorHalClient>& client) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(sensor_hal_server_);
mojo::PendingRemote<mojom::SensorService> service_remote;
sensor_hal_server_->CreateChannel(
service_remote.InitWithNewPipeAndPassReceiver());
client->SetUpChannel(std::move(service_remote));
}
void SensorHalDispatcher::OnSensorHalServerDisconnect() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
LOG(ERROR) << "Sensor HAL Server connection lost";
sensor_hal_server_.reset();
}
void SensorHalDispatcher::OnSensorHalClientDisconnect(
mojo::RemoteSetElementId id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
LOG(ERROR) << "Sensor HAL Client connection lost: " << id;
}
} // namespace sensors
} // namespace chromeos
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROMEOS_COMPONENTS_SENSORS_SENSOR_HAL_DISPATCHER_H_
#define CHROMEOS_COMPONENTS_SENSORS_SENSOR_HAL_DISPATCHER_H_
#include <map>
#include <memory>
#include "base/component_export.h"
#include "base/sequence_checker.h"
#include "chromeos/components/sensors/mojom/cros_sensor_service.mojom.h"
#include "chromeos/components/sensors/mojom/sensor.mojom.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/bindings/remote_set.h"
namespace chromeos {
namespace sensors {
// SensorHalDispatcher is a dispatcher that receives registrations of Sensor
// Service, including the server (IIO Service) and clients (e.g. powerd,
// simpleclient and Chrome's components). It'll then establish mojo channels
// between the server and clients.
// SensorHalDispatcher should be only used on the UI thread.
class COMPONENT_EXPORT(CHROMEOS_SENSORS) SensorHalDispatcher {
public:
// Creates the global SensorHalDispatcher instance.
static void Initialize();
// Destroys the global SensorHalDispatcher instance if it exists.
static void Shutdown();
// Returns a pointer to the global SensorHalDispatcher instance.
// Initialize() should already have been called.
static SensorHalDispatcher* GetInstance();
// Register IIO Service's mojo remote to this dispatcher.
void RegisterServer(mojo::PendingRemote<mojom::SensorHalServer> remote);
// Register a sensor client's mojo remote to this dispatcher.
void RegisterClient(mojo::PendingRemote<mojom::SensorHalClient> remote);
private:
SensorHalDispatcher();
~SensorHalDispatcher();
void EstablishMojoChannel(const mojo::Remote<mojom::SensorHalClient>& client);
void OnSensorHalServerDisconnect();
void OnSensorHalClientDisconnect(mojo::RemoteSetElementId id);
mojo::Remote<mojom::SensorHalServer> sensor_hal_server_;
mojo::RemoteSet<mojom::SensorHalClient> sensor_hal_clients_;
SEQUENCE_CHECKER(sequence_checker_);
};
} // namespace sensors
} // namespace chromeos
#endif // CHROMEOS_COMPONENTS_SENSORS_SENSOR_HAL_DISPATCHER_H_
// Copyright 2020 The Chromium 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 "chromeos/components/sensors/sensor_hal_dispatcher.h"
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/run_loop.h"
#include "base/test/task_environment.h"
#include "chromeos/components/sensors/fake_sensor_hal_client.h"
#include "chromeos/components/sensors/fake_sensor_hal_server.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromeos {
namespace sensors {
class SensorHalDispatcherTest : public ::testing::Test {
public:
SensorHalDispatcherTest() {}
SensorHalDispatcherTest(const SensorHalDispatcherTest&) = delete;
SensorHalDispatcherTest& operator=(const SensorHalDispatcherTest&) = delete;
~SensorHalDispatcherTest() override = default;
void SetUp() override {
SensorHalDispatcher::Initialize();
EXPECT_TRUE(SensorHalDispatcher::GetInstance());
}
void TearDown() override {
SensorHalDispatcher::Shutdown();
EXPECT_FALSE(SensorHalDispatcher::GetInstance());
}
private:
base::test::SingleThreadTaskEnvironment task_environment;
};
// Test that the SensorHalDisptcher correctly re-establishes a Mojo channel
// for the client when the server crashes.
TEST_F(SensorHalDispatcherTest, ServerConnectionError) {
// First verify that a the SensorHalDispatcher establishes a Mojo channel
// between the server and the client.
auto fake_server = std::make_unique<FakeSensorHalServer>();
auto fake_client = std::make_unique<FakeSensorHalClient>();
auto remote_server = fake_server->PassRemote();
auto remote_client = fake_client->PassRemote();
SensorHalDispatcher::GetInstance()->RegisterServer(std::move(remote_server));
SensorHalDispatcher::GetInstance()->RegisterClient(std::move(remote_client));
// Wait until the server and client get the established Mojo channel.
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(fake_server->SensorServiceIsValid());
EXPECT_TRUE(fake_client->SensorServiceIsValid());
// Re-create a new server to simulate a server crash.
fake_server = std::make_unique<FakeSensorHalServer>();
fake_client->ResetSensorService();
// Wait until the dispatcher resets the server and client remotes.
base::RunLoop().RunUntilIdle();
remote_server = fake_server->PassRemote();
SensorHalDispatcher::GetInstance()->RegisterServer(std::move(remote_server));
// Make sure we re-create the Mojo channel from the new server to the same
// client.
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(fake_server->SensorServiceIsValid());
EXPECT_TRUE(fake_client->SensorServiceIsValid());
}
// Test that the SensorHalDisptcher correctly re-establishes a Mojo channel
// for the client when the client reconnects after crash.
TEST_F(SensorHalDispatcherTest, ClientConnectionError) {
// First verify that a the SensorHalDispatcher establishes a Mojo channel
// between the server and the client.
auto fake_server = std::make_unique<FakeSensorHalServer>();
auto fake_client = std::make_unique<FakeSensorHalClient>();
auto remote_server = fake_server->PassRemote();
auto remote_client = fake_client->PassRemote();
SensorHalDispatcher::GetInstance()->RegisterServer(std::move(remote_server));
SensorHalDispatcher::GetInstance()->RegisterClient(std::move(remote_client));
// Wait until the server and client get the established Mojo channel.
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(fake_server->SensorServiceIsValid());
EXPECT_TRUE(fake_client->SensorServiceIsValid());
// Re-create a new client to simulate a client crash.
fake_client = std::make_unique<FakeSensorHalClient>();
fake_server->ResetSensorService();
remote_client = fake_client->PassRemote();
SensorHalDispatcher::GetInstance()->RegisterClient(std::move(remote_client));
// Make sure we re-create the Mojo channel from the same server to the new
// client.
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(fake_server->SensorServiceIsValid());
EXPECT_TRUE(fake_client->SensorServiceIsValid());
}
} // namespace sensors
} // namespace chromeos
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment