// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/webapps/browser/android/app_banner_manager_android.h"
#include <limits>
#include <memory>
#include <optional>
#include <string>
#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "base/android/scoped_java_ref.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/metrics/field_trial_params.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/types/expected.h"
#include "components/site_engagement/content/site_engagement_service.h"
#include "components/version_info/android/channel_getter.h"
#include "components/version_info/channel.h"
#include "components/version_info/version_info.h"
#include "components/webapps/browser/android/add_to_homescreen_coordinator.h"
#include "components/webapps/browser/android/add_to_homescreen_params.h"
#include "components/webapps/browser/android/ambient_badge_manager.h"
#include "components/webapps/browser/android/bottomsheet/pwa_bottom_sheet_controller.h"
#include "components/webapps/browser/android/shortcut_info.h"
#include "components/webapps/browser/android/webapps_icon_utils.h"
#include "components/webapps/browser/android/webapps_utils.h"
#include "components/webapps/browser/banners/app_banner_metrics.h"
#include "components/webapps/browser/banners/app_banner_settings_helper.h"
#include "components/webapps/browser/banners/install_banner_config.h"
#include "components/webapps/browser/banners/native_app_banner_data.h"
#include "components/webapps/browser/banners/web_app_banner_data.h"
#include "components/webapps/browser/features.h"
#include "components/webapps/browser/installable/installable_logging.h"
#include "components/webapps/browser/installable/installable_manager.h"
#include "components/webapps/browser/installable/installable_metrics.h"
#include "components/webapps/browser/webapps_client.h"
#include "components/webapps/common/web_page_metadata.mojom.h"
#include "content/public/browser/manifest_icon_downloader.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_user_data.h"
#include "net/base/url_util.h"
#include "skia/ext/skia_utils_base.h"
#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
#include "third_party/skia/include/core/SkBitmap.h"
// Must come after all headers that specialize FromJniType() / ToJniType().
#include "components/webapps/browser/android/webapps_jni_headers/AppBannerManager_jni.h"
using base::android::ConvertJavaStringToUTF16;
using base::android::ConvertJavaStringToUTF8;
using base::android::ConvertUTF16ToJavaString;
using base::android::ConvertUTF8ToJavaString;
using base::android::JavaParamRef;
namespace webapps {
namespace {
constexpr char kPlatformPlay[] = "play";
// Whether to ignore the Chrome channel in QueryNativeApp() for testing.
bool gIgnoreChromeChannelForTesting = false;
std::string ExtractQueryValueForName(const GURL& url, const std::string& name) {
for (net::QueryIterator it(url); !it.IsAtEnd(); it.Advance()) {
if (it.GetKey() == name) {
return std::string(it.GetValue());
}
}
return std::string();
}
} // anonymous namespace
// static
void AppBannerManagerAndroid::CreateForWebContents(
content::WebContents* web_contents,
std::unique_ptr<ChromeDelegate> delegate) {
if (FromWebContents(web_contents)) {
return;
}
web_contents->SetUserData(UserDataKey(),
base::WrapUnique(new AppBannerManagerAndroid(
web_contents, std::move(delegate))));
}
AppBannerManagerAndroid::AppBannerManagerAndroid(
content::WebContents* web_contents,
std::unique_ptr<ChromeDelegate> delegate)
: AppBannerManager(web_contents),
content::WebContentsUserData<AppBannerManagerAndroid>(*web_contents),
delegate_(std::move(delegate)) {
CreateJavaBannerManager(web_contents);
}
AppBannerManagerAndroid::~AppBannerManagerAndroid() {
JNIEnv* env = base::android::AttachCurrentThread();
Java_AppBannerManager_destroy(env, java_banner_manager_);
java_banner_manager_.Reset();
}
AppBannerManagerAndroid::QueryNativeAppConfig::QueryNativeAppConfig(
const base::android::ScopedJavaLocalRef<jstring>& url,
const base::android::ScopedJavaLocalRef<jstring>& package,
const base::android::ScopedJavaLocalRef<jstring>& referrer)
: url(url), package(package), referrer(referrer) {}
AppBannerManagerAndroid::QueryNativeAppConfig::QueryNativeAppConfig(
const QueryNativeAppConfig& config) = default;
AppBannerManagerAndroid::QueryNativeAppConfig::~QueryNativeAppConfig() =
default;
const base::android::ScopedJavaLocalRef<jobject>
AppBannerManagerAndroid::GetJavaBannerManager() const {
return base::android::ScopedJavaLocalRef<jobject>(java_banner_manager_);
}
bool AppBannerManagerAndroid::IsRunningForTesting(
JNIEnv* env,
const JavaParamRef<jobject>& obj) {
return IsRunning();
}
int AppBannerManagerAndroid::GetPipelineStatusForTesting(JNIEnv* env) {
return (int)state();
}
int AppBannerManagerAndroid::GetBadgeStatusForTesting(JNIEnv* env) {
if (!ambient_badge_manager_) {
return 0;
}
return (int)ambient_badge_manager_->state();
}
void AppBannerManagerAndroid::OnAppDetailsRetrieved(
JNIEnv* env,
const JavaParamRef<jobject>& obj,
int request_id,
const JavaParamRef<jobject>& japp_data,
const JavaParamRef<jstring>& japp_title,
const JavaParamRef<jstring>& japp_package,
const JavaParamRef<jstring>& jicon_url) {
// If the state isn't fetching native data, that means the page must have
// navigated or reset in some way.
if (state() != State::FETCHING_NATIVE_DATA) {
return;
}
if (request_id != current_native_request_id_) {
return;
}
if (!native_check_callback_storage_) {
return;
}
current_native_request_id_ = std::nullopt;
native_java_app_data_.Reset(japp_data);
std::string app_package = ConvertJavaStringToUTF8(env, japp_package);
std::u16string app_title = ConvertJavaStringToUTF16(env, japp_title);
GURL primary_icon_url = GURL(ConvertJavaStringToUTF8(env, jicon_url));
if (app_package.empty()) {
std::move(native_check_callback_storage_)
.Run(base::unexpected(
InstallableStatusCode::PACKAGE_NAME_OR_START_URL_EMPTY));
return;
}
bool icon_download_initiated = content::ManifestIconDownloader::Download(
&GetWebContents(), primary_icon_url,
WebappsIconUtils::GetIdealHomescreenIconSizeInPx(),
WebappsIconUtils::GetMinimumHomescreenIconSizeInPx(),
/* maximum_icon_size_in_px= */ std::numeric_limits<int>::max(),
base::BindOnce(&AppBannerManagerAndroid::OnNativeAppIconFetched,
GetAndroidWeakPtr(), std::move(app_package),
std::move(app_title), primary_icon_url));
if (!icon_download_initiated) {
std::move(native_check_callback_storage_)
.Run(base::unexpected(InstallableStatusCode::CANNOT_DOWNLOAD_ICON));
}
}
void AppBannerManagerAndroid::ShowBannerFromBadge(
const InstallBannerConfig& config) {
ShowBannerUi(InstallableMetrics::GetInstallSource(
web_contents(), InstallTrigger::AMBIENT_BADGE),
config);
// Close our bindings to ensure that any existing beforeinstallprompt events
// cannot trigger add to home screen (which would cause a crash). If the
// banner is dismissed, the event will be resent.
ResetBindings();
}
// static
std::unique_ptr<AddToHomescreenParams>
AppBannerManagerAndroid::CreateAddToHomescreenParams(
const InstallBannerConfig& config,
const base::android::ScopedJavaGlobalRef<jobject>& native_java_app_data,
WebappInstallSource install_source) {
if (native_java_app_data.is_null()) {
CHECK(config.mode == AppBannerMode::kWebApp);
const WebAppBannerData& web_app_data = config.web_app_data;
return std::make_unique<AddToHomescreenParams>(
AddToHomescreenParams::AppType::WEBAPK,
ShortcutInfo::CreateShortcutInfo(
config.validated_url, web_app_data.manifest_url,
web_app_data.manifest(), web_app_data.web_page_metadata(),
web_app_data.primary_icon_url,
web_app_data.has_maskable_primary_icon),
web_app_data.primary_icon, InstallableStatusCode::NO_ERROR_DETECTED,
install_source);
} else {
CHECK(config.mode == AppBannerMode::kNativeApp);
CHECK(config.native_app_data);
const NativeAppBannerData& native_app_data = *config.native_app_data;
return std::make_unique<AddToHomescreenParams>(
native_app_data.app_package, native_java_app_data,
native_app_data.primary_icon, install_source);
}
}
bool AppBannerManagerAndroid::CanRequestAppBanner() const {
JNIEnv* env = base::android::AttachCurrentThread();
// Note: This check is actually for "A2HS" aka add shortcuts. It doesn't
// really belongs here.
if (!Java_AppBannerManager_isSupported(env) ||
!WebappsClient::Get()->CanShowAppBanners(&GetWebContents())) {
return false;
}
return true;
}
InstallableParams
AppBannerManagerAndroid::ParamsToPerformInstallableWebAppCheck() {
InstallableParams params;
params.valid_primary_icon = true;
params.installable_criteria =
InstallableCriteria::kImplicitManifestFieldsHTML;
params.fetch_screenshots = true;
params.prefer_maskable_icon =
WebappsIconUtils::DoesAndroidSupportMaskableIcons();
params.fetch_favicon = true;
return params;
}
bool AppBannerManagerAndroid::ShouldDoNativeAppCheck(
const blink::mojom::Manifest& manifest) const {
if (!manifest.prefer_related_applications || java_banner_manager_.is_null()) {
return false;
}
// Ensure there is at least one related app specified that is supported on
// the current platform.
for (const auto& application : manifest.related_applications) {
if (base::EqualsASCII(application.platform.value_or(std::u16string()),
kPlatformPlay)) {
return true;
}
}
return false;
}
void AppBannerManagerAndroid::DoNativeAppInstallableCheck(
content::WebContents* web_contents,
const GURL& validated_url,
const blink::mojom::Manifest& manifest,
NativeCheckCallback callback) {
CHECK(manifest.prefer_related_applications &&
!java_banner_manager_.is_null());
InstallableStatusCode code = InstallableStatusCode::NO_ERROR_DETECTED;
for (const auto& application : manifest.related_applications) {
JNIEnv* env = base::android::AttachCurrentThread();
base::expected<QueryNativeAppConfig, InstallableStatusCode> result =
GetNativeAppFetchRequestConfig(validated_url, env, application);
if (!result.has_value()) {
code = result.error();
continue;
}
TrackDisplayEvent(DISPLAY_EVENT_NATIVE_APP_BANNER_REQUESTED);
// Send the info to the Java side to get info about the app.
// This async call will run OnAppDetailsRetrieved() when completed.
current_native_request_id_ = next_native_request_id_;
++next_native_request_id_;
native_check_callback_storage_ = std::move(callback);
Java_AppBannerManager_fetchAppDetails(
env, java_banner_manager_, current_native_request_id_.value(),
result.value().url, result.value().package, result.value().referrer,
WebappsIconUtils::GetIdealHomescreenIconSizeInPx());
return;
}
CHECK(callback);
std::move(callback).Run(base::unexpected(code));
}
void AppBannerManagerAndroid::OnWebAppInstallableCheckedNoErrors(
const ManifestId& manifest_id) const {
delegate_->OnInstallableCheckedNoErrors(manifest_id);
}
base::expected<void, InstallableStatusCode>
AppBannerManagerAndroid::CanRunWebAppInstallableChecks(
const blink::mojom::Manifest& manifest) {
if (!webapps::WebappsUtils::AreWebManifestUrlsWebApkCompatible(manifest)) {
return base::unexpected(
InstallableStatusCode::URL_NOT_SUPPORTED_FOR_WEBAPK);
}
return base::ok();
}
bool AppBannerManagerAndroid::IsSupportedNonWebAppPlatform(
const std::u16string& platform) const {
return base::EqualsASCII(platform, kPlatformPlay);
}
bool AppBannerManagerAndroid::IsRelatedNonWebAppInstalled(
const blink::Manifest::RelatedApplication& related_app) const {
if (!related_app.id || related_app.id->empty()) {
return false;
}
JNIEnv* env = base::android::AttachCurrentThread();
base::android::ScopedJavaLocalRef<jstring> java_id(
ConvertUTF16ToJavaString(env, related_app.id.value()));
return Java_AppBannerManager_isRelatedNonWebAppInstalled(env, java_id);
}
void AppBannerManagerAndroid::MaybeShowAmbientBadge(
const InstallBannerConfig& install_config) {
// Since this can be triggered in some weird async ways, check against the
// current config, and if their manifest_id's don't match then do not proceed.
std::optional<InstallBannerConfig> current_config = GetCurrentBannerConfig();
if (!current_config || install_config.web_app_data.manifest_id !=
current_config->web_app_data.manifest_id) {
// TODO(https://crbug.com/324322110): remove once crash understood.
DUMP_WILL_BE_CHECK(false) << "Pipeline state:" << (int)state();
return;
}
ambient_badge_manager_ = std::make_unique<AmbientBadgeManager>(
GetWebContents(), delegate_->GetSegmentationPlatformService(),
*delegate_->GetPrefService());
std::unique_ptr<AddToHomescreenParams> a2hs_params =
AppBannerManagerAndroid::CreateAddToHomescreenParams(
install_config, native_java_app_data_,
InstallableMetrics::GetInstallSource(&GetWebContents(),
InstallTrigger::AMBIENT_BADGE));
ambient_badge_manager_->MaybeShow(
install_config.validated_url, install_config.GetWebOrNativeAppName(),
install_config.GetWebOrNativeAppIdentifier(), std::move(a2hs_params),
// TODO(b/323192242): See if these callbacks can be merged.
base::BindOnce(&AppBannerManagerAndroid::ShowBannerFromBadge,
GetAndroidWeakPtr(), install_config),
// Create the params, then pass them to MaybeShow.
base::BindOnce(&AppBannerManagerAndroid::CreateAddToHomescreenParams,
install_config, native_java_app_data_)
.Then(base::BindOnce(
&PwaBottomSheetController::MaybeShow, web_contents(),
install_config.web_app_data, /*expand_sheet=*/false,
base::BindRepeating(&AppBannerManagerAndroid::OnInstallEvent,
GetAndroidWeakPtr(),
install_config.validated_url))));
}
void AppBannerManagerAndroid::ShowBannerUi(WebappInstallSource install_source,
const InstallBannerConfig& config) {
content::WebContents* contents = web_contents();
DCHECK(contents);
bool was_shown = native_java_app_data_.is_null() &&
MaybeShowPwaBottomSheetController(/* expand_sheet= */ true,
install_source, config);
if (!was_shown) {
auto a2hs_params = AppBannerManagerAndroid::CreateAddToHomescreenParams(
config, native_java_app_data_, install_source);
was_shown = AddToHomescreenCoordinator::ShowForAppBanner(
weak_factory_.GetWeakPtr(), std::move(a2hs_params),
base::BindRepeating(&AppBannerManagerAndroid::OnInstallEvent,
weak_factory_.GetWeakPtr(), config.validated_url));
}
// If we are installing from the ambient badge, it will remove itself.
if (install_source != WebappInstallSource::AMBIENT_BADGE_CUSTOM_TAB &&
install_source != WebappInstallSource::AMBIENT_BADGE_BROWSER_TAB &&
install_source != WebappInstallSource::RICH_INSTALL_UI_WEBLAYER) {
if (ambient_badge_manager_) {
ambient_badge_manager_->HideAmbientBadge();
}
}
if (was_shown) {
if (native_java_app_data_.is_null()) {
ReportStatus(InstallableStatusCode::SHOWING_WEB_APP_BANNER);
} else {
ReportStatus(InstallableStatusCode::SHOWING_NATIVE_APP_BANNER);
}
} else {
ReportStatus(InstallableStatusCode::FAILED_TO_CREATE_BANNER);
}
}
void AppBannerManagerAndroid::ResetCurrentPageData() {
current_native_request_id_ = std::nullopt;
ambient_badge_manager_.reset();
native_check_callback_storage_.Reset();
native_java_app_data_.Reset();
}
void AppBannerManagerAndroid::OnInstallEvent(
GURL validated_url,
AddToHomescreenInstaller::Event event,
const AddToHomescreenParams& a2hs_params) {
delegate_->RecordExtraMetricsForInstallEvent(event, a2hs_params);
// If the app is not native and the install source is a menu install source,
// user interacted with the bottom sheet installer UI.
if (a2hs_params.app_type != AddToHomescreenParams::AppType::NATIVE &&
(a2hs_params.install_source == WebappInstallSource::MENU_BROWSER_TAB ||
a2hs_params.install_source == WebappInstallSource::MENU_CUSTOM_TAB)) {
switch (event) {
case AddToHomescreenInstaller::Event::INSTALL_REQUEST_FINISHED:
SendBannerAccepted();
OnInstall(a2hs_params.shortcut_info->display,
/*set_current_web_app_not_installable=*/false);
break;
case AddToHomescreenInstaller::Event::UI_CANCELLED:
// Collapsing the bottom sheet installer UI does not count as
// UI_CANCELLED as it is still visible to the user and they can expand
// it later.
SendBannerDismissed();
AppBannerSettingsHelper::RecordBannerDismissEvent(
web_contents(), a2hs_params.shortcut_info->url.spec());
break;
default:
break;
}
return;
}
DCHECK(a2hs_params.app_type == AddToHomescreenParams::AppType::NATIVE ||
a2hs_params.install_source ==
WebappInstallSource::AMBIENT_BADGE_BROWSER_TAB ||
a2hs_params.install_source ==
WebappInstallSource::AMBIENT_BADGE_CUSTOM_TAB ||
a2hs_params.install_source ==
WebappInstallSource::RICH_INSTALL_UI_WEBLAYER ||
a2hs_params.install_source == WebappInstallSource::API_BROWSER_TAB ||
a2hs_params.install_source == WebappInstallSource::API_CUSTOM_TAB ||
a2hs_params.install_source == WebappInstallSource::DEVTOOLS);
std::string identifier;
if (a2hs_params.app_type == AddToHomescreenParams::AppType::NATIVE) {
DCHECK(!a2hs_params.native_app_package_name.empty());
identifier = a2hs_params.native_app_package_name;
} else {
DCHECK(a2hs_params.shortcut_info->manifest_id.is_valid());
identifier = a2hs_params.shortcut_info->manifest_id.spec();
}
switch (event) {
case AddToHomescreenInstaller::Event::INSTALL_STARTED:
TrackDismissEvent(DISMISS_EVENT_DISMISSED);
switch (a2hs_params.app_type) {
case AddToHomescreenParams::AppType::NATIVE:
TrackUserResponse(USER_RESPONSE_NATIVE_APP_ACCEPTED);
break;
case AddToHomescreenParams::AppType::WEBAPK:
[[fallthrough]];
case AddToHomescreenParams::AppType::SHORTCUT:
TrackUserResponse(USER_RESPONSE_WEB_APP_ACCEPTED);
AppBannerSettingsHelper::RecordBannerInstallEvent(
web_contents(), a2hs_params.shortcut_info->url.spec());
break;
default:
NOTREACHED_IN_MIGRATION();
}
break;
case AddToHomescreenInstaller::Event::INSTALL_FAILED:
TrackDismissEvent(DISMISS_EVENT_ERROR);
break;
case AddToHomescreenInstaller::Event::NATIVE_INSTALL_OR_OPEN_FAILED:
DCHECK_EQ(a2hs_params.app_type, AddToHomescreenParams::AppType::NATIVE);
TrackInstallEvent(INSTALL_EVENT_NATIVE_APP_INSTALL_TRIGGERED);
break;
case AddToHomescreenInstaller::Event::NATIVE_INSTALL_OR_OPEN_SUCCEEDED:
DCHECK_EQ(a2hs_params.app_type, AddToHomescreenParams::AppType::NATIVE);
TrackDismissEvent(DISMISS_EVENT_APP_OPEN);
break;
case AddToHomescreenInstaller::Event::INSTALL_REQUEST_FINISHED:
SendBannerAccepted();
if (a2hs_params.app_type == AddToHomescreenParams::AppType::WEBAPK ||
a2hs_params.app_type == AddToHomescreenParams::AppType::SHORTCUT) {
OnInstall(a2hs_params.shortcut_info->display,
/*set_current_web_app_not_installable=*/false);
}
break;
case AddToHomescreenInstaller::Event::NATIVE_DETAILS_SHOWN:
TrackDismissEvent(DISMISS_EVENT_BANNER_CLICK);
break;
case AddToHomescreenInstaller::Event::UI_SHOWN:
AppBannerSettingsHelper::RecordBannerEvent(
web_contents(), validated_url, identifier,
AppBannerSettingsHelper::APP_BANNER_EVENT_DID_SHOW, GetCurrentTime());
TrackDisplayEvent(a2hs_params.app_type ==
AddToHomescreenParams::AppType::NATIVE
? DISPLAY_EVENT_NATIVE_APP_BANNER_CREATED
: DISPLAY_EVENT_WEB_APP_BANNER_CREATED);
break;
case AddToHomescreenInstaller::Event::UI_CANCELLED:
TrackDismissEvent(DISMISS_EVENT_DISMISSED);
SendBannerDismissed();
if (a2hs_params.app_type == AddToHomescreenParams::AppType::NATIVE) {
DCHECK(!a2hs_params.native_app_package_name.empty());
TrackUserResponse(USER_RESPONSE_NATIVE_APP_DISMISSED);
AppBannerSettingsHelper::RecordBannerDismissEvent(
web_contents(), a2hs_params.native_app_package_name);
} else {
TrackUserResponse(USER_RESPONSE_WEB_APP_DISMISSED);
AppBannerSettingsHelper::RecordBannerDismissEvent(
web_contents(), a2hs_params.shortcut_info->url.spec());
}
break;
}
}
void AppBannerManagerAndroid::CreateJavaBannerManager(
content::WebContents* web_contents) {
JNIEnv* env = base::android::AttachCurrentThread();
java_banner_manager_.Reset(
Java_AppBannerManager_create(env, reinterpret_cast<intptr_t>(this)));
}
base::expected<AppBannerManagerAndroid::QueryNativeAppConfig,
InstallableStatusCode>
AppBannerManagerAndroid::GetNativeAppFetchRequestConfig(
const GURL& validated_url,
JNIEnv* env,
const blink::Manifest::RelatedApplication& related_application) const {
if (!related_application.platform.has_value() ||
!base::EqualsASCII(related_application.platform.value(), kPlatformPlay)) {
return base::unexpected(
InstallableStatusCode::PLATFORM_NOT_SUPPORTED_ON_ANDROID);
}
std::string id =
base::UTF16ToUTF8(related_application.id.value_or(std::u16string()));
if (id.empty()) {
return base::unexpected(InstallableStatusCode::NO_ID_SPECIFIED);
}
// AppBannerManager#fetchAppDetails() only works on Beta and Stable because
// the called Google Play API uses an old way of checking whether the Chrome
// app is first party. See http://b/147780265
// Run AppBannerManager#fetchAppDetails() for local builds regardless of
// Android channel to avoid having to set android_channel GN flag for manual
// testing. Do not run AppBannerManager#fetchAppDetails() for non-official
// local builds to ensure that tests use gIgnoreChromeChannelForTesting rather
// than relying on the local build exemption.
version_info::Channel channel = version_info::android::GetChannel();
bool local_build = channel == version_info::Channel::UNKNOWN &&
version_info::IsOfficialBuild();
if (!(local_build || gIgnoreChromeChannelForTesting ||
channel == version_info::Channel::BETA ||
channel == version_info::Channel::STABLE)) {
return base::unexpected(
InstallableStatusCode::
PREFER_RELATED_APPLICATIONS_SUPPORTED_ONLY_BETA_STABLE);
}
std::string id_from_app_url =
ExtractQueryValueForName(related_application.url, "id");
if (id_from_app_url.size() && id != id_from_app_url) {
return base::unexpected(InstallableStatusCode::IDS_DO_NOT_MATCH);
}
// Attach the chrome_inline referrer value, prefixed with "&" if the
// referrer is non empty.
std::string referrer =
ExtractQueryValueForName(related_application.url, "referrer");
if (!referrer.empty()) {
referrer += "&";
}
referrer += "playinline=chrome_inline";
base::android::ScopedJavaLocalRef<jstring> jurl(
ConvertUTF8ToJavaString(env, validated_url.spec()));
base::android::ScopedJavaLocalRef<jstring> jpackage(
ConvertUTF8ToJavaString(env, id));
base::android::ScopedJavaLocalRef<jstring> jreferrer(
ConvertUTF8ToJavaString(env, referrer));
return QueryNativeAppConfig(jurl, jpackage, jreferrer);
}
void AppBannerManagerAndroid::OnNativeAppIconFetched(std::string app_package,
std::u16string app_title,
GURL primary_icon_url,
const SkBitmap& bitmap) {
if (!native_check_callback_storage_) {
return;
}
if (bitmap.drawsNothing()) {
std::move(native_check_callback_storage_)
.Run(base::unexpected(InstallableStatusCode::NO_ICON_AVAILABLE));
return;
}
SkBitmap primary_icon;
if (!skia::SkBitmapToN32OpaqueOrPremul(bitmap, &primary_icon)) {
std::move(native_check_callback_storage_)
.Run(base::unexpected(InstallableStatusCode::NO_ICON_AVAILABLE));
return;
}
std::move(native_check_callback_storage_)
.Run(base::ok(NativeAppBannerData(
std::move(app_package), std::move(app_title),
std::move(primary_icon_url), std::move(primary_icon))));
}
bool AppBannerManagerAndroid::MaybeShowPwaBottomSheetController(
bool expand_sheet,
WebappInstallSource install_source,
const InstallBannerConfig& config) {
if (config.mode == AppBannerMode::kNativeApp) {
return false;
}
const WebAppBannerData& web_app_data = config.web_app_data;
// Do not show the peeked bottom sheet if it was recently dismissed.
if (!expand_sheet && AppBannerSettingsHelper::WasBannerRecentlyBlocked(
&GetWebContents(), config.validated_url,
web_app_data.manifest_id.spec(),
AppBannerManager::GetCurrentTime())) {
return false;
}
auto a2hs_params = AppBannerManagerAndroid::CreateAddToHomescreenParams(
config, native_java_app_data_, install_source);
return PwaBottomSheetController::MaybeShow(
web_contents(), web_app_data, expand_sheet,
base::BindRepeating(&AppBannerManagerAndroid::OnInstallEvent,
AppBannerManagerAndroid::GetAndroidWeakPtr(),
config.validated_url),
std::move(a2hs_params));
}
void AppBannerManagerAndroid::OnMlInstallPrediction(
base::PassKey<MLInstallabilityPromoter>,
std::string result_label) {
// TODO(crbug.com/40269982): Implement.
}
void AppBannerManagerAndroid::Install(
const AddToHomescreenParams& a2hs_params,
base::RepeatingCallback<void(AddToHomescreenInstaller::Event,
const AddToHomescreenParams&)>
a2hs_event_callback) {
AddToHomescreenInstaller::Install(web_contents(), a2hs_params,
std::move(a2hs_event_callback));
}
base::WeakPtr<AppBannerManager>
AppBannerManagerAndroid::GetWeakPtrForThisNavigation() {
return weak_factory_.GetWeakPtr();
}
base::WeakPtr<AppBannerManagerAndroid>
AppBannerManagerAndroid::GetAndroidWeakPtr() {
return weak_factory_.GetWeakPtr();
}
void AppBannerManagerAndroid::InvalidateWeakPtrsForThisNavigation() {
weak_factory_.InvalidateWeakPtrs();
}
WEB_CONTENTS_USER_DATA_KEY_IMPL(AppBannerManagerAndroid);
// static
base::android::ScopedJavaLocalRef<jobject>
JNI_AppBannerManager_GetJavaBannerManagerForWebContents(
JNIEnv* env,
const JavaParamRef<jobject>& java_web_contents) {
auto* manager =
static_cast<AppBannerManagerAndroid*>(AppBannerManager::FromWebContents(
content::WebContents::FromJavaWebContents(java_web_contents)));
return manager ? manager->GetJavaBannerManager()
: base::android::ScopedJavaLocalRef<jobject>();
}
// static
base::android::ScopedJavaLocalRef<jstring>
JNI_AppBannerManager_GetInstallableWebAppManifestId(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& java_web_contents) {
return base::android::ConvertUTF8ToJavaString(
env, AppBannerManager::GetInstallableWebAppManifestId(
content::WebContents::FromJavaWebContents(java_web_contents)));
}
// static
void JNI_AppBannerManager_IgnoreChromeChannelForTesting(JNIEnv*) {
gIgnoreChromeChannelForTesting = true;
}
// static
void JNI_AppBannerManager_SetDaysAfterDismissAndIgnoreToTrigger(
JNIEnv* env,
jint dismiss_days,
jint ignore_days) {
AppBannerSettingsHelper::SetDaysAfterDismissAndIgnoreToTrigger(dismiss_days,
ignore_days);
}
// static
void JNI_AppBannerManager_SetTimeDeltaForTesting(JNIEnv* env, jint days) {
AppBannerManager::SetTimeDeltaForTesting(days);
}
// static
void JNI_AppBannerManager_SetTotalEngagementToTrigger(JNIEnv* env,
jdouble engagement) {
AppBannerSettingsHelper::SetTotalEngagementToTrigger(engagement);
}
// static
void JNI_AppBannerManager_SetOverrideSegmentationResultForTesting( // IN-TEST
JNIEnv* env,
jboolean show) {
AmbientBadgeManager::SetOverrideSegmentationResultForTesting(show);
}
} // namespace webapps