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