| OLD | NEW |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/banners/app_banner_settings_helper.h" | 5 #include "chrome/browser/banners/app_banner_settings_helper.h" |
| 6 | 6 |
| 7 #include <stddef.h> | 7 #include <stddef.h> |
| 8 | 8 |
| 9 #include <algorithm> | 9 #include <memory> |
| 10 #include <string> | 10 #include <string> |
| 11 #include <utility> | 11 #include <utility> |
| 12 | 12 |
| 13 #include "base/command_line.h" | 13 #include "base/command_line.h" |
| 14 #include "base/memory/ptr_util.h" | 14 #include "base/memory/ptr_util.h" |
| 15 #include "base/metrics/field_trial.h" | |
| 16 #include "base/strings/string_number_conversions.h" | 15 #include "base/strings/string_number_conversions.h" |
| 17 #include "base/strings/string_util.h" | |
| 18 #include "chrome/browser/banners/app_banner_manager.h" | 16 #include "chrome/browser/banners/app_banner_manager.h" |
| 19 #include "chrome/browser/banners/app_banner_metrics.h" | 17 #include "chrome/browser/banners/app_banner_metrics.h" |
| 20 #include "chrome/browser/browser_process.h" | 18 #include "chrome/browser/browser_process.h" |
| 21 #include "chrome/browser/content_settings/host_content_settings_map_factory.h" | 19 #include "chrome/browser/content_settings/host_content_settings_map_factory.h" |
| 22 #include "chrome/browser/installable/installable_logging.h" | 20 #include "chrome/browser/installable/installable_logging.h" |
| 23 #include "chrome/browser/profiles/profile.h" | 21 #include "chrome/browser/profiles/profile.h" |
| 24 #include "chrome/common/chrome_switches.h" | 22 #include "chrome/common/chrome_switches.h" |
| 25 #include "components/content_settings/core/browser/host_content_settings_map.h" | 23 #include "components/content_settings/core/browser/host_content_settings_map.h" |
| 26 #include "components/content_settings/core/common/content_settings_pattern.h" | 24 #include "components/content_settings/core/common/content_settings_pattern.h" |
| 27 #include "components/rappor/rappor_utils.h" | 25 #include "components/rappor/rappor_utils.h" |
| 28 #include "components/variations/variations_associated_data.h" | 26 #include "components/variations/variations_associated_data.h" |
| 29 #include "content/public/browser/web_contents.h" | 27 #include "content/public/browser/web_contents.h" |
| 30 #include "net/base/escape.h" | |
| 31 #include "url/gurl.h" | 28 #include "url/gurl.h" |
| 32 | 29 |
| 33 namespace { | 30 namespace { |
| 34 | 31 |
| 35 // Max number of apps (including ServiceWorker based web apps) that a particular | 32 // Max number of apps (including ServiceWorker based web apps) that a particular |
| 36 // site may show a banner for. | 33 // site may show a banner for. |
| 37 const size_t kMaxAppsPerSite = 3; | 34 const size_t kMaxAppsPerSite = 3; |
| 38 | 35 |
| 39 // Oldest could show banner event we care about, in days. | |
| 40 const unsigned int kOldestCouldShowBannerEventInDays = 14; | |
| 41 | |
| 42 // Default number of days that dismissing or ignoring the banner will prevent it | 36 // Default number of days that dismissing or ignoring the banner will prevent it |
| 43 // being seen again for. | 37 // being seen again for. |
| 44 const unsigned int kMinimumBannerBlockedToBannerShown = 90; | 38 const unsigned int kMinimumBannerBlockedToBannerShown = 90; |
| 45 const unsigned int kMinimumDaysBetweenBannerShows = 14; | 39 const unsigned int kMinimumDaysBetweenBannerShows = 14; |
| 46 | 40 |
| 47 const unsigned int kNumberOfMinutesInADay = 1440; | 41 // Default site engagement required to trigger the banner. |
| 48 | |
| 49 // Default scores assigned to direct and indirect navigations respectively. | |
| 50 const unsigned int kDefaultDirectNavigationEngagement = 1; | |
| 51 const unsigned int kDefaultIndirectNavigationEngagement = 1; | |
| 52 | |
| 53 // Default number of navigations required to trigger the banner. | |
| 54 const unsigned int kDefaultTotalEngagementToTrigger = 2; | 42 const unsigned int kDefaultTotalEngagementToTrigger = 2; |
| 55 | 43 |
| 56 // The number of days in the past that a site should be launched from homescreen | 44 // The number of days in the past that a site should be launched from homescreen |
| 57 // to be considered recent. | 45 // to be considered recent. |
| 58 // TODO(dominickn): work out how to unify this with | 46 // TODO(dominickn): work out how to unify this with |
| 59 // WebappDataStorage.wasLaunchedRecently. | 47 // WebappDataStorage.wasLaunchedRecently. |
| 60 const unsigned int kRecentLastLaunchInDays = 10; | 48 const unsigned int kRecentLastLaunchInDays = 10; |
| 61 | 49 |
| 62 // Dictionary keys to use for the events. | 50 // Dictionary keys to use for the events. Must be kept in sync with |
| 51 // AppBannerEvent. |
| 63 const char* kBannerEventKeys[] = { | 52 const char* kBannerEventKeys[] = { |
| 64 "couldShowBannerEvents", | 53 "couldShowBannerEvents", |
| 65 "didShowBannerEvent", | 54 "didShowBannerEvent", |
| 66 "didBlockBannerEvent", | 55 "didBlockBannerEvent", |
| 67 "didAddToHomescreenEvent", | 56 "didAddToHomescreenEvent", |
| 68 }; | 57 }; |
| 69 | 58 |
| 70 // Keys to use when storing BannerEvent structs. | |
| 71 const char kBannerTimeKey[] = "time"; | |
| 72 const char kBannerEngagementKey[] = "engagement"; | |
| 73 | |
| 74 // Keys to use when querying the variations params. | 59 // Keys to use when querying the variations params. |
| 75 const char kBannerParamsKey[] = "AppBannerTriggering"; | 60 const char kBannerParamsKey[] = "AppBannerTriggering"; |
| 76 const char kBannerParamsDirectKey[] = "direct"; | |
| 77 const char kBannerParamsIndirectKey[] = "indirect"; | |
| 78 const char kBannerParamsTotalKey[] = "total"; | |
| 79 const char kBannerParamsMinutesKey[] = "minutes"; | |
| 80 const char kBannerParamsEngagementTotalKey[] = "site_engagement_total"; | 61 const char kBannerParamsEngagementTotalKey[] = "site_engagement_total"; |
| 81 const char kBannerParamsDaysAfterBannerDismissedKey[] = "days_after_dismiss"; | 62 const char kBannerParamsDaysAfterBannerDismissedKey[] = "days_after_dismiss"; |
| 82 const char kBannerParamsDaysAfterBannerIgnoredKey[] = "days_after_ignore"; | 63 const char kBannerParamsDaysAfterBannerIgnoredKey[] = "days_after_ignore"; |
| 83 const char kBannerSiteEngagementParamsKey[] = "use_site_engagement"; | |
| 84 const char kBannerParamsLanguageKey[] = "language_option"; | 64 const char kBannerParamsLanguageKey[] = "language_option"; |
| 85 | 65 |
| 86 // Engagement weight assigned to direct and indirect navigations. | |
| 87 // By default, a direct navigation is a page visit via ui::PAGE_TRANSITION_TYPED | |
| 88 // or ui::PAGE_TRANSITION_GENERATED. | |
| 89 double gDirectNavigationEngagement = kDefaultDirectNavigationEngagement; | |
| 90 double gIndirectNavigationEnagagement = kDefaultIndirectNavigationEngagement; | |
| 91 | |
| 92 // Number of minutes between visits that will trigger a could show banner event. | |
| 93 // Defaults to the number of minutes in a day. | |
| 94 unsigned int gMinimumMinutesBetweenVisits = kNumberOfMinutesInADay; | |
| 95 | |
| 96 // Total engagement score required before a banner will actually be triggered. | 66 // Total engagement score required before a banner will actually be triggered. |
| 97 double gTotalEngagementToTrigger = kDefaultTotalEngagementToTrigger; | 67 double gTotalEngagementToTrigger = kDefaultTotalEngagementToTrigger; |
| 98 | 68 |
| 99 unsigned int gDaysAfterDismissedToShow = kMinimumBannerBlockedToBannerShown; | 69 unsigned int gDaysAfterDismissedToShow = kMinimumBannerBlockedToBannerShown; |
| 100 unsigned int gDaysAfterIgnoredToShow = kMinimumDaysBetweenBannerShows; | 70 unsigned int gDaysAfterIgnoredToShow = kMinimumDaysBetweenBannerShows; |
| 101 | 71 |
| 102 std::unique_ptr<base::DictionaryValue> GetOriginDict( | 72 std::unique_ptr<base::DictionaryValue> GetOriginDict( |
| 103 HostContentSettingsMap* settings, | 73 HostContentSettingsMap* settings, |
| 104 const GURL& origin_url) { | 74 const GURL& origin_url) { |
| 105 if (!settings) | 75 if (!settings) |
| (...skipping 17 matching lines...) Expand all Loading... |
| 123 if (origin_dict->size() < kMaxAppsPerSite) { | 93 if (origin_dict->size() < kMaxAppsPerSite) { |
| 124 app_dict = new base::DictionaryValue(); | 94 app_dict = new base::DictionaryValue(); |
| 125 origin_dict->SetWithoutPathExpansion(key_name, | 95 origin_dict->SetWithoutPathExpansion(key_name, |
| 126 base::WrapUnique(app_dict)); | 96 base::WrapUnique(app_dict)); |
| 127 } | 97 } |
| 128 } | 98 } |
| 129 | 99 |
| 130 return app_dict; | 100 return app_dict; |
| 131 } | 101 } |
| 132 | 102 |
| 133 double GetEventEngagement(ui::PageTransition transition_type) { | |
| 134 if (ui::PageTransitionCoreTypeIs(transition_type, | |
| 135 ui::PAGE_TRANSITION_TYPED) || | |
| 136 ui::PageTransitionCoreTypeIs(transition_type, | |
| 137 ui::PAGE_TRANSITION_GENERATED)) { | |
| 138 return gDirectNavigationEngagement; | |
| 139 } else { | |
| 140 return gIndirectNavigationEnagagement; | |
| 141 } | |
| 142 } | |
| 143 | |
| 144 // Queries variations for the number of days which dismissing and ignoring the | 103 // Queries variations for the number of days which dismissing and ignoring the |
| 145 // banner should prevent a banner from showing. | 104 // banner should prevent a banner from showing. |
| 146 void UpdateDaysBetweenShowing() { | 105 void UpdateDaysBetweenShowing() { |
| 147 std::string dismiss_param = variations::GetVariationParamValue( | 106 std::string dismiss_param = variations::GetVariationParamValue( |
| 148 kBannerParamsKey, kBannerParamsDaysAfterBannerDismissedKey); | 107 kBannerParamsKey, kBannerParamsDaysAfterBannerDismissedKey); |
| 149 std::string ignore_param = variations::GetVariationParamValue( | 108 std::string ignore_param = variations::GetVariationParamValue( |
| 150 kBannerParamsKey, kBannerParamsDaysAfterBannerIgnoredKey); | 109 kBannerParamsKey, kBannerParamsDaysAfterBannerIgnoredKey); |
| 151 | 110 |
| 152 if (!dismiss_param.empty() && !ignore_param.empty()) { | 111 if (!dismiss_param.empty() && !ignore_param.empty()) { |
| 153 unsigned int dismiss_days = 0; | 112 unsigned int dismiss_days = 0; |
| (...skipping 16 matching lines...) Expand all Loading... |
| 170 if (!total_param.empty()) { | 129 if (!total_param.empty()) { |
| 171 double total_engagement = -1; | 130 double total_engagement = -1; |
| 172 | 131 |
| 173 if (base::StringToDouble(total_param, &total_engagement) && | 132 if (base::StringToDouble(total_param, &total_engagement) && |
| 174 total_engagement >= 0) { | 133 total_engagement >= 0) { |
| 175 AppBannerSettingsHelper::SetTotalEngagementToTrigger(total_engagement); | 134 AppBannerSettingsHelper::SetTotalEngagementToTrigger(total_engagement); |
| 176 } | 135 } |
| 177 } | 136 } |
| 178 } | 137 } |
| 179 | 138 |
| 180 // Queries variations for updates to the default engagement values assigned | |
| 181 // to direct and indirect navigations. | |
| 182 void UpdateEngagementWeights() { | |
| 183 std::string direct_param = variations::GetVariationParamValue( | |
| 184 kBannerParamsKey, kBannerParamsDirectKey); | |
| 185 std::string indirect_param = variations::GetVariationParamValue( | |
| 186 kBannerParamsKey, kBannerParamsIndirectKey); | |
| 187 std::string total_param = variations::GetVariationParamValue( | |
| 188 kBannerParamsKey, kBannerParamsTotalKey); | |
| 189 | |
| 190 if (!direct_param.empty() && !indirect_param.empty() && | |
| 191 !total_param.empty()) { | |
| 192 double direct_engagement = -1; | |
| 193 double indirect_engagement = -1; | |
| 194 double total_engagement = -1; | |
| 195 | |
| 196 // Ensure that we get valid doubles from the field trial, and that both | |
| 197 // values are greater than or equal to zero and less than or equal to the | |
| 198 // total engagement required to trigger the banner. | |
| 199 if (base::StringToDouble(direct_param, &direct_engagement) && | |
| 200 base::StringToDouble(indirect_param, &indirect_engagement) && | |
| 201 base::StringToDouble(total_param, &total_engagement) && | |
| 202 direct_engagement >= 0 && indirect_engagement >= 0 && | |
| 203 total_engagement > 0 && direct_engagement <= total_engagement && | |
| 204 indirect_engagement <= total_engagement) { | |
| 205 AppBannerSettingsHelper::SetEngagementWeights(direct_engagement, | |
| 206 indirect_engagement); | |
| 207 AppBannerSettingsHelper::SetTotalEngagementToTrigger(total_engagement); | |
| 208 } | |
| 209 } | |
| 210 } | |
| 211 | |
| 212 // Queries variation for updates to the default number of minutes between | |
| 213 // site visits counted for the purposes of displaying a banner. | |
| 214 void UpdateMinutesBetweenVisits() { | |
| 215 std::string param = variations::GetVariationParamValue( | |
| 216 kBannerParamsKey, kBannerParamsMinutesKey); | |
| 217 if (!param.empty()) { | |
| 218 int minimum_minutes = 0; | |
| 219 if (base::StringToInt(param, &minimum_minutes)) | |
| 220 AppBannerSettingsHelper::SetMinimumMinutesBetweenVisits(minimum_minutes); | |
| 221 } | |
| 222 } | |
| 223 | |
| 224 } // namespace | 139 } // namespace |
| 225 | 140 |
| 226 // Key to store instant apps events. | 141 // Key to store instant apps events. |
| 227 const char AppBannerSettingsHelper::kInstantAppsKey[] = "instantapps"; | 142 const char AppBannerSettingsHelper::kInstantAppsKey[] = "instantapps"; |
| 228 | 143 |
| 229 void AppBannerSettingsHelper::ClearHistoryForURLs( | 144 void AppBannerSettingsHelper::ClearHistoryForURLs( |
| 230 Profile* profile, | 145 Profile* profile, |
| 231 const std::set<GURL>& origin_urls) { | 146 const std::set<GURL>& origin_urls) { |
| 232 HostContentSettingsMap* settings = | 147 HostContentSettingsMap* settings = |
| 233 HostContentSettingsMapFactory::GetForProfile(profile); | 148 HostContentSettingsMapFactory::GetForProfile(profile); |
| 234 for (const GURL& origin_url : origin_urls) { | 149 for (const GURL& origin_url : origin_urls) { |
| 235 settings->SetWebsiteSettingDefaultScope(origin_url, GURL(), | 150 settings->SetWebsiteSettingDefaultScope(origin_url, GURL(), |
| 236 CONTENT_SETTINGS_TYPE_APP_BANNER, | 151 CONTENT_SETTINGS_TYPE_APP_BANNER, |
| 237 std::string(), nullptr); | 152 std::string(), nullptr); |
| 238 settings->FlushLossyWebsiteSettings(); | 153 settings->FlushLossyWebsiteSettings(); |
| 239 } | 154 } |
| 240 } | 155 } |
| 241 | 156 |
| 242 void AppBannerSettingsHelper::RecordBannerInstallEvent( | 157 void AppBannerSettingsHelper::RecordBannerInstallEvent( |
| 243 content::WebContents* web_contents, | 158 content::WebContents* web_contents, |
| 244 const std::string& package_name_or_start_url, | 159 const std::string& package_name_or_start_url, |
| 245 AppBannerRapporMetric rappor_metric) { | 160 AppBannerRapporMetric rappor_metric) { |
| 246 banners::TrackInstallEvent(banners::INSTALL_EVENT_WEB_APP_INSTALLED); | 161 banners::TrackInstallEvent(banners::INSTALL_EVENT_WEB_APP_INSTALLED); |
| 247 | 162 |
| 248 AppBannerSettingsHelper::RecordBannerEvent( | 163 AppBannerSettingsHelper::RecordBannerEvent( |
| 249 web_contents, web_contents->GetURL(), package_name_or_start_url, | 164 web_contents, web_contents->GetLastCommittedURL(), |
| 165 package_name_or_start_url, |
| 250 AppBannerSettingsHelper::APP_BANNER_EVENT_DID_ADD_TO_HOMESCREEN, | 166 AppBannerSettingsHelper::APP_BANNER_EVENT_DID_ADD_TO_HOMESCREEN, |
| 251 banners::AppBannerManager::GetCurrentTime()); | 167 banners::AppBannerManager::GetCurrentTime()); |
| 252 | 168 |
| 253 rappor::SampleDomainAndRegistryFromGURL( | 169 rappor::SampleDomainAndRegistryFromGURL( |
| 254 g_browser_process->rappor_service(), | 170 g_browser_process->rappor_service(), |
| 255 (rappor_metric == WEB ? "AppBanner.WebApp.Installed" | 171 (rappor_metric == WEB ? "AppBanner.WebApp.Installed" |
| 256 : "AppBanner.NativeApp.Installed"), | 172 : "AppBanner.NativeApp.Installed"), |
| 257 web_contents->GetURL()); | 173 web_contents->GetLastCommittedURL()); |
| 258 } | 174 } |
| 259 | 175 |
| 260 void AppBannerSettingsHelper::RecordBannerDismissEvent( | 176 void AppBannerSettingsHelper::RecordBannerDismissEvent( |
| 261 content::WebContents* web_contents, | 177 content::WebContents* web_contents, |
| 262 const std::string& package_name_or_start_url, | 178 const std::string& package_name_or_start_url, |
| 263 AppBannerRapporMetric rappor_metric) { | 179 AppBannerRapporMetric rappor_metric) { |
| 264 banners::TrackDismissEvent(banners::DISMISS_EVENT_CLOSE_BUTTON); | 180 banners::TrackDismissEvent(banners::DISMISS_EVENT_CLOSE_BUTTON); |
| 265 | 181 |
| 266 AppBannerSettingsHelper::RecordBannerEvent( | 182 AppBannerSettingsHelper::RecordBannerEvent( |
| 267 web_contents, web_contents->GetURL(), package_name_or_start_url, | 183 web_contents, web_contents->GetLastCommittedURL(), |
| 184 package_name_or_start_url, |
| 268 AppBannerSettingsHelper::APP_BANNER_EVENT_DID_BLOCK, | 185 AppBannerSettingsHelper::APP_BANNER_EVENT_DID_BLOCK, |
| 269 banners::AppBannerManager::GetCurrentTime()); | 186 banners::AppBannerManager::GetCurrentTime()); |
| 270 | 187 |
| 271 rappor::SampleDomainAndRegistryFromGURL( | 188 rappor::SampleDomainAndRegistryFromGURL( |
| 272 g_browser_process->rappor_service(), | 189 g_browser_process->rappor_service(), |
| 273 (rappor_metric == WEB ? "AppBanner.WebApp.Dismissed" | 190 (rappor_metric == WEB ? "AppBanner.WebApp.Dismissed" |
| 274 : "AppBanner.NativeApp.Dismissed"), | 191 : "AppBanner.NativeApp.Dismissed"), |
| 275 web_contents->GetURL()); | 192 web_contents->GetLastCommittedURL()); |
| 276 } | 193 } |
| 277 | 194 |
| 278 void AppBannerSettingsHelper::RecordBannerEvent( | 195 void AppBannerSettingsHelper::RecordBannerEvent( |
| 279 content::WebContents* web_contents, | 196 content::WebContents* web_contents, |
| 280 const GURL& origin_url, | 197 const GURL& origin_url, |
| 281 const std::string& package_name_or_start_url, | 198 const std::string& package_name_or_start_url, |
| 282 AppBannerEvent event, | 199 AppBannerEvent event, |
| 283 base::Time time) { | 200 base::Time time) { |
| 284 DCHECK(event != APP_BANNER_EVENT_COULD_SHOW); | |
| 285 | |
| 286 Profile* profile = | 201 Profile* profile = |
| 287 Profile::FromBrowserContext(web_contents->GetBrowserContext()); | 202 Profile::FromBrowserContext(web_contents->GetBrowserContext()); |
| 288 if (profile->IsOffTheRecord() || package_name_or_start_url.empty()) | 203 if (profile->IsOffTheRecord() || package_name_or_start_url.empty()) |
| 289 return; | 204 return; |
| 290 | 205 |
| 291 HostContentSettingsMap* settings = | 206 HostContentSettingsMap* settings = |
| 292 HostContentSettingsMapFactory::GetForProfile(profile); | 207 HostContentSettingsMapFactory::GetForProfile(profile); |
| 293 std::unique_ptr<base::DictionaryValue> origin_dict = | 208 std::unique_ptr<base::DictionaryValue> origin_dict = |
| 294 GetOriginDict(settings, origin_url); | 209 GetOriginDict(settings, origin_url); |
| 295 if (!origin_dict) | 210 if (!origin_dict) |
| 296 return; | 211 return; |
| 297 | 212 |
| 298 base::DictionaryValue* app_dict = | 213 base::DictionaryValue* app_dict = |
| 299 GetAppDict(origin_dict.get(), package_name_or_start_url); | 214 GetAppDict(origin_dict.get(), package_name_or_start_url); |
| 300 if (!app_dict) | 215 if (!app_dict) |
| 301 return; | 216 return; |
| 302 | 217 |
| 303 // Dates are stored in their raw form (i.e. not local dates) to be resilient | 218 // Dates are stored in their raw form (i.e. not local dates) to be resilient |
| 304 // to time zone changes. | 219 // to time zone changes. |
| 305 std::string event_key(kBannerEventKeys[event]); | 220 std::string event_key(kBannerEventKeys[event]); |
| 221 |
| 222 if (event == APP_BANNER_EVENT_COULD_SHOW) { |
| 223 // Do not overwrite a could show event, as this is used for metrics. |
| 224 double internal_date; |
| 225 if (app_dict->GetDouble(event_key, &internal_date)) |
| 226 return; |
| 227 } |
| 306 app_dict->SetDouble(event_key, time.ToInternalValue()); | 228 app_dict->SetDouble(event_key, time.ToInternalValue()); |
| 307 | 229 |
| 308 settings->SetWebsiteSettingDefaultScope( | 230 settings->SetWebsiteSettingDefaultScope( |
| 309 origin_url, GURL(), CONTENT_SETTINGS_TYPE_APP_BANNER, std::string(), | 231 origin_url, GURL(), CONTENT_SETTINGS_TYPE_APP_BANNER, std::string(), |
| 310 std::move(origin_dict)); | 232 std::move(origin_dict)); |
| 311 | 233 |
| 312 // App banner content settings are lossy, meaning they will not cause the | 234 // App banner content settings are lossy, meaning they will not cause the |
| 313 // prefs to become dirty. This is fine for most events, as if they are lost it | 235 // prefs to become dirty. This is fine for most events, as if they are lost it |
| 314 // just means the user will have to engage a little bit more. However the | 236 // just means the user will have to engage a little bit more. However the |
| 315 // DID_ADD_TO_HOMESCREEN event should always be recorded to prevent | 237 // DID_ADD_TO_HOMESCREEN event should always be recorded to prevent |
| 316 // spamminess. | 238 // spamminess. |
| 317 if (event == APP_BANNER_EVENT_DID_ADD_TO_HOMESCREEN) | 239 if (event == APP_BANNER_EVENT_DID_ADD_TO_HOMESCREEN) |
| 318 settings->FlushLossyWebsiteSettings(); | 240 settings->FlushLossyWebsiteSettings(); |
| 319 } | 241 } |
| 320 | 242 |
| 321 void AppBannerSettingsHelper::RecordBannerCouldShowEvent( | |
| 322 content::WebContents* web_contents, | |
| 323 const GURL& origin_url, | |
| 324 const std::string& package_name_or_start_url, | |
| 325 base::Time time, | |
| 326 ui::PageTransition transition_type) { | |
| 327 Profile* profile = | |
| 328 Profile::FromBrowserContext(web_contents->GetBrowserContext()); | |
| 329 if (profile->IsOffTheRecord() || package_name_or_start_url.empty()) | |
| 330 return; | |
| 331 | |
| 332 HostContentSettingsMap* settings = | |
| 333 HostContentSettingsMapFactory::GetForProfile(profile); | |
| 334 std::unique_ptr<base::DictionaryValue> origin_dict = | |
| 335 GetOriginDict(settings, origin_url); | |
| 336 if (!origin_dict) | |
| 337 return; | |
| 338 | |
| 339 base::DictionaryValue* app_dict = | |
| 340 GetAppDict(origin_dict.get(), package_name_or_start_url); | |
| 341 if (!app_dict) | |
| 342 return; | |
| 343 | |
| 344 std::string event_key(kBannerEventKeys[APP_BANNER_EVENT_COULD_SHOW]); | |
| 345 double engagement = GetEventEngagement(transition_type); | |
| 346 | |
| 347 base::ListValue* could_show_list = nullptr; | |
| 348 if (!app_dict->GetList(event_key, &could_show_list)) { | |
| 349 could_show_list = new base::ListValue(); | |
| 350 app_dict->Set(event_key, base::WrapUnique(could_show_list)); | |
| 351 } | |
| 352 | |
| 353 // Trim any items that are older than we should care about. For comparisons | |
| 354 // the times are converted to local dates. | |
| 355 base::Time date = BucketTimeToResolution(time, gMinimumMinutesBetweenVisits); | |
| 356 for (auto it = could_show_list->begin(); it != could_show_list->end();) { | |
| 357 if ((*it)->IsType(base::Value::TYPE_DICTIONARY)) { | |
| 358 base::DictionaryValue* internal_value; | |
| 359 double internal_date; | |
| 360 (*it)->GetAsDictionary(&internal_value); | |
| 361 | |
| 362 if (internal_value->GetDouble(kBannerTimeKey, &internal_date)) { | |
| 363 base::Time other_date = | |
| 364 BucketTimeToResolution(base::Time::FromInternalValue(internal_date), | |
| 365 gMinimumMinutesBetweenVisits); | |
| 366 if (other_date == date) { | |
| 367 double other_engagement = 0; | |
| 368 if (internal_value->GetDouble(kBannerEngagementKey, | |
| 369 &other_engagement) && | |
| 370 other_engagement >= engagement) { | |
| 371 // This date has already been added, but with an equal or higher | |
| 372 // engagement. Don't add the date again. If the conditional fails, | |
| 373 // fall to the end of the loop where the existing entry is deleted. | |
| 374 return; | |
| 375 } | |
| 376 } else { | |
| 377 base::TimeDelta delta = date - other_date; | |
| 378 if (delta < | |
| 379 base::TimeDelta::FromDays(kOldestCouldShowBannerEventInDays)) { | |
| 380 ++it; | |
| 381 continue; | |
| 382 } | |
| 383 } | |
| 384 } | |
| 385 } | |
| 386 | |
| 387 // Either this date is older than we care about, or it isn't in the correct | |
| 388 // format, or it is the same as the current date but with a lower | |
| 389 // engagement, so remove it. | |
| 390 it = could_show_list->Erase(it, nullptr); | |
| 391 } | |
| 392 | |
| 393 // Dates are stored in their raw form (i.e. not local dates) to be resilient | |
| 394 // to time zone changes. | |
| 395 std::unique_ptr<base::DictionaryValue> value(new base::DictionaryValue()); | |
| 396 value->SetDouble(kBannerTimeKey, time.ToInternalValue()); | |
| 397 value->SetDouble(kBannerEngagementKey, engagement); | |
| 398 could_show_list->Append(std::move(value)); | |
| 399 | |
| 400 settings->SetWebsiteSettingDefaultScope( | |
| 401 origin_url, GURL(), CONTENT_SETTINGS_TYPE_APP_BANNER, std::string(), | |
| 402 std::move(origin_dict)); | |
| 403 } | |
| 404 | |
| 405 InstallableStatusCode AppBannerSettingsHelper::ShouldShowBanner( | 243 InstallableStatusCode AppBannerSettingsHelper::ShouldShowBanner( |
| 406 content::WebContents* web_contents, | 244 content::WebContents* web_contents, |
| 407 const GURL& origin_url, | 245 const GURL& origin_url, |
| 408 const std::string& package_name_or_start_url, | 246 const std::string& package_name_or_start_url, |
| 409 base::Time time) { | 247 base::Time time) { |
| 410 // Ignore all checks if the flag to do so is set. | 248 // Ignore all checks if the flag to do so is set. |
| 411 if (base::CommandLine::ForCurrentProcess()->HasSwitch( | 249 if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| 412 switches::kBypassAppBannerEngagementChecks)) { | 250 switches::kBypassAppBannerEngagementChecks)) { |
| 413 return NO_ERROR_DETECTED; | 251 return NO_ERROR_DETECTED; |
| 414 } | 252 } |
| (...skipping 20 matching lines...) Expand all Loading... |
| 435 return PREVIOUSLY_BLOCKED; | 273 return PREVIOUSLY_BLOCKED; |
| 436 } | 274 } |
| 437 | 275 |
| 438 base::Time shown_time = | 276 base::Time shown_time = |
| 439 GetSingleBannerEvent(web_contents, origin_url, package_name_or_start_url, | 277 GetSingleBannerEvent(web_contents, origin_url, package_name_or_start_url, |
| 440 APP_BANNER_EVENT_DID_SHOW); | 278 APP_BANNER_EVENT_DID_SHOW); |
| 441 if (time - shown_time < base::TimeDelta::FromDays(gDaysAfterIgnoredToShow)) { | 279 if (time - shown_time < base::TimeDelta::FromDays(gDaysAfterIgnoredToShow)) { |
| 442 return PREVIOUSLY_IGNORED; | 280 return PREVIOUSLY_IGNORED; |
| 443 } | 281 } |
| 444 | 282 |
| 445 // If we have gotten this far and want to use site engagement, the banner flow | |
| 446 // was triggered by the site engagement service informing the banner manager | |
| 447 // that sufficient engagement has been accumulated. Hence there is no need to | |
| 448 // check the total amount of engagement. | |
| 449 // TODO(dominickn): just return true here and remove all of the following code | |
| 450 // in this method when app banners have fully migrated to using site | |
| 451 // engagement as a trigger condition. See crbug.com/616322. | |
| 452 // Do not do engagement checks for instant app banners. | |
| 453 if (ShouldUseSiteEngagementScore() || | |
| 454 package_name_or_start_url == kInstantAppsKey) { | |
| 455 return NO_ERROR_DETECTED; | |
| 456 } | |
| 457 | |
| 458 double total_engagement = 0; | |
| 459 std::vector<BannerEvent> could_show_events = GetCouldShowBannerEvents( | |
| 460 web_contents, origin_url, package_name_or_start_url); | |
| 461 | |
| 462 for (const auto& event : could_show_events) | |
| 463 total_engagement += event.engagement; | |
| 464 | |
| 465 if (!HasSufficientEngagement(total_engagement)) | |
| 466 return INSUFFICIENT_ENGAGEMENT; | |
| 467 | |
| 468 return NO_ERROR_DETECTED; | 283 return NO_ERROR_DETECTED; |
| 469 } | 284 } |
| 470 | 285 |
| 471 std::vector<AppBannerSettingsHelper::BannerEvent> | |
| 472 AppBannerSettingsHelper::GetCouldShowBannerEvents( | |
| 473 content::WebContents* web_contents, | |
| 474 const GURL& origin_url, | |
| 475 const std::string& package_name_or_start_url) { | |
| 476 std::vector<BannerEvent> result; | |
| 477 | |
| 478 Profile* profile = | |
| 479 Profile::FromBrowserContext(web_contents->GetBrowserContext()); | |
| 480 HostContentSettingsMap* settings = | |
| 481 HostContentSettingsMapFactory::GetForProfile(profile); | |
| 482 std::unique_ptr<base::DictionaryValue> origin_dict = | |
| 483 GetOriginDict(settings, origin_url); | |
| 484 | |
| 485 if (!origin_dict) | |
| 486 return result; | |
| 487 | |
| 488 base::DictionaryValue* app_dict = | |
| 489 GetAppDict(origin_dict.get(), package_name_or_start_url); | |
| 490 if (!app_dict) | |
| 491 return result; | |
| 492 | |
| 493 std::string event_key(kBannerEventKeys[APP_BANNER_EVENT_COULD_SHOW]); | |
| 494 base::ListValue* could_show_list = nullptr; | |
| 495 if (!app_dict->GetList(event_key, &could_show_list)) | |
| 496 return result; | |
| 497 | |
| 498 for (const auto& value : *could_show_list) { | |
| 499 if (value->IsType(base::Value::TYPE_DICTIONARY)) { | |
| 500 base::DictionaryValue* internal_value; | |
| 501 double internal_date = 0; | |
| 502 value->GetAsDictionary(&internal_value); | |
| 503 double engagement = 0; | |
| 504 | |
| 505 if (internal_value->GetDouble(kBannerTimeKey, &internal_date) && | |
| 506 internal_value->GetDouble(kBannerEngagementKey, &engagement)) { | |
| 507 base::Time date = base::Time::FromInternalValue(internal_date); | |
| 508 result.push_back({date, engagement}); | |
| 509 } | |
| 510 } | |
| 511 } | |
| 512 | |
| 513 return result; | |
| 514 } | |
| 515 | |
| 516 base::Time AppBannerSettingsHelper::GetSingleBannerEvent( | 286 base::Time AppBannerSettingsHelper::GetSingleBannerEvent( |
| 517 content::WebContents* web_contents, | 287 content::WebContents* web_contents, |
| 518 const GURL& origin_url, | 288 const GURL& origin_url, |
| 519 const std::string& package_name_or_start_url, | 289 const std::string& package_name_or_start_url, |
| 520 AppBannerEvent event) { | 290 AppBannerEvent event) { |
| 521 DCHECK(event != APP_BANNER_EVENT_COULD_SHOW); | |
| 522 DCHECK(event < APP_BANNER_EVENT_NUM_EVENTS); | 291 DCHECK(event < APP_BANNER_EVENT_NUM_EVENTS); |
| 523 | 292 |
| 524 Profile* profile = | 293 Profile* profile = |
| 525 Profile::FromBrowserContext(web_contents->GetBrowserContext()); | 294 Profile::FromBrowserContext(web_contents->GetBrowserContext()); |
| 526 HostContentSettingsMap* settings = | 295 HostContentSettingsMap* settings = |
| 527 HostContentSettingsMapFactory::GetForProfile(profile); | 296 HostContentSettingsMapFactory::GetForProfile(profile); |
| 528 std::unique_ptr<base::DictionaryValue> origin_dict = | 297 std::unique_ptr<base::DictionaryValue> origin_dict = |
| 529 GetOriginDict(settings, origin_url); | 298 GetOriginDict(settings, origin_url); |
| 530 | 299 |
| 531 if (!origin_dict) | 300 if (!origin_dict) |
| (...skipping 16 matching lines...) Expand all Loading... |
| 548 return (base::CommandLine::ForCurrentProcess()->HasSwitch( | 317 return (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| 549 switches::kBypassAppBannerEngagementChecks)) || | 318 switches::kBypassAppBannerEngagementChecks)) || |
| 550 (total_engagement >= gTotalEngagementToTrigger); | 319 (total_engagement >= gTotalEngagementToTrigger); |
| 551 } | 320 } |
| 552 | 321 |
| 553 void AppBannerSettingsHelper::RecordMinutesFromFirstVisitToShow( | 322 void AppBannerSettingsHelper::RecordMinutesFromFirstVisitToShow( |
| 554 content::WebContents* web_contents, | 323 content::WebContents* web_contents, |
| 555 const GURL& origin_url, | 324 const GURL& origin_url, |
| 556 const std::string& package_name_or_start_url, | 325 const std::string& package_name_or_start_url, |
| 557 base::Time time) { | 326 base::Time time) { |
| 558 std::vector<BannerEvent> could_show_events = GetCouldShowBannerEvents( | 327 base::Time could_show_time = |
| 559 web_contents, origin_url, package_name_or_start_url); | 328 GetSingleBannerEvent(web_contents, origin_url, package_name_or_start_url, |
| 329 APP_BANNER_EVENT_COULD_SHOW); |
| 560 | 330 |
| 561 int minutes = 0; | 331 int minutes = 0; |
| 562 if (could_show_events.size()) | 332 if (!could_show_time.is_null()) |
| 563 minutes = (time - could_show_events[0].time).InMinutes(); | 333 minutes = (time - could_show_time).InMinutes(); |
| 564 | 334 |
| 565 banners::TrackMinutesFromFirstVisitToBannerShown(minutes); | 335 banners::TrackMinutesFromFirstVisitToBannerShown(minutes); |
| 566 } | 336 } |
| 567 | 337 |
| 568 bool AppBannerSettingsHelper::WasLaunchedRecently(Profile* profile, | 338 bool AppBannerSettingsHelper::WasLaunchedRecently(Profile* profile, |
| 569 const GURL& origin_url, | 339 const GURL& origin_url, |
| 570 base::Time now) { | 340 base::Time now) { |
| 571 HostContentSettingsMap* settings = | 341 HostContentSettingsMap* settings = |
| 572 HostContentSettingsMapFactory::GetForProfile(profile); | 342 HostContentSettingsMapFactory::GetForProfile(profile); |
| 573 std::unique_ptr<base::DictionaryValue> origin_dict = | 343 std::unique_ptr<base::DictionaryValue> origin_dict = |
| 574 GetOriginDict(settings, origin_url); | 344 GetOriginDict(settings, origin_url); |
| 575 | 345 |
| 576 if (!origin_dict) | 346 if (!origin_dict) |
| 577 return false; | 347 return false; |
| 578 | 348 |
| 579 // Iterate over everything in the content setting, which should be a set of | 349 // Iterate over everything in the content setting, which should be a set of |
| 580 // dictionaries per app path. If we find one that has been added to | 350 // dictionaries per app path. If we find one that has been added to |
| 581 // homescreen recently, return true. | 351 // homescreen recently, return true. |
| 352 base::TimeDelta recent_last_launch_in_days = |
| 353 base::TimeDelta::FromDays(kRecentLastLaunchInDays); |
| 582 for (base::DictionaryValue::Iterator it(*origin_dict); !it.IsAtEnd(); | 354 for (base::DictionaryValue::Iterator it(*origin_dict); !it.IsAtEnd(); |
| 583 it.Advance()) { | 355 it.Advance()) { |
| 584 if (it.value().IsType(base::Value::TYPE_DICTIONARY)) { | 356 if (it.value().IsType(base::Value::TYPE_DICTIONARY)) { |
| 585 const base::DictionaryValue* value; | 357 const base::DictionaryValue* value; |
| 586 it.value().GetAsDictionary(&value); | 358 it.value().GetAsDictionary(&value); |
| 587 | 359 |
| 588 std::string event_key( | 360 std::string event_key( |
| 589 kBannerEventKeys[APP_BANNER_EVENT_DID_ADD_TO_HOMESCREEN]); | 361 kBannerEventKeys[APP_BANNER_EVENT_DID_ADD_TO_HOMESCREEN]); |
| 590 double internal_time; | 362 double internal_time; |
| 591 if (it.key() == kInstantAppsKey || | 363 if (it.key() == kInstantAppsKey || |
| 592 !value->GetDouble(event_key, &internal_time)) { | 364 !value->GetDouble(event_key, &internal_time)) { |
| 593 continue; | 365 continue; |
| 594 } | 366 } |
| 595 | 367 |
| 596 base::Time added_time = base::Time::FromInternalValue(internal_time); | 368 if ((now - base::Time::FromInternalValue(internal_time)) <= |
| 597 | 369 recent_last_launch_in_days) { |
| 598 if ((now - added_time) <= | |
| 599 base::TimeDelta::FromDays(kRecentLastLaunchInDays)) { | |
| 600 return true; | 370 return true; |
| 601 } | 371 } |
| 602 } | 372 } |
| 603 } | 373 } |
| 604 | 374 |
| 605 return false; | 375 return false; |
| 606 } | 376 } |
| 607 | 377 |
| 608 void AppBannerSettingsHelper::SetDaysAfterDismissAndIgnoreToTrigger( | 378 void AppBannerSettingsHelper::SetDaysAfterDismissAndIgnoreToTrigger( |
| 609 unsigned int dismiss_days, | 379 unsigned int dismiss_days, |
| 610 unsigned int ignore_days) { | 380 unsigned int ignore_days) { |
| 611 gDaysAfterDismissedToShow = dismiss_days; | 381 gDaysAfterDismissedToShow = dismiss_days; |
| 612 gDaysAfterIgnoredToShow = ignore_days; | 382 gDaysAfterIgnoredToShow = ignore_days; |
| 613 } | 383 } |
| 614 | 384 |
| 615 void AppBannerSettingsHelper::SetEngagementWeights(double direct_engagement, | |
| 616 double indirect_engagement) { | |
| 617 gDirectNavigationEngagement = direct_engagement; | |
| 618 gIndirectNavigationEnagagement = indirect_engagement; | |
| 619 } | |
| 620 | |
| 621 void AppBannerSettingsHelper::SetMinimumMinutesBetweenVisits( | |
| 622 unsigned int minutes) { | |
| 623 gMinimumMinutesBetweenVisits = minutes; | |
| 624 } | |
| 625 | |
| 626 void AppBannerSettingsHelper::SetTotalEngagementToTrigger( | 385 void AppBannerSettingsHelper::SetTotalEngagementToTrigger( |
| 627 double total_engagement) { | 386 double total_engagement) { |
| 628 gTotalEngagementToTrigger = total_engagement; | 387 gTotalEngagementToTrigger = total_engagement; |
| 629 } | 388 } |
| 630 | 389 |
| 631 void AppBannerSettingsHelper::SetDefaultParameters() { | 390 void AppBannerSettingsHelper::SetDefaultParameters() { |
| 632 SetDaysAfterDismissAndIgnoreToTrigger(kMinimumBannerBlockedToBannerShown, | |
| 633 kMinimumDaysBetweenBannerShows); | |
| 634 SetEngagementWeights(kDefaultDirectNavigationEngagement, | |
| 635 kDefaultIndirectNavigationEngagement); | |
| 636 SetMinimumMinutesBetweenVisits(kNumberOfMinutesInADay); | |
| 637 SetTotalEngagementToTrigger(kDefaultTotalEngagementToTrigger); | 391 SetTotalEngagementToTrigger(kDefaultTotalEngagementToTrigger); |
| 638 } | 392 } |
| 639 | 393 |
| 640 // Given a time, returns that time scoped to the nearest minute resolution | |
| 641 // locally. For example, if the resolution is one hour, this function will | |
| 642 // return the time to the closest (previous) hour in the local time zone. | |
| 643 base::Time AppBannerSettingsHelper::BucketTimeToResolution( | |
| 644 base::Time time, | |
| 645 unsigned int minutes) { | |
| 646 // Only support resolutions smaller than or equal to one day. Enforce | |
| 647 // that resolutions divide evenly into one day. Otherwise, default to a | |
| 648 // day resolution (each time converted to midnight local time). | |
| 649 if (minutes == 0 || minutes >= kNumberOfMinutesInADay || | |
| 650 kNumberOfMinutesInADay % minutes != 0) { | |
| 651 return time.LocalMidnight(); | |
| 652 } | |
| 653 | |
| 654 // Extract the number of minutes past midnight in local time. Divide that | |
| 655 // number by the resolution size, and return the time converted to local | |
| 656 // midnight with the resulting truncated number added. | |
| 657 base::Time::Exploded exploded; | |
| 658 time.LocalExplode(&exploded); | |
| 659 int total_minutes = exploded.hour * 60 + exploded.minute; | |
| 660 | |
| 661 // Use truncating integer division here. | |
| 662 return time.LocalMidnight() + | |
| 663 base::TimeDelta::FromMinutes((total_minutes / minutes) * minutes); | |
| 664 } | |
| 665 | |
| 666 void AppBannerSettingsHelper::UpdateFromFieldTrial() { | 394 void AppBannerSettingsHelper::UpdateFromFieldTrial() { |
| 667 // If we are using the site engagement score, only extract the total | 395 // If we are using the site engagement score, only extract the total |
| 668 // engagement to trigger from the params variations. | 396 // engagement to trigger from the params variations. |
| 669 UpdateDaysBetweenShowing(); | 397 UpdateDaysBetweenShowing(); |
| 670 if (ShouldUseSiteEngagementScore()) { | 398 UpdateSiteEngagementToTrigger(); |
| 671 UpdateSiteEngagementToTrigger(); | |
| 672 } else { | |
| 673 UpdateEngagementWeights(); | |
| 674 UpdateMinutesBetweenVisits(); | |
| 675 } | |
| 676 } | 399 } |
| 677 | 400 |
| 678 AppBannerSettingsHelper::LanguageOption | 401 AppBannerSettingsHelper::LanguageOption |
| 679 AppBannerSettingsHelper::GetHomescreenLanguageOption() { | 402 AppBannerSettingsHelper::GetHomescreenLanguageOption() { |
| 680 std::string param = variations::GetVariationParamValue( | 403 std::string param = variations::GetVariationParamValue( |
| 681 kBannerParamsKey, kBannerParamsLanguageKey); | 404 kBannerParamsKey, kBannerParamsLanguageKey); |
| 682 unsigned int language_option = 0; | 405 unsigned int language_option = 0; |
| 683 | 406 |
| 684 if (param.empty() || !base::StringToUint(param, &language_option) || | 407 if (param.empty() || !base::StringToUint(param, &language_option) || |
| 685 language_option < LANGUAGE_OPTION_MIN || | 408 language_option < LANGUAGE_OPTION_MIN || |
| 686 language_option > LANGUAGE_OPTION_MAX) { | 409 language_option > LANGUAGE_OPTION_MAX) { |
| 687 return LANGUAGE_OPTION_DEFAULT; | 410 return LANGUAGE_OPTION_DEFAULT; |
| 688 } | 411 } |
| 689 | 412 |
| 690 return static_cast<LanguageOption>(language_option); | 413 return static_cast<LanguageOption>(language_option); |
| 691 } | 414 } |
| 692 | |
| 693 bool AppBannerSettingsHelper::ShouldUseSiteEngagementScore() { | |
| 694 if (base::CommandLine::ForCurrentProcess()->HasSwitch( | |
| 695 switches::kEnableSiteEngagementAppBanner)) { | |
| 696 return true; | |
| 697 } | |
| 698 | |
| 699 // Assume any value which is not "0" or "false" indicates that we should use | |
| 700 // site engagement. | |
| 701 std::string param = variations::GetVariationParamValue( | |
| 702 kBannerParamsKey, kBannerSiteEngagementParamsKey); | |
| 703 | |
| 704 return (!param.empty() && param != "0" && param != "false"); | |
| 705 } | |
| OLD | NEW |