blob: 3e02afd1fc17e9eaa25f59d3d06b937f305f6022 [file] [log] [blame]
// Copyright 2021 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/ui/sharing_hub/sharing_hub_bubble_controller.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "base/strings/utf_string_conversions.h"
#include "build/chromeos_buildflags.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/share/share_metrics.h"
#include "chrome/browser/sharing_hub/sharing_hub_features.h"
#include "chrome/browser/sharing_hub/sharing_hub_model.h"
#include "chrome/browser/sharing_hub/sharing_hub_service.h"
#include "chrome/browser/sharing_hub/sharing_hub_service_factory.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/qrcode_generator/qrcode_generator_bubble_controller.h"
#include "chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_controller.h"
#include "chrome/browser/ui/sharing_hub/sharing_hub_bubble_view.h"
#include "chrome/grit/generated_resources.h"
#include "components/services/app_service/public/cpp/intent_util.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/views/controls/button/button.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "chrome/browser/sharesheet/sharesheet_metrics.h"
#include "chrome/browser/sharesheet/sharesheet_service.h"
#include "chrome/browser/sharesheet/sharesheet_service_factory.h"
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
namespace sharing_hub {
namespace {
#if BUILDFLAG(IS_CHROMEOS_ASH)
// Result of the CrOS sharesheet, i.e. whether the user selects a share target
// after opening the sharesheet.
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused. Keep in sync with
// SharingHubSharesheetResult in src/tools/metrics/histograms/enums.xml.
enum class SharingHubSharesheetResult {
SUCCESS = 0,
CANCELED = 1,
kMaxValue = CANCELED,
};
const char kSharesheetResult[] =
"Sharing.SharingHubDesktop.CrOSSharesheetResult";
SharingHubSharesheetResult GetSharesheetResultHistogram(
sharesheet::SharesheetResult result) {
switch (result) {
case sharesheet::SharesheetResult::kSuccess:
return SharingHubSharesheetResult::SUCCESS;
case sharesheet::SharesheetResult::kCancel:
case sharesheet::SharesheetResult::kErrorAlreadyOpen:
case sharesheet::SharesheetResult::kErrorWindowClosed:
return SharingHubSharesheetResult::CANCELED;
}
}
void LogCrOSSharesheetResult(sharesheet::SharesheetResult result) {
UMA_HISTOGRAM_ENUMERATION(kSharesheetResult,
GetSharesheetResultHistogram(result));
}
#endif
} // namespace
SharingHubBubbleController::~SharingHubBubbleController() {
if (sharing_hub_bubble_view_) {
sharing_hub_bubble_view_->Hide();
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
if (bubble_showing_) {
bubble_showing_ = false;
// Close any remnant Sharesheet dialog.
if (web_contents_containing_window_) {
CloseSharesheet();
}
// We must deselect the icon manually since the Sharesheet will not be able
// to invoke OnSharesheetClosed() at this point.
DeselectIcon();
}
#endif
}
// static
SharingHubBubbleController*
SharingHubBubbleController::CreateOrGetFromWebContents(
content::WebContents* web_contents) {
SharingHubBubbleController::CreateForWebContents(web_contents);
SharingHubBubbleController* controller =
SharingHubBubbleController::FromWebContents(web_contents);
return controller;
}
void SharingHubBubbleController::HideBubble() {
if (sharing_hub_bubble_view_) {
sharing_hub_bubble_view_->Hide();
sharing_hub_bubble_view_ = nullptr;
}
}
void SharingHubBubbleController::ShowBubble() {
Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
#if BUILDFLAG(IS_CHROMEOS_ASH)
// Ignore subsequent calls to open the Sharesheet if it already is open. This
// is especially for the Nearby Share dialog, where clicking outside of it
// will not dismiss the dialog.
if (bubble_showing_)
return;
bubble_showing_ = true;
ShowSharesheet(browser->window()->GetSharingHubIconButton());
#else
sharing_hub_bubble_view_ =
browser->window()->ShowSharingHubBubble(web_contents(), this, true);
#endif
share::LogShareSourceDesktop(share::ShareSourceDesktop::kOmniboxSharingHub);
}
SharingHubBubbleView* SharingHubBubbleController::sharing_hub_bubble_view()
const {
return sharing_hub_bubble_view_;
}
std::u16string SharingHubBubbleController::GetWindowTitle() const {
return l10n_util::GetStringUTF16(IDS_SHARING_HUB_TITLE);
}
Profile* SharingHubBubbleController::GetProfile() const {
return Profile::FromBrowserContext(web_contents()->GetBrowserContext());
}
bool SharingHubBubbleController::ShouldOfferOmniboxIcon() {
return SharingHubOmniboxEnabled(GetWebContents().GetBrowserContext());
}
std::vector<SharingHubAction>
SharingHubBubbleController::GetFirstPartyActions() {
std::vector<SharingHubAction> actions;
SharingHubModel* model = GetSharingHubModel();
if (model)
model->GetFirstPartyActionList(&GetWebContents(), &actions);
return actions;
}
std::vector<SharingHubAction>
SharingHubBubbleController::GetThirdPartyActions() {
std::vector<SharingHubAction> actions;
SharingHubModel* model = GetSharingHubModel();
if (model)
model->GetThirdPartyActionList(&actions);
return actions;
}
void SharingHubBubbleController::OnActionSelected(
int command_id,
bool is_first_party,
std::string feature_name_for_metrics) {
Browser* browser = chrome::FindBrowserWithWebContents(&GetWebContents());
// Can be null in tests.
if (!browser)
return;
if (is_first_party) {
base::RecordComputedAction(feature_name_for_metrics);
// Show a back button for 1P dialogs anchored to the sharing hub icon.
if (command_id == IDC_QRCODE_GENERATOR) {
qrcode_generator::QRCodeGeneratorBubbleController* qrcode_controller =
qrcode_generator::QRCodeGeneratorBubbleController::Get(
&GetWebContents());
qrcode_controller->ShowBubble(GetWebContents().GetLastCommittedURL(),
true);
} else if (command_id == IDC_SEND_TAB_TO_SELF) {
send_tab_to_self::SendTabToSelfBubbleController*
send_tab_to_self_controller =
send_tab_to_self::SendTabToSelfBubbleController::
CreateOrGetFromWebContents(&GetWebContents());
send_tab_to_self_controller->ShowBubble(true);
} else {
chrome::ExecuteCommand(browser, command_id);
}
} else {
SharingHubModel* model = GetSharingHubModel();
DCHECK(model);
model->ExecuteThirdPartyAction(&GetWebContents(), command_id);
}
}
void SharingHubBubbleController::OnBubbleClosed() {
sharing_hub_bubble_view_ = nullptr;
}
SharingHubModel* SharingHubBubbleController::GetSharingHubModel() {
if (!sharing_hub_model_) {
SharingHubService* const service =
SharingHubServiceFactory::GetForProfile(GetProfile());
if (!service)
return nullptr;
sharing_hub_model_ = service->GetSharingHubModel();
}
return sharing_hub_model_;
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
sharesheet::SharesheetService*
SharingHubBubbleController::GetSharesheetService() {
if (!sharesheet_service_) {
Profile* const profile =
Profile::FromBrowserContext(GetWebContents().GetBrowserContext());
DCHECK(profile);
sharesheet_service_ =
sharesheet::SharesheetServiceFactory::GetForProfile(profile);
}
return sharesheet_service_;
}
void SharingHubBubbleController::ShowSharesheet(
views::Button* highlighted_button) {
DCHECK(highlighted_button);
highlighted_button_tracker_.SetView(highlighted_button);
sharesheet::SharesheetService* sharesheet_service = GetSharesheetService();
if (!sharesheet_service)
return;
apps::mojom::IntentPtr intent = apps_util::CreateShareIntentFromText(
GetWebContents().GetLastCommittedURL().spec(),
base::UTF16ToUTF8(GetWebContents().GetTitle()));
sharesheet_service->ShowBubble(
&GetWebContents(), std::move(intent),
sharesheet::LaunchSource::kOmniboxShare,
base::BindOnce(&SharingHubBubbleController::OnShareDelivered,
weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(&SharingHubBubbleController::OnSharesheetClosed,
weak_ptr_factory_.GetWeakPtr()));
// Save the window in order to close the sharesheet if the tab is closed. This
// will return the incorrect window if called later.
web_contents_containing_window_ = GetWebContents().GetTopLevelNativeWindow();
}
void SharingHubBubbleController::CloseSharesheet() {
sharesheet::SharesheetService* sharesheet_service = GetSharesheetService();
if (!sharesheet_service)
return;
sharesheet::SharesheetController* sharesheet_controller =
sharesheet_service->GetSharesheetController(
web_contents_containing_window_);
if (!sharesheet_controller)
return;
sharesheet_controller->CloseBubble(sharesheet::SharesheetResult::kCancel);
// OnSharesheetClosed() is not guaranteed to be called by the
// SharesheetController (specifically for the case where this is invoked by
// our destructor). Hence, we must explicitly set this null here.
web_contents_containing_window_ = nullptr;
}
void SharingHubBubbleController::OnShareDelivered(
sharesheet::SharesheetResult result) {
LogCrOSSharesheetResult(result);
}
void SharingHubBubbleController::OnSharesheetClosed(
views::Widget::ClosedReason reason) {
bubble_showing_ = false;
web_contents_containing_window_ = nullptr;
// Deselect the omnibox icon now that the sharesheet is closed.
DeselectIcon();
}
void SharingHubBubbleController::DeselectIcon() {
if (!highlighted_button_tracker_.view())
return;
views::Button* button =
views::Button::AsButton(highlighted_button_tracker_.view());
if (button)
button->SetHighlighted(false);
}
void SharingHubBubbleController::OnVisibilityChanged(
content::Visibility visibility) {
// Cancel the current share attempt if the user switches to a different tab in
// the window. Switching windows is permitted since a sharesheet is tied to
// the native window.
if (bubble_showing_ && visibility == content::Visibility::HIDDEN) {
CloseSharesheet();
}
}
#endif
SharingHubBubbleController::SharingHubBubbleController(
content::WebContents* web_contents)
: content::WebContentsObserver(web_contents),
content::WebContentsUserData<SharingHubBubbleController>(*web_contents) {}
WEB_CONTENTS_USER_DATA_KEY_IMPL(SharingHubBubbleController);
} // namespace sharing_hub