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