kiosk_app_data.cc revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
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
28using content::BrowserThread;
29
30namespace chromeos {
31
32namespace {
33
34// Keys for local state data. See sample layout in KioskAppManager.
35const char kKeyName[] = "name";
36const char kKeyIcon[] = "icon";
37
38// Web store data keys.
39const char kManifestKey[] = "manifest";
40const char kIconUrlKey[] = "icon_url";
41const char kLocalizedNameKey[] = "localized_name";
42
43const char kInvalidWebstoreResponseError[] = "Invalid Chrome Web Store reponse";
44
45// Icon file extension.
46const char kIconFileExtension[] = ".png";
47
48// Save |raw_icon| for given |app_id|.
49void 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 (!file_util::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.
64bool 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
80class 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
182class 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
248KioskAppData::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
257KioskAppData::~KioskAppData() {}
258
259void KioskAppData::Load() {
260  SetStatus(STATUS_LOADING);
261
262  if (LoadFromCache())
263    return;
264
265  StartFetch();
266}
267
268void 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::Delete), icon_path_, false));
281  }
282}
283
284bool KioskAppData::IsLoading() const {
285  return status_ == STATUS_LOADING;
286}
287
288void 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
310net::URLRequestContextGetter* KioskAppData::GetRequestContextGetter() {
311  return g_browser_process->system_request_context();
312}
313
314bool 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
336void 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
350void 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
359void KioskAppData::OnIconLoadFailure() {
360  // Re-fetch data from web store when failed to load cached data.
361  StartFetch();
362}
363
364void 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
387void KioskAppData::OnWebstoreParseFailure() {
388  SetStatus(STATUS_ERROR);
389}
390
391void KioskAppData::StartFetch() {
392  webstore_fetcher_.reset(new extensions::WebstoreDataFetcher(
393      this,
394      GetRequestContextGetter(),
395      GURL(),
396      app_id_));
397  webstore_fetcher_->Start();
398}
399
400void KioskAppData::OnWebstoreRequestFailure() {
401  SetStatus(STATUS_ERROR);
402}
403
404void 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
441void KioskAppData::OnWebstoreResponseParseFailure(const std::string& error) {
442  webstore_fetcher_.reset();
443  SetStatus(STATUS_ERROR);
444}
445
446}  // namespace chromeos
447