1// Copyright 2013 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/ui/sync/profile_signin_confirmation_helper.h"
6
7#include "base/bind.h"
8#include "base/memory/ref_counted.h"
9#include "base/prefs/pref_service.h"
10#include "base/strings/string16.h"
11#include "base/task/cancelable_task_tracker.h"
12#include "chrome/browser/bookmarks/bookmark_model_factory.h"
13#include "chrome/browser/history/history_backend.h"
14#include "chrome/browser/history/history_db_task.h"
15#include "chrome/browser/history/history_service.h"
16#include "chrome/browser/history/history_service_factory.h"
17#include "chrome/browser/profiles/profile.h"
18#include "components/bookmarks/browser/bookmark_model.h"
19#include "components/history/core/browser/history_types.h"
20#include "content/public/browser/browser_thread.h"
21#include "ui/gfx/color_utils.h"
22#include "ui/native_theme/native_theme.h"
23
24#if defined(ENABLE_EXTENSIONS)
25#include "chrome/browser/extensions/extension_service.h"
26#include "chrome/common/extensions/extension_constants.h"
27#include "chrome/common/extensions/sync_helper.h"
28#include "extensions/browser/extension_system.h"
29#include "extensions/common/constants.h"
30#include "extensions/common/extension.h"
31#include "extensions/common/extension_set.h"
32#endif
33
34namespace {
35
36const int kHistoryEntriesBeforeNewProfilePrompt = 10;
37
38// Determines whether a profile has any typed URLs in its history.
39class HasTypedURLsTask : public history::HistoryDBTask {
40 public:
41  explicit HasTypedURLsTask(const base::Callback<void(bool)>& cb)
42      : has_typed_urls_(false), cb_(cb) {
43  }
44
45  virtual bool RunOnDBThread(history::HistoryBackend* backend,
46                             history::HistoryDatabase* db) OVERRIDE {
47    history::URLRows rows;
48    backend->GetAllTypedURLs(&rows);
49    if (!rows.empty()) {
50      DVLOG(1) << "ProfileSigninConfirmationHelper: profile contains "
51               << rows.size() << " typed URLs";
52      has_typed_urls_ = true;
53    }
54    return true;
55  }
56
57  virtual void DoneRunOnMainThread() OVERRIDE {
58    cb_.Run(has_typed_urls_);
59  }
60
61 private:
62  virtual ~HasTypedURLsTask() {}
63
64  bool has_typed_urls_;
65  base::Callback<void(bool)> cb_;
66};
67
68bool HasBookmarks(Profile* profile) {
69  BookmarkModel* bookmarks = BookmarkModelFactory::GetForProfile(profile);
70  bool has_bookmarks = bookmarks && bookmarks->HasBookmarks();
71  if (has_bookmarks)
72    DVLOG(1) << "ProfileSigninConfirmationHelper: profile contains bookmarks";
73  return has_bookmarks;
74}
75
76// Helper functions for Chrome profile signin.
77class ProfileSigninConfirmationHelper {
78 public:
79  ProfileSigninConfirmationHelper(
80      Profile* profile,
81      const base::Callback<void(bool)>& return_result);
82  void CheckHasHistory(int max_entries);
83  void CheckHasTypedURLs();
84
85 private:
86  // Deletes itself.
87  ~ProfileSigninConfirmationHelper();
88
89  void OnHistoryQueryResults(size_t max_entries,
90                             history::QueryResults* results);
91  void ReturnResult(bool result);
92
93  // Weak pointer to the profile being signed-in.
94  Profile* profile_;
95
96  // Used for async tasks.
97  base::CancelableTaskTracker task_tracker_;
98
99  // Keep track of how many async requests are pending.
100  int pending_requests_;
101
102  // Callback to pass the result back to the caller.
103  const base::Callback<void(bool)> return_result_;
104
105  DISALLOW_COPY_AND_ASSIGN(ProfileSigninConfirmationHelper);
106};
107
108ProfileSigninConfirmationHelper::ProfileSigninConfirmationHelper(
109    Profile* profile,
110    const base::Callback<void(bool)>& return_result)
111    : profile_(profile),
112      pending_requests_(0),
113      return_result_(return_result) {
114}
115
116ProfileSigninConfirmationHelper::~ProfileSigninConfirmationHelper() {
117  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
118}
119
120void ProfileSigninConfirmationHelper::OnHistoryQueryResults(
121    size_t max_entries,
122    history::QueryResults* results) {
123  history::QueryResults owned_results;
124  results->Swap(&owned_results);
125  bool too_much_history = owned_results.size() >= max_entries;
126  if (too_much_history) {
127    DVLOG(1) << "ProfileSigninConfirmationHelper: profile contains "
128             << owned_results.size() << " history entries";
129  }
130  ReturnResult(too_much_history);
131}
132
133void ProfileSigninConfirmationHelper::CheckHasHistory(int max_entries) {
134  HistoryService* service =
135      HistoryServiceFactory::GetForProfileWithoutCreating(profile_);
136  pending_requests_++;
137  if (!service) {
138    ReturnResult(false);
139    return;
140  }
141  history::QueryOptions opts;
142  opts.max_count = max_entries;
143  service->QueryHistory(
144      base::string16(),
145      opts,
146      base::Bind(&ProfileSigninConfirmationHelper::OnHistoryQueryResults,
147                 base::Unretained(this),
148                 max_entries),
149      &task_tracker_);
150}
151
152void ProfileSigninConfirmationHelper::CheckHasTypedURLs() {
153  HistoryService* service =
154      HistoryServiceFactory::GetForProfileWithoutCreating(profile_);
155  pending_requests_++;
156  if (!service) {
157    ReturnResult(false);
158    return;
159  }
160  service->ScheduleDBTask(
161      scoped_ptr<history::HistoryDBTask>(new HasTypedURLsTask(
162          base::Bind(&ProfileSigninConfirmationHelper::ReturnResult,
163                     base::Unretained(this)))),
164      &task_tracker_);
165}
166
167void ProfileSigninConfirmationHelper::ReturnResult(bool result) {
168  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
169  // Pass |true| into the callback as soon as one of the tasks passes a
170  // result of |true|, otherwise pass the last returned result.
171  if (--pending_requests_ == 0 || result) {
172    return_result_.Run(result);
173
174    // This leaks at shutdown if the HistoryService is destroyed, but
175    // the process is going to die anyway.
176    delete this;
177  }
178}
179
180}  // namespace
181
182namespace ui {
183
184SkColor GetSigninConfirmationPromptBarColor(SkAlpha alpha) {
185  static const SkColor kBackgroundColor =
186      ui::NativeTheme::instance()->GetSystemColor(
187          ui::NativeTheme::kColorId_DialogBackground);
188  return color_utils::BlendTowardOppositeLuminance(kBackgroundColor, alpha);
189}
190
191bool HasBeenShutdown(Profile* profile) {
192#if defined(OS_IOS)
193  // This check is not useful on iOS: the browser can be shut down without
194  // explicit user action (for example, in response to memory pressure), and
195  // this should be invisible to the user. The desktop assumption that the
196  // profile going through a restart indicates something about user intention
197  // does not hold. We rely on the other profile dirtiness checks.
198  return false;
199#else
200  bool has_been_shutdown = !profile->IsNewProfile();
201  if (has_been_shutdown)
202    DVLOG(1) << "ProfileSigninConfirmationHelper: profile is not new";
203  return has_been_shutdown;
204#endif
205}
206
207bool HasSyncedExtensions(Profile* profile) {
208#if defined(ENABLE_EXTENSIONS)
209  extensions::ExtensionSystem* system =
210      extensions::ExtensionSystem::Get(profile);
211  if (system && system->extension_service()) {
212    const extensions::ExtensionSet* extensions =
213        system->extension_service()->extensions();
214    for (extensions::ExtensionSet::const_iterator iter = extensions->begin();
215         iter != extensions->end(); ++iter) {
216      // The webstore is synced so that it stays put on the new tab
217      // page, but since it's installed by default we don't want to
218      // consider it when determining if the profile is dirty.
219      if (extensions::sync_helper::IsSyncable(iter->get()) &&
220          (*iter)->id() != extensions::kWebStoreAppId &&
221          (*iter)->id() != extension_misc::kChromeAppId) {
222        DVLOG(1) << "ProfileSigninConfirmationHelper: "
223                 << "profile contains a synced extension: " << (*iter)->id();
224        return true;
225      }
226    }
227  }
228#endif
229  return false;
230}
231
232void CheckShouldPromptForNewProfile(
233    Profile* profile,
234    const base::Callback<void(bool)>& return_result) {
235  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
236
237  if (HasBeenShutdown(profile) ||
238      HasBookmarks(profile) ||
239      HasSyncedExtensions(profile)) {
240    return_result.Run(true);
241    return;
242  }
243  // Fire asynchronous queries for profile data.
244  ProfileSigninConfirmationHelper* helper =
245      new ProfileSigninConfirmationHelper(profile, return_result);
246  helper->CheckHasHistory(kHistoryEntriesBeforeNewProfilePrompt);
247  helper->CheckHasTypedURLs();
248}
249
250}  // namespace ui
251