| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/chromeos/app_mode/kiosk_app_data.h" |
| |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/file_util.h" |
| #include "base/json/json_writer.h" |
| #include "base/memory/ref_counted_memory.h" |
| #include "base/prefs/pref_service.h" |
| #include "base/prefs/scoped_user_pref_update.h" |
| #include "base/threading/sequenced_worker_pool.h" |
| #include "base/values.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/chromeos/app_mode/kiosk_app_data_delegate.h" |
| #include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h" |
| #include "chrome/browser/extensions/webstore_data_fetcher.h" |
| #include "chrome/browser/extensions/webstore_install_helper.h" |
| #include "chrome/browser/image_decoder.h" |
| #include "chrome/common/extensions/extension_constants.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "extensions/common/manifest.h" |
| #include "extensions/common/manifest_constants.h" |
| #include "ui/gfx/codec/png_codec.h" |
| |
| using content::BrowserThread; |
| |
| namespace chromeos { |
| |
| namespace { |
| |
| // Keys for local state data. See sample layout in KioskAppManager. |
| const char kKeyName[] = "name"; |
| const char kKeyIcon[] = "icon"; |
| |
| // Web store data keys. |
| const char kManifestKey[] = "manifest"; |
| const char kIconUrlKey[] = "icon_url"; |
| const char kLocalizedNameKey[] = "localized_name"; |
| |
| const char kInvalidWebstoreResponseError[] = "Invalid Chrome Web Store reponse"; |
| |
| // Icon file extension. |
| const char kIconFileExtension[] = ".png"; |
| |
| // Save |raw_icon| for given |app_id|. |
| void SaveIconToLocalOnBlockingPool( |
| const base::FilePath& icon_path, |
| scoped_refptr<base::RefCountedString> raw_icon) { |
| DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); |
| |
| base::FilePath dir = icon_path.DirName(); |
| if (!base::PathExists(dir)) |
| CHECK(file_util::CreateDirectory(dir)); |
| |
| CHECK_EQ(static_cast<int>(raw_icon->size()), |
| file_util::WriteFile(icon_path, |
| raw_icon->data().c_str(), raw_icon->size())); |
| } |
| |
| // Returns true for valid kiosk app manifest. |
| bool IsValidKioskAppManifest(const extensions::Manifest& manifest) { |
| bool kiosk_enabled; |
| if (manifest.GetBoolean(extensions::manifest_keys::kKioskEnabled, |
| &kiosk_enabled)) { |
| return kiosk_enabled; |
| } |
| |
| return false; |
| } |
| |
| std::string ValueToString(const base::Value* value) { |
| std::string json; |
| base::JSONWriter::Write(value, &json); |
| return json; |
| } |
| |
| } // namespace |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // KioskAppData::IconLoader |
| // Loads locally stored icon data and decode it. |
| |
| class KioskAppData::IconLoader : public ImageDecoder::Delegate { |
| public: |
| enum LoadResult { |
| SUCCESS, |
| FAILED_TO_LOAD, |
| FAILED_TO_DECODE, |
| }; |
| |
| IconLoader(const base::WeakPtr<KioskAppData>& client, |
| const base::FilePath& icon_path) |
| : client_(client), |
| icon_path_(icon_path), |
| load_result_(SUCCESS) {} |
| |
| void Start() { |
| base::SequencedWorkerPool* pool = BrowserThread::GetBlockingPool(); |
| base::SequencedWorkerPool::SequenceToken token = pool->GetSequenceToken(); |
| task_runner_ = pool->GetSequencedTaskRunnerWithShutdownBehavior( |
| token, |
| base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN); |
| task_runner_->PostTask(FROM_HERE, |
| base::Bind(&IconLoader::LoadOnBlockingPool, |
| base::Unretained(this))); |
| } |
| |
| private: |
| friend class base::RefCountedThreadSafe<IconLoader>; |
| |
| virtual ~IconLoader() {} |
| |
| // Loads the icon from locally stored |icon_path_| on the blocking pool |
| void LoadOnBlockingPool() { |
| DCHECK(task_runner_->RunsTasksOnCurrentThread()); |
| |
| std::string data; |
| if (!base::ReadFileToString(base::FilePath(icon_path_), &data)) { |
| ReportResultOnBlockingPool(FAILED_TO_LOAD); |
| return; |
| } |
| raw_icon_ = base::RefCountedString::TakeString(&data); |
| |
| scoped_refptr<ImageDecoder> image_decoder = new ImageDecoder( |
| this, raw_icon_->data(), ImageDecoder::DEFAULT_CODEC); |
| image_decoder->Start(task_runner_); |
| } |
| |
| void ReportResultOnBlockingPool(LoadResult result) { |
| DCHECK(task_runner_->RunsTasksOnCurrentThread()); |
| |
| load_result_ = result; |
| BrowserThread::PostTask( |
| BrowserThread::UI, |
| FROM_HERE, |
| base::Bind(&IconLoader::ReportResultOnUIThread, |
| base::Unretained(this))); |
| } |
| |
| void NotifyClient() { |
| if (!client_) |
| return; |
| |
| if (load_result_ == SUCCESS) |
| client_->OnIconLoadSuccess(raw_icon_, icon_); |
| else |
| client_->OnIconLoadFailure(); |
| } |
| |
| void ReportResultOnUIThread() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| NotifyClient(); |
| delete this; |
| } |
| |
| // ImageDecoder::Delegate overrides: |
| virtual void OnImageDecoded(const ImageDecoder* decoder, |
| const SkBitmap& decoded_image) OVERRIDE { |
| icon_ = gfx::ImageSkia::CreateFrom1xBitmap(decoded_image); |
| icon_.MakeThreadSafe(); |
| ReportResultOnBlockingPool(SUCCESS); |
| } |
| |
| virtual void OnDecodeImageFailed(const ImageDecoder* decoder) OVERRIDE { |
| ReportResultOnBlockingPool(FAILED_TO_DECODE); |
| } |
| |
| base::WeakPtr<KioskAppData> client_; |
| base::FilePath icon_path_; |
| |
| LoadResult load_result_; |
| scoped_refptr<base::SequencedTaskRunner> task_runner_; |
| |
| gfx::ImageSkia icon_; |
| scoped_refptr<base::RefCountedString> raw_icon_; |
| |
| DISALLOW_COPY_AND_ASSIGN(IconLoader); |
| }; |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // KioskAppData::WebstoreDataParser |
| // Use WebstoreInstallHelper to parse the manifest and decode the icon. |
| |
| class KioskAppData::WebstoreDataParser |
| : public extensions::WebstoreInstallHelper::Delegate { |
| public: |
| explicit WebstoreDataParser(const base::WeakPtr<KioskAppData>& client) |
| : client_(client) {} |
| |
| void Start(const std::string& app_id, |
| const std::string& manifest, |
| const GURL& icon_url, |
| net::URLRequestContextGetter* context_getter) { |
| scoped_refptr<extensions::WebstoreInstallHelper> webstore_helper = |
| new extensions::WebstoreInstallHelper(this, |
| app_id, |
| manifest, |
| "", // No icon data. |
| icon_url, |
| context_getter); |
| webstore_helper->Start(); |
| } |
| |
| private: |
| friend class base::RefCounted<WebstoreDataParser>; |
| |
| virtual ~WebstoreDataParser() {} |
| |
| void ReportFailure() { |
| if (client_) |
| client_->OnWebstoreParseFailure(); |
| |
| delete this; |
| } |
| |
| // WebstoreInstallHelper::Delegate overrides: |
| virtual void OnWebstoreParseSuccess( |
| const std::string& id, |
| const SkBitmap& icon, |
| base::DictionaryValue* parsed_manifest) OVERRIDE { |
| // Takes ownership of |parsed_manifest|. |
| extensions::Manifest manifest( |
| extensions::Manifest::INVALID_LOCATION, |
| scoped_ptr<base::DictionaryValue>(parsed_manifest)); |
| |
| if (!IsValidKioskAppManifest(manifest)) { |
| ReportFailure(); |
| return; |
| } |
| |
| if (client_) |
| client_->OnWebstoreParseSuccess(icon); |
| delete this; |
| } |
| virtual void OnWebstoreParseFailure( |
| const std::string& id, |
| InstallHelperResultCode result_code, |
| const std::string& error_message) OVERRIDE { |
| ReportFailure(); |
| } |
| |
| base::WeakPtr<KioskAppData> client_; |
| |
| DISALLOW_COPY_AND_ASSIGN(WebstoreDataParser); |
| }; |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // KioskAppData |
| |
| KioskAppData::KioskAppData(KioskAppDataDelegate* delegate, |
| const std::string& app_id, |
| const std::string& user_id) |
| : delegate_(delegate), |
| status_(STATUS_INIT), |
| app_id_(app_id), |
| user_id_(user_id) { |
| } |
| |
| KioskAppData::~KioskAppData() {} |
| |
| void KioskAppData::Load() { |
| SetStatus(STATUS_LOADING); |
| |
| if (LoadFromCache()) |
| return; |
| |
| StartFetch(); |
| } |
| |
| void KioskAppData::ClearCache() { |
| PrefService* local_state = g_browser_process->local_state(); |
| |
| DictionaryPrefUpdate dict_update(local_state, |
| KioskAppManager::kKioskDictionaryName); |
| |
| std::string app_key = std::string(KioskAppManager::kKeyApps) + '.' + app_id_; |
| dict_update->Remove(app_key, NULL); |
| |
| if (!icon_path_.empty()) { |
| BrowserThread::PostBlockingPoolTask( |
| FROM_HERE, |
| base::Bind(base::IgnoreResult(&base::DeleteFile), icon_path_, false)); |
| } |
| } |
| |
| bool KioskAppData::IsLoading() const { |
| return status_ == STATUS_LOADING; |
| } |
| |
| void KioskAppData::SetStatus(Status status) { |
| if (status_ == status) |
| return; |
| |
| status_ = status; |
| |
| if (!delegate_) |
| return; |
| |
| switch (status_) { |
| case STATUS_INIT: |
| break; |
| case STATUS_LOADING: |
| case STATUS_LOADED: |
| delegate_->OnKioskAppDataChanged(app_id_); |
| break; |
| case STATUS_ERROR: |
| delegate_->OnKioskAppDataLoadFailure(app_id_); |
| break; |
| }; |
| } |
| |
| net::URLRequestContextGetter* KioskAppData::GetRequestContextGetter() { |
| return g_browser_process->system_request_context(); |
| } |
| |
| bool KioskAppData::LoadFromCache() { |
| std::string app_key = std::string(KioskAppManager::kKeyApps) + '.' + app_id_; |
| std::string name_key = app_key + '.' + kKeyName; |
| std::string icon_path_key = app_key + '.' + kKeyIcon; |
| |
| PrefService* local_state = g_browser_process->local_state(); |
| const base::DictionaryValue* dict = |
| local_state->GetDictionary(KioskAppManager::kKioskDictionaryName); |
| |
| icon_path_.clear(); |
| std::string icon_path_string; |
| if (!dict->GetString(name_key, &name_) || |
| !dict->GetString(icon_path_key, &icon_path_string)) { |
| return false; |
| } |
| icon_path_ = base::FilePath(icon_path_string); |
| |
| // IconLoader deletes itself when done. |
| (new IconLoader(AsWeakPtr(), icon_path_))->Start(); |
| return true; |
| } |
| |
| void KioskAppData::SetCache(const std::string& name, |
| const base::FilePath& icon_path) { |
| std::string app_key = std::string(KioskAppManager::kKeyApps) + '.' + app_id_; |
| std::string name_key = app_key + '.' + kKeyName; |
| std::string icon_path_key = app_key + '.' + kKeyIcon; |
| |
| PrefService* local_state = g_browser_process->local_state(); |
| DictionaryPrefUpdate dict_update(local_state, |
| KioskAppManager::kKioskDictionaryName); |
| dict_update->SetString(name_key, name); |
| dict_update->SetString(icon_path_key, icon_path.value()); |
| icon_path_ = icon_path; |
| } |
| |
| void KioskAppData::OnIconLoadSuccess( |
| const scoped_refptr<base::RefCountedString>& raw_icon, |
| const gfx::ImageSkia& icon) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| raw_icon_ = raw_icon; |
| icon_ = icon; |
| SetStatus(STATUS_LOADED); |
| } |
| |
| void KioskAppData::OnIconLoadFailure() { |
| // Re-fetch data from web store when failed to load cached data. |
| StartFetch(); |
| } |
| |
| void KioskAppData::OnWebstoreParseSuccess(const SkBitmap& icon) { |
| icon_ = gfx::ImageSkia::CreateFrom1xBitmap(icon); |
| icon_.MakeThreadSafe(); |
| |
| std::vector<unsigned char> image_data; |
| CHECK(gfx::PNGCodec::EncodeBGRASkBitmap(icon, false, &image_data)); |
| raw_icon_ = new base::RefCountedString; |
| raw_icon_->data().assign(image_data.begin(), image_data.end()); |
| |
| base::FilePath cache_dir; |
| if (delegate_) |
| delegate_->GetKioskAppIconCacheDir(&cache_dir); |
| |
| base::FilePath icon_path = |
| cache_dir.AppendASCII(app_id_).AddExtension(kIconFileExtension); |
| BrowserThread::GetBlockingPool()->PostTask( |
| FROM_HERE, |
| base::Bind(&SaveIconToLocalOnBlockingPool, icon_path, raw_icon_)); |
| |
| SetCache(name_, icon_path); |
| SetStatus(STATUS_LOADED); |
| } |
| |
| void KioskAppData::OnWebstoreParseFailure() { |
| SetStatus(STATUS_ERROR); |
| } |
| |
| void KioskAppData::StartFetch() { |
| webstore_fetcher_.reset(new extensions::WebstoreDataFetcher( |
| this, |
| GetRequestContextGetter(), |
| GURL(), |
| app_id_)); |
| webstore_fetcher_->Start(); |
| } |
| |
| void KioskAppData::OnWebstoreRequestFailure() { |
| SetStatus(STATUS_ERROR); |
| } |
| |
| void KioskAppData::OnWebstoreResponseParseSuccess( |
| base::DictionaryValue* webstore_data) { |
| // Takes ownership of |webstore_data|. |
| scoped_ptr<base::DictionaryValue> data(webstore_data); |
| |
| webstore_fetcher_.reset(); |
| |
| std::string manifest; |
| if (!CheckResponseKeyValue(data.get(), kManifestKey, &manifest)) |
| return; |
| |
| if (!CheckResponseKeyValue(data.get(), kLocalizedNameKey, &name_)) |
| return; |
| |
| std::string icon_url_string; |
| if (!CheckResponseKeyValue(data.get(), kIconUrlKey, &icon_url_string)) |
| return; |
| |
| GURL icon_url = GURL(extension_urls::GetWebstoreLaunchURL()).Resolve( |
| icon_url_string); |
| if (!icon_url.is_valid()) { |
| LOG(ERROR) << "Webstore response error (icon url): " |
| << ValueToString(data.get()); |
| OnWebstoreResponseParseFailure(kInvalidWebstoreResponseError); |
| return; |
| } |
| |
| // WebstoreDataParser deletes itself when done. |
| (new WebstoreDataParser(AsWeakPtr()))->Start(app_id_, |
| manifest, |
| icon_url, |
| GetRequestContextGetter()); |
| } |
| |
| void KioskAppData::OnWebstoreResponseParseFailure(const std::string& error) { |
| LOG(ERROR) << "Webstore failed for kiosk app " << app_id_ |
| << ", " << error; |
| webstore_fetcher_.reset(); |
| SetStatus(STATUS_ERROR); |
| } |
| |
| bool KioskAppData::CheckResponseKeyValue(const base::DictionaryValue* response, |
| const char* key, |
| std::string* value) { |
| if (!response->GetString(key, value)) { |
| LOG(ERROR) << "Webstore response error (" << key |
| << "): " << ValueToString(response); |
| OnWebstoreResponseParseFailure(kInvalidWebstoreResponseError); |
| return false; |
| } |
| return true; |
| } |
| |
| } // namespace chromeos |