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.
5 #include "chrome/browser/chromeos/app_mode/kiosk_app_data.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_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 "chrome/common/extensions/extension_constants.h"
27 #include "content/public/browser/browser_thread.h"
28 #include "extensions/browser/extension_system.h"
29 #include "extensions/browser/image_loader.h"
30 #include "extensions/common/constants.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"
37 using content::BrowserThread;
43 // Keys for local state data. See sample layout in KioskAppManager.
44 const char kKeyName[] = "name";
45 const char kKeyIcon[] = "icon";
47 const char kInvalidWebstoreResponseError[] = "Invalid Chrome Web Store reponse";
49 // Icon file extension.
50 const char kIconFileExtension[] = ".png";
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());
58 base::FilePath dir = icon_path.DirName();
59 if (!base::PathExists(dir))
60 CHECK(base::CreateDirectory(dir));
62 CHECK_EQ(static_cast<int>(raw_icon->size()),
63 base::WriteFile(icon_path,
64 raw_icon->data().c_str(), raw_icon->size()));
67 // Returns true for valid kiosk app manifest.
68 bool IsValidKioskAppManifest(const extensions::Manifest& manifest) {
70 if (manifest.GetBoolean(extensions::manifest_keys::kKioskEnabled,
78 std::string ValueToString(const base::Value* value) {
80 base::JSONWriter::Write(value, &json);
86 ////////////////////////////////////////////////////////////////////////////////
87 // KioskAppData::IconLoader
88 // Loads locally stored icon data and decode it.
90 class KioskAppData::IconLoader : public ImageDecoder::Delegate {
98 IconLoader(const base::WeakPtr<KioskAppData>& client,
99 const base::FilePath& icon_path)
101 icon_path_(icon_path),
102 load_result_(SUCCESS) {}
105 base::SequencedWorkerPool* pool = BrowserThread::GetBlockingPool();
106 base::SequencedWorkerPool::SequenceToken token = pool->GetSequenceToken();
107 task_runner_ = pool->GetSequencedTaskRunnerWithShutdownBehavior(
109 base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
110 task_runner_->PostTask(FROM_HERE,
111 base::Bind(&IconLoader::LoadOnBlockingPool,
112 base::Unretained(this)));
116 friend class base::RefCountedThreadSafe<IconLoader>;
118 virtual ~IconLoader() {}
120 // Loads the icon from locally stored |icon_path_| on the blocking pool
121 void LoadOnBlockingPool() {
122 DCHECK(task_runner_->RunsTasksOnCurrentThread());
125 if (!base::ReadFileToString(base::FilePath(icon_path_), &data)) {
126 ReportResultOnBlockingPool(FAILED_TO_LOAD);
129 raw_icon_ = base::RefCountedString::TakeString(&data);
131 scoped_refptr<ImageDecoder> image_decoder = new ImageDecoder(
132 this, raw_icon_->data(), ImageDecoder::DEFAULT_CODEC);
133 image_decoder->Start(task_runner_);
136 void ReportResultOnBlockingPool(LoadResult result) {
137 DCHECK(task_runner_->RunsTasksOnCurrentThread());
139 load_result_ = result;
140 BrowserThread::PostTask(
143 base::Bind(&IconLoader::ReportResultOnUIThread,
144 base::Unretained(this)));
147 void NotifyClient() {
151 if (load_result_ == SUCCESS)
152 client_->OnIconLoadSuccess(raw_icon_, icon_);
154 client_->OnIconLoadFailure();
157 void ReportResultOnUIThread() {
158 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
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);
172 virtual void OnDecodeImageFailed(const ImageDecoder* decoder) OVERRIDE {
173 ReportResultOnBlockingPool(FAILED_TO_DECODE);
176 base::WeakPtr<KioskAppData> client_;
177 base::FilePath icon_path_;
179 LoadResult load_result_;
180 scoped_refptr<base::SequencedTaskRunner> task_runner_;
182 gfx::ImageSkia icon_;
183 scoped_refptr<base::RefCountedString> raw_icon_;
185 DISALLOW_COPY_AND_ASSIGN(IconLoader);
188 ////////////////////////////////////////////////////////////////////////////////
189 // KioskAppData::WebstoreDataParser
190 // Use WebstoreInstallHelper to parse the manifest and decode the icon.
192 class KioskAppData::WebstoreDataParser
193 : public extensions::WebstoreInstallHelper::Delegate {
195 explicit WebstoreDataParser(const base::WeakPtr<KioskAppData>& client)
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,
209 webstore_helper->Start();
213 friend class base::RefCounted<WebstoreDataParser>;
215 virtual ~WebstoreDataParser() {}
217 void ReportFailure() {
219 client_->OnWebstoreParseFailure();
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));
234 if (!IsValidKioskAppManifest(manifest)) {
240 client_->OnWebstoreParseSuccess(icon);
243 virtual void OnWebstoreParseFailure(
244 const std::string& id,
245 InstallHelperResultCode result_code,
246 const std::string& error_message) OVERRIDE {
250 base::WeakPtr<KioskAppData> client_;
252 DISALLOW_COPY_AND_ASSIGN(WebstoreDataParser);
255 ////////////////////////////////////////////////////////////////////////////////
258 KioskAppData::KioskAppData(KioskAppDataDelegate* delegate,
259 const std::string& app_id,
260 const std::string& user_id)
261 : delegate_(delegate),
262 status_(STATUS_INIT),
267 KioskAppData::~KioskAppData() {}
269 void KioskAppData::Load() {
270 SetStatus(STATUS_LOADING);
278 void KioskAppData::ClearCache() {
279 PrefService* local_state = g_browser_process->local_state();
281 DictionaryPrefUpdate dict_update(local_state,
282 KioskAppManager::kKioskDictionaryName);
284 std::string app_key = std::string(KioskAppManager::kKeyApps) + '.' + app_id_;
285 dict_update->Remove(app_key, NULL);
287 if (!icon_path_.empty()) {
288 BrowserThread::PostBlockingPoolTask(
290 base::Bind(base::IgnoreResult(&base::DeleteFile), icon_path_, false));
294 void KioskAppData::LoadFromInstalledApp(Profile* profile,
295 const extensions::Extension* app) {
296 SetStatus(STATUS_LOADING);
299 app = extensions::ExtensionSystem::Get(profile)
300 ->extension_service()
301 ->GetInstalledExtension(app_id_);
304 DCHECK_EQ(app_id_, app->id());
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()));
316 bool KioskAppData::IsLoading() const {
317 return status_ == STATUS_LOADING;
320 void KioskAppData::SetStatus(Status status) {
321 if (status_ == status)
334 delegate_->OnKioskAppDataChanged(app_id_);
337 delegate_->OnKioskAppDataLoadFailure(app_id_);
342 net::URLRequestContextGetter* KioskAppData::GetRequestContextGetter() {
343 return g_browser_process->system_request_context();
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;
351 PrefService* local_state = g_browser_process->local_state();
352 const base::DictionaryValue* dict =
353 local_state->GetDictionary(KioskAppManager::kKioskDictionaryName);
356 std::string icon_path_string;
357 if (!dict->GetString(name_key, &name_) ||
358 !dict->GetString(icon_path_key, &icon_path_string)) {
361 icon_path_ = base::FilePath(icon_path_string);
363 // IconLoader deletes itself when done.
364 (new IconLoader(AsWeakPtr(), icon_path_))->Start();
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;
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;
382 void KioskAppData::SetCache(const std::string& name, const SkBitmap& icon) {
383 icon_ = gfx::ImageSkia::CreateFrom1xBitmap(icon);
384 icon_.MakeThreadSafe();
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());
391 base::FilePath cache_dir;
393 delegate_->GetKioskAppIconCacheDir(&cache_dir);
395 base::FilePath icon_path =
396 cache_dir.AppendASCII(app_id_).AddExtension(kIconFileExtension);
397 BrowserThread::GetBlockingPool()->PostTask(
399 base::Bind(&SaveIconToLocalOnBlockingPool, icon_path, raw_icon_));
401 SetCache(name, icon_path);
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());
410 SetCache(name_, icon.AsBitmap());
413 SetStatus(STATUS_LOADED);
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;
422 SetStatus(STATUS_LOADED);
425 void KioskAppData::OnIconLoadFailure() {
426 // Re-fetch data from web store when failed to load cached data.
430 void KioskAppData::OnWebstoreParseSuccess(const SkBitmap& icon) {
431 SetCache(name_, icon);
432 SetStatus(STATUS_LOADED);
435 void KioskAppData::OnWebstoreParseFailure() {
436 SetStatus(STATUS_ERROR);
439 void KioskAppData::StartFetch() {
440 webstore_fetcher_.reset(new extensions::WebstoreDataFetcher(
442 GetRequestContextGetter(),
445 webstore_fetcher_->set_max_auto_retries(3);
446 webstore_fetcher_->Start();
449 void KioskAppData::OnWebstoreRequestFailure() {
450 SetStatus(STATUS_ERROR);
453 void KioskAppData::OnWebstoreResponseParseSuccess(
454 scoped_ptr<base::DictionaryValue> webstore_data) {
455 // Takes ownership of |webstore_data|.
456 webstore_fetcher_.reset();
458 std::string manifest;
459 if (!CheckResponseKeyValue(webstore_data.get(), kManifestKey, &manifest))
462 if (!CheckResponseKeyValue(webstore_data.get(), kLocalizedNameKey, &name_))
465 std::string icon_url_string;
466 if (!CheckResponseKeyValue(webstore_data.get(), kIconUrlKey,
470 GURL icon_url = GURL(extension_urls::GetWebstoreLaunchURL()).Resolve(
472 if (!icon_url.is_valid()) {
473 LOG(ERROR) << "Webstore response error (icon url): "
474 << ValueToString(webstore_data.get());
475 OnWebstoreResponseParseFailure(kInvalidWebstoreResponseError);
479 // WebstoreDataParser deletes itself when done.
480 (new WebstoreDataParser(AsWeakPtr()))->Start(app_id_,
483 GetRequestContextGetter());
486 void KioskAppData::OnWebstoreResponseParseFailure(const std::string& error) {
487 LOG(ERROR) << "Webstore failed for kiosk app " << app_id_
489 webstore_fetcher_.reset();
490 SetStatus(STATUS_ERROR);
493 bool KioskAppData::CheckResponseKeyValue(const base::DictionaryValue* response,
495 std::string* value) {
496 if (!response->GetString(key, value)) {
497 LOG(ERROR) << "Webstore response error (" << key
498 << "): " << ValueToString(response);
499 OnWebstoreResponseParseFailure(kInvalidWebstoreResponseError);
505 } // namespace chromeos