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/common/constants.h"
30 #include "extensions/common/extension_urls.h"
31 #include "extensions/common/manifest.h"
32 #include "extensions/common/manifest_constants.h"
33 #include "extensions/common/manifest_handlers/icons_handler.h"
34 #include "ui/gfx/codec/png_codec.h"
35 #include "ui/gfx/image/image.h"
37 using content::BrowserThread;
43 // Keys for local state data. See sample layout in KioskAppManager.
44 const char kKeyName[] = "name";
45 const char kKeyIcon[] = "icon";
47 const char kInvalidWebstoreResponseError[] = "Invalid Chrome Web Store reponse";
49 // Icon file extension.
50 const char kIconFileExtension[] = ".png";
52 // Save |raw_icon| for given |app_id|.
53 void SaveIconToLocalOnBlockingPool(
54 const base::FilePath& icon_path,
55 scoped_refptr<base::RefCountedString> raw_icon) {
56 DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
58 base::FilePath dir = icon_path.DirName();
59 if (!base::PathExists(dir))
60 CHECK(base::CreateDirectory(dir));
62 CHECK_EQ(static_cast<int>(raw_icon->size()),
63 base::WriteFile(icon_path,
64 raw_icon->data().c_str(), raw_icon->size()));
67 // Returns true for valid kiosk app manifest.
68 bool IsValidKioskAppManifest(const extensions::Manifest& manifest) {
70 if (manifest.GetBoolean(extensions::manifest_keys::kKioskEnabled,
78 std::string ValueToString(const base::Value* value) {
80 base::JSONWriter::Write(value, &json);
86 ////////////////////////////////////////////////////////////////////////////////
87 // KioskAppData::IconLoader
88 // Loads locally stored icon data and decode it.
90 class KioskAppData::IconLoader : public ImageDecoder::Delegate {
98 IconLoader(const base::WeakPtr<KioskAppData>& client,
99 const base::FilePath& icon_path)
101 icon_path_(icon_path),
102 load_result_(SUCCESS) {}
105 base::SequencedWorkerPool* pool = BrowserThread::GetBlockingPool();
106 base::SequencedWorkerPool::SequenceToken token = pool->GetSequenceToken();
107 task_runner_ = pool->GetSequencedTaskRunnerWithShutdownBehavior(
109 base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
110 task_runner_->PostTask(FROM_HERE,
111 base::Bind(&IconLoader::LoadOnBlockingPool,
112 base::Unretained(this)));
116 friend class base::RefCountedThreadSafe<IconLoader>;
118 virtual ~IconLoader() {}
120 // Loads the icon from locally stored |icon_path_| on the blocking pool
121 void LoadOnBlockingPool() {
122 DCHECK(task_runner_->RunsTasksOnCurrentThread());
125 if (!base::ReadFileToString(base::FilePath(icon_path_), &data)) {
126 ReportResultOnBlockingPool(FAILED_TO_LOAD);
129 raw_icon_ = base::RefCountedString::TakeString(&data);
131 scoped_refptr<ImageDecoder> image_decoder = new ImageDecoder(
132 this, raw_icon_->data(), ImageDecoder::DEFAULT_CODEC);
133 image_decoder->Start(task_runner_);
136 void ReportResultOnBlockingPool(LoadResult result) {
137 DCHECK(task_runner_->RunsTasksOnCurrentThread());
139 load_result_ = result;
140 BrowserThread::PostTask(
143 base::Bind(&IconLoader::ReportResultOnUIThread,
144 base::Unretained(this)));
147 void NotifyClient() {
151 if (load_result_ == SUCCESS)
152 client_->OnIconLoadSuccess(raw_icon_, icon_);
154 client_->OnIconLoadFailure();
157 void ReportResultOnUIThread() {
158 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
164 // ImageDecoder::Delegate overrides:
165 virtual void OnImageDecoded(const ImageDecoder* decoder,
166 const SkBitmap& decoded_image) override {
167 icon_ = gfx::ImageSkia::CreateFrom1xBitmap(decoded_image);
168 icon_.MakeThreadSafe();
169 ReportResultOnBlockingPool(SUCCESS);
172 virtual void OnDecodeImageFailed(const ImageDecoder* decoder) override {
173 ReportResultOnBlockingPool(FAILED_TO_DECODE);
176 base::WeakPtr<KioskAppData> client_;
177 base::FilePath icon_path_;
179 LoadResult load_result_;
180 scoped_refptr<base::SequencedTaskRunner> task_runner_;
182 gfx::ImageSkia icon_;
183 scoped_refptr<base::RefCountedString> raw_icon_;
185 DISALLOW_COPY_AND_ASSIGN(IconLoader);
188 ////////////////////////////////////////////////////////////////////////////////
189 // KioskAppData::WebstoreDataParser
190 // Use WebstoreInstallHelper to parse the manifest and decode the icon.
192 class KioskAppData::WebstoreDataParser
193 : public extensions::WebstoreInstallHelper::Delegate {
195 explicit WebstoreDataParser(const base::WeakPtr<KioskAppData>& client)
198 void Start(const std::string& app_id,
199 const std::string& manifest,
200 const GURL& icon_url,
201 net::URLRequestContextGetter* context_getter) {
202 scoped_refptr<extensions::WebstoreInstallHelper> webstore_helper =
203 new extensions::WebstoreInstallHelper(this,
209 webstore_helper->Start();
213 friend class base::RefCounted<WebstoreDataParser>;
215 virtual ~WebstoreDataParser() {}
217 void ReportFailure() {
219 client_->OnWebstoreParseFailure();
224 // WebstoreInstallHelper::Delegate overrides:
225 virtual void OnWebstoreParseSuccess(
226 const std::string& id,
227 const SkBitmap& icon,
228 base::DictionaryValue* parsed_manifest) override {
229 // Takes ownership of |parsed_manifest|.
230 extensions::Manifest manifest(
231 extensions::Manifest::INVALID_LOCATION,
232 scoped_ptr<base::DictionaryValue>(parsed_manifest));
234 if (!IsValidKioskAppManifest(manifest)) {
240 client_->OnWebstoreParseSuccess(icon);
243 virtual void OnWebstoreParseFailure(
244 const std::string& id,
245 InstallHelperResultCode result_code,
246 const std::string& error_message) override {
250 base::WeakPtr<KioskAppData> client_;
252 DISALLOW_COPY_AND_ASSIGN(WebstoreDataParser);
255 ////////////////////////////////////////////////////////////////////////////////
258 KioskAppData::KioskAppData(KioskAppDataDelegate* delegate,
259 const std::string& app_id,
260 const std::string& user_id,
261 const GURL& update_url)
262 : delegate_(delegate),
263 status_(STATUS_INIT),
266 update_url_(update_url) {
269 KioskAppData::~KioskAppData() {}
271 void KioskAppData::Load() {
272 SetStatus(STATUS_LOADING);
280 void KioskAppData::ClearCache() {
281 PrefService* local_state = g_browser_process->local_state();
283 DictionaryPrefUpdate dict_update(local_state,
284 KioskAppManager::kKioskDictionaryName);
286 std::string app_key = std::string(KioskAppManager::kKeyApps) + '.' + app_id_;
287 dict_update->Remove(app_key, NULL);
289 if (!icon_path_.empty()) {
290 BrowserThread::PostBlockingPoolTask(
292 base::Bind(base::IgnoreResult(&base::DeleteFile), icon_path_, false));
296 void KioskAppData::LoadFromInstalledApp(Profile* profile,
297 const extensions::Extension* app) {
298 SetStatus(STATUS_LOADING);
301 app = extensions::ExtensionSystem::Get(profile)
302 ->extension_service()
303 ->GetInstalledExtension(app_id_);
306 DCHECK_EQ(app_id_, app->id());
310 const int kIconSize = extension_misc::EXTENSION_ICON_LARGE;
311 extensions::ExtensionResource image = extensions::IconsInfo::GetIconResource(
312 app, kIconSize, ExtensionIconSet::MATCH_BIGGER);
313 extensions::ImageLoader::Get(profile)->LoadImageAsync(
314 app, image, gfx::Size(kIconSize, kIconSize),
315 base::Bind(&KioskAppData::OnExtensionIconLoaded, AsWeakPtr()));
318 bool KioskAppData::IsLoading() const {
319 return status_ == STATUS_LOADING;
322 void KioskAppData::SetStatus(Status status) {
323 if (status_ == status)
336 delegate_->OnKioskAppDataChanged(app_id_);
339 delegate_->OnKioskAppDataLoadFailure(app_id_);
344 net::URLRequestContextGetter* KioskAppData::GetRequestContextGetter() {
345 return g_browser_process->system_request_context();
348 bool KioskAppData::LoadFromCache() {
349 std::string app_key = std::string(KioskAppManager::kKeyApps) + '.' + app_id_;
350 std::string name_key = app_key + '.' + kKeyName;
351 std::string icon_path_key = app_key + '.' + kKeyIcon;
353 PrefService* local_state = g_browser_process->local_state();
354 const base::DictionaryValue* dict =
355 local_state->GetDictionary(KioskAppManager::kKioskDictionaryName);
358 std::string icon_path_string;
359 if (!dict->GetString(name_key, &name_) ||
360 !dict->GetString(icon_path_key, &icon_path_string)) {
363 icon_path_ = base::FilePath(icon_path_string);
365 // IconLoader deletes itself when done.
366 (new IconLoader(AsWeakPtr(), icon_path_))->Start();
370 void KioskAppData::SetCache(const std::string& name,
371 const base::FilePath& icon_path) {
372 std::string app_key = std::string(KioskAppManager::kKeyApps) + '.' + app_id_;
373 std::string name_key = app_key + '.' + kKeyName;
374 std::string icon_path_key = app_key + '.' + kKeyIcon;
376 PrefService* local_state = g_browser_process->local_state();
377 DictionaryPrefUpdate dict_update(local_state,
378 KioskAppManager::kKioskDictionaryName);
379 dict_update->SetString(name_key, name);
380 dict_update->SetString(icon_path_key, icon_path.value());
381 icon_path_ = icon_path;
384 void KioskAppData::SetCache(const std::string& name, const SkBitmap& icon) {
385 icon_ = gfx::ImageSkia::CreateFrom1xBitmap(icon);
386 icon_.MakeThreadSafe();
388 std::vector<unsigned char> image_data;
389 CHECK(gfx::PNGCodec::EncodeBGRASkBitmap(icon, false, &image_data));
390 raw_icon_ = new base::RefCountedString;
391 raw_icon_->data().assign(image_data.begin(), image_data.end());
393 base::FilePath cache_dir;
395 delegate_->GetKioskAppIconCacheDir(&cache_dir);
397 base::FilePath icon_path =
398 cache_dir.AppendASCII(app_id_).AddExtension(kIconFileExtension);
399 BrowserThread::GetBlockingPool()->PostTask(
401 base::Bind(&SaveIconToLocalOnBlockingPool, icon_path, raw_icon_));
403 SetCache(name, icon_path);
406 void KioskAppData::OnExtensionIconLoaded(const gfx::Image& icon) {
407 if (icon.IsEmpty()) {
408 LOG(WARNING) << "Failed to load icon from installed app"
409 << ", id=" << app_id_;
410 SetCache(name_, *extensions::util::GetDefaultAppIcon().bitmap());
412 SetCache(name_, icon.AsBitmap());
415 SetStatus(STATUS_LOADED);
418 void KioskAppData::OnIconLoadSuccess(
419 const scoped_refptr<base::RefCountedString>& raw_icon,
420 const gfx::ImageSkia& icon) {
421 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
422 raw_icon_ = raw_icon;
424 SetStatus(STATUS_LOADED);
427 void KioskAppData::OnIconLoadFailure() {
428 // Re-fetch data from web store when failed to load cached data.
432 void KioskAppData::OnWebstoreParseSuccess(const SkBitmap& icon) {
433 SetCache(name_, icon);
434 SetStatus(STATUS_LOADED);
437 void KioskAppData::OnWebstoreParseFailure() {
438 SetStatus(STATUS_ERROR);
441 void KioskAppData::StartFetch() {
442 webstore_fetcher_.reset(new extensions::WebstoreDataFetcher(
444 GetRequestContextGetter(),
447 webstore_fetcher_->set_max_auto_retries(3);
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