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