| // Copyright 2020 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 "chrome/browser/nearby_sharing/nearby_per_session_discovery_manager.h" |
| |
| #include <string> |
| |
| #include "base/callback_helpers.h" |
| #include "base/containers/contains.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "chrome/browser/nearby_sharing/attachment.h" |
| #include "chrome/browser/nearby_sharing/logging/logging.h" |
| #include "chrome/browser/nearby_sharing/nearby_confirmation_manager.h" |
| #include "chrome/browser/ui/webui/nearby_share/nearby_share.mojom-forward.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "mojo/public/cpp/bindings/self_owned_receiver.h" |
| |
| namespace { |
| base::Optional<nearby_share::mojom::TransferStatus> GetTransferStatus( |
| const TransferMetadata& transfer_metadata) { |
| switch (transfer_metadata.status()) { |
| case TransferMetadata::Status::kAwaitingLocalConfirmation: |
| return nearby_share::mojom::TransferStatus::kAwaitingLocalConfirmation; |
| case TransferMetadata::Status::kAwaitingRemoteAcceptance: |
| return nearby_share::mojom::TransferStatus::kAwaitingRemoteAcceptance; |
| case TransferMetadata::Status::kComplete: |
| return nearby_share::mojom::TransferStatus::kComplete; |
| case TransferMetadata::Status::kInProgress: |
| return nearby_share::mojom::TransferStatus::kInProgress; |
| case TransferMetadata::Status::kRejected: |
| return nearby_share::mojom::TransferStatus::kRejected; |
| case TransferMetadata::Status::kTimedOut: |
| return nearby_share::mojom::TransferStatus::kTimedOut; |
| case TransferMetadata::Status::kUnsupportedAttachmentType: |
| return nearby_share::mojom::TransferStatus::kUnsupportedAttachmentType; |
| case TransferMetadata::Status::kMediaUnavailable: |
| return nearby_share::mojom::TransferStatus::kMediaUnavailable; |
| case TransferMetadata::Status::kNotEnoughSpace: |
| return nearby_share::mojom::TransferStatus::kNotEnoughSpace; |
| case TransferMetadata::Status::kFailed: |
| return nearby_share::mojom::TransferStatus::kFailed; |
| case TransferMetadata::Status::kAwaitingRemoteAcceptanceFailed: |
| return nearby_share::mojom::TransferStatus:: |
| kAwaitingRemoteAcceptanceFailed; |
| case TransferMetadata::Status::kUnknown: |
| return nearby_share::mojom::TransferStatus::kUnknown; |
| case TransferMetadata::Status::kConnecting: |
| return nearby_share::mojom::TransferStatus::kConnecting; |
| case TransferMetadata::Status::kCancelled: |
| return nearby_share::mojom::TransferStatus::kCancelled; |
| case TransferMetadata::Status::kDecodeAdvertisementFailed: |
| return nearby_share::mojom::TransferStatus::kDecodeAdvertisementFailed; |
| case TransferMetadata::Status::kMissingTransferUpdateCallback: |
| return nearby_share::mojom::TransferStatus:: |
| kMissingTransferUpdateCallback; |
| case TransferMetadata::Status::kMissingShareTarget: |
| return nearby_share::mojom::TransferStatus::kMissingShareTarget; |
| case TransferMetadata::Status::kMissingEndpointId: |
| return nearby_share::mojom::TransferStatus::kMissingEndpointId; |
| case TransferMetadata::Status::kMissingPayloads: |
| return nearby_share::mojom::TransferStatus::kMissingPayloads; |
| case TransferMetadata::Status::kPairedKeyVerificationFailed: |
| return nearby_share::mojom::TransferStatus::kPairedKeyVerificationFailed; |
| case TransferMetadata::Status::kInvalidIntroductionFrame: |
| return nearby_share::mojom::TransferStatus::kInvalidIntroductionFrame; |
| case TransferMetadata::Status::kIncompletePayloads: |
| return nearby_share::mojom::TransferStatus::kIncompletePayloads; |
| case TransferMetadata::Status::kFailedToCreateShareTarget: |
| return nearby_share::mojom::TransferStatus::kFailedToCreateShareTarget; |
| case TransferMetadata::Status::kFailedToInitiateOutgoingConnection: |
| return nearby_share::mojom::TransferStatus:: |
| kFailedToInitiateOutgoingConnection; |
| case TransferMetadata::Status::kFailedToReadOutgoingConnectionResponse: |
| return nearby_share::mojom::TransferStatus:: |
| kFailedToReadOutgoingConnectionResponse; |
| case TransferMetadata::Status::kUnexpectedDisconnection: |
| return nearby_share::mojom::TransferStatus::kUnexpectedDisconnection; |
| case TransferMetadata::Status::kMediaDownloading: |
| case TransferMetadata::Status::kExternalProviderLaunched: |
| // Ignore all other transfer status updates. |
| return base::nullopt; |
| } |
| } |
| |
| std::string GetDeviceIdForLogs(const ShareTarget& share_target) { |
| return (share_target.device_id |
| ? base::HexEncode(share_target.device_id.value().data(), |
| share_target.device_id.value().size()) |
| : "[null]"); |
| } |
| |
| } // namespace |
| |
| NearbyPerSessionDiscoveryManager::NearbyPerSessionDiscoveryManager( |
| NearbySharingService* nearby_sharing_service, |
| std::vector<std::unique_ptr<Attachment>> attachments) |
| : nearby_sharing_service_(nearby_sharing_service), |
| attachments_(std::move(attachments)) { |
| nearby_sharing_service_->AddObserver(this); |
| } |
| |
| NearbyPerSessionDiscoveryManager::~NearbyPerSessionDiscoveryManager() { |
| StopDiscovery(base::DoNothing()); |
| observers_set_.Clear(); |
| nearby_sharing_service_->RemoveObserver(this); |
| base::UmaHistogramEnumeration( |
| "Nearby.Share.Discovery.FurthestDiscoveryProgress", furthest_progress_); |
| base::UmaHistogramCounts100( |
| "Nearby.Share.Discovery.NumShareTargets.Discovered", num_discovered_); |
| base::UmaHistogramCounts100("Nearby.Share.Discovery.NumShareTargets.Lost", |
| num_lost_); |
| } |
| |
| void NearbyPerSessionDiscoveryManager::OnTransferUpdate( |
| const ShareTarget& share_target, |
| const TransferMetadata& transfer_metadata) { |
| if (!transfer_update_listener_.is_bound()) { |
| // This can happen when registering the send surface and an existing |
| // transfer is happening or recently happened. |
| NS_LOG(VERBOSE) << __func__ |
| << ": transfer_update_listener_ is not is_bound(), cannot " |
| "forward transfer updates"; |
| return; |
| } |
| |
| NS_LOG(VERBOSE) << __func__ << ": Nearby per-session discovery manager: " |
| << "Transfer update for share target with ID " |
| << share_target.id << ": " |
| << TransferMetadata::StatusToString( |
| transfer_metadata.status()); |
| |
| base::Optional<nearby_share::mojom::TransferStatus> status = |
| GetTransferStatus(transfer_metadata); |
| |
| if (!status) { |
| NS_LOG(VERBOSE) << __func__ << ": Nearby per-session discovery manager: " |
| << " skipping status update, no mojo mapping defined yet."; |
| return; |
| } |
| |
| transfer_update_listener_->OnTransferUpdate(*status, |
| transfer_metadata.token()); |
| } |
| |
| void NearbyPerSessionDiscoveryManager::OnShareTargetDiscovered( |
| ShareTarget share_target) { |
| NS_LOG(VERBOSE) << "NearbyPerSessionDiscoveryManager::" << __func__ |
| << ": id=" << share_target.id |
| << ", device_id=" << GetDeviceIdForLogs(share_target); |
| // Update metrics. |
| UpdateFurthestDiscoveryProgressIfNecessary( |
| DiscoveryProgress::kDiscoveredShareTargetNothingSent); |
| if (!base::Contains(discovered_share_targets_, share_target.id)) { |
| ++num_discovered_; |
| if (num_discovered_ == 1) { |
| base::UmaHistogramMediumTimes( |
| "Nearby.Share.Discovery.Delay.FromStartDiscoveryToFirstDiscovery", |
| base::TimeTicks::Now() - *discovery_start_time_); |
| } |
| base::UmaHistogramMediumTimes( |
| "Nearby.Share.Discovery.Delay.FromStartDiscoveryToAnyDiscovery", |
| base::TimeTicks::Now() - *discovery_start_time_); |
| } |
| |
| // Dedup by the more stable device ID if possible. |
| if (share_target.device_id) { |
| auto it = std::find_if(discovered_share_targets_.begin(), |
| discovered_share_targets_.end(), |
| [&share_target](const auto& id_share_target_pair) { |
| return share_target.device_id == |
| id_share_target_pair.second.device_id; |
| }); |
| |
| if (it != discovered_share_targets_.end()) { |
| NS_LOG(VERBOSE) << "NearbyPerSessionDiscoveryManager::" << __func__ |
| << ": Removing previously discovered share target with " |
| << "identical device_id=" |
| << GetDeviceIdForLogs(share_target); |
| OnShareTargetLost(it->second); |
| } |
| } |
| |
| discovered_share_targets_.insert_or_assign(share_target.id, share_target); |
| share_target_listener_->OnShareTargetDiscovered(share_target); |
| } |
| |
| void NearbyPerSessionDiscoveryManager::AddDiscoveryObserver( |
| ::mojo::PendingRemote<nearby_share::mojom::DiscoveryObserver> observer) { |
| observers_set_.Add(std::move(observer)); |
| } |
| |
| void NearbyPerSessionDiscoveryManager::OnShareTargetLost( |
| ShareTarget share_target) { |
| NS_LOG(VERBOSE) << "NearbyPerSessionDiscoveryManager::" << __func__ |
| << ": id=" << share_target.id |
| << ", device_id=" << GetDeviceIdForLogs(share_target); |
| |
| // It is possible that we already removed a ShareTarget from the map when |
| // deduping by ShareTarget device_id. |
| if (!base::Contains(discovered_share_targets_, share_target.id)) { |
| NS_LOG(VERBOSE) << "NearbyPerSessionDiscoveryManager::" << __func__ |
| << ": Share target id=" << share_target.id |
| << " already removed. Taking no action."; |
| return; |
| } |
| |
| ++num_lost_; |
| discovered_share_targets_.erase(share_target.id); |
| share_target_listener_->OnShareTargetLost(share_target); |
| } |
| |
| void NearbyPerSessionDiscoveryManager::StartDiscovery( |
| mojo::PendingRemote<nearby_share::mojom::ShareTargetListener> listener, |
| StartDiscoveryCallback callback) { |
| if (nearby_sharing_service_->IsTransferring() || |
| nearby_sharing_service_->IsScanning() || |
| nearby_sharing_service_->IsConnecting()) { |
| // Is there is currently a file transfer ongoing, return early with the |
| // corresponding error code. |
| std::move(callback).Run(nearby_share::mojom::StartDiscoveryResult:: |
| kErrorInProgressTransferring); |
| return; |
| } |
| |
| discovery_start_time_ = base::TimeTicks::Now(); |
| |
| // Starting discovery again closes any previous discovery session. |
| share_target_listener_.reset(); |
| share_target_listener_.Bind(std::move(listener)); |
| // NOTE: Previously we set a disconnect handler here that called |
| // UnregisterSendSurface, but this causes transfer updates to stop flowing to |
| // to the UI. Instead, we rely on the destructor's call to |
| // UnregisterSendSurface which will trigger when the share sheet goes away. |
| |
| NearbySharingService::StatusCodes status = |
| nearby_sharing_service_->RegisterSendSurface( |
| this, this, NearbySharingService::SendSurfaceState::kForeground); |
| base::UmaHistogramEnumeration("Nearby.Share.Discovery.StartDiscovery", |
| status); |
| if (status != NearbySharingService::StatusCodes::kOk) { |
| NS_LOG(WARNING) << __func__ << ": Failed to register send surface"; |
| UpdateFurthestDiscoveryProgressIfNecessary( |
| DiscoveryProgress::kFailedToStartDiscovery); |
| share_target_listener_.reset(); |
| std::move(callback).Run( |
| nearby_share::mojom::StartDiscoveryResult::kErrorGeneric); |
| return; |
| } |
| |
| UpdateFurthestDiscoveryProgressIfNecessary( |
| DiscoveryProgress::kStartedDiscoveryNothingFound); |
| |
| // Once this object is registered as send surface, we stay registered until |
| // UnregisterSendSurface is called so that the transfer update listeners can |
| // get updates even if Discovery is stopped. |
| registered_as_send_surface_ = true; |
| std::move(callback).Run(nearby_share::mojom::StartDiscoveryResult::kSuccess); |
| } |
| |
| void NearbyPerSessionDiscoveryManager::StopDiscovery( |
| base::OnceClosure callback) { |
| if (registered_as_send_surface_) { |
| NearbySharingService::StatusCodes status = |
| nearby_sharing_service_->UnregisterSendSurface(this, this); |
| base::UmaHistogramEnumeration( |
| "Nearby.Share.Discovery.UnregisterSendSurface", status); |
| if (status != NearbySharingService::StatusCodes::kOk) { |
| NS_LOG(WARNING) << __func__ << ": Failed to unregister send surface"; |
| } |
| registered_as_send_surface_ = false; |
| } |
| |
| share_target_listener_.reset(); |
| std::move(callback).Run(); |
| } |
| |
| void NearbyPerSessionDiscoveryManager::SelectShareTarget( |
| const base::UnguessableToken& share_target_id, |
| SelectShareTargetCallback callback) { |
| DCHECK(share_target_listener_.is_bound()); |
| DCHECK(!transfer_update_listener_.is_bound()); |
| |
| auto iter = discovered_share_targets_.find(share_target_id); |
| bool look_up_share_target_success = iter != discovered_share_targets_.end(); |
| base::UmaHistogramBoolean("Nearby.Share.Discovery.LookUpSelectedShareTarget", |
| look_up_share_target_success); |
| if (!look_up_share_target_success) { |
| NS_LOG(VERBOSE) << __func__ << ": Unknown share target selected: id=" |
| << share_target_id; |
| UpdateFurthestDiscoveryProgressIfNecessary( |
| DiscoveryProgress::kFailedToLookUpSelectedShareTarget); |
| std::move(callback).Run( |
| nearby_share::mojom::SelectShareTargetResult::kInvalidShareTarget, |
| mojo::NullReceiver(), mojo::NullRemote()); |
| return; |
| } |
| |
| // Bind update listener before calling the sharing service to get all updates. |
| mojo::PendingReceiver<nearby_share::mojom::TransferUpdateListener> receiver = |
| transfer_update_listener_.BindNewPipeAndPassReceiver(); |
| transfer_update_listener_.reset_on_disconnect(); |
| |
| base::UmaHistogramCounts100( |
| "Nearby.Share.Discovery.NumShareTargets.PresentWhenSendStarts", |
| discovered_share_targets_.size()); |
| base::UmaHistogramMediumTimes( |
| "Nearby.Share.Discovery.Delay.FromStartDiscoveryToStartSend", |
| base::TimeTicks::Now() - *discovery_start_time_); |
| |
| NearbySharingService::StatusCodes status = |
| nearby_sharing_service_->SendAttachments(iter->second, |
| std::move(attachments_)); |
| base::UmaHistogramEnumeration("Nearby.Share.Discovery.StartSend", status); |
| |
| // If the send call succeeded, we expect OnTransferUpdate() to be called next. |
| if (status == NearbySharingService::StatusCodes::kOk) { |
| UpdateFurthestDiscoveryProgressIfNecessary(DiscoveryProgress::kStartedSend); |
| mojo::PendingRemote<nearby_share::mojom::ConfirmationManager> remote; |
| mojo::MakeSelfOwnedReceiver(std::make_unique<NearbyConfirmationManager>( |
| nearby_sharing_service_, iter->second), |
| remote.InitWithNewPipeAndPassReceiver()); |
| std::move(callback).Run(nearby_share::mojom::SelectShareTargetResult::kOk, |
| std::move(receiver), std::move(remote)); |
| return; |
| } |
| |
| NS_LOG(VERBOSE) << __func__ << ": Failed to start send to share target"; |
| UpdateFurthestDiscoveryProgressIfNecessary( |
| DiscoveryProgress::kFailedToStartSend); |
| transfer_update_listener_.reset(); |
| std::move(callback).Run(nearby_share::mojom::SelectShareTargetResult::kError, |
| mojo::NullReceiver(), mojo::NullRemote()); |
| } |
| |
| void NearbyPerSessionDiscoveryManager::GetPayloadPreview( |
| GetPayloadPreviewCallback callback) { |
| // TODO(crbug.com/1158627): Extract this which is very similar to logic in |
| // nearby share mojo traits. |
| nearby_share::mojom::PayloadPreviewPtr payload_preview = |
| nearby_share::mojom::PayloadPreview::New(); |
| payload_preview->file_count = 0; |
| payload_preview->share_type = nearby_share::mojom::ShareType::kText; |
| if (attachments_.empty()) { |
| // Return with an empty text attachment. |
| std::move(callback).Run(std::move(payload_preview)); |
| return; |
| } |
| |
| // We have at least 1 attachment, use that one for the default description. |
| auto& attachment = attachments_[0]; |
| payload_preview->description = attachment->GetDescription(); |
| |
| if (attachment->family() == Attachment::Family::kFile) |
| payload_preview->file_count = attachments_.size(); |
| |
| if (payload_preview->file_count > 1) { |
| payload_preview->share_type = |
| nearby_share::mojom::ShareType::kMultipleFiles; |
| } else { |
| payload_preview->share_type = attachment->GetShareType(); |
| } |
| |
| std::move(callback).Run(std::move(payload_preview)); |
| } |
| |
| void NearbyPerSessionDiscoveryManager::OnNearbyProcessStopped() { |
| for (auto& remote : observers_set_) { |
| remote->OnNearbyProcessStopped(); |
| } |
| } |
| |
| void NearbyPerSessionDiscoveryManager::OnStartDiscoveryResult(bool success) { |
| for (auto& remote : observers_set_) { |
| remote->OnStartDiscoveryResult(success); |
| } |
| } |
| |
| void NearbyPerSessionDiscoveryManager:: |
| UpdateFurthestDiscoveryProgressIfNecessary(DiscoveryProgress progress) { |
| if (static_cast<int>(progress) > static_cast<int>(furthest_progress_)) |
| furthest_progress_ = progress; |
| } |