• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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|.
SaveIconToLocalOnBlockingPool(const base::FilePath & icon_path,scoped_refptr<base::RefCountedString> raw_icon)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.
IsValidKioskAppManifest(const extensions::Manifest & 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 
ValueToString(const base::Value * value)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 
IconLoader(const base::WeakPtr<KioskAppData> & client,const base::FilePath & icon_path)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 
Start()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 
~IconLoader()118   virtual ~IconLoader() {}
119 
120   // Loads the icon from locally stored |icon_path_| on the blocking pool
LoadOnBlockingPool()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 
ReportResultOnBlockingPool(LoadResult result)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 
NotifyClient()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 
ReportResultOnUIThread()157   void ReportResultOnUIThread() {
158     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
159 
160     NotifyClient();
161     delete this;
162   }
163 
164   // ImageDecoder::Delegate overrides:
OnImageDecoded(const ImageDecoder * decoder,const SkBitmap & decoded_image)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 
OnDecodeImageFailed(const ImageDecoder * decoder)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:
WebstoreDataParser(const base::WeakPtr<KioskAppData> & client)195   explicit WebstoreDataParser(const base::WeakPtr<KioskAppData>& client)
196       : client_(client) {}
197 
Start(const std::string & app_id,const std::string & manifest,const GURL & icon_url,net::URLRequestContextGetter * context_getter)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 
~WebstoreDataParser()215   virtual ~WebstoreDataParser() {}
216 
ReportFailure()217   void ReportFailure() {
218     if (client_)
219       client_->OnWebstoreParseFailure();
220 
221     delete this;
222   }
223 
224   // WebstoreInstallHelper::Delegate overrides:
OnWebstoreParseSuccess(const std::string & id,const SkBitmap & icon,base::DictionaryValue * parsed_manifest)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   }
OnWebstoreParseFailure(const std::string & id,InstallHelperResultCode result_code,const std::string & error_message)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 
KioskAppData(KioskAppDataDelegate * delegate,const std::string & app_id,const std::string & user_id)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 
~KioskAppData()267 KioskAppData::~KioskAppData() {}
268 
Load()269 void KioskAppData::Load() {
270   SetStatus(STATUS_LOADING);
271 
272   if (LoadFromCache())
273     return;
274 
275   StartFetch();
276 }
277 
ClearCache()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 
LoadFromInstalledApp(Profile * profile,const extensions::Extension * app)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 
IsLoading() const316 bool KioskAppData::IsLoading() const {
317   return status_ == STATUS_LOADING;
318 }
319 
SetStatus(Status status)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 
GetRequestContextGetter()342 net::URLRequestContextGetter* KioskAppData::GetRequestContextGetter() {
343   return g_browser_process->system_request_context();
344 }
345 
LoadFromCache()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 
SetCache(const std::string & name,const base::FilePath & icon_path)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 
SetCache(const std::string & name,const SkBitmap & icon)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 
OnExtensionIconLoaded(const gfx::Image & icon)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 
OnIconLoadSuccess(const scoped_refptr<base::RefCountedString> & raw_icon,const gfx::ImageSkia & icon)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 
OnIconLoadFailure()425 void KioskAppData::OnIconLoadFailure() {
426   // Re-fetch data from web store when failed to load cached data.
427   StartFetch();
428 }
429 
OnWebstoreParseSuccess(const SkBitmap & icon)430 void KioskAppData::OnWebstoreParseSuccess(const SkBitmap& icon) {
431   SetCache(name_, icon);
432   SetStatus(STATUS_LOADED);
433 }
434 
OnWebstoreParseFailure()435 void KioskAppData::OnWebstoreParseFailure() {
436   SetStatus(STATUS_ERROR);
437 }
438 
StartFetch()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 
OnWebstoreRequestFailure()449 void KioskAppData::OnWebstoreRequestFailure() {
450   SetStatus(STATUS_ERROR);
451 }
452 
OnWebstoreResponseParseSuccess(scoped_ptr<base::DictionaryValue> webstore_data)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 
OnWebstoreResponseParseFailure(const std::string & error)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 
CheckResponseKeyValue(const base::DictionaryValue * response,const char * key,std::string * value)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