| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chromeos/ash/components/tether/tether_availability_operation.h" |
| |
| #include <memory> |
| |
| #include "base/containers/contains.h" |
| #include "base/functional/bind.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/time/default_clock.h" |
| #include "chromeos/ash/components/multidevice/logging/logging.h" |
| #include "chromeos/ash/components/tether/connection_preserver.h" |
| #include "chromeos/ash/components/tether/message_wrapper.h" |
| #include "chromeos/ash/components/tether/proto/tether.pb.h" |
| #include "chromeos/ash/components/tether/tether_host_response_recorder.h" |
| |
| namespace ash::tether { |
| |
| namespace { |
| |
| bool IsTetheringAvailableWithValidDeviceStatus( |
| const TetherAvailabilityResponse* response) { |
| if (!response) { |
| return false; |
| } |
| |
| if (!response->has_device_status()) { |
| return false; |
| } |
| |
| if (!response->has_response_code()) { |
| return false; |
| } |
| |
| const TetherAvailabilityResponse_ResponseCode response_code = |
| response->response_code(); |
| if (response_code == |
| TetherAvailabilityResponse_ResponseCode:: |
| TetherAvailabilityResponse_ResponseCode_SETUP_NEEDED || |
| response_code == |
| TetherAvailabilityResponse_ResponseCode:: |
| TetherAvailabilityResponse_ResponseCode_TETHER_AVAILABLE || |
| response_code == |
| TetherAvailabilityResponse_ResponseCode:: |
| TetherAvailabilityResponse_ResponseCode_LAST_PROVISIONING_FAILED) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool AreGmsCoreNotificationsDisabled( |
| const TetherAvailabilityResponse* response) { |
| if (!response) { |
| return false; |
| } |
| |
| if (!response->has_response_code()) { |
| return false; |
| } |
| |
| return response->response_code() == |
| TetherAvailabilityResponse_ResponseCode:: |
| TetherAvailabilityResponse_ResponseCode_NOTIFICATIONS_DISABLED_LEGACY || |
| response->response_code() == |
| TetherAvailabilityResponse_ResponseCode:: |
| TetherAvailabilityResponse_ResponseCode_NOTIFICATIONS_DISABLED_WITH_NOTIFICATION_CHANNEL; |
| } |
| |
| } // namespace |
| |
| TetherAvailabilityOperation::Initializer::Initializer( |
| raw_ptr<HostConnection::Factory> host_connection_factory, |
| raw_ptr<TetherHostResponseRecorder> tether_host_response_recorder, |
| raw_ptr<ConnectionPreserver> connection_preserver) |
| : host_connection_factory_(host_connection_factory), |
| tether_host_response_recorder_(tether_host_response_recorder), |
| connection_preserver_(connection_preserver) {} |
| |
| TetherAvailabilityOperation::Initializer::~Initializer() = default; |
| |
| std::unique_ptr<TetherAvailabilityOperation> |
| TetherAvailabilityOperation::Initializer::Initialize( |
| const TetherHost& tether_host, |
| TetherAvailabilityOperation::OnTetherAvailabilityOperationFinishedCallback |
| callback) { |
| auto operation = std::make_unique<TetherAvailabilityOperation>( |
| tether_host, std::move(callback), host_connection_factory_, |
| tether_host_response_recorder_, connection_preserver_); |
| operation->Initialize(); |
| return operation; |
| } |
| |
| TetherAvailabilityOperation::TetherAvailabilityOperation( |
| const TetherHost& tether_host, |
| TetherAvailabilityOperation::OnTetherAvailabilityOperationFinishedCallback |
| callback, |
| raw_ptr<HostConnection::Factory> host_connection_factory, |
| TetherHostResponseRecorder* tether_host_response_recorder, |
| ConnectionPreserver* connection_preserver) |
| : MessageTransferOperation( |
| tether_host, |
| HostConnection::Factory::ConnectionPriority::kLow, |
| host_connection_factory), |
| tether_host_(tether_host), |
| tether_host_response_recorder_(tether_host_response_recorder), |
| connection_preserver_(connection_preserver), |
| clock_(base::DefaultClock::GetInstance()), |
| task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()), |
| on_operation_finished_(std::move(callback)) {} |
| |
| TetherAvailabilityOperation::~TetherAvailabilityOperation() = default; |
| |
| void TetherAvailabilityOperation::OnDeviceAuthenticated() { |
| CHECK(!tether_availability_request_start_time_.has_value()); |
| tether_availability_request_start_time_ = clock_->Now(); |
| PA_LOG(VERBOSE) << "Sending TetherAvailabilityRequest message to " |
| << GetDeviceId(/*truncate_for_logs=*/true) << "."; |
| SendMessage(std::make_unique<MessageWrapper>(TetherAvailabilityRequest()), |
| /*on_message_sent=*/base::DoNothing()); |
| } |
| |
| void TetherAvailabilityOperation::OnMessageReceived( |
| std::unique_ptr<MessageWrapper> message_wrapper) { |
| if (message_wrapper->GetMessageType() != |
| MessageType::TETHER_AVAILABILITY_RESPONSE) { |
| // If another type of message has been received, ignore it. |
| return; |
| } |
| |
| TetherAvailabilityResponse* response = |
| static_cast<TetherAvailabilityResponse*>(message_wrapper->GetProto()); |
| if (AreGmsCoreNotificationsDisabled(response)) { |
| PA_LOG(WARNING) |
| << "Received TetherAvailabilityResponse from device with ID " |
| << GetDeviceId(/*truncate_for_logs=*/true) << " which " |
| << "indicates that Google Play Services notifications are " |
| << "disabled. Response code: " << response->response_code(); |
| |
| scanned_device_info_result_ = ScannedDeviceInfo( |
| tether_host_.GetDeviceId(), tether_host_.GetName(), std::nullopt, |
| /*setup_required=*/false, /*notifications_enabled=*/false); |
| } else if (!IsTetheringAvailableWithValidDeviceStatus(response)) { |
| // If the received message is invalid or if it states that tethering is |
| // unavailable, ignore it. |
| PA_LOG(WARNING) |
| << "Received TetherAvailabilityResponse from device with ID " |
| << GetDeviceId(/*truncate_for_logs=*/true) << " which " |
| << "indicates that tethering is not available. Response code: " |
| << response->response_code(); |
| } else { |
| bool setup_required = |
| response->response_code() == |
| TetherAvailabilityResponse_ResponseCode:: |
| TetherAvailabilityResponse_ResponseCode_SETUP_NEEDED; |
| |
| PA_LOG(VERBOSE) |
| << "Received TetherAvailabilityResponse from device with ID " |
| << GetDeviceId(/*truncate_for_logs=*/true) << " which " |
| << "indicates that tethering is available. setup_required = " |
| << setup_required; |
| |
| tether_host_response_recorder_->RecordSuccessfulTetherAvailabilityResponse( |
| GetDeviceId(/*truncate_for_logs=*/false)); |
| |
| // Only attempt to preserve the BLE connection to this device if the |
| // response indicated that the device can serve as a host. |
| connection_preserver_->HandleSuccessfulTetherAvailabilityResponse( |
| GetDeviceId(/*truncate_for_logs=*/false)); |
| |
| scanned_device_info_result_ = |
| ScannedDeviceInfo(tether_host_.GetDeviceId(), tether_host_.GetName(), |
| response->device_status(), setup_required, |
| /*notifications_enabled=*/true); |
| } |
| |
| RecordTetherAvailabilityResponseDuration( |
| GetDeviceId(/*truncate_for_logs=*/false)); |
| |
| // Unregister the device after a TetherAvailabilityResponse has been received. |
| // Delay this in order to let |connection_preserver_| fully preserve the |
| // connection, if necessary, before attempting to tear down the connection. |
| task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&TetherAvailabilityOperation::StopOperation, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void TetherAvailabilityOperation::OnOperationFinished() { |
| std::move(on_operation_finished_).Run(scanned_device_info_result_); |
| } |
| |
| MessageType TetherAvailabilityOperation::GetMessageTypeForConnection() { |
| return MessageType::TETHER_AVAILABILITY_REQUEST; |
| } |
| |
| void TetherAvailabilityOperation::SetTestDoubles( |
| base::Clock* clock_for_test, |
| scoped_refptr<base::TaskRunner> test_task_runner) { |
| clock_ = clock_for_test; |
| task_runner_ = test_task_runner; |
| } |
| |
| void TetherAvailabilityOperation::RecordTetherAvailabilityResponseDuration( |
| const std::string device_id) { |
| if (!tether_availability_request_start_time_.has_value()) { |
| LOG(ERROR) << "Failed to record TetherAvailabilityResponse duration: " |
| << "start time is invalid"; |
| return; |
| } |
| |
| UMA_HISTOGRAM_TIMES( |
| "InstantTethering.Performance.TetherAvailabilityResponseDuration", |
| clock_->Now() - *tether_availability_request_start_time_); |
| tether_availability_request_start_time_.reset(); |
| } |
| |
| } // namespace ash::tether |