people_provider.cc revision f8ee788a64d60abd8f2d742a5fdedde054ecd910
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/app_list/search/people/people_provider.h"
6
7#include <string>
8
9#include "base/bind.h"
10#include "base/callback.h"
11#include "base/strings/string_number_conversions.h"
12#include "base/strings/utf_string_conversions.h"
13#include "base/values.h"
14#include "chrome/browser/browser_process.h"
15#include "chrome/browser/profiles/profile.h"
16#include "chrome/browser/search/search.h"
17#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
18#include "chrome/browser/signin/signin_manager_factory.h"
19#include "chrome/browser/ui/app_list/search/common/json_response_fetcher.h"
20#include "chrome/browser/ui/app_list/search/people/people_result.h"
21#include "chrome/browser/ui/app_list/search/people/person.h"
22#include "components/signin/core/browser/profile_oauth2_token_service.h"
23#include "components/signin/core/browser/signin_manager.h"
24#include "google_apis/gaia/gaia_constants.h"
25#include "net/base/url_util.h"
26#include "url/gurl.h"
27
28namespace app_list {
29
30namespace {
31
32const char kKeyItems[] = "items";
33
34const char kAccessTokenField[] = "access_token";
35const char kQueryField[] = "query";
36const char kPeopleSearchUrl[] =
37    "https://www.googleapis.com/plus/v2whitelisted/people/autocomplete";
38
39// OAuth2 scope for access to the Google+ People Search API.
40const char kPeopleSearchOAuth2Scope[] =
41    "https://www.googleapis.com/auth/plus.peopleapi.readwrite";
42
43}  // namespace
44
45PeopleProvider::PeopleProvider(Profile* profile)
46  : WebserviceSearchProvider(profile),
47    OAuth2TokenService::Consumer("people_provider"),
48    people_search_url_(kPeopleSearchUrl),
49    skip_request_token_for_test_(false) {
50  oauth2_scope_.insert(kPeopleSearchOAuth2Scope);
51}
52
53PeopleProvider::~PeopleProvider() {}
54
55void PeopleProvider::Start(const base::string16& query) {
56  ClearResults();
57  if (!IsValidQuery(query)) {
58    query_.clear();
59    return;
60  }
61
62  query_ = base::UTF16ToUTF8(query);
63
64  const CacheResult result = cache_->Get(WebserviceCache::PEOPLE, query_);
65  if (result.second) {
66    ProcessPeopleSearchResults(result.second);
67    if (!people_search_fetched_callback_.is_null())
68      people_search_fetched_callback_.Run();
69    if (result.first == FRESH)
70      return;
71  }
72
73
74  if (!people_search_) {
75    people_search_.reset(new JSONResponseFetcher(
76        base::Bind(&PeopleProvider::OnPeopleSearchFetched,
77                   base::Unretained(this)),
78        profile_->GetRequestContext()));
79  }
80
81  if (!skip_request_token_for_test_) {
82    // We start with reqesting the access token. Once the token is fetched,
83    // we'll create the full query URL and fetch it.
84    StartThrottledQuery(base::Bind(&PeopleProvider::RequestAccessToken,
85                                   base::Unretained(this)));
86  } else {
87    // Running in a test, skip requesting the access token, straight away
88    // start our query.
89    StartThrottledQuery(base::Bind(&PeopleProvider::StartQuery,
90                                   base::Unretained(this)));
91  }
92}
93
94void PeopleProvider::Stop() {
95  if (people_search_)
96    people_search_->Stop();
97}
98
99void PeopleProvider::OnGetTokenSuccess(
100    const OAuth2TokenService::Request* request,
101    const std::string& access_token,
102    const base::Time& expiration_time) {
103  DCHECK_EQ(access_token_request_, request);
104  access_token_request_.reset();
105  access_token_ = access_token;
106  StartQuery();
107}
108
109void PeopleProvider::OnGetTokenFailure(
110    const OAuth2TokenService::Request* request,
111    const GoogleServiceAuthError& error) {
112  DCHECK_EQ(access_token_request_, request);
113  access_token_request_.reset();
114}
115
116void PeopleProvider::RequestAccessToken() {
117  // Only one active request at a time.
118  if (access_token_request_ != NULL)
119    return;
120
121  ProfileOAuth2TokenService* token_service =
122      ProfileOAuth2TokenServiceFactory::GetForProfile(profile_);
123  SigninManagerBase* signin_manager =
124      SigninManagerFactory::GetInstance()->GetForProfile(profile_);
125  access_token_request_ = token_service->StartRequest(
126      signin_manager->GetAuthenticatedAccountId(), oauth2_scope_, this);
127}
128
129GURL PeopleProvider::GetQueryUrl(const std::string& query) {
130  GURL people_search_url = people_search_url_;
131  people_search_url = net::AppendQueryParameter(people_search_url,
132                                                kAccessTokenField,
133                                                access_token_);
134  people_search_url = net::AppendQueryParameter(people_search_url,
135                                                kQueryField,
136                                                query);
137
138  return people_search_url;
139}
140
141void PeopleProvider::StartQuery() {
142  // |query_| can be NULL when the query is scheduled but then canceled.
143  if (!people_search_ || query_.empty())
144    return;
145
146  GURL url = GetQueryUrl(query_);
147  people_search_->Start(url);
148}
149
150void PeopleProvider::OnPeopleSearchFetched(
151    scoped_ptr<base::DictionaryValue> json) {
152  ProcessPeopleSearchResults(json.get());
153  cache_->Put(WebserviceCache::PEOPLE, query_, json.Pass());
154
155  if (!people_search_fetched_callback_.is_null())
156    people_search_fetched_callback_.Run();
157}
158
159void PeopleProvider::ProcessPeopleSearchResults(
160    const base::DictionaryValue* json) {
161  const base::ListValue* item_list = NULL;
162  if (!json ||
163      !json->GetList(kKeyItems, &item_list) ||
164      !item_list ||
165      item_list->empty()) {
166    return;
167  }
168
169  ClearResults();
170  for (base::ListValue::const_iterator it = item_list->begin();
171       it != item_list->end();
172       ++it) {
173    const base::DictionaryValue* dict;
174    if (!(*it)->GetAsDictionary(&dict))
175      continue;
176
177    scoped_ptr<SearchResult> result(CreateResult(*dict));
178    if (!result)
179      continue;
180
181    Add(result.Pass());
182  }
183}
184
185scoped_ptr<ChromeSearchResult> PeopleProvider::CreateResult(
186    const base::DictionaryValue& dict) {
187  scoped_ptr<ChromeSearchResult> result;
188
189  scoped_ptr<Person> person = Person::Create(dict);
190  if (!person)
191    return result.Pass();
192
193  result.reset(new PeopleResult(profile_, person.Pass()));
194  return result.Pass();
195}
196
197void PeopleProvider::SetupForTest(
198    const base::Closure& people_search_fetched_callback,
199    const GURL& people_search_url) {
200  people_search_fetched_callback_ = people_search_fetched_callback;
201  people_search_url_ = people_search_url;
202  skip_request_token_for_test_ = true;
203}
204
205}  // namespace app_list
206