| // 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 <optional> |
| #include <string> |
| |
| #include "base/feature_list.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.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/app_banner_manager_android.h" |
| #include "components/webapps/browser/android/install_prompt_prefs.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/installable/ml_installability_promoter.h" |
| #include "components/webapps/browser/webapps_client.h" |
| #include "content/public/browser/web_contents.h" |
| |
| namespace webapps { |
| |
| namespace { |
| |
| constexpr char kSegmentationResultHistogramName[] = |
| "WebApk.InstallPrompt.SegmentationResult"; |
| constexpr char kAmbientBadgeTerminateHistogram[] = |
| "Webapp.AmbientBadge.Terminate"; |
| |
| // This enum is used to back UMA histograms, Entries should not be renumbered |
| // and numeric values should never be reused. |
| enum class SegmentationResult { |
| kInvalid = 0, |
| kDontShow = 1, |
| kShowInstallPrompt = 2, |
| kMaxValue = kShowInstallPrompt, |
| }; |
| |
| bool gOverrideSegmentationResultForTesting = false; |
| bool gShowInstallPromptForTesting = false; |
| |
| } // namespace |
| |
| AmbientBadgeManager::AmbientBadgeManager( |
| content::WebContents& web_contents, |
| segmentation_platform::SegmentationPlatformService* |
| segmentation_platform_service, |
| PrefService& prefs) |
| : web_contents_(web_contents), |
| segmentation_platform_service_(segmentation_platform_service), |
| pref_service_(prefs) {} |
| |
| AmbientBadgeManager::~AmbientBadgeManager() { |
| base::UmaHistogramEnumeration(kAmbientBadgeTerminateHistogram, state_); |
| } |
| |
| void AmbientBadgeManager::MaybeShow( |
| const GURL& validated_url, |
| const std::u16string& app_name, |
| const std::string& app_identifier, |
| std::unique_ptr<AddToHomescreenParams> a2hs_params, |
| base::OnceClosure show_banner_callback, |
| MaybeShowPwaBottomSheetCallback maybe_show_pwa_bottom_sheet) { |
| validated_url_ = validated_url; |
| app_name_ = app_name; |
| app_identifier_ = app_identifier; |
| a2hs_params_ = std::move(a2hs_params); |
| show_banner_callback_ = std::move(show_banner_callback); |
| maybe_show_pwa_bottom_sheet_ = std::move(maybe_show_pwa_bottom_sheet); |
| |
| UpdateState(State::kActive); |
| if (base::FeatureList::IsEnabled( |
| features::kWebAppsEnableMLModelForPromotion)) { |
| MaybeShowAmbientBadgeSmart(); |
| } |
| } |
| |
| void AmbientBadgeManager::AddToHomescreenFromBadge() { |
| CHECK(a2hs_params_); |
| InstallPromptPrefs::RecordInstallPromptClicked(pref_service()); |
| std::move(show_banner_callback_).Run(); |
| } |
| |
| void AmbientBadgeManager::BadgeDismissed() { |
| CHECK(a2hs_params_); |
| AppBannerSettingsHelper::RecordBannerEvent( |
| web_contents(), validated_url_, app_identifier_, |
| AppBannerSettingsHelper::APP_BANNER_EVENT_DID_BLOCK, |
| AppBannerManager::GetCurrentTime()); |
| |
| InstallPromptPrefs::RecordInstallPromptDismissed( |
| pref_service(), AppBannerManager::GetCurrentTime()); |
| UpdateState(State::kDismissed); |
| } |
| |
| void AmbientBadgeManager::BadgeIgnored() { |
| CHECK(validated_url_.is_valid()); |
| AppBannerSettingsHelper::RecordBannerEvent( |
| web_contents(), validated_url_, app_identifier_, |
| AppBannerSettingsHelper::APP_BANNER_EVENT_DID_SHOW, |
| AppBannerManager::GetCurrentTime()); |
| |
| InstallPromptPrefs::RecordInstallPromptIgnored( |
| pref_service(), AppBannerManager::GetCurrentTime()); |
| UpdateState(State::kDismissed); |
| } |
| |
| void AmbientBadgeManager::HideAmbientBadge() { |
| message_controller_.DismissMessage(); |
| } |
| |
| void AmbientBadgeManager::UpdateState(State state) { |
| state_ = state; |
| } |
| |
| void AmbientBadgeManager::MaybeShowAmbientBadgeSmart() { |
| if (ShouldMessageBeBlockedByGuardrail()) { |
| UpdateState(State::kBlocked); |
| return; |
| } |
| |
| if (!segmentation_platform_service_) { |
| return; |
| } |
| |
| CHECK(validated_url_.is_valid()); |
| CHECK(a2hs_params_); |
| |
| UpdateState(State::kPendingSegmentation); |
| |
| if (gOverrideSegmentationResultForTesting) { |
| segmentation_platform::ClassificationResult result( |
| segmentation_platform::PredictionStatus::kSucceeded); |
| result.ordered_labels.emplace_back( |
| gShowInstallPromptForTesting |
| ? MLInstallabilityPromoter::kShowInstallPromptLabel |
| : MLInstallabilityPromoter::kDontShowLabel); |
| OnGotClassificationResult(result); |
| return; |
| } |
| |
| 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( |
| "origin", url::Origin::Create(validated_url_).GetURL()); |
| input_context->metadata_args.emplace( |
| "maskable_icon", |
| segmentation_platform::processing::ProcessedValue::FromFloat( |
| a2hs_params_->HasMaskablePrimaryIcon())); |
| input_context->metadata_args.emplace( |
| "app_type", segmentation_platform::processing::ProcessedValue::FromFloat( |
| (float)a2hs_params_->app_type)); |
| segmentation_platform_service_->GetClassificationResult( |
| segmentation_platform::kWebAppInstallationPromoKey, prediction_options, |
| input_context, |
| base::BindOnce(&AmbientBadgeManager::OnGotClassificationResult, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void AmbientBadgeManager::OnGotClassificationResult( |
| const segmentation_platform::ClassificationResult& result) { |
| if (result.status != segmentation_platform::PredictionStatus::kSucceeded) { |
| UMA_HISTOGRAM_ENUMERATION(kSegmentationResultHistogramName, |
| SegmentationResult::kInvalid, |
| SegmentationResult::kMaxValue); |
| UpdateState(State::kSegmentationBlock); |
| return; |
| } |
| |
| bool show = !result.ordered_labels.empty() && |
| result.ordered_labels[0] == |
| MLInstallabilityPromoter::kShowInstallPromptLabel; |
| |
| UMA_HISTOGRAM_ENUMERATION(kSegmentationResultHistogramName, |
| show ? SegmentationResult::kShowInstallPrompt |
| : SegmentationResult::kDontShow, |
| SegmentationResult::kMaxValue); |
| |
| if (!show) { |
| UpdateState(State::kSegmentationBlock); |
| return; |
| } |
| |
| ShowAmbientBadge(); |
| } |
| |
| bool AmbientBadgeManager::ShouldMessageBeBlockedByGuardrail() { |
| if (AppBannerSettingsHelper::WasBannerRecentlyBlocked( |
| web_contents(), validated_url_, app_identifier_, |
| AppBannerManager::GetCurrentTime())) { |
| return true; |
| } |
| |
| if (AppBannerSettingsHelper::WasBannerRecentlyIgnored( |
| web_contents(), validated_url_, app_identifier_, |
| 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() { |
| if (message_controller_.IsMessageEnqueued()) { |
| return; |
| } |
| |
| UpdateState(State::kShowing); |
| |
| WebappInstallSource install_source = InstallableMetrics::GetInstallSource( |
| web_contents(), InstallTrigger::AMBIENT_BADGE); |
| // TODO(crbug.com/40260952): Move the maybe show peeked bottom sheet logic out |
| // of AppBannerManager. |
| if (!maybe_show_pwa_bottom_sheet_.is_null() && |
| std::move(maybe_show_pwa_bottom_sheet_).Run(install_source)) { |
| // Bottom sheet shown. |
| return; |
| } |
| |
| GURL url = a2hs_params_->app_type == AddToHomescreenParams::AppType::WEBAPK |
| ? a2hs_params_->shortcut_info->url |
| : validated_url_; |
| message_controller_.EnqueueMessage( |
| web_contents(), app_name_, a2hs_params_->primary_icon, |
| a2hs_params_->HasMaskablePrimaryIcon(), url); |
| } |
| |
| // static |
| void AmbientBadgeManager::SetOverrideSegmentationResultForTesting(bool show) { |
| gOverrideSegmentationResultForTesting = true; |
| gShowInstallPromptForTesting = show; |
| } |
| |
| } // namespace webapps |