190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)// Copyright 2013 The Chromium Authors. All rights reserved.
290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)// found in the LICENSE file.
490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include "chrome/browser/ui/app_list/search/search_controller.h"
690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include <algorithm>
890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include <vector>
990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
1090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include "base/bind.h"
1190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include "base/command_line.h"
1290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include "base/memory/scoped_ptr.h"
13eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch#include "base/metrics/histogram.h"
147d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)#include "base/strings/string_util.h"
15868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include "base/strings/utf_string_conversions.h"
1690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include "chrome/browser/profiles/profile.h"
17a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch#include "chrome/browser/ui/app_list/app_list_controller_delegate.h"
1890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include "chrome/browser/ui/app_list/search/app_search_provider.h"
1990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
2090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include "chrome/browser/ui/app_list/search/history.h"
2190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include "chrome/browser/ui/app_list/search/history_factory.h"
2290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include "chrome/browser/ui/app_list/search/omnibox_provider.h"
2358537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)#include "chrome/browser/ui/app_list/search/people/people_provider.h"
2458537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)#include "chrome/browser/ui/app_list/search/webstore/webstore_provider.h"
25f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "chrome/browser/ui/app_list/start_page_service.h"
2658537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)#include "chrome/common/chrome_switches.h"
271320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#include "chrome/grit/generated_resources.h"
2890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include "content/public/browser/user_metrics.h"
291320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#include "grit/components_scaled_resources.h"
3090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include "grit/theme_resources.h"
3190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include "ui/app_list/search_box_model.h"
325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#include "ui/app_list/speech_ui_model.h"
3390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include "ui/base/l10n/l10n_util.h"
3490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include "ui/base/resource/resource_bundle.h"
3590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
36eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochnamespace {
37eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  const char kAppListSearchResultOpenTypeHistogram[] =
38eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      "Apps.AppListSearchResultOpenType";
395f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
405f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  // Maximum time (in milliseconds) to wait to the search providers to finish.
415f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  const int kStopTimeMS = 1500;
42eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
43eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
4490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)namespace app_list {
4590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
4690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)SearchController::SearchController(Profile* profile,
4790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)                                   SearchBoxModel* search_box,
4890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)                                   AppListModel::SearchResults* results,
495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                                   SpeechUIModel* speech_ui,
5090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)                                   AppListControllerDelegate* list_controller)
5190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  : profile_(profile),
5290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    search_box_(search_box),
535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    speech_ui_(speech_ui),
5490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    list_controller_(list_controller),
5590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    dispatching_query_(false),
5690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    mixer_(new Mixer(results)),
5790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    history_(HistoryFactory::GetForBrowserContext(profile)) {
585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  speech_ui_->AddObserver(this);
5990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  Init();
6090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)}
6190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)SearchController::~SearchController() {
635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  speech_ui_->RemoveObserver(this);
645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)}
6590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
6690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)void SearchController::Init() {
67f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
68f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  search_box_->SetIcon(*bundle.GetImageSkiaNamed(IDR_OMNIBOX_SEARCH));
695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  StartPageService* service = StartPageService::Get(profile_);
705d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if (service && service->GetSpeechRecognitionContents()) {
71f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    search_box_->SetSpeechRecognitionButton(
725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        scoped_ptr<SearchBoxModel::SpeechButtonProperty>(
735d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            new SearchBoxModel::SpeechButtonProperty(
74f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                *bundle.GetImageSkiaNamed(IDR_OMNIBOX_MIC_SEARCH),
75f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                l10n_util::GetStringUTF16(
765d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    IDS_APP_LIST_HOTWORD_LISTENING),
775d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                *bundle.GetImageSkiaNamed(IDR_APP_LIST_MIC_HOTWORD_OFF),
785d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                l10n_util::GetStringUTF16(
79a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)                    IDS_APP_LIST_START_SPEECH_RECOGNITION))));
80f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  }
815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  OnSpeechRecognitionStateChanged(speech_ui_->state());
8290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
8390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  mixer_->Init();
8490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
8590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  AddProvider(Mixer::MAIN_GROUP, scoped_ptr<SearchProvider>(
8690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)      new AppSearchProvider(profile_, list_controller_)).Pass());
8790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  AddProvider(Mixer::OMNIBOX_GROUP, scoped_ptr<SearchProvider>(
8890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)      new OmniboxProvider(profile_)).Pass());
8990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  AddProvider(Mixer::WEBSTORE_GROUP, scoped_ptr<SearchProvider>(
90eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      new WebstoreProvider(profile_, list_controller_)).Pass());
91f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  if (!CommandLine::ForCurrentProcess()->HasSwitch(
92f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            switches::kDisablePeopleSearch)) {
9358537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)    AddProvider(Mixer::PEOPLE_GROUP, scoped_ptr<SearchProvider>(
9458537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)        new PeopleProvider(profile_)).Pass());
9558537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)  }
9690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)}
9790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
9890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)void SearchController::Start() {
9990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  Stop();
10090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
101a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch  list_controller_->OnSearchStarted();
102a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch
103a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)  base::string16 query;
104a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  base::TrimWhitespace(search_box_->text(), base::TRIM_ALL, &query);
10590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
10690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  dispatching_query_ = true;
10790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  for (Providers::iterator it = providers_.begin();
10890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)       it != providers_.end();
10990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)       ++it) {
11090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    (*it)->Start(query);
11190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  }
11290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  dispatching_query_ = false;
11390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
11490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  OnResultsChanged();
11590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
11690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  stop_timer_.Start(FROM_HERE,
11790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)                    base::TimeDelta::FromMilliseconds(kStopTimeMS),
11890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)                    base::Bind(&SearchController::Stop,
11990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)                               base::Unretained(this)));
12090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)}
12190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
12290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)void SearchController::Stop() {
12390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  stop_timer_.Stop();
12490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
12590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  for (Providers::iterator it = providers_.begin();
12690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)       it != providers_.end();
12790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)       ++it) {
12890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    (*it)->Stop();
12990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  }
13090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)}
13190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
13290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)void SearchController::OpenResult(SearchResult* result, int event_flags) {
13390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  // Count AppList.Search here because it is composed of search + action.
1345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  content::RecordAction(base::UserMetricsAction("AppList_Search"));
13590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
13690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  ChromeSearchResult* chrome_result =
13790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)      static_cast<app_list::ChromeSearchResult*>(result);
138eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  UMA_HISTOGRAM_ENUMERATION(kAppListSearchResultOpenTypeHistogram,
139eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch                            chrome_result->GetType(),
140eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch                            SEARCH_RESULT_TYPE_BOUNDARY);
14190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  chrome_result->Open(event_flags);
14290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
14390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  if (history_ && history_->IsReady()) {
1445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    history_->AddLaunchEvent(base::UTF16ToUTF8(search_box_->text()),
14590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)                             chrome_result->id());
14690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  }
14790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)}
14890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
14990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)void SearchController::InvokeResultAction(SearchResult* result,
15090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)                                          int action_index,
15190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)                                          int event_flags) {
15290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  // TODO(xiyuan): Hook up with user learning.
15390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  static_cast<app_list::ChromeSearchResult*>(result)->InvokeAction(
15490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)      action_index, event_flags);
15590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)}
15690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
15790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)void SearchController::AddProvider(Mixer::GroupId group,
15890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)                                   scoped_ptr<SearchProvider> provider) {
15990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  provider->set_result_changed_callback(base::Bind(
16090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)      &SearchController::OnResultsChanged,
16190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)      base::Unretained(this)));
16290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  mixer_->AddProviderToGroup(group, provider.get());
16390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  providers_.push_back(provider.release());  // Takes ownership.
16490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)}
16590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
16690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)void SearchController::OnResultsChanged() {
16790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  if (dispatching_query_)
16890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    return;
16990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
17090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  KnownResults known_results;
17190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  if (history_ && history_->IsReady()) {
1725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    history_->GetKnownResults(base::UTF16ToUTF8(search_box_->text()))
17390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)        ->swap(known_results);
17490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  }
17590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
17690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  mixer_->MixAndPublish(known_results);
17790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)}
17890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
1795d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)void SearchController::OnSpeechRecognitionStateChanged(
1805d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    SpeechRecognitionState new_state) {
1815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  search_box_->SetHintText(l10n_util::GetStringUTF16(
1825d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      (new_state == SPEECH_RECOGNITION_HOTWORD_LISTENING) ?
1835d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      IDS_SEARCH_BOX_HOTWORD_HINT : IDS_SEARCH_BOX_HINT));
1845d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)}
1855d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
18690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)}  // namespace app_list
187