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/chromeos/app_mode/kiosk_app_data.h" 6 7 #include <vector> 8 9 #include "base/bind.h" 10 #include "base/files/file_util.h" 11 #include "base/json/json_writer.h" 12 #include "base/memory/ref_counted_memory.h" 13 #include "base/prefs/pref_service.h" 14 #include "base/prefs/scoped_user_pref_update.h" 15 #include "base/threading/sequenced_worker_pool.h" 16 #include "base/values.h" 17 #include "chrome/browser/browser_process.h" 18 #include "chrome/browser/chromeos/app_mode/kiosk_app_data_delegate.h" 19 #include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h" 20 #include "chrome/browser/extensions/extension_service.h" 21 #include "chrome/browser/extensions/extension_util.h" 22 #include "chrome/browser/extensions/webstore_data_fetcher.h" 23 #include "chrome/browser/extensions/webstore_install_helper.h" 24 #include "chrome/browser/image_decoder.h" 25 #include "chrome/browser/profiles/profile.h" 26 #include "content/public/browser/browser_thread.h" 27 #include "extensions/browser/extension_system.h" 28 #include "extensions/browser/image_loader.h" 29 #include "extensions/common/constants.h" 30 #include "extensions/common/extension_urls.h" 31 #include "extensions/common/manifest.h" 32 #include "extensions/common/manifest_constants.h" 33 #include "extensions/common/manifest_handlers/icons_handler.h" 34 #include "ui/gfx/codec/png_codec.h" 35 #include "ui/gfx/image/image.h" 36 37 using content::BrowserThread; 38 39 namespace chromeos { 40 41 namespace { 42 43 // Keys for local state data. See sample layout in KioskAppManager. 44 const char kKeyName[] = "name"; 45 const char kKeyIcon[] = "icon"; 46 47 const char kInvalidWebstoreResponseError[] = "Invalid Chrome Web Store reponse"; 48 49 // Icon file extension. 50 const char kIconFileExtension[] = ".png"; 51 52 // Save |raw_icon| for given |app_id|. 53 void SaveIconToLocalOnBlockingPool( 54 const base::FilePath& icon_path, 55 scoped_refptr<base::RefCountedString> raw_icon) { 56 DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); 57 58 base::FilePath dir = icon_path.DirName(); 59 if (!base::PathExists(dir)) 60 CHECK(base::CreateDirectory(dir)); 61 62 CHECK_EQ(static_cast<int>(raw_icon->size()), 63 base::WriteFile(icon_path, 64 raw_icon->data().c_str(), raw_icon->size())); 65 } 66 67 // Returns true for valid kiosk app manifest. 68 bool IsValidKioskAppManifest(const extensions::Manifest& manifest) { 69 bool kiosk_enabled; 70 if (manifest.GetBoolean(extensions::manifest_keys::kKioskEnabled, 71 &kiosk_enabled)) { 72 return kiosk_enabled; 73 } 74 75 return false; 76 } 77 78 std::string ValueToString(const base::Value* value) { 79 std::string json; 80 base::JSONWriter::Write(value, &json); 81 return json; 82 } 83 84 } // namespace 85 86 //////////////////////////////////////////////////////////////////////////////// 87 // KioskAppData::IconLoader 88 // Loads locally stored icon data and decode it. 89 90 class KioskAppData::IconLoader : public ImageDecoder::Delegate { 91 public: 92 enum LoadResult { 93 SUCCESS, 94 FAILED_TO_LOAD, 95 FAILED_TO_DECODE, 96 }; 97 98 IconLoader(const base::WeakPtr<KioskAppData>& client, 99 const base::FilePath& icon_path) 100 : client_(client), 101 icon_path_(icon_path), 102 load_result_(SUCCESS) {} 103 104 void Start() { 105 base::SequencedWorkerPool* pool = BrowserThread::GetBlockingPool(); 106 base::SequencedWorkerPool::SequenceToken token = pool->GetSequenceToken(); 107 task_runner_ = pool->GetSequencedTaskRunnerWithShutdownBehavior( 108 token, 109 base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN); 110 task_runner_->PostTask(FROM_HERE, 111 base::Bind(&IconLoader::LoadOnBlockingPool, 112 base::Unretained(this))); 113 } 114 115 private: 116 friend class base::RefCountedThreadSafe<IconLoader>; 117 118 virtual ~IconLoader() {} 119 120 // Loads the icon from locally stored |icon_path_| on the blocking pool 121 void LoadOnBlockingPool() { 122 DCHECK(task_runner_->RunsTasksOnCurrentThread()); 123 124 std::string data; 125 if (!base::ReadFileToString(base::FilePath(icon_path_), &data)) { 126 ReportResultOnBlockingPool(FAILED_TO_LOAD); 127 return; 128 } 129 raw_icon_ = base::RefCountedString::TakeString(&data); 130 131 scoped_refptr<ImageDecoder> image_decoder = new ImageDecoder( 132 this, raw_icon_->data(), ImageDecoder::DEFAULT_CODEC); 133 image_decoder->Start(task_runner_); 134 } 135 136 void ReportResultOnBlockingPool(LoadResult result) { 137 DCHECK(task_runner_->RunsTasksOnCurrentThread()); 138 139 load_result_ = result; 140 BrowserThread::PostTask( 141 BrowserThread::UI, 142 FROM_HERE, 143 base::Bind(&IconLoader::ReportResultOnUIThread, 144 base::Unretained(this))); 145 } 146 147 void NotifyClient() { 148 if (!client_) 149 return; 150 151 if (load_result_ == SUCCESS) 152 client_->OnIconLoadSuccess(raw_icon_, icon_); 153 else 154 client_->OnIconLoadFailure(); 155 } 156 157 void ReportResultOnUIThread() { 158 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 159 160 NotifyClient(); 161 delete this; 162 } 163 164 // ImageDecoder::Delegate overrides: 165 virtual void OnImageDecoded(const ImageDecoder* decoder, 166 const SkBitmap& decoded_image) OVERRIDE { 167 icon_ = gfx::ImageSkia::CreateFrom1xBitmap(decoded_image); 168 icon_.MakeThreadSafe(); 169 ReportResultOnBlockingPool(SUCCESS); 170 } 171 172 virtual void OnDecodeImageFailed(const ImageDecoder* decoder) OVERRIDE { 173 ReportResultOnBlockingPool(FAILED_TO_DECODE); 174 } 175 176 base::WeakPtr<KioskAppData> client_; 177 base::FilePath icon_path_; 178 179 LoadResult load_result_; 180 scoped_refptr<base::SequencedTaskRunner> task_runner_; 181 182 gfx::ImageSkia icon_; 183 scoped_refptr<base::RefCountedString> raw_icon_; 184 185 DISALLOW_COPY_AND_ASSIGN(IconLoader); 186 }; 187 188 //////////////////////////////////////////////////////////////////////////////// 189 // KioskAppData::WebstoreDataParser 190 // Use WebstoreInstallHelper to parse the manifest and decode the icon. 191 192 class KioskAppData::WebstoreDataParser 193 : public extensions::WebstoreInstallHelper::Delegate { 194 public: 195 explicit WebstoreDataParser(const base::WeakPtr<KioskAppData>& client) 196 : client_(client) {} 197 198 void Start(const std::string& app_id, 199 const std::string& manifest, 200 const GURL& icon_url, 201 net::URLRequestContextGetter* context_getter) { 202 scoped_refptr<extensions::WebstoreInstallHelper> webstore_helper = 203 new extensions::WebstoreInstallHelper(this, 204 app_id, 205 manifest, 206 "", // No icon data. 207 icon_url, 208 context_getter); 209 webstore_helper->Start(); 210 } 211 212 private: 213 friend class base::RefCounted<WebstoreDataParser>; 214 215 virtual ~WebstoreDataParser() {} 216 217 void ReportFailure() { 218 if (client_) 219 client_->OnWebstoreParseFailure(); 220 221 delete this; 222 } 223 224 // WebstoreInstallHelper::Delegate overrides: 225 virtual void OnWebstoreParseSuccess( 226 const std::string& id, 227 const SkBitmap& icon, 228 base::DictionaryValue* parsed_manifest) OVERRIDE { 229 // Takes ownership of |parsed_manifest|. 230 extensions::Manifest manifest( 231 extensions::Manifest::INVALID_LOCATION, 232 scoped_ptr<base::DictionaryValue>(parsed_manifest)); 233 234 if (!IsValidKioskAppManifest(manifest)) { 235 ReportFailure(); 236 return; 237 } 238 239 if (client_) 240 client_->OnWebstoreParseSuccess(icon); 241 delete this; 242 } 243 virtual void OnWebstoreParseFailure( 244 const std::string& id, 245 InstallHelperResultCode result_code, 246 const std::string& error_message) OVERRIDE { 247 ReportFailure(); 248 } 249 250 base::WeakPtr<KioskAppData> client_; 251 252 DISALLOW_COPY_AND_ASSIGN(WebstoreDataParser); 253 }; 254 255 //////////////////////////////////////////////////////////////////////////////// 256 // KioskAppData 257 258 KioskAppData::KioskAppData(KioskAppDataDelegate* delegate, 259 const std::string& app_id, 260 const std::string& user_id) 261 : delegate_(delegate), 262 status_(STATUS_INIT), 263 app_id_(app_id), 264 user_id_(user_id) { 265 } 266 267 KioskAppData::~KioskAppData() {} 268 269 void KioskAppData::Load() { 270 SetStatus(STATUS_LOADING); 271 272 if (LoadFromCache()) 273 return; 274 275 StartFetch(); 276 } 277 278 void KioskAppData::ClearCache() { 279 PrefService* local_state = g_browser_process->local_state(); 280 281 DictionaryPrefUpdate dict_update(local_state, 282 KioskAppManager::kKioskDictionaryName); 283 284 std::string app_key = std::string(KioskAppManager::kKeyApps) + '.' + app_id_; 285 dict_update->Remove(app_key, NULL); 286 287 if (!icon_path_.empty()) { 288 BrowserThread::PostBlockingPoolTask( 289 FROM_HERE, 290 base::Bind(base::IgnoreResult(&base::DeleteFile), icon_path_, false)); 291 } 292 } 293 294 void KioskAppData::LoadFromInstalledApp(Profile* profile, 295 const extensions::Extension* app) { 296 SetStatus(STATUS_LOADING); 297 298 if (!app) { 299 app = extensions::ExtensionSystem::Get(profile) 300 ->extension_service() 301 ->GetInstalledExtension(app_id_); 302 } 303 304 DCHECK_EQ(app_id_, app->id()); 305 306 name_ = app->name(); 307 308 const int kIconSize = extension_misc::EXTENSION_ICON_LARGE; 309 extensions::ExtensionResource image = extensions::IconsInfo::GetIconResource( 310 app, kIconSize, ExtensionIconSet::MATCH_BIGGER); 311 extensions::ImageLoader::Get(profile)->LoadImageAsync( 312 app, image, gfx::Size(kIconSize, kIconSize), 313 base::Bind(&KioskAppData::OnExtensionIconLoaded, AsWeakPtr())); 314 } 315 316 bool KioskAppData::IsLoading() const { 317 return status_ == STATUS_LOADING; 318 } 319 320 void KioskAppData::SetStatus(Status status) { 321 if (status_ == status) 322 return; 323 324 status_ = status; 325 326 if (!delegate_) 327 return; 328 329 switch (status_) { 330 case STATUS_INIT: 331 break; 332 case STATUS_LOADING: 333 case STATUS_LOADED: 334 delegate_->OnKioskAppDataChanged(app_id_); 335 break; 336 case STATUS_ERROR: 337 delegate_->OnKioskAppDataLoadFailure(app_id_); 338 break; 339 } 340 } 341 342 net::URLRequestContextGetter* KioskAppData::GetRequestContextGetter() { 343 return g_browser_process->system_request_context(); 344 } 345 346 bool KioskAppData::LoadFromCache() { 347 std::string app_key = std::string(KioskAppManager::kKeyApps) + '.' + app_id_; 348 std::string name_key = app_key + '.' + kKeyName; 349 std::string icon_path_key = app_key + '.' + kKeyIcon; 350 351 PrefService* local_state = g_browser_process->local_state(); 352 const base::DictionaryValue* dict = 353 local_state->GetDictionary(KioskAppManager::kKioskDictionaryName); 354 355 icon_path_.clear(); 356 std::string icon_path_string; 357 if (!dict->GetString(name_key, &name_) || 358 !dict->GetString(icon_path_key, &icon_path_string)) { 359 return false; 360 } 361 icon_path_ = base::FilePath(icon_path_string); 362 363 // IconLoader deletes itself when done. 364 (new IconLoader(AsWeakPtr(), icon_path_))->Start(); 365 return true; 366 } 367 368 void KioskAppData::SetCache(const std::string& name, 369 const base::FilePath& icon_path) { 370 std::string app_key = std::string(KioskAppManager::kKeyApps) + '.' + app_id_; 371 std::string name_key = app_key + '.' + kKeyName; 372 std::string icon_path_key = app_key + '.' + kKeyIcon; 373 374 PrefService* local_state = g_browser_process->local_state(); 375 DictionaryPrefUpdate dict_update(local_state, 376 KioskAppManager::kKioskDictionaryName); 377 dict_update->SetString(name_key, name); 378 dict_update->SetString(icon_path_key, icon_path.value()); 379 icon_path_ = icon_path; 380 } 381 382 void KioskAppData::SetCache(const std::string& name, const SkBitmap& icon) { 383 icon_ = gfx::ImageSkia::CreateFrom1xBitmap(icon); 384 icon_.MakeThreadSafe(); 385 386 std::vector<unsigned char> image_data; 387 CHECK(gfx::PNGCodec::EncodeBGRASkBitmap(icon, false, &image_data)); 388 raw_icon_ = new base::RefCountedString; 389 raw_icon_->data().assign(image_data.begin(), image_data.end()); 390 391 base::FilePath cache_dir; 392 if (delegate_) 393 delegate_->GetKioskAppIconCacheDir(&cache_dir); 394 395 base::FilePath icon_path = 396 cache_dir.AppendASCII(app_id_).AddExtension(kIconFileExtension); 397 BrowserThread::GetBlockingPool()->PostTask( 398 FROM_HERE, 399 base::Bind(&SaveIconToLocalOnBlockingPool, icon_path, raw_icon_)); 400 401 SetCache(name, icon_path); 402 } 403 404 void KioskAppData::OnExtensionIconLoaded(const gfx::Image& icon) { 405 if (icon.IsEmpty()) { 406 LOG(WARNING) << "Failed to load icon from installed app" 407 << ", id=" << app_id_; 408 SetCache(name_, *extensions::util::GetDefaultAppIcon().bitmap()); 409 } else { 410 SetCache(name_, icon.AsBitmap()); 411 } 412 413 SetStatus(STATUS_LOADED); 414 } 415 416 void KioskAppData::OnIconLoadSuccess( 417 const scoped_refptr<base::RefCountedString>& raw_icon, 418 const gfx::ImageSkia& icon) { 419 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 420 raw_icon_ = raw_icon; 421 icon_ = icon; 422 SetStatus(STATUS_LOADED); 423 } 424 425 void KioskAppData::OnIconLoadFailure() { 426 // Re-fetch data from web store when failed to load cached data. 427 StartFetch(); 428 } 429 430 void KioskAppData::OnWebstoreParseSuccess(const SkBitmap& icon) { 431 SetCache(name_, icon); 432 SetStatus(STATUS_LOADED); 433 } 434 435 void KioskAppData::OnWebstoreParseFailure() { 436 SetStatus(STATUS_ERROR); 437 } 438 439 void KioskAppData::StartFetch() { 440 webstore_fetcher_.reset(new extensions::WebstoreDataFetcher( 441 this, 442 GetRequestContextGetter(), 443 GURL(), 444 app_id_)); 445 webstore_fetcher_->set_max_auto_retries(3); 446 webstore_fetcher_->Start(); 447 } 448 449 void KioskAppData::OnWebstoreRequestFailure() { 450 SetStatus(STATUS_ERROR); 451 } 452 453 void KioskAppData::OnWebstoreResponseParseSuccess( 454 scoped_ptr<base::DictionaryValue> webstore_data) { 455 // Takes ownership of |webstore_data|. 456 webstore_fetcher_.reset(); 457 458 std::string manifest; 459 if (!CheckResponseKeyValue(webstore_data.get(), kManifestKey, &manifest)) 460 return; 461 462 if (!CheckResponseKeyValue(webstore_data.get(), kLocalizedNameKey, &name_)) 463 return; 464 465 std::string icon_url_string; 466 if (!CheckResponseKeyValue(webstore_data.get(), kIconUrlKey, 467 &icon_url_string)) 468 return; 469 470 GURL icon_url = GURL(extension_urls::GetWebstoreLaunchURL()).Resolve( 471 icon_url_string); 472 if (!icon_url.is_valid()) { 473 LOG(ERROR) << "Webstore response error (icon url): " 474 << ValueToString(webstore_data.get()); 475 OnWebstoreResponseParseFailure(kInvalidWebstoreResponseError); 476 return; 477 } 478 479 // WebstoreDataParser deletes itself when done. 480 (new WebstoreDataParser(AsWeakPtr()))->Start(app_id_, 481 manifest, 482 icon_url, 483 GetRequestContextGetter()); 484 } 485 486 void KioskAppData::OnWebstoreResponseParseFailure(const std::string& error) { 487 LOG(ERROR) << "Webstore failed for kiosk app " << app_id_ 488 << ", " << error; 489 webstore_fetcher_.reset(); 490 SetStatus(STATUS_ERROR); 491 } 492 493 bool KioskAppData::CheckResponseKeyValue(const base::DictionaryValue* response, 494 const char* key, 495 std::string* value) { 496 if (!response->GetString(key, value)) { 497 LOG(ERROR) << "Webstore response error (" << key 498 << "): " << ValueToString(response); 499 OnWebstoreResponseParseFailure(kInvalidWebstoreResponseError); 500 return false; 501 } 502 return true; 503 } 504 505 } // namespace chromeos 506