| // Copyright 2017 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "extensions/browser/api/media_perception_private/media_perception_api_manager.h" |
| |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/files/file_path.h" |
| #include "base/functional/bind.h" |
| #include "base/lazy_instance.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/time/time.h" |
| #include "chromeos/ash/components/dbus/dbus_thread_manager.h" |
| #include "chromeos/ash/components/dbus/upstart/upstart_client.h" |
| #include "extensions/browser/api/extensions_api_client.h" |
| #include "extensions/browser/api/media_perception_private/conversion_utils.h" |
| #include "extensions/browser/api/media_perception_private/media_perception_api_delegate.h" |
| #include "extensions/browser/event_router.h" |
| #include "extensions/browser/extension_function.h" |
| #include "mojo/public/cpp/bindings/pending_receiver.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "mojo/public/cpp/bindings/receiver.h" |
| #include "mojo/public/cpp/platform/platform_channel.h" |
| #include "mojo/public/cpp/system/invitation.h" |
| |
| namespace extensions { |
| |
| namespace { |
| |
| const int kStartupDelayMs = 1000; |
| |
| extensions::api::media_perception_private::State GetStateForServiceError( |
| const extensions::api::media_perception_private::ServiceError |
| service_error) { |
| extensions::api::media_perception_private::State state; |
| state.status = |
| extensions::api::media_perception_private::Status::kServiceError; |
| state.service_error = service_error; |
| return state; |
| } |
| |
| extensions::api::media_perception_private::ProcessState |
| GetProcessStateForServiceError( |
| const extensions::api::media_perception_private::ServiceError |
| service_error) { |
| extensions::api::media_perception_private::ProcessState process_state; |
| process_state.status = |
| extensions::api::media_perception_private::ProcessStatus::kServiceError; |
| process_state.service_error = service_error; |
| return process_state; |
| } |
| |
| extensions::api::media_perception_private::Diagnostics |
| GetDiagnosticsForServiceError( |
| const extensions::api::media_perception_private::ServiceError& |
| service_error) { |
| extensions::api::media_perception_private::Diagnostics diagnostics; |
| diagnostics.service_error = service_error; |
| return diagnostics; |
| } |
| |
| extensions::api::media_perception_private::ComponentState |
| GetFailedToInstallComponentState() { |
| extensions::api::media_perception_private::ComponentState component_state; |
| component_state.status = extensions::api::media_perception_private:: |
| ComponentStatus::kFailedToInstall; |
| return component_state; |
| } |
| |
| // Pulls out the version number from a mount_point location for the media |
| // perception component. Mount points look like |
| // /run/imageloader/rtanalytics-light/1.0, where 1.0 is the version string. |
| std::string ExtractVersionFromMountPoint(const std::string& mount_point) { |
| return base::FilePath(mount_point).BaseName().value(); |
| } |
| |
| } // namespace |
| |
| class MediaPerceptionAPIManager::MediaPerceptionControllerClient |
| : public chromeos::media_perception::mojom:: |
| MediaPerceptionControllerClient { |
| public: |
| // delegate is owned by the ExtensionsAPIClient. |
| MediaPerceptionControllerClient( |
| MediaPerceptionAPIDelegate* delegate, |
| mojo::PendingReceiver< |
| chromeos::media_perception::mojom::MediaPerceptionControllerClient> |
| receiver) |
| : delegate_(delegate), receiver_(this, std::move(receiver)) { |
| DCHECK(delegate_) << "Delegate not set."; |
| } |
| |
| MediaPerceptionControllerClient(const MediaPerceptionControllerClient&) = |
| delete; |
| MediaPerceptionControllerClient& operator=( |
| const MediaPerceptionControllerClient&) = delete; |
| |
| ~MediaPerceptionControllerClient() override = default; |
| |
| // media_perception::mojom::MediaPerceptionControllerClient: |
| void ConnectToVideoCaptureService( |
| mojo::PendingReceiver<video_capture::mojom::VideoSourceProvider> receiver) |
| override { |
| DCHECK(delegate_) << "Delegate not set."; |
| delegate_->BindVideoSourceProvider(std::move(receiver)); |
| } |
| |
| private: |
| // Provides access to methods for talking to core Chrome code. |
| raw_ptr<MediaPerceptionAPIDelegate, DanglingUntriaged> delegate_; |
| |
| // Receiver of the MediaPerceptionControllerClient to the message pipe. |
| mojo::Receiver< |
| chromeos::media_perception::mojom::MediaPerceptionControllerClient> |
| receiver_; |
| }; |
| |
| // static |
| MediaPerceptionAPIManager* MediaPerceptionAPIManager::Get( |
| content::BrowserContext* context) { |
| return GetFactoryInstance()->Get(context); |
| } |
| |
| static base::LazyInstance< |
| BrowserContextKeyedAPIFactory<MediaPerceptionAPIManager>>::Leaky g_factory = |
| LAZY_INSTANCE_INITIALIZER; |
| |
| // static |
| BrowserContextKeyedAPIFactory<MediaPerceptionAPIManager>* |
| MediaPerceptionAPIManager::GetFactoryInstance() { |
| return g_factory.Pointer(); |
| } |
| |
| MediaPerceptionAPIManager::MediaPerceptionAPIManager( |
| content::BrowserContext* context) |
| : browser_context_(context), |
| analytics_process_state_(AnalyticsProcessState::IDLE) { |
| // `MediaAnalyticsClient` can be null in tests (browser_tests or |
| // extensions_browsertests). |
| if (auto* client = ash::MediaAnalyticsClient::Get()) { |
| scoped_observation_.Observe(client); |
| } |
| } |
| |
| MediaPerceptionAPIManager::~MediaPerceptionAPIManager() { |
| // Stop the separate media analytics process. |
| // `UpstartClient` can be null in tests (browser_tests or |
| // extensions_browsertests). |
| if (auto* client = ash::UpstartClient::Get()) { |
| client->StopMediaAnalytics(); |
| } |
| } |
| |
| void MediaPerceptionAPIManager::ActivateMediaPerception( |
| mojo::PendingReceiver<chromeos::media_perception::mojom::MediaPerception> |
| receiver) { |
| if (media_perception_controller_.is_bound()) |
| media_perception_controller_->ActivateMediaPerception(std::move(receiver)); |
| } |
| |
| void MediaPerceptionAPIManager::SetMountPointNonEmptyForTesting() { |
| mount_point_ = "non-empty-string"; |
| } |
| |
| void MediaPerceptionAPIManager::GetState(APIStateCallback callback) { |
| if (analytics_process_state_ == AnalyticsProcessState::RUNNING) { |
| ash::MediaAnalyticsClient::Get()->GetState( |
| base::BindOnce(&MediaPerceptionAPIManager::StateCallback, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| return; |
| } |
| |
| if (analytics_process_state_ == |
| AnalyticsProcessState::CHANGING_PROCESS_STATE) { |
| std::move(callback).Run( |
| GetStateForServiceError(extensions::api::media_perception_private:: |
| ServiceError::kServiceBusyLaunching)); |
| return; |
| } |
| |
| // Calling getState with process not running returns State UNINITIALIZED. |
| extensions::api::media_perception_private::State state_uninitialized; |
| state_uninitialized.status = |
| extensions::api::media_perception_private::Status::kUninitialized; |
| std::move(callback).Run(std::move(state_uninitialized)); |
| } |
| |
| void MediaPerceptionAPIManager::SetAnalyticsComponent( |
| const extensions::api::media_perception_private::Component& component, |
| APISetAnalyticsComponentCallback callback) { |
| MediaPerceptionAPIDelegate* delegate = |
| ExtensionsAPIClient::Get()->GetMediaPerceptionAPIDelegate(); |
| if (!delegate) { |
| LOG(WARNING) << "Could not get MediaPerceptionAPIDelegate."; |
| std::move(callback).Run(GetFailedToInstallComponentState()); |
| return; |
| } |
| |
| delegate->LoadCrOSComponent( |
| component.type, |
| base::BindOnce(&MediaPerceptionAPIManager::LoadComponentCallback, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void MediaPerceptionAPIManager::LoadComponentCallback( |
| APISetAnalyticsComponentCallback callback, |
| const extensions::api::media_perception_private::ComponentInstallationError |
| installation_error, |
| const base::FilePath& mount_point) { |
| if (installation_error != extensions::api::media_perception_private:: |
| ComponentInstallationError::kNone) { |
| extensions::api::media_perception_private::ComponentState component_state = |
| GetFailedToInstallComponentState(); |
| component_state.installation_error_code = installation_error; |
| std::move(callback).Run(std::move(component_state)); |
| return; |
| } |
| |
| // If the new component is loaded, override the mount point. |
| mount_point_ = mount_point.value(); |
| |
| extensions::api::media_perception_private::ComponentState component_state; |
| component_state.status = |
| extensions::api::media_perception_private::ComponentStatus::kInstalled; |
| component_state.version = ExtractVersionFromMountPoint(mount_point_); |
| std::move(callback).Run(std::move(component_state)); |
| return; |
| } |
| |
| void MediaPerceptionAPIManager::SetComponentProcessState( |
| const extensions::api::media_perception_private::ProcessState& |
| process_state, |
| APIComponentProcessStateCallback callback) { |
| DCHECK( |
| process_state.status == |
| extensions::api::media_perception_private::ProcessStatus::kStarted || |
| process_state.status == |
| extensions::api::media_perception_private::ProcessStatus::kStopped); |
| if (analytics_process_state_ == |
| AnalyticsProcessState::CHANGING_PROCESS_STATE) { |
| std::move(callback).Run(GetProcessStateForServiceError( |
| extensions::api::media_perception_private::ServiceError:: |
| kServiceBusyLaunching)); |
| return; |
| } |
| |
| analytics_process_state_ = AnalyticsProcessState::CHANGING_PROCESS_STATE; |
| if (process_state.status == |
| extensions::api::media_perception_private::ProcessStatus::kStopped) { |
| base::OnceCallback<void(bool)> stop_callback = |
| base::BindOnce(&MediaPerceptionAPIManager::UpstartStopProcessCallback, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback)); |
| ash::UpstartClient::Get()->StopMediaAnalytics(std::move(stop_callback)); |
| return; |
| } |
| |
| if (process_state.status == |
| extensions::api::media_perception_private::ProcessStatus::kStarted) { |
| // Check if a component is loaded and add the necessary mount_point |
| // information to the Upstart start command. |
| if (mount_point_.empty()) { |
| analytics_process_state_ = AnalyticsProcessState::IDLE; |
| std::move(callback).Run(GetProcessStateForServiceError( |
| extensions::api::media_perception_private::ServiceError:: |
| kServiceNotInstalled)); |
| return; |
| } |
| |
| std::vector<std::string> upstart_env; |
| upstart_env.push_back(std::string("mount_point=") + mount_point_); |
| |
| ash::UpstartClient::Get()->StartMediaAnalytics( |
| upstart_env, |
| base::BindOnce(&MediaPerceptionAPIManager::UpstartStartProcessCallback, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| return; |
| } |
| |
| analytics_process_state_ = AnalyticsProcessState::IDLE; |
| std::move(callback).Run( |
| GetProcessStateForServiceError(extensions::api::media_perception_private:: |
| ServiceError::kServiceNotRunning)); |
| } |
| |
| void MediaPerceptionAPIManager::SetState( |
| const extensions::api::media_perception_private::State& state, |
| APIStateCallback callback) { |
| mri::State state_proto = StateIdlToProto(state); |
| DCHECK(state_proto.status() == mri::State::RUNNING || |
| state_proto.status() == mri::State::SUSPENDED || |
| state_proto.status() == mri::State::RESTARTING || |
| state_proto.status() == mri::State::STOPPED) |
| << "Cannot set state to something other than RUNNING, SUSPENDED " |
| "RESTARTING, or STOPPED."; |
| |
| if (analytics_process_state_ == |
| AnalyticsProcessState::CHANGING_PROCESS_STATE) { |
| std::move(callback).Run( |
| GetStateForServiceError(extensions::api::media_perception_private:: |
| ServiceError::kServiceBusyLaunching)); |
| return; |
| } |
| |
| // Regardless of the state of the media analytics process, always send an |
| // upstart stop command if requested. |
| if (state_proto.status() == mri::State::STOPPED) { |
| analytics_process_state_ = AnalyticsProcessState::CHANGING_PROCESS_STATE; |
| ash::UpstartClient::Get()->StopMediaAnalytics( |
| base::BindOnce(&MediaPerceptionAPIManager::UpstartStopCallback, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| return; |
| } |
| |
| // If the media analytics process is running or not and restart is requested, |
| // then send restart upstart command. |
| if (state_proto.status() == mri::State::RESTARTING) { |
| analytics_process_state_ = AnalyticsProcessState::CHANGING_PROCESS_STATE; |
| ash::UpstartClient::Get()->RestartMediaAnalytics( |
| base::BindOnce(&MediaPerceptionAPIManager::UpstartRestartCallback, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| return; |
| } |
| |
| if (analytics_process_state_ == AnalyticsProcessState::RUNNING) { |
| SetStateInternal(std::move(callback), state_proto); |
| return; |
| } |
| |
| // Analytics process is in state IDLE. |
| if (state_proto.status() == mri::State::RUNNING) { |
| analytics_process_state_ = AnalyticsProcessState::CHANGING_PROCESS_STATE; |
| std::vector<std::string> upstart_env; |
| // Check if a component is loaded and add the necessary mount_point |
| // information to the Upstart start command. If no component is loaded, |
| // StartMediaAnalytics will likely fail and the client will get an error |
| // callback. StartMediaAnalytics is still called, however, in the case that |
| // the old CrOS deployment path for the media analytics process is still in |
| // use. |
| // TODO(crbug.com/789376): When the old deployment path is no longer in use, |
| // only start media analytics if the mount point is set. |
| if (!mount_point_.empty()) |
| upstart_env.push_back(std::string("mount_point=") + mount_point_); |
| |
| ash::UpstartClient::Get()->StartMediaAnalytics( |
| upstart_env, |
| base::BindOnce(&MediaPerceptionAPIManager::UpstartStartCallback, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback), |
| state_proto)); |
| return; |
| } |
| |
| std::move(callback).Run( |
| GetStateForServiceError(extensions::api::media_perception_private:: |
| ServiceError::kServiceNotRunning)); |
| } |
| |
| void MediaPerceptionAPIManager::SetStateInternal(APIStateCallback callback, |
| const mri::State& state) { |
| ash::MediaAnalyticsClient::Get()->SetState( |
| state, |
| base::BindOnce(&MediaPerceptionAPIManager::StateCallback, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void MediaPerceptionAPIManager::GetDiagnostics( |
| APIGetDiagnosticsCallback callback) { |
| ash::MediaAnalyticsClient::Get()->GetDiagnostics( |
| base::BindOnce(&MediaPerceptionAPIManager::GetDiagnosticsCallback, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void MediaPerceptionAPIManager::UpstartStartProcessCallback( |
| APIComponentProcessStateCallback callback, |
| bool succeeded) { |
| if (!succeeded) { |
| analytics_process_state_ = AnalyticsProcessState::IDLE; |
| std::move(callback).Run(GetProcessStateForServiceError( |
| extensions::api::media_perception_private::ServiceError:: |
| kServiceNotRunning)); |
| return; |
| } |
| |
| // Check if the extensions api client is available in this context. Code path |
| // used for testing. |
| if (!ExtensionsAPIClient::Get()) { |
| LOG(ERROR) << "Could not get ExtensionsAPIClient."; |
| OnBootstrapMojoConnection(std::move(callback), true); |
| return; |
| } |
| |
| // TODO(crbug.com/1003968): Look into using |
| // ObjectProxy::WaitForServiceToBeAvailable instead, since a timeout is |
| // inherently not deterministic, even if it works in practice. |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&MediaPerceptionAPIManager::SendMojoInvitation, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback)), |
| base::Milliseconds(kStartupDelayMs)); |
| } |
| |
| void MediaPerceptionAPIManager::SendMojoInvitation( |
| APIComponentProcessStateCallback callback) { |
| MediaPerceptionAPIDelegate* delegate = |
| ExtensionsAPIClient::Get()->GetMediaPerceptionAPIDelegate(); |
| if (!delegate) { |
| DLOG(WARNING) << "Could not get MediaPerceptionAPIDelegate."; |
| std::move(callback).Run(GetProcessStateForServiceError( |
| extensions::api::media_perception_private::ServiceError:: |
| kMojoConnectionFailure)); |
| return; |
| } |
| |
| mojo::OutgoingInvitation invitation; |
| mojo::PlatformChannel channel; |
| mojo::ScopedMessagePipeHandle server_pipe = |
| invitation.AttachMessagePipe("mpp-connector-pipe"); |
| mojo::OutgoingInvitation::Send(std::move(invitation), |
| base::kNullProcessHandle, |
| channel.TakeLocalEndpoint()); |
| |
| media_perception_service_.reset(); |
| media_perception_service_.Bind( |
| mojo::PendingRemote< |
| chromeos::media_perception::mojom::MediaPerceptionService>( |
| std::move(server_pipe), 0)); |
| |
| base::ScopedFD fd = |
| channel.TakeRemoteEndpoint().TakePlatformHandle().TakeFD(); |
| ash::MediaAnalyticsClient::Get()->BootstrapMojoConnection( |
| std::move(fd), |
| base::BindOnce(&MediaPerceptionAPIManager::OnBootstrapMojoConnection, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void MediaPerceptionAPIManager::OnBootstrapMojoConnection( |
| APIComponentProcessStateCallback callback, |
| bool succeeded) { |
| if (!succeeded) { |
| analytics_process_state_ = AnalyticsProcessState::UNKNOWN; |
| std::move(callback).Run(GetProcessStateForServiceError( |
| extensions::api::media_perception_private::ServiceError:: |
| kMojoConnectionFailure)); |
| return; |
| } |
| |
| analytics_process_state_ = AnalyticsProcessState::RUNNING; |
| extensions::api::media_perception_private::ProcessState state_started; |
| state_started.status = |
| extensions::api::media_perception_private::ProcessStatus::kStarted; |
| |
| // Check if the extensions api client is available in this context. Code path |
| // used for testing. |
| if (!ExtensionsAPIClient::Get()) { |
| DLOG(ERROR) << "Could not get ExtensionsAPIClient."; |
| std::move(callback).Run(std::move(state_started)); |
| return; |
| } |
| |
| MediaPerceptionAPIDelegate* delegate = |
| ExtensionsAPIClient::Get()->GetMediaPerceptionAPIDelegate(); |
| if (!delegate) { |
| DLOG(WARNING) << "Could not get MediaPerceptionAPIDelegate."; |
| std::move(callback).Run(GetProcessStateForServiceError( |
| extensions::api::media_perception_private::ServiceError:: |
| kMojoConnectionFailure)); |
| return; |
| } |
| |
| if (!media_perception_service_.is_bound()) { |
| DLOG(WARNING) << "MediaPerceptionService interface not bound."; |
| std::move(callback).Run(GetProcessStateForServiceError( |
| extensions::api::media_perception_private::ServiceError:: |
| kMojoConnectionFailure)); |
| return; |
| } |
| |
| media_perception_controller_.reset(); |
| auto controller_receiver = |
| media_perception_controller_.BindNewPipeAndPassReceiver(); |
| |
| mojo::PendingRemote< |
| chromeos::media_perception::mojom::MediaPerceptionControllerClient> |
| client_remote; |
| media_perception_controller_client_ = |
| std::make_unique<MediaPerceptionControllerClient>( |
| delegate, client_remote.InitWithNewPipeAndPassReceiver()); |
| delegate->SetMediaPerceptionRequestHandler( |
| base::BindRepeating(&MediaPerceptionAPIManager::ActivateMediaPerception, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| media_perception_service_->GetController(std::move(controller_receiver), |
| std::move(client_remote)); |
| std::move(callback).Run(std::move(state_started)); |
| } |
| |
| void MediaPerceptionAPIManager::UpstartStopProcessCallback( |
| APIComponentProcessStateCallback callback, |
| bool succeeded) { |
| if (!succeeded) { |
| analytics_process_state_ = AnalyticsProcessState::UNKNOWN; |
| std::move(callback).Run(GetProcessStateForServiceError( |
| extensions::api::media_perception_private::ServiceError:: |
| kServiceUnreachable)); |
| return; |
| } |
| analytics_process_state_ = AnalyticsProcessState::IDLE; |
| // Stopping the process succeeded so fire a callback with status STOPPED. |
| extensions::api::media_perception_private::ProcessState state_stopped; |
| state_stopped.status = |
| extensions::api::media_perception_private::ProcessStatus::kStopped; |
| std::move(callback).Run(std::move(state_stopped)); |
| } |
| |
| void MediaPerceptionAPIManager::UpstartStartCallback(APIStateCallback callback, |
| const mri::State& state, |
| bool succeeded) { |
| if (!succeeded) { |
| analytics_process_state_ = AnalyticsProcessState::IDLE; |
| std::move(callback).Run( |
| GetStateForServiceError(extensions::api::media_perception_private:: |
| ServiceError::kServiceNotRunning)); |
| return; |
| } |
| analytics_process_state_ = AnalyticsProcessState::RUNNING; |
| SetStateInternal(std::move(callback), state); |
| } |
| |
| void MediaPerceptionAPIManager::UpstartStopCallback(APIStateCallback callback, |
| bool succeeded) { |
| if (!succeeded) { |
| analytics_process_state_ = AnalyticsProcessState::UNKNOWN; |
| std::move(callback).Run( |
| GetStateForServiceError(extensions::api::media_perception_private:: |
| ServiceError::kServiceUnreachable)); |
| return; |
| } |
| analytics_process_state_ = AnalyticsProcessState::IDLE; |
| // Stopping the process succeeded so fire a callback with status STOPPED. |
| extensions::api::media_perception_private::State state_stopped; |
| state_stopped.status = |
| extensions::api::media_perception_private::Status::kStopped; |
| std::move(callback).Run(std::move(state_stopped)); |
| } |
| |
| void MediaPerceptionAPIManager::UpstartRestartCallback( |
| APIStateCallback callback, |
| bool succeeded) { |
| if (!succeeded) { |
| analytics_process_state_ = AnalyticsProcessState::IDLE; |
| std::move(callback).Run( |
| GetStateForServiceError(extensions::api::media_perception_private:: |
| ServiceError::kServiceNotRunning)); |
| return; |
| } |
| analytics_process_state_ = AnalyticsProcessState::RUNNING; |
| GetState(std::move(callback)); |
| } |
| |
| void MediaPerceptionAPIManager::StateCallback( |
| APIStateCallback callback, |
| std::optional<mri::State> result) { |
| if (!result.has_value()) { |
| std::move(callback).Run( |
| GetStateForServiceError(extensions::api::media_perception_private:: |
| ServiceError::kServiceUnreachable)); |
| return; |
| } |
| std::move(callback).Run( |
| extensions::api::media_perception_private::StateProtoToIdl( |
| result.value())); |
| } |
| |
| void MediaPerceptionAPIManager::GetDiagnosticsCallback( |
| APIGetDiagnosticsCallback callback, |
| std::optional<mri::Diagnostics> result) { |
| if (!result.has_value()) { |
| std::move(callback).Run(GetDiagnosticsForServiceError( |
| extensions::api::media_perception_private::ServiceError:: |
| kServiceUnreachable)); |
| return; |
| } |
| std::move(callback).Run( |
| extensions::api::media_perception_private::DiagnosticsProtoToIdl( |
| result.value())); |
| } |
| |
| void MediaPerceptionAPIManager::OnDetectionSignal( |
| const mri::MediaPerception& media_perception_proto) { |
| EventRouter* router = EventRouter::Get(browser_context_); |
| DCHECK(router) << "EventRouter is null."; |
| |
| extensions::api::media_perception_private::MediaPerception media_perception = |
| extensions::api::media_perception_private::MediaPerceptionProtoToIdl( |
| media_perception_proto); |
| std::unique_ptr<Event> event(new Event( |
| events::MEDIA_PERCEPTION_PRIVATE_ON_MEDIA_PERCEPTION, |
| extensions::api::media_perception_private::OnMediaPerception::kEventName, |
| extensions::api::media_perception_private::OnMediaPerception::Create( |
| media_perception))); |
| router->BroadcastEvent(std::move(event)); |
| } |
| |
| } // namespace extensions |