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#include "chrome/browser/first_run/first_run.h"
6
7#include "base/command_line.h"
8#include "base/compiler_specific.h"
9#include "base/file_util.h"
10#include "base/path_service.h"
11#include "base/utf_string_conversions.h"
12#include "build/build_config.h"
13#include "chrome/browser/browser_process.h"
14#include "chrome/browser/first_run/first_run_dialog.h"
15#include "chrome/browser/first_run/first_run_import_observer.h"
16#include "chrome/browser/importer/external_process_importer_host.h"
17#include "chrome/browser/importer/importer_host.h"
18#include "chrome/browser/importer/importer_list.h"
19#include "chrome/browser/importer/importer_progress_dialog.h"
20#include "chrome/browser/importer/importer_progress_observer.h"
21#include "chrome/browser/metrics/user_metrics.h"
22#include "chrome/browser/prefs/pref_service.h"
23#include "chrome/browser/process_singleton.h"
24#include "chrome/browser/profiles/profile_manager.h"
25#include "chrome/browser/search_engines/template_url_model.h"
26#include "chrome/browser/shell_integration.h"
27#include "chrome/common/chrome_paths.h"
28#include "chrome/common/chrome_switches.h"
29#include "chrome/common/pref_names.h"
30#include "chrome/installer/util/master_preferences.h"
31#include "chrome/installer/util/master_preferences_constants.h"
32#include "chrome/installer/util/util_constants.h"
33#include "googleurl/src/gurl.h"
34
35#if defined(OS_WIN)
36// TODO(port): move more code in back from the first_run_win.cc module.
37#include "chrome/installer/util/google_update_settings.h"
38#include "chrome/installer/util/install_util.h"
39#endif
40
41namespace {
42
43// The kSentinelFile file absence will tell us it is a first run.
44const char kSentinelFile[] = "First Run";
45
46FilePath GetDefaultPrefFilePath(bool create_profile_dir,
47                                const FilePath& user_data_dir) {
48  FilePath default_pref_dir =
49      ProfileManager::GetDefaultProfileDir(user_data_dir);
50  if (create_profile_dir) {
51    if (!file_util::PathExists(default_pref_dir)) {
52      if (!file_util::CreateDirectory(default_pref_dir))
53        return FilePath();
54    }
55  }
56  return ProfileManager::GetProfilePrefsPath(default_pref_dir);
57}
58
59}  // namespace
60
61// FirstRun -------------------------------------------------------------------
62
63FirstRun::FirstRunState FirstRun::first_run_ = FIRST_RUN_UNKNOWN;
64
65FirstRun::MasterPrefs::MasterPrefs()
66    : ping_delay(0),
67      homepage_defined(false),
68      do_import_items(0),
69      dont_import_items(0),
70      run_search_engine_experiment(false),
71      randomize_search_engine_experiment(false),
72      make_chrome_default(false) {
73}
74
75FirstRun::MasterPrefs::~MasterPrefs() {}
76
77// TODO(port): Import switches need to be ported to both Mac and Linux. Not all
78// import switches here are implemented for Linux. None are implemented for Mac
79// (as this function will not be called on Mac).
80int FirstRun::ImportNow(Profile* profile, const CommandLine& cmdline) {
81  int return_code = true;
82  if (cmdline.HasSwitch(switches::kImportFromFile)) {
83    // Silently import preset bookmarks from file.
84    // This is an OEM scenario.
85    return_code = ImportFromFile(profile, cmdline);
86  }
87  if (cmdline.HasSwitch(switches::kImport)) {
88#if defined(OS_WIN)
89    return_code = ImportFromBrowser(profile, cmdline);
90#else
91    NOTIMPLEMENTED();
92#endif
93  }
94  return return_code;
95}
96
97// static
98bool FirstRun::ProcessMasterPreferences(const FilePath& user_data_dir,
99                                        MasterPrefs* out_prefs) {
100  DCHECK(!user_data_dir.empty());
101
102  // The standard location of the master prefs is next to the chrome binary.
103  FilePath master_prefs;
104  if (!PathService::Get(base::DIR_EXE, &master_prefs))
105    return true;
106  master_prefs = master_prefs.AppendASCII(installer::kDefaultMasterPrefs);
107
108  installer::MasterPreferences prefs(master_prefs);
109  if (!prefs.read_from_file())
110    return true;
111
112  out_prefs->new_tabs = prefs.GetFirstRunTabs();
113
114  bool value = false;
115
116#if defined(OS_WIN)
117  // RLZ is currently a Windows-only phenomenon.  When it comes to the Mac/
118  // Linux, enable it here.
119  if (!prefs.GetInt(installer::master_preferences::kDistroPingDelay,
120                    &out_prefs->ping_delay)) {
121    // 90 seconds is the default that we want to use in case master
122    // preferences is missing, corrupt or ping_delay is missing.
123    out_prefs->ping_delay = 90;
124  }
125
126  if (prefs.GetBool(installer::master_preferences::kRequireEula, &value) &&
127      value) {
128    // Show the post-installation EULA. This is done by setup.exe and the
129    // result determines if we continue or not. We wait here until the user
130    // dismisses the dialog.
131
132    // The actual eula text is in a resource in chrome. We extract it to
133    // a text file so setup.exe can use it as an inner frame.
134    FilePath inner_html;
135    if (WriteEULAtoTempFile(&inner_html)) {
136      int retcode = 0;
137      if (!LaunchSetupWithParam(installer::switches::kShowEula,
138                                inner_html.value(), &retcode) ||
139          (retcode == installer::EULA_REJECTED)) {
140        LOG(WARNING) << "EULA rejected. Fast exit.";
141        ::ExitProcess(1);
142      }
143      if (retcode == installer::EULA_ACCEPTED) {
144        VLOG(1) << "EULA : no collection";
145        GoogleUpdateSettings::SetCollectStatsConsent(false);
146      } else if (retcode == installer::EULA_ACCEPTED_OPT_IN) {
147        VLOG(1) << "EULA : collection consent";
148        GoogleUpdateSettings::SetCollectStatsConsent(true);
149      }
150    }
151  }
152#endif
153
154  if (prefs.GetBool(installer::master_preferences::kAltFirstRunBubble,
155                    &value) && value) {
156    FirstRun::SetOEMFirstRunBubblePref();
157  }
158
159  FilePath user_prefs = GetDefaultPrefFilePath(true, user_data_dir);
160  if (user_prefs.empty())
161    return true;
162
163  // The master prefs are regular prefs so we can just copy the file
164  // to the default place and they just work.
165  if (!file_util::CopyFile(master_prefs, user_prefs))
166    return true;
167
168#if defined(OS_WIN)
169  DictionaryValue* extensions = 0;
170  if (prefs.GetExtensionsBlock(&extensions)) {
171    VLOG(1) << "Extensions block found in master preferences";
172    DoDelayedInstallExtensions();
173  }
174#endif
175
176  if (prefs.GetBool(installer::master_preferences::kDistroImportSearchPref,
177                    &value)) {
178    if (value) {
179      out_prefs->do_import_items |= importer::SEARCH_ENGINES;
180    } else {
181      out_prefs->dont_import_items |= importer::SEARCH_ENGINES;
182    }
183  }
184
185  // Check to see if search engine logos should be randomized.
186  if (prefs.GetBool(
187          installer::master_preferences::
188              kSearchEngineExperimentRandomizePref,
189          &value) && value) {
190    out_prefs->randomize_search_engine_experiment = true;
191  }
192
193  // If we're suppressing the first-run bubble, set that preference now.
194  // Otherwise, wait until the user has completed first run to set it, so the
195  // user is guaranteed to see the bubble iff he or she has completed the first
196  // run process.
197  if (prefs.GetBool(
198          installer::master_preferences::kDistroSuppressFirstRunBubble,
199          &value) && value)
200    FirstRun::SetShowFirstRunBubblePref(false);
201
202  if (prefs.GetBool(
203          installer::master_preferences::kDistroImportHistoryPref,
204          &value)) {
205    if (value) {
206      out_prefs->do_import_items |= importer::HISTORY;
207    } else {
208      out_prefs->dont_import_items |= importer::HISTORY;
209    }
210  }
211
212  std::string not_used;
213  out_prefs->homepage_defined = prefs.GetString(prefs::kHomePage, &not_used);
214
215  if (prefs.GetBool(
216          installer::master_preferences::kDistroImportHomePagePref,
217          &value)) {
218    if (value) {
219      out_prefs->do_import_items |= importer::HOME_PAGE;
220    } else {
221      out_prefs->dont_import_items |= importer::HOME_PAGE;
222    }
223  }
224
225  // Bookmarks are never imported unless specifically turned on.
226  if (prefs.GetBool(
227          installer::master_preferences::kDistroImportBookmarksPref,
228          &value) && value) {
229    out_prefs->do_import_items |= importer::FAVORITES;
230  }
231
232  if (prefs.GetBool(
233          installer::master_preferences::kMakeChromeDefaultForUser,
234          &value) && value) {
235    out_prefs->make_chrome_default = true;
236  }
237
238  // TODO(mirandac): Refactor skip-first-run-ui process into regular first run
239  // import process.  http://crbug.com/49647
240  // Note we are skipping all other master preferences if skip-first-run-ui
241  // is *not* specified. (That is, we continue only if skipping first run ui.)
242  if (!prefs.GetBool(
243          installer::master_preferences::kDistroSkipFirstRunPref,
244          &value) || !value) {
245    return true;
246  }
247
248#if !defined(OS_WIN)
249  // From here on we won't show first run so we need to do the work to show the
250  // bubble anyway, unless it's already been explicitly suppressed.
251  FirstRun::SetShowFirstRunBubblePref(true);
252#endif
253
254  // We need to be able to create the first run sentinel or else we cannot
255  // proceed because ImportSettings will launch the importer process which
256  // would end up here if the sentinel is not present.
257  if (!FirstRun::CreateSentinel())
258    return false;
259
260  if (prefs.GetBool(installer::master_preferences::kDistroShowWelcomePage,
261                    &value) && value) {
262    FirstRun::SetShowWelcomePagePref();
263  }
264
265  std::string import_bookmarks_path;
266  prefs.GetString(
267      installer::master_preferences::kDistroImportBookmarksFromFilePref,
268      &import_bookmarks_path);
269
270#if defined(OS_WIN)
271  if (!IsOrganicFirstRun()) {
272    // If search engines aren't explicitly imported, don't import.
273    if (!(out_prefs->do_import_items & importer::SEARCH_ENGINES)) {
274      out_prefs->dont_import_items |= importer::SEARCH_ENGINES;
275    }
276    // If home page isn't explicitly imported, don't import.
277    if (!(out_prefs->do_import_items & importer::HOME_PAGE)) {
278      out_prefs->dont_import_items |= importer::HOME_PAGE;
279    }
280    // If history isn't explicitly forbidden, do import.
281    if (!(out_prefs->dont_import_items & importer::HISTORY)) {
282      out_prefs->do_import_items |= importer::HISTORY;
283    }
284  }
285
286  if (out_prefs->do_import_items || !import_bookmarks_path.empty()) {
287    // There is something to import from the default browser. This launches
288    // the importer process and blocks until done or until it fails.
289    scoped_refptr<ImporterList> importer_list(new ImporterList);
290    importer_list->DetectSourceProfilesHack();
291    if (!FirstRun::ImportSettings(NULL,
292          importer_list->GetSourceProfileAt(0).importer_type,
293          out_prefs->do_import_items,
294          FilePath::FromWStringHack(UTF8ToWide(import_bookmarks_path)),
295          true, NULL)) {
296      LOG(WARNING) << "silent import failed";
297    }
298  }
299#else
300  if (!import_bookmarks_path.empty()) {
301    // There are bookmarks to import from a file.
302    FilePath path = FilePath::FromWStringHack(UTF8ToWide(
303        import_bookmarks_path));
304    if (!FirstRun::ImportBookmarks(path)) {
305      LOG(WARNING) << "silent bookmark import failed";
306    }
307  }
308#endif
309
310  // Even on the first run we only allow for the user choice to take effect if
311  // no policy has been set by the admin.
312  if (!g_browser_process->local_state()->IsManagedPreference(
313          prefs::kDefaultBrowserSettingEnabled)) {
314    if (prefs.GetBool(
315            installer::master_preferences::kMakeChromeDefaultForUser,
316            &value) && value) {
317      ShellIntegration::SetAsDefaultBrowser();
318    }
319  } else {
320    if (g_browser_process->local_state()->GetBoolean(
321            prefs::kDefaultBrowserSettingEnabled)) {
322      ShellIntegration::SetAsDefaultBrowser();
323    }
324  }
325
326  return false;
327}
328
329// static
330bool FirstRun::IsChromeFirstRun() {
331  if (first_run_ != FIRST_RUN_UNKNOWN)
332    return first_run_ == FIRST_RUN_TRUE;
333
334  FilePath first_run_sentinel;
335  if (!GetFirstRunSentinelFilePath(&first_run_sentinel) ||
336      file_util::PathExists(first_run_sentinel)) {
337    first_run_ = FIRST_RUN_FALSE;
338    return false;
339  }
340  first_run_ = FIRST_RUN_TRUE;
341  return true;
342}
343
344// static
345bool FirstRun::RemoveSentinel() {
346  FilePath first_run_sentinel;
347  if (!GetFirstRunSentinelFilePath(&first_run_sentinel))
348    return false;
349  return file_util::Delete(first_run_sentinel, false);
350}
351
352// static
353bool FirstRun::CreateSentinel() {
354  FilePath first_run_sentinel;
355  if (!GetFirstRunSentinelFilePath(&first_run_sentinel))
356    return false;
357  return file_util::WriteFile(first_run_sentinel, "", 0) != -1;
358}
359
360// static
361bool FirstRun::SetShowFirstRunBubblePref(bool show_bubble) {
362  PrefService* local_state = g_browser_process->local_state();
363  if (!local_state)
364    return false;
365  if (!local_state->FindPreference(prefs::kShouldShowFirstRunBubble)) {
366    local_state->RegisterBooleanPref(prefs::kShouldShowFirstRunBubble, false);
367    local_state->SetBoolean(prefs::kShouldShowFirstRunBubble, show_bubble);
368  }
369  return true;
370}
371
372// static
373bool FirstRun::SetShowWelcomePagePref() {
374  PrefService* local_state = g_browser_process->local_state();
375  if (!local_state)
376    return false;
377  if (!local_state->FindPreference(prefs::kShouldShowWelcomePage)) {
378    local_state->RegisterBooleanPref(prefs::kShouldShowWelcomePage, false);
379    local_state->SetBoolean(prefs::kShouldShowWelcomePage, true);
380  }
381  return true;
382}
383
384// static
385bool FirstRun::SetPersonalDataManagerFirstRunPref() {
386  PrefService* local_state = g_browser_process->local_state();
387  if (!local_state)
388    return false;
389  if (!local_state->FindPreference(
390          prefs::kAutofillPersonalDataManagerFirstRun)) {
391    local_state->RegisterBooleanPref(
392        prefs::kAutofillPersonalDataManagerFirstRun, false);
393    local_state->SetBoolean(prefs::kAutofillPersonalDataManagerFirstRun, true);
394  }
395  return true;
396}
397
398// static
399bool FirstRun::SearchEngineSelectorDisallowed() {
400  // For now, the only case in which the search engine dialog should never be
401  // shown is if the locale is Russia.
402  std::string locale = g_browser_process->GetApplicationLocale();
403  return (locale == "ru");
404}
405
406// static
407bool FirstRun::SetOEMFirstRunBubblePref() {
408  PrefService* local_state = g_browser_process->local_state();
409  if (!local_state)
410    return false;
411  if (!local_state->FindPreference(prefs::kShouldUseOEMFirstRunBubble)) {
412    local_state->RegisterBooleanPref(prefs::kShouldUseOEMFirstRunBubble,
413                                     false);
414    local_state->SetBoolean(prefs::kShouldUseOEMFirstRunBubble, true);
415  }
416  return true;
417}
418
419// static
420bool FirstRun::SetMinimalFirstRunBubblePref() {
421  PrefService* local_state = g_browser_process->local_state();
422  if (!local_state)
423    return false;
424  if (!local_state->FindPreference(prefs::kShouldUseMinimalFirstRunBubble)) {
425    local_state->RegisterBooleanPref(prefs::kShouldUseMinimalFirstRunBubble,
426                                     false);
427    local_state->SetBoolean(prefs::kShouldUseMinimalFirstRunBubble, true);
428  }
429  return true;
430}
431
432// static
433int FirstRun::ImportFromFile(Profile* profile, const CommandLine& cmdline) {
434  FilePath file_path = cmdline.GetSwitchValuePath(switches::kImportFromFile);
435  if (file_path.empty()) {
436    NOTREACHED();
437    return false;
438  }
439  scoped_refptr<ImporterHost> importer_host(new ImporterHost);
440  importer_host->set_headless();
441
442  importer::SourceProfile source_profile;
443  source_profile.importer_type = importer::BOOKMARKS_HTML;
444  source_profile.source_path = file_path;
445
446  FirstRunImportObserver importer_observer;
447  importer::ShowImportProgressDialog(NULL,
448                                     importer::FAVORITES,
449                                     importer_host,
450                                     &importer_observer,
451                                     source_profile,
452                                     profile,
453                                     true);
454
455  importer_observer.RunLoop();
456  return importer_observer.import_result();
457}
458
459// static
460bool FirstRun::GetFirstRunSentinelFilePath(FilePath* path) {
461  FilePath first_run_sentinel;
462
463#if defined(OS_WIN)
464  FilePath exe_path;
465  if (!PathService::Get(base::DIR_EXE, &exe_path))
466    return false;
467  if (InstallUtil::IsPerUserInstall(exe_path.value().c_str())) {
468    first_run_sentinel = exe_path;
469  } else {
470    if (!PathService::Get(chrome::DIR_USER_DATA, &first_run_sentinel))
471      return false;
472  }
473#else
474  if (!PathService::Get(chrome::DIR_USER_DATA, &first_run_sentinel))
475    return false;
476#endif
477
478  *path = first_run_sentinel.AppendASCII(kSentinelFile);
479  return true;
480}
481
482// static
483void FirstRun::AutoImport(
484    Profile* profile,
485    bool homepage_defined,
486    int import_items,
487    int dont_import_items,
488    bool search_engine_experiment,
489    bool randomize_search_engine_experiment,
490    bool make_chrome_default,
491    ProcessSingleton* process_singleton) {
492  // We need to avoid dispatching new tabs when we are importing because
493  // that will lead to data corruption or a crash. Because there is no UI for
494  // the import process, we pass NULL as the window to bring to the foreground
495  // when a CopyData message comes in; this causes the message to be silently
496  // discarded, which is the correct behavior during the import process.
497  process_singleton->Lock(NULL);
498
499  PlatformSetup();
500
501  FilePath local_state_path;
502  PathService::Get(chrome::FILE_LOCAL_STATE, &local_state_path);
503  bool local_state_file_exists = file_util::PathExists(local_state_path);
504
505  scoped_refptr<ImporterHost> importer_host;
506  // TODO(csilv,mirandac): Out-of-process import has only been qualified on
507  // MacOS X, so we will only use it on that platform since it is required.
508  // Remove this conditional logic once oop import is qualified for
509  // Linux/Windows. http://crbug.com/22142
510#if defined(OS_MACOSX)
511  importer_host = new ExternalProcessImporterHost;
512#else
513  importer_host = new ImporterHost;
514#endif
515
516  scoped_refptr<ImporterList> importer_list(new ImporterList);
517  importer_list->DetectSourceProfilesHack();
518
519  // Do import if there is an available profile for us to import.
520  if (importer_list->count() > 0) {
521    // Don't show the warning dialog if import fails.
522    importer_host->set_headless();
523    int items = 0;
524
525    // History is always imported unless turned off in master_preferences.
526    if (!(dont_import_items & importer::HISTORY))
527      items = items | importer::HISTORY;
528    // Home page is imported in organic builds only unless turned off or
529    // defined in master_preferences.
530    if (IsOrganicFirstRun()) {
531      if (!(dont_import_items & importer::HOME_PAGE) && !homepage_defined)
532        items = items | importer::HOME_PAGE;
533    } else {
534      if (import_items & importer::HOME_PAGE)
535        items = items | importer::HOME_PAGE;
536    }
537    // Search engines are only imported in certain builds unless overridden
538    // in master_preferences. Search engines are not imported automatically
539    // if the user already has a user preferences directory.
540    if (IsOrganicFirstRun()) {
541      if (!(dont_import_items & importer::SEARCH_ENGINES) &&
542          !local_state_file_exists) {
543        items = items | importer::SEARCH_ENGINES;
544      }
545    } else if (import_items & importer::SEARCH_ENGINES) {
546        items = items | importer::SEARCH_ENGINES;
547    }
548
549    // Bookmarks are never imported, unless turned on in master_preferences.
550    if (import_items & importer::FAVORITES)
551      items = items | importer::FAVORITES;
552
553    ImportSettings(profile, importer_host, importer_list, items);
554  }
555
556  UserMetrics::RecordAction(UserMetricsAction("FirstRunDef_Accept"));
557
558  // Launch the search engine dialog only for certain builds, and only if the
559  // user has not already set preferences.
560  if (IsOrganicFirstRun() && !local_state_file_exists) {
561    // The home page string may be set in the preferences, but the user should
562    // initially use Chrome with the NTP as home page in organic builds.
563    profile->GetPrefs()->SetBoolean(prefs::kHomePageIsNewTabPage, true);
564    first_run::ShowFirstRunDialog(profile, randomize_search_engine_experiment);
565  }
566
567  if (make_chrome_default)
568    ShellIntegration::SetAsDefaultBrowser();
569
570  // Don't display the minimal bubble if there is no default search provider.
571  TemplateURLModel* search_engines_model = profile->GetTemplateURLModel();
572  if (search_engines_model &&
573      search_engines_model->GetDefaultSearchProvider()) {
574    FirstRun::SetShowFirstRunBubblePref(true);
575    // Set the first run bubble to minimal.
576    FirstRun::SetMinimalFirstRunBubblePref();
577  }
578  FirstRun::SetShowWelcomePagePref();
579  FirstRun::SetPersonalDataManagerFirstRunPref();
580
581  process_singleton->Unlock();
582  FirstRun::CreateSentinel();
583}
584
585#if defined(OS_POSIX)
586namespace {
587
588// This class acts as an observer for the ImporterProgressObserver::ImportEnded
589// callback. When the import process is started, certain errors may cause
590// ImportEnded() to be called synchronously, but the typical case is that
591// ImportEnded() is called asynchronously. Thus we have to handle both cases.
592class ImportEndedObserver : public importer::ImporterProgressObserver {
593 public:
594  ImportEndedObserver() : ended_(false),
595                          should_quit_message_loop_(false) {}
596  virtual ~ImportEndedObserver() {}
597
598  // importer::ImporterProgressObserver:
599  virtual void ImportStarted() OVERRIDE {}
600  virtual void ImportItemStarted(importer::ImportItem item) OVERRIDE {}
601  virtual void ImportItemEnded(importer::ImportItem item) OVERRIDE {}
602  virtual void ImportEnded() OVERRIDE {
603    ended_ = true;
604    if (should_quit_message_loop_)
605      MessageLoop::current()->Quit();
606  }
607
608  void set_should_quit_message_loop() {
609    should_quit_message_loop_ = true;
610  }
611
612  bool ended() {
613    return ended_;
614  }
615
616 private:
617  // Set if the import has ended.
618  bool ended_;
619
620  // Set by the client (via set_should_quit_message_loop) if, when the import
621  // ends, this class should quit the message loop.
622  bool should_quit_message_loop_;
623};
624
625}  // namespace
626
627// static
628bool FirstRun::ImportSettings(Profile* profile,
629                              scoped_refptr<ImporterHost> importer_host,
630                              scoped_refptr<ImporterList> importer_list,
631                              int items_to_import) {
632  const importer::SourceProfile& source_profile =
633      importer_list->GetSourceProfileAt(0);
634
635  // Ensure that importers aren't requested to import items that they do not
636  // support.
637  items_to_import &= source_profile.services_supported;
638
639  scoped_ptr<ImportEndedObserver> observer(new ImportEndedObserver);
640  importer_host->SetObserver(observer.get());
641  importer_host->StartImportSettings(source_profile,
642                                     profile,
643                                     items_to_import,
644                                     new ProfileWriter(profile),
645                                     true);
646  // If the import process has not errored out, block on it.
647  if (!observer->ended()) {
648    observer->set_should_quit_message_loop();
649    MessageLoop::current()->Run();
650  }
651
652  // Unfortunately there's no success/fail signal in ImporterHost.
653  return true;
654}
655
656#endif  // OS_POSIX
657