- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / rlz / rlz.cc
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.
4 //
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.
8
9 #include "chrome/browser/rlz/rlz.h"
10
11 #include <algorithm>
12
13 #include "base/bind.h"
14 #include "base/command_line.h"
15 #include "base/debug/trace_event.h"
16 #include "base/message_loop/message_loop.h"
17 #include "base/prefs/pref_service.h"
18 #include "base/strings/string_util.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "chrome/browser/browser_process.h"
21 #include "chrome/browser/chrome_notification_types.h"
22 #include "chrome/browser/google/google_util.h"
23 #include "chrome/browser/prefs/session_startup_pref.h"
24 #include "chrome/browser/search_engines/template_url.h"
25 #include "chrome/browser/search_engines/template_url_service.h"
26 #include "chrome/browser/search_engines/template_url_service_factory.h"
27 #include "chrome/browser/ui/startup/startup_browser_creator.h"
28 #include "chrome/common/chrome_switches.h"
29 #include "chrome/common/pref_names.h"
30 #include "content/public/browser/browser_thread.h"
31 #include "content/public/browser/navigation_entry.h"
32 #include "content/public/browser/notification_service.h"
33 #include "net/http/http_util.h"
34
35 #if defined(OS_WIN)
36 #include "chrome/installer/util/google_update_settings.h"
37 #else
38 namespace GoogleUpdateSettings {
39 static bool GetLanguage(string16* language) {
40   // TODO(thakis): Implement.
41   NOTIMPLEMENTED();
42   return false;
43 }
44
45 // The referral program is defunct and not used. No need to implement these
46 // functions on non-Win platforms.
47 static bool GetReferral(string16* referral) {
48   return true;
49 }
50 static bool ClearReferral() {
51   return true;
52 }
53 }  // namespace GoogleUpdateSettings
54 #endif
55
56 using content::BrowserThread;
57 using content::NavigationEntry;
58
59 namespace {
60
61 // Maximum and minimum delay for financial ping we would allow to be set through
62 // master preferences. Somewhat arbitrary, may need to be adjusted in future.
63 const base::TimeDelta kMaxInitDelay = base::TimeDelta::FromSeconds(200);
64 const base::TimeDelta kMinInitDelay = base::TimeDelta::FromSeconds(20);
65
66 bool IsBrandOrganic(const std::string& brand) {
67   return brand.empty() || google_util::IsOrganic(brand);
68 }
69
70 void RecordProductEvents(bool first_run,
71                          bool is_google_default_search,
72                          bool is_google_homepage,
73                          bool is_google_in_startpages,
74                          bool already_ran,
75                          bool omnibox_used,
76                          bool homepage_used) {
77   TRACE_EVENT0("RLZ", "RecordProductEvents");
78   // Record the installation of chrome. We call this all the time but the rlz
79   // lib should ignore all but the first one.
80   rlz_lib::RecordProductEvent(rlz_lib::CHROME,
81                               RLZTracker::CHROME_OMNIBOX,
82                               rlz_lib::INSTALL);
83   rlz_lib::RecordProductEvent(rlz_lib::CHROME,
84                               RLZTracker::CHROME_HOME_PAGE,
85                               rlz_lib::INSTALL);
86
87   if (!already_ran) {
88     // Do the initial event recording if is the first run or if we have an
89     // empty rlz which means we haven't got a chance to do it.
90     char omnibox_rlz[rlz_lib::kMaxRlzLength + 1];
91     if (!rlz_lib::GetAccessPointRlz(RLZTracker::CHROME_OMNIBOX, omnibox_rlz,
92                                     rlz_lib::kMaxRlzLength)) {
93       omnibox_rlz[0] = 0;
94     }
95
96     // Record if google is the initial search provider and/or home page.
97     if ((first_run || omnibox_rlz[0] == 0) && is_google_default_search) {
98       rlz_lib::RecordProductEvent(rlz_lib::CHROME,
99                                   RLZTracker::CHROME_OMNIBOX,
100                                   rlz_lib::SET_TO_GOOGLE);
101     }
102
103     char homepage_rlz[rlz_lib::kMaxRlzLength + 1];
104     if (!rlz_lib::GetAccessPointRlz(RLZTracker::CHROME_HOME_PAGE, homepage_rlz,
105                                     rlz_lib::kMaxRlzLength)) {
106       homepage_rlz[0] = 0;
107     }
108
109     if ((first_run || homepage_rlz[0] == 0) &&
110         (is_google_homepage || is_google_in_startpages)) {
111       rlz_lib::RecordProductEvent(rlz_lib::CHROME,
112                                   RLZTracker::CHROME_HOME_PAGE,
113                                   rlz_lib::SET_TO_GOOGLE);
114     }
115   }
116
117   // Record first user interaction with the omnibox. We call this all the
118   // time but the rlz lib should ingore all but the first one.
119   if (omnibox_used) {
120     rlz_lib::RecordProductEvent(rlz_lib::CHROME,
121                                 RLZTracker::CHROME_OMNIBOX,
122                                 rlz_lib::FIRST_SEARCH);
123   }
124
125   // Record first user interaction with the home page. We call this all the
126   // time but the rlz lib should ingore all but the first one.
127   if (homepage_used || is_google_in_startpages) {
128     rlz_lib::RecordProductEvent(rlz_lib::CHROME,
129                                 RLZTracker::CHROME_HOME_PAGE,
130                                 rlz_lib::FIRST_SEARCH);
131   }
132 }
133
134 bool SendFinancialPing(const std::string& brand,
135                        const string16& lang,
136                        const string16& referral) {
137   rlz_lib::AccessPoint points[] = {RLZTracker::CHROME_OMNIBOX,
138                                    RLZTracker::CHROME_HOME_PAGE,
139                                    rlz_lib::NO_ACCESS_POINT};
140   std::string lang_ascii(UTF16ToASCII(lang));
141   std::string referral_ascii(UTF16ToASCII(referral));
142   std::string product_signature;
143 #if defined(OS_CHROMEOS)
144   product_signature = "chromeos";
145 #else
146   product_signature = "chrome";
147 #endif
148   return rlz_lib::SendFinancialPing(rlz_lib::CHROME, points,
149                                     product_signature.c_str(),
150                                     brand.c_str(), referral_ascii.c_str(),
151                                     lang_ascii.c_str(), false, true);
152 }
153
154 }  // namespace
155
156 #if defined(OS_WIN)
157 // static
158 const rlz_lib::AccessPoint RLZTracker::CHROME_OMNIBOX =
159     rlz_lib::CHROME_OMNIBOX;
160 // static
161 const rlz_lib::AccessPoint RLZTracker::CHROME_HOME_PAGE =
162     rlz_lib::CHROME_HOME_PAGE;
163 #elif defined(OS_IOS)
164 // static
165 const rlz_lib::AccessPoint RLZTracker::CHROME_OMNIBOX =
166     rlz_lib::CHROME_IOS_OMNIBOX;
167 // static
168 const rlz_lib::AccessPoint RLZTracker::CHROME_HOME_PAGE =
169     rlz_lib::CHROME_IOS_HOME_PAGE;
170 #elif defined(OS_MACOSX)
171 // static
172 const rlz_lib::AccessPoint RLZTracker::CHROME_OMNIBOX =
173     rlz_lib::CHROME_MAC_OMNIBOX;
174 // static
175 const rlz_lib::AccessPoint RLZTracker::CHROME_HOME_PAGE =
176     rlz_lib::CHROME_MAC_HOME_PAGE;
177 #elif defined(OS_CHROMEOS)
178 // static
179 const rlz_lib::AccessPoint RLZTracker::CHROME_OMNIBOX =
180     rlz_lib::CHROMEOS_OMNIBOX;
181 // static
182 const rlz_lib::AccessPoint RLZTracker::CHROME_HOME_PAGE =
183     rlz_lib::CHROMEOS_HOME_PAGE;
184 #endif
185
186 RLZTracker* RLZTracker::tracker_ = NULL;
187
188 // static
189 RLZTracker* RLZTracker::GetInstance() {
190   return tracker_ ? tracker_ : Singleton<RLZTracker>::get();
191 }
192
193 RLZTracker::RLZTracker()
194     : first_run_(false),
195       send_ping_immediately_(false),
196       is_google_default_search_(false),
197       is_google_homepage_(false),
198       is_google_in_startpages_(false),
199       worker_pool_token_(BrowserThread::GetBlockingPool()->GetSequenceToken()),
200       already_ran_(false),
201       omnibox_used_(false),
202       homepage_used_(false),
203       min_init_delay_(kMinInitDelay) {
204 }
205
206 RLZTracker::~RLZTracker() {
207 }
208
209 // static
210 bool RLZTracker::InitRlzDelayed(bool first_run,
211                                 bool send_ping_immediately,
212                                 base::TimeDelta delay,
213                                 bool is_google_default_search,
214                                 bool is_google_homepage,
215                                 bool is_google_in_startpages) {
216   return GetInstance()->Init(first_run, send_ping_immediately, delay,
217                              is_google_default_search, is_google_homepage,
218                              is_google_in_startpages);
219 }
220
221 // static
222 bool RLZTracker::InitRlzFromProfileDelayed(Profile* profile,
223                                            bool first_run,
224                                            bool send_ping_immediately,
225                                            base::TimeDelta delay) {
226   bool is_google_default_search = false;
227   TemplateURLService* template_url_service =
228       TemplateURLServiceFactory::GetForProfile(profile);
229   if (template_url_service) {
230     const TemplateURL* url_template =
231         template_url_service->GetDefaultSearchProvider();
232     is_google_default_search =
233         url_template && url_template->url_ref().HasGoogleBaseURLs();
234   }
235
236   PrefService* pref_service = profile->GetPrefs();
237   bool is_google_homepage = google_util::IsGoogleHomePageUrl(
238       GURL(pref_service->GetString(prefs::kHomePage)));
239
240   bool is_google_in_startpages = false;
241 #if !defined(OS_IOS)
242   // iOS does not have a notion of startpages.
243   SessionStartupPref session_startup_prefs =
244       StartupBrowserCreator::GetSessionStartupPref(
245           *CommandLine::ForCurrentProcess(), profile);
246   if (session_startup_prefs.type == SessionStartupPref::URLS) {
247     is_google_in_startpages =
248         std::count_if(session_startup_prefs.urls.begin(),
249                       session_startup_prefs.urls.end(),
250                       google_util::IsGoogleHomePageUrl) > 0;
251   }
252 #endif
253
254   if (!InitRlzDelayed(first_run, send_ping_immediately, delay,
255                       is_google_default_search, is_google_homepage,
256                       is_google_in_startpages)) {
257     return false;
258   }
259
260   // Prime the RLZ cache for the home page access point so that its avaiable
261   // for the startup page if needed (i.e., when the startup page is set to
262   // the home page).
263   GetAccessPointRlz(CHROME_HOME_PAGE, NULL);
264
265   return true;
266 }
267
268 bool RLZTracker::Init(bool first_run,
269                       bool send_ping_immediately,
270                       base::TimeDelta delay,
271                       bool is_google_default_search,
272                       bool is_google_homepage,
273                       bool is_google_in_startpages) {
274   first_run_ = first_run;
275   is_google_default_search_ = is_google_default_search;
276   is_google_homepage_ = is_google_homepage;
277   is_google_in_startpages_ = is_google_in_startpages;
278   send_ping_immediately_ = send_ping_immediately;
279
280   // Enable zero delays for testing.
281   if (CommandLine::ForCurrentProcess()->HasSwitch(::switches::kTestType))
282     EnableZeroDelayForTesting();
283
284   delay = std::min(kMaxInitDelay, std::max(min_init_delay_, delay));
285
286   if (google_util::GetBrand(&brand_) && !IsBrandOrganic(brand_)) {
287     // Register for notifications from the omnibox so that we can record when
288     // the user performs a first search.
289     registrar_.Add(this, chrome::NOTIFICATION_OMNIBOX_OPENED_URL,
290                    content::NotificationService::AllSources());
291
292     // Register for notifications from navigations, to see if the user has used
293     // the home page.
294     registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_PENDING,
295                    content::NotificationService::AllSources());
296   }
297   google_util::GetReactivationBrand(&reactivation_brand_);
298
299   net::URLRequestContextGetter* context_getter =
300       g_browser_process->system_request_context();
301
302   // Could be NULL; don't run if so.  RLZ will try again next restart.
303   if (context_getter) {
304     rlz_lib::SetURLRequestContext(context_getter);
305     ScheduleDelayedInit(delay);
306   }
307
308   return true;
309 }
310
311 void RLZTracker::ScheduleDelayedInit(base::TimeDelta delay) {
312   // The RLZTracker is a singleton object that outlives any runnable tasks
313   // that will be queued up.
314   BrowserThread::GetBlockingPool()->PostDelayedSequencedWorkerTask(
315       worker_pool_token_,
316       FROM_HERE,
317       base::Bind(&RLZTracker::DelayedInit, base::Unretained(this)),
318       delay);
319 }
320
321 void RLZTracker::DelayedInit() {
322   bool schedule_ping = false;
323
324   // For organic brandcodes do not use rlz at all. Empty brandcode usually
325   // means a chromium install. This is ok.
326   if (!IsBrandOrganic(brand_)) {
327     RecordProductEvents(first_run_, is_google_default_search_,
328                         is_google_homepage_, is_google_in_startpages_,
329                         already_ran_, omnibox_used_, homepage_used_);
330     schedule_ping = true;
331   }
332
333   // If chrome has been reactivated, record the events for this brand
334   // as well.
335   if (!IsBrandOrganic(reactivation_brand_)) {
336     rlz_lib::SupplementaryBranding branding(reactivation_brand_.c_str());
337     RecordProductEvents(first_run_, is_google_default_search_,
338                         is_google_homepage_, is_google_in_startpages_,
339                         already_ran_, omnibox_used_, homepage_used_);
340     schedule_ping = true;
341   }
342
343   already_ran_ = true;
344
345   if (schedule_ping)
346     ScheduleFinancialPing();
347 }
348
349 void RLZTracker::ScheduleFinancialPing() {
350   BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior(
351       worker_pool_token_,
352       FROM_HERE,
353       base::Bind(&RLZTracker::PingNowImpl, base::Unretained(this)),
354       base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
355 }
356
357 void RLZTracker::PingNowImpl() {
358   TRACE_EVENT0("RLZ", "RLZTracker::PingNowImpl");
359   string16 lang;
360   GoogleUpdateSettings::GetLanguage(&lang);
361   if (lang.empty())
362     lang = ASCIIToUTF16("en");
363   string16 referral;
364   GoogleUpdateSettings::GetReferral(&referral);
365
366   if (!IsBrandOrganic(brand_) && SendFinancialPing(brand_, lang, referral)) {
367     GoogleUpdateSettings::ClearReferral();
368
369     {
370       base::AutoLock lock(cache_lock_);
371       rlz_cache_.clear();
372     }
373
374     // Prime the RLZ cache for the access points we are interested in.
375     GetAccessPointRlz(RLZTracker::CHROME_OMNIBOX, NULL);
376     GetAccessPointRlz(RLZTracker::CHROME_HOME_PAGE, NULL);
377   }
378
379   if (!IsBrandOrganic(reactivation_brand_)) {
380     rlz_lib::SupplementaryBranding branding(reactivation_brand_.c_str());
381     SendFinancialPing(reactivation_brand_, lang, referral);
382   }
383 }
384
385 bool RLZTracker::SendFinancialPing(const std::string& brand,
386                                    const string16& lang,
387                                    const string16& referral) {
388   return ::SendFinancialPing(brand, lang, referral);
389 }
390
391 void RLZTracker::Observe(int type,
392                          const content::NotificationSource& source,
393                          const content::NotificationDetails& details) {
394   switch (type) {
395     case chrome::NOTIFICATION_OMNIBOX_OPENED_URL:
396       RecordFirstSearch(CHROME_OMNIBOX);
397       registrar_.Remove(this, chrome::NOTIFICATION_OMNIBOX_OPENED_URL,
398                         content::NotificationService::AllSources());
399       break;
400     case content::NOTIFICATION_NAV_ENTRY_PENDING: {
401       const NavigationEntry* entry =
402           content::Details<content::NavigationEntry>(details).ptr();
403       if (entry != NULL &&
404           ((entry->GetTransitionType() &
405             content::PAGE_TRANSITION_HOME_PAGE) != 0)) {
406         RecordFirstSearch(CHROME_HOME_PAGE);
407         registrar_.Remove(this, content::NOTIFICATION_NAV_ENTRY_PENDING,
408                           content::NotificationService::AllSources());
409       }
410       break;
411     }
412     default:
413       NOTREACHED();
414       break;
415   }
416 }
417
418 // static
419 bool RLZTracker::RecordProductEvent(rlz_lib::Product product,
420                                     rlz_lib::AccessPoint point,
421                                     rlz_lib::Event event_id) {
422   return GetInstance()->RecordProductEventImpl(product, point, event_id);
423 }
424
425 bool RLZTracker::RecordProductEventImpl(rlz_lib::Product product,
426                                         rlz_lib::AccessPoint point,
427                                         rlz_lib::Event event_id) {
428   // Make sure we don't access disk outside of the I/O thread.
429   // In such case we repost the task on the right thread and return error.
430   if (ScheduleRecordProductEvent(product, point, event_id))
431     return true;
432
433   bool ret = rlz_lib::RecordProductEvent(product, point, event_id);
434
435   // If chrome has been reactivated, record the event for this brand as well.
436   if (!reactivation_brand_.empty()) {
437     rlz_lib::SupplementaryBranding branding(reactivation_brand_.c_str());
438     ret &= rlz_lib::RecordProductEvent(product, point, event_id);
439   }
440
441   return ret;
442 }
443
444 bool RLZTracker::ScheduleRecordProductEvent(rlz_lib::Product product,
445                                             rlz_lib::AccessPoint point,
446                                             rlz_lib::Event event_id) {
447   if (!BrowserThread::CurrentlyOn(BrowserThread::UI))
448     return false;
449
450   BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior(
451       worker_pool_token_,
452       FROM_HERE,
453       base::Bind(base::IgnoreResult(&RLZTracker::RecordProductEvent),
454                  product, point, event_id),
455       base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
456
457   return true;
458 }
459
460 void RLZTracker::RecordFirstSearch(rlz_lib::AccessPoint point) {
461   // Make sure we don't access disk outside of the I/O thread.
462   // In such case we repost the task on the right thread and return error.
463   if (ScheduleRecordFirstSearch(point))
464     return;
465
466   bool* record_used = point == CHROME_OMNIBOX ?
467       &omnibox_used_ : &homepage_used_;
468
469   // Try to record event now, else set the flag to try later when we
470   // attempt the ping.
471   if (!RecordProductEvent(rlz_lib::CHROME, point, rlz_lib::FIRST_SEARCH))
472     *record_used = true;
473   else if (send_ping_immediately_ && point == CHROME_OMNIBOX)
474     ScheduleDelayedInit(base::TimeDelta());
475 }
476
477 bool RLZTracker::ScheduleRecordFirstSearch(rlz_lib::AccessPoint point) {
478   if (!BrowserThread::CurrentlyOn(BrowserThread::UI))
479     return false;
480   BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior(
481       worker_pool_token_,
482       FROM_HERE,
483       base::Bind(&RLZTracker::RecordFirstSearch,
484                  base::Unretained(this), point),
485       base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
486   return true;
487 }
488
489 // static
490 std::string RLZTracker::GetAccessPointHttpHeader(rlz_lib::AccessPoint point) {
491   TRACE_EVENT0("RLZ", "RLZTracker::GetAccessPointHttpHeader");
492   std::string extra_headers;
493   string16 rlz_string;
494   RLZTracker::GetAccessPointRlz(point, &rlz_string);
495   if (!rlz_string.empty()) {
496     net::HttpUtil::AppendHeaderIfMissing("X-Rlz-String",
497                                          UTF16ToUTF8(rlz_string),
498                                          &extra_headers);
499   }
500
501   return extra_headers;
502 }
503
504 // GetAccessPointRlz() caches RLZ strings for all access points. If we had
505 // a successful ping, then we update the cached value.
506 bool RLZTracker::GetAccessPointRlz(rlz_lib::AccessPoint point,
507                                    string16* rlz) {
508   TRACE_EVENT0("RLZ", "RLZTracker::GetAccessPointRlz");
509   return GetInstance()->GetAccessPointRlzImpl(point, rlz);
510 }
511
512 // GetAccessPointRlz() caches RLZ strings for all access points. If we had
513 // a successful ping, then we update the cached value.
514 bool RLZTracker::GetAccessPointRlzImpl(rlz_lib::AccessPoint point,
515                                        string16* rlz) {
516   // If the RLZ string for the specified access point is already cached,
517   // simply return its value.
518   {
519     base::AutoLock lock(cache_lock_);
520     if (rlz_cache_.find(point) != rlz_cache_.end()) {
521       if (rlz)
522         *rlz = rlz_cache_[point];
523       return true;
524     }
525   }
526
527   // Make sure we don't access disk outside of the I/O thread.
528   // In such case we repost the task on the right thread and return error.
529   if (ScheduleGetAccessPointRlz(point))
530     return false;
531
532   char str_rlz[rlz_lib::kMaxRlzLength + 1];
533   if (!rlz_lib::GetAccessPointRlz(point, str_rlz, rlz_lib::kMaxRlzLength))
534     return false;
535
536   string16 rlz_local(ASCIIToUTF16(std::string(str_rlz)));
537   if (rlz)
538     *rlz = rlz_local;
539
540   base::AutoLock lock(cache_lock_);
541   rlz_cache_[point] = rlz_local;
542   return true;
543 }
544
545 bool RLZTracker::ScheduleGetAccessPointRlz(rlz_lib::AccessPoint point) {
546   if (!BrowserThread::CurrentlyOn(BrowserThread::UI))
547     return false;
548
549   string16* not_used = NULL;
550   BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior(
551       worker_pool_token_,
552       FROM_HERE,
553       base::Bind(base::IgnoreResult(&RLZTracker::GetAccessPointRlz), point,
554                  not_used),
555       base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
556   return true;
557 }
558
559 #if defined(OS_CHROMEOS)
560 // static
561 void RLZTracker::ClearRlzState() {
562   GetInstance()->ClearRlzStateImpl();
563 }
564
565 void RLZTracker::ClearRlzStateImpl() {
566   if (ScheduleClearRlzState())
567     return;
568   rlz_lib::ClearAllProductEvents(rlz_lib::CHROME);
569 }
570
571 bool RLZTracker::ScheduleClearRlzState() {
572   if (!BrowserThread::CurrentlyOn(BrowserThread::UI))
573     return false;
574
575   BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior(
576       worker_pool_token_,
577       FROM_HERE,
578       base::Bind(&RLZTracker::ClearRlzStateImpl,
579                  base::Unretained(this)),
580       base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
581   return true;
582 }
583 #endif
584
585 // static
586 void RLZTracker::CleanupRlz() {
587   GetInstance()->rlz_cache_.clear();
588   GetInstance()->registrar_.RemoveAll();
589   rlz_lib::SetURLRequestContext(NULL);
590 }
591
592 // static
593 void RLZTracker::EnableZeroDelayForTesting() {
594   GetInstance()->min_init_delay_ = base::TimeDelta();
595 }