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