Upstream version 9.37.197.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / chromeos / app_mode / kiosk_app_data.cc
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