rlz.cc revision 731df977c0511bca2206b5f333555b1205ff1f43
1// Copyright (c) 2010 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 <windows.h>
12#include <process.h>
13
14#include <algorithm>
15
16#include "base/file_path.h"
17#include "base/message_loop.h"
18#include "base/path_service.h"
19#include "base/string_util.h"
20#include "base/task.h"
21#include "base/thread.h"
22#include "base/utf_string_conversions.h"
23#include "chrome/browser/browser_process.h"
24#include "chrome/browser/profile.h"
25#include "chrome/browser/profile_manager.h"
26#include "chrome/browser/search_engines/template_url.h"
27#include "chrome/browser/search_engines/template_url_model.h"
28#include "chrome/common/chrome_paths.h"
29#include "chrome/common/env_vars.h"
30#include "chrome/common/notification_registrar.h"
31#include "chrome/common/notification_service.h"
32#include "chrome/installer/util/google_update_settings.h"
33
34namespace {
35
36// The maximum length of an access points RLZ in wide chars.
37const DWORD kMaxRlzLength = 64;
38
39enum {
40  ACCESS_VALUES_STALE,      // Possibly new values available.
41  ACCESS_VALUES_FRESH       // The cached values are current.
42};
43
44// Tracks if we have tried and succeeded sending the ping. This helps us
45// decide if we need to refresh the some cached strings.
46volatile int access_values_state = ACCESS_VALUES_STALE;
47
48bool SendFinancialPing(const std::wstring& brand, const std::wstring& lang,
49                       const std::wstring& referral, bool exclude_id) {
50  rlz_lib::AccessPoint points[] = {rlz_lib::CHROME_OMNIBOX,
51                                   rlz_lib::CHROME_HOME_PAGE,
52                                   rlz_lib::NO_ACCESS_POINT};
53  std::string brand_ascii(WideToASCII(brand));
54  std::string lang_ascii(WideToASCII(lang));
55  std::string referral_ascii(WideToASCII(referral));
56
57  return rlz_lib::SendFinancialPing(rlz_lib::CHROME, points, "chrome",
58                                    brand_ascii.c_str(), referral_ascii.c_str(),
59                                    lang_ascii.c_str(), exclude_id, NULL, true);
60}
61
62// This class leverages the AutocompleteEditModel notification to know when
63// the user first interacted with the omnibox and set a global accordingly.
64class OmniBoxUsageObserver : public NotificationObserver {
65 public:
66  OmniBoxUsageObserver() {
67    registrar_.Add(this, NotificationType::OMNIBOX_OPENED_URL,
68                   NotificationService::AllSources());
69    // If instant is enabled we'll start searching as soon as the user starts
70    // typing in the omnibox (which triggers INSTANT_CONTROLLER_UPDATED).
71    registrar_.Add(this, NotificationType::INSTANT_CONTROLLER_UPDATED,
72                   NotificationService::AllSources());
73    omnibox_used_ = false;
74    DCHECK(!instance_);
75    instance_ = this;
76  }
77
78  virtual void Observe(NotificationType type,
79                       const NotificationSource& source,
80                       const NotificationDetails& details) {
81    // Try to record event now, else set the flag to try later when we
82    // attempt the ping.
83    if (!RLZTracker::RecordProductEvent(rlz_lib::CHROME,
84                                        rlz_lib::CHROME_OMNIBOX,
85                                        rlz_lib::FIRST_SEARCH))
86      omnibox_used_ = true;
87    delete this;
88  }
89
90  static bool used() {
91    return omnibox_used_;
92  }
93
94  // Deletes the single instance of OmniBoxUsageObserver.
95  static void DeleteInstance() {
96    delete instance_;
97  }
98
99 private:
100  // Dtor is private so the object cannot be created on the stack.
101  ~OmniBoxUsageObserver() {
102    instance_ = NULL;
103  }
104
105  static bool omnibox_used_;
106
107  // There should only be one instance created at a time, and instance_ points
108  // to that instance.
109  // NOTE: this is only non-null for the amount of time it is needed. Once the
110  // instance_ is no longer needed (or Chrome is exiting), this is null.
111  static OmniBoxUsageObserver* instance_;
112
113  NotificationRegistrar registrar_;
114};
115
116bool OmniBoxUsageObserver::omnibox_used_ = false;
117OmniBoxUsageObserver* OmniBoxUsageObserver::instance_ = NULL;
118
119// This task is run in the file thread, so to not block it for a long time
120// we use a throwaway thread to do the blocking url request.
121class DailyPingTask : public Task {
122 public:
123  virtual ~DailyPingTask() {
124  }
125  virtual void Run() {
126    // We use a transient thread because we have no guarantees about
127    // how long the RLZ lib can block us.
128    _beginthread(PingNow, 0, NULL);
129  }
130
131 private:
132  // Causes a ping to the server using WinInet.
133  static void _cdecl PingNow(void*) {
134    std::wstring lang;
135    GoogleUpdateSettings::GetLanguage(&lang);
136    if (lang.empty())
137      lang = L"en";
138    std::wstring brand;
139    GoogleUpdateSettings::GetBrand(&brand);
140    std::wstring referral;
141    GoogleUpdateSettings::GetReferral(&referral);
142    if (SendFinancialPing(brand, lang, referral, is_organic(brand))) {
143      access_values_state = ACCESS_VALUES_STALE;
144      GoogleUpdateSettings::ClearReferral();
145    }
146  }
147
148  // Organic brands all start with GG, such as GGCM.
149  static bool is_organic(const std::wstring& brand) {
150    return (brand.size() < 2) ? false : (brand.substr(0, 2) == L"GG");
151  }
152};
153
154// Performs late RLZ initialization and RLZ event recording for chrome.
155// This task needs to run on the UI thread.
156class DelayedInitTask : public Task {
157 public:
158  explicit DelayedInitTask(bool first_run)
159      : first_run_(first_run) {
160  }
161  virtual ~DelayedInitTask() {
162  }
163  virtual void Run() {
164    // For non-interactive tests we don't do the rest of the initialization
165    // because sometimes the very act of loading the dll causes QEMU to crash.
166    if (::GetEnvironmentVariableW(ASCIIToWide(env_vars::kHeadless).c_str(),
167                                  NULL, 0)) {
168      return;
169    }
170    // For organic brandcodes do not use rlz at all. Empty brandcode usually
171    // means a chromium install. This is ok.
172    std::wstring brand;
173    if (!GoogleUpdateSettings::GetBrand(&brand) || brand.empty() ||
174        GoogleUpdateSettings::IsOrganic(brand))
175      return;
176
177    // Do the initial event recording if is the first run or if we have an
178    // empty rlz which means we haven't got a chance to do it.
179    std::wstring omnibox_rlz;
180    RLZTracker::GetAccessPointRlz(rlz_lib::CHROME_OMNIBOX, &omnibox_rlz);
181
182    if (first_run_ || omnibox_rlz.empty()) {
183      // Record the installation of chrome.
184      RLZTracker::RecordProductEvent(rlz_lib::CHROME,
185                                     rlz_lib::CHROME_OMNIBOX,
186                                     rlz_lib::INSTALL);
187      RLZTracker::RecordProductEvent(rlz_lib::CHROME,
188                                     rlz_lib::CHROME_HOME_PAGE,
189                                     rlz_lib::INSTALL);
190      // Record if google is the initial search provider.
191      if (IsGoogleDefaultSearch()) {
192        RLZTracker::RecordProductEvent(rlz_lib::CHROME,
193                                       rlz_lib::CHROME_OMNIBOX,
194                                       rlz_lib::SET_TO_GOOGLE);
195      }
196    }
197    // Record first user interaction with the omnibox. We call this all the
198    // time but the rlz lib should ingore all but the first one.
199    if (OmniBoxUsageObserver::used()) {
200      RLZTracker::RecordProductEvent(rlz_lib::CHROME,
201                                     rlz_lib::CHROME_OMNIBOX,
202                                     rlz_lib::FIRST_SEARCH);
203    }
204    // Schedule the daily RLZ ping.
205    base::Thread* thread = g_browser_process->file_thread();
206    if (thread)
207      thread->message_loop()->PostTask(FROM_HERE, new DailyPingTask());
208  }
209
210 private:
211  bool IsGoogleDefaultSearch() {
212    if (!g_browser_process)
213      return false;
214    FilePath user_data_dir;
215    if (!PathService::Get(chrome::DIR_USER_DATA, &user_data_dir))
216      return false;
217    ProfileManager* profile_manager = g_browser_process->profile_manager();
218    Profile* profile = profile_manager->GetDefaultProfile(user_data_dir);
219    if (!profile)
220      return false;
221    const TemplateURL* url_template =
222        profile->GetTemplateURLModel()->GetDefaultSearchProvider();
223    if (!url_template)
224      return false;
225    const TemplateURLRef* urlref = url_template->url();
226    if (!urlref)
227      return false;
228    return urlref->HasGoogleBaseURLs();
229  }
230
231  bool first_run_;
232  DISALLOW_IMPLICIT_CONSTRUCTORS(DelayedInitTask);
233};
234
235}  // namespace
236
237bool RLZTracker::InitRlzDelayed(bool first_run, int delay) {
238  // Maximum and minimum delay we would allow to be set through master
239  // preferences. Somewhat arbitrary, may need to be adjusted in future.
240  const int kMaxDelay = 200 * 1000;
241  const int kMinDelay = 20 * 1000;
242
243  delay *= 1000;
244  delay = (delay < kMinDelay) ? kMinDelay : delay;
245  delay = (delay > kMaxDelay) ? kMaxDelay : delay;
246
247  if (!OmniBoxUsageObserver::used())
248    new OmniBoxUsageObserver();
249
250  // Schedule the delayed init items.
251  MessageLoop::current()->PostDelayedTask(FROM_HERE,
252      new DelayedInitTask(first_run), delay);
253  return true;
254}
255
256bool RLZTracker::RecordProductEvent(rlz_lib::Product product,
257                                    rlz_lib::AccessPoint point,
258                                    rlz_lib::Event event_id) {
259  return rlz_lib::RecordProductEvent(product, point, event_id);
260}
261
262bool RLZTracker::ClearAllProductEvents(rlz_lib::Product product) {
263  return rlz_lib::ClearAllProductEvents(product);
264}
265
266// We implement caching of the answer of get_access_point() if the request
267// is for CHROME_OMNIBOX. If we had a successful ping, then we update the
268// cached value.
269
270bool RLZTracker::GetAccessPointRlz(rlz_lib::AccessPoint point,
271                                   std::wstring* rlz) {
272  static std::wstring cached_ommibox_rlz;
273  if ((rlz_lib::CHROME_OMNIBOX == point) &&
274      (access_values_state == ACCESS_VALUES_FRESH)) {
275    *rlz = cached_ommibox_rlz;
276    return true;
277  }
278  char str_rlz[kMaxRlzLength + 1];
279  if (!rlz_lib::GetAccessPointRlz(point, str_rlz, rlz_lib::kMaxRlzLength, NULL))
280    return false;
281  *rlz = ASCIIToWide(std::string(str_rlz));
282  if (rlz_lib::CHROME_OMNIBOX == point) {
283    access_values_state = ACCESS_VALUES_FRESH;
284    cached_ommibox_rlz.assign(*rlz);
285  }
286  return true;
287}
288
289// static
290void RLZTracker::CleanupRlz() {
291  OmniBoxUsageObserver::DeleteInstance();
292}
293