| // Copyright 2023 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/ambient_badge_manager.h" |
| |
| #include <limits> |
| #include <string> |
| |
| #include "base/feature_list.h" |
| #include "components/infobars/content/content_infobar_manager.h" |
| #include "components/infobars/core/infobar.h" |
| #include "components/infobars/core/infobar_delegate.h" |
| #include "components/messages/android/messages_feature.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/segmentation_platform/public/constants.h" |
| #include "components/segmentation_platform/public/input_context.h" |
| #include "components/segmentation_platform/public/result.h" |
| #include "components/segmentation_platform/public/segmentation_platform_service.h" |
| #include "components/webapps/browser/android/add_to_homescreen_params.h" |
| #include "components/webapps/browser/android/ambient_badge_metrics.h" |
| #include "components/webapps/browser/android/app_banner_manager_android.h" |
| #include "components/webapps/browser/android/install_prompt_prefs.h" |
| #include "components/webapps/browser/android/installable/installable_ambient_badge_infobar_delegate.h" |
| #include "components/webapps/browser/android/shortcut_info.h" |
| #include "components/webapps/browser/banners/app_banner_settings_helper.h" |
| #include "components/webapps/browser/features.h" |
| #include "components/webapps/browser/webapps_client.h" |
| #include "content/public/browser/web_contents.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| |
| namespace webapps { |
| |
| namespace { |
| |
| InstallableParams ParamsToPerformWorkerCheck() { |
| InstallableParams params; |
| params.has_worker = true; |
| return params; |
| } |
| |
| } // namespace |
| |
| AmbientBadgeManager::AmbientBadgeManager( |
| content::WebContents* web_contents, |
| base::WeakPtr<AppBannerManagerAndroid> app_banner_manager, |
| segmentation_platform::SegmentationPlatformService* |
| segmentation_platform_service, |
| PrefService* prefs) |
| : web_contents_(web_contents->GetWeakPtr()), |
| app_banner_manager_(app_banner_manager), |
| segmentation_platform_service_(segmentation_platform_service), |
| pref_service_(prefs) {} |
| |
| AmbientBadgeManager::~AmbientBadgeManager() { |
| RecordAmbientBadgeTeminateState(state_); |
| } |
| |
| void AmbientBadgeManager::MaybeShow( |
| const GURL& validated_url, |
| const std::u16string& app_name, |
| std::unique_ptr<AddToHomescreenParams> a2hs_params, |
| base::OnceClosure show_banner_callback) { |
| validated_url_ = validated_url; |
| app_name_ = app_name; |
| a2hs_params_ = std::move(a2hs_params); |
| show_banner_callback_ = std::move(show_banner_callback); |
| |
| if (!base::FeatureList::IsEnabled( |
| features::kInstallableAmbientBadgeInfoBar) && |
| !base::FeatureList::IsEnabled( |
| features::kInstallableAmbientBadgeMessage)) { |
| return; |
| } |
| |
| UpdateState(State::kActive); |
| |
| if (base::FeatureList::IsEnabled(features::kInstallPromptSegmentation)) { |
| InstallableParams params = ParamsToPerformWorkerCheck(); |
| params.wait_for_worker = false; |
| PerformWorkerCheckForAmbientBadge( |
| params, base::BindOnce(&AmbientBadgeManager::MaybeShowAmbientBadgeSmart, |
| weak_factory_.GetWeakPtr())); |
| } else { |
| MaybeShowAmbientBadgeLegacy(); |
| } |
| } |
| |
| void AmbientBadgeManager::AddToHomescreenFromBadge() { |
| RecordAmbientBadgeClickEvent(a2hs_params_->app_type); |
| InstallPromptPrefs::RecordInstallPromptClicked(pref_service_); |
| std::move(show_banner_callback_).Run(); |
| } |
| |
| void AmbientBadgeManager::BadgeDismissed() { |
| AppBannerSettingsHelper::RecordBannerEvent( |
| web_contents_.get(), validated_url_, a2hs_params_->GetAppIdentifier(), |
| AppBannerSettingsHelper::APP_BANNER_EVENT_DID_BLOCK, |
| AppBannerManager::GetCurrentTime()); |
| |
| InstallPromptPrefs::RecordInstallPromptDismissed( |
| pref_service_, AppBannerManager::GetCurrentTime()); |
| RecordAmbientBadgeDismissEvent(a2hs_params_->app_type); |
| UpdateState(State::kDismissed); |
| } |
| |
| void AmbientBadgeManager::BadgeIgnored() { |
| AppBannerSettingsHelper::RecordBannerEvent( |
| web_contents_.get(), validated_url_, a2hs_params_->GetAppIdentifier(), |
| AppBannerSettingsHelper::APP_BANNER_EVENT_DID_SHOW, |
| AppBannerManager::GetCurrentTime()); |
| |
| InstallPromptPrefs::RecordInstallPromptIgnored( |
| pref_service_, AppBannerManager::GetCurrentTime()); |
| RecordAmbientBadgeDismissEvent(a2hs_params_->app_type); |
| UpdateState(State::kDismissed); |
| } |
| |
| void AmbientBadgeManager::HideAmbientBadge() { |
| message_controller_.DismissMessage(); |
| infobars::ContentInfoBarManager* infobar_manager = |
| webapps::WebappsClient::Get()->GetInfoBarManagerForWebContents( |
| web_contents_.get()); |
| if (infobar_manager == nullptr) { |
| return; |
| } |
| |
| infobars::InfoBar* ambient_badge_infobar = |
| InstallableAmbientBadgeInfoBarDelegate::GetVisibleAmbientBadgeInfoBar( |
| infobar_manager); |
| |
| if (ambient_badge_infobar) { |
| infobar_manager->RemoveInfoBar(ambient_badge_infobar); |
| } |
| } |
| |
| void AmbientBadgeManager::UpdateState(State state) { |
| state_ = state; |
| } |
| |
| void AmbientBadgeManager::MaybeShowAmbientBadgeLegacy() { |
| // Do not show the ambient badge if it was recently dismissed. |
| if (AppBannerSettingsHelper::WasBannerRecentlyBlocked( |
| web_contents_.get(), validated_url_, a2hs_params_->GetAppIdentifier(), |
| AppBannerManager::GetCurrentTime())) { |
| UpdateState(State::kBlocked); |
| return; |
| } |
| |
| if (ShouldSuppressAmbientBadgeOnFirstVisit()) { |
| UpdateState(State::kPendingEngagement); |
| return; |
| } |
| |
| // if it's showing for web app (not native app), only show if the worker check |
| // already passed. |
| if (a2hs_params_->app_type == AddToHomescreenParams::AppType::WEBAPK && |
| features::SkipServiceWorkerForInstallPromotion() && |
| !passed_worker_check_) { |
| InstallableParams params = ParamsToPerformWorkerCheck(); |
| params.wait_for_worker = true; |
| PerformWorkerCheckForAmbientBadge( |
| params, base::BindOnce(&AmbientBadgeManager::OnWorkerCheckResult, |
| weak_factory_.GetWeakPtr())); |
| return; |
| } |
| |
| ShowAmbientBadge(); |
| } |
| |
| bool AmbientBadgeManager::ShouldSuppressAmbientBadgeOnFirstVisit() { |
| if (!base::FeatureList::IsEnabled( |
| features::kAmbientBadgeSuppressFirstVisit)) { |
| return false; |
| } |
| |
| absl::optional<base::Time> last_could_show_time = |
| AppBannerSettingsHelper::GetSingleBannerEvent( |
| web_contents_.get(), validated_url_, a2hs_params_->GetAppIdentifier(), |
| AppBannerSettingsHelper::APP_BANNER_EVENT_COULD_SHOW_AMBIENT_BADGE); |
| |
| AppBannerSettingsHelper::RecordBannerEvent( |
| web_contents_.get(), validated_url_, a2hs_params_->GetAppIdentifier(), |
| AppBannerSettingsHelper::APP_BANNER_EVENT_COULD_SHOW_AMBIENT_BADGE, |
| AppBannerManager::GetCurrentTime()); |
| |
| if (!last_could_show_time || last_could_show_time->is_null()) { |
| return true; |
| } |
| |
| base::TimeDelta period = |
| features::kAmbientBadgeSuppressFirstVisit_Period.Get(); |
| return AppBannerManager::GetCurrentTime() - *last_could_show_time > period; |
| } |
| |
| void AmbientBadgeManager::PerformWorkerCheckForAmbientBadge( |
| InstallableParams params, |
| InstallableCallback callback) { |
| UpdateState(State::kPendingWorker); |
| // TODO(crbug/1425546): Move the worker check logic from AppBannerManager. |
| app_banner_manager_->PerformWorkerCheckForAmbientBadge(params, |
| std::move(callback)); |
| } |
| |
| void AmbientBadgeManager::OnWorkerCheckResult(const InstallableData& data) { |
| if (!data.NoBlockingErrors()) { |
| return; |
| } |
| passed_worker_check_ = true; |
| |
| if (state_ == State::kPendingWorker) { |
| ShowAmbientBadge(); |
| } |
| } |
| |
| void AmbientBadgeManager::MaybeShowAmbientBadgeSmart( |
| const InstallableData& data) { |
| if (!segmentation_platform_service_) { |
| return; |
| } |
| |
| UpdateState(State::kSegmentation); |
| |
| segmentation_platform::PredictionOptions prediction_options; |
| prediction_options.on_demand_execution = true; |
| |
| auto input_context = |
| base::MakeRefCounted<segmentation_platform::InputContext>(); |
| input_context->metadata_args.emplace("url", validated_url_); |
| input_context->metadata_args.emplace("maskable_icon", |
| a2hs_params_->has_maskable_primary_icon); |
| segmentation_platform_service_->GetClassificationResult( |
| segmentation_platform::kWebAppInstallationPromoKey, prediction_options, |
| input_context, |
| base::BindOnce(&AmbientBadgeManager::OnGotClassificationResult, |
| base::Unretained(this))); |
| } |
| |
| void AmbientBadgeManager::OnGotClassificationResult( |
| const segmentation_platform::ClassificationResult& result) { |
| if (result.status != segmentation_platform::PredictionStatus::kSucceeded) { |
| return; |
| } |
| |
| // TODO(eirage): replace this with label type. |
| if (result.ordered_labels[0] == "ShowMessage") { |
| if (ShouldMessageBeBlockedByGuardrail()) { |
| UpdateState(State::kBlocked); |
| } else { |
| ShowAmbientBadge(); |
| } |
| } |
| } |
| |
| bool AmbientBadgeManager::ShouldMessageBeBlockedByGuardrail() { |
| if (AppBannerSettingsHelper::WasBannerRecentlyBlocked( |
| web_contents(), validated_url_, a2hs_params_->GetAppIdentifier(), |
| AppBannerManager::GetCurrentTime())) { |
| return true; |
| } |
| |
| if (AppBannerSettingsHelper::WasBannerRecentlyIgnored( |
| web_contents(), validated_url_, a2hs_params_->GetAppIdentifier(), |
| AppBannerManager::GetCurrentTime())) { |
| return true; |
| } |
| |
| if (InstallPromptPrefs::IsPromptDismissedConsecutivelyRecently( |
| pref_service_, AppBannerManager::GetCurrentTime())) { |
| return true; |
| } |
| |
| if (InstallPromptPrefs::IsPromptIgnoredConsecutivelyRecently( |
| pref_service_, AppBannerManager::GetCurrentTime())) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void AmbientBadgeManager::ShowAmbientBadge() { |
| infobars::ContentInfoBarManager* infobar_manager = |
| webapps::WebappsClient::Get()->GetInfoBarManagerForWebContents( |
| web_contents_.get()); |
| bool infobar_visible = |
| infobar_manager && |
| InstallableAmbientBadgeInfoBarDelegate::GetVisibleAmbientBadgeInfoBar( |
| infobar_manager); |
| |
| if (infobar_visible || message_controller_.IsMessageEnqueued()) { |
| return; |
| } |
| |
| RecordAmbientBadgeDisplayEvent(a2hs_params_->app_type); |
| UpdateState(State::kShowing); |
| |
| WebappInstallSource install_source = InstallableMetrics::GetInstallSource( |
| web_contents_.get(), InstallTrigger::AMBIENT_BADGE); |
| // TODO(crbug/1425546): Move the maybe show peeked bottom sheet logic out of |
| // AppBannerManager. |
| if (app_banner_manager_->MaybeShowPwaBottomSheetController( |
| /* expand_sheet= */ false, install_source)) { |
| // Bottom sheet shown. |
| return; |
| } |
| |
| GURL url = a2hs_params_->app_type == AddToHomescreenParams::AppType::WEBAPK |
| ? a2hs_params_->shortcut_info->url |
| : validated_url_; |
| if (base::FeatureList::IsEnabled(features::kInstallableAmbientBadgeMessage) && |
| base::FeatureList::IsEnabled( |
| messages::kMessagesForAndroidInfrastructure)) { |
| message_controller_.EnqueueMessage( |
| web_contents_.get(), app_name_, a2hs_params_->primary_icon, |
| a2hs_params_->has_maskable_primary_icon, url); |
| } else { |
| InstallableAmbientBadgeInfoBarDelegate::Create( |
| web_contents_.get(), weak_factory_.GetWeakPtr(), app_name_, |
| a2hs_params_->primary_icon, a2hs_params_->has_maskable_primary_icon, |
| url); |
| } |
| } |
| |
| } // namespace webapps |