Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(818)

Side by Side Diff: chrome/browser/gaia_userinfo/profile_downloader.cc

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

Powered by Google App Engine
This is Rietveld 408576698