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/memory/ref_counted_memory.h"
     12 #include "base/prefs/pref_service.h"
     13 #include "base/threading/sequenced_worker_pool.h"
     14 #include "base/values.h"
     15 #include "chrome/browser/browser_process.h"
     16 #include "chrome/browser/chromeos/app_mode/kiosk_app_data_delegate.h"
     17 #include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h"
     18 #include "chrome/browser/extensions/webstore_data_fetcher.h"
     19 #include "chrome/browser/extensions/webstore_install_helper.h"
     20 #include "chrome/browser/image_decoder.h"
     21 #include "chrome/browser/prefs/scoped_user_pref_update.h"
     22 #include "chrome/common/extensions/extension_constants.h"
     23 #include "chrome/common/extensions/extension_manifest_constants.h"
     24 #include "chrome/common/extensions/manifest.h"
     25 #include "content/public/browser/browser_thread.h"
     26 #include "ui/gfx/codec/png_codec.h"
     27 
     28 using content::BrowserThread;
     29 
     30 namespace chromeos {
     31 
     32 namespace {
     33 
     34 // Keys for local state data. See sample layout in KioskAppManager.
     35 const char kKeyName[] = "name";
     36 const char kKeyIcon[] = "icon";
     37 
     38 // Web store data keys.
     39 const char kManifestKey[] = "manifest";
     40 const char kIconUrlKey[] = "icon_url";
     41 const char kLocalizedNameKey[] = "localized_name";
     42 
     43 const char kInvalidWebstoreResponseError[] = "Invalid Chrome Web Store reponse";
     44 
     45 // Icon file extension.
     46 const char kIconFileExtension[] = ".png";
     47 
     48 // Save |raw_icon| for given |app_id|.
     49 void SaveIconToLocalOnBlockingPool(
     50     const base::FilePath& icon_path,
     51     scoped_refptr<base::RefCountedString> raw_icon) {
     52   DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
     53 
     54   base::FilePath dir = icon_path.DirName();
     55   if (!base::PathExists(dir))
     56     CHECK(file_util::CreateDirectory(dir));
     57 
     58   CHECK_EQ(static_cast<int>(raw_icon->size()),
     59            file_util::WriteFile(icon_path,
     60                                 raw_icon->data().c_str(), raw_icon->size()));
     61 }
     62 
     63 // Returns true for valid kiosk app manifest.
     64 bool IsValidKioskAppManifest(const extensions::Manifest& manifest) {
     65   bool kiosk_enabled;
     66   if (manifest.GetBoolean(extension_manifest_keys::kKioskEnabled,
     67                           &kiosk_enabled)) {
     68     return kiosk_enabled;
     69   }
     70 
     71   return false;
     72 }
     73 
     74 }  // namespace
     75 
     76 ////////////////////////////////////////////////////////////////////////////////
     77 // KioskAppData::IconLoader
     78 // Loads locally stored icon data and decode it.
     79 
     80 class KioskAppData::IconLoader : public ImageDecoder::Delegate {
     81  public:
     82   enum LoadResult {
     83     SUCCESS,
     84     FAILED_TO_LOAD,
     85     FAILED_TO_DECODE,
     86   };
     87 
     88   IconLoader(const base::WeakPtr<KioskAppData>& client,
     89              const base::FilePath& icon_path)
     90       : client_(client),
     91         icon_path_(icon_path),
     92         load_result_(SUCCESS) {}
     93 
     94   void Start() {
     95     base::SequencedWorkerPool* pool = BrowserThread::GetBlockingPool();
     96     base::SequencedWorkerPool::SequenceToken token = pool->GetSequenceToken();
     97     task_runner_ = pool->GetSequencedTaskRunnerWithShutdownBehavior(
     98         token,
     99         base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
    100     task_runner_->PostTask(FROM_HERE,
    101                            base::Bind(&IconLoader::LoadOnBlockingPool,
    102                                       base::Unretained(this)));
    103   }
    104 
    105  private:
    106   friend class base::RefCountedThreadSafe<IconLoader>;
    107 
    108   virtual ~IconLoader() {}
    109 
    110   // Loads the icon from locally stored |icon_path_| on the blocking pool
    111   void LoadOnBlockingPool() {
    112     DCHECK(task_runner_->RunsTasksOnCurrentThread());
    113 
    114     std::string data;
    115     if (!file_util::ReadFileToString(base::FilePath(icon_path_), &data)) {
    116       ReportResultOnBlockingPool(FAILED_TO_LOAD);
    117       return;
    118     }
    119     raw_icon_ = base::RefCountedString::TakeString(&data);
    120 
    121     scoped_refptr<ImageDecoder> image_decoder = new ImageDecoder(
    122         this, raw_icon_->data(), ImageDecoder::DEFAULT_CODEC);
    123     image_decoder->Start(task_runner_);
    124   }
    125 
    126   void ReportResultOnBlockingPool(LoadResult result) {
    127     DCHECK(task_runner_->RunsTasksOnCurrentThread());
    128 
    129     load_result_ = result;
    130     BrowserThread::PostTask(
    131         BrowserThread::UI,
    132         FROM_HERE,
    133         base::Bind(&IconLoader::ReportResultOnUIThread,
    134                    base::Unretained(this)));
    135   }
    136 
    137   void NotifyClient() {
    138     if (!client_)
    139       return;
    140 
    141     if (load_result_ == SUCCESS)
    142       client_->OnIconLoadSuccess(raw_icon_, icon_);
    143     else
    144       client_->OnIconLoadFailure();
    145   }
    146 
    147   void ReportResultOnUIThread() {
    148     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    149 
    150     NotifyClient();
    151     delete this;
    152   }
    153 
    154   // ImageDecoder::Delegate overrides:
    155   virtual void OnImageDecoded(const ImageDecoder* decoder,
    156                               const SkBitmap& decoded_image) OVERRIDE {
    157     icon_ = gfx::ImageSkia::CreateFrom1xBitmap(decoded_image);
    158     icon_.MakeThreadSafe();
    159     ReportResultOnBlockingPool(SUCCESS);
    160   }
    161 
    162   virtual void OnDecodeImageFailed(const ImageDecoder* decoder) OVERRIDE {
    163     ReportResultOnBlockingPool(FAILED_TO_DECODE);
    164   }
    165 
    166   base::WeakPtr<KioskAppData> client_;
    167   base::FilePath icon_path_;
    168 
    169   LoadResult load_result_;
    170   scoped_refptr<base::SequencedTaskRunner> task_runner_;
    171 
    172   gfx::ImageSkia icon_;
    173   scoped_refptr<base::RefCountedString> raw_icon_;
    174 
    175   DISALLOW_COPY_AND_ASSIGN(IconLoader);
    176 };
    177 
    178 ////////////////////////////////////////////////////////////////////////////////
    179 // KioskAppData::WebstoreDataParser
    180 // Use WebstoreInstallHelper to parse the manifest and decode the icon.
    181 
    182 class KioskAppData::WebstoreDataParser
    183     : public extensions::WebstoreInstallHelper::Delegate {
    184  public:
    185   explicit WebstoreDataParser(const base::WeakPtr<KioskAppData>& client)
    186       : client_(client) {}
    187 
    188   void Start(const std::string& app_id,
    189              const std::string& manifest,
    190              const GURL& icon_url,
    191              net::URLRequestContextGetter* context_getter) {
    192     scoped_refptr<extensions::WebstoreInstallHelper> webstore_helper =
    193         new extensions::WebstoreInstallHelper(this,
    194                                               app_id,
    195                                               manifest,
    196                                               "",  // No icon data.
    197                                               icon_url,
    198                                               context_getter);
    199     webstore_helper->Start();
    200   }
    201 
    202  private:
    203   friend class base::RefCounted<WebstoreDataParser>;
    204 
    205   virtual ~WebstoreDataParser() {}
    206 
    207   void ReportFailure() {
    208     if (client_)
    209       client_->OnWebstoreParseFailure();
    210 
    211     delete this;
    212   }
    213 
    214   // WebstoreInstallHelper::Delegate overrides:
    215   virtual void OnWebstoreParseSuccess(
    216       const std::string& id,
    217       const SkBitmap& icon,
    218       base::DictionaryValue* parsed_manifest) OVERRIDE {
    219     // Takes ownership of |parsed_manifest|.
    220     extensions::Manifest manifest(
    221         extensions::Manifest::INVALID_LOCATION,
    222         scoped_ptr<base::DictionaryValue>(parsed_manifest));
    223 
    224     if (!IsValidKioskAppManifest(manifest)) {
    225       ReportFailure();
    226       return;
    227     }
    228 
    229     if (client_)
    230       client_->OnWebstoreParseSuccess(icon);
    231     delete this;
    232   }
    233   virtual void OnWebstoreParseFailure(
    234       const std::string& id,
    235       InstallHelperResultCode result_code,
    236       const std::string& error_message) OVERRIDE {
    237     ReportFailure();
    238   }
    239 
    240   base::WeakPtr<KioskAppData> client_;
    241 
    242   DISALLOW_COPY_AND_ASSIGN(WebstoreDataParser);
    243 };
    244 
    245 ////////////////////////////////////////////////////////////////////////////////
    246 // KioskAppData
    247 
    248 KioskAppData::KioskAppData(KioskAppDataDelegate* delegate,
    249                            const std::string& app_id,
    250                            const std::string& user_id)
    251     : delegate_(delegate),
    252       status_(STATUS_INIT),
    253       app_id_(app_id),
    254       user_id_(user_id) {
    255 }
    256 
    257 KioskAppData::~KioskAppData() {}
    258 
    259 void KioskAppData::Load() {
    260   SetStatus(STATUS_LOADING);
    261 
    262   if (LoadFromCache())
    263     return;
    264 
    265   StartFetch();
    266 }
    267 
    268 void KioskAppData::ClearCache() {
    269   PrefService* local_state = g_browser_process->local_state();
    270 
    271   DictionaryPrefUpdate dict_update(local_state,
    272                                    KioskAppManager::kKioskDictionaryName);
    273 
    274   std::string app_key = std::string(KioskAppManager::kKeyApps) + '.' + app_id_;
    275   dict_update->Remove(app_key, NULL);
    276 
    277   if (!icon_path_.empty()) {
    278     BrowserThread::PostBlockingPoolTask(
    279         FROM_HERE,
    280         base::Bind(base::IgnoreResult(&base::DeleteFile), icon_path_, false));
    281   }
    282 }
    283 
    284 bool KioskAppData::IsLoading() const {
    285   return status_ == STATUS_LOADING;
    286 }
    287 
    288 void KioskAppData::SetStatus(Status status) {
    289   if (status_ == status)
    290     return;
    291 
    292   status_ = status;
    293 
    294   if (!delegate_)
    295     return;
    296 
    297   switch (status_) {
    298     case STATUS_INIT:
    299       break;
    300     case STATUS_LOADING:
    301     case STATUS_LOADED:
    302       delegate_->OnKioskAppDataChanged(app_id_);
    303       break;
    304     case STATUS_ERROR:
    305       delegate_->OnKioskAppDataLoadFailure(app_id_);
    306       break;
    307   };
    308 }
    309 
    310 net::URLRequestContextGetter* KioskAppData::GetRequestContextGetter() {
    311   return g_browser_process->system_request_context();
    312 }
    313 
    314 bool KioskAppData::LoadFromCache() {
    315   std::string app_key = std::string(KioskAppManager::kKeyApps) + '.' + app_id_;
    316   std::string name_key = app_key + '.' + kKeyName;
    317   std::string icon_path_key = app_key + '.' + kKeyIcon;
    318 
    319   PrefService* local_state = g_browser_process->local_state();
    320   const base::DictionaryValue* dict =
    321       local_state->GetDictionary(KioskAppManager::kKioskDictionaryName);
    322 
    323   icon_path_.clear();
    324   std::string icon_path_string;
    325   if (!dict->GetString(name_key, &name_) ||
    326       !dict->GetString(icon_path_key, &icon_path_string)) {
    327     return false;
    328   }
    329   icon_path_ = base::FilePath(icon_path_string);
    330 
    331   // IconLoader deletes itself when done.
    332   (new IconLoader(AsWeakPtr(), icon_path_))->Start();
    333   return true;
    334 }
    335 
    336 void KioskAppData::SetCache(const std::string& name,
    337                             const base::FilePath& icon_path) {
    338   std::string app_key = std::string(KioskAppManager::kKeyApps) + '.' + app_id_;
    339   std::string name_key = app_key + '.' + kKeyName;
    340   std::string icon_path_key = app_key + '.' + kKeyIcon;
    341 
    342   PrefService* local_state = g_browser_process->local_state();
    343   DictionaryPrefUpdate dict_update(local_state,
    344                                    KioskAppManager::kKioskDictionaryName);
    345   dict_update->SetString(name_key, name);
    346   dict_update->SetString(icon_path_key, icon_path.value());
    347   icon_path_ = icon_path;
    348 }
    349 
    350 void KioskAppData::OnIconLoadSuccess(
    351     const scoped_refptr<base::RefCountedString>& raw_icon,
    352     const gfx::ImageSkia& icon) {
    353   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    354   raw_icon_ = raw_icon;
    355   icon_ = icon;
    356   SetStatus(STATUS_LOADED);
    357 }
    358 
    359 void KioskAppData::OnIconLoadFailure() {
    360   // Re-fetch data from web store when failed to load cached data.
    361   StartFetch();
    362 }
    363 
    364 void KioskAppData::OnWebstoreParseSuccess(const SkBitmap& icon) {
    365   icon_ = gfx::ImageSkia::CreateFrom1xBitmap(icon);
    366   icon_.MakeThreadSafe();
    367 
    368   std::vector<unsigned char> image_data;
    369   CHECK(gfx::PNGCodec::EncodeBGRASkBitmap(icon, false, &image_data));
    370   raw_icon_ = new base::RefCountedString;
    371   raw_icon_->data().assign(image_data.begin(), image_data.end());
    372 
    373   base::FilePath cache_dir;
    374   if (delegate_)
    375     delegate_->GetKioskAppIconCacheDir(&cache_dir);
    376 
    377   base::FilePath icon_path =
    378       cache_dir.AppendASCII(app_id_).AddExtension(kIconFileExtension);
    379   BrowserThread::GetBlockingPool()->PostTask(
    380       FROM_HERE,
    381       base::Bind(&SaveIconToLocalOnBlockingPool, icon_path, raw_icon_));
    382 
    383   SetCache(name_, icon_path);
    384   SetStatus(STATUS_LOADED);
    385 }
    386 
    387 void KioskAppData::OnWebstoreParseFailure() {
    388   SetStatus(STATUS_ERROR);
    389 }
    390 
    391 void KioskAppData::StartFetch() {
    392   webstore_fetcher_.reset(new extensions::WebstoreDataFetcher(
    393       this,
    394       GetRequestContextGetter(),
    395       GURL(),
    396       app_id_));
    397   webstore_fetcher_->Start();
    398 }
    399 
    400 void KioskAppData::OnWebstoreRequestFailure() {
    401   SetStatus(STATUS_ERROR);
    402 }
    403 
    404 void KioskAppData::OnWebstoreResponseParseSuccess(
    405       base::DictionaryValue* webstore_data) {
    406   // Takes ownership of |webstore_data|.
    407   scoped_ptr<base::DictionaryValue> data(webstore_data);
    408 
    409   webstore_fetcher_.reset();
    410 
    411   std::string manifest;
    412   if (!webstore_data->GetString(kManifestKey, &manifest)) {
    413     OnWebstoreResponseParseFailure(kInvalidWebstoreResponseError);
    414     return;
    415   }
    416 
    417   if (!webstore_data->GetString(kLocalizedNameKey, &name_)) {
    418     OnWebstoreResponseParseFailure(kInvalidWebstoreResponseError);
    419     return;
    420   }
    421 
    422   std::string icon_url_string;
    423   if (!webstore_data->GetString(kIconUrlKey, &icon_url_string)) {
    424     OnWebstoreResponseParseFailure(kInvalidWebstoreResponseError);
    425     return;
    426   }
    427   GURL icon_url = GURL(extension_urls::GetWebstoreLaunchURL()).Resolve(
    428       icon_url_string);
    429   if (!icon_url.is_valid()) {
    430     OnWebstoreResponseParseFailure(kInvalidWebstoreResponseError);
    431     return;
    432   }
    433 
    434   // WebstoreDataParser deletes itself when done.
    435   (new WebstoreDataParser(AsWeakPtr()))->Start(app_id_,
    436                                                manifest,
    437                                                icon_url,
    438                                                GetRequestContextGetter());
    439 }
    440 
    441 void KioskAppData::OnWebstoreResponseParseFailure(const std::string& error) {
    442   webstore_fetcher_.reset();
    443   SetStatus(STATUS_ERROR);
    444 }
    445 
    446 }  // namespace chromeos
    447