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