blob: 45254ef48708d814503939b4466a4396699eedba [file] [log] [blame]
// 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/apps/intent_helper/chromeos_intent_picker_helpers.h"
#include <algorithm>
#include <utility>
#include "base/debug/dump_without_crashing.h"
#include "base/functional/bind.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/apps/app_service/launch_utils.h"
#include "chrome/browser/apps/intent_helper/apps_navigation_types.h"
#include "chrome/browser/apps/intent_helper/intent_picker_auto_display_prefs.h"
#include "chrome/browser/apps/intent_helper/intent_picker_constants.h"
#include "chrome/browser/apps/intent_helper/intent_picker_features.h"
#include "chrome/browser/apps/intent_helper/intent_picker_internal.h"
#include "chrome/browser/apps/intent_helper/metrics/intent_handling_metrics.h"
#include "chrome/browser/apps/intent_helper/supported_links_infobar_delegate.h"
#include "chrome/browser/feature_engagement/tracker_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/intent_picker_tab_helper.h"
#include "chrome/browser/ui/web_applications/web_app_launch_utils.h"
#include "components/feature_engagement/public/tracker.h"
#include "components/services/app_service/public/cpp/app_launch_util.h"
#include "components/services/app_service/public/cpp/intent_util.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/visibility.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/models/image_model.h"
#include "ui/display/types/display_constants.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ash/constants/ash_switches.h"
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
namespace apps {
namespace {
bool ShouldAutoDisplayUi(
const std::vector<IntentPickerAppInfo>& apps_for_picker,
NavigationInfo navigation_info) {
content::WebContents* web_contents = navigation_info.web_contents;
if (web_contents->GetVisibility() == content::Visibility::HIDDEN) {
return false;
}
const GURL& url = navigation_info.url;
// Disable Auto-display in the new Intent Picker UI.
if (features::LinkCapturingUiUpdateEnabled()) {
return false;
}
if (apps_for_picker.empty())
return false;
if (InAppBrowser(web_contents))
return false;
if (!ShouldOverrideUrlLoading(navigation_info.starting_url, url))
return false;
Profile* profile =
Profile::FromBrowserContext(web_contents->GetBrowserContext());
// On devices with tablet form factor we should not pop out the intent
// picker if Chrome has been chosen by the user as the platform for this URL.
// TODO(crbug.com/1225828): Handle this for lacros-chrome as well.
#if BUILDFLAG(IS_CHROMEOS_ASH)
if (ash::switches::IsTabletFormFactor()) {
if (IntentPickerAutoDisplayPrefs::GetLastUsedPlatformForTablets(
profile, url) == IntentPickerAutoDisplayPrefs::Platform::kChrome) {
return false;
}
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
// If the preferred app is use browser, do not show the intent picker.
auto* proxy = AppServiceProxyFactory::GetForProfile(profile);
if (proxy) {
auto preferred_app_id =
proxy->PreferredAppsList().FindPreferredAppForUrl(url);
if (preferred_app_id.has_value() &&
preferred_app_id.value() == apps_util::kUseBrowserForLink) {
return false;
}
}
return IntentPickerAutoDisplayPrefs::ShouldAutoDisplayUi(profile, url);
}
PickerShowState GetPickerShowState(
NavigationInfo navigation_info,
const std::vector<IntentPickerAppInfo>& apps_for_picker) {
return ShouldAutoDisplayUi(apps_for_picker, navigation_info) &&
navigation_info.is_navigate_from_link
? PickerShowState::kPopOut
: PickerShowState::kOmnibox;
}
void OnAppIconsLoaded(content::WebContents* web_contents,
const GURL& url,
std::vector<IntentPickerAppInfo> apps) {
ShowIntentPickerBubbleForApps(
web_contents, std::move(apps),
/*show_stay_in_chrome=*/true,
/*show_remember_selection=*/true,
base::BindOnce(&OnIntentPickerClosedChromeOs, web_contents->GetWeakPtr(),
PickerShowState::kPopOut, url));
}
} // namespace
void MaybeShowIntentPickerBubble(NavigationInfo navigation_info,
std::vector<IntentPickerAppInfo> apps) {
if (apps.empty() ||
GetPickerShowState(navigation_info, apps) == PickerShowState::kOmnibox) {
return;
}
IntentHandlingMetrics::RecordIntentPickerIconEvent(
IntentHandlingMetrics::IntentPickerIconEvent::kAutoPopOut);
content::WebContents* web_contents = navigation_info.web_contents;
const GURL& url = navigation_info.url;
IntentPickerTabHelper::LoadAppIcons(
web_contents, std::move(apps),
base::BindOnce(&OnAppIconsLoaded, web_contents, url));
}
void OnIntentPickerClosedChromeOs(
base::WeakPtr<content::WebContents> web_contents,
PickerShowState show_state,
const GURL& url,
const std::string& launch_name,
PickerEntryType entry_type,
IntentPickerCloseReason close_reason,
bool should_persist) {
if (!web_contents) {
return;
}
Profile* profile =
Profile::FromBrowserContext(web_contents->GetBrowserContext());
// TODO(crbug.com/1225828): Handle this for lacros-chrome as well.
#if BUILDFLAG(IS_CHROMEOS_ASH)
if (ash::switches::IsTabletFormFactor() && should_persist) {
// On devices of tablet form factor, until the user has decided to persist
// the setting, the browser-side intent picker should always be seen.
auto platform = IntentPickerAutoDisplayPrefs::Platform::kNone;
if (entry_type == PickerEntryType::kArc) {
platform = IntentPickerAutoDisplayPrefs::Platform::kArc;
} else if (entry_type == PickerEntryType::kUnknown &&
close_reason == IntentPickerCloseReason::STAY_IN_CHROME) {
platform = IntentPickerAutoDisplayPrefs::Platform::kChrome;
}
IntentPickerAutoDisplayPrefs::UpdatePlatformForTablets(profile, url,
platform);
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
const bool should_launch_app =
close_reason == IntentPickerCloseReason::OPEN_APP;
// If the picker was closed without an app being chosen,
// e.g. due to the tab being closed. Keep count of this scenario so we can
// stop the UI from showing after 2+ dismissals.
if (entry_type == PickerEntryType::kUnknown &&
close_reason == IntentPickerCloseReason::DIALOG_DEACTIVATED &&
show_state == PickerShowState::kPopOut) {
IntentPickerAutoDisplayPrefs::IncrementPickerUICounter(profile, url);
}
if (should_persist) {
DCHECK(!launch_name.empty());
auto* proxy = AppServiceProxyFactory::GetForProfile(profile);
DCHECK(proxy);
proxy->AddPreferredApp(launch_name, url);
apps::IntentHandlingMetrics::RecordLinkCapturingEvent(
entry_type,
apps::IntentHandlingMetrics::LinkCapturingEvent::kSettingsChanged);
}
if (should_launch_app) {
LaunchAppFromIntentPickerChromeOs(web_contents.get(), url, launch_name,
entry_type);
}
IntentHandlingMetrics::RecordIntentPickerMetrics(entry_type, close_reason,
should_persist);
}
void LaunchAppFromIntentPickerChromeOs(content::WebContents* web_contents,
const GURL& url,
const std::string& launch_name,
PickerEntryType app_type) {
DCHECK(!launch_name.empty());
Profile* profile =
Profile::FromBrowserContext(web_contents->GetBrowserContext());
if (base::FeatureList::IsEnabled(features::kLinkCapturingUiUpdate)) {
chrome::FindBrowserWithWebContents(web_contents)->window()
->NotifyFeatureEngagementEvent(kIntentChipOpensAppEvent);
IntentPickerAutoDisplayPrefs::ResetIntentChipCounter(profile, url);
}
apps::IntentHandlingMetrics::RecordLinkCapturingEvent(
app_type, apps::IntentHandlingMetrics::LinkCapturingEvent::kAppOpened);
if (app_type == PickerEntryType::kWeb) {
web_app::ReparentWebContentsIntoAppBrowser(web_contents, launch_name);
if (features::LinkCapturingInfoBarEnabled()) {
SupportedLinksInfoBarDelegate::MaybeShowSupportedLinksInfoBar(
web_contents, launch_name);
}
} else {
auto* proxy = AppServiceProxyFactory::GetForProfile(profile);
// TODO(crbug.com/853604): Distinguish the source from link and omnibox.
proxy->LaunchAppWithUrl(
launch_name,
GetEventFlags(WindowOpenDisposition::NEW_WINDOW,
/*prefer_container=*/true),
url, LaunchSource::kFromLink,
std::make_unique<WindowInfo>(display::kDefaultDisplayId));
CloseOrGoBack(web_contents);
}
}
} // namespace apps