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