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(BrowserThread::CurrentlyOn(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
: public ImageDecoder::Delegate
{
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
>;
222 ~IconLoader() override
{}
224 // Loads the icon from locally stored |icon_path_| on the blocking pool
225 void LoadOnBlockingPool() {
226 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
229 if (!base::ReadFileToString(base::FilePath(icon_path_
), &data
)) {
230 ReportResultOnBlockingPool(FAILED_TO_LOAD
);
233 raw_icon_
= base::RefCountedString::TakeString(&data
);
235 scoped_refptr
<ImageDecoder
> image_decoder
= new ImageDecoder(
236 this, raw_icon_
->data(), ImageDecoder::DEFAULT_CODEC
);
237 image_decoder
->Start(task_runner_
);
240 void ReportResultOnBlockingPool(LoadResult result
) {
241 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
243 load_result_
= result
;
244 BrowserThread::PostTask(
247 base::Bind(&IconLoader::ReportResultOnUIThread
,
248 base::Unretained(this)));
251 void NotifyClient() {
255 if (load_result_
== SUCCESS
)
256 client_
->OnIconLoadSuccess(icon_
);
258 client_
->OnIconLoadFailure();
261 void ReportResultOnUIThread() {
262 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
268 // ImageDecoder::Delegate overrides:
269 void OnImageDecoded(const ImageDecoder
* decoder
,
270 const SkBitmap
& decoded_image
) override
{
271 icon_
= gfx::ImageSkia::CreateFrom1xBitmap(decoded_image
);
272 icon_
.MakeThreadSafe();
273 ReportResultOnBlockingPool(SUCCESS
);
276 void OnDecodeImageFailed(const ImageDecoder
* decoder
) override
{
277 ReportResultOnBlockingPool(FAILED_TO_DECODE
);
280 base::WeakPtr
<KioskAppData
> client_
;
281 base::FilePath icon_path_
;
283 LoadResult load_result_
;
284 scoped_refptr
<base::SequencedTaskRunner
> task_runner_
;
286 gfx::ImageSkia icon_
;
287 scoped_refptr
<base::RefCountedString
> raw_icon_
;
289 DISALLOW_COPY_AND_ASSIGN(IconLoader
);
292 ////////////////////////////////////////////////////////////////////////////////
293 // KioskAppData::WebstoreDataParser
294 // Use WebstoreInstallHelper to parse the manifest and decode the icon.
296 class KioskAppData::WebstoreDataParser
297 : public extensions::WebstoreInstallHelper::Delegate
{
299 explicit WebstoreDataParser(const base::WeakPtr
<KioskAppData
>& client
)
302 void Start(const std::string
& app_id
,
303 const std::string
& manifest
,
304 const GURL
& icon_url
,
305 net::URLRequestContextGetter
* context_getter
) {
306 scoped_refptr
<extensions::WebstoreInstallHelper
> webstore_helper
=
307 new extensions::WebstoreInstallHelper(this,
313 webstore_helper
->Start();
317 friend class base::RefCounted
<WebstoreDataParser
>;
319 ~WebstoreDataParser() override
{}
321 void ReportFailure() {
323 client_
->OnWebstoreParseFailure();
328 // WebstoreInstallHelper::Delegate overrides:
329 void OnWebstoreParseSuccess(const std::string
& id
,
330 const SkBitmap
& icon
,
331 base::DictionaryValue
* parsed_manifest
) override
{
332 // Takes ownership of |parsed_manifest|.
333 extensions::Manifest
manifest(
334 extensions::Manifest::INVALID_LOCATION
,
335 scoped_ptr
<base::DictionaryValue
>(parsed_manifest
));
337 if (!IsValidKioskAppManifest(manifest
)) {
343 client_
->OnWebstoreParseSuccess(icon
);
346 void OnWebstoreParseFailure(const std::string
& id
,
347 InstallHelperResultCode result_code
,
348 const std::string
& error_message
) override
{
352 base::WeakPtr
<KioskAppData
> client_
;
354 DISALLOW_COPY_AND_ASSIGN(WebstoreDataParser
);
357 ////////////////////////////////////////////////////////////////////////////////
360 KioskAppData::KioskAppData(KioskAppDataDelegate
* delegate
,
361 const std::string
& app_id
,
362 const std::string
& user_id
,
363 const GURL
& update_url
)
364 : delegate_(delegate
),
365 status_(STATUS_INIT
),
368 update_url_(update_url
) {
371 KioskAppData::~KioskAppData() {}
373 void KioskAppData::Load() {
374 SetStatus(STATUS_LOADING
);
382 void KioskAppData::ClearCache() {
383 PrefService
* local_state
= g_browser_process
->local_state();
385 DictionaryPrefUpdate
dict_update(local_state
,
386 KioskAppManager::kKioskDictionaryName
);
388 std::string app_key
= std::string(KioskAppManager::kKeyApps
) + '.' + app_id_
;
389 dict_update
->Remove(app_key
, NULL
);
391 if (!icon_path_
.empty()) {
392 BrowserThread::PostBlockingPoolTask(
394 base::Bind(base::IgnoreResult(&base::DeleteFile
), icon_path_
, false));
398 void KioskAppData::LoadFromInstalledApp(Profile
* profile
,
399 const extensions::Extension
* app
) {
400 SetStatus(STATUS_LOADING
);
403 app
= extensions::ExtensionSystem::Get(profile
)
404 ->extension_service()
405 ->GetInstalledExtension(app_id_
);
408 DCHECK_EQ(app_id_
, app
->id());
412 const int kIconSize
= extension_misc::EXTENSION_ICON_LARGE
;
413 extensions::ExtensionResource image
= extensions::IconsInfo::GetIconResource(
414 app
, kIconSize
, ExtensionIconSet::MATCH_BIGGER
);
415 extensions::ImageLoader::Get(profile
)->LoadImageAsync(
416 app
, image
, gfx::Size(kIconSize
, kIconSize
),
417 base::Bind(&KioskAppData::OnExtensionIconLoaded
, AsWeakPtr()));
420 void KioskAppData::SetCachedCrx(const base::FilePath
& crx_file
) {
421 if (crx_file_
== crx_file
)
424 crx_file_
= crx_file
;
428 bool KioskAppData::IsLoading() const {
429 return status_
== STATUS_LOADING
;
432 bool KioskAppData::IsFromWebStore() const {
433 return update_url_
.is_empty() ||
434 extension_urls::IsWebstoreUpdateUrl(update_url_
);
437 void KioskAppData::SetStatus(Status status
) {
438 if (status_
== status
)
451 delegate_
->OnKioskAppDataChanged(app_id_
);
454 delegate_
->OnKioskAppDataLoadFailure(app_id_
);
459 net::URLRequestContextGetter
* KioskAppData::GetRequestContextGetter() {
460 return g_browser_process
->system_request_context();
463 bool KioskAppData::LoadFromCache() {
464 std::string app_key
= std::string(KioskAppManager::kKeyApps
) + '.' + app_id_
;
465 std::string name_key
= app_key
+ '.' + kKeyName
;
466 std::string icon_path_key
= app_key
+ '.' + kKeyIcon
;
468 PrefService
* local_state
= g_browser_process
->local_state();
469 const base::DictionaryValue
* dict
=
470 local_state
->GetDictionary(KioskAppManager::kKioskDictionaryName
);
473 std::string icon_path_string
;
474 if (!dict
->GetString(name_key
, &name_
) ||
475 !dict
->GetString(icon_path_key
, &icon_path_string
)) {
478 icon_path_
= base::FilePath(icon_path_string
);
480 // IconLoader deletes itself when done.
481 (new IconLoader(AsWeakPtr(), icon_path_
))->Start();
485 void KioskAppData::SetCache(const std::string
& name
,
486 const base::FilePath
& icon_path
) {
487 std::string app_key
= std::string(KioskAppManager::kKeyApps
) + '.' + app_id_
;
488 std::string name_key
= app_key
+ '.' + kKeyName
;
489 std::string icon_path_key
= app_key
+ '.' + kKeyIcon
;
491 PrefService
* local_state
= g_browser_process
->local_state();
492 DictionaryPrefUpdate
dict_update(local_state
,
493 KioskAppManager::kKioskDictionaryName
);
494 dict_update
->SetString(name_key
, name
);
495 dict_update
->SetString(icon_path_key
, icon_path
.value());
496 icon_path_
= icon_path
;
499 void KioskAppData::SetCache(const std::string
& name
, const SkBitmap
& icon
) {
502 icon_
= gfx::ImageSkia::CreateFrom1xBitmap(icon
);
503 icon_
.MakeThreadSafe();
505 std::vector
<unsigned char> image_data
;
506 CHECK(gfx::PNGCodec::EncodeBGRASkBitmap(icon
, false, &image_data
));
507 scoped_refptr
<base::RefCountedString
> raw_icon(new base::RefCountedString
);
508 raw_icon
->data().assign(image_data
.begin(), image_data
.end());
510 base::FilePath cache_dir
;
512 delegate_
->GetKioskAppIconCacheDir(&cache_dir
);
514 base::FilePath icon_path
=
515 cache_dir
.AppendASCII(app_id_
).AddExtension(kIconFileExtension
);
516 BrowserThread::GetBlockingPool()->PostTask(
518 base::Bind(&SaveIconToLocalOnBlockingPool
, icon_path
, raw_icon
));
520 SetCache(name
, icon_path
);
523 void KioskAppData::OnExtensionIconLoaded(const gfx::Image
& icon
) {
524 if (icon
.IsEmpty()) {
525 LOG(WARNING
) << "Failed to load icon from installed app"
526 << ", id=" << app_id_
;
527 SetCache(name_
, *extensions::util::GetDefaultAppIcon().bitmap());
529 SetCache(name_
, icon
.AsBitmap());
532 SetStatus(STATUS_LOADED
);
535 void KioskAppData::OnIconLoadSuccess(const gfx::ImageSkia
& icon
) {
536 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
538 SetStatus(STATUS_LOADED
);
541 void KioskAppData::OnIconLoadFailure() {
542 // Re-fetch data from web store when failed to load cached data.
546 void KioskAppData::OnWebstoreParseSuccess(const SkBitmap
& icon
) {
547 SetCache(name_
, icon
);
548 SetStatus(STATUS_LOADED
);
551 void KioskAppData::OnWebstoreParseFailure() {
552 SetStatus(STATUS_ERROR
);
555 void KioskAppData::StartFetch() {
556 if (!IsFromWebStore()) {
561 webstore_fetcher_
.reset(new extensions::WebstoreDataFetcher(
563 GetRequestContextGetter(),
566 webstore_fetcher_
->set_max_auto_retries(3);
567 webstore_fetcher_
->Start();
570 void KioskAppData::OnWebstoreRequestFailure() {
571 SetStatus(STATUS_ERROR
);
574 void KioskAppData::OnWebstoreResponseParseSuccess(
575 scoped_ptr
<base::DictionaryValue
> webstore_data
) {
576 // Takes ownership of |webstore_data|.
577 webstore_fetcher_
.reset();
579 std::string manifest
;
580 if (!CheckResponseKeyValue(webstore_data
.get(), kManifestKey
, &manifest
))
583 if (!CheckResponseKeyValue(webstore_data
.get(), kLocalizedNameKey
, &name_
))
586 std::string icon_url_string
;
587 if (!CheckResponseKeyValue(webstore_data
.get(), kIconUrlKey
,
591 GURL icon_url
= GURL(extension_urls::GetWebstoreLaunchURL()).Resolve(
593 if (!icon_url
.is_valid()) {
594 LOG(ERROR
) << "Webstore response error (icon url): "
595 << ValueToString(webstore_data
.get());
596 OnWebstoreResponseParseFailure(kInvalidWebstoreResponseError
);
600 // WebstoreDataParser deletes itself when done.
601 (new WebstoreDataParser(AsWeakPtr()))->Start(app_id_
,
604 GetRequestContextGetter());
607 void KioskAppData::OnWebstoreResponseParseFailure(const std::string
& error
) {
608 LOG(ERROR
) << "Webstore failed for kiosk app " << app_id_
610 webstore_fetcher_
.reset();
611 SetStatus(STATUS_ERROR
);
614 bool KioskAppData::CheckResponseKeyValue(const base::DictionaryValue
* response
,
616 std::string
* value
) {
617 if (!response
->GetString(key
, value
)) {
618 LOG(ERROR
) << "Webstore response error (" << key
619 << "): " << ValueToString(response
);
620 OnWebstoreResponseParseFailure(kInvalidWebstoreResponseError
);
626 void KioskAppData::MaybeLoadFromCrx() {
627 if (status_
== STATUS_LOADED
|| crx_file_
.empty())
630 scoped_refptr
<CrxLoader
> crx_loader(new CrxLoader(AsWeakPtr(), crx_file_
));
634 void KioskAppData::OnCrxLoadFinished(const CrxLoader
* crx_loader
) {
637 if (crx_loader
->crx_file() != crx_file_
)
640 if (!crx_loader
->success()) {
641 SetStatus(STATUS_ERROR
);
645 SkBitmap icon
= crx_loader
->icon();
647 icon
= *extensions::util::GetDefaultAppIcon().bitmap();
648 SetCache(crx_loader
->name(), icon
);
650 SetStatus(STATUS_LOADED
);
653 } // namespace chromeos