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::Manifest::INTERNAL
, extensions::Extension::NO_FLAGS
,
151 temp_dir_
.path(), task_runner_
.get(), this));
152 unpacker
->StartWithCrx(extensions::CRXFileInfo(crx_file_
));
155 void NotifyFinishedOnBlockingPool() {
156 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
158 if (!temp_dir_
.Delete()) {
159 LOG(WARNING
) << "Can not delete temp directory at "
160 << temp_dir_
.path().value();
163 BrowserThread::PostTask(
164 BrowserThread::UI
, FROM_HERE
,
165 base::Bind(&CrxLoader::NotifyFinishedOnUIThread
, this));
168 void NotifyFinishedOnUIThread() {
169 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
172 client_
->OnCrxLoadFinished(this);
175 base::WeakPtr
<KioskAppData
> client_
;
176 base::FilePath crx_file_
;
179 scoped_refptr
<base::SequencedTaskRunner
> task_runner_
;
180 base::ScopedTempDir temp_dir_
;
182 // Extracted meta data.
186 DISALLOW_COPY_AND_ASSIGN(CrxLoader
);
189 ////////////////////////////////////////////////////////////////////////////////
190 // KioskAppData::IconLoader
191 // Loads locally stored icon data and decode it.
193 class KioskAppData::IconLoader
{
201 IconLoader(const base::WeakPtr
<KioskAppData
>& client
,
202 const base::FilePath
& icon_path
)
204 icon_path_(icon_path
),
205 load_result_(SUCCESS
) {}
208 base::SequencedWorkerPool
* pool
= BrowserThread::GetBlockingPool();
209 base::SequencedWorkerPool::SequenceToken token
= pool
->GetSequenceToken();
210 task_runner_
= pool
->GetSequencedTaskRunnerWithShutdownBehavior(
212 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN
);
213 task_runner_
->PostTask(FROM_HERE
,
214 base::Bind(&IconLoader::LoadOnBlockingPool
,
215 base::Unretained(this)));
219 friend class base::RefCountedThreadSafe
<IconLoader
>;
223 class IconImageRequest
: public ImageDecoder::ImageRequest
{
226 const scoped_refptr
<base::SequencedTaskRunner
>& task_runner
,
227 IconLoader
* icon_loader
)
228 : ImageRequest(task_runner
), icon_loader_(icon_loader
) {}
230 void OnImageDecoded(const SkBitmap
& decoded_image
) override
{
231 icon_loader_
->icon_
= gfx::ImageSkia::CreateFrom1xBitmap(decoded_image
);
232 icon_loader_
->icon_
.MakeThreadSafe();
233 icon_loader_
->ReportResultOnBlockingPool(SUCCESS
);
237 void OnDecodeImageFailed() override
{
238 icon_loader_
->ReportResultOnBlockingPool(FAILED_TO_DECODE
);
243 ~IconImageRequest() override
{}
244 IconLoader
* icon_loader_
;
247 // Loads the icon from locally stored |icon_path_| on the blocking pool
248 void LoadOnBlockingPool() {
249 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
252 if (!base::ReadFileToString(base::FilePath(icon_path_
), &data
)) {
253 ReportResultOnBlockingPool(FAILED_TO_LOAD
);
256 raw_icon_
= base::RefCountedString::TakeString(&data
);
258 IconImageRequest
* image_request
= new IconImageRequest(task_runner_
, this);
259 ImageDecoder::Start(image_request
, raw_icon_
->data());
262 void ReportResultOnBlockingPool(LoadResult result
) {
263 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
265 load_result_
= result
;
266 BrowserThread::PostTask(
269 base::Bind(&IconLoader::ReportResultOnUIThread
,
270 base::Unretained(this)));
273 void NotifyClient() {
277 if (load_result_
== SUCCESS
)
278 client_
->OnIconLoadSuccess(icon_
);
280 client_
->OnIconLoadFailure();
283 void ReportResultOnUIThread() {
284 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
290 base::WeakPtr
<KioskAppData
> client_
;
291 base::FilePath icon_path_
;
293 LoadResult load_result_
;
294 scoped_refptr
<base::SequencedTaskRunner
> task_runner_
;
296 gfx::ImageSkia icon_
;
297 scoped_refptr
<base::RefCountedString
> raw_icon_
;
299 DISALLOW_COPY_AND_ASSIGN(IconLoader
);
302 ////////////////////////////////////////////////////////////////////////////////
303 // KioskAppData::WebstoreDataParser
304 // Use WebstoreInstallHelper to parse the manifest and decode the icon.
306 class KioskAppData::WebstoreDataParser
307 : public extensions::WebstoreInstallHelper::Delegate
{
309 explicit WebstoreDataParser(const base::WeakPtr
<KioskAppData
>& client
)
312 void Start(const std::string
& app_id
,
313 const std::string
& manifest
,
314 const GURL
& icon_url
,
315 net::URLRequestContextGetter
* context_getter
) {
316 scoped_refptr
<extensions::WebstoreInstallHelper
> webstore_helper
=
317 new extensions::WebstoreInstallHelper(this,
322 webstore_helper
->Start();
326 friend class base::RefCounted
<WebstoreDataParser
>;
328 ~WebstoreDataParser() override
{}
330 void ReportFailure() {
332 client_
->OnWebstoreParseFailure();
337 // WebstoreInstallHelper::Delegate overrides:
338 void OnWebstoreParseSuccess(const std::string
& id
,
339 const SkBitmap
& icon
,
340 base::DictionaryValue
* parsed_manifest
) override
{
341 // Takes ownership of |parsed_manifest|.
342 extensions::Manifest
manifest(
343 extensions::Manifest::INVALID_LOCATION
,
344 scoped_ptr
<base::DictionaryValue
>(parsed_manifest
));
346 if (!IsValidKioskAppManifest(manifest
)) {
352 client_
->OnWebstoreParseSuccess(icon
);
355 void OnWebstoreParseFailure(const std::string
& id
,
356 InstallHelperResultCode result_code
,
357 const std::string
& error_message
) override
{
361 base::WeakPtr
<KioskAppData
> client_
;
363 DISALLOW_COPY_AND_ASSIGN(WebstoreDataParser
);
366 ////////////////////////////////////////////////////////////////////////////////
369 KioskAppData::KioskAppData(KioskAppDataDelegate
* delegate
,
370 const std::string
& app_id
,
371 const std::string
& user_id
,
372 const GURL
& update_url
)
373 : delegate_(delegate
),
374 status_(STATUS_INIT
),
377 update_url_(update_url
) {
380 KioskAppData::~KioskAppData() {}
382 void KioskAppData::Load() {
383 SetStatus(STATUS_LOADING
);
391 void KioskAppData::ClearCache() {
392 PrefService
* local_state
= g_browser_process
->local_state();
394 DictionaryPrefUpdate
dict_update(local_state
,
395 KioskAppManager::kKioskDictionaryName
);
397 std::string app_key
= std::string(KioskAppManager::kKeyApps
) + '.' + app_id_
;
398 dict_update
->Remove(app_key
, NULL
);
400 if (!icon_path_
.empty()) {
401 BrowserThread::PostBlockingPoolTask(
403 base::Bind(base::IgnoreResult(&base::DeleteFile
), icon_path_
, false));
407 void KioskAppData::LoadFromInstalledApp(Profile
* profile
,
408 const extensions::Extension
* app
) {
409 SetStatus(STATUS_LOADING
);
412 app
= extensions::ExtensionSystem::Get(profile
)
413 ->extension_service()
414 ->GetInstalledExtension(app_id_
);
417 DCHECK_EQ(app_id_
, app
->id());
421 const int kIconSize
= extension_misc::EXTENSION_ICON_LARGE
;
422 extensions::ExtensionResource image
= extensions::IconsInfo::GetIconResource(
423 app
, kIconSize
, ExtensionIconSet::MATCH_BIGGER
);
424 extensions::ImageLoader::Get(profile
)->LoadImageAsync(
425 app
, image
, gfx::Size(kIconSize
, kIconSize
),
426 base::Bind(&KioskAppData::OnExtensionIconLoaded
, AsWeakPtr()));
429 void KioskAppData::SetCachedCrx(const base::FilePath
& crx_file
) {
430 if (crx_file_
== crx_file
)
433 crx_file_
= crx_file
;
437 bool KioskAppData::IsLoading() const {
438 return status_
== STATUS_LOADING
;
441 bool KioskAppData::IsFromWebStore() const {
442 return update_url_
.is_empty() ||
443 extension_urls::IsWebstoreUpdateUrl(update_url_
);
446 void KioskAppData::SetStatus(Status status
) {
447 if (status_
== status
)
460 delegate_
->OnKioskAppDataChanged(app_id_
);
463 delegate_
->OnKioskAppDataLoadFailure(app_id_
);
468 net::URLRequestContextGetter
* KioskAppData::GetRequestContextGetter() {
469 return g_browser_process
->system_request_context();
472 bool KioskAppData::LoadFromCache() {
473 std::string app_key
= std::string(KioskAppManager::kKeyApps
) + '.' + app_id_
;
474 std::string name_key
= app_key
+ '.' + kKeyName
;
475 std::string icon_path_key
= app_key
+ '.' + kKeyIcon
;
477 PrefService
* local_state
= g_browser_process
->local_state();
478 const base::DictionaryValue
* dict
=
479 local_state
->GetDictionary(KioskAppManager::kKioskDictionaryName
);
482 std::string icon_path_string
;
483 if (!dict
->GetString(name_key
, &name_
) ||
484 !dict
->GetString(icon_path_key
, &icon_path_string
)) {
487 icon_path_
= base::FilePath(icon_path_string
);
489 // IconLoader deletes itself when done.
490 (new IconLoader(AsWeakPtr(), icon_path_
))->Start();
494 void KioskAppData::SetCache(const std::string
& name
,
495 const base::FilePath
& icon_path
) {
496 std::string app_key
= std::string(KioskAppManager::kKeyApps
) + '.' + app_id_
;
497 std::string name_key
= app_key
+ '.' + kKeyName
;
498 std::string icon_path_key
= app_key
+ '.' + kKeyIcon
;
500 PrefService
* local_state
= g_browser_process
->local_state();
501 DictionaryPrefUpdate
dict_update(local_state
,
502 KioskAppManager::kKioskDictionaryName
);
503 dict_update
->SetString(name_key
, name
);
504 dict_update
->SetString(icon_path_key
, icon_path
.value());
505 icon_path_
= icon_path
;
508 void KioskAppData::SetCache(const std::string
& name
, const SkBitmap
& icon
) {
511 icon_
= gfx::ImageSkia::CreateFrom1xBitmap(icon
);
512 icon_
.MakeThreadSafe();
514 std::vector
<unsigned char> image_data
;
515 CHECK(gfx::PNGCodec::EncodeBGRASkBitmap(icon
, false, &image_data
));
516 scoped_refptr
<base::RefCountedString
> raw_icon(new base::RefCountedString
);
517 raw_icon
->data().assign(image_data
.begin(), image_data
.end());
519 base::FilePath cache_dir
;
521 delegate_
->GetKioskAppIconCacheDir(&cache_dir
);
523 base::FilePath icon_path
=
524 cache_dir
.AppendASCII(app_id_
).AddExtension(kIconFileExtension
);
525 BrowserThread::GetBlockingPool()->PostTask(
527 base::Bind(&SaveIconToLocalOnBlockingPool
, icon_path
, raw_icon
));
529 SetCache(name
, icon_path
);
532 void KioskAppData::OnExtensionIconLoaded(const gfx::Image
& icon
) {
533 if (icon
.IsEmpty()) {
534 LOG(WARNING
) << "Failed to load icon from installed app"
535 << ", id=" << app_id_
;
536 SetCache(name_
, *extensions::util::GetDefaultAppIcon().bitmap());
538 SetCache(name_
, icon
.AsBitmap());
541 SetStatus(STATUS_LOADED
);
544 void KioskAppData::OnIconLoadSuccess(const gfx::ImageSkia
& icon
) {
545 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
547 SetStatus(STATUS_LOADED
);
550 void KioskAppData::OnIconLoadFailure() {
551 // Re-fetch data from web store when failed to load cached data.
555 void KioskAppData::OnWebstoreParseSuccess(const SkBitmap
& icon
) {
556 SetCache(name_
, icon
);
557 SetStatus(STATUS_LOADED
);
560 void KioskAppData::OnWebstoreParseFailure() {
561 SetStatus(STATUS_ERROR
);
564 void KioskAppData::StartFetch() {
565 if (!IsFromWebStore()) {
570 webstore_fetcher_
.reset(new extensions::WebstoreDataFetcher(
572 GetRequestContextGetter(),
575 webstore_fetcher_
->set_max_auto_retries(3);
576 webstore_fetcher_
->Start();
579 void KioskAppData::OnWebstoreRequestFailure() {
580 SetStatus(STATUS_ERROR
);
583 void KioskAppData::OnWebstoreResponseParseSuccess(
584 scoped_ptr
<base::DictionaryValue
> webstore_data
) {
585 // Takes ownership of |webstore_data|.
586 webstore_fetcher_
.reset();
588 std::string manifest
;
589 if (!CheckResponseKeyValue(webstore_data
.get(), kManifestKey
, &manifest
))
592 if (!CheckResponseKeyValue(webstore_data
.get(), kLocalizedNameKey
, &name_
))
595 std::string icon_url_string
;
596 if (!CheckResponseKeyValue(webstore_data
.get(), kIconUrlKey
,
600 GURL icon_url
= GURL(extension_urls::GetWebstoreLaunchURL()).Resolve(
602 if (!icon_url
.is_valid()) {
603 LOG(ERROR
) << "Webstore response error (icon url): "
604 << ValueToString(*webstore_data
);
605 OnWebstoreResponseParseFailure(kInvalidWebstoreResponseError
);
609 // WebstoreDataParser deletes itself when done.
610 (new WebstoreDataParser(AsWeakPtr()))->Start(app_id_
,
613 GetRequestContextGetter());
616 void KioskAppData::OnWebstoreResponseParseFailure(const std::string
& error
) {
617 LOG(ERROR
) << "Webstore failed for kiosk app " << app_id_
619 webstore_fetcher_
.reset();
620 SetStatus(STATUS_ERROR
);
623 bool KioskAppData::CheckResponseKeyValue(const base::DictionaryValue
* response
,
625 std::string
* value
) {
626 if (!response
->GetString(key
, value
)) {
627 LOG(ERROR
) << "Webstore response error (" << key
628 << "): " << ValueToString(*response
);
629 OnWebstoreResponseParseFailure(kInvalidWebstoreResponseError
);
635 void KioskAppData::MaybeLoadFromCrx() {
636 if (status_
== STATUS_LOADED
|| crx_file_
.empty())
639 scoped_refptr
<CrxLoader
> crx_loader(new CrxLoader(AsWeakPtr(), crx_file_
));
643 void KioskAppData::OnCrxLoadFinished(const CrxLoader
* crx_loader
) {
646 if (crx_loader
->crx_file() != crx_file_
)
649 if (!crx_loader
->success()) {
650 SetStatus(STATUS_ERROR
);
654 SkBitmap icon
= crx_loader
->icon();
656 icon
= *extensions::util::GetDefaultAppIcon().bitmap();
657 SetCache(crx_loader
->name(), icon
);
659 SetStatus(STATUS_LOADED
);
662 } // namespace chromeos