1// Copyright (c) 2011 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 <process.h>
12#include <windows.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/synchronization/lock.h"
21#include "base/task.h"
22#include "base/threading/thread.h"
23#include "base/threading/thread_restrictions.h"
24#include "base/utf_string_conversions.h"
25#include "chrome/browser/browser_process.h"
26#include "chrome/browser/profiles/profile.h"
27#include "chrome/browser/profiles/profile_manager.h"
28#include "chrome/browser/search_engines/template_url.h"
29#include "chrome/browser/search_engines/template_url_model.h"
30#include "chrome/common/chrome_paths.h"
31#include "chrome/common/env_vars.h"
32#include "chrome/installer/util/google_update_settings.h"
33#include "content/browser/browser_thread.h"
34#include "content/common/notification_registrar.h"
35#include "content/common/notification_service.h"
36
37namespace {
38
39// The maximum length of an access points RLZ in wide chars.
40const DWORD kMaxRlzLength = 64;
41
42enum {
43  ACCESS_VALUES_STALE,      // Possibly new values available.
44  ACCESS_VALUES_FRESH       // The cached values are current.
45};
46
47// Tracks if we have tried and succeeded sending the ping. This helps us
48// decide if we need to refresh the some cached strings.
49volatile int access_values_state = ACCESS_VALUES_STALE;
50base::Lock rlz_lock;
51
52bool SendFinancialPing(const std::wstring& brand, const std::wstring& lang,
53                       const std::wstring& referral, bool exclude_id) {
54  rlz_lib::AccessPoint points[] = {rlz_lib::CHROME_OMNIBOX,
55                                   rlz_lib::CHROME_HOME_PAGE,
56                                   rlz_lib::NO_ACCESS_POINT};
57  std::string brand_ascii(WideToASCII(brand));
58  std::string lang_ascii(WideToASCII(lang));
59  std::string referral_ascii(WideToASCII(referral));
60
61  return rlz_lib::SendFinancialPing(rlz_lib::CHROME, points, "chrome",
62                                    brand_ascii.c_str(), referral_ascii.c_str(),
63                                    lang_ascii.c_str(), exclude_id, NULL, true);
64}
65
66// This class leverages the AutocompleteEditModel notification to know when
67// the user first interacted with the omnibox and set a global accordingly.
68class OmniBoxUsageObserver : public NotificationObserver {
69 public:
70  OmniBoxUsageObserver(bool first_run, bool send_ping_immediately)
71    : first_run_(first_run),
72      send_ping_immediately_(send_ping_immediately) {
73    registrar_.Add(this, NotificationType::OMNIBOX_OPENED_URL,
74                   NotificationService::AllSources());
75    // If instant is enabled we'll start searching as soon as the user starts
76    // typing in the omnibox (which triggers INSTANT_CONTROLLER_UPDATED).
77    registrar_.Add(this, NotificationType::INSTANT_CONTROLLER_UPDATED,
78                   NotificationService::AllSources());
79    omnibox_used_ = false;
80    DCHECK(!instance_);
81    instance_ = this;
82  }
83
84  virtual void Observe(NotificationType type,
85                       const NotificationSource& source,
86                       const NotificationDetails& details);
87
88  static bool used() {
89    return omnibox_used_;
90  }
91
92  // Deletes the single instance of OmniBoxUsageObserver.
93  static void DeleteInstance() {
94    delete instance_;
95  }
96
97 private:
98  // Dtor is private so the object cannot be created on the stack.
99  ~OmniBoxUsageObserver() {
100    instance_ = NULL;
101  }
102
103  static bool omnibox_used_;
104
105  // There should only be one instance created at a time, and instance_ points
106  // to that instance.
107  // NOTE: this is only non-null for the amount of time it is needed. Once the
108  // instance_ is no longer needed (or Chrome is exiting), this is null.
109  static OmniBoxUsageObserver* instance_;
110
111  NotificationRegistrar registrar_;
112  bool first_run_;
113  bool send_ping_immediately_;
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    // Needs to be evaluated. See http://crbug.com/62328.
135    base::ThreadRestrictions::ScopedAllowIO allow_io;
136
137    std::wstring lang;
138    GoogleUpdateSettings::GetLanguage(&lang);
139    if (lang.empty())
140      lang = L"en";
141    std::wstring brand;
142    GoogleUpdateSettings::GetBrand(&brand);
143    std::wstring referral;
144    GoogleUpdateSettings::GetReferral(&referral);
145    if (SendFinancialPing(brand, lang, referral, is_organic(brand))) {
146      base::AutoLock lock(rlz_lock);
147      access_values_state = ACCESS_VALUES_STALE;
148      GoogleUpdateSettings::ClearReferral();
149    }
150  }
151
152  // Organic brands all start with GG, such as GGCM.
153  static bool is_organic(const std::wstring& brand) {
154    return (brand.size() < 2) ? false : (brand.substr(0, 2) == L"GG");
155  }
156};
157
158// Performs late RLZ initialization and RLZ event recording for chrome.
159// This task needs to run on the UI thread.
160class DelayedInitTask : public Task {
161 public:
162  explicit DelayedInitTask(bool first_run)
163      : first_run_(first_run) {
164  }
165  virtual ~DelayedInitTask() {
166  }
167  virtual void Run() {
168    // For non-interactive tests we don't do the rest of the initialization
169    // because sometimes the very act of loading the dll causes QEMU to crash.
170    if (::GetEnvironmentVariableW(ASCIIToWide(env_vars::kHeadless).c_str(),
171                                  NULL, 0)) {
172      return;
173    }
174    // For organic brandcodes do not use rlz at all. Empty brandcode usually
175    // means a chromium install. This is ok.
176    std::wstring brand;
177    if (!GoogleUpdateSettings::GetBrand(&brand) || brand.empty() ||
178        GoogleUpdateSettings::IsOrganic(brand))
179      return;
180
181    // Do the initial event recording if is the first run or if we have an
182    // empty rlz which means we haven't got a chance to do it.
183    std::wstring omnibox_rlz;
184    RLZTracker::GetAccessPointRlz(rlz_lib::CHROME_OMNIBOX, &omnibox_rlz);
185
186    if ((first_run_ || omnibox_rlz.empty()) && !already_ran_) {
187      already_ran_ = true;
188
189      // Record the installation of chrome.
190      RLZTracker::RecordProductEvent(rlz_lib::CHROME,
191                                     rlz_lib::CHROME_OMNIBOX,
192                                     rlz_lib::INSTALL);
193      RLZTracker::RecordProductEvent(rlz_lib::CHROME,
194                                     rlz_lib::CHROME_HOME_PAGE,
195                                     rlz_lib::INSTALL);
196      // Record if google is the initial search provider.
197      if (IsGoogleDefaultSearch()) {
198        RLZTracker::RecordProductEvent(rlz_lib::CHROME,
199                                       rlz_lib::CHROME_OMNIBOX,
200                                       rlz_lib::SET_TO_GOOGLE);
201      }
202    }
203    // Record first user interaction with the omnibox. We call this all the
204    // time but the rlz lib should ingore all but the first one.
205    if (OmniBoxUsageObserver::used()) {
206      RLZTracker::RecordProductEvent(rlz_lib::CHROME,
207                                     rlz_lib::CHROME_OMNIBOX,
208                                     rlz_lib::FIRST_SEARCH);
209    }
210    // Schedule the daily RLZ ping.
211    MessageLoop::current()->PostTask(FROM_HERE, new DailyPingTask());
212  }
213
214 private:
215  bool IsGoogleDefaultSearch() {
216    if (!g_browser_process)
217      return false;
218    FilePath user_data_dir;
219    if (!PathService::Get(chrome::DIR_USER_DATA, &user_data_dir))
220      return false;
221    ProfileManager* profile_manager = g_browser_process->profile_manager();
222    Profile* profile = profile_manager->GetDefaultProfile(user_data_dir);
223    if (!profile)
224      return false;
225    const TemplateURL* url_template =
226        profile->GetTemplateURLModel()->GetDefaultSearchProvider();
227    if (!url_template)
228      return false;
229    const TemplateURLRef* urlref = url_template->url();
230    if (!urlref)
231      return false;
232    return urlref->HasGoogleBaseURLs();
233  }
234
235  // Flag that remembers if the delayed task already ran or not.  This is
236  // needed only in the first_run case, since we don't want to record the
237  // set-to-google event more than once.  We need to worry about this event
238  // (and not the others) because it is not a stateful RLZ event.
239  static bool already_ran_;
240
241  bool first_run_;
242  DISALLOW_IMPLICIT_CONSTRUCTORS(DelayedInitTask);
243};
244
245bool DelayedInitTask::already_ran_ = false;
246
247void OmniBoxUsageObserver::Observe(NotificationType type,
248                                   const NotificationSource& source,
249                                   const NotificationDetails& details) {
250  // Needs to be evaluated. See http://crbug.com/62328.
251  base::ThreadRestrictions::ScopedAllowIO allow_io;
252
253  // Try to record event now, else set the flag to try later when we
254  // attempt the ping.
255  if (!RLZTracker::RecordProductEvent(rlz_lib::CHROME,
256                                      rlz_lib::CHROME_OMNIBOX,
257                                      rlz_lib::FIRST_SEARCH))
258    omnibox_used_ = true;
259  else if (send_ping_immediately_) {
260    BrowserThread::PostTask(
261        BrowserThread::FILE, FROM_HERE, new DelayedInitTask(first_run_));
262  }
263
264  delete this;
265}
266
267}  // namespace
268
269bool RLZTracker::InitRlzDelayed(bool first_run, int delay) {
270  // A negative delay means that a financial ping should be sent immediately
271  // after a first search is recorded, without waiting for the next restart
272  // of chrome.  However, we only want this behaviour on first run.
273  bool send_ping_immediately = false;
274  if (delay < 0) {
275    send_ping_immediately = true;
276    delay = -delay;
277  }
278
279  // Maximum and minimum delay we would allow to be set through master
280  // preferences. Somewhat arbitrary, may need to be adjusted in future.
281  const int kMaxDelay = 200 * 1000;
282  const int kMinDelay = 20 * 1000;
283
284  delay *= 1000;
285  delay = (delay < kMinDelay) ? kMinDelay : delay;
286  delay = (delay > kMaxDelay) ? kMaxDelay : delay;
287
288  if (!OmniBoxUsageObserver::used())
289    new OmniBoxUsageObserver(first_run, send_ping_immediately);
290
291  // Schedule the delayed init items.
292  BrowserThread::PostDelayedTask(
293      BrowserThread::FILE, FROM_HERE, new DelayedInitTask(first_run), delay);
294  return true;
295}
296
297bool RLZTracker::RecordProductEvent(rlz_lib::Product product,
298                                    rlz_lib::AccessPoint point,
299                                    rlz_lib::Event event_id) {
300  return rlz_lib::RecordProductEvent(product, point, event_id);
301}
302
303bool RLZTracker::ClearAllProductEvents(rlz_lib::Product product) {
304  return rlz_lib::ClearAllProductEvents(product);
305}
306
307// We implement caching of the answer of get_access_point() if the request
308// is for CHROME_OMNIBOX. If we had a successful ping, then we update the
309// cached value.
310
311bool RLZTracker::GetAccessPointRlz(rlz_lib::AccessPoint point,
312                                   std::wstring* rlz) {
313  static std::wstring cached_ommibox_rlz;
314  if (rlz_lib::CHROME_OMNIBOX == point) {
315    base::AutoLock lock(rlz_lock);
316    if (access_values_state == ACCESS_VALUES_FRESH) {
317      *rlz = cached_ommibox_rlz;
318      return true;
319    }
320  }
321
322  // Make sure we don't access disk outside of the file context.
323  // In such case we repost the task on the right thread and return error.
324  if (!BrowserThread::CurrentlyOn(BrowserThread::FILE)) {
325    // Caching of access points is now only implemented for the CHROME_OMNIBOX.
326    // Thus it is not possible to call this function on another thread for
327    // other access points until proper caching for these has been implemented
328    // and the code that calls this function can handle synchronous fetching
329    // of the access point.
330    DCHECK_EQ(rlz_lib::CHROME_OMNIBOX, point);
331
332    BrowserThread::PostTask(
333        BrowserThread::FILE, FROM_HERE,
334        NewRunnableFunction(&RLZTracker::GetAccessPointRlz,
335                            point, &cached_ommibox_rlz));
336      rlz->erase();
337      return false;
338  }
339
340  char str_rlz[kMaxRlzLength + 1];
341  if (!rlz_lib::GetAccessPointRlz(point, str_rlz, rlz_lib::kMaxRlzLength, NULL))
342    return false;
343  *rlz = ASCIIToWide(std::string(str_rlz));
344  if (rlz_lib::CHROME_OMNIBOX == point) {
345    base::AutoLock lock(rlz_lock);
346    cached_ommibox_rlz.assign(*rlz);
347    access_values_state = ACCESS_VALUES_FRESH;
348  }
349  return true;
350}
351
352// static
353void RLZTracker::CleanupRlz() {
354  OmniBoxUsageObserver::DeleteInstance();
355}
356