chromium/chrome/browser/ui/webui/nearby_internals/nearby_internals_ui_trigger_handler.cc

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ui/webui/nearby_internals/nearby_internals_ui_trigger_handler.h"

#include <memory>
#include <utility>
#include <vector>

#include "base/functional/bind.h"
#include "base/time/time.h"
#include "chrome/browser/nearby_sharing/attachment.h"
#include "chrome/browser/nearby_sharing/nearby_notification_manager.h"
#include "chrome/browser/nearby_sharing/nearby_sharing_service_factory.h"
#include "chrome/browser/nearby_sharing/share_target.h"
#include "chrome/browser/nearby_sharing/text_attachment.h"
#include "chromeos/ash/services/nearby/public/mojom/nearby_share_target_types.mojom.h"
#include "components/cross_device/logging/logging.h"

namespace {

// Text Payload Example used in SendText().
const char kPayloadExample[] = "TEXT PAYLOAD EXAMPLE";

// Keys in the JSON representation of a dictionaries sent to the UITriggerTab.
const char kTimeStampKey[] = "time";
const char kShareTargetDeviceNamesKey[] = "deviceName";
const char kShareTargetIdKey[] = "share_targetId";
const char kStatusCodeKey[] = "statusCode";
const char kTriggerEventKey[] = "triggerEvent";
const char kTransferUpdateMetaDataKey[] = "transfer_metadataStatus";

// Keys in the JSON representation of a dictiory send to UITriggerTab for
// the state of the transfer.
const char kIsConnecting[] = "isConnecting";
const char kIsInHighVisibility[] = "isInHighVisibility";
const char kIsReceiving[] = "isReceiving";
const char kIsScanning[] = "isScanning";
const char kIsSending[] = "isSending";
const char kIsTransferring[] = "isTransferring";

// KFields used in ShowReceiveNotification.
const char kShareTargetFakeFullName[] = "Daniel's Rotom";
const char kTextAttachmentFakeBodyText[] = "Long text that should be truncated";

// TriggerEvents in alphabetical order.
enum class TriggerEvent {
  kAccept,
  kCancel,
  kOpen,
  kRegisterBackgroundReceiveSurface,
  kRegisterBackgroundSendSurface,
  kRegisterForegroundReceiveSurface,
  kRegisterForegroundSendSurface,
  kReject,
  kSendText,
  kUnregisterReceiveSurface,
  kUnregisterSendSurface,
};

base::Value GetJavascriptTimestamp() {
  return base::Value(
      base::Time::Now().InMillisecondsFSinceUnixEpochIgnoringNull());
}

std::string StatusCodeToString(
    const NearbySharingService::StatusCodes status_code) {
  std::string status_code_string;
  switch (status_code) {
    case NearbySharingService::StatusCodes::kOk:
      return "OK";
    case NearbySharingService::StatusCodes::kError:
      return "ERROR";
    case NearbySharingService::StatusCodes::kOutOfOrderApiCall:
      return "OUT OF ORDER API CALL";
    case NearbySharingService::StatusCodes::kStatusAlreadyStopped:
      return "STATUS ALREADY STOPPED";
    case NearbySharingService::StatusCodes::kTransferAlreadyInProgress:
      return "TRANSFER ALREADY IN PROGRESS";
    case NearbySharingService::StatusCodes::kNoAvailableConnectionMedium:
      return "NO AVAILABLE CONNECTION MEDIUM";
  }
}

std::string TriggerEventToString(const TriggerEvent trigger_event) {
  switch (trigger_event) {
    case TriggerEvent::kSendText:
      return "SendText";
    case TriggerEvent::kAccept:
      return "Accept";
    case TriggerEvent::kCancel:
      return "Cancel";
    case TriggerEvent::kOpen:
      return "Open";
    case TriggerEvent::kReject:
      return "Reject";
    case TriggerEvent::kRegisterBackgroundSendSurface:
      return "Register SendSurface (BACKGROUND)";
    case TriggerEvent::kRegisterForegroundSendSurface:
      return "Register SendSurface (FOREGROUND)";
    case TriggerEvent::kUnregisterSendSurface:
      return "Unregister SendSurface";
    case TriggerEvent::kRegisterBackgroundReceiveSurface:
      return "Register ReceiveSurface (BACKGROUND)";
    case TriggerEvent::kRegisterForegroundReceiveSurface:
      return "Register ReceiveSurface (FOREGROUND)";
    case TriggerEvent::kUnregisterReceiveSurface:
      return "Unregister ReceiveSurface";
  }
}

std::string TransferUpdateMetaDataToString(
    const TransferMetadata& transfer_metadata) {
  switch (transfer_metadata.status()) {
    case TransferMetadata::Status::kUnknown:
      return "Transfer status: Unknown";
    case TransferMetadata::Status::kAwaitingLocalConfirmation:
      return "Transfer status: Awaiting Local Confirmation";
    case TransferMetadata::Status::kAwaitingRemoteAcceptance:
      return "Transfer status: Awaiting Remote Acceptance";
    case TransferMetadata::Status::kAwaitingRemoteAcceptanceFailed:
      return "Transfer status: Awaiting Remote Acceptance Failed";
    case TransferMetadata::Status::kInProgress:
      return "Transfer status: In Progress";
    case TransferMetadata::Status::kComplete:
      return "Transfer status: Complete";
    case TransferMetadata::Status::kFailed:
      return "Transfer status: Failed";
    case TransferMetadata::Status::kRejected:
      return "Transfer status: Rejected";
    case TransferMetadata::Status::kCancelled:
      return "Transfer status: Cancelled";
    case TransferMetadata::Status::kTimedOut:
      return "Transfer status: Timed Out";
    case TransferMetadata::Status::kMediaUnavailable:
      return "Transfer status: Media Unavailable";
    case TransferMetadata::Status::kMediaDownloading:
      return "Transfer status: Media Downloading";
    case TransferMetadata::Status::kNotEnoughSpace:
      return "Transfer status: Not Enough Space";
    case TransferMetadata::Status::kUnsupportedAttachmentType:
      return "Transfer status: Unsupported Attachment Type";
    case TransferMetadata::Status::kExternalProviderLaunched:
      return "Transfer status: External Provider Launched";
    case TransferMetadata::Status::kConnecting:
      return "Transfer status: Connecting";
    case TransferMetadata::Status::kDecodeAdvertisementFailed:
      return "Transfer status: Decode Advertistement Failed";
    case TransferMetadata::Status::kMissingTransferUpdateCallback:
      return "Transfer status: Missing Transfer Update Callback";
    case TransferMetadata::Status::kMissingShareTarget:
      return "Transfer status: Missing Share Target";
    case TransferMetadata::Status::kMissingEndpointId:
      return "Transfer status: Missing Endpoint Id";
    case TransferMetadata::Status::kMissingPayloads:
      return "Transfer status: Missing Payloads";
    case TransferMetadata::Status::kPairedKeyVerificationFailed:
      return "Transfer status: Paired Key Verification Failed";
    case TransferMetadata::Status::kInvalidIntroductionFrame:
      return "Transfer status: Invalid Introduction Frame";
    case TransferMetadata::Status::kIncompletePayloads:
      return "Transfer status: Incomplete Payloads";
    case TransferMetadata::Status::kFailedToCreateShareTarget:
      return "Transfer status: Failed To Create Share Target";
    case TransferMetadata::Status::kFailedToInitiateOutgoingConnection:
      return "Transfer status: Failed To Initiate Outgoing Connection";
    case TransferMetadata::Status::kFailedToReadOutgoingConnectionResponse:
      return "Transfer status: Failed To Read Outgoing Connection Response.";
    case TransferMetadata::Status::kUnexpectedDisconnection:
      return "Transfer status: Unexpected Disconnection";
  }
}

// Converts |status_code| to a raw dictionary value used as a JSON argument
// to JavaScript functions.
base::Value::Dict StatusCodeToDictionary(
    const NearbySharingService::StatusCodes status_code,
    TriggerEvent trigger_event) {
  base::Value::Dict dictionary;
  dictionary.Set(kStatusCodeKey, StatusCodeToString(status_code));
  dictionary.Set(kTriggerEventKey, TriggerEventToString(trigger_event));
  dictionary.Set(kTimeStampKey, GetJavascriptTimestamp());
  return dictionary;
}

// Converts |share_target| to a raw dictionary value used as a JSON argument
// to JavaScript functions.
base::Value::Dict ShareTargetToDictionary(const ShareTarget share_target) {
  base::Value::Dict share_target_dictionary;
  share_target_dictionary.Set(kShareTargetDeviceNamesKey,
                              share_target.device_name);
  share_target_dictionary.Set(kShareTargetIdKey, share_target.id.ToString());
  share_target_dictionary.Set(kTimeStampKey, GetJavascriptTimestamp());
  return share_target_dictionary;
}

// Converts |id_to_share_target_map| to a raw dictionary value used as a JSON
// argument to JavaScript functions.
base::Value::List ShareTargetMapToList(
    const base::flat_map<std::string, ShareTarget>& id_to_share_target_map) {
  base::Value::List share_target_list;
  share_target_list.reserve(id_to_share_target_map.size());

  for (const auto& it : id_to_share_target_map) {
    share_target_list.Append(ShareTargetToDictionary(it.second));
  }

  return share_target_list;
}

// Converts |transfer_metadata| to a raw dictionary value used as a JSON
// argument to JavaScript functions.
base::Value::Dict TransferUpdateToDictionary(
    const ShareTarget& share_target,
    const TransferMetadata& transfer_metadata) {
  base::Value::Dict dictionary;
  dictionary.Set(kTransferUpdateMetaDataKey,
                 TransferUpdateMetaDataToString(transfer_metadata));
  dictionary.Set(kTimeStampKey, GetJavascriptTimestamp());
  dictionary.Set(kShareTargetDeviceNamesKey, share_target.device_name);
  dictionary.Set(kShareTargetIdKey, share_target.id.ToString());
  return dictionary;
}

base::Value::Dict StatusBooleansToDictionary(const bool is_scanning,
                                             const bool is_transferring,
                                             const bool is_receiving_files,
                                             const bool is_sending_files,
                                             const bool is_conecting,
                                             const bool is_in_high_visibility) {
  base::Value::Dict dictionary;
  dictionary.Set(kIsScanning, is_scanning);
  dictionary.Set(kIsTransferring, is_transferring);
  dictionary.Set(kIsSending, is_sending_files);
  dictionary.Set(kIsReceiving, is_receiving_files);
  dictionary.Set(kIsConnecting, is_conecting);
  dictionary.Set(kIsInHighVisibility, is_in_high_visibility);
  dictionary.Set(kTimeStampKey, GetJavascriptTimestamp());
  return dictionary;
}

}  // namespace

NearbyInternalsUiTriggerHandler::NearbyInternalsUiTriggerHandler(
    content::BrowserContext* context)
    : context_(context) {}

NearbyInternalsUiTriggerHandler::~NearbyInternalsUiTriggerHandler() = default;

void NearbyInternalsUiTriggerHandler::RegisterMessages() {
  web_ui()->RegisterMessageCallback(
      "initializeUiTrigger",
      base::BindRepeating(&NearbyInternalsUiTriggerHandler::InitializeContents,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "sendText",
      base::BindRepeating(&NearbyInternalsUiTriggerHandler::SendText,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "accept", base::BindRepeating(&NearbyInternalsUiTriggerHandler::Accept,
                                    base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "reject", base::BindRepeating(&NearbyInternalsUiTriggerHandler::Reject,
                                    base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "cancel", base::BindRepeating(&NearbyInternalsUiTriggerHandler::Cancel,
                                    base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "open", base::BindRepeating(&NearbyInternalsUiTriggerHandler::Open,
                                  base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "registerSendSurfaceForeground",
      base::BindRepeating(
          &NearbyInternalsUiTriggerHandler::RegisterSendSurfaceForeground,
          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "registerSendSurfaceBackground",
      base::BindRepeating(
          &NearbyInternalsUiTriggerHandler::RegisterSendSurfaceBackground,
          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "unregisterSendSurface",
      base::BindRepeating(
          &NearbyInternalsUiTriggerHandler::UnregisterSendSurface,
          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "registerReceiveSurfaceForeground",
      base::BindRepeating(
          &NearbyInternalsUiTriggerHandler::RegisterReceiveSurfaceForeground,
          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "registerReceiveSurfaceBackground",
      base::BindRepeating(
          &NearbyInternalsUiTriggerHandler::RegisterReceiveSurfaceBackground,
          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "unregisterReceiveSurface",
      base::BindRepeating(
          &NearbyInternalsUiTriggerHandler::UnregisterReceiveSurface,
          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "getStates",
      base::BindRepeating(&NearbyInternalsUiTriggerHandler::GetState,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "showNearbyShareReceivedNotification",
      base::BindRepeating(
          &NearbyInternalsUiTriggerHandler::ShowReceivedNotification,
          base::Unretained(this)));
}

void NearbyInternalsUiTriggerHandler::InitializeContents(
    const base::Value::List& args) {
  AllowJavascript();
}

void NearbyInternalsUiTriggerHandler::RegisterSendSurfaceForeground(
    const base::Value::List& args) {
  NearbySharingService* service_ =
      NearbySharingServiceFactory::GetForBrowserContext(context_);
  if (!service_) {
    CD_LOG(ERROR, Feature::NS) << "No NearbyShareService instance to call.";
    return;
  }

  const base::Value& callback_id = args[0];
  ResolveJavascriptCallback(
      callback_id,
      StatusCodeToDictionary(
          service_->RegisterSendSurface(
              this, this, NearbySharingService::SendSurfaceState::kForeground),
          TriggerEvent::kRegisterForegroundSendSurface));
}

void NearbyInternalsUiTriggerHandler::RegisterSendSurfaceBackground(
    const base::Value::List& args) {
  NearbySharingService* service_ =
      NearbySharingServiceFactory::GetForBrowserContext(context_);
  if (!service_) {
    CD_LOG(ERROR, Feature::NS) << "No NearbyShareService instance to call.";
    return;
  }

  const base::Value& callback_id = args[0];
  ResolveJavascriptCallback(
      callback_id,
      StatusCodeToDictionary(
          service_->RegisterSendSurface(
              this, this, NearbySharingService::SendSurfaceState::kBackground),
          TriggerEvent::kRegisterBackgroundSendSurface));
}

void NearbyInternalsUiTriggerHandler::UnregisterSendSurface(
    const base::Value::List& args) {
  NearbySharingService* service_ =
      NearbySharingServiceFactory::GetForBrowserContext(context_);
  if (!service_) {
    CD_LOG(ERROR, Feature::NS) << "No NearbyShareService instance to call.";
    return;
  }

  const base::Value& callback_id = args[0];
  ResolveJavascriptCallback(
      callback_id,
      StatusCodeToDictionary(service_->UnregisterSendSurface(this, this),
                             TriggerEvent::kUnregisterSendSurface));
}

void NearbyInternalsUiTriggerHandler::RegisterReceiveSurfaceForeground(
    const base::Value::List& args) {
  NearbySharingService* service_ =
      NearbySharingServiceFactory::GetForBrowserContext(context_);
  if (!service_) {
    CD_LOG(ERROR, Feature::NS) << "No NearbyShareService instance to call.";
    return;
  }

  const base::Value& callback_id = args[0];
  ResolveJavascriptCallback(
      callback_id,
      StatusCodeToDictionary(
          service_->RegisterReceiveSurface(
              this, NearbySharingService::ReceiveSurfaceState::kForeground),
          TriggerEvent::kRegisterForegroundReceiveSurface));
}

void NearbyInternalsUiTriggerHandler::RegisterReceiveSurfaceBackground(
    const base::Value::List& args) {
  NearbySharingService* service_ =
      NearbySharingServiceFactory::GetForBrowserContext(context_);
  if (!service_) {
    CD_LOG(ERROR, Feature::NS) << "No NearbyShareService instance to call.";
    return;
  }

  const base::Value& callback_id = args[0];
  ResolveJavascriptCallback(
      callback_id,
      StatusCodeToDictionary(
          service_->RegisterReceiveSurface(
              this, NearbySharingService::ReceiveSurfaceState::kBackground),
          TriggerEvent::kRegisterBackgroundReceiveSurface));
}

void NearbyInternalsUiTriggerHandler::UnregisterReceiveSurface(
    const base::Value::List& args) {
  NearbySharingService* service_ =
      NearbySharingServiceFactory::GetForBrowserContext(context_);
  if (!service_) {
    CD_LOG(ERROR, Feature::NS) << "No NearbyShareService instance to call.";
    return;
  }

  const base::Value& callback_id = args[0];
  ResolveJavascriptCallback(
      callback_id,
      StatusCodeToDictionary(service_->UnregisterReceiveSurface(this),
                             TriggerEvent::kUnregisterReceiveSurface));
}

void NearbyInternalsUiTriggerHandler::OnTransferUpdate(
    const ShareTarget& share_target,
    const TransferMetadata& transfer_metadata) {
  FireWebUIListener("transfer-updated", TransferUpdateToDictionary(
                                            share_target, transfer_metadata));
}

void NearbyInternalsUiTriggerHandler::OnShareTargetDiscovered(
    ShareTarget share_target) {
  id_to_share_target_map_.insert({share_target.id.ToString(), share_target});
  FireWebUIListener("share-target-discovered",
                    ShareTargetToDictionary(share_target));
  FireWebUIListener("share-target-map-updated",
                    ShareTargetMapToList(id_to_share_target_map_));
}

void NearbyInternalsUiTriggerHandler::OnShareTargetLost(
    ShareTarget share_target) {
  id_to_share_target_map_.erase(share_target.id.ToString());
  FireWebUIListener("share-target-lost", ShareTargetToDictionary(share_target));
  FireWebUIListener("share-target-map-updated",
                    ShareTargetMapToList(id_to_share_target_map_));
}

void NearbyInternalsUiTriggerHandler::OnAcceptCalled(
    NearbySharingService::StatusCodes status_codes) {
  FireWebUIListener(
      "on-status-code-returned",
      StatusCodeToDictionary(status_codes, TriggerEvent::kAccept));
}

void NearbyInternalsUiTriggerHandler::OnOpenCalled(
    NearbySharingService::StatusCodes status_codes) {
  FireWebUIListener("on-status-code-returned",
                    StatusCodeToDictionary(status_codes, TriggerEvent::kOpen));
}

void NearbyInternalsUiTriggerHandler::OnRejectCalled(
    NearbySharingService::StatusCodes status_codes) {
  FireWebUIListener(
      "on-status-code-returned",
      StatusCodeToDictionary(status_codes, TriggerEvent::kReject));
}

void NearbyInternalsUiTriggerHandler::OnCancelCalled(
    NearbySharingService::StatusCodes status_codes) {
  FireWebUIListener(
      "on-status-code-returned",
      StatusCodeToDictionary(status_codes, TriggerEvent::kCancel));
}

void NearbyInternalsUiTriggerHandler::SendText(const base::Value::List& args) {
  NearbySharingService* service_ =
      NearbySharingServiceFactory::GetForBrowserContext(context_);
  if (!service_) {
    CD_LOG(ERROR, Feature::NS) << "No NearbyShareService instance to call.";
    return;
  }

  std::string share_target_id = args[1].GetString();
  auto it = id_to_share_target_map_.find(share_target_id);
  if (it == id_to_share_target_map_.end()) {
    CD_LOG(ERROR, Feature::NS)
        << "Invalid ShareTarget ID " << share_target_id << " for SendText.";
    return;
  }

  std::vector<std::unique_ptr<Attachment>> attachments;
  attachments.push_back(std::make_unique<TextAttachment>(
      TextAttachment::Type::kText, kPayloadExample, /*title=*/std::nullopt,
      /*mime_type=*/std::nullopt));

  const base::Value& callback_id = args[0];
  ResolveJavascriptCallback(
      callback_id,
      StatusCodeToDictionary(
          service_->SendAttachments(it->second, std::move(attachments)),
          TriggerEvent::kSendText));
}

void NearbyInternalsUiTriggerHandler::Accept(const base::Value::List& args) {
  NearbySharingService* service_ =
      NearbySharingServiceFactory::GetForBrowserContext(context_);
  if (!service_) {
    CD_LOG(ERROR, Feature::NS) << "No NearbyShareService instance to call.";
    return;
  }

  std::string share_target_id = args[0].GetString();
  auto it = id_to_share_target_map_.find(share_target_id);
  if (it == id_to_share_target_map_.end()) {
    CD_LOG(ERROR, Feature::NS)
        << "Invalid ShareTarget ID " << share_target_id << " for Accept.";
    return;
  }

  service_->Accept(
      it->second,
      base::BindOnce(&NearbyInternalsUiTriggerHandler::OnAcceptCalled,
                     weak_ptr_factory_.GetWeakPtr()));
}

void NearbyInternalsUiTriggerHandler::Open(const base::Value::List& args) {
  NearbySharingService* service_ =
      NearbySharingServiceFactory::GetForBrowserContext(context_);
  if (!service_) {
    CD_LOG(ERROR, Feature::NS) << "No NearbyShareService instance to call.";
    return;
  }

  std::string share_target_id = args[0].GetString();
  auto it = id_to_share_target_map_.find(share_target_id);
  if (it == id_to_share_target_map_.end()) {
    CD_LOG(ERROR, Feature::NS)
        << "Invalid ShareTarget ID " << share_target_id << " for Open.";
    return;
  }

  service_->Open(it->second,
                 base::BindOnce(&NearbyInternalsUiTriggerHandler::OnOpenCalled,
                                weak_ptr_factory_.GetWeakPtr()));
}

void NearbyInternalsUiTriggerHandler::Reject(const base::Value::List& args) {
  NearbySharingService* service_ =
      NearbySharingServiceFactory::GetForBrowserContext(context_);
  if (!service_) {
    CD_LOG(ERROR, Feature::NS) << "No NearbyShareService instance to call.";
    return;
  }

  std::string share_target_id = args[0].GetString();
  auto it = id_to_share_target_map_.find(share_target_id);
  if (it == id_to_share_target_map_.end()) {
    CD_LOG(ERROR, Feature::NS)
        << "Invalid ShareTarget ID " << share_target_id << " for Reject.";
    return;
  }

  service_->Reject(
      it->second,
      base::BindOnce(&NearbyInternalsUiTriggerHandler::OnRejectCalled,
                     weak_ptr_factory_.GetWeakPtr()));
}

void NearbyInternalsUiTriggerHandler::Cancel(const base::Value::List& args) {
  NearbySharingService* service_ =
      NearbySharingServiceFactory::GetForBrowserContext(context_);
  if (!service_) {
    CD_LOG(ERROR, Feature::NS) << "No NearbyShareService instance to call.";
    return;
  }

  std::string share_target_id = args[0].GetString();
  auto it = id_to_share_target_map_.find(share_target_id);
  if (it == id_to_share_target_map_.end()) {
    CD_LOG(ERROR, Feature::NS)
        << "Invalid ShareTarget ID " << share_target_id << " for Cancel.";
    return;
  }

  service_->Cancel(
      it->second,
      base::BindOnce(&NearbyInternalsUiTriggerHandler::OnCancelCalled,
                     weak_ptr_factory_.GetWeakPtr()));
}

void NearbyInternalsUiTriggerHandler::GetState(const base::Value::List& args) {
  NearbySharingService* service_ =
      NearbySharingServiceFactory::GetForBrowserContext(context_);
  if (!service_) {
    CD_LOG(ERROR, Feature::NS) << "No NearbyShareService instance to call.";
    return;
  }

  const base::Value& callback_id = args[0];
  ResolveJavascriptCallback(
      callback_id,
      StatusBooleansToDictionary(
          service_->IsScanning(), service_->IsTransferring(),
          service_->IsReceivingFile(), service_->IsSendingFile(),
          service_->IsConnecting(), service_->IsInHighVisibility()));
}

void NearbyInternalsUiTriggerHandler::ShowReceivedNotification(
    const base::Value::List& args) {
  NearbySharingService* service =
      NearbySharingServiceFactory::GetForBrowserContext(context_);
  if (!service) {
    CD_LOG(ERROR, Feature::NS) << "No NearbyShareService instance to call.";
    return;
  }

  NearbyNotificationManager* manager = service->GetNotificationManager();

  if (!manager) {
    CD_LOG(ERROR, Feature::NS)
        << "No NearbyNotificationManager instance to call.";
    return;
  }

  // Create a share target with a fake text attachment.
  TextAttachment attachment(TextAttachment::Type::kText,
                            kTextAttachmentFakeBodyText,
                            /*title=*/std::nullopt,
                            /*mime_type=*/std::nullopt);
  ShareTarget target;
  target.is_incoming = true;
  target.device_name = kShareTargetFakeFullName;
  attachment.MoveToShareTarget(target);

  manager->ShowSuccess(target);
}