Commit 4379e292 authored by Phillis Tang's avatar Phillis Tang Committed by Commit Bot

PWA: add custom triggering logic for install icon In-Product Help promo.

Add IPH globally scoped data and add trigger logic for install IPH that
doesn't show IPH unless site-engagement score is above 10 and IPH hasn't
been shown for this site for 90 days, and hasn't been shown globally for
14 days, and hasn't been ignored by user 3 days in a row for this site
and hasn't been ignored by user 4 days in a row globally.

Bug: 1149670
Change-Id: Idff12df63673f6e8e1fd63383f7fc57c07eb748a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2530197
Commit-Queue: Phillis Tang <phillis@chromium.org>
Reviewed-by: default avatarMichael Wasserman <msw@chromium.org>
Reviewed-by: default avatarDaniel Murphy <dmurph@chromium.org>
Cr-Commit-Position: refs/heads/master@{#828483}
parent 63706137
...@@ -9,6 +9,8 @@ ...@@ -9,6 +9,8 @@
#include "base/metrics/field_trial_params.h" #include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_functions.h" #include "base/metrics/histogram_functions.h"
#include "base/metrics/user_metrics.h" #include "base/metrics/user_metrics.h"
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/app/chrome_command_ids.h" #include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/banners/app_banner_manager.h" #include "chrome/browser/banners/app_banner_manager.h"
#include "chrome/browser/installable/installable_metrics.h" #include "chrome/browser/installable/installable_metrics.h"
...@@ -41,6 +43,11 @@ constexpr base::FeatureParam<ExperimentIcon> kInstallIconParam{ ...@@ -41,6 +43,11 @@ constexpr base::FeatureParam<ExperimentIcon> kInstallIconParam{
&kInstallIconExperiment, "installIcon", ExperimentIcon::kDownloadToDevice, &kInstallIconExperiment, "installIcon", ExperimentIcon::kDownloadToDevice,
&kIconParamOptions}; &kIconParamOptions};
// Site engagement score threshold to show In-Product Help.
constexpr base::FeatureParam<int> kIphSiteEngagementThresholdParam{
&feature_engagement::kIPHDesktopPwaInstallFeature,
"siteEngagementThreshold", 10};
} // namespace } // namespace
PwaInstallView::PwaInstallView( PwaInstallView::PwaInstallView(
...@@ -79,7 +86,7 @@ void PwaInstallView::UpdateImpl() { ...@@ -79,7 +86,7 @@ void PwaInstallView::UpdateImpl() {
else else
ResetSlideAnimation(false); ResetSlideAnimation(false);
if (is_probably_promotable) { if (is_probably_promotable && ShouldShowIph(web_contents, manager)) {
FeaturePromoControllerViews* controller = FeaturePromoControllerViews* controller =
FeaturePromoControllerViews::GetForView(this); FeaturePromoControllerViews::GetForView(this);
if (controller) { if (controller) {
...@@ -178,3 +185,19 @@ base::string16 PwaInstallView::GetTextForTooltipAndAccessibleName() const { ...@@ -178,3 +185,19 @@ base::string16 PwaInstallView::GetTextForTooltipAndAccessibleName() const {
const char* PwaInstallView::GetClassName() const { const char* PwaInstallView::GetClassName() const {
return "PwaInstallView"; return "PwaInstallView";
} }
bool PwaInstallView::ShouldShowIph(content::WebContents* web_contents,
banners::AppBannerManager* manager) {
auto start_url = manager->GetManifestStartUrl();
if (start_url.is_empty())
return false;
web_app::AppId app_id = web_app::GenerateAppIdFromURL(start_url);
Profile* profile =
Profile::FromBrowserContext(web_contents->GetBrowserContext());
auto score =
SiteEngagementService::Get(profile)->GetScore(web_contents->GetURL());
return score > kIphSiteEngagementThresholdParam.Get() &&
web_app::ShouldShowIph(profile->GetPrefs(), app_id);
}
...@@ -8,6 +8,10 @@ ...@@ -8,6 +8,10 @@
#include "base/macros.h" #include "base/macros.h"
#include "chrome/browser/ui/views/page_action/page_action_icon_view.h" #include "chrome/browser/ui/views/page_action/page_action_icon_view.h"
namespace banners {
class AppBannerManager;
} // namespace banners
// A plus icon to surface whether a site has passed PWA (progressive web app) // A plus icon to surface whether a site has passed PWA (progressive web app)
// installability checks and can be installed. // installability checks and can be installed.
class PwaInstallView : public PageActionIconView { class PwaInstallView : public PageActionIconView {
...@@ -34,6 +38,10 @@ class PwaInstallView : public PageActionIconView { ...@@ -34,6 +38,10 @@ class PwaInstallView : public PageActionIconView {
// Track whether IPH is closed because of install icon being clicked. // Track whether IPH is closed because of install icon being clicked.
bool install_icon_clicked_after_iph_shown_ = false; bool install_icon_clicked_after_iph_shown_ = false;
// Decide whether IPH promo should be shown based on previous interactions.
bool ShouldShowIph(content::WebContents* web_contents,
banners::AppBannerManager* manager);
base::WeakPtrFactory<PwaInstallView> weak_ptr_factory_{this}; base::WeakPtrFactory<PwaInstallView> weak_ptr_factory_{this};
DISALLOW_COPY_AND_ASSIGN(PwaInstallView); DISALLOW_COPY_AND_ASSIGN(PwaInstallView);
......
...@@ -226,6 +226,15 @@ enum class InstallIphResult { ...@@ -226,6 +226,15 @@ enum class InstallIphResult {
kMaxValue = kIgnored kMaxValue = kIgnored
}; };
// Number of times IPH can be ignored for this app before it's muted.
constexpr int kIphMuteAfterConsecutiveAppSpecificIgnores = 3;
// Number of times IPH can be ignored for any app before it's muted.
constexpr int kIphMuteAfterConsecutiveAppAgnosticIgnores = 4;
// Number of days to mute IPH after it's ignored for this app.
constexpr int kIphAppSpecificMuteTimeSpanDays = 90;
// Number of days to mute IPH after it's ignored for any app.
constexpr int kIphAppAgnosticMuteTimeSpanDays = 14;
} // namespace web_app } // namespace web_app
#endif // CHROME_BROWSER_WEB_APPLICATIONS_COMPONENTS_WEB_APP_CONSTANTS_H_ #endif // CHROME_BROWSER_WEB_APPLICATIONS_COMPONENTS_WEB_APP_CONSTANTS_H_
...@@ -6,15 +6,18 @@ ...@@ -6,15 +6,18 @@
#include <memory> #include <memory>
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_piece_forward.h" #include "base/strings/string_piece_forward.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "base/util/values/values_util.h" #include "base/util/values/values_util.h"
#include "base/values.h" #include "base/values.h"
#include "chrome/browser/web_applications/components/web_app_constants.h"
#include "chrome/common/pref_names.h" #include "chrome/common/pref_names.h"
#include "components/pref_registry/pref_registry_syncable.h" #include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h" #include "components/prefs/pref_service.h"
#include "content/public/browser/browser_thread.h" #include "content/public/browser/browser_thread.h"
#include "services/preferences/public/cpp/dictionary_value_update.h" #include "services/preferences/public/cpp/dictionary_value_update.h"
#include "services/preferences/public/cpp/scoped_pref_update.h"
namespace web_app { namespace web_app {
...@@ -50,6 +53,11 @@ std::unique_ptr<prefs::DictionaryValueUpdate> UpdateWebAppDictionary( ...@@ -50,6 +53,11 @@ std::unique_ptr<prefs::DictionaryValueUpdate> UpdateWebAppDictionary(
return web_app_prefs_update; return web_app_prefs_update;
} }
// Returns whether the time occurred within X days.
bool TimeOccurredWithinDays(base::Optional<base::Time> time, int days) {
return time && (base::Time::Now() - time.value()).InDays() < days;
}
} // namespace } // namespace
// The stored preferences look like: // The stored preferences look like:
...@@ -61,14 +69,24 @@ std::unique_ptr<prefs::DictionaryValueUpdate> UpdateWebAppDictionary( ...@@ -61,14 +69,24 @@ std::unique_ptr<prefs::DictionaryValueUpdate> UpdateWebAppDictionary(
// A double representing the number of seconds since epoch, in local time. // A double representing the number of seconds since epoch, in local time.
// Convert from/to using base::Time::FromDoubleT() and // Convert from/to using base::Time::FromDoubleT() and
// base::Time::ToDoubleT(). // base::Time::ToDoubleT().
// "file_handling_origin_trial_expiry_time": 1580475600000 // "file_handling_origin_trial_expiry_time": 1580475600000,
// "IPH_num_of_consecutive_ignore": 2,
// A string-flavored base::value representing the int64_t number of
// microseconds since the Windows epoch, using util::TimeToValue().
// "IPH_last_ignore_time": "13249617864945580",
// }, // },
// "<app_id_N>": { // "<app_id_N>": {
// "was_external_app_uninstalled_by_user": false, // "was_external_app_uninstalled_by_user": false,
// "file_handlers_enabled": false, // "file_handlers_enabled": false,
// "file_handling_origin_trial_expiry_time": 0 // "file_handling_origin_trial_expiry_time": 0
// } // }
// } // },
// "app_agnostic_iph_state": {
// "IPH_num_of_consecutive_ignore": 3,
// A string-flavored base::Value representing int64_t number of microseconds
// since the Windows epoch, using util::TimeToValue().
// "IPH_last_ignore_time": "13249617864945500",
// },
// } // }
// //
const char kWasExternalAppUninstalledByUser[] = const char kWasExternalAppUninstalledByUser[] =
...@@ -90,6 +108,7 @@ const char kIphLastIgnoreTime[] = "IPH_last_ignore_time"; ...@@ -90,6 +108,7 @@ const char kIphLastIgnoreTime[] = "IPH_last_ignore_time";
void WebAppPrefsUtilsRegisterProfilePrefs( void WebAppPrefsUtilsRegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) { user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterDictionaryPref(::prefs::kWebAppsPreferences); registry->RegisterDictionaryPref(::prefs::kWebAppsPreferences);
registry->RegisterDictionaryPref(::prefs::kWebAppsAppAgnosticIphState);
} }
bool GetBoolWebAppPref(const PrefService* pref_service, bool GetBoolWebAppPref(const PrefService* pref_service,
...@@ -196,11 +215,20 @@ void RemoveWebAppPref(PrefService* pref_service, ...@@ -196,11 +215,20 @@ void RemoveWebAppPref(PrefService* pref_service,
void RecordInstallIphIgnored(PrefService* pref_service, const AppId& app_id) { void RecordInstallIphIgnored(PrefService* pref_service, const AppId& app_id) {
base::Optional<int> ignored_count = base::Optional<int> ignored_count =
GetIntWebAppPref(pref_service, app_id, kIphIgnoreCount); GetIntWebAppPref(pref_service, app_id, kIphIgnoreCount);
int new_count = 1 + ignored_count.value_or(0); int new_count = base::saturated_cast<int>(1 + ignored_count.value_or(0));
UpdateIntWebAppPref(pref_service, app_id, kIphIgnoreCount, new_count); UpdateIntWebAppPref(pref_service, app_id, kIphIgnoreCount, new_count);
UpdateTimeWebAppPref(pref_service, app_id, kIphLastIgnoreTime, UpdateTimeWebAppPref(pref_service, app_id, kIphLastIgnoreTime,
base::Time::Now()); base::Time::Now());
prefs::ScopedDictionaryPrefUpdate update(pref_service,
prefs::kWebAppsAppAgnosticIphState);
int global_count = 0;
update->GetInteger(kIphIgnoreCount, &global_count);
update->SetInteger(kIphIgnoreCount,
base::saturated_cast<int>(global_count + 1));
update->Set(kIphLastIgnoreTime, std::make_unique<base::Value>(
util::TimeToValue(base::Time::Now())));
} }
void RecordInstallIphInstalled(PrefService* pref_service, const AppId& app_id) { void RecordInstallIphInstalled(PrefService* pref_service, const AppId& app_id) {
...@@ -208,6 +236,40 @@ void RecordInstallIphInstalled(PrefService* pref_service, const AppId& app_id) { ...@@ -208,6 +236,40 @@ void RecordInstallIphInstalled(PrefService* pref_service, const AppId& app_id) {
// ignoring IPH, to help determine when IPH should be muted. Therefore // ignoring IPH, to help determine when IPH should be muted. Therefore
// resetting ignored count on successful install. // resetting ignored count on successful install.
UpdateIntWebAppPref(pref_service, app_id, kIphIgnoreCount, 0); UpdateIntWebAppPref(pref_service, app_id, kIphIgnoreCount, 0);
prefs::ScopedDictionaryPrefUpdate update(pref_service,
prefs::kWebAppsAppAgnosticIphState);
update->SetInteger(kIphIgnoreCount, 0);
}
bool ShouldShowIph(PrefService* pref_service, const AppId& app_id) {
// Do not show IPH if the user ignored the last N+ promos for this app.
int app_ignored_count =
GetIntWebAppPref(pref_service, app_id, kIphIgnoreCount).value_or(0);
if (app_ignored_count >= kIphMuteAfterConsecutiveAppSpecificIgnores)
return false;
// Do not show IPH if the user ignored a promo for this app within N days.
auto app_last_ignore =
GetTimeWebAppPref(pref_service, app_id, kIphLastIgnoreTime);
if (TimeOccurredWithinDays(app_last_ignore,
kIphAppSpecificMuteTimeSpanDays)) {
return false;
}
auto* dict = pref_service->GetDictionary(prefs::kWebAppsAppAgnosticIphState);
// Do not show IPH if the user ignored the last N+ promos for any app.
int global_ignored_count = dict->FindIntKey(kIphIgnoreCount).value_or(0);
if (global_ignored_count >= kIphMuteAfterConsecutiveAppAgnosticIgnores)
return false;
// Do not show IPH if the user ignored a promo for any app within N days.
auto global_last_ignore =
util::ValueToTime(dict->FindKey(kIphLastIgnoreTime));
if (TimeOccurredWithinDays(global_last_ignore,
kIphAppAgnosticMuteTimeSpanDays)) {
return false;
}
return true;
} }
} // namespace web_app } // namespace web_app
...@@ -79,6 +79,10 @@ void RecordInstallIphIgnored(PrefService* pref_service, const AppId& app_id); ...@@ -79,6 +79,10 @@ void RecordInstallIphIgnored(PrefService* pref_service, const AppId& app_id);
void RecordInstallIphInstalled(PrefService* pref_service, const AppId& app_id); void RecordInstallIphInstalled(PrefService* pref_service, const AppId& app_id);
// Returns whether Web App Install In Product Help should be shown based on
// previous interactions with this promo.
bool ShouldShowIph(PrefService* pref_service, const AppId& app_id);
} // namespace web_app } // namespace web_app
#endif // CHROME_BROWSER_WEB_APPLICATIONS_COMPONENTS_WEB_APP_PREFS_UTILS_H_ #endif // CHROME_BROWSER_WEB_APPLICATIONS_COMPONENTS_WEB_APP_PREFS_UTILS_H_
...@@ -1922,6 +1922,9 @@ const char kWebAppsDailyMetricsDate[] = "web_apps.daily_metrics_date"; ...@@ -1922,6 +1922,9 @@ const char kWebAppsDailyMetricsDate[] = "web_apps.daily_metrics_date";
// Dictionary that maps web app URLs to Chrome extension IDs. // Dictionary that maps web app URLs to Chrome extension IDs.
const char kWebAppsExtensionIDs[] = "web_apps.extension_ids"; const char kWebAppsExtensionIDs[] = "web_apps.extension_ids";
// Dictionary that stores IPH state not scoped to a particular app.
const char kWebAppsAppAgnosticIphState[] = "web_apps.app_agnostic_iph_state";
// A string representing the last version of Chrome preinstalled web apps were // A string representing the last version of Chrome preinstalled web apps were
// synchronised for. // synchronised for.
const char kWebAppsLastPreinstallSynchronizeVersion[] = const char kWebAppsLastPreinstallSynchronizeVersion[] =
......
...@@ -647,6 +647,7 @@ extern const char kWebAppInstallMetrics[]; ...@@ -647,6 +647,7 @@ extern const char kWebAppInstallMetrics[];
extern const char kWebAppsDailyMetrics[]; extern const char kWebAppsDailyMetrics[];
extern const char kWebAppsDailyMetricsDate[]; extern const char kWebAppsDailyMetricsDate[];
extern const char kWebAppsExtensionIDs[]; extern const char kWebAppsExtensionIDs[];
extern const char kWebAppsAppAgnosticIphState[];
extern const char kWebAppsLastPreinstallSynchronizeVersion[]; extern const char kWebAppsLastPreinstallSynchronizeVersion[];
extern const char kWebAppsPreferences[]; extern const char kWebAppsPreferences[];
extern const char kWebAppsUserDisplayModeCleanedUp[]; extern const char kWebAppsUserDisplayModeCleanedUp[];
......
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