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/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/browser/sandboxed_unpacker.h"
30 #include "extensions/common/constants.h"
31 #include "extensions/common/extension_urls.h"
32 #include "extensions/common/manifest.h"
33 #include "extensions/common/manifest_constants.h"
34 #include "extensions/common/manifest_handlers/icons_handler.h"
35 #include "ui/gfx/codec/png_codec.h"
36 #include "ui/gfx/image/image.h"
38 using content::BrowserThread
;
44 // Keys for local state data. See sample layout in KioskAppManager.
45 const char kKeyName
[] = "name";
46 const char kKeyIcon
[] = "icon";
48 const char kInvalidWebstoreResponseError
[] = "Invalid Chrome Web Store reponse";
50 // Icon file extension.
51 const char kIconFileExtension
[] = ".png";
53 // Save |raw_icon| for given |app_id|.
54 void SaveIconToLocalOnBlockingPool(
55 const base::FilePath
& icon_path
,
56 scoped_refptr
<base::RefCountedString
> raw_icon
) {
57 DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
59 base::FilePath dir
= icon_path
.DirName();
60 if (!base::PathExists(dir
))
61 CHECK(base::CreateDirectory(dir
));
63 CHECK_EQ(static_cast<int>(raw_icon
->size()),
64 base::WriteFile(icon_path
,
65 raw_icon
->data().c_str(), raw_icon
->size()));
68 // Returns true for valid kiosk app manifest.
69 bool IsValidKioskAppManifest(const extensions::Manifest
& manifest
) {
71 if (manifest
.GetBoolean(extensions::manifest_keys::kKioskEnabled
,
79 std::string
ValueToString(const base::Value
* value
) {
81 base::JSONWriter::Write(value
, &json
);
87 ////////////////////////////////////////////////////////////////////////////////
88 // KioskAppData::CrxLoader
89 // Loads meta data from crx file.
91 class KioskAppData::CrxLoader
: public extensions::SandboxedUnpackerClient
{
93 CrxLoader(const base::WeakPtr
<KioskAppData
>& client
,
94 const base::FilePath
& crx_file
)
101 base::SequencedWorkerPool
* pool
= BrowserThread::GetBlockingPool();
102 base::SequencedWorkerPool::SequenceToken token
=
103 pool
->GetNamedSequenceToken("KioskAppData.CrxLoaderWorker");
104 task_runner_
= pool
->GetSequencedTaskRunnerWithShutdownBehavior(
106 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN
);
107 task_runner_
->PostTask(FROM_HERE
,
108 base::Bind(&CrxLoader::StartOnBlockingPool
, this));
111 bool success() const { return success_
; }
112 const base::FilePath
& crx_file() const { return crx_file_
; }
113 const std::string
& name() const { return name_
; }
114 const SkBitmap
& icon() const { return icon_
; }
117 ~CrxLoader() override
{};
119 // extensions::SandboxedUnpackerClient
120 void OnUnpackSuccess(const base::FilePath
& temp_dir
,
121 const base::FilePath
& extension_root
,
122 const base::DictionaryValue
* original_manifest
,
123 const extensions::Extension
* extension
,
124 const SkBitmap
& install_icon
) override
{
125 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
128 name_
= extension
->name();
129 icon_
= install_icon
;
130 NotifyFinishedOnBlockingPool();
132 void OnUnpackFailure(const extensions::CrxInstallError
& error
) override
{
133 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
136 NotifyFinishedOnBlockingPool();
139 void StartOnBlockingPool() {
140 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
142 if (!temp_dir_
.CreateUniqueTempDir()) {
144 NotifyFinishedOnBlockingPool();
148 scoped_refptr
<extensions::SandboxedUnpacker
> unpacker(
149 new extensions::SandboxedUnpacker(
150 extensions::CRXFileInfo(crx_file_
), extensions::Manifest::INTERNAL
,
151 extensions::Extension::NO_FLAGS
, temp_dir_
.path(),
152 task_runner_
.get(), this));
156 void NotifyFinishedOnBlockingPool() {
157 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
159 if (!temp_dir_
.Delete()) {
160 LOG(WARNING
) << "Can not delete temp directory at "
161 << temp_dir_
.path().value();
164 BrowserThread::PostTask(
165 BrowserThread::UI
, FROM_HERE
,
166 base::Bind(&CrxLoader::NotifyFinishedOnUIThread
, this));
169 void NotifyFinishedOnUIThread() {
170 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
173 client_
->OnCrxLoadFinished(this);
176 base::WeakPtr
<KioskAppData
> client_
;
177 base::FilePath crx_file_
;
180 scoped_refptr
<base::SequencedTaskRunner
> task_runner_
;
181 base::ScopedTempDir temp_dir_
;
183 // Extracted meta data.
187 DISALLOW_COPY_AND_ASSIGN(CrxLoader
);
190 ////////////////////////////////////////////////////////////////////////////////
191 // KioskAppData::IconLoader
192 // Loads locally stored icon data and decode it.
194 class KioskAppData::IconLoader
{
202 IconLoader(const base::WeakPtr
<KioskAppData
>& client
,
203 const base::FilePath
& icon_path
)
205 icon_path_(icon_path
),
206 load_result_(SUCCESS
) {}
209 base::SequencedWorkerPool
* pool
= BrowserThread::GetBlockingPool();
210 base::SequencedWorkerPool::SequenceToken token
= pool
->GetSequenceToken();
211 task_runner_
= pool
->GetSequencedTaskRunnerWithShutdownBehavior(
213 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN
);
214 task_runner_
->PostTask(FROM_HERE
,
215 base::Bind(&IconLoader::LoadOnBlockingPool
,
216 base::Unretained(this)));
220 friend class base::RefCountedThreadSafe
<IconLoader
>;
224 class IconImageRequest
: public ImageDecoder::ImageRequest
{
227 const scoped_refptr
<base::SequencedTaskRunner
>& task_runner
,
228 IconLoader
* icon_loader
)
229 : ImageRequest(task_runner
), icon_loader_(icon_loader
) {}
231 void OnImageDecoded(const SkBitmap
& decoded_image
) override
{
232 icon_loader_
->icon_
= gfx::ImageSkia::CreateFrom1xBitmap(decoded_image
);
233 icon_loader_
->icon_
.MakeThreadSafe();
234 icon_loader_
->ReportResultOnBlockingPool(SUCCESS
);
238 void OnDecodeImageFailed() override
{
239 icon_loader_
->ReportResultOnBlockingPool(FAILED_TO_DECODE
);
244 ~IconImageRequest() override
{}
245 IconLoader
* icon_loader_
;
248 // Loads the icon from locally stored |icon_path_| on the blocking pool
249 void LoadOnBlockingPool() {
250 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
253 if (!base::ReadFileToString(base::FilePath(icon_path_
), &data
)) {
254 ReportResultOnBlockingPool(FAILED_TO_LOAD
);
257 raw_icon_
= base::RefCountedString::TakeString(&data
);
259 IconImageRequest
* image_request
= new IconImageRequest(task_runner_
, this);
260 ImageDecoder::Start(image_request
, raw_icon_
->data());
263 void ReportResultOnBlockingPool(LoadResult result
) {
264 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
266 load_result_
= result
;
267 BrowserThread::PostTask(
270 base::Bind(&IconLoader::ReportResultOnUIThread
,
271 base::Unretained(this)));
274 void NotifyClient() {
278 if (load_result_
== SUCCESS
)
279 client_
->OnIconLoadSuccess(icon_
);
281 client_
->OnIconLoadFailure();
284 void ReportResultOnUIThread() {
285 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
291 base::WeakPtr
<KioskAppData
> client_
;
292 base::FilePath icon_path_
;
294 LoadResult load_result_
;
295 scoped_refptr
<base::SequencedTaskRunner
> task_runner_
;
297 gfx::ImageSkia icon_
;
298 scoped_refptr
<base::RefCountedString
> raw_icon_
;
300 DISALLOW_COPY_AND_ASSIGN(IconLoader
);
303 ////////////////////////////////////////////////////////////////////////////////
304 // KioskAppData::WebstoreDataParser
305 // Use WebstoreInstallHelper to parse the manifest and decode the icon.
307 class KioskAppData::WebstoreDataParser
308 : public extensions::WebstoreInstallHelper::Delegate
{
310 explicit WebstoreDataParser(const base::WeakPtr
<KioskAppData
>& client
)
313 void Start(const std::string
& app_id
,
314 const std::string
& manifest
,
315 const GURL
& icon_url
,
316 net::URLRequestContextGetter
* context_getter
) {
317 scoped_refptr
<extensions::WebstoreInstallHelper
> webstore_helper
=
318 new extensions::WebstoreInstallHelper(this,
323 webstore_helper
->Start();
327 friend class base::RefCounted
<WebstoreDataParser
>;
329 ~WebstoreDataParser() override
{}
331 void ReportFailure() {
333 client_
->OnWebstoreParseFailure();
338 // WebstoreInstallHelper::Delegate overrides:
339 void OnWebstoreParseSuccess(const std::string
& id
,
340 const SkBitmap
& icon
,
341 base::DictionaryValue
* parsed_manifest
) override
{
342 // Takes ownership of |parsed_manifest|.
343 extensions::Manifest
manifest(
344 extensions::Manifest::INVALID_LOCATION
,
345 scoped_ptr
<base::DictionaryValue
>(parsed_manifest
));
347 if (!IsValidKioskAppManifest(manifest
)) {
353 client_
->OnWebstoreParseSuccess(icon
);
356 void OnWebstoreParseFailure(const std::string
& id
,
357 InstallHelperResultCode result_code
,
358 const std::string
& error_message
) override
{
362 base::WeakPtr
<KioskAppData
> client_
;
364 DISALLOW_COPY_AND_ASSIGN(WebstoreDataParser
);
367 ////////////////////////////////////////////////////////////////////////////////
370 KioskAppData::KioskAppData(KioskAppDataDelegate
* delegate
,
371 const std::string
& app_id
,
372 const std::string
& user_id
,
373 const GURL
& update_url
)
374 : delegate_(delegate
),
375 status_(STATUS_INIT
),
378 update_url_(update_url
) {
381 KioskAppData::~KioskAppData() {}
383 void KioskAppData::Load() {
384 SetStatus(STATUS_LOADING
);
392 void KioskAppData::ClearCache() {
393 PrefService
* local_state
= g_browser_process
->local_state();
395 DictionaryPrefUpdate
dict_update(local_state
,
396 KioskAppManager::kKioskDictionaryName
);
398 std::string app_key
= std::string(KioskAppManager::kKeyApps
) + '.' + app_id_
;
399 dict_update
->Remove(app_key
, NULL
);
401 if (!icon_path_
.empty()) {
402 BrowserThread::PostBlockingPoolTask(
404 base::Bind(base::IgnoreResult(&base::DeleteFile
), icon_path_
, false));
408 void KioskAppData::LoadFromInstalledApp(Profile
* profile
,
409 const extensions::Extension
* app
) {
410 SetStatus(STATUS_LOADING
);
413 app
= extensions::ExtensionSystem::Get(profile
)
414 ->extension_service()
415 ->GetInstalledExtension(app_id_
);
418 DCHECK_EQ(app_id_
, app
->id());
422 const int kIconSize
= extension_misc::EXTENSION_ICON_LARGE
;
423 extensions::ExtensionResource image
= extensions::IconsInfo::GetIconResource(
424 app
, kIconSize
, ExtensionIconSet::MATCH_BIGGER
);
425 extensions::ImageLoader::Get(profile
)->LoadImageAsync(
426 app
, image
, gfx::Size(kIconSize
, kIconSize
),
427 base::Bind(&KioskAppData::OnExtensionIconLoaded
, AsWeakPtr()));
430 void KioskAppData::SetCachedCrx(const base::FilePath
& crx_file
) {
431 if (crx_file_
== crx_file
)
434 crx_file_
= crx_file
;
438 bool KioskAppData::IsLoading() const {
439 return status_
== STATUS_LOADING
;
442 bool KioskAppData::IsFromWebStore() const {
443 return update_url_
.is_empty() ||
444 extension_urls::IsWebstoreUpdateUrl(update_url_
);
447 void KioskAppData::SetStatus(Status status
) {
448 if (status_
== status
)
461 delegate_
->OnKioskAppDataChanged(app_id_
);
464 delegate_
->OnKioskAppDataLoadFailure(app_id_
);
469 net::URLRequestContextGetter
* KioskAppData::GetRequestContextGetter() {
470 return g_browser_process
->system_request_context();
473 bool KioskAppData::LoadFromCache() {
474 std::string app_key
= std::string(KioskAppManager::kKeyApps
) + '.' + app_id_
;
475 std::string name_key
= app_key
+ '.' + kKeyName
;
476 std::string icon_path_key
= app_key
+ '.' + kKeyIcon
;
478 PrefService
* local_state
= g_browser_process
->local_state();
479 const base::DictionaryValue
* dict
=
480 local_state
->GetDictionary(KioskAppManager::kKioskDictionaryName
);
483 std::string icon_path_string
;
484 if (!dict
->GetString(name_key
, &name_
) ||
485 !dict
->GetString(icon_path_key
, &icon_path_string
)) {
488 icon_path_
= base::FilePath(icon_path_string
);
490 // IconLoader deletes itself when done.
491 (new IconLoader(AsWeakPtr(), icon_path_
))->Start();
495 void KioskAppData::SetCache(const std::string
& name
,
496 const base::FilePath
& icon_path
) {
497 std::string app_key
= std::string(KioskAppManager::kKeyApps
) + '.' + app_id_
;
498 std::string name_key
= app_key
+ '.' + kKeyName
;
499 std::string icon_path_key
= app_key
+ '.' + kKeyIcon
;
501 PrefService
* local_state
= g_browser_process
->local_state();
502 DictionaryPrefUpdate
dict_update(local_state
,
503 KioskAppManager::kKioskDictionaryName
);
504 dict_update
->SetString(name_key
, name
);
505 dict_update
->SetString(icon_path_key
, icon_path
.value());
506 icon_path_
= icon_path
;
509 void KioskAppData::SetCache(const std::string
& name
, const SkBitmap
& icon
) {
512 icon_
= gfx::ImageSkia::CreateFrom1xBitmap(icon
);
513 icon_
.MakeThreadSafe();
515 std::vector
<unsigned char> image_data
;
516 CHECK(gfx::PNGCodec::EncodeBGRASkBitmap(icon
, false, &image_data
));
517 scoped_refptr
<base::RefCountedString
> raw_icon(new base::RefCountedString
);
518 raw_icon
->data().assign(image_data
.begin(), image_data
.end());
520 base::FilePath cache_dir
;
522 delegate_
->GetKioskAppIconCacheDir(&cache_dir
);
524 base::FilePath icon_path
=
525 cache_dir
.AppendASCII(app_id_
).AddExtension(kIconFileExtension
);
526 BrowserThread::GetBlockingPool()->PostTask(
528 base::Bind(&SaveIconToLocalOnBlockingPool
, icon_path
, raw_icon
));
530 SetCache(name
, icon_path
);
533 void KioskAppData::OnExtensionIconLoaded(const gfx::Image
& icon
) {
534 if (icon
.IsEmpty()) {
535 LOG(WARNING
) << "Failed to load icon from installed app"
536 << ", id=" << app_id_
;
537 SetCache(name_
, *extensions::util::GetDefaultAppIcon().bitmap());
539 SetCache(name_
, icon
.AsBitmap());
542 SetStatus(STATUS_LOADED
);
545 void KioskAppData::OnIconLoadSuccess(const gfx::ImageSkia
& icon
) {
546 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
548 SetStatus(STATUS_LOADED
);
551 void KioskAppData::OnIconLoadFailure() {
552 // Re-fetch data from web store when failed to load cached data.
556 void KioskAppData::OnWebstoreParseSuccess(const SkBitmap
& icon
) {
557 SetCache(name_
, icon
);
558 SetStatus(STATUS_LOADED
);
561 void KioskAppData::OnWebstoreParseFailure() {
562 SetStatus(STATUS_ERROR
);
565 void KioskAppData::StartFetch() {
566 if (!IsFromWebStore()) {
571 webstore_fetcher_
.reset(new extensions::WebstoreDataFetcher(
573 GetRequestContextGetter(),
576 webstore_fetcher_
->set_max_auto_retries(3);
577 webstore_fetcher_
->Start();
580 void KioskAppData::OnWebstoreRequestFailure() {
581 SetStatus(STATUS_ERROR
);
584 void KioskAppData::OnWebstoreResponseParseSuccess(
585 scoped_ptr
<base::DictionaryValue
> webstore_data
) {
586 // Takes ownership of |webstore_data|.
587 webstore_fetcher_
.reset();
589 std::string manifest
;
590 if (!CheckResponseKeyValue(webstore_data
.get(), kManifestKey
, &manifest
))
593 if (!CheckResponseKeyValue(webstore_data
.get(), kLocalizedNameKey
, &name_
))
596 std::string icon_url_string
;
597 if (!CheckResponseKeyValue(webstore_data
.get(), kIconUrlKey
,
601 GURL icon_url
= GURL(extension_urls::GetWebstoreLaunchURL()).Resolve(
603 if (!icon_url
.is_valid()) {
604 LOG(ERROR
) << "Webstore response error (icon url): "
605 << ValueToString(webstore_data
.get());
606 OnWebstoreResponseParseFailure(kInvalidWebstoreResponseError
);
610 // WebstoreDataParser deletes itself when done.
611 (new WebstoreDataParser(AsWeakPtr()))->Start(app_id_
,
614 GetRequestContextGetter());
617 void KioskAppData::OnWebstoreResponseParseFailure(const std::string
& error
) {
618 LOG(ERROR
) << "Webstore failed for kiosk app " << app_id_
620 webstore_fetcher_
.reset();
621 SetStatus(STATUS_ERROR
);
624 bool KioskAppData::CheckResponseKeyValue(const base::DictionaryValue
* response
,
626 std::string
* value
) {
627 if (!response
->GetString(key
, value
)) {
628 LOG(ERROR
) << "Webstore response error (" << key
629 << "): " << ValueToString(response
);
630 OnWebstoreResponseParseFailure(kInvalidWebstoreResponseError
);
636 void KioskAppData::MaybeLoadFromCrx() {
637 if (status_
== STATUS_LOADED
|| crx_file_
.empty())
640 scoped_refptr
<CrxLoader
> crx_loader(new CrxLoader(AsWeakPtr(), crx_file_
));
644 void KioskAppData::OnCrxLoadFinished(const CrxLoader
* crx_loader
) {
647 if (crx_loader
->crx_file() != crx_file_
)
650 if (!crx_loader
->success()) {
651 SetStatus(STATUS_ERROR
);
655 SkBitmap icon
= crx_loader
->icon();
657 icon
= *extensions::util::GetDefaultAppIcon().bitmap();
658 SetCache(crx_loader
->name(), icon
);
660 SetStatus(STATUS_LOADED
);
663 } // namespace chromeos