Home | History | Annotate | Download | only in app_mode
      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.
      4 
      5 #include "chrome/browser/chromeos/app_mode/kiosk_app_data.h"
      6 
      7 #include <vector>
      8 
      9 #include "base/bind.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_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 "chrome/common/extensions/extension_constants.h"
     27 #include "content/public/browser/browser_thread.h"
     28 #include "extensions/browser/extension_system.h"
     29 #include "extensions/browser/image_loader.h"
     30 #include "extensions/common/constants.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"
     36 
     37 using content::BrowserThread;
     38 
     39 namespace chromeos {
     40 
     41 namespace {
     42 
     43 // Keys for local state data. See sample layout in KioskAppManager.
     44 const char kKeyName[] = "name";
     45 const char kKeyIcon[] = "icon";
     46 
     47 const char kInvalidWebstoreResponseError[] = "Invalid Chrome Web Store reponse";
     48 
     49 // Icon file extension.
     50 const char kIconFileExtension[] = ".png";
     51 
     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());
     57 
     58   base::FilePath dir = icon_path.DirName();
     59   if (!base::PathExists(dir))
     60     CHECK(base::CreateDirectory(dir));
     61 
     62   CHECK_EQ(static_cast<int>(raw_icon->size()),
     63            base::WriteFile(icon_path,
     64                            raw_icon->data().c_str(), raw_icon->size()));
     65 }
     66 
     67 // Returns true for valid kiosk app manifest.
     68 bool IsValidKioskAppManifest(const extensions::Manifest& manifest) {
     69   bool kiosk_enabled;
     70   if (manifest.GetBoolean(extensions::manifest_keys::kKioskEnabled,
     71                           &kiosk_enabled)) {
     72     return kiosk_enabled;
     73   }
     74 
     75   return false;
     76 }
     77 
     78 std::string ValueToString(const base::Value* value) {
     79   std::string json;
     80   base::JSONWriter::Write(value, &json);
     81   return json;
     82 }
     83 
     84 }  // namespace
     85 
     86 ////////////////////////////////////////////////////////////////////////////////
     87 // KioskAppData::IconLoader
     88 // Loads locally stored icon data and decode it.
     89 
     90 class KioskAppData::IconLoader : public ImageDecoder::Delegate {
     91  public:
     92   enum LoadResult {
     93     SUCCESS,
     94     FAILED_TO_LOAD,
     95     FAILED_TO_DECODE,
     96   };
     97 
     98   IconLoader(const base::WeakPtr<KioskAppData>& client,
     99              const base::FilePath& icon_path)
    100       : client_(client),
    101         icon_path_(icon_path),
    102         load_result_(SUCCESS) {}
    103 
    104   void Start() {
    105     base::SequencedWorkerPool* pool = BrowserThread::GetBlockingPool();
    106     base::SequencedWorkerPool::SequenceToken token = pool->GetSequenceToken();
    107     task_runner_ = pool->GetSequencedTaskRunnerWithShutdownBehavior(
    108         token,
    109         base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
    110     task_runner_->PostTask(FROM_HERE,
    111                            base::Bind(&IconLoader::LoadOnBlockingPool,
    112                                       base::Unretained(this)));
    113   }
    114 
    115  private:
    116   friend class base::RefCountedThreadSafe<IconLoader>;
    117 
    118   virtual ~IconLoader() {}
    119 
    120   // Loads the icon from locally stored |icon_path_| on the blocking pool
    121   void LoadOnBlockingPool() {
    122     DCHECK(task_runner_->RunsTasksOnCurrentThread());
    123 
    124     std::string data;
    125     if (!base::ReadFileToString(base::FilePath(icon_path_), &data)) {
    126       ReportResultOnBlockingPool(FAILED_TO_LOAD);
    127       return;
    128     }
    129     raw_icon_ = base::RefCountedString::TakeString(&data);
    130 
    131     scoped_refptr<ImageDecoder> image_decoder = new ImageDecoder(
    132         this, raw_icon_->data(), ImageDecoder::DEFAULT_CODEC);
    133     image_decoder->Start(task_runner_);
    134   }
    135 
    136   void ReportResultOnBlockingPool(LoadResult result) {
    137     DCHECK(task_runner_->RunsTasksOnCurrentThread());
    138 
    139     load_result_ = result;
    140     BrowserThread::PostTask(
    141         BrowserThread::UI,
    142         FROM_HERE,
    143         base::Bind(&IconLoader::ReportResultOnUIThread,
    144                    base::Unretained(this)));
    145   }
    146 
    147   void NotifyClient() {
    148     if (!client_)
    149       return;
    150 
    151     if (load_result_ == SUCCESS)
    152       client_->OnIconLoadSuccess(raw_icon_, icon_);
    153     else
    154       client_->OnIconLoadFailure();
    155   }
    156 
    157   void ReportResultOnUIThread() {
    158     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    159 
    160     NotifyClient();
    161     delete this;
    162   }
    163 
    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);
    170   }
    171 
    172   virtual void OnDecodeImageFailed(const ImageDecoder* decoder) OVERRIDE {
    173     ReportResultOnBlockingPool(FAILED_TO_DECODE);
    174   }
    175 
    176   base::WeakPtr<KioskAppData> client_;
    177   base::FilePath icon_path_;
    178 
    179   LoadResult load_result_;
    180   scoped_refptr<base::SequencedTaskRunner> task_runner_;
    181 
    182   gfx::ImageSkia icon_;
    183   scoped_refptr<base::RefCountedString> raw_icon_;
    184 
    185   DISALLOW_COPY_AND_ASSIGN(IconLoader);
    186 };
    187 
    188 ////////////////////////////////////////////////////////////////////////////////
    189 // KioskAppData::WebstoreDataParser
    190 // Use WebstoreInstallHelper to parse the manifest and decode the icon.
    191 
    192 class KioskAppData::WebstoreDataParser
    193     : public extensions::WebstoreInstallHelper::Delegate {
    194  public:
    195   explicit WebstoreDataParser(const base::WeakPtr<KioskAppData>& client)
    196       : client_(client) {}
    197 
    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,
    204                                               app_id,
    205                                               manifest,
    206                                               "",  // No icon data.
    207                                               icon_url,
    208                                               context_getter);
    209     webstore_helper->Start();
    210   }
    211 
    212  private:
    213   friend class base::RefCounted<WebstoreDataParser>;
    214 
    215   virtual ~WebstoreDataParser() {}
    216 
    217   void ReportFailure() {
    218     if (client_)
    219       client_->OnWebstoreParseFailure();
    220 
    221     delete this;
    222   }
    223 
    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));
    233 
    234     if (!IsValidKioskAppManifest(manifest)) {
    235       ReportFailure();
    236       return;
    237     }
    238 
    239     if (client_)
    240       client_->OnWebstoreParseSuccess(icon);
    241     delete this;
    242   }
    243   virtual void OnWebstoreParseFailure(
    244       const std::string& id,
    245       InstallHelperResultCode result_code,
    246       const std::string& error_message) OVERRIDE {
    247     ReportFailure();
    248   }
    249 
    250   base::WeakPtr<KioskAppData> client_;
    251 
    252   DISALLOW_COPY_AND_ASSIGN(WebstoreDataParser);
    253 };
    254 
    255 ////////////////////////////////////////////////////////////////////////////////
    256 // KioskAppData
    257 
    258 KioskAppData::KioskAppData(KioskAppDataDelegate* delegate,
    259                            const std::string& app_id,
    260                            const std::string& user_id)
    261     : delegate_(delegate),
    262       status_(STATUS_INIT),
    263       app_id_(app_id),
    264       user_id_(user_id) {
    265 }
    266 
    267 KioskAppData::~KioskAppData() {}
    268 
    269 void KioskAppData::Load() {
    270   SetStatus(STATUS_LOADING);
    271 
    272   if (LoadFromCache())
    273     return;
    274 
    275   StartFetch();
    276 }
    277 
    278 void KioskAppData::ClearCache() {
    279   PrefService* local_state = g_browser_process->local_state();
    280 
    281   DictionaryPrefUpdate dict_update(local_state,
    282                                    KioskAppManager::kKioskDictionaryName);
    283 
    284   std::string app_key = std::string(KioskAppManager::kKeyApps) + '.' + app_id_;
    285   dict_update->Remove(app_key, NULL);
    286 
    287   if (!icon_path_.empty()) {
    288     BrowserThread::PostBlockingPoolTask(
    289         FROM_HERE,
    290         base::Bind(base::IgnoreResult(&base::DeleteFile), icon_path_, false));
    291   }
    292 }
    293 
    294 void KioskAppData::LoadFromInstalledApp(Profile* profile,
    295                                         const extensions::Extension* app) {
    296   SetStatus(STATUS_LOADING);
    297 
    298   if (!app) {
    299     app = extensions::ExtensionSystem::Get(profile)
    300               ->extension_service()
    301               ->GetInstalledExtension(app_id_);
    302   }
    303 
    304   DCHECK_EQ(app_id_, app->id());
    305 
    306   name_ = app->name();
    307 
    308   const int kIconSize = extension_misc::EXTENSION_ICON_LARGE;
    309   extensions::ExtensionResource image = extensions::IconsInfo::GetIconResource(
    310       app, kIconSize, ExtensionIconSet::MATCH_BIGGER);
    311   extensions::ImageLoader::Get(profile)->LoadImageAsync(
    312       app, image, gfx::Size(kIconSize, kIconSize),
    313       base::Bind(&KioskAppData::OnExtensionIconLoaded, AsWeakPtr()));
    314 }
    315 
    316 bool KioskAppData::IsLoading() const {
    317   return status_ == STATUS_LOADING;
    318 }
    319 
    320 void KioskAppData::SetStatus(Status status) {
    321   if (status_ == status)
    322     return;
    323 
    324   status_ = status;
    325 
    326   if (!delegate_)
    327     return;
    328 
    329   switch (status_) {
    330     case STATUS_INIT:
    331       break;
    332     case STATUS_LOADING:
    333     case STATUS_LOADED:
    334       delegate_->OnKioskAppDataChanged(app_id_);
    335       break;
    336     case STATUS_ERROR:
    337       delegate_->OnKioskAppDataLoadFailure(app_id_);
    338       break;
    339   }
    340 }
    341 
    342 net::URLRequestContextGetter* KioskAppData::GetRequestContextGetter() {
    343   return g_browser_process->system_request_context();
    344 }
    345 
    346 bool KioskAppData::LoadFromCache() {
    347   std::string app_key = std::string(KioskAppManager::kKeyApps) + '.' + app_id_;
    348   std::string name_key = app_key + '.' + kKeyName;
    349   std::string icon_path_key = app_key + '.' + kKeyIcon;
    350 
    351   PrefService* local_state = g_browser_process->local_state();
    352   const base::DictionaryValue* dict =
    353       local_state->GetDictionary(KioskAppManager::kKioskDictionaryName);
    354 
    355   icon_path_.clear();
    356   std::string icon_path_string;
    357   if (!dict->GetString(name_key, &name_) ||
    358       !dict->GetString(icon_path_key, &icon_path_string)) {
    359     return false;
    360   }
    361   icon_path_ = base::FilePath(icon_path_string);
    362 
    363   // IconLoader deletes itself when done.
    364   (new IconLoader(AsWeakPtr(), icon_path_))->Start();
    365   return true;
    366 }
    367 
    368 void KioskAppData::SetCache(const std::string& name,
    369                             const base::FilePath& icon_path) {
    370   std::string app_key = std::string(KioskAppManager::kKeyApps) + '.' + app_id_;
    371   std::string name_key = app_key + '.' + kKeyName;
    372   std::string icon_path_key = app_key + '.' + kKeyIcon;
    373 
    374   PrefService* local_state = g_browser_process->local_state();
    375   DictionaryPrefUpdate dict_update(local_state,
    376                                    KioskAppManager::kKioskDictionaryName);
    377   dict_update->SetString(name_key, name);
    378   dict_update->SetString(icon_path_key, icon_path.value());
    379   icon_path_ = icon_path;
    380 }
    381 
    382 void KioskAppData::SetCache(const std::string& name, const SkBitmap& icon) {
    383   icon_ = gfx::ImageSkia::CreateFrom1xBitmap(icon);
    384   icon_.MakeThreadSafe();
    385 
    386   std::vector<unsigned char> image_data;
    387   CHECK(gfx::PNGCodec::EncodeBGRASkBitmap(icon, false, &image_data));
    388   raw_icon_ = new base::RefCountedString;
    389   raw_icon_->data().assign(image_data.begin(), image_data.end());
    390 
    391   base::FilePath cache_dir;
    392   if (delegate_)
    393     delegate_->GetKioskAppIconCacheDir(&cache_dir);
    394 
    395   base::FilePath icon_path =
    396       cache_dir.AppendASCII(app_id_).AddExtension(kIconFileExtension);
    397   BrowserThread::GetBlockingPool()->PostTask(
    398       FROM_HERE,
    399       base::Bind(&SaveIconToLocalOnBlockingPool, icon_path, raw_icon_));
    400 
    401   SetCache(name, icon_path);
    402 }
    403 
    404 void KioskAppData::OnExtensionIconLoaded(const gfx::Image& icon) {
    405   if (icon.IsEmpty()) {
    406     LOG(WARNING) << "Failed to load icon from installed app"
    407                  << ", id=" << app_id_;
    408     SetCache(name_, *extensions::util::GetDefaultAppIcon().bitmap());
    409   } else {
    410     SetCache(name_, icon.AsBitmap());
    411   }
    412 
    413   SetStatus(STATUS_LOADED);
    414 }
    415 
    416 void KioskAppData::OnIconLoadSuccess(
    417     const scoped_refptr<base::RefCountedString>& raw_icon,
    418     const gfx::ImageSkia& icon) {
    419   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    420   raw_icon_ = raw_icon;
    421   icon_ = icon;
    422   SetStatus(STATUS_LOADED);
    423 }
    424 
    425 void KioskAppData::OnIconLoadFailure() {
    426   // Re-fetch data from web store when failed to load cached data.
    427   StartFetch();
    428 }
    429 
    430 void KioskAppData::OnWebstoreParseSuccess(const SkBitmap& icon) {
    431   SetCache(name_, icon);
    432   SetStatus(STATUS_LOADED);
    433 }
    434 
    435 void KioskAppData::OnWebstoreParseFailure() {
    436   SetStatus(STATUS_ERROR);
    437 }
    438 
    439 void KioskAppData::StartFetch() {
    440   webstore_fetcher_.reset(new extensions::WebstoreDataFetcher(
    441       this,
    442       GetRequestContextGetter(),
    443       GURL(),
    444       app_id_));
    445   webstore_fetcher_->set_max_auto_retries(3);
    446   webstore_fetcher_->Start();
    447 }
    448 
    449 void KioskAppData::OnWebstoreRequestFailure() {
    450   SetStatus(STATUS_ERROR);
    451 }
    452 
    453 void KioskAppData::OnWebstoreResponseParseSuccess(
    454       scoped_ptr<base::DictionaryValue> webstore_data) {
    455   // Takes ownership of |webstore_data|.
    456   webstore_fetcher_.reset();
    457 
    458   std::string manifest;
    459   if (!CheckResponseKeyValue(webstore_data.get(), kManifestKey, &manifest))
    460     return;
    461 
    462   if (!CheckResponseKeyValue(webstore_data.get(), kLocalizedNameKey, &name_))
    463     return;
    464 
    465   std::string icon_url_string;
    466   if (!CheckResponseKeyValue(webstore_data.get(), kIconUrlKey,
    467                              &icon_url_string))
    468     return;
    469 
    470   GURL icon_url = GURL(extension_urls::GetWebstoreLaunchURL()).Resolve(
    471       icon_url_string);
    472   if (!icon_url.is_valid()) {
    473     LOG(ERROR) << "Webstore response error (icon url): "
    474                << ValueToString(webstore_data.get());
    475     OnWebstoreResponseParseFailure(kInvalidWebstoreResponseError);
    476     return;
    477   }
    478 
    479   // WebstoreDataParser deletes itself when done.
    480   (new WebstoreDataParser(AsWeakPtr()))->Start(app_id_,
    481                                                manifest,
    482                                                icon_url,
    483                                                GetRequestContextGetter());
    484 }
    485 
    486 void KioskAppData::OnWebstoreResponseParseFailure(const std::string& error) {
    487   LOG(ERROR) << "Webstore failed for kiosk app " << app_id_
    488              << ", " << error;
    489   webstore_fetcher_.reset();
    490   SetStatus(STATUS_ERROR);
    491 }
    492 
    493 bool KioskAppData::CheckResponseKeyValue(const base::DictionaryValue* response,
    494                                          const char* key,
    495                                          std::string* value) {
    496   if (!response->GetString(key, value)) {
    497     LOG(ERROR) << "Webstore response error (" << key
    498                << "): " << ValueToString(response);
    499     OnWebstoreResponseParseFailure(kInvalidWebstoreResponseError);
    500     return false;
    501   }
    502   return true;
    503 }
    504 
    505 }  // namespace chromeos
    506