Update To 11.40.268.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/files/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 "content/public/browser/browser_thread.h"
27 #include "extensions/browser/extension_system.h"
28 #include "extensions/browser/image_loader.h"
29 #include "extensions/common/constants.h"
30 #include "extensions/common/extension_urls.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                            const GURL& update_url)
262     : delegate_(delegate),
263       status_(STATUS_INIT),
264       app_id_(app_id),
265       user_id_(user_id),
266       update_url_(update_url) {
267 }
268
269 KioskAppData::~KioskAppData() {}
270
271 void KioskAppData::Load() {
272   SetStatus(STATUS_LOADING);
273
274   if (LoadFromCache())
275     return;
276
277   StartFetch();
278 }
279
280 void KioskAppData::ClearCache() {
281   PrefService* local_state = g_browser_process->local_state();
282
283   DictionaryPrefUpdate dict_update(local_state,
284                                    KioskAppManager::kKioskDictionaryName);
285
286   std::string app_key = std::string(KioskAppManager::kKeyApps) + '.' + app_id_;
287   dict_update->Remove(app_key, NULL);
288
289   if (!icon_path_.empty()) {
290     BrowserThread::PostBlockingPoolTask(
291         FROM_HERE,
292         base::Bind(base::IgnoreResult(&base::DeleteFile), icon_path_, false));
293   }
294 }
295
296 void KioskAppData::LoadFromInstalledApp(Profile* profile,
297                                         const extensions::Extension* app) {
298   SetStatus(STATUS_LOADING);
299
300   if (!app) {
301     app = extensions::ExtensionSystem::Get(profile)
302               ->extension_service()
303               ->GetInstalledExtension(app_id_);
304   }
305
306   DCHECK_EQ(app_id_, app->id());
307
308   name_ = app->name();
309
310   const int kIconSize = extension_misc::EXTENSION_ICON_LARGE;
311   extensions::ExtensionResource image = extensions::IconsInfo::GetIconResource(
312       app, kIconSize, ExtensionIconSet::MATCH_BIGGER);
313   extensions::ImageLoader::Get(profile)->LoadImageAsync(
314       app, image, gfx::Size(kIconSize, kIconSize),
315       base::Bind(&KioskAppData::OnExtensionIconLoaded, AsWeakPtr()));
316 }
317
318 bool KioskAppData::IsLoading() const {
319   return status_ == STATUS_LOADING;
320 }
321
322 void KioskAppData::SetStatus(Status status) {
323   if (status_ == status)
324     return;
325
326   status_ = status;
327
328   if (!delegate_)
329     return;
330
331   switch (status_) {
332     case STATUS_INIT:
333       break;
334     case STATUS_LOADING:
335     case STATUS_LOADED:
336       delegate_->OnKioskAppDataChanged(app_id_);
337       break;
338     case STATUS_ERROR:
339       delegate_->OnKioskAppDataLoadFailure(app_id_);
340       break;
341   }
342 }
343
344 net::URLRequestContextGetter* KioskAppData::GetRequestContextGetter() {
345   return g_browser_process->system_request_context();
346 }
347
348 bool KioskAppData::LoadFromCache() {
349   std::string app_key = std::string(KioskAppManager::kKeyApps) + '.' + app_id_;
350   std::string name_key = app_key + '.' + kKeyName;
351   std::string icon_path_key = app_key + '.' + kKeyIcon;
352
353   PrefService* local_state = g_browser_process->local_state();
354   const base::DictionaryValue* dict =
355       local_state->GetDictionary(KioskAppManager::kKioskDictionaryName);
356
357   icon_path_.clear();
358   std::string icon_path_string;
359   if (!dict->GetString(name_key, &name_) ||
360       !dict->GetString(icon_path_key, &icon_path_string)) {
361     return false;
362   }
363   icon_path_ = base::FilePath(icon_path_string);
364
365   // IconLoader deletes itself when done.
366   (new IconLoader(AsWeakPtr(), icon_path_))->Start();
367   return true;
368 }
369
370 void KioskAppData::SetCache(const std::string& name,
371                             const base::FilePath& icon_path) {
372   std::string app_key = std::string(KioskAppManager::kKeyApps) + '.' + app_id_;
373   std::string name_key = app_key + '.' + kKeyName;
374   std::string icon_path_key = app_key + '.' + kKeyIcon;
375
376   PrefService* local_state = g_browser_process->local_state();
377   DictionaryPrefUpdate dict_update(local_state,
378                                    KioskAppManager::kKioskDictionaryName);
379   dict_update->SetString(name_key, name);
380   dict_update->SetString(icon_path_key, icon_path.value());
381   icon_path_ = icon_path;
382 }
383
384 void KioskAppData::SetCache(const std::string& name, const SkBitmap& icon) {
385   icon_ = gfx::ImageSkia::CreateFrom1xBitmap(icon);
386   icon_.MakeThreadSafe();
387
388   std::vector<unsigned char> image_data;
389   CHECK(gfx::PNGCodec::EncodeBGRASkBitmap(icon, false, &image_data));
390   raw_icon_ = new base::RefCountedString;
391   raw_icon_->data().assign(image_data.begin(), image_data.end());
392
393   base::FilePath cache_dir;
394   if (delegate_)
395     delegate_->GetKioskAppIconCacheDir(&cache_dir);
396
397   base::FilePath icon_path =
398       cache_dir.AppendASCII(app_id_).AddExtension(kIconFileExtension);
399   BrowserThread::GetBlockingPool()->PostTask(
400       FROM_HERE,
401       base::Bind(&SaveIconToLocalOnBlockingPool, icon_path, raw_icon_));
402
403   SetCache(name, icon_path);
404 }
405
406 void KioskAppData::OnExtensionIconLoaded(const gfx::Image& icon) {
407   if (icon.IsEmpty()) {
408     LOG(WARNING) << "Failed to load icon from installed app"
409                  << ", id=" << app_id_;
410     SetCache(name_, *extensions::util::GetDefaultAppIcon().bitmap());
411   } else {
412     SetCache(name_, icon.AsBitmap());
413   }
414
415   SetStatus(STATUS_LOADED);
416 }
417
418 void KioskAppData::OnIconLoadSuccess(
419     const scoped_refptr<base::RefCountedString>& raw_icon,
420     const gfx::ImageSkia& icon) {
421   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
422   raw_icon_ = raw_icon;
423   icon_ = icon;
424   SetStatus(STATUS_LOADED);
425 }
426
427 void KioskAppData::OnIconLoadFailure() {
428   // Re-fetch data from web store when failed to load cached data.
429   StartFetch();
430 }
431
432 void KioskAppData::OnWebstoreParseSuccess(const SkBitmap& icon) {
433   SetCache(name_, icon);
434   SetStatus(STATUS_LOADED);
435 }
436
437 void KioskAppData::OnWebstoreParseFailure() {
438   SetStatus(STATUS_ERROR);
439 }
440
441 void KioskAppData::StartFetch() {
442   webstore_fetcher_.reset(new extensions::WebstoreDataFetcher(
443       this,
444       GetRequestContextGetter(),
445       GURL(),
446       app_id_));
447   webstore_fetcher_->set_max_auto_retries(3);
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