OLD | NEW |
| (Empty) |
1 // Copyright (c) 2011 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/chromeos/login/profile_image_downloader.h" | |
6 | |
7 #include <string> | |
8 #include <vector> | |
9 | |
10 #include "base/json/json_reader.h" | |
11 #include "base/logging.h" | |
12 #include "base/message_loop.h" | |
13 #include "base/string_split.h" | |
14 #include "base/string_util.h" | |
15 #include "base/stringprintf.h" | |
16 #include "chrome/browser/chromeos/login/helper.h" | |
17 #include "chrome/browser/chromeos/login/user_manager.h" | |
18 #include "chrome/browser/net/gaia/token_service.h" | |
19 #include "chrome/browser/profiles/profile_manager.h" | |
20 #include "chrome/common/chrome_notification_types.h" | |
21 #include "chrome/common/net/gaia/gaia_constants.h" | |
22 #include "content/public/browser/browser_thread.h" | |
23 #include "content/public/browser/notification_details.h" | |
24 #include "content/public/browser/notification_observer.h" | |
25 #include "content/public/browser/notification_registrar.h" | |
26 #include "content/public/browser/notification_source.h" | |
27 #include "content/public/browser/notification_types.h" | |
28 #include "content/public/common/url_fetcher.h" | |
29 #include "googleurl/src/gurl.h" | |
30 #include "skia/ext/image_operations.h" | |
31 | |
32 using content::BrowserThread; | |
33 | |
34 namespace chromeos { | |
35 | |
36 namespace { | |
37 | |
38 // Template for optional authorization header. | |
39 const char kAuthorizationHeader[] = "Authorization: GoogleLogin auth=%s"; | |
40 | |
41 // URL requesting Picasa API for user info. | |
42 const char kUserEntryURL[] = | |
43 "http://picasaweb.google.com/data/entry/api/user/default?alt=json"; | |
44 // Path in JSON dictionary to user's photo thumbnail URL. | |
45 const char kPhotoThumbnailURLPath[] = "entry.gphoto$thumbnail.$t"; | |
46 // Path format for specifying thumbnail's size. | |
47 const char kThumbnailSizeFormat[] = "s%d-c"; | |
48 // Default Picasa thumbnail size. | |
49 const int kDefaultThumbnailSize = 64; | |
50 | |
51 // Separator of URL path components. | |
52 const char kURLPathSeparator = '/'; | |
53 | |
54 // Photo ID of the Picasa Web Albums profile picture (base64 of 0). | |
55 const char kPicasaPhotoId[] = "AAAAAAAAAAA"; | |
56 | |
57 // Photo version of the default PWA profile picture (base64 of 1). | |
58 const char kDefaultPicasaPhotoVersion[] = "AAAAAAAAAAE"; | |
59 | |
60 // Photo ID of the Google+ profile picture (base64 of 2). | |
61 const char kGooglePlusPhotoId[] = "AAAAAAAAAAI"; | |
62 | |
63 // Photo version of the default Google+ profile picture (base64 of 0). | |
64 const char kDefaultGooglePlusPhotoVersion[] = "AAAAAAAAAAA"; | |
65 | |
66 // Number of path components in profile picture URL. | |
67 const size_t kProfileImageURLPathComponentsCount = 7; | |
68 | |
69 // Index of path component with photo ID. | |
70 const int kPhotoIdPathComponentIndex = 2; | |
71 | |
72 // Index of path component with photo version. | |
73 const int kPhotoVersionPathComponentIndex = 3; | |
74 | |
75 } // namespace | |
76 | |
77 std::string ProfileImageDownloader::GetProfileImageURL( | |
78 const std::string& data) const { | |
79 int error_code = -1; | |
80 std::string error_message; | |
81 scoped_ptr<base::Value> root_value(base::JSONReader::ReadAndReturnError( | |
82 data, false, &error_code, &error_message)); | |
83 if (!root_value.get()) { | |
84 LOG(ERROR) << "Error while parsing Picasa user entry response: " | |
85 << error_message; | |
86 return std::string(); | |
87 } | |
88 if (!root_value->IsType(base::Value::TYPE_DICTIONARY)) { | |
89 LOG(ERROR) << "JSON root is not a dictionary: " | |
90 << root_value->GetType(); | |
91 return std::string(); | |
92 } | |
93 base::DictionaryValue* root_dictionary = | |
94 static_cast<base::DictionaryValue*>(root_value.get()); | |
95 | |
96 std::string thumbnail_url_string; | |
97 if (!root_dictionary->GetString( | |
98 kPhotoThumbnailURLPath, &thumbnail_url_string)) { | |
99 LOG(ERROR) << "Can't find thumbnail path in JSON data: " | |
100 << data; | |
101 return std::string(); | |
102 } | |
103 | |
104 // Try to change the size of thumbnail we are going to get. | |
105 // Typical URL looks like this: | |
106 // http://lh0.ggpht.com/-abcd1aBCDEf/AAAA/AAA_A/abc12/s64-c/1234567890.jpg | |
107 std::string default_thumbnail_size_path_component( | |
108 base::StringPrintf(kThumbnailSizeFormat, kDefaultThumbnailSize)); | |
109 std::string new_thumbnail_size_path_component( | |
110 base::StringPrintf(kThumbnailSizeFormat, login::kUserImageSize)); | |
111 size_t thumbnail_size_pos = | |
112 thumbnail_url_string.find(default_thumbnail_size_path_component); | |
113 if (thumbnail_size_pos != std::string::npos) { | |
114 size_t thumbnail_size_end = | |
115 thumbnail_size_pos + default_thumbnail_size_path_component.size(); | |
116 thumbnail_url_string = | |
117 thumbnail_url_string.substr(0, thumbnail_size_pos) + | |
118 new_thumbnail_size_path_component + | |
119 thumbnail_url_string.substr( | |
120 thumbnail_size_end, | |
121 thumbnail_url_string.size() - thumbnail_size_end); | |
122 } else { | |
123 LOG(WARNING) << "Hasn't found thumbnail size part in image URL: " | |
124 << thumbnail_url_string; | |
125 // Use the thumbnail URL we have. | |
126 } | |
127 | |
128 GURL thumbnail_url(thumbnail_url_string); | |
129 if (!thumbnail_url.is_valid()) { | |
130 LOG(ERROR) << "Thumbnail URL is not valid: " << thumbnail_url_string; | |
131 return std::string(); | |
132 } | |
133 return thumbnail_url.spec(); | |
134 } | |
135 | |
136 bool ProfileImageDownloader::IsDefaultProfileImageURL( | |
137 const std::string& url) const { | |
138 | |
139 GURL image_url_object(url); | |
140 DCHECK(image_url_object.is_valid()); | |
141 VLOG(1) << "URL to check for default image: " << image_url_object.spec(); | |
142 std::vector<std::string> path_components; | |
143 base::SplitString(image_url_object.path(), | |
144 kURLPathSeparator, | |
145 &path_components); | |
146 | |
147 if (path_components.size() != kProfileImageURLPathComponentsCount) | |
148 return false; | |
149 | |
150 const std::string& photo_id = path_components[kPhotoIdPathComponentIndex]; | |
151 const std::string& photo_version = | |
152 path_components[kPhotoVersionPathComponentIndex]; | |
153 | |
154 // There are at least two pairs of (ID, version) for the default photo: | |
155 // the default Google+ profile photo and the default Picasa profile photo. | |
156 return ((photo_id == kPicasaPhotoId && | |
157 photo_version == kDefaultPicasaPhotoVersion) || | |
158 (photo_id == kGooglePlusPhotoId && | |
159 photo_version == kDefaultGooglePlusPhotoVersion)); | |
160 } | |
161 | |
162 ProfileImageDownloader::ProfileImageDownloader(Delegate* delegate) | |
163 : delegate_(delegate) { | |
164 } | |
165 | |
166 void ProfileImageDownloader::Start() { | |
167 VLOG(1) << "Starting profile image downloader..."; | |
168 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
169 | |
170 TokenService* service = | |
171 ProfileManager::GetDefaultProfile()->GetTokenService(); | |
172 if (!service) { | |
173 // This can happen in some test paths. | |
174 LOG(WARNING) << "User has no token service"; | |
175 if (delegate_) | |
176 delegate_->OnDownloadFailure(); | |
177 return; | |
178 } | |
179 if (service->HasTokenForService(GaiaConstants::kPicasaService)) { | |
180 auth_token_ = | |
181 service->GetTokenForService(GaiaConstants::kPicasaService); | |
182 StartFetchingImage(); | |
183 } else { | |
184 registrar_.Add(this, | |
185 chrome::NOTIFICATION_TOKEN_AVAILABLE, | |
186 content::Source<TokenService>(service)); | |
187 registrar_.Add(this, | |
188 chrome::NOTIFICATION_TOKEN_REQUEST_FAILED, | |
189 content::Source<TokenService>(service)); | |
190 } | |
191 } | |
192 | |
193 void ProfileImageDownloader::StartFetchingImage() { | |
194 std::string email = UserManager::Get()->logged_in_user().email(); | |
195 if (email.empty()) | |
196 return; | |
197 VLOG(1) << "Fetching user entry with token: " << auth_token_; | |
198 user_entry_fetcher_.reset(content::URLFetcher::Create( | |
199 GURL(kUserEntryURL), content::URLFetcher::GET, this)); | |
200 user_entry_fetcher_->SetRequestContext( | |
201 ProfileManager::GetDefaultProfile()->GetRequestContext()); | |
202 if (!auth_token_.empty()) { | |
203 user_entry_fetcher_->SetExtraRequestHeaders( | |
204 base::StringPrintf(kAuthorizationHeader, auth_token_.c_str())); | |
205 } | |
206 user_entry_fetcher_->Start(); | |
207 } | |
208 | |
209 ProfileImageDownloader::~ProfileImageDownloader() {} | |
210 | |
211 void ProfileImageDownloader::OnURLFetchComplete( | |
212 const content::URLFetcher* source) { | |
213 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
214 std::string data; | |
215 source->GetResponseAsString(&data); | |
216 if (source->GetResponseCode() != 200) { | |
217 LOG(ERROR) << "Response code is " << source->GetResponseCode(); | |
218 LOG(ERROR) << "Url is " << source->GetURL().spec(); | |
219 LOG(ERROR) << "Data is " << data; | |
220 if (delegate_) | |
221 delegate_->OnDownloadFailure(); | |
222 return; | |
223 } | |
224 | |
225 if (source == user_entry_fetcher_.get()) { | |
226 std::string image_url = GetProfileImageURL(data); | |
227 if (image_url.empty()) { | |
228 if (delegate_) | |
229 delegate_->OnDownloadFailure(); | |
230 return; | |
231 } | |
232 if (IsDefaultProfileImageURL(image_url)) { | |
233 if (delegate_) | |
234 delegate_->OnDownloadDefaultImage(); | |
235 return; | |
236 } | |
237 VLOG(1) << "Fetching profile image from " << image_url; | |
238 profile_image_fetcher_.reset(content::URLFetcher::Create( | |
239 GURL(image_url), content::URLFetcher::GET, this)); | |
240 profile_image_fetcher_->SetRequestContext( | |
241 ProfileManager::GetDefaultProfile()->GetRequestContext()); | |
242 if (!auth_token_.empty()) { | |
243 profile_image_fetcher_->SetExtraRequestHeaders( | |
244 base::StringPrintf(kAuthorizationHeader, auth_token_.c_str())); | |
245 } | |
246 profile_image_fetcher_->Start(); | |
247 } else if (source == profile_image_fetcher_.get()) { | |
248 VLOG(1) << "Decoding the image..."; | |
249 scoped_refptr<ImageDecoder> image_decoder = new ImageDecoder( | |
250 this, data); | |
251 image_decoder->Start(); | |
252 } | |
253 } | |
254 | |
255 void ProfileImageDownloader::OnImageDecoded(const ImageDecoder* decoder, | |
256 const SkBitmap& decoded_image) { | |
257 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
258 SkBitmap resized_image = skia::ImageOperations::Resize( | |
259 decoded_image, | |
260 skia::ImageOperations::RESIZE_BEST, | |
261 login::kUserImageSize, | |
262 login::kUserImageSize); | |
263 if (delegate_) | |
264 delegate_->OnDownloadSuccess(resized_image); | |
265 } | |
266 | |
267 void ProfileImageDownloader::OnDecodeImageFailed(const ImageDecoder* decoder) { | |
268 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
269 if (delegate_) | |
270 delegate_->OnDownloadFailure(); | |
271 } | |
272 | |
273 void ProfileImageDownloader::Observe( | |
274 int type, | |
275 const content::NotificationSource& source, | |
276 const content::NotificationDetails& details) { | |
277 DCHECK(type == chrome::NOTIFICATION_TOKEN_AVAILABLE || | |
278 type == chrome::NOTIFICATION_TOKEN_REQUEST_FAILED); | |
279 | |
280 TokenService::TokenAvailableDetails* token_details = | |
281 content::Details<TokenService::TokenAvailableDetails>(details).ptr(); | |
282 if (type == chrome::NOTIFICATION_TOKEN_AVAILABLE) { | |
283 if (token_details->service() == GaiaConstants::kPicasaService) { | |
284 registrar_.RemoveAll(); | |
285 auth_token_ = token_details->token(); | |
286 StartFetchingImage(); | |
287 } | |
288 } else { | |
289 if (token_details->service() == GaiaConstants::kPicasaService) { | |
290 LOG(WARNING) << "ProfileImageDownloader: token request failed"; | |
291 if (delegate_) | |
292 delegate_->OnDownloadFailure(); | |
293 } | |
294 } | |
295 } | |
296 | |
297 } // namespace chromeos | |
OLD | NEW |