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_system.h"
22 #include "chrome/browser/extensions/image_loader.h"
23 #include "chrome/browser/extensions/webstore_data_fetcher.h"
24 #include "chrome/browser/extensions/webstore_install_helper.h"
25 #include "chrome/browser/image_decoder.h"
26 #include "chrome/browser/profiles/profile.h"
27 #include "chrome/common/extensions/extension_constants.h"
28 #include "chrome/common/extensions/manifest_handlers/icons_handler.h"
29 #include "content/public/browser/browser_thread.h"
30 #include "extensions/common/manifest.h"
31 #include "extensions/common/manifest_constants.h"
32 #include "ui/gfx/codec/png_codec.h"
33 #include "ui/gfx/image/image.h"
35 using content::BrowserThread
;
41 // Keys for local state data. See sample layout in KioskAppManager.
42 const char kKeyName
[] = "name";
43 const char kKeyIcon
[] = "icon";
45 // Web store data keys.
46 const char kManifestKey
[] = "manifest";
47 const char kIconUrlKey
[] = "icon_url";
48 const char kLocalizedNameKey
[] = "localized_name";
50 const char kInvalidWebstoreResponseError
[] = "Invalid Chrome Web Store reponse";
52 // Icon file extension.
53 const char kIconFileExtension
[] = ".png";
55 // Save |raw_icon| for given |app_id|.
56 void SaveIconToLocalOnBlockingPool(
57 const base::FilePath
& icon_path
,
58 scoped_refptr
<base::RefCountedString
> raw_icon
) {
59 DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
61 base::FilePath dir
= icon_path
.DirName();
62 if (!base::PathExists(dir
))
63 CHECK(base::CreateDirectory(dir
));
65 CHECK_EQ(static_cast<int>(raw_icon
->size()),
66 file_util::WriteFile(icon_path
,
67 raw_icon
->data().c_str(), raw_icon
->size()));
70 // Returns true for valid kiosk app manifest.
71 bool IsValidKioskAppManifest(const extensions::Manifest
& manifest
) {
73 if (manifest
.GetBoolean(extensions::manifest_keys::kKioskEnabled
,
81 std::string
ValueToString(const base::Value
* value
) {
83 base::JSONWriter::Write(value
, &json
);
89 ////////////////////////////////////////////////////////////////////////////////
90 // KioskAppData::IconLoader
91 // Loads locally stored icon data and decode it.
93 class KioskAppData::IconLoader
: public ImageDecoder::Delegate
{
101 IconLoader(const base::WeakPtr
<KioskAppData
>& client
,
102 const base::FilePath
& icon_path
)
104 icon_path_(icon_path
),
105 load_result_(SUCCESS
) {}
108 base::SequencedWorkerPool
* pool
= BrowserThread::GetBlockingPool();
109 base::SequencedWorkerPool::SequenceToken token
= pool
->GetSequenceToken();
110 task_runner_
= pool
->GetSequencedTaskRunnerWithShutdownBehavior(
112 base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN
);
113 task_runner_
->PostTask(FROM_HERE
,
114 base::Bind(&IconLoader::LoadOnBlockingPool
,
115 base::Unretained(this)));
119 friend class base::RefCountedThreadSafe
<IconLoader
>;
121 virtual ~IconLoader() {}
123 // Loads the icon from locally stored |icon_path_| on the blocking pool
124 void LoadOnBlockingPool() {
125 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
128 if (!base::ReadFileToString(base::FilePath(icon_path_
), &data
)) {
129 ReportResultOnBlockingPool(FAILED_TO_LOAD
);
132 raw_icon_
= base::RefCountedString::TakeString(&data
);
134 scoped_refptr
<ImageDecoder
> image_decoder
= new ImageDecoder(
135 this, raw_icon_
->data(), ImageDecoder::DEFAULT_CODEC
);
136 image_decoder
->Start(task_runner_
);
139 void ReportResultOnBlockingPool(LoadResult result
) {
140 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
142 load_result_
= result
;
143 BrowserThread::PostTask(
146 base::Bind(&IconLoader::ReportResultOnUIThread
,
147 base::Unretained(this)));
150 void NotifyClient() {
154 if (load_result_
== SUCCESS
)
155 client_
->OnIconLoadSuccess(raw_icon_
, icon_
);
157 client_
->OnIconLoadFailure();
160 void ReportResultOnUIThread() {
161 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
167 // ImageDecoder::Delegate overrides:
168 virtual void OnImageDecoded(const ImageDecoder
* decoder
,
169 const SkBitmap
& decoded_image
) OVERRIDE
{
170 icon_
= gfx::ImageSkia::CreateFrom1xBitmap(decoded_image
);
171 icon_
.MakeThreadSafe();
172 ReportResultOnBlockingPool(SUCCESS
);
175 virtual void OnDecodeImageFailed(const ImageDecoder
* decoder
) OVERRIDE
{
176 ReportResultOnBlockingPool(FAILED_TO_DECODE
);
179 base::WeakPtr
<KioskAppData
> client_
;
180 base::FilePath icon_path_
;
182 LoadResult load_result_
;
183 scoped_refptr
<base::SequencedTaskRunner
> task_runner_
;
185 gfx::ImageSkia icon_
;
186 scoped_refptr
<base::RefCountedString
> raw_icon_
;
188 DISALLOW_COPY_AND_ASSIGN(IconLoader
);
191 ////////////////////////////////////////////////////////////////////////////////
192 // KioskAppData::WebstoreDataParser
193 // Use WebstoreInstallHelper to parse the manifest and decode the icon.
195 class KioskAppData::WebstoreDataParser
196 : public extensions::WebstoreInstallHelper::Delegate
{
198 explicit WebstoreDataParser(const base::WeakPtr
<KioskAppData
>& client
)
201 void Start(const std::string
& app_id
,
202 const std::string
& manifest
,
203 const GURL
& icon_url
,
204 net::URLRequestContextGetter
* context_getter
) {
205 scoped_refptr
<extensions::WebstoreInstallHelper
> webstore_helper
=
206 new extensions::WebstoreInstallHelper(this,
212 webstore_helper
->Start();
216 friend class base::RefCounted
<WebstoreDataParser
>;
218 virtual ~WebstoreDataParser() {}
220 void ReportFailure() {
222 client_
->OnWebstoreParseFailure();
227 // WebstoreInstallHelper::Delegate overrides:
228 virtual void OnWebstoreParseSuccess(
229 const std::string
& id
,
230 const SkBitmap
& icon
,
231 base::DictionaryValue
* parsed_manifest
) OVERRIDE
{
232 // Takes ownership of |parsed_manifest|.
233 extensions::Manifest
manifest(
234 extensions::Manifest::INVALID_LOCATION
,
235 scoped_ptr
<base::DictionaryValue
>(parsed_manifest
));
237 if (!IsValidKioskAppManifest(manifest
)) {
243 client_
->OnWebstoreParseSuccess(icon
);
246 virtual void OnWebstoreParseFailure(
247 const std::string
& id
,
248 InstallHelperResultCode result_code
,
249 const std::string
& error_message
) OVERRIDE
{
253 base::WeakPtr
<KioskAppData
> client_
;
255 DISALLOW_COPY_AND_ASSIGN(WebstoreDataParser
);
258 ////////////////////////////////////////////////////////////////////////////////
261 KioskAppData::KioskAppData(KioskAppDataDelegate
* delegate
,
262 const std::string
& app_id
,
263 const std::string
& user_id
)
264 : delegate_(delegate
),
265 status_(STATUS_INIT
),
270 KioskAppData::~KioskAppData() {}
272 void KioskAppData::Load() {
273 SetStatus(STATUS_LOADING
);
281 void KioskAppData::ClearCache() {
282 PrefService
* local_state
= g_browser_process
->local_state();
284 DictionaryPrefUpdate
dict_update(local_state
,
285 KioskAppManager::kKioskDictionaryName
);
287 std::string app_key
= std::string(KioskAppManager::kKeyApps
) + '.' + app_id_
;
288 dict_update
->Remove(app_key
, NULL
);
290 if (!icon_path_
.empty()) {
291 BrowserThread::PostBlockingPoolTask(
293 base::Bind(base::IgnoreResult(&base::DeleteFile
), icon_path_
, false));
297 void KioskAppData::LoadFromInstalledApp(Profile
* profile
,
298 const extensions::Extension
* app
) {
299 SetStatus(STATUS_LOADING
);
302 app
= extensions::ExtensionSystem::Get(profile
)
303 ->extension_service()
304 ->GetInstalledExtension(app_id_
);
307 DCHECK_EQ(app_id_
, app
->id());
311 const int kIconSize
= extension_misc::EXTENSION_ICON_LARGE
;
312 extensions::ExtensionResource image
= extensions::IconsInfo::GetIconResource(
313 app
, kIconSize
, ExtensionIconSet::MATCH_BIGGER
);
314 extensions::ImageLoader::Get(profile
)->LoadImageAsync(
315 app
, image
, gfx::Size(kIconSize
, kIconSize
),
316 base::Bind(&KioskAppData::OnExtensionIconLoaded
, AsWeakPtr()));
319 bool KioskAppData::IsLoading() const {
320 return status_
== STATUS_LOADING
;
323 void KioskAppData::SetStatus(Status status
) {
324 if (status_
== status
)
337 delegate_
->OnKioskAppDataChanged(app_id_
);
340 delegate_
->OnKioskAppDataLoadFailure(app_id_
);
345 net::URLRequestContextGetter
* KioskAppData::GetRequestContextGetter() {
346 return g_browser_process
->system_request_context();
349 bool KioskAppData::LoadFromCache() {
350 std::string app_key
= std::string(KioskAppManager::kKeyApps
) + '.' + app_id_
;
351 std::string name_key
= app_key
+ '.' + kKeyName
;
352 std::string icon_path_key
= app_key
+ '.' + kKeyIcon
;
354 PrefService
* local_state
= g_browser_process
->local_state();
355 const base::DictionaryValue
* dict
=
356 local_state
->GetDictionary(KioskAppManager::kKioskDictionaryName
);
359 std::string icon_path_string
;
360 if (!dict
->GetString(name_key
, &name_
) ||
361 !dict
->GetString(icon_path_key
, &icon_path_string
)) {
364 icon_path_
= base::FilePath(icon_path_string
);
366 // IconLoader deletes itself when done.
367 (new IconLoader(AsWeakPtr(), icon_path_
))->Start();
371 void KioskAppData::SetCache(const std::string
& name
,
372 const base::FilePath
& icon_path
) {
373 std::string app_key
= std::string(KioskAppManager::kKeyApps
) + '.' + app_id_
;
374 std::string name_key
= app_key
+ '.' + kKeyName
;
375 std::string icon_path_key
= app_key
+ '.' + kKeyIcon
;
377 PrefService
* local_state
= g_browser_process
->local_state();
378 DictionaryPrefUpdate
dict_update(local_state
,
379 KioskAppManager::kKioskDictionaryName
);
380 dict_update
->SetString(name_key
, name
);
381 dict_update
->SetString(icon_path_key
, icon_path
.value());
382 icon_path_
= icon_path
;
385 void KioskAppData::SetCache(const std::string
& name
, const SkBitmap
& icon
) {
386 icon_
= gfx::ImageSkia::CreateFrom1xBitmap(icon
);
387 icon_
.MakeThreadSafe();
389 std::vector
<unsigned char> image_data
;
390 CHECK(gfx::PNGCodec::EncodeBGRASkBitmap(icon
, false, &image_data
));
391 raw_icon_
= new base::RefCountedString
;
392 raw_icon_
->data().assign(image_data
.begin(), image_data
.end());
394 base::FilePath cache_dir
;
396 delegate_
->GetKioskAppIconCacheDir(&cache_dir
);
398 base::FilePath icon_path
=
399 cache_dir
.AppendASCII(app_id_
).AddExtension(kIconFileExtension
);
400 BrowserThread::GetBlockingPool()->PostTask(
402 base::Bind(&SaveIconToLocalOnBlockingPool
, icon_path
, raw_icon_
));
404 SetCache(name
, icon_path
);
407 void KioskAppData::OnExtensionIconLoaded(const gfx::Image
& icon
) {
408 if (icon
.IsEmpty()) {
409 LOG(WARNING
) << "Failed to load icon from installed app"
410 << ", id=" << app_id_
;
411 SetCache(name_
, *extensions::IconsInfo::GetDefaultAppIcon().bitmap());
413 SetCache(name_
, icon
.AsBitmap());
416 SetStatus(STATUS_LOADED
);
419 void KioskAppData::OnIconLoadSuccess(
420 const scoped_refptr
<base::RefCountedString
>& raw_icon
,
421 const gfx::ImageSkia
& icon
) {
422 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
423 raw_icon_
= raw_icon
;
425 SetStatus(STATUS_LOADED
);
428 void KioskAppData::OnIconLoadFailure() {
429 // Re-fetch data from web store when failed to load cached data.
433 void KioskAppData::OnWebstoreParseSuccess(const SkBitmap
& icon
) {
434 SetCache(name_
, icon
);
435 SetStatus(STATUS_LOADED
);
438 void KioskAppData::OnWebstoreParseFailure() {
439 SetStatus(STATUS_ERROR
);
442 void KioskAppData::StartFetch() {
443 webstore_fetcher_
.reset(new extensions::WebstoreDataFetcher(
445 GetRequestContextGetter(),
448 webstore_fetcher_
->Start();
451 void KioskAppData::OnWebstoreRequestFailure() {
452 SetStatus(STATUS_ERROR
);
455 void KioskAppData::OnWebstoreResponseParseSuccess(
456 scoped_ptr
<base::DictionaryValue
> webstore_data
) {
457 // Takes ownership of |webstore_data|.
458 webstore_fetcher_
.reset();
460 std::string manifest
;
461 if (!CheckResponseKeyValue(webstore_data
.get(), kManifestKey
, &manifest
))
464 if (!CheckResponseKeyValue(webstore_data
.get(), kLocalizedNameKey
, &name_
))
467 std::string icon_url_string
;
468 if (!CheckResponseKeyValue(webstore_data
.get(), kIconUrlKey
,
472 GURL icon_url
= GURL(extension_urls::GetWebstoreLaunchURL()).Resolve(
474 if (!icon_url
.is_valid()) {
475 LOG(ERROR
) << "Webstore response error (icon url): "
476 << ValueToString(webstore_data
.get());
477 OnWebstoreResponseParseFailure(kInvalidWebstoreResponseError
);
481 // WebstoreDataParser deletes itself when done.
482 (new WebstoreDataParser(AsWeakPtr()))->Start(app_id_
,
485 GetRequestContextGetter());
488 void KioskAppData::OnWebstoreResponseParseFailure(const std::string
& error
) {
489 LOG(ERROR
) << "Webstore failed for kiosk app " << app_id_
491 webstore_fetcher_
.reset();
492 SetStatus(STATUS_ERROR
);
495 bool KioskAppData::CheckResponseKeyValue(const base::DictionaryValue
* response
,
497 std::string
* value
) {
498 if (!response
->GetString(key
, value
)) {
499 LOG(ERROR
) << "Webstore response error (" << key
500 << "): " << ValueToString(response
);
501 OnWebstoreResponseParseFailure(kInvalidWebstoreResponseError
);
507 } // namespace chromeos