| // Copyright 2014 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 CONTENT_RENDERER_DEVICE_SENSORS_DEVICE_SENSOR_EVENT_PUMP_H_ |
| #define CONTENT_RENDERER_DEVICE_SENSORS_DEVICE_SENSOR_EVENT_PUMP_H_ |
| |
| #include <algorithm> |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/macros.h" |
| #include "base/time/time.h" |
| #include "base/timer/timer.h" |
| #include "content/public/common/service_names.mojom.h" |
| #include "content/public/renderer/platform_event_observer.h" |
| #include "content/public/renderer/render_frame.h" |
| #include "content/public/renderer/render_thread.h" |
| #include "mojo/public/cpp/bindings/binding.h" |
| #include "mojo/public/cpp/bindings/interface_request.h" |
| #include "services/device/public/cpp/generic_sensor/sensor_reading.h" |
| #include "services/device/public/cpp/generic_sensor/sensor_reading_shared_buffer_reader.h" |
| #include "services/device/public/cpp/generic_sensor/sensor_traits.h" |
| #include "services/device/public/interfaces/constants.mojom.h" |
| #include "services/device/public/interfaces/sensor_provider.mojom.h" |
| #include "third_party/WebKit/public/platform/modules/device_orientation/WebDeviceMotionListener.h" |
| #include "third_party/WebKit/public/platform/modules/device_orientation/WebDeviceOrientationListener.h" |
| #include "third_party/WebKit/public/web/WebLocalFrame.h" |
| |
| namespace content { |
| |
| template <typename ListenerType> |
| class CONTENT_EXPORT DeviceSensorEventPump |
| : public PlatformEventObserver<ListenerType> { |
| public: |
| // Default rate for firing events. |
| static constexpr int kDefaultPumpFrequencyHz = 60; |
| static constexpr int kDefaultPumpDelayMicroseconds = |
| base::Time::kMicrosecondsPerSecond / kDefaultPumpFrequencyHz; |
| |
| // The pump is a tri-state automaton with allowed transitions as follows: |
| // STOPPED -> PENDING_START |
| // PENDING_START -> RUNNING |
| // PENDING_START -> STOPPED |
| // RUNNING -> STOPPED |
| enum class PumpState { STOPPED, RUNNING, PENDING_START }; |
| |
| // The sensor state is an automaton with allowed transitions as follows: |
| // NOT_INITIALIZED -> INITIALIZING |
| // INITIALIZING -> ACTIVE |
| // INITIALIZING -> SHOULD_SUSPEND |
| // ACTIVE -> SUSPENDED |
| // SHOULD_SUSPEND -> INITIALIZING |
| // SHOULD_SUSPEND -> SUSPENDED |
| // SUSPENDED -> ACTIVE |
| // { INITIALIZING, ACTIVE, SHOULD_SUSPEND, SUSPENDED } -> NOT_INITIALIZED |
| enum class SensorState { |
| NOT_INITIALIZED, |
| INITIALIZING, |
| ACTIVE, |
| SHOULD_SUSPEND, |
| SUSPENDED |
| }; |
| |
| // PlatformEventObserver: |
| void Start(blink::WebPlatformEventListener* listener) override { |
| DVLOG(2) << "requested start"; |
| |
| if (state_ != PumpState::STOPPED) |
| return; |
| |
| DCHECK(!timer_.IsRunning()); |
| |
| state_ = PumpState::PENDING_START; |
| PlatformEventObserver<ListenerType>::Start(listener); |
| } |
| |
| // PlatformEventObserver: |
| void Stop() override { |
| DVLOG(2) << "requested stop"; |
| |
| if (state_ == PumpState::STOPPED) |
| return; |
| |
| DCHECK((state_ == PumpState::PENDING_START && !timer_.IsRunning()) || |
| (state_ == PumpState::RUNNING && timer_.IsRunning())); |
| |
| if (timer_.IsRunning()) |
| timer_.Stop(); |
| |
| PlatformEventObserver<ListenerType>::Stop(); |
| state_ = PumpState::STOPPED; |
| } |
| |
| void HandleSensorProviderError() { sensor_provider_.reset(); } |
| |
| void SetSensorProviderForTesting( |
| device::mojom::SensorProviderPtr sensor_provider) { |
| sensor_provider_ = std::move(sensor_provider); |
| } |
| |
| PumpState GetPumpStateForTesting() { return state_; } |
| |
| protected: |
| explicit DeviceSensorEventPump(RenderThread* thread) |
| : PlatformEventObserver<ListenerType>(thread), |
| state_(PumpState::STOPPED) {} |
| |
| ~DeviceSensorEventPump() override { |
| PlatformEventObserver<ListenerType>::StopIfObserving(); |
| } |
| |
| virtual void FireEvent() = 0; |
| |
| struct SensorEntry : public device::mojom::SensorClient { |
| SensorEntry(DeviceSensorEventPump* pump, |
| device::mojom::SensorType sensor_type) |
| : event_pump(pump), |
| sensor_state(SensorState::NOT_INITIALIZED), |
| type(sensor_type), |
| client_binding(this) {} |
| |
| ~SensorEntry() override {} |
| |
| // device::mojom::SensorClient: |
| void RaiseError() override { HandleSensorError(); } |
| |
| // device::mojom::SensorClient: |
| void SensorReadingChanged() override { |
| // Since DeviceSensorEventPump::FireEvent is called in a fixed |
| // frequency, the |shared_buffer| is read frequently, and |
| // Sensor::ConfigureReadingChangeNotifications() is set to false, |
| // so this method is not called and doesn't need to be implemented. |
| NOTREACHED(); |
| } |
| |
| // Mojo callback for SensorProvider::GetSensor(). |
| void OnSensorCreated(device::mojom::SensorCreationResult result, |
| device::mojom::SensorInitParamsPtr params) { |
| // |sensor_state| can be SensorState::SHOULD_SUSPEND if Stop() is called |
| // before OnSensorCreated() is called. |
| DCHECK(sensor_state == SensorState::INITIALIZING || |
| sensor_state == SensorState::SHOULD_SUSPEND); |
| |
| if (!params) { |
| HandleSensorError(); |
| event_pump->DidStartIfPossible(); |
| return; |
| } |
| DCHECK_EQ(device::mojom::SensorCreationResult::SUCCESS, result); |
| |
| constexpr size_t kReadBufferSize = |
| sizeof(device::SensorReadingSharedBuffer); |
| |
| DCHECK_EQ(0u, params->buffer_offset % kReadBufferSize); |
| |
| mode = params->mode; |
| default_config = params->default_configuration; |
| |
| sensor.Bind(std::move(params->sensor)); |
| client_binding.Bind(std::move(params->client_request)); |
| |
| shared_buffer_handle = std::move(params->memory); |
| DCHECK(!shared_buffer); |
| shared_buffer = shared_buffer_handle->MapAtOffset(kReadBufferSize, |
| params->buffer_offset); |
| if (!shared_buffer) { |
| HandleSensorError(); |
| event_pump->DidStartIfPossible(); |
| return; |
| } |
| |
| const device::SensorReadingSharedBuffer* buffer = |
| static_cast<const device::SensorReadingSharedBuffer*>( |
| shared_buffer.get()); |
| shared_buffer_reader.reset( |
| new device::SensorReadingSharedBufferReader(buffer)); |
| |
| default_config.set_frequency( |
| std::min(static_cast<double>(kDefaultPumpFrequencyHz), |
| params->maximum_frequency)); |
| |
| sensor.set_connection_error_handler(base::BindOnce( |
| &SensorEntry::HandleSensorError, base::Unretained(this))); |
| sensor->ConfigureReadingChangeNotifications(false /* disabled */); |
| sensor->AddConfiguration( |
| default_config, base::BindOnce(&SensorEntry::OnSensorAddConfiguration, |
| base::Unretained(this))); |
| } |
| |
| // Mojo callback for Sensor::AddConfiguration(). |
| void OnSensorAddConfiguration(bool success) { |
| if (!success) |
| HandleSensorError(); |
| |
| if (sensor_state == SensorState::INITIALIZING) { |
| sensor_state = SensorState::ACTIVE; |
| event_pump->DidStartIfPossible(); |
| } else if (sensor_state == SensorState::SHOULD_SUSPEND) { |
| sensor->Suspend(); |
| sensor_state = SensorState::SUSPENDED; |
| } |
| } |
| |
| void HandleSensorError() { |
| sensor.reset(); |
| sensor_state = SensorState::NOT_INITIALIZED; |
| shared_buffer_handle.reset(); |
| shared_buffer.reset(); |
| client_binding.Close(); |
| } |
| |
| bool SensorReadingCouldBeRead() { |
| if (!sensor) |
| return false; |
| |
| DCHECK(shared_buffer); |
| |
| if (!shared_buffer_handle->is_valid() || |
| !shared_buffer_reader->GetReading(&reading)) { |
| HandleSensorError(); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool ReadyOrErrored() const { |
| // When some sensors are not available, the pump still needs to fire |
| // events which set the unavailable sensor data fields to null. |
| return sensor_state == SensorState::ACTIVE || |
| sensor_state == SensorState::NOT_INITIALIZED; |
| } |
| |
| void Start(device::mojom::SensorProvider* sensor_provider) { |
| if (sensor_state == SensorState::NOT_INITIALIZED) { |
| sensor_state = SensorState::INITIALIZING; |
| sensor_provider->GetSensor(type, |
| base::BindOnce(&SensorEntry::OnSensorCreated, |
| base::Unretained(this))); |
| } else if (sensor_state == SensorState::SUSPENDED) { |
| sensor->Resume(); |
| sensor_state = SensorState::ACTIVE; |
| event_pump->DidStartIfPossible(); |
| } else if (sensor_state == SensorState::SHOULD_SUSPEND) { |
| // This can happen when calling Start(), Stop(), Start() in a sequence: |
| // After the first Start() call, the sensor state is |
| // SensorState::INITIALIZING. Then after the Stop() call, the sensor |
| // state is SensorState::SHOULD_SUSPEND, and the next Start() call needs |
| // to set the sensor state to be SensorState::INITIALIZING again. |
| sensor_state = SensorState::INITIALIZING; |
| } else { |
| NOTREACHED(); |
| } |
| } |
| |
| void Stop() { |
| if (sensor) { |
| sensor->Suspend(); |
| sensor_state = SensorState::SUSPENDED; |
| } else if (sensor_state == SensorState::INITIALIZING) { |
| // When the sensor needs to be suspended, and it is still in the |
| // SensorState::INITIALIZING state, the sensor creation is not affected |
| // (the SensorEntry::OnSensorCreated() callback will run as usual), but |
| // the sensor is marked as SensorState::SHOULD_SUSPEND, and when the |
| // sensor is created successfully, it will be suspended and its state |
| // will be marked as SensorState::SUSPENDED in the |
| // SensorEntry::OnSensorAddConfiguration(). |
| sensor_state = SensorState::SHOULD_SUSPEND; |
| } |
| } |
| |
| DeviceSensorEventPump* event_pump; |
| device::mojom::SensorPtr sensor; |
| SensorState sensor_state; |
| device::mojom::SensorType type; |
| device::mojom::ReportingMode mode; |
| device::PlatformSensorConfiguration default_config; |
| mojo::ScopedSharedBufferHandle shared_buffer_handle; |
| mojo::ScopedSharedBufferMapping shared_buffer; |
| std::unique_ptr<device::SensorReadingSharedBufferReader> |
| shared_buffer_reader; |
| device::SensorReading reading; |
| mojo::Binding<device::mojom::SensorClient> client_binding; |
| }; |
| |
| friend struct SensorEntry; |
| |
| virtual void DidStartIfPossible() { |
| DVLOG(2) << "did start sensor event pump"; |
| |
| if (state_ != PumpState::PENDING_START) |
| return; |
| |
| if (!SensorsReadyOrErrored()) |
| return; |
| |
| DCHECK(!timer_.IsRunning()); |
| |
| timer_.Start( |
| FROM_HERE, |
| base::TimeDelta::FromMicroseconds(kDefaultPumpDelayMicroseconds), this, |
| &DeviceSensorEventPump::FireEvent); |
| state_ = PumpState::RUNNING; |
| } |
| |
| static RenderFrame* GetRenderFrame() { |
| blink::WebLocalFrame* const web_frame = |
| blink::WebLocalFrame::FrameForCurrentContext(); |
| |
| return RenderFrame::FromWebFrame(web_frame); |
| } |
| |
| device::mojom::SensorProviderPtr sensor_provider_; |
| |
| private: |
| virtual bool SensorsReadyOrErrored() const = 0; |
| |
| PumpState state_; |
| base::RepeatingTimer timer_; |
| |
| DISALLOW_COPY_AND_ASSIGN(DeviceSensorEventPump); |
| }; |
| |
| template class DeviceSensorEventPump<blink::WebDeviceMotionListener>; |
| template class DeviceSensorEventPump<blink::WebDeviceOrientationListener>; |
| |
| } // namespace content |
| |
| #endif // CONTENT_RENDERER_DEVICE_SENSORS_DEVICE_SENSOR_EVENT_PUMP_H_ |