blob: 597da59a5e47f2e853f6432e9017e1d7430e0481 [file] [log] [blame]
// Copyright 2017 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 "extensions/browser/api/media_perception_private/media_perception_api_manager.h"
#include <memory>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/lazy_instance.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/media_analytics_client.h"
#include "chromeos/dbus/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/binding.h"
#include "mojo/public/cpp/bindings/interface_request.h"
#include "mojo/public/cpp/platform/platform_channel.h"
#include "mojo/public/cpp/system/invitation.h"
#include "services/video_capture/public/mojom/device_factory_provider.mojom.h"
namespace extensions {
namespace {
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_SERVICE_ERROR;
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::PROCESS_STATUS_SERVICE_ERROR;
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::
COMPONENT_STATUS_FAILED_TO_INSTALL;
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::unique_ptr<std::string> ExtractVersionFromMountPoint(
const std::string& mount_point) {
return std::make_unique<std::string>(
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,
chromeos::media_perception::mojom::MediaPerceptionControllerClientRequest
request)
: delegate_(delegate), binding_(this, std::move(request)) {
DCHECK(delegate_) << "Delegate not set.";
}
~MediaPerceptionControllerClient() override = default;
// media_perception::mojom::MediaPerceptionControllerClient:
void ConnectToVideoCaptureService(
video_capture::mojom::DeviceFactoryRequest request) override {
DCHECK(delegate_) << "Delegate not set.";
delegate_->BindDeviceFactoryProviderToVideoCaptureService(
&device_factory_provider_);
device_factory_provider_->ConnectToDeviceFactory(std::move(request));
}
private:
// Provides access to methods for talking to core Chrome code.
MediaPerceptionAPIDelegate* delegate_;
// Binding of the MediaPerceptionControllerClient to the message pipe.
mojo::Binding<
chromeos::media_perception::mojom::MediaPerceptionControllerClient>
binding_;
// Bound to the VideoCaptureService to establish the connection to the
// media analytics process.
video_capture::mojom::DeviceFactoryProviderPtr device_factory_provider_;
DISALLOW_COPY_AND_ASSIGN(MediaPerceptionControllerClient);
};
// 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),
scoped_observer_(this),
weak_ptr_factory_(this) {
scoped_observer_.Add(
chromeos::DBusThreadManager::Get()->GetMediaAnalyticsClient());
}
MediaPerceptionAPIManager::~MediaPerceptionAPIManager() {
// Stop the separate media analytics process.
chromeos::UpstartClient* upstart_client =
chromeos::DBusThreadManager::Get()->GetUpstartClient();
upstart_client->StopMediaAnalytics();
}
void MediaPerceptionAPIManager::ActivateMediaPerception(
chromeos::media_perception::mojom::MediaPerceptionRequest request) {
if (media_perception_controller_.is_bound())
media_perception_controller_->ActivateMediaPerception(std::move(request));
}
void MediaPerceptionAPIManager::SetMountPointNonEmptyForTesting() {
mount_point_ = "non-empty-string";
}
void MediaPerceptionAPIManager::GetState(APIStateCallback callback) {
if (analytics_process_state_ == AnalyticsProcessState::RUNNING) {
chromeos::MediaAnalyticsClient* dbus_client =
chromeos::DBusThreadManager::Get()->GetMediaAnalyticsClient();
dbus_client->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::
SERVICE_ERROR_SERVICE_BUSY_LAUNCHING));
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_UNINITIALIZED;
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,
bool success,
const base::FilePath& mount_point) {
if (!success) {
std::move(callback).Run(GetFailedToInstallComponentState());
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::COMPONENT_STATUS_INSTALLED;
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::PROCESS_STATUS_STARTED ||
process_state.status ==
extensions::api::media_perception_private::PROCESS_STATUS_STOPPED);
if (analytics_process_state_ ==
AnalyticsProcessState::CHANGING_PROCESS_STATE) {
std::move(callback).Run(GetProcessStateForServiceError(
extensions::api::media_perception_private::
SERVICE_ERROR_SERVICE_BUSY_LAUNCHING));
return;
}
analytics_process_state_ = AnalyticsProcessState::CHANGING_PROCESS_STATE;
if (process_state.status ==
extensions::api::media_perception_private::PROCESS_STATUS_STOPPED) {
chromeos::UpstartClient* dbus_client =
chromeos::DBusThreadManager::Get()->GetUpstartClient();
base::OnceCallback<void(bool)> stop_callback =
base::BindOnce(&MediaPerceptionAPIManager::UpstartStopProcessCallback,
weak_ptr_factory_.GetWeakPtr(), std::move(callback));
dbus_client->StopMediaAnalytics(std::move(stop_callback));
return;
}
if (process_state.status ==
extensions::api::media_perception_private::PROCESS_STATUS_STARTED) {
// 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::
SERVICE_ERROR_SERVICE_NOT_INSTALLED));
return;
}
chromeos::UpstartClient* dbus_client =
chromeos::DBusThreadManager::Get()->GetUpstartClient();
std::vector<std::string> upstart_env;
upstart_env.push_back(std::string("mount_point=") + mount_point_);
dbus_client->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::
SERVICE_ERROR_SERVICE_NOT_RUNNING));
}
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::
SERVICE_ERROR_SERVICE_BUSY_LAUNCHING));
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;
chromeos::UpstartClient* dbus_client =
chromeos::DBusThreadManager::Get()->GetUpstartClient();
dbus_client->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;
chromeos::UpstartClient* dbus_client =
chromeos::DBusThreadManager::Get()->GetUpstartClient();
dbus_client->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;
chromeos::UpstartClient* dbus_client =
chromeos::DBusThreadManager::Get()->GetUpstartClient();
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_);
dbus_client->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::
SERVICE_ERROR_SERVICE_NOT_RUNNING));
}
void MediaPerceptionAPIManager::SetStateInternal(APIStateCallback callback,
const mri::State& state) {
chromeos::MediaAnalyticsClient* dbus_client =
chromeos::DBusThreadManager::Get()->GetMediaAnalyticsClient();
dbus_client->SetState(
state,
base::BindOnce(&MediaPerceptionAPIManager::StateCallback,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void MediaPerceptionAPIManager::GetDiagnostics(
const APIGetDiagnosticsCallback& callback) {
chromeos::MediaAnalyticsClient* dbus_client =
chromeos::DBusThreadManager::Get()->GetMediaAnalyticsClient();
dbus_client->GetDiagnostics(
base::Bind(&MediaPerceptionAPIManager::GetDiagnosticsCallback,
weak_ptr_factory_.GetWeakPtr(), 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::
SERVICE_ERROR_SERVICE_NOT_RUNNING));
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;
}
SendMojoInvitation(std::move(callback));
}
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::
SERVICE_ERROR_MOJO_CONNECTION_FAILURE));
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_ =
chromeos::media_perception::mojom::MediaPerceptionServicePtr(
chromeos::media_perception::mojom::MediaPerceptionServicePtrInfo(
std::move(server_pipe), 0));
base::ScopedFD fd =
channel.TakeRemoteEndpoint().TakePlatformHandle().TakeFD();
chromeos::DBusThreadManager::Get()
->GetMediaAnalyticsClient()
->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::
SERVICE_ERROR_MOJO_CONNECTION_FAILURE));
return;
}
analytics_process_state_ = AnalyticsProcessState::RUNNING;
extensions::api::media_perception_private::ProcessState state_started;
state_started.status =
extensions::api::media_perception_private::PROCESS_STATUS_STARTED;
// 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::
SERVICE_ERROR_MOJO_CONNECTION_FAILURE));
return;
}
if (!media_perception_service_.is_bound()) {
DLOG(WARNING) << "MediaPerceptionService interface not bound.";
std::move(callback).Run(GetProcessStateForServiceError(
extensions::api::media_perception_private::
SERVICE_ERROR_MOJO_CONNECTION_FAILURE));
return;
}
auto controller_request = mojo::MakeRequest(&media_perception_controller_);
chromeos::media_perception::mojom::MediaPerceptionControllerClientPtr
client_ptr;
media_perception_controller_client_ =
std::make_unique<MediaPerceptionControllerClient>(
delegate, mojo::MakeRequest(&client_ptr));
delegate->SetMediaPerceptionRequestHandler(
base::BindRepeating(&MediaPerceptionAPIManager::ActivateMediaPerception,
weak_ptr_factory_.GetWeakPtr()));
media_perception_service_->GetController(std::move(controller_request),
std::move(client_ptr));
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::
SERVICE_ERROR_SERVICE_UNREACHABLE));
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::PROCESS_STATUS_STOPPED;
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::
SERVICE_ERROR_SERVICE_NOT_RUNNING));
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::
SERVICE_ERROR_SERVICE_UNREACHABLE));
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_STOPPED;
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::
SERVICE_ERROR_SERVICE_NOT_RUNNING));
return;
}
analytics_process_state_ = AnalyticsProcessState::RUNNING;
GetState(std::move(callback));
}
void MediaPerceptionAPIManager::StateCallback(
APIStateCallback callback,
base::Optional<mri::State> result) {
if (!result.has_value()) {
std::move(callback).Run(
GetStateForServiceError(extensions::api::media_perception_private::
SERVICE_ERROR_SERVICE_UNREACHABLE));
return;
}
std::move(callback).Run(
extensions::api::media_perception_private::StateProtoToIdl(
result.value()));
}
void MediaPerceptionAPIManager::GetDiagnosticsCallback(
const APIGetDiagnosticsCallback& callback,
base::Optional<mri::Diagnostics> result) {
if (!result.has_value()) {
callback.Run(GetDiagnosticsForServiceError(
extensions::api::media_perception_private::
SERVICE_ERROR_SERVICE_UNREACHABLE));
return;
}
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