blob: 65b90660525caad94d9f13e99e93005fe40521ac [file] [log] [blame]
// 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.
#import "ios/chrome/browser/app_launcher/app_launcher_browser_agent.h"
#include "base/bind.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#import "ios/chrome/browser/app_launcher/app_launcher_tab_helper.h"
#import "ios/chrome/browser/app_launcher/app_launcher_util.h"
#import "ios/chrome/browser/main/browser.h"
#include "ios/chrome/browser/overlays/public/overlay_callback_manager.h"
#import "ios/chrome/browser/overlays/public/overlay_request.h"
#import "ios/chrome/browser/overlays/public/overlay_request_queue.h"
#import "ios/chrome/browser/overlays/public/overlay_response.h"
#import "ios/chrome/browser/overlays/public/web_content_area/app_launcher_alert_overlay.h"
#import "ios/chrome/browser/web_state_list/web_state_list.h"
#import "ios/chrome/browser/web_state_list/web_state_opener.h"
#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
#include "ios/public/provider/chrome/browser/mailto/mailto_handler_provider.h"
#import "ios/web/public/navigation/navigation_manager.h"
#import "ios/web/public/web_state.h"
#import "net/base/mac/url_conversions.h"
#include "url/gurl.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
BROWSER_USER_DATA_KEY_IMPL(AppLauncherBrowserAgent)
namespace {
// Records histogram metric on the user's response when prompted to open another
// application. |user_accepted| should be YES if the user accepted the prompt to
// launch another application. This call is extracted to a separate function to
// reduce macro code expansion.
void RecordUserAcceptedAppLaunchMetric(BOOL user_accepted) {
UMA_HISTOGRAM_BOOLEAN("Tab.ExternalApplicationOpened", user_accepted);
}
// Callback for the app launcher alert overlay.
void AppLauncherOverlayCallback(base::OnceCallback<void(bool)> completion,
bool repeated_request,
OverlayResponse* response) {
// Extract the user decision from |response|.
bool user_accepted = false;
if (response) {
AppLauncherAlertOverlayResponseInfo* info =
response->GetInfo<AppLauncherAlertOverlayResponseInfo>();
user_accepted = info && info->allow_navigation();
}
// Record the UMA for repeated requests.
if (repeated_request)
RecordUserAcceptedAppLaunchMetric(user_accepted);
// Execute the completion with the response.
DCHECK(!completion.is_null());
std::move(completion).Run(user_accepted);
}
// Launches the app for |url| if |user_accepted| is true.
void LaunchExternalApp(const GURL url, bool user_accepted = true) {
if (!user_accepted)
return;
[[UIApplication sharedApplication] openURL:net::NSURLWithGURL(url)
options:@{}
completionHandler:nil];
}
} // namespace
#pragma mark - AppLauncherBrowserAgent
AppLauncherBrowserAgent::AppLauncherBrowserAgent(Browser* browser)
: tab_helper_delegate_(browser->GetWebStateList()),
tab_helper_delegate_installer_(&tab_helper_delegate_,
browser->GetWebStateList()),
shutdown_helper_(browser, &tab_helper_delegate_installer_) {}
AppLauncherBrowserAgent::~AppLauncherBrowserAgent() = default;
#pragma mark - AppLauncherBrowserAgent::TabHelperDelegate
AppLauncherBrowserAgent::TabHelperDelegate::TabHelperDelegate(
WebStateList* web_state_list)
: web_state_list_(web_state_list) {
DCHECK(web_state_list_);
}
AppLauncherBrowserAgent::TabHelperDelegate::~TabHelperDelegate() = default;
#pragma mark AppLauncherTabHelperDelegate
void AppLauncherBrowserAgent::TabHelperDelegate::LaunchAppForTabHelper(
AppLauncherTabHelper* tab_helper,
const GURL& url,
bool link_transition) {
// Don't open application if chrome is not active.
if ([[UIApplication sharedApplication] applicationState] !=
UIApplicationStateActive) {
return;
}
// Uses a Mailto Handler to open the appropriate app.
if (url.SchemeIs(url::kMailToScheme)) {
MailtoHandlerProvider* provider =
ios::GetChromeBrowserProvider()->GetMailtoHandlerProvider();
provider->HandleMailtoURL(net::NSURLWithGURL(url));
return;
}
// Show the a dialog for app store launches and external URL navigations that
// did not originate from a link tap.
bool show_dialog = UrlHasAppStoreScheme(url) || !link_transition;
if (show_dialog) {
std::unique_ptr<OverlayRequest> request =
OverlayRequest::CreateWithConfig<AppLauncherAlertOverlayRequestConfig>(
/*is_repeated_request=*/false);
request->GetCallbackManager()->AddCompletionCallback(base::BindOnce(
&AppLauncherOverlayCallback, base::BindOnce(&LaunchExternalApp, url),
/*repeated_request=*/false));
GetQueueForAppLaunchDialog(tab_helper->web_state())
->AddRequest(std::move(request));
} else {
LaunchExternalApp(url);
}
}
void AppLauncherBrowserAgent::TabHelperDelegate::ShowRepeatedAppLaunchAlert(
AppLauncherTabHelper* tab_helper,
base::OnceCallback<void(bool)> completion) {
std::unique_ptr<OverlayRequest> request =
OverlayRequest::CreateWithConfig<AppLauncherAlertOverlayRequestConfig>(
/*is_repeated_request=*/true);
request->GetCallbackManager()->AddCompletionCallback(
base::BindOnce(&AppLauncherOverlayCallback, std::move(completion),
/*is_repeated_request=*/true));
GetQueueForAppLaunchDialog(tab_helper->web_state())
->AddRequest(std::move(request));
}
#pragma mark Private
OverlayRequestQueue*
AppLauncherBrowserAgent::TabHelperDelegate::GetQueueForAppLaunchDialog(
web::WebState* web_state) {
web::WebState* queue_web_state = web_state;
// If an app launch navigation is occurring in a new tab, the tab will be
// closed immediately after the navigation fails, cancelling the app launcher
// dialog before it gets a chance to be shown. When this occurs, use the
// OverlayRequestQueue for the tab's opener instead.
if (!web_state->GetNavigationManager()->GetItemCount() &&
web_state->HasOpener()) {
int index = web_state_list_->GetIndexOfWebState(web_state);
queue_web_state =
web_state_list_->GetOpenerOfWebStateAt(index).opener ?: queue_web_state;
}
return OverlayRequestQueue::FromWebState(queue_web_state,
OverlayModality::kWebContentArea);
}
#pragma mark - AppLauncherBrowserAgent::TabHelperDelegateInstaller
AppLauncherBrowserAgent::TabHelperDelegateInstaller::TabHelperDelegateInstaller(
AppLauncherTabHelperDelegate* delegate,
WebStateList* web_state_list)
: delegate_(delegate) {
DCHECK(delegate_);
DCHECK(web_state_list);
for (int i = 0; i < web_state_list->count(); ++i) {
AppLauncherTabHelper::FromWebState(web_state_list->GetWebStateAt(i))
->SetDelegate(delegate_);
}
}
AppLauncherBrowserAgent::TabHelperDelegateInstaller::
~TabHelperDelegateInstaller() = default;
#pragma mark WebStateListObserver
void AppLauncherBrowserAgent::TabHelperDelegateInstaller::WebStateInsertedAt(
WebStateList* web_state_list,
web::WebState* web_state,
int index,
bool activating) {
AppLauncherTabHelper::FromWebState(web_state)->SetDelegate(delegate_);
}
void AppLauncherBrowserAgent::TabHelperDelegateInstaller::WebStateReplacedAt(
WebStateList* web_state_list,
web::WebState* old_web_state,
web::WebState* new_web_state,
int index) {
AppLauncherTabHelper::FromWebState(old_web_state)->SetDelegate(nullptr);
AppLauncherTabHelper::FromWebState(new_web_state)->SetDelegate(delegate_);
}
void AppLauncherBrowserAgent::TabHelperDelegateInstaller::WillDetachWebStateAt(
WebStateList* web_state_list,
web::WebState* web_state,
int index) {
AppLauncherTabHelper::FromWebState(web_state)->SetDelegate(nullptr);
}
#pragma mark - AppLauncherBrowserAgent::BrowserShutdownHelper
AppLauncherBrowserAgent::BrowserShutdownHelper::BrowserShutdownHelper(
Browser* browser,
WebStateListObserver* web_state_list_observer)
: web_state_list_observer_(web_state_list_observer) {
DCHECK(browser);
DCHECK(web_state_list_observer_);
scoped_observer_.Add(browser);
browser->GetWebStateList()->AddObserver(web_state_list_observer_);
}
AppLauncherBrowserAgent::BrowserShutdownHelper::~BrowserShutdownHelper() {
// The WebStateListObserver must be detached before destruction.
DCHECK(!web_state_list_observer_);
}
#pragma mark BrowserObserver
void AppLauncherBrowserAgent::BrowserShutdownHelper::BrowserDestroyed(
Browser* browser) {
scoped_observer_.Remove(browser);
browser->GetWebStateList()->RemoveObserver(web_state_list_observer_);
web_state_list_observer_ = nullptr;
}