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