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/omnibox_search_hint.h"
6
7#include "base/command_line.h"
8#include "base/metrics/histogram.h"
9#include "base/task.h"
10// TODO(avi): remove when conversions not needed any more
11#include "base/utf_string_conversions.h"
12#include "chrome/browser/autocomplete/autocomplete.h"
13#include "chrome/browser/autocomplete/autocomplete_edit.h"
14#include "chrome/browser/autocomplete/autocomplete_edit_view.h"
15#include "chrome/browser/autocomplete/autocomplete_match.h"
16#include "chrome/browser/prefs/pref_service.h"
17#include "chrome/browser/profiles/profile.h"
18#include "chrome/browser/search_engines/template_url.h"
19#include "chrome/browser/search_engines/template_url_model.h"
20#include "chrome/browser/tab_contents/confirm_infobar_delegate.h"
21#include "chrome/browser/ui/browser_list.h"
22#include "chrome/browser/ui/browser_window.h"
23#include "chrome/browser/ui/omnibox/location_bar.h"
24#include "chrome/common/chrome_switches.h"
25#include "chrome/common/pref_names.h"
26#include "content/browser/tab_contents/tab_contents.h"
27#include "content/common/notification_details.h"
28#include "content/common/notification_source.h"
29#include "content/common/notification_type.h"
30#include "grit/generated_resources.h"
31#include "grit/theme_resources.h"
32#include "ui/base/l10n/l10n_util.h"
33#include "ui/base/resource/resource_bundle.h"
34
35// The URLs of search engines for which we want to trigger the infobar.
36const char* kSearchEngineURLs[] = {
37    "http://www.google.com/",
38    "http://www.yahoo.com/",
39    "http://www.bing.com/",
40    "http://www.altavista.com/",
41    "http://www.ask.com/",
42    "http://www.wolframalpha.com/",
43};
44
45
46// HintInfoBar ----------------------------------------------------------------
47
48class HintInfoBar : public ConfirmInfoBarDelegate {
49 public:
50  explicit HintInfoBar(OmniboxSearchHint* omnibox_hint);
51
52 private:
53  virtual ~HintInfoBar();
54
55  void AllowExpiry() { should_expire_ = true; }
56
57  // ConfirmInfoBarDelegate:
58  virtual bool ShouldExpire(
59      const NavigationController::LoadCommittedDetails& details) const;
60  virtual void InfoBarDismissed();
61  virtual void InfoBarClosed();
62  virtual SkBitmap* GetIcon() const;
63  virtual Type GetInfoBarType() const;
64  virtual string16 GetMessageText() const;
65  virtual int GetButtons() const;
66  virtual string16 GetButtonLabel(InfoBarButton button) const;
67  virtual bool Accept();
68
69  // The omnibox hint that shows us.
70  OmniboxSearchHint* omnibox_hint_;
71
72  // Whether the user clicked one of the buttons.
73  bool action_taken_;
74
75  // Whether the info-bar should be dismissed on the next navigation.
76  bool should_expire_;
77
78  // Used to delay the expiration of the info-bar.
79  ScopedRunnableMethodFactory<HintInfoBar> method_factory_;
80
81  DISALLOW_COPY_AND_ASSIGN(HintInfoBar);
82};
83
84HintInfoBar::HintInfoBar(OmniboxSearchHint* omnibox_hint)
85    : ConfirmInfoBarDelegate(omnibox_hint->tab()),
86      omnibox_hint_(omnibox_hint),
87      action_taken_(false),
88      should_expire_(false),
89      ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) {
90  // We want the info-bar to stick-around for few seconds and then be hidden
91  // on the next navigation after that.
92  MessageLoop::current()->PostDelayedTask(FROM_HERE,
93      method_factory_.NewRunnableMethod(&HintInfoBar::AllowExpiry),
94      8000);  // 8 seconds.
95}
96
97HintInfoBar::~HintInfoBar() {
98}
99
100bool HintInfoBar::ShouldExpire(
101    const NavigationController::LoadCommittedDetails& details) const {
102  return should_expire_;
103}
104
105void HintInfoBar::InfoBarDismissed() {
106  action_taken_ = true;
107  UMA_HISTOGRAM_COUNTS("OmniboxSearchHint.Closed", 1);
108  // User closed the infobar, let's not bug him again with this in the future.
109  omnibox_hint_->DisableHint();
110}
111
112void HintInfoBar::InfoBarClosed() {
113  if (!action_taken_)
114    UMA_HISTOGRAM_COUNTS("OmniboxSearchHint.Ignored", 1);
115  delete this;
116}
117
118SkBitmap* HintInfoBar::GetIcon() const {
119  return ResourceBundle::GetSharedInstance().GetBitmapNamed(
120     IDR_INFOBAR_QUESTION_MARK);
121}
122
123InfoBarDelegate::Type HintInfoBar::GetInfoBarType() const {
124  return PAGE_ACTION_TYPE;
125}
126
127string16 HintInfoBar::GetMessageText() const {
128  return l10n_util::GetStringUTF16(IDS_OMNIBOX_SEARCH_HINT_INFOBAR_TEXT);
129}
130
131int HintInfoBar::GetButtons() const {
132  return BUTTON_OK;
133}
134
135string16 HintInfoBar::GetButtonLabel(InfoBarButton button) const {
136  DCHECK_EQ(BUTTON_OK, button);
137  return l10n_util::GetStringUTF16(
138      IDS_OMNIBOX_SEARCH_HINT_INFOBAR_BUTTON_LABEL);
139}
140
141bool HintInfoBar::Accept() {
142  action_taken_ = true;
143  UMA_HISTOGRAM_COUNTS("OmniboxSearchHint.ShowMe", 1);
144  omnibox_hint_->DisableHint();
145  omnibox_hint_->ShowEnteringQuery();
146  return true;
147}
148
149
150// OmniboxSearchHint ----------------------------------------------------------
151
152OmniboxSearchHint::OmniboxSearchHint(TabContents* tab) : tab_(tab) {
153  NavigationController* controller = &(tab->controller());
154  notification_registrar_.Add(this,
155                              NotificationType::NAV_ENTRY_COMMITTED,
156                              Source<NavigationController>(controller));
157  // Fill the search_engine_urls_ map, used for faster look-up (overkill?).
158  for (size_t i = 0;
159       i < sizeof(kSearchEngineURLs) / sizeof(kSearchEngineURLs[0]); ++i) {
160    search_engine_urls_[kSearchEngineURLs[i]] = 1;
161  }
162
163  // Listen for omnibox to figure-out when the user searches from the omnibox.
164  notification_registrar_.Add(this,
165                              NotificationType::OMNIBOX_OPENED_URL,
166                              Source<Profile>(tab->profile()));
167}
168
169OmniboxSearchHint::~OmniboxSearchHint() {
170}
171
172void OmniboxSearchHint::Observe(NotificationType type,
173                                const NotificationSource& source,
174                                const NotificationDetails& details) {
175  if (type == NotificationType::NAV_ENTRY_COMMITTED) {
176    NavigationEntry* entry = tab_->controller().GetActiveEntry();
177    if (search_engine_urls_.find(entry->url().spec()) ==
178        search_engine_urls_.end()) {
179      // The search engine is not in our white-list, bail.
180      return;
181    }
182    const TemplateURL* const default_provider =
183        tab_->profile()->GetTemplateURLModel()->GetDefaultSearchProvider();
184    if (!default_provider)
185      return;
186
187    const TemplateURLRef* const search_url = default_provider->url();
188    if (search_url->GetHost() == entry->url().host())
189      ShowInfoBar();
190  } else if (type == NotificationType::OMNIBOX_OPENED_URL) {
191    AutocompleteLog* log = Details<AutocompleteLog>(details).ptr();
192    AutocompleteMatch::Type type =
193        log->result.match_at(log->selected_index).type;
194    if (type == AutocompleteMatch::SEARCH_WHAT_YOU_TYPED ||
195        type == AutocompleteMatch::SEARCH_HISTORY ||
196        type == AutocompleteMatch::SEARCH_SUGGEST) {
197      // The user performed a search from the omnibox, don't show the infobar
198      // again.
199      DisableHint();
200    }
201  }
202}
203
204void OmniboxSearchHint::ShowInfoBar() {
205  tab_->AddInfoBar(new HintInfoBar(this));
206}
207
208void OmniboxSearchHint::ShowEnteringQuery() {
209  LocationBar* location_bar = BrowserList::GetLastActive()->window()->
210      GetLocationBar();
211  AutocompleteEditView*  edit_view = location_bar->location_entry();
212  location_bar->FocusLocation(true);
213  edit_view->SetUserText(
214      l10n_util::GetStringUTF16(IDS_OMNIBOX_SEARCH_HINT_OMNIBOX_TEXT));
215  edit_view->SelectAll(false);
216  // Entering text in the autocomplete edit view triggers the suggestion popup
217  // that we don't want to show in this case.
218  edit_view->ClosePopup();
219}
220
221void OmniboxSearchHint::DisableHint() {
222  // The NAV_ENTRY_COMMITTED notification was needed to show the infobar, the
223  // OMNIBOX_OPENED_URL notification was there to set the kShowOmniboxSearchHint
224  // prefs to false, none of them are needed anymore.
225  notification_registrar_.RemoveAll();
226  tab_->profile()->GetPrefs()->SetBoolean(prefs::kShowOmniboxSearchHint,
227                                          false);
228}
229
230// static
231bool OmniboxSearchHint::IsEnabled(Profile* profile) {
232  // The infobar can only be shown if the correct switch has been provided and
233  // the user did not dismiss the infobar before.
234  return profile->GetPrefs()->GetBoolean(prefs::kShowOmniboxSearchHint) &&
235      CommandLine::ForCurrentProcess()->HasSwitch(
236      switches::kSearchInOmniboxHint);
237}
238