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