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