blob: 8ed38f183531d6a0ede638411ef2bca089b87a8f [file] [log] [blame]
// Copyright 2016 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 "components/arc/intent_helper/arc_intent_helper_bridge.h"
#include <iterator>
#include <utility>
#include "ash/components/arc/arc_browser_context_keyed_service_factory_base.h"
#include "ash/components/arc/audio/arc_audio_bridge.h"
#include "ash/components/arc/session/arc_bridge_service.h"
#include "ash/components/arc/session/arc_service_manager.h"
#include "ash/public/cpp/new_window_delegate.h"
#include "ash/public/cpp/wallpaper/wallpaper_controller.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/memory/singleton.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_util.h"
#include "base/values.h"
#include "components/arc/common/intent_helper/arc_intent_helper_package.h"
#include "components/arc/intent_helper/control_camera_app_delegate.h"
#include "components/arc/intent_helper/intent_constants.h"
#include "components/arc/intent_helper/open_url_delegate.h"
#include "components/url_formatter/url_fixer.h"
#include "net/base/url_util.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "ui/base/layout.h"
#include "url/url_constants.h"
namespace arc {
namespace {
constexpr const char* kArcSchemes[] = {url::kHttpScheme, url::kHttpsScheme,
url::kContentScheme, url::kFileScheme,
url::kMailToScheme};
// Not owned. Must outlive all ArcIntentHelperBridge instances. Typically this
// is ChromeNewWindowClient in the browser.
OpenUrlDelegate* g_open_url_delegate = nullptr;
ControlCameraAppDelegate* g_control_camera_app_delegate = nullptr;
// Singleton factory for ArcIntentHelperBridge.
class ArcIntentHelperBridgeFactory
: public internal::ArcBrowserContextKeyedServiceFactoryBase<
ArcIntentHelperBridge,
ArcIntentHelperBridgeFactory> {
public:
// Factory name used by ArcBrowserContextKeyedServiceFactoryBase.
static constexpr const char* kName = "ArcIntentHelperBridgeFactory";
static ArcIntentHelperBridgeFactory* GetInstance() {
return base::Singleton<ArcIntentHelperBridgeFactory>::get();
}
private:
friend struct base::DefaultSingletonTraits<ArcIntentHelperBridgeFactory>;
ArcIntentHelperBridgeFactory() = default;
~ArcIntentHelperBridgeFactory() override = default;
};
// Keep in sync with ArcIntentHelperOpenType enum in
// //tools/metrics/histograms/enums.xml.
enum class ArcIntentHelperOpenType {
DOWNLOADS = 0,
URL = 1,
CUSTOM_TAB = 2,
WALLPAPER_PICKER = 3,
VOLUME_CONTROL = 4,
CHROME_PAGE = 5,
WEB_APP = 6,
kMaxValue = WEB_APP,
};
// Records Arc.IntentHelper.OpenType UMA histogram.
void RecordOpenType(ArcIntentHelperOpenType type) {
UMA_HISTOGRAM_ENUMERATION("Arc.IntentHelper.OpenType", type);
}
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class OpenIntentAction {
kUnknown = 0,
kView = 1,
kSend = 2,
kSendMultiple = 3,
kMaxValue = kSendMultiple,
};
void RecordOpenAppIntentAction(const mojom::LaunchIntentPtr& intent) {
OpenIntentAction action = OpenIntentAction::kUnknown;
if (intent->action == kIntentActionView) {
action = OpenIntentAction::kView;
} else if (intent->action == kIntentActionSend) {
action = OpenIntentAction::kSend;
} else if (intent->action == kIntentActionSendMultiple) {
action = OpenIntentAction::kSendMultiple;
}
UMA_HISTOGRAM_ENUMERATION("Arc.IntentHelper.OpenAppWithIntentAction", action);
}
// Returns true if a Web App is allowed to be opened for the given URL.
bool CanOpenWebAppForUrl(const GURL& url) {
bool is_http_localhost =
url.SchemeIs(url::kHttpScheme) && net::IsLocalhost(url);
return url.is_valid() &&
(url.SchemeIs(url::kHttpsScheme) || is_http_localhost);
}
} // namespace
// static
ArcIntentHelperBridge* ArcIntentHelperBridge::GetForBrowserContext(
content::BrowserContext* context) {
return ArcIntentHelperBridgeFactory::GetForBrowserContext(context);
}
// static
ArcIntentHelperBridge* ArcIntentHelperBridge::GetForBrowserContextForTesting(
content::BrowserContext* context) {
return ArcIntentHelperBridgeFactory::GetForBrowserContextForTesting(context);
}
// static
BrowserContextKeyedServiceFactory* ArcIntentHelperBridge::GetFactory() {
return ArcIntentHelperBridgeFactory::GetInstance();
}
// static
std::string ArcIntentHelperBridge::AppendStringToIntentHelperPackageName(
const std::string& to_append) {
return base::JoinString({kArcIntentHelperPackageName, to_append}, ".");
}
// static
void ArcIntentHelperBridge::SetOpenUrlDelegate(OpenUrlDelegate* delegate) {
g_open_url_delegate = delegate;
}
// static
void ArcIntentHelperBridge::SetControlCameraAppDelegate(
ControlCameraAppDelegate* delegate) {
g_control_camera_app_delegate = delegate;
}
void ArcIntentHelperBridge::SetDelegate(std::unique_ptr<Delegate> delegate) {
delegate_ = std::move(delegate);
}
ArcIntentHelperBridge::ArcIntentHelperBridge(content::BrowserContext* context,
ArcBridgeService* bridge_service)
: context_(context),
arc_bridge_service_(bridge_service),
allowed_arc_schemes_(std::cbegin(kArcSchemes), std::cend(kArcSchemes)) {
arc_bridge_service_->intent_helper()->SetHost(this);
}
ArcIntentHelperBridge::~ArcIntentHelperBridge() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
arc_bridge_service_->intent_helper()->SetHost(nullptr);
}
void ArcIntentHelperBridge::Shutdown() {
for (auto& observer : observer_list_)
observer.OnArcIntentHelperBridgeShutdown();
}
void ArcIntentHelperBridge::OnIconInvalidated(const std::string& package_name) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
icon_loader_.InvalidateIcons(package_name);
for (auto& observer : observer_list_)
observer.OnIconInvalidated(package_name);
}
void ArcIntentHelperBridge::OnIntentFiltersUpdated(
std::vector<IntentFilter> filters) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
intent_filters_.clear();
for (auto& filter : filters)
intent_filters_[filter.package_name()].push_back(std::move(filter));
for (auto& observer : observer_list_)
observer.OnIntentFiltersUpdated(absl::nullopt);
}
void ArcIntentHelperBridge::OnOpenDownloads() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
RecordOpenType(ArcIntentHelperOpenType::DOWNLOADS);
ash::NewWindowDelegate::GetInstance()->OpenDownloadsFolder();
}
void ArcIntentHelperBridge::OnOpenUrl(const std::string& url) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
RecordOpenType(ArcIntentHelperOpenType::URL);
// Converts |url| to a fixed-up one and checks validity.
const GURL gurl(url_formatter::FixupURL(url, /*desired_tld=*/std::string()));
if (!gurl.is_valid())
return;
if (allowed_arc_schemes_.find(gurl.scheme()) != allowed_arc_schemes_.end())
g_open_url_delegate->OpenUrlFromArc(gurl);
}
void ArcIntentHelperBridge::OnOpenCustomTab(const std::string& url,
int32_t task_id,
OnOpenCustomTabCallback callback) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
RecordOpenType(ArcIntentHelperOpenType::CUSTOM_TAB);
// Converts |url| to a fixed-up one and checks validity.
const GURL gurl(url_formatter::FixupURL(url, /*desired_tld=*/std::string()));
if (!gurl.is_valid() ||
allowed_arc_schemes_.find(gurl.scheme()) == allowed_arc_schemes_.end()) {
std::move(callback).Run(mojo::NullRemote());
return;
}
g_open_url_delegate->OpenArcCustomTab(gurl, task_id, std::move(callback));
}
void ArcIntentHelperBridge::OnOpenChromePage(mojom::ChromePage page) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
RecordOpenType(ArcIntentHelperOpenType::CHROME_PAGE);
g_open_url_delegate->OpenChromePageFromArc(page);
}
void ArcIntentHelperBridge::FactoryResetArc() {
if (delegate_)
delegate_->ResetArc();
}
void ArcIntentHelperBridge::OpenWallpaperPicker() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
RecordOpenType(ArcIntentHelperOpenType::WALLPAPER_PICKER);
ash::WallpaperController::Get()->OpenWallpaperPickerIfAllowed();
}
void ArcIntentHelperBridge::OpenVolumeControl() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
RecordOpenType(ArcIntentHelperOpenType::VOLUME_CONTROL);
auto* audio = ArcAudioBridge::GetForBrowserContext(context_);
DCHECK(audio);
audio->ShowVolumeControls();
}
void ArcIntentHelperBridge::OnOpenWebApp(const std::string& url) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
RecordOpenType(ArcIntentHelperOpenType::WEB_APP);
// Converts |url| to a fixed-up one and checks validity.
const GURL gurl(url_formatter::FixupURL(url, /*desired_tld=*/std::string()));
// Web app launches should only be invoked on HTTPS URLs.
if (CanOpenWebAppForUrl(gurl))
g_open_url_delegate->OpenWebAppFromArc(gurl);
}
// TODO(b/200873831): Delete this anytime on 2022.
void ArcIntentHelperBridge::RecordShareFilesMetricsDeprecated(
mojom::ShareFiles flag) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// Record metrics coming from ARC, these are related Share files feature
// stability.
LOG(ERROR) << "Arc.ShareFilesOnExit is deprecated, erasing incoming";
}
void ArcIntentHelperBridge::LaunchCameraApp(uint32_t intent_id,
arc::mojom::CameraIntentMode mode,
bool should_handle_result,
bool should_down_scale,
bool is_secure,
int32_t task_id) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
base::DictionaryValue intent_info;
std::string mode_str =
mode == arc::mojom::CameraIntentMode::PHOTO ? "photo" : "video";
std::stringstream queries;
queries << "?intentId=" << intent_id << "&mode=" << mode_str
<< "&shouldHandleResult=" << should_handle_result
<< "&shouldDownScale=" << should_down_scale
<< "&isSecure=" << is_secure;
g_control_camera_app_delegate->LaunchCameraApp(queries.str(), task_id);
}
void ArcIntentHelperBridge::OnIntentFiltersUpdatedForPackage(
const std::string& package_name,
std::vector<IntentFilter> filters) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
intent_filters_.erase(package_name);
if (filters.size() > 0)
intent_filters_[package_name] = std::move(filters);
for (auto& observer : observer_list_)
observer.OnIntentFiltersUpdated(package_name);
}
void ArcIntentHelperBridge::CloseCameraApp() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
g_control_camera_app_delegate->CloseCameraApp();
}
void ArcIntentHelperBridge::IsChromeAppEnabled(
arc::mojom::ChromeApp app,
IsChromeAppEnabledCallback callback) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (app == arc::mojom::ChromeApp::CAMERA) {
std::move(callback).Run(
g_control_camera_app_delegate->IsCameraAppEnabled());
return;
}
NOTREACHED() << "Unknown chrome app";
std::move(callback).Run(false);
}
void ArcIntentHelperBridge::OnSupportedLinksChanged(
std::vector<arc::mojom::SupportedLinksPtr> added_packages,
std::vector<arc::mojom::SupportedLinksPtr> removed_packages,
arc::mojom::SupportedLinkChangeSource source) {
for (auto& observer : observer_list_)
observer.OnArcSupportedLinksChanged(added_packages, removed_packages,
source);
}
void ArcIntentHelperBridge::OnDownloadAdded(
const std::string& relative_path_as_string,
const std::string& owner_package_name) {
const base::FilePath download_folder("Download/");
const base::FilePath relative_path(relative_path_as_string);
// Observers should *not* be called when a download is added outside of the
// Download/ folder. This would be an unexpected event coming from ARC but
// we protect against it because ARC is treated as an untrusted source.
if (!download_folder.IsParent(relative_path) ||
relative_path.ReferencesParent()) {
return;
}
for (auto& observer : observer_list_)
observer.OnArcDownloadAdded(relative_path, owner_package_name);
}
void ArcIntentHelperBridge::OnOpenAppWithIntent(
const GURL& start_url,
arc::mojom::LaunchIntentPtr intent) {
// Web app launches should only be invoked on HTTPS URLs.
if (CanOpenWebAppForUrl(start_url)) {
RecordOpenType(ArcIntentHelperOpenType::WEB_APP);
RecordOpenAppIntentAction(intent);
g_open_url_delegate->OpenAppWithIntent(start_url, std::move(intent));
}
}
ArcIntentHelperBridge::GetResult ArcIntentHelperBridge::GetActivityIcons(
const std::vector<ActivityName>& activities,
OnIconsReadyCallback callback) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return icon_loader_.GetActivityIcons(activities, std::move(callback));
}
bool ArcIntentHelperBridge::ShouldChromeHandleUrl(const GURL& url) {
if (!url.SchemeIsHTTPOrHTTPS()) {
// Chrome will handle everything that is not http and https.
return true;
}
for (auto& package_filters : intent_filters_) {
// The intent helper package is used by ARC to send URLs to Chrome, so it
// does not count as a candidate.
if (package_filters.first == kArcIntentHelperPackageName)
continue;
for (auto& filter : package_filters.second) {
if (filter.Match(url))
return false;
}
}
// Didn't find any matches for Android so let Chrome handle it.
return true;
}
void ArcIntentHelperBridge::SetAdaptiveIconDelegate(
AdaptiveIconDelegate* delegate) {
icon_loader_.SetAdaptiveIconDelegate(delegate);
}
void ArcIntentHelperBridge::AddObserver(ArcIntentHelperObserver* observer) {
observer_list_.AddObserver(observer);
}
void ArcIntentHelperBridge::RemoveObserver(ArcIntentHelperObserver* observer) {
observer_list_.RemoveObserver(observer);
}
bool ArcIntentHelperBridge::HasObserver(
ArcIntentHelperObserver* observer) const {
return observer_list_.HasObserver(observer);
}
void ArcIntentHelperBridge::HandleCameraResult(
uint32_t intent_id,
arc::mojom::CameraIntentAction action,
const std::vector<uint8_t>& data,
arc::mojom::IntentHelperInstance::HandleCameraResultCallback callback) {
auto* arc_service_manager = arc::ArcServiceManager::Get();
arc::mojom::IntentHelperInstance* instance = nullptr;
if (arc_service_manager) {
instance = ARC_GET_INSTANCE_FOR_METHOD(
arc_service_manager->arc_bridge_service()->intent_helper(),
HandleCameraResult);
}
if (!instance) {
LOG(ERROR) << "Failed to get instance for HandleCameraResult().";
std::move(callback).Run(false);
return;
}
instance->HandleCameraResult(intent_id, action, data, std::move(callback));
}
void ArcIntentHelperBridge::SendNewCaptureBroadcast(bool is_video,
std::string file_path) {
auto* arc_service_manager = arc::ArcServiceManager::Get();
arc::mojom::IntentHelperInstance* instance = nullptr;
if (arc_service_manager) {
instance = ARC_GET_INSTANCE_FOR_METHOD(
arc_service_manager->arc_bridge_service()->intent_helper(),
SendBroadcast);
}
if (!instance) {
LOG(ERROR) << "Failed to get instance for SendBroadcast().";
return;
}
std::string action =
is_video ? "org.chromium.arc.intent_helper.ACTION_SEND_NEW_VIDEO"
: "org.chromium.arc.intent_helper.ACTION_SEND_NEW_PICTURE";
base::DictionaryValue value;
value.SetString("file_path", file_path);
std::string extras;
base::JSONWriter::Write(value, &extras);
instance->SendBroadcast(action, "org.chromium.arc.intent_helper",
/*cls=*/std::string(), extras);
}
// static
std::vector<mojom::IntentHandlerInfoPtr>
ArcIntentHelperBridge::FilterOutIntentHelper(
std::vector<mojom::IntentHandlerInfoPtr> handlers) {
std::vector<mojom::IntentHandlerInfoPtr> handlers_filtered;
for (auto& handler : handlers) {
if (handler->package_name == kArcIntentHelperPackageName)
continue;
handlers_filtered.push_back(std::move(handler));
}
return handlers_filtered;
}
const std::vector<IntentFilter>&
ArcIntentHelperBridge::GetIntentFilterForPackage(
const std::string& package_name) {
return intent_filters_[package_name];
}
} // namespace arc