| // Copyright 2018 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 "diagnostics/wilco_dtc_supportd/core.h" |
| |
| #include <algorithm> |
| #include <cstddef> |
| #include <utility> |
| |
| #include <base/barrier_closure.h> |
| #include <base/bind.h> |
| #include <base/logging.h> |
| #include <base/optional.h> |
| #include <base/strings/string_util.h> |
| #include <base/strings/stringprintf.h> |
| #include <base/threading/thread_task_runner_handle.h> |
| |
| #include "diagnostics/wilco_dtc_supportd/grpc_client_manager.h" |
| #include "diagnostics/wilco_dtc_supportd/mojo_service.h" |
| #include "diagnostics/wilco_dtc_supportd/mojo_service_factory.h" |
| #include "diagnostics/wilco_dtc_supportd/probe_service_impl.h" |
| |
| #include "mojo/cros_healthd_probe.mojom.h" |
| |
| namespace diagnostics { |
| |
| namespace { |
| |
| using EcEvent = EcService::EcEvent; |
| using EcEventReason = EcService::EcEvent::Reason; |
| using MojomWilcoDtcSupportdWebRequestStatus = |
| chromeos::wilco_dtc_supportd::mojom::WilcoDtcSupportdWebRequestStatus; |
| using MojomWilcoDtcSupportdWebRequestHttpMethod = |
| chromeos::wilco_dtc_supportd::mojom::WilcoDtcSupportdWebRequestHttpMethod; |
| |
| // Converts HTTP method into an appropriate mojom one. |
| bool ConvertWebRequestHttpMethodToMojom( |
| Core::WebRequestHttpMethod http_method, |
| MojomWilcoDtcSupportdWebRequestHttpMethod* mojo_http_method_out) { |
| DCHECK(mojo_http_method_out); |
| switch (http_method) { |
| case Core::WebRequestHttpMethod::kGet: |
| *mojo_http_method_out = MojomWilcoDtcSupportdWebRequestHttpMethod::kGet; |
| return true; |
| case Core::WebRequestHttpMethod::kHead: |
| *mojo_http_method_out = MojomWilcoDtcSupportdWebRequestHttpMethod::kHead; |
| return true; |
| case Core::WebRequestHttpMethod::kPost: |
| *mojo_http_method_out = MojomWilcoDtcSupportdWebRequestHttpMethod::kPost; |
| return true; |
| case Core::WebRequestHttpMethod::kPut: |
| *mojo_http_method_out = MojomWilcoDtcSupportdWebRequestHttpMethod::kPut; |
| return true; |
| case Core::WebRequestHttpMethod::kPatch: |
| *mojo_http_method_out = MojomWilcoDtcSupportdWebRequestHttpMethod::kPatch; |
| return true; |
| } |
| return false; |
| } |
| |
| // Convert the result back from mojom status. |
| bool ConvertStatusFromMojom(MojomWilcoDtcSupportdWebRequestStatus mojo_status, |
| Core::WebRequestStatus* status_out) { |
| DCHECK(status_out); |
| switch (mojo_status) { |
| case MojomWilcoDtcSupportdWebRequestStatus::kOk: |
| *status_out = Core::WebRequestStatus::kOk; |
| return true; |
| case MojomWilcoDtcSupportdWebRequestStatus::kNetworkError: |
| *status_out = Core::WebRequestStatus::kNetworkError; |
| return true; |
| case MojomWilcoDtcSupportdWebRequestStatus::kHttpError: |
| *status_out = Core::WebRequestStatus::kHttpError; |
| return true; |
| } |
| return false; |
| } |
| |
| bool ConvertPowerEventToGrpc( |
| PowerdEventService::Observer::PowerEventType type, |
| grpc_api::HandlePowerNotificationRequest::PowerEvent* type_out) { |
| DCHECK(type_out); |
| switch (type) { |
| case PowerdEventService::Observer::PowerEventType::kAcInsert: |
| *type_out = grpc_api::HandlePowerNotificationRequest::AC_INSERT; |
| return true; |
| case PowerdEventService::Observer::PowerEventType::kAcRemove: |
| *type_out = grpc_api::HandlePowerNotificationRequest::AC_REMOVE; |
| return true; |
| case PowerdEventService::Observer::PowerEventType::kOsSuspend: |
| *type_out = grpc_api::HandlePowerNotificationRequest::OS_SUSPEND; |
| return true; |
| case PowerdEventService::Observer::PowerEventType::kOsResume: |
| *type_out = grpc_api::HandlePowerNotificationRequest::OS_RESUME; |
| return true; |
| } |
| return false; |
| } |
| |
| } // namespace |
| |
| Core::Core(Delegate* delegate, |
| const GrpcClientManager* grpc_client_manager, |
| const std::vector<std::string>& grpc_service_uris, |
| MojoServiceFactory* mojo_service_factory) |
| : delegate_(delegate), |
| grpc_client_manager_(grpc_client_manager), |
| grpc_service_uris_(grpc_service_uris), |
| grpc_server_(base::ThreadTaskRunnerHandle::Get(), grpc_service_uris_), |
| mojo_service_factory_(mojo_service_factory) { |
| DCHECK(delegate); |
| DCHECK(grpc_client_manager_); |
| DCHECK(mojo_service_factory_); |
| ec_service_ = delegate_->CreateEcService(); |
| probe_service_ = delegate->CreateProbeService(this); |
| DCHECK(ec_service_); |
| ec_service_->AddObserver(this); |
| } |
| |
| Core::~Core() = default; |
| |
| bool Core::Start() { |
| // Associate RPCs of the to-be-exposed gRPC interface with methods of |
| // |grpc_service_|. |
| grpc_server_.RegisterHandler( |
| &grpc_api::WilcoDtcSupportd::AsyncService::RequestSendMessageToUi, |
| base::Bind(&GrpcService::SendMessageToUi, |
| base::Unretained(&grpc_service_))); |
| grpc_server_.RegisterHandler( |
| &grpc_api::WilcoDtcSupportd::AsyncService::RequestGetProcData, |
| base::Bind(&GrpcService::GetProcData, base::Unretained(&grpc_service_))); |
| grpc_server_.RegisterHandler( |
| &grpc_api::WilcoDtcSupportd::AsyncService::RequestGetSysfsData, |
| base::Bind(&GrpcService::GetSysfsData, base::Unretained(&grpc_service_))); |
| grpc_server_.RegisterHandler( |
| &grpc_api::WilcoDtcSupportd::AsyncService::RequestGetEcTelemetry, |
| base::Bind(&GrpcService::GetEcTelemetry, |
| base::Unretained(&grpc_service_))); |
| grpc_server_.RegisterHandler( |
| &grpc_api::WilcoDtcSupportd::AsyncService::RequestPerformWebRequest, |
| base::Bind(&GrpcService::PerformWebRequest, |
| base::Unretained(&grpc_service_))); |
| grpc_server_.RegisterHandler( |
| &grpc_api::WilcoDtcSupportd::AsyncService::RequestGetAvailableRoutines, |
| base::Bind(&GrpcService::GetAvailableRoutines, |
| base::Unretained(&grpc_service_))); |
| grpc_server_.RegisterHandler( |
| &grpc_api::WilcoDtcSupportd::AsyncService::RequestRunRoutine, |
| base::Bind(&GrpcService::RunRoutine, base::Unretained(&grpc_service_))); |
| grpc_server_.RegisterHandler( |
| &grpc_api::WilcoDtcSupportd::AsyncService::RequestGetRoutineUpdate, |
| base::Bind(&GrpcService::GetRoutineUpdate, |
| base::Unretained(&grpc_service_))); |
| grpc_server_.RegisterHandler( |
| &grpc_api::WilcoDtcSupportd::AsyncService::RequestGetOsVersion, |
| base::Bind(&GrpcService::GetOsVersion, base::Unretained(&grpc_service_))); |
| grpc_server_.RegisterHandler( |
| &grpc_api::WilcoDtcSupportd::AsyncService::RequestGetVpdField, |
| base::Bind(&GrpcService::GetVpdField, base::Unretained(&grpc_service_))); |
| grpc_server_.RegisterHandler( |
| &grpc_api::WilcoDtcSupportd::AsyncService::RequestGetConfigurationData, |
| base::Bind(&GrpcService::GetConfigurationData, |
| base::Unretained(&grpc_service_))); |
| grpc_server_.RegisterHandler( |
| &grpc_api::WilcoDtcSupportd::AsyncService::RequestGetDriveSystemData, |
| base::Bind(&GrpcService::GetDriveSystemData, |
| base::Unretained(&grpc_service_))); |
| grpc_server_.RegisterHandler( |
| &grpc_api::WilcoDtcSupportd::AsyncService:: |
| RequestRequestBluetoothDataNotification, |
| base::Bind(&GrpcService::RequestBluetoothDataNotification, |
| base::Unretained(&grpc_service_))); |
| grpc_server_.RegisterHandler( |
| &grpc_api::WilcoDtcSupportd::AsyncService:: |
| RequestGetStatefulPartitionAvailableCapacity, |
| base::Bind(&GrpcService::GetStatefulPartitionAvailableCapacity, |
| base::Unretained(&grpc_service_))); |
| |
| // Start the gRPC server that listens for incoming gRPC requests. |
| VLOG(1) << "Starting gRPC server"; |
| if (!grpc_server_.Start()) { |
| LOG(ERROR) << "Failed to start the gRPC server listening on: " |
| << base::JoinString(grpc_service_uris_, ", "); |
| return false; |
| } |
| |
| VLOG(0) << "Successfully started gRPC server listening on " |
| << base::JoinString(grpc_service_uris_, ","); |
| |
| // Start EC event service. |
| if (!ec_service_->Start()) { |
| LOG(WARNING) |
| << "Failed to start EC event service. EC events will be ignored."; |
| } |
| |
| return true; |
| } |
| |
| void Core::ShutDown(base::OnceClosure on_shutdown_callback) { |
| VLOG(1) << "Tearing down gRPC server, gRPC wilco_dtc clients, " |
| "EC event service and D-Bus server"; |
| UnsubscribeFromEventServices(); |
| const base::Closure barrier_closure = |
| base::BarrierClosure(2, std::move(on_shutdown_callback)); |
| ec_service_->ShutDown(barrier_closure); |
| grpc_server_.ShutDown(barrier_closure); |
| } |
| |
| void Core::CreateDbusAdapters(const scoped_refptr<dbus::Bus>& bus) { |
| DCHECK(bus); |
| |
| bluetooth_client_ = delegate_->CreateBluetoothClient(bus); |
| DCHECK(bluetooth_client_); |
| |
| debugd_adapter_ = delegate_->CreateDebugdAdapter(bus); |
| DCHECK(debugd_adapter_); |
| |
| powerd_adapter_ = delegate_->CreatePowerdAdapter(bus); |
| DCHECK(powerd_adapter_); |
| |
| bluetooth_event_service_ = |
| delegate_->CreateBluetoothEventService(bluetooth_client_.get()); |
| DCHECK(bluetooth_event_service_); |
| bluetooth_event_service_->AddObserver(this); |
| |
| powerd_event_service_ = |
| delegate_->CreatePowerdEventService(powerd_adapter_.get()); |
| DCHECK(powerd_event_service_); |
| powerd_event_service_->AddObserver(this); |
| } |
| |
| bool Core::GetCrosHealthdDiagnosticsService( |
| chromeos::cros_healthd::mojom::CrosHealthdDiagnosticsServiceRequest |
| service) { |
| MojoService* mojo_service = mojo_service_factory_->Get(); |
| if (!mojo_service) { |
| LOG(WARNING) << "GetCrosHealthdDiagnosticsService happens before Mojo " |
| << "connection is established."; |
| return false; |
| } |
| |
| mojo_service->GetCrosHealthdDiagnosticsService(std::move(service)); |
| return true; |
| } |
| |
| bool Core::BindCrosHealthdProbeService( |
| chromeos::cros_healthd::mojom::CrosHealthdProbeServiceRequest service) { |
| MojoService* mojo_service = mojo_service_factory_->Get(); |
| if (!mojo_service) { |
| LOG(WARNING) << "BindCrosHealthdProbeService happens before Mojo " |
| << "connection is established."; |
| return false; |
| } |
| |
| mojo_service->GetCrosHealthdProbeService(std::move(service)); |
| return true; |
| } |
| |
| void Core::SendWilcoDtcMessageToUi(const std::string& json_message, |
| const SendMessageToUiCallback& callback) { |
| VLOG(1) << "SendWilcoDtcMessageToUi() json_message=" << json_message; |
| MojoService* mojo_service = mojo_service_factory_->Get(); |
| if (!mojo_service) { |
| constexpr char kErrMsg[] = |
| "GetConfigurationDataFromBrowser happens before " |
| "Mojo connection is established."; |
| LOG(WARNING) << kErrMsg; |
| callback.Run(grpc::Status(grpc::StatusCode::UNKNOWN, kErrMsg), ""); |
| return; |
| } |
| mojo_service->SendWilcoDtcMessageToUi(json_message, callback); |
| } |
| |
| void Core::PerformWebRequestToBrowser( |
| WebRequestHttpMethod http_method, |
| const std::string& url, |
| const std::vector<std::string>& headers, |
| const std::string& request_body, |
| const PerformWebRequestToBrowserCallback& callback) { |
| VLOG(1) << "Core::PerformWebRequestToBrowser"; |
| |
| MojoService* mojo_service = mojo_service_factory_->Get(); |
| if (!mojo_service) { |
| LOG(WARNING) << "PerformWebRequestToBrowser happens before Mojo connection " |
| << "is established."; |
| callback.Run(WebRequestStatus::kInternalError, 0 /* http_status */, |
| "" /* response_body */); |
| return; |
| } |
| |
| MojomWilcoDtcSupportdWebRequestHttpMethod mojo_http_method; |
| if (!ConvertWebRequestHttpMethodToMojom(http_method, &mojo_http_method)) { |
| LOG(ERROR) << "Unknown gRPC http method: " << static_cast<int>(http_method); |
| callback.Run(WebRequestStatus::kInternalError, 0 /* http_status */, |
| "" /* response_body */); |
| return; |
| } |
| |
| mojo_service->PerformWebRequest( |
| mojo_http_method, url, headers, request_body, |
| base::Bind( |
| [](const PerformWebRequestToBrowserCallback& callback, |
| MojomWilcoDtcSupportdWebRequestStatus mojo_status, int http_status, |
| base::StringPiece response_body) { |
| WebRequestStatus status; |
| if (!ConvertStatusFromMojom(mojo_status, &status)) { |
| LOG(ERROR) << "Unknown mojo web request status: " << mojo_status; |
| callback.Run(WebRequestStatus::kInternalError, |
| 0 /* http_status */, "" /* response_body */); |
| return; |
| } |
| callback.Run(status, http_status, response_body); |
| }, |
| callback)); |
| } |
| |
| void Core::GetAvailableRoutinesToService( |
| const GetAvailableRoutinesToServiceCallback& callback) { |
| routine_service_.GetAvailableRoutines(callback); |
| } |
| |
| void Core::RunRoutineToService(const grpc_api::RunRoutineRequest& request, |
| const RunRoutineToServiceCallback& callback) { |
| routine_service_.RunRoutine(request, callback); |
| } |
| |
| void Core::GetRoutineUpdateRequestToService( |
| int uuid, |
| grpc_api::GetRoutineUpdateRequest::Command command, |
| bool include_output, |
| const GetRoutineUpdateRequestToServiceCallback& callback) { |
| routine_service_.GetRoutineUpdate(uuid, command, include_output, callback); |
| } |
| |
| void Core::GetConfigurationDataFromBrowser( |
| const GetConfigurationDataFromBrowserCallback& callback) { |
| VLOG(1) << "Core::GetConfigurationDataFromBrowser"; |
| |
| MojoService* mojo_service = mojo_service_factory_->Get(); |
| if (!mojo_service) { |
| LOG(WARNING) << "GetConfigurationDataFromBrowser happens before Mojo " |
| << "connection is established."; |
| callback.Run("" /* json_configuration_data */); |
| return; |
| } |
| |
| mojo_service->GetConfigurationData(callback); |
| } |
| |
| void Core::GetDriveSystemData(DriveSystemDataType data_type, |
| const GetDriveSystemDataCallback& callback) { |
| if (!debugd_adapter_) { |
| LOG(WARNING) << "DebugdAdapter is not yet ready for incoming requests"; |
| callback.Run("", false /* success */); |
| return; |
| } |
| |
| auto result_callback = base::Bind( |
| [](const GetDriveSystemDataCallback& callback, const std::string& result, |
| brillo::Error* error) { |
| if (error) { |
| LOG(WARNING) << "Debugd smartctl failed with error: " |
| << error->GetMessage(); |
| callback.Run("", false /* success */); |
| return; |
| } |
| callback.Run(result, true /* success */); |
| }, |
| callback); |
| |
| switch (data_type) { |
| case DriveSystemDataType::kSmartAttributes: |
| debugd_adapter_->GetSmartAttributes(result_callback); |
| break; |
| case DriveSystemDataType::kIdentityAttributes: |
| debugd_adapter_->GetNvmeIdentity(result_callback); |
| break; |
| } |
| } |
| |
| void Core::RequestBluetoothDataNotification() { |
| VLOG(1) << "WilcoDtcSupportdCore::RequestBluetoothDataNotification"; |
| |
| if (!bluetooth_event_service_) { |
| VLOG(1) << "Bluetooth event service not yet ready"; |
| return; |
| } |
| |
| NotifyClientsBluetoothAdapterState( |
| bluetooth_event_service_->GetLatestEvent()); |
| } |
| |
| void Core::ProbeTelemetryInfo( |
| std::vector<chromeos::cros_healthd::mojom::ProbeCategoryEnum> categories, |
| ProbeTelemetryInfoCallback callback) { |
| VLOG(1) << "Core::ProbeTelemetryInfo"; |
| probe_service_->ProbeTelemetryInfo(std::move(categories), |
| std::move(callback)); |
| } |
| |
| EcService* Core::GetEcService() { |
| DCHECK(ec_service_); |
| return ec_service_.get(); |
| } |
| |
| void Core::BluetoothAdapterDataChanged( |
| const std::vector<BluetoothEventService::AdapterData>& adapters) { |
| VLOG(1) << "Core::BluetoothAdapterDataChanged"; |
| |
| NotifyClientsBluetoothAdapterState(adapters); |
| } |
| |
| void Core::OnPowerdEvent(PowerEventType type) { |
| VLOG(1) << "Core::OnPowerdEvent: " << static_cast<int>(type); |
| |
| grpc_api::HandlePowerNotificationRequest::PowerEvent grpc_type; |
| if (!ConvertPowerEventToGrpc(type, &grpc_type)) { |
| LOG(ERROR) << "Unable to convert power event to gRPC power event: " |
| << static_cast<int>(type); |
| return; |
| } |
| |
| grpc_api::HandlePowerNotificationRequest request; |
| request.set_power_event(grpc_type); |
| |
| for (auto& client : grpc_client_manager_->GetClients()) { |
| client->CallRpc( |
| &grpc_api::WilcoDtc::Stub::AsyncHandlePowerNotification, request, |
| base::Bind([](grpc::Status status, |
| std::unique_ptr<grpc_api::HandlePowerNotificationResponse> |
| response) { |
| if (!status.ok()) { |
| VLOG(1) << "Failed to call HandlePowerNotification gRPC " |
| "method on wilco_dtc. grpc error code: " |
| << status.error_code() |
| << ", error message: " << status.error_message(); |
| return; |
| } |
| VLOG(1) << "gRPC method HandlePowerNotification was " |
| "successfully called on wilco_dtc"; |
| })); |
| } |
| } |
| |
| void Core::OnEcEvent(const EcEvent& ec_event) { |
| VLOG(1) << "Core::OnEcEvent: type=" << static_cast<int>(ec_event.type) |
| << " reason=" << static_cast<int>(ec_event.GetReason()); |
| |
| SendGrpcEcEventToWilcoDtc(ec_event); |
| |
| // Parse EcEventReason into a MojoEvent and forward to the delegate. |
| // We only will forward certain events. If they aren't relevant, ignore. |
| switch (ec_event.GetReason()) { |
| case EcEventReason::kNonWilcoCharger: |
| SendMojoEcEventToBrowser(MojoEvent::kNonWilcoCharger); |
| break; |
| case EcEventReason::kLowPowerCharger: |
| SendMojoEcEventToBrowser(MojoEvent::kLowPowerCharger); |
| break; |
| case EcEventReason::kBatteryAuth: |
| SendMojoEcEventToBrowser(MojoEvent::kBatteryAuth); |
| break; |
| case EcEventReason::kDockDisplay: |
| SendMojoEcEventToBrowser(MojoEvent::kDockDisplay); |
| break; |
| case EcEventReason::kDockThunderbolt: |
| SendMojoEcEventToBrowser(MojoEvent::kDockThunderbolt); |
| break; |
| case EcEventReason::kIncompatibleDock: |
| SendMojoEcEventToBrowser(MojoEvent::kIncompatibleDock); |
| break; |
| case EcEventReason::kDockError: |
| SendMojoEcEventToBrowser(MojoEvent::kDockError); |
| break; |
| case EcEventReason::kSysNotification: |
| VLOG(2) << "Received EC event that doesn't trigger a mojo event"; |
| break; |
| case EcEventReason::kNonSysNotification: |
| VLOG(2) << "Received a non-system notification EC event"; |
| break; |
| } |
| } |
| |
| void Core::SendGrpcEcEventToWilcoDtc(const EcEvent& ec_event) { |
| VLOG(1) << "Core::SendGrpcEcEventToWilcoDtc"; |
| |
| size_t payload_size = ec_event.PayloadSizeInBytes(); |
| if (payload_size > sizeof(ec_event.payload)) { |
| VLOG(2) << "Received EC event with invalid payload size: " << payload_size; |
| return; |
| } |
| |
| grpc_api::HandleEcNotificationRequest request; |
| request.set_type(ec_event.type); |
| request.set_payload(&ec_event.payload, payload_size); |
| |
| for (auto& client : grpc_client_manager_->GetClients()) { |
| client->CallRpc( |
| &grpc_api::WilcoDtc::Stub::AsyncHandleEcNotification, request, |
| base::Bind([](grpc::Status status, |
| std::unique_ptr<grpc_api::HandleEcNotificationResponse> |
| response) { |
| if (!status.ok()) { |
| VLOG(1) |
| << "Failed to call HandleEcNotificationRequest gRPC method on " |
| "wilco_dtc. grpc error code: " |
| << status.error_code() |
| << ", error message: " << status.error_message(); |
| return; |
| } |
| VLOG(1) << "gRPC method HandleEcNotificationRequest was successfully" |
| "called on wilco_dtc"; |
| })); |
| } |
| } |
| |
| void Core::SendMojoEcEventToBrowser(const MojoEvent& mojo_event) { |
| VLOG(1) << "Core::HandleEvent"; |
| |
| MojoService* mojo_service = mojo_service_factory_->Get(); |
| if (!mojo_service) { |
| LOG(WARNING) << "SendMojoEcEventToBrowser happens before Mojo connection " |
| "is established."; |
| return; |
| } |
| |
| mojo_service->HandleEvent(mojo_event); |
| } |
| |
| void Core::NotifyClientsBluetoothAdapterState( |
| const std::vector<BluetoothEventService::AdapterData>& adapters) { |
| grpc_api::HandleBluetoothDataChangedRequest request; |
| for (const auto& adapter : adapters) { |
| VLOG(1) << base::StringPrintf( |
| "Bluetooth adapter adapter: name=%s addres=%s powered=%d " |
| "connected_devices_count=%d", |
| adapter.name.c_str(), adapter.address.c_str(), adapter.powered, |
| adapter.connected_devices_count); |
| |
| auto adapter_data = request.add_adapters(); |
| adapter_data->set_adapter_name(adapter.name); |
| adapter_data->set_adapter_mac_address(adapter.address); |
| adapter_data->set_connected_devices_count(adapter.connected_devices_count); |
| if (adapter.powered) { |
| adapter_data->set_carrier_status( |
| grpc_api::HandleBluetoothDataChangedRequest::AdapterData::STATUS_UP); |
| } else { |
| adapter_data->set_carrier_status( |
| grpc_api::HandleBluetoothDataChangedRequest::AdapterData:: |
| STATUS_DOWN); |
| } |
| } |
| |
| for (auto& client : grpc_client_manager_->GetClients()) { |
| client->CallRpc( |
| &grpc_api::WilcoDtc::Stub::AsyncHandleBluetoothDataChanged, request, |
| base::Bind( |
| [](grpc::Status status, |
| std::unique_ptr<grpc_api::HandleBluetoothDataChangedResponse> |
| response) { |
| if (!status.ok()) { |
| VLOG(1) << "Failed to call HandleBluetoothDataChanged gRPC " |
| "method on wilco_dtc. grpc error code: " |
| << status.error_code() |
| << ", error message: " << status.error_message(); |
| return; |
| } |
| VLOG(1) << "gRPC method HandleBluetoothDataChanged was " |
| "successfully called on wilco_dtc"; |
| })); |
| } |
| } |
| |
| void Core::UnsubscribeFromEventServices() { |
| if (bluetooth_event_service_) { |
| bluetooth_event_service_->RemoveObserver(this); |
| } |
| if (powerd_event_service_) { |
| powerd_event_service_->RemoveObserver(this); |
| } |
| ec_service_->RemoveObserver(this); |
| } |
| |
| } // namespace diagnostics |