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/image_loader.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 "chrome/common/extensions/manifest_handlers/icons_handler.h"
28 #include "content/public/browser/browser_thread.h"
29 #include "extensions/browser/extension_system.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 const char kInvalidWebstoreResponseError[] = "Invalid Chrome Web Store reponse";
47 // Icon file extension.
48 const char kIconFileExtension[] = ".png";
50 // Save |raw_icon| for given |app_id|.
51 void SaveIconToLocalOnBlockingPool(
52 const base::FilePath& icon_path,
53 scoped_refptr<base::RefCountedString> raw_icon) {
54 DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
56 base::FilePath dir = icon_path.DirName();
57 if (!base::PathExists(dir))
58 CHECK(base::CreateDirectory(dir));
60 CHECK_EQ(static_cast<int>(raw_icon->size()),
61 file_util::WriteFile(icon_path,
62 raw_icon->data().c_str(), raw_icon->size()));
65 // Returns true for valid kiosk app manifest.
66 bool IsValidKioskAppManifest(const extensions::Manifest& manifest) {
68 if (manifest.GetBoolean(extensions::manifest_keys::kKioskEnabled,
76 std::string ValueToString(const base::Value* value) {
78 base::JSONWriter::Write(value, &json);
84 ////////////////////////////////////////////////////////////////////////////////
85 // KioskAppData::IconLoader
86 // Loads locally stored icon data and decode it.
88 class KioskAppData::IconLoader : public ImageDecoder::Delegate {
96 IconLoader(const base::WeakPtr<KioskAppData>& client,
97 const base::FilePath& icon_path)
99 icon_path_(icon_path),
100 load_result_(SUCCESS) {}
103 base::SequencedWorkerPool* pool = BrowserThread::GetBlockingPool();
104 base::SequencedWorkerPool::SequenceToken token = pool->GetSequenceToken();
105 task_runner_ = pool->GetSequencedTaskRunnerWithShutdownBehavior(
107 base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
108 task_runner_->PostTask(FROM_HERE,
109 base::Bind(&IconLoader::LoadOnBlockingPool,
110 base::Unretained(this)));
114 friend class base::RefCountedThreadSafe<IconLoader>;
116 virtual ~IconLoader() {}
118 // Loads the icon from locally stored |icon_path_| on the blocking pool
119 void LoadOnBlockingPool() {
120 DCHECK(task_runner_->RunsTasksOnCurrentThread());
123 if (!base::ReadFileToString(base::FilePath(icon_path_), &data)) {
124 ReportResultOnBlockingPool(FAILED_TO_LOAD);
127 raw_icon_ = base::RefCountedString::TakeString(&data);
129 scoped_refptr<ImageDecoder> image_decoder = new ImageDecoder(
130 this, raw_icon_->data(), ImageDecoder::DEFAULT_CODEC);
131 image_decoder->Start(task_runner_);
134 void ReportResultOnBlockingPool(LoadResult result) {
135 DCHECK(task_runner_->RunsTasksOnCurrentThread());
137 load_result_ = result;
138 BrowserThread::PostTask(
141 base::Bind(&IconLoader::ReportResultOnUIThread,
142 base::Unretained(this)));
145 void NotifyClient() {
149 if (load_result_ == SUCCESS)
150 client_->OnIconLoadSuccess(raw_icon_, icon_);
152 client_->OnIconLoadFailure();
155 void ReportResultOnUIThread() {
156 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
162 // ImageDecoder::Delegate overrides:
163 virtual void OnImageDecoded(const ImageDecoder* decoder,
164 const SkBitmap& decoded_image) OVERRIDE {
165 icon_ = gfx::ImageSkia::CreateFrom1xBitmap(decoded_image);
166 icon_.MakeThreadSafe();
167 ReportResultOnBlockingPool(SUCCESS);
170 virtual void OnDecodeImageFailed(const ImageDecoder* decoder) OVERRIDE {
171 ReportResultOnBlockingPool(FAILED_TO_DECODE);
174 base::WeakPtr<KioskAppData> client_;
175 base::FilePath icon_path_;
177 LoadResult load_result_;
178 scoped_refptr<base::SequencedTaskRunner> task_runner_;
180 gfx::ImageSkia icon_;
181 scoped_refptr<base::RefCountedString> raw_icon_;
183 DISALLOW_COPY_AND_ASSIGN(IconLoader);
186 ////////////////////////////////////////////////////////////////////////////////
187 // KioskAppData::WebstoreDataParser
188 // Use WebstoreInstallHelper to parse the manifest and decode the icon.
190 class KioskAppData::WebstoreDataParser
191 : public extensions::WebstoreInstallHelper::Delegate {
193 explicit WebstoreDataParser(const base::WeakPtr<KioskAppData>& client)
196 void Start(const std::string& app_id,
197 const std::string& manifest,
198 const GURL& icon_url,
199 net::URLRequestContextGetter* context_getter) {
200 scoped_refptr<extensions::WebstoreInstallHelper> webstore_helper =
201 new extensions::WebstoreInstallHelper(this,
207 webstore_helper->Start();
211 friend class base::RefCounted<WebstoreDataParser>;
213 virtual ~WebstoreDataParser() {}
215 void ReportFailure() {
217 client_->OnWebstoreParseFailure();
222 // WebstoreInstallHelper::Delegate overrides:
223 virtual void OnWebstoreParseSuccess(
224 const std::string& id,
225 const SkBitmap& icon,
226 base::DictionaryValue* parsed_manifest) OVERRIDE {
227 // Takes ownership of |parsed_manifest|.
228 extensions::Manifest manifest(
229 extensions::Manifest::INVALID_LOCATION,
230 scoped_ptr<base::DictionaryValue>(parsed_manifest));
232 if (!IsValidKioskAppManifest(manifest)) {
238 client_->OnWebstoreParseSuccess(icon);
241 virtual void OnWebstoreParseFailure(
242 const std::string& id,
243 InstallHelperResultCode result_code,
244 const std::string& error_message) OVERRIDE {
248 base::WeakPtr<KioskAppData> client_;
250 DISALLOW_COPY_AND_ASSIGN(WebstoreDataParser);
253 ////////////////////////////////////////////////////////////////////////////////
256 KioskAppData::KioskAppData(KioskAppDataDelegate* delegate,
257 const std::string& app_id,
258 const std::string& user_id)
259 : delegate_(delegate),
260 status_(STATUS_INIT),
265 KioskAppData::~KioskAppData() {}
267 void KioskAppData::Load() {
268 SetStatus(STATUS_LOADING);
276 void KioskAppData::ClearCache() {
277 PrefService* local_state = g_browser_process->local_state();
279 DictionaryPrefUpdate dict_update(local_state,
280 KioskAppManager::kKioskDictionaryName);
282 std::string app_key = std::string(KioskAppManager::kKeyApps) + '.' + app_id_;
283 dict_update->Remove(app_key, NULL);
285 if (!icon_path_.empty()) {
286 BrowserThread::PostBlockingPoolTask(
288 base::Bind(base::IgnoreResult(&base::DeleteFile), icon_path_, false));
292 void KioskAppData::LoadFromInstalledApp(Profile* profile,
293 const extensions::Extension* app) {
294 SetStatus(STATUS_LOADING);
297 app = extensions::ExtensionSystem::Get(profile)
298 ->extension_service()
299 ->GetInstalledExtension(app_id_);
302 DCHECK_EQ(app_id_, app->id());
306 const int kIconSize = extension_misc::EXTENSION_ICON_LARGE;
307 extensions::ExtensionResource image = extensions::IconsInfo::GetIconResource(
308 app, kIconSize, ExtensionIconSet::MATCH_BIGGER);
309 extensions::ImageLoader::Get(profile)->LoadImageAsync(
310 app, image, gfx::Size(kIconSize, kIconSize),
311 base::Bind(&KioskAppData::OnExtensionIconLoaded, AsWeakPtr()));
314 bool KioskAppData::IsLoading() const {
315 return status_ == STATUS_LOADING;
318 void KioskAppData::SetStatus(Status status) {
319 if (status_ == status)
332 delegate_->OnKioskAppDataChanged(app_id_);
335 delegate_->OnKioskAppDataLoadFailure(app_id_);
340 net::URLRequestContextGetter* KioskAppData::GetRequestContextGetter() {
341 return g_browser_process->system_request_context();
344 bool KioskAppData::LoadFromCache() {
345 std::string app_key = std::string(KioskAppManager::kKeyApps) + '.' + app_id_;
346 std::string name_key = app_key + '.' + kKeyName;
347 std::string icon_path_key = app_key + '.' + kKeyIcon;
349 PrefService* local_state = g_browser_process->local_state();
350 const base::DictionaryValue* dict =
351 local_state->GetDictionary(KioskAppManager::kKioskDictionaryName);
354 std::string icon_path_string;
355 if (!dict->GetString(name_key, &name_) ||
356 !dict->GetString(icon_path_key, &icon_path_string)) {
359 icon_path_ = base::FilePath(icon_path_string);
361 // IconLoader deletes itself when done.
362 (new IconLoader(AsWeakPtr(), icon_path_))->Start();
366 void KioskAppData::SetCache(const std::string& name,
367 const base::FilePath& icon_path) {
368 std::string app_key = std::string(KioskAppManager::kKeyApps) + '.' + app_id_;
369 std::string name_key = app_key + '.' + kKeyName;
370 std::string icon_path_key = app_key + '.' + kKeyIcon;
372 PrefService* local_state = g_browser_process->local_state();
373 DictionaryPrefUpdate dict_update(local_state,
374 KioskAppManager::kKioskDictionaryName);
375 dict_update->SetString(name_key, name);
376 dict_update->SetString(icon_path_key, icon_path.value());
377 icon_path_ = icon_path;
380 void KioskAppData::SetCache(const std::string& name, const SkBitmap& icon) {
381 icon_ = gfx::ImageSkia::CreateFrom1xBitmap(icon);
382 icon_.MakeThreadSafe();
384 std::vector<unsigned char> image_data;
385 CHECK(gfx::PNGCodec::EncodeBGRASkBitmap(icon, false, &image_data));
386 raw_icon_ = new base::RefCountedString;
387 raw_icon_->data().assign(image_data.begin(), image_data.end());
389 base::FilePath cache_dir;
391 delegate_->GetKioskAppIconCacheDir(&cache_dir);
393 base::FilePath icon_path =
394 cache_dir.AppendASCII(app_id_).AddExtension(kIconFileExtension);
395 BrowserThread::GetBlockingPool()->PostTask(
397 base::Bind(&SaveIconToLocalOnBlockingPool, icon_path, raw_icon_));
399 SetCache(name, icon_path);
402 void KioskAppData::OnExtensionIconLoaded(const gfx::Image& icon) {
403 if (icon.IsEmpty()) {
404 LOG(WARNING) << "Failed to load icon from installed app"
405 << ", id=" << app_id_;
406 SetCache(name_, *extensions::IconsInfo::GetDefaultAppIcon().bitmap());
408 SetCache(name_, icon.AsBitmap());
411 SetStatus(STATUS_LOADED);
414 void KioskAppData::OnIconLoadSuccess(
415 const scoped_refptr<base::RefCountedString>& raw_icon,
416 const gfx::ImageSkia& icon) {
417 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
418 raw_icon_ = raw_icon;
420 SetStatus(STATUS_LOADED);
423 void KioskAppData::OnIconLoadFailure() {
424 // Re-fetch data from web store when failed to load cached data.
428 void KioskAppData::OnWebstoreParseSuccess(const SkBitmap& icon) {
429 SetCache(name_, icon);
430 SetStatus(STATUS_LOADED);
433 void KioskAppData::OnWebstoreParseFailure() {
434 SetStatus(STATUS_ERROR);
437 void KioskAppData::StartFetch() {
438 webstore_fetcher_.reset(new extensions::WebstoreDataFetcher(
440 GetRequestContextGetter(),
443 webstore_fetcher_->Start();
446 void KioskAppData::OnWebstoreRequestFailure() {
447 SetStatus(STATUS_ERROR);
450 void KioskAppData::OnWebstoreResponseParseSuccess(
451 scoped_ptr<base::DictionaryValue> webstore_data) {
452 // Takes ownership of |webstore_data|.
453 webstore_fetcher_.reset();
455 std::string manifest;
456 if (!CheckResponseKeyValue(webstore_data.get(), kManifestKey, &manifest))
459 if (!CheckResponseKeyValue(webstore_data.get(), kLocalizedNameKey, &name_))
462 std::string icon_url_string;
463 if (!CheckResponseKeyValue(webstore_data.get(), kIconUrlKey,
467 GURL icon_url = GURL(extension_urls::GetWebstoreLaunchURL()).Resolve(
469 if (!icon_url.is_valid()) {
470 LOG(ERROR) << "Webstore response error (icon url): "
471 << ValueToString(webstore_data.get());
472 OnWebstoreResponseParseFailure(kInvalidWebstoreResponseError);
476 // WebstoreDataParser deletes itself when done.
477 (new WebstoreDataParser(AsWeakPtr()))->Start(app_id_,
480 GetRequestContextGetter());
483 void KioskAppData::OnWebstoreResponseParseFailure(const std::string& error) {
484 LOG(ERROR) << "Webstore failed for kiosk app " << app_id_
486 webstore_fetcher_.reset();
487 SetStatus(STATUS_ERROR);
490 bool KioskAppData::CheckResponseKeyValue(const base::DictionaryValue* response,
492 std::string* value) {
493 if (!response->GetString(key, value)) {
494 LOG(ERROR) << "Webstore response error (" << key
495 << "): " << ValueToString(response);
496 OnWebstoreResponseParseFailure(kInvalidWebstoreResponseError);
502 } // namespace chromeos