people_provider.cc revision 58537e28ecd584eab876aee8be7156509866d23a
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/metrics/field_trial.h"
12#include "base/strings/string_number_conversions.h"
13#include "base/strings/utf_string_conversions.h"
14#include "base/values.h"
15#include "chrome/browser/browser_process.h"
16#include "chrome/browser/profiles/profile.h"
17#include "chrome/browser/search/search.h"
18#include "chrome/browser/signin/profile_oauth2_token_service.h"
19#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
20#include "chrome/browser/ui/app_list/search/common/json_response_fetcher.h"
21#include "chrome/browser/ui/app_list/search/people/people_result.h"
22#include "google_apis/gaia/gaia_constants.h"
23#include "net/base/url_util.h"
24#include "url/gurl.h"
25
26namespace app_list {
27
28namespace {
29
30const char kKeyItems[] = "items";
31const char kKeyId[] = "person.id";
32const char kKeyNames[] = "person.names";
33const char kKeyDisplayName[] = "displayName";
34const char kKeySortKeys[] = "person.sortKeys";
35const char kKeyInteractionRank[] = "interactionRank";
36const char kKeyImages[] = "person.images";
37const char kKeyUrl[] = "url";
38
39const char kAccessTokenField[] = "access_token";
40const char kQueryField[] = "query";
41const char kPeopleSearchUrl[] =
42    "https://www.googleapis.com/plus/v2whitelisted/people/autocomplete";
43
44// OAuth2 scope for access to the Google+ People Search API.
45const char kPeopleSearchOAuth2Scope[] =
46    "https://www.googleapis.com/auth/plus.peopleapi.readwrite";
47
48// Get's the value associated with the key in the first dictionary in the list.
49std::string GetFirstValue(const ListValue& list, const char key[]) {
50  ListValue::const_iterator it = list.begin();
51  if (it == list.end())
52    return std::string();
53
54  base::DictionaryValue* dict;
55  if (!(*it)->GetAsDictionary(&dict))
56    return std::string();
57
58  std::string value;
59  if (!dict || !dict->GetString(key, &value))
60    return std::string();
61
62  return value;
63}
64
65}  // namespace
66
67PeopleProvider::PeopleProvider(Profile* profile)
68  : WebserviceSearchProvider(profile),
69    people_search_url_(kPeopleSearchUrl),
70    skip_request_token_for_test_(false) {
71  oauth2_scope_.insert(kPeopleSearchOAuth2Scope);
72}
73
74PeopleProvider::~PeopleProvider() {}
75
76void PeopleProvider::Start(const base::string16& query) {
77  ClearResults();
78  if (!IsValidQuery(query)) {
79    query_.clear();
80    return;
81  }
82
83  query_ = UTF16ToUTF8(query);
84  if (!people_search_) {
85    people_search_.reset(new JSONResponseFetcher(
86        base::Bind(&PeopleProvider::OnPeopleSearchFetched,
87                   base::Unretained(this)),
88        profile_->GetRequestContext()));
89  }
90
91  if (!skip_request_token_for_test_) {
92    // We start with reqesting the access token. Once the token is fetched,
93    // we'll create the full query URL and fetch it.
94    StartThrottledQuery(base::Bind(&PeopleProvider::RequestAccessToken,
95                                   base::Unretained(this)));
96  } else {
97    // Running in a test, skip requesting the access token, straight away
98    // start our query.
99    StartThrottledQuery(base::Bind(&PeopleProvider::StartQuery,
100                                   base::Unretained(this)));
101  }
102}
103
104void PeopleProvider::Stop() {
105  if (people_search_)
106    people_search_->Stop();
107}
108
109void PeopleProvider::OnGetTokenSuccess(
110    const OAuth2TokenService::Request* request,
111    const std::string& access_token,
112    const base::Time& expiration_time) {
113  DCHECK_EQ(access_token_request_, request);
114  access_token_request_.reset();
115  access_token_ = access_token;
116  StartQuery();
117}
118
119void PeopleProvider::OnGetTokenFailure(
120    const OAuth2TokenService::Request* request,
121    const GoogleServiceAuthError& error) {
122  DCHECK_EQ(access_token_request_, request);
123  access_token_request_.reset();
124}
125
126void PeopleProvider::RequestAccessToken() {
127  // Only one active request at a time.
128  if (access_token_request_ != NULL)
129    return;
130
131  OAuth2TokenService* token_service =
132      ProfileOAuth2TokenServiceFactory::GetForProfile(profile_);
133  access_token_request_ = token_service->StartRequest(oauth2_scope_, this);
134}
135
136GURL PeopleProvider::GetQueryUrl(const std::string& query) {
137  GURL people_search_url = people_search_url_;
138  people_search_url = net::AppendQueryParameter(people_search_url,
139                                                kAccessTokenField,
140                                                access_token_);
141  people_search_url = net::AppendQueryParameter(people_search_url,
142                                                kQueryField,
143                                                query);
144
145  return people_search_url;
146}
147
148void PeopleProvider::StartQuery() {
149  // |query_| can be NULL when the query is scheduled but then canceled.
150  if (!people_search_ || query_.empty())
151    return;
152
153  GURL url = GetQueryUrl(query_);
154  people_search_->Start(url);
155}
156
157void PeopleProvider::OnPeopleSearchFetched(
158    scoped_ptr<base::DictionaryValue> json) {
159  ProcessPeopleSearchResults(json.get());
160
161  if (!people_search_fetched_callback_.is_null())
162    people_search_fetched_callback_.Run();
163}
164
165void PeopleProvider::ProcessPeopleSearchResults(
166    const base::DictionaryValue* json) {
167  const base::ListValue* item_list = NULL;
168  if (!json ||
169      !json->GetList(kKeyItems, &item_list) ||
170      !item_list ||
171      item_list->empty()) {
172    return;
173  }
174
175  ClearResults();
176  for (ListValue::const_iterator it = item_list->begin();
177       it != item_list->end();
178       ++it) {
179    const base::DictionaryValue* dict;
180    if (!(*it)->GetAsDictionary(&dict))
181      continue;
182
183    scoped_ptr<ChromeSearchResult> result(CreateResult(*dict));
184    if (!result)
185      continue;
186
187    Add(result.Pass());
188  }
189}
190
191scoped_ptr<ChromeSearchResult> PeopleProvider::CreateResult(
192    const base::DictionaryValue& dict) {
193  scoped_ptr<ChromeSearchResult> result;
194
195  std::string id;
196  if (!dict.GetString(kKeyId, &id))
197    return result.Pass();
198
199  // Get the display name.
200  const base::ListValue* names;
201  if (!dict.GetList(kKeyNames, &names))
202    return result.Pass();
203  std::string display_name;
204  display_name = GetFirstValue(*names, kKeyDisplayName);
205
206  // Get the interaction rank.
207  const base::DictionaryValue* sort_keys;
208  if (!dict.GetDictionary(kKeySortKeys, &sort_keys))
209    return result.Pass();
210  std::string interaction_rank_string;
211  if (!sort_keys->GetString(kKeyInteractionRank, &interaction_rank_string))
212    return result.Pass();
213
214  double interaction_rank;
215  if (!base::StringToDouble(interaction_rank_string, &interaction_rank))
216    return result.Pass();
217
218  // If there has been no interaction with this user, the result
219  // is meaningless, hence discard it.
220  if (interaction_rank == 0.0)
221    return result.Pass();
222
223  // Get the image URL.
224  const base::ListValue* images;
225  if (!dict.GetList(kKeyImages, &images))
226    return result.Pass();
227  std::string image_url_string;
228  image_url_string = GetFirstValue(*images, kKeyUrl);
229
230  if (id.empty() ||
231      display_name.empty() ||
232      interaction_rank_string.empty() ||
233      image_url_string.empty()) {
234    return result.Pass();
235  }
236
237  GURL image_url(image_url_string);
238  if (!image_url.is_valid())
239    return result.Pass();
240
241  result.reset(new PeopleResult(
242      profile_, id, display_name, interaction_rank, image_url));
243  return result.Pass();
244}
245
246void PeopleProvider::SetupForTest(
247    const base::Closure& people_search_fetched_callback,
248    const GURL& people_search_url) {
249  people_search_fetched_callback_ = people_search_fetched_callback;
250  people_search_url_ = people_search_url;
251  skip_request_token_for_test_ = true;
252}
253
254}  // namespace app_list
255