1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 // This code glues the RLZ library DLL with Chrome. It allows Chrome to work
6 // with or without the DLL being present. If the DLL is not present the
7 // functions do nothing and just return false.
9 #include "components/rlz/rlz_tracker.h"
14 #include "base/bind.h"
15 #include "base/message_loop/message_loop.h"
16 #include "base/sequenced_task_runner.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/stringprintf.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/task_scheduler/post_task.h"
21 #include "base/task_scheduler/task_traits.h"
22 #include "base/trace_event/trace_event.h"
23 #include "build/build_config.h"
24 #include "components/rlz/rlz_tracker_delegate.h"
26 #if defined(OS_CHROMEOS)
27 #include "base/syslog_logging.h"
33 // Maximum and minimum delay for financial ping we would allow to be set through
34 // master preferences. Somewhat arbitrary, may need to be adjusted in future.
35 #if defined(OS_CHROMEOS)
36 const base::TimeDelta kMaxInitDelay = base::TimeDelta::FromHours(24);
38 const base::TimeDelta kMaxInitDelay = base::TimeDelta::FromSeconds(200);
40 const base::TimeDelta kMinInitDelay = base::TimeDelta::FromSeconds(20);
42 void RecordProductEvents(bool first_run,
43 bool is_google_default_search,
44 bool is_google_homepage,
45 bool is_google_in_startpages,
50 TRACE_EVENT0("RLZ", "RecordProductEvents");
51 // Record the installation of chrome. We call this all the time but the rlz
52 // lib should ignore all but the first one.
53 rlz_lib::RecordProductEvent(rlz_lib::CHROME,
54 RLZTracker::ChromeOmnibox(),
57 rlz_lib::RecordProductEvent(rlz_lib::CHROME,
58 RLZTracker::ChromeHomePage(),
60 rlz_lib::RecordProductEvent(rlz_lib::CHROME,
61 RLZTracker::ChromeAppList(),
63 #endif // !defined(OS_IOS)
66 // Do the initial event recording if is the first run or if we have an
67 // empty rlz which means we haven't got a chance to do it.
68 char omnibox_rlz[rlz_lib::kMaxRlzLength + 1];
69 if (!rlz_lib::GetAccessPointRlz(RLZTracker::ChromeOmnibox(), omnibox_rlz,
70 rlz_lib::kMaxRlzLength)) {
74 // Record if google is the initial search provider and/or home page.
75 if ((first_run || omnibox_rlz[0] == 0) && is_google_default_search) {
76 rlz_lib::RecordProductEvent(rlz_lib::CHROME,
77 RLZTracker::ChromeOmnibox(),
78 rlz_lib::SET_TO_GOOGLE);
82 char homepage_rlz[rlz_lib::kMaxRlzLength + 1];
83 if (!rlz_lib::GetAccessPointRlz(RLZTracker::ChromeHomePage(), homepage_rlz,
84 rlz_lib::kMaxRlzLength)) {
88 if ((first_run || homepage_rlz[0] == 0) &&
89 (is_google_homepage || is_google_in_startpages)) {
90 rlz_lib::RecordProductEvent(rlz_lib::CHROME,
91 RLZTracker::ChromeHomePage(),
92 rlz_lib::SET_TO_GOOGLE);
95 char app_list_rlz[rlz_lib::kMaxRlzLength + 1];
96 if (!rlz_lib::GetAccessPointRlz(RLZTracker::ChromeAppList(), app_list_rlz,
97 rlz_lib::kMaxRlzLength)) {
101 // Record if google is the initial search provider and/or home page.
102 if ((first_run || app_list_rlz[0] == 0) && is_google_default_search) {
103 rlz_lib::RecordProductEvent(rlz_lib::CHROME,
104 RLZTracker::ChromeAppList(),
105 rlz_lib::SET_TO_GOOGLE);
107 #endif // !defined(OS_IOS)
110 // Record first user interaction with the omnibox. We call this all the
111 // time but the rlz lib should ingore all but the first one.
113 rlz_lib::RecordProductEvent(rlz_lib::CHROME,
114 RLZTracker::ChromeOmnibox(),
115 rlz_lib::FIRST_SEARCH);
119 // Record first user interaction with the home page. We call this all the
120 // time but the rlz lib should ingore all but the first one.
121 if (homepage_used || is_google_in_startpages) {
122 rlz_lib::RecordProductEvent(rlz_lib::CHROME,
123 RLZTracker::ChromeHomePage(),
124 rlz_lib::FIRST_SEARCH);
127 // Record first user interaction with the app list. We call this all the
128 // time but the rlz lib should ingore all but the first one.
130 rlz_lib::RecordProductEvent(rlz_lib::CHROME,
131 RLZTracker::ChromeAppList(),
132 rlz_lib::FIRST_SEARCH);
134 #endif // !defined(OS_IOS)
137 bool SendFinancialPing(const std::string& brand,
138 const base::string16& lang,
139 const base::string16& referral) {
140 rlz_lib::AccessPoint points[] = {RLZTracker::ChromeOmnibox(),
142 RLZTracker::ChromeHomePage(),
143 RLZTracker::ChromeAppList(),
145 rlz_lib::NO_ACCESS_POINT};
146 std::string lang_ascii(base::UTF16ToASCII(lang));
147 std::string referral_ascii(base::UTF16ToASCII(referral));
148 std::string product_signature;
149 #if defined(OS_CHROMEOS)
150 product_signature = "chromeos";
152 product_signature = "chrome";
154 return rlz_lib::SendFinancialPing(rlz_lib::CHROME, points,
155 product_signature.c_str(),
156 brand.c_str(), referral_ascii.c_str(),
157 lang_ascii.c_str(), false, true);
162 RLZTracker* RLZTracker::tracker_ = nullptr;
165 RLZTracker* RLZTracker::GetInstance() {
166 return tracker_ ? tracker_ : base::Singleton<RLZTracker>::get();
169 RLZTracker::RLZTracker()
171 send_ping_immediately_(false),
172 is_google_default_search_(false),
173 is_google_homepage_(false),
174 is_google_in_startpages_(false),
176 omnibox_used_(false),
177 homepage_used_(false),
178 app_list_used_(false),
179 min_init_delay_(kMinInitDelay),
180 background_task_runner_(base::CreateSequencedTaskRunnerWithTraits(
181 {base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN, base::MayBlock(),
182 base::TaskPriority::BACKGROUND})) {
183 DETACH_FROM_SEQUENCE(sequence_checker_);
186 RLZTracker::~RLZTracker() {
190 void RLZTracker::SetRlzDelegate(std::unique_ptr<RLZTrackerDelegate> delegate) {
191 RLZTracker* tracker = GetInstance();
192 if (!tracker->delegate_) {
193 // RLZTracker::SetRlzDelegate is called at Profile creation time which can
194 // happens multiple time on ChromeOS, so do nothing if the delegate already
196 tracker->SetDelegate(std::move(delegate));
200 void RLZTracker::SetDelegate(std::unique_ptr<RLZTrackerDelegate> delegate) {
203 delegate_ = std::move(delegate);
207 bool RLZTracker::InitRlzDelayed(bool first_run,
208 bool send_ping_immediately,
209 base::TimeDelta delay,
210 bool is_google_default_search,
211 bool is_google_homepage,
212 bool is_google_in_startpages) {
213 return GetInstance()->Init(first_run, send_ping_immediately, delay,
214 is_google_default_search, is_google_homepage,
215 is_google_in_startpages);
218 bool RLZTracker::Init(bool first_run,
219 bool send_ping_immediately,
220 base::TimeDelta delay,
221 bool is_google_default_search,
222 bool is_google_homepage,
223 bool is_google_in_startpages) {
224 DCHECK(delegate_) << "RLZTracker used before initialization";
225 first_run_ = first_run;
226 is_google_default_search_ = is_google_default_search;
227 is_google_homepage_ = is_google_homepage;
228 is_google_in_startpages_ = is_google_in_startpages;
229 send_ping_immediately_ = send_ping_immediately;
231 // Enable zero delays for testing.
232 if (delegate_->ShouldEnableZeroDelayForTesting())
233 EnableZeroDelayForTesting();
235 delay = std::min(kMaxInitDelay, std::max(min_init_delay_, delay));
237 if (delegate_->GetBrand(&brand_) && !delegate_->IsBrandOrganic(brand_)) {
238 // Register for notifications from the omnibox so that we can record when
239 // the user performs a first search.
240 delegate_->SetOmniboxSearchCallback(
241 base::Bind(&RLZTracker::RecordFirstSearch, base::Unretained(this),
245 // Register for notifications from navigations, to see if the user has used
247 delegate_->SetHomepageSearchCallback(
248 base::Bind(&RLZTracker::RecordFirstSearch, base::Unretained(this),
252 delegate_->GetReactivationBrand(&reactivation_brand_);
254 #if defined(OS_CHROMEOS)
255 // If the brand is organic, RLZ is essentially disabled. Write a log to the
256 // console for administrators and QA.
257 if (delegate_->IsBrandOrganic(brand_) &&
258 delegate_->IsBrandOrganic(reactivation_brand_)) {
259 SYSLOG(INFO) << "RLZ is disabled";
263 // Could be null; don't run if so. RLZ will try again next restart.
264 net::URLRequestContextGetter* context_getter = delegate_->GetRequestContext();
265 if (context_getter) {
266 rlz_lib::SetURLRequestContext(context_getter);
267 ScheduleDelayedInit(delay);
271 // Prime the RLZ cache for the home page access point so that its avaiable
272 // for the startup page if needed (i.e., when the startup page is set to
274 GetAccessPointRlz(ChromeHomePage(), nullptr);
275 #endif // !defined(OS_IOS)
280 void RLZTracker::Cleanup() {
283 delegate_->Cleanup();
286 void RLZTracker::ScheduleDelayedInit(base::TimeDelta delay) {
287 DCHECK(delegate_) << "RLZTracker used before initialization";
288 // The RLZTracker is a singleton object that outlives any runnable tasks
289 // that will be queued up.
290 background_task_runner_->PostDelayedTask(
291 FROM_HERE, base::Bind(&RLZTracker::DelayedInit, base::Unretained(this)),
295 void RLZTracker::DelayedInit() {
296 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
297 DCHECK(delegate_) << "RLZTracker used before initialization";
298 bool schedule_ping = false;
300 // For organic brandcodes do not use rlz at all. Empty brandcode usually
301 // means a chromium install. This is ok.
302 if (!delegate_->IsBrandOrganic(brand_)) {
303 RecordProductEvents(first_run_, is_google_default_search_,
304 is_google_homepage_, is_google_in_startpages_,
305 already_ran_, omnibox_used_, homepage_used_,
307 schedule_ping = true;
310 // If chrome has been reactivated, record the events for this brand
312 if (!delegate_->IsBrandOrganic(reactivation_brand_)) {
313 rlz_lib::SupplementaryBranding branding(reactivation_brand_.c_str());
314 RecordProductEvents(first_run_, is_google_default_search_,
315 is_google_homepage_, is_google_in_startpages_,
316 already_ran_, omnibox_used_, homepage_used_,
318 schedule_ping = true;
324 ScheduleFinancialPing();
327 void RLZTracker::ScheduleFinancialPing() {
328 DCHECK(delegate_) << "RLZTracker used before initialization";
329 background_task_runner_->PostTask(
330 FROM_HERE, base::Bind(&RLZTracker::PingNowImpl, base::Unretained(this)));
333 void RLZTracker::PingNowImpl() {
334 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
335 DCHECK(delegate_) << "RLZTracker used before initialization";
336 TRACE_EVENT0("RLZ", "RLZTracker::PingNowImpl");
338 delegate_->GetLanguage(&lang);
340 lang = base::ASCIIToUTF16("en");
341 base::string16 referral;
342 delegate_->GetReferral(&referral);
344 if (!delegate_->IsBrandOrganic(brand_) &&
345 SendFinancialPing(brand_, lang, referral)) {
346 delegate_->ClearReferral();
349 base::AutoLock lock(cache_lock_);
353 // Prime the RLZ cache for the access points we are interested in.
354 GetAccessPointRlz(RLZTracker::ChromeOmnibox(), nullptr);
356 GetAccessPointRlz(RLZTracker::ChromeHomePage(), nullptr);
357 GetAccessPointRlz(RLZTracker::ChromeAppList(), nullptr);
358 #endif // !defined(OS_IOS)
361 if (!delegate_->IsBrandOrganic(reactivation_brand_)) {
362 rlz_lib::SupplementaryBranding branding(reactivation_brand_.c_str());
363 SendFinancialPing(reactivation_brand_, lang, referral);
367 bool RLZTracker::SendFinancialPing(const std::string& brand,
368 const base::string16& lang,
369 const base::string16& referral) {
370 return ::rlz::SendFinancialPing(brand, lang, referral);
374 bool RLZTracker::RecordProductEvent(rlz_lib::Product product,
375 rlz_lib::AccessPoint point,
376 rlz_lib::Event event_id) {
377 // This method is called during unit tests while the RLZTracker has not been
378 // initialized, so check for the presence of a delegate and exit if there is
380 RLZTracker* tracker = GetInstance();
381 return !tracker->delegate_ ? false : tracker->RecordProductEventImpl(
382 product, point, event_id);
385 bool RLZTracker::RecordProductEventImpl(rlz_lib::Product product,
386 rlz_lib::AccessPoint point,
387 rlz_lib::Event event_id) {
388 DCHECK(delegate_) << "RLZTracker used before initialization";
389 // Make sure we don't access disk outside of the I/O thread.
390 // In such case we repost the task on the right thread and return error.
391 if (ScheduleRecordProductEvent(product, point, event_id))
394 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
396 bool ret = rlz_lib::RecordProductEvent(product, point, event_id);
398 // If chrome has been reactivated, record the event for this brand as well.
399 if (!reactivation_brand_.empty()) {
400 rlz_lib::SupplementaryBranding branding(reactivation_brand_.c_str());
401 ret &= rlz_lib::RecordProductEvent(product, point, event_id);
407 bool RLZTracker::ScheduleRecordProductEvent(rlz_lib::Product product,
408 rlz_lib::AccessPoint point,
409 rlz_lib::Event event_id) {
410 DCHECK(delegate_) << "RLZTracker used before initialization";
411 if (!delegate_->IsOnUIThread())
414 background_task_runner_->PostTask(
415 FROM_HERE, base::Bind(base::IgnoreResult(&RLZTracker::RecordProductEvent),
416 product, point, event_id));
420 void RLZTracker::RecordFirstSearch(rlz_lib::AccessPoint point) {
421 DCHECK(delegate_) << "RLZTracker used before initialization";
422 // Make sure we don't access disk outside of the I/O thread.
423 // In such case we repost the task on the right thread and return error.
424 if (ScheduleRecordFirstSearch(point))
427 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
429 bool* record_used = GetAccessPointRecord(point);
431 // Try to record event now, else set the flag to try later when we
433 if (!RecordProductEvent(rlz_lib::CHROME, point, rlz_lib::FIRST_SEARCH)) {
435 } else if (send_ping_immediately_ && point == ChromeOmnibox()) {
436 ScheduleDelayedInit(base::TimeDelta());
440 bool RLZTracker::ScheduleRecordFirstSearch(rlz_lib::AccessPoint point) {
441 DCHECK(delegate_) << "RLZTracker used before initialization";
442 if (!delegate_->IsOnUIThread())
444 background_task_runner_->PostTask(FROM_HERE,
445 base::Bind(&RLZTracker::RecordFirstSearch,
446 base::Unretained(this), point));
450 bool* RLZTracker::GetAccessPointRecord(rlz_lib::AccessPoint point) {
451 if (point == ChromeOmnibox())
452 return &omnibox_used_;
454 if (point == ChromeHomePage())
455 return &homepage_used_;
456 if (point == ChromeAppList())
457 return &app_list_used_;
458 #endif // !defined(OS_IOS)
464 std::string RLZTracker::GetAccessPointHttpHeader(rlz_lib::AccessPoint point) {
465 TRACE_EVENT0("RLZ", "RLZTracker::GetAccessPointHttpHeader");
466 std::string extra_headers;
467 base::string16 rlz_string;
468 RLZTracker::GetAccessPointRlz(point, &rlz_string);
469 if (!rlz_string.empty()) {
470 return base::StringPrintf("X-Rlz-String: %s\r\n",
471 base::UTF16ToUTF8(rlz_string).c_str());
474 return extra_headers;
477 // GetAccessPointRlz() caches RLZ strings for all access points. If we had
478 // a successful ping, then we update the cached value.
480 bool RLZTracker::GetAccessPointRlz(rlz_lib::AccessPoint point,
481 base::string16* rlz) {
482 // This method is called during unit tests while the RLZTracker has not been
483 // initialized, so check for the presence of a delegate and exit if there is
485 TRACE_EVENT0("RLZ", "RLZTracker::GetAccessPointRlz");
486 RLZTracker* tracker = GetInstance();
487 return !tracker->delegate_ ? false
488 : tracker->GetAccessPointRlzImpl(point, rlz);
491 // GetAccessPointRlz() caches RLZ strings for all access points. If we had
492 // a successful ping, then we update the cached value.
493 bool RLZTracker::GetAccessPointRlzImpl(rlz_lib::AccessPoint point,
494 base::string16* rlz) {
495 DCHECK(delegate_) << "RLZTracker used before initialization";
496 // If the RLZ string for the specified access point is already cached,
497 // simply return its value.
499 base::AutoLock lock(cache_lock_);
500 if (rlz_cache_.find(point) != rlz_cache_.end()) {
502 *rlz = rlz_cache_[point];
507 // Make sure we don't access disk outside of the I/O thread.
508 // In such case we repost the task on the right thread and return error.
509 if (ScheduleGetAccessPointRlz(point))
512 char str_rlz[rlz_lib::kMaxRlzLength + 1];
513 if (!rlz_lib::GetAccessPointRlz(point, str_rlz, rlz_lib::kMaxRlzLength))
516 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
518 base::string16 rlz_local(base::ASCIIToUTF16(str_rlz));
522 base::AutoLock lock(cache_lock_);
523 rlz_cache_[point] = rlz_local;
527 bool RLZTracker::ScheduleGetAccessPointRlz(rlz_lib::AccessPoint point) {
528 DCHECK(delegate_) << "RLZTracker used before initialization";
529 if (!delegate_->IsOnUIThread())
532 base::string16* not_used = nullptr;
533 background_task_runner_->PostTask(
534 FROM_HERE, base::Bind(base::IgnoreResult(&RLZTracker::GetAccessPointRlz),
539 #if defined(OS_CHROMEOS)
541 void RLZTracker::ClearRlzState() {
542 RLZTracker* tracker = GetInstance();
543 if (tracker->delegate_)
544 tracker->ClearRlzStateImpl();
547 void RLZTracker::ClearRlzStateImpl() {
548 DCHECK(delegate_) << "RLZTracker used before initialization";
549 if (ScheduleClearRlzState())
552 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
553 rlz_lib::ClearAllProductEvents(rlz_lib::CHROME);
556 bool RLZTracker::ScheduleClearRlzState() {
557 DCHECK(delegate_) << "RLZTracker used before initialization";
558 if (!delegate_->IsOnUIThread())
561 background_task_runner_->PostTask(
563 base::Bind(&RLZTracker::ClearRlzStateImpl, base::Unretained(this)));
569 void RLZTracker::CleanupRlz() {
570 GetInstance()->Cleanup();
571 rlz_lib::SetURLRequestContext(nullptr);
575 void RLZTracker::EnableZeroDelayForTesting() {
576 GetInstance()->min_init_delay_ = base::TimeDelta();
581 void RLZTracker::RecordAppListSearch() {
582 // This method is called during unit tests while the RLZTracker has not been
583 // initialized, so check for the presence of a delegate and exit if there is
585 RLZTracker* tracker = GetInstance();
586 if (tracker->delegate_)
587 tracker->RecordFirstSearch(RLZTracker::ChromeAppList());