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