Commit 7027a4a8 authored by Dominick Ng's avatar Dominick Ng Committed by Commit Bot

Make web apps with a supported related app intent to the store.

This CL allows Chrome OS to query ARC for whether a PWA has a supported
related app available and installable. If it does, the installation
action redirects to the Play Store. A Finch kill-switch to disable the
behaviour is also added.

BUG=969560

Change-Id: I7f2d748836400aecec40655e25de0e2f901c4432
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1642753Reviewed-by: default avatarAlexey Baskakov <loyso@chromium.org>
Commit-Queue: Dominick Ng <dominickn@chromium.org>
Cr-Commit-Position: refs/heads/master@{#666623}
parent c374c44b
......@@ -7,12 +7,14 @@
#include <utility>
#include "base/bind.h"
#include "base/feature_list.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/browser/chromeos/apps/apk_web_app_service_factory.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/web_applications/components/externally_installed_web_app_prefs.h"
#include "chrome/browser/web_applications/components/web_app_constants.h"
#include "chrome/common/chrome_features.h"
#include "components/arc/common/app.mojom.h"
#include "components/arc/session/connection_holder.h"
#include "components/pref_registry/pref_registry_syncable.h"
......@@ -124,6 +126,9 @@ void ApkWebAppService::Shutdown() {
void ApkWebAppService::OnPackageInstalled(
const arc::mojom::ArcPackageInfo& package_info) {
if (!base::FeatureList::IsEnabled(features::kApkWebAppInstalls))
return;
// This method is called when a) new packages are installed, and b) existing
// packages are updated. In (b), there are two cases to handle: the package
// could previously have been an Android app and has now become a web app, and
......@@ -192,6 +197,9 @@ void ApkWebAppService::OnPackageRemoved(const std::string& package_name,
// will trigger the uninstallation of the web app. Similarly, this method
// removes the associated web_app_id before triggering uninstallation, so
// OnExtensionUninstalled() will do nothing.
if (!base::FeatureList::IsEnabled(features::kApkWebAppInstalls))
return;
DictionaryPrefUpdate web_apps_to_apks(profile_->GetPrefs(),
kWebAppToApkDictPref);
......@@ -217,6 +225,9 @@ void ApkWebAppService::OnPackageRemoved(const std::string& package_name,
}
void ApkWebAppService::OnPackageListInitialRefreshed() {
if (!base::FeatureList::IsEnabled(features::kApkWebAppInstalls))
return;
// Scan through the list of apps to see if any were uninstalled while ARC
// wasn't running.
DictionaryPrefUpdate web_apps_to_apks(profile_->GetPrefs(),
......@@ -259,6 +270,9 @@ void ApkWebAppService::OnExtensionUninstalled(
content::BrowserContext* browser_context,
const extensions::Extension* extension,
extensions::UninstallReason reason) {
if (!base::FeatureList::IsEnabled(features::kApkWebAppInstalls))
return;
DictionaryPrefUpdate web_apps_to_apks(profile_->GetPrefs(),
kWebAppToApkDictPref);
......
......@@ -57,10 +57,20 @@
#include "extensions/browser/pref_names.h"
#include "extensions/common/extension.h"
#include "net/base/load_flags.h"
#include "net/base/url_util.h"
#include "net/url_request/url_request.h"
#include "third_party/blink/public/common/manifest/manifest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#if defined(OS_CHROMEOS)
#include "base/strings/string_util.h"
#include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
#include "components/arc/arc_service_manager.h"
#include "components/arc/common/app.mojom.h"
#include "components/arc/common/intent_helper.mojom.h"
#include "components/arc/session/arc_bridge_service.h"
#endif
namespace extensions {
namespace {
......@@ -203,6 +213,20 @@ class BookmarkAppInstaller : public base::RefCounted<BookmarkAppInstaller>,
std::vector<web_app::BitmapAndSource> downloaded_bitmaps_;
};
#if defined(OS_CHROMEOS)
const char kChromeOsPlayPlatform[] = "chromeos_play";
const char kPlayIntentPrefix[] =
"https://play.google.com/store/apps/details?id=";
std::string ExtractQueryValueForName(const GURL& url, const std::string& name) {
for (net::QueryIterator it(url); !it.IsAtEnd(); it.Advance()) {
if (it.GetKey() == name)
return it.GetValue();
}
return std::string();
}
#endif // defined(OS_CHROMEOS)
} // namespace
BookmarkAppHelper::BookmarkAppHelper(
......@@ -312,6 +336,11 @@ void BookmarkAppHelper::OnDidPerformInstallableCheck(
contents_->GetVisibleURL().SchemeIs(content::kChromeUIScheme))
web_app_icon_downloader_->SkipPageFavicons();
// If we tried to check for an intent to the Play Store, wait for the async
// reply.
if (DidCheckForIntentToPlayStore(*data.manifest))
return;
web_app_icon_downloader_->Start();
}
......@@ -487,6 +516,78 @@ void BookmarkAppHelper::OnShortcutCreationCompleted(
callback_.Run(extension, web_app_info_);
}
bool BookmarkAppHelper::DidCheckForIntentToPlayStore(
const blink::Manifest& manifest) {
#if defined(OS_CHROMEOS)
if (!base::FeatureList::IsEnabled(features::kApkWebAppInstalls))
return false;
if (for_installable_site_ != web_app::ForInstallableSite::kYes)
return false;
for (const auto& application : manifest.related_applications) {
std::string id = base::UTF16ToUTF8(application.id.string());
if (!base::EqualsASCII(application.platform.string(),
kChromeOsPlayPlatform)) {
continue;
}
std::string id_from_app_url =
ExtractQueryValueForName(application.url, "id");
if (id.empty()) {
if (id_from_app_url.empty())
continue;
id = id_from_app_url;
}
// Attach the referrer value.
std::string referrer =
ExtractQueryValueForName(application.url, "referrer");
if (!referrer.empty())
referrer = "&referrer=" + referrer;
std::string intent = kPlayIntentPrefix + id + referrer;
auto* arc_service_manager = arc::ArcServiceManager::Get();
if (arc_service_manager) {
auto* instance = ARC_GET_INSTANCE_FOR_METHOD(
arc_service_manager->arc_bridge_service()->app(), IsInstallable);
if (instance) {
instance->IsInstallable(
id,
base::BindOnce(&BookmarkAppHelper::OnDidCheckForIntentToPlayStore,
weak_factory_.GetWeakPtr(), intent));
return true;
}
}
}
#endif // defined(OS_CHROMEOS)
return false;
}
void BookmarkAppHelper::OnDidCheckForIntentToPlayStore(
const std::string& intent,
bool should_intent_to_store) {
#if defined(OS_CHROMEOS)
if (should_intent_to_store) {
auto* arc_service_manager = arc::ArcServiceManager::Get();
if (arc_service_manager) {
auto* instance = ARC_GET_INSTANCE_FOR_METHOD(
arc_service_manager->arc_bridge_service()->intent_helper(),
HandleUrl);
if (instance) {
instance->HandleUrl(intent, arc::kPlayStorePackage);
callback_.Run(nullptr, web_app_info_);
return;
}
}
}
#endif // defined(OS_CHROMEOS)
web_app_icon_downloader_->Start();
}
void BookmarkAppHelper::Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
......
......@@ -28,6 +28,10 @@ class InstallableManager;
class Profile;
class SkBitmap;
namespace blink {
struct Manifest;
}
namespace content {
class WebContents;
} // namespace content
......@@ -163,6 +167,17 @@ class BookmarkAppHelper : public content::NotificationObserver {
void OnShortcutCreationCompleted(const std::string& extension_id,
bool shortcut_created);
void MaybeStartIconDownload();
// Returns true if we dispatched an asynchronous check for whether an intent
// to the Play Store should be made, and false otherwise.
bool DidCheckForIntentToPlayStore(const blink::Manifest& manifest);
// Called when the asynchronous check for whether an intent to the Play Store
// should be made returns.
void OnDidCheckForIntentToPlayStore(const std::string& intent,
bool should_intent_to_store);
// Overridden from content::NotificationObserver:
void Observe(int type,
const content::NotificationSource& source,
......
......@@ -28,6 +28,12 @@ const base::Feature kAddToHomescreenMessaging{
"AddToHomescreenMessaging", base::FEATURE_DISABLED_BY_DEFAULT};
#endif // defined(OS_ANDROID)
#if defined(OS_CHROMEOS)
// Controls whether web apps can be installed via APKs on Chrome OS.
const base::Feature kApkWebAppInstalls{"ApkWebAppInstalls",
base::FEATURE_ENABLED_BY_DEFAULT};
#endif // defined(OS_CHROMEOS)
#if defined(OS_MACOSX)
// Enables the menu item for Javascript execution via AppleScript.
const base::Feature kAppleScriptExecuteJavaScriptMenuItem{
......
......@@ -29,6 +29,11 @@ COMPONENT_EXPORT(CHROME_FEATURES)
extern const base::Feature kAddToHomescreenMessaging;
#endif // defined(OS_ANDROID)
#if defined(OS_CHROMEOS)
COMPONENT_EXPORT(CHROME_FEATURES)
extern const base::Feature kApkWebAppInstalls;
#endif // defined(OS_CHROMEOS)
#if defined(OS_MACOSX)
COMPONENT_EXPORT(CHROME_FEATURES)
extern const base::Feature kAppleScriptExecuteJavaScriptMenuItem;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment