1 // Copyright (c) 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.
6 * WallpaperManager constructor.
8 * WallpaperManager objects encapsulate the functionality of the wallpaper
12 * @param {HTMLElement} dialogDom The DOM node containing the prototypical
16 function WallpaperManager(dialogDom) {
17 this.dialogDom_ = dialogDom;
18 this.document_ = dialogDom.ownerDocument;
19 this.enableOnlineWallpaper_ = loadTimeData.valueExists('manifestBaseURL');
20 this.selectedCategory = null;
21 this.selectedItem_ = null;
22 this.progressManager_ = new ProgressManager();
23 this.customWallpaperData_ = null;
24 this.currentWallpaper_ = null;
25 this.wallpaperRequest_ = null;
26 this.wallpaperDirs_ = WallpaperDirectories.getInstance();
27 this.preManifestDomInit_();
28 this.fetchManifest_();
31 // Anonymous 'namespace'.
32 // TODO(bshe): Get rid of anonymous namespace.
36 * URL of the learn more page for wallpaper picker.
38 /** @const */ var LearnMoreURL =
39 'https://support.google.com/chromeos/?p=wallpaper_fileerror&hl=' +
43 * Index of the All category. It is the first category in wallpaper picker.
45 /** @const */ var AllCategoryIndex = 0;
48 * Index offset of categories parsed from manifest. The All category is added
49 * before them. So the offset is 1.
51 /** @const */ var OnlineCategoriesOffset = 1;
54 * Returns a translated string.
56 * Wrapper function to make dealing with translated strings more concise.
57 * Equivilant to localStrings.getString(id).
59 * @param {string} id The id of the string to return.
60 * @return {string} The translated string.
63 return loadTimeData.getString(id);
67 * Retruns the current selected layout.
68 * @return {string} The selected layout.
70 function getSelectedLayout() {
71 var setWallpaperLayout = $('set-wallpaper-layout');
72 return setWallpaperLayout.options[setWallpaperLayout.selectedIndex].value;
76 * Loads translated strings.
78 WallpaperManager.initStrings = function(callback) {
79 chrome.wallpaperPrivate.getStrings(function(strings) {
80 loadTimeData.data = strings;
87 * Requests wallpaper manifest file from server.
89 WallpaperManager.prototype.fetchManifest_ = function() {
90 var locale = navigator.language;
91 if (!this.enableOnlineWallpaper_) {
92 this.postManifestDomInit_();
97 str('manifestBaseURL') + locale + '.json',
98 // Fallback url. Use 'en' locale by default.
99 str('manifestBaseURL') + 'en.json'];
101 var asyncFetchManifestFromUrls = function(urls, func, successCallback,
106 if (index < urls.length) {
107 func(loop, urls[index]);
114 success: function(response) {
115 successCallback(response);
118 failure: function() {
125 var fetchManifestAsync = function(loop, url) {
126 var xhr = new XMLHttpRequest();
128 xhr.addEventListener('loadend', function(e) {
129 if (this.status == 200 && this.responseText != null) {
131 var manifest = JSON.parse(this.responseText);
132 loop.success(manifest);
140 xhr.open('GET', url, true);
147 if (navigator.onLine) {
148 asyncFetchManifestFromUrls(urls, fetchManifestAsync,
149 this.onLoadManifestSuccess_.bind(this),
150 this.onLoadManifestFailed_.bind(this));
152 // If device is offline, fetches manifest from local storage.
153 // TODO(bshe): Always loading the offline manifest first and replacing
154 // with the online one when available.
155 this.onLoadManifestFailed_();
160 * Shows error message in a centered dialog.
162 * @param {string} errroMessage The string to show in the error dialog.
164 WallpaperManager.prototype.showError_ = function(errorMessage) {
165 document.querySelector('.error-message').textContent = errorMessage;
166 $('error-container').hidden = false;
170 * Sets manifest loaded from server. Called after manifest is successfully
172 * @param {object} manifest The parsed manifest file.
174 WallpaperManager.prototype.onLoadManifestSuccess_ = function(manifest) {
175 this.manifest_ = manifest;
176 WallpaperUtil.saveToStorage(Constants.AccessManifestKey, manifest, false);
177 this.postManifestDomInit_();
180 // Sets manifest to previously saved object if any and shows connection error.
181 // Called after manifest failed to load.
182 WallpaperManager.prototype.onLoadManifestFailed_ = function() {
183 var accessManifestKey = Constants.AccessManifestKey;
185 Constants.WallpaperLocalStorage.get(accessManifestKey, function(items) {
186 self.manifest_ = items[accessManifestKey] ? items[accessManifestKey] : {};
187 self.showError_(str('connectionFailed'));
188 self.postManifestDomInit_();
189 $('wallpaper-grid').classList.add('image-picker-offline');
194 * Toggle surprise me feature of wallpaper picker. It fires an storage
195 * onChanged event. Event handler for that event is in event_page.js.
198 WallpaperManager.prototype.toggleSurpriseMe_ = function() {
199 var checkbox = $('surprise-me').querySelector('#checkbox');
200 var shouldEnable = !checkbox.classList.contains('checked');
201 WallpaperUtil.saveToStorage(Constants.AccessSurpriseMeEnabledKey,
202 shouldEnable, false, function() {
203 if (chrome.runtime.lastError == null) {
205 checkbox.classList.add('checked');
207 checkbox.classList.remove('checked');
209 $('categories-list').disabled = shouldEnable;
210 $('wallpaper-grid').disabled = shouldEnable;
212 // TODO(bshe): show error message to user.
213 console.error('Failed to save surprise me option to chrome storage.');
219 * One-time initialization of various DOM nodes. Fetching manifest may take a
220 * long time due to slow connection. Dom nodes that do not depend on manifest
221 * should be initialized here to unblock from manifest fetching.
223 WallpaperManager.prototype.preManifestDomInit_ = function() {
224 $('window-close-button').addEventListener('click', function() {
227 this.document_.defaultView.addEventListener(
228 'resize', this.onResize_.bind(this));
229 this.document_.defaultView.addEventListener(
230 'keydown', this.onKeyDown_.bind(this));
231 $('learn-more').href = LearnMoreURL;
232 $('close-error').addEventListener('click', function() {
233 $('error-container').hidden = true;
235 $('close-wallpaper-selection').addEventListener('click', function() {
236 $('wallpaper-selection-container').hidden = true;
237 $('set-wallpaper-layout').disabled = true;
242 * One-time initialization of various DOM nodes. Dom nodes that do depend on
243 * manifest should be initialized here.
245 WallpaperManager.prototype.postManifestDomInit_ = function() {
246 i18nTemplate.process(this.document_, loadTimeData);
247 this.initCategoriesList_();
248 this.initThumbnailsGrid_();
249 this.presetCategory_();
251 $('file-selector').addEventListener(
252 'change', this.onFileSelectorChanged_.bind(this));
253 $('set-wallpaper-layout').addEventListener(
254 'change', this.onWallpaperLayoutChanged_.bind(this));
256 if (this.enableOnlineWallpaper_) {
258 $('surprise-me').hidden = false;
259 $('surprise-me').addEventListener('click',
260 this.toggleSurpriseMe_.bind(this));
261 Constants.WallpaperLocalStorage.get(Constants.AccessSurpriseMeEnabledKey,
263 if (items[Constants.AccessSurpriseMeEnabledKey]) {
264 $('surprise-me').querySelector('#checkbox').classList.add('checked');
265 $('categories-list').disabled = true;
266 $('wallpaper-grid').disabled = true;
270 window.addEventListener('offline', function() {
271 chrome.wallpaperPrivate.getOfflineWallpaperList(function(lists) {
272 if (!self.downloadedListMap_)
273 self.downloadedListMap_ = {};
274 for (var i = 0; i < lists.length; i++) {
275 self.downloadedListMap_[lists[i]] = true;
277 var thumbnails = self.document_.querySelectorAll('.thumbnail');
278 for (var i = 0; i < thumbnails.length; i++) {
279 var thumbnail = thumbnails[i];
280 var url = self.wallpaperGrid_.dataModel.item(i).baseURL;
281 var fileName = url.substring(url.lastIndexOf('/') + 1) +
282 Constants.HighResolutionSuffix;
283 if (self.downloadedListMap_ &&
284 self.downloadedListMap_.hasOwnProperty(encodeURI(fileName))) {
285 thumbnail.offline = true;
289 $('wallpaper-grid').classList.add('image-picker-offline');
291 window.addEventListener('online', function() {
292 self.downloadedListMap_ = null;
293 $('wallpaper-grid').classList.remove('image-picker-offline');
298 this.initContextMenuAndCommand_();
302 * One-time initialization of context menu and command.
304 WallpaperManager.prototype.initContextMenuAndCommand_ = function() {
305 this.wallpaperContextMenu_ = $('wallpaper-context-menu');
306 cr.ui.Menu.decorate(this.wallpaperContextMenu_);
307 cr.ui.contextMenuHandler.setContextMenu(this.wallpaperGrid_,
308 this.wallpaperContextMenu_);
309 var commands = this.dialogDom_.querySelectorAll('command');
310 for (var i = 0; i < commands.length; i++)
311 cr.ui.Command.decorate(commands[i]);
313 var doc = this.document_;
314 doc.addEventListener('command', this.onCommand_.bind(this));
315 doc.addEventListener('canExecute', this.onCommandCanExecute_.bind(this));
319 * Handles a command being executed.
320 * @param {Event} event A command event.
322 WallpaperManager.prototype.onCommand_ = function(event) {
323 if (event.command.id == 'delete') {
324 var wallpaperGrid = this.wallpaperGrid_;
325 var selectedIndex = wallpaperGrid.selectionModel.selectedIndex;
326 var item = wallpaperGrid.dataModel.item(selectedIndex);
327 if (!item || item.source != Constants.WallpaperSourceEnum.Custom)
329 this.removeCustomWallpaper(item.baseURL);
330 wallpaperGrid.dataModel.splice(selectedIndex, 1);
331 // Calculate the number of remaining custom wallpapers. The add new button
332 // in data model needs to be excluded.
333 var customWallpaperCount = wallpaperGrid.dataModel.length - 1;
334 if (customWallpaperCount == 0) {
335 // Active custom wallpaper is also copied in chronos data dir. It needs
337 chrome.wallpaperPrivate.resetWallpaper();
339 selectedIndex = Math.min(selectedIndex, customWallpaperCount - 1);
340 wallpaperGrid.selectionModel.selectedIndex = selectedIndex;
342 event.cancelBubble = true;
347 * Decides if a command can be executed on current target.
348 * @param {Event} event A command event.
350 WallpaperManager.prototype.onCommandCanExecute_ = function(event) {
351 switch (event.command.id) {
353 var wallpaperGrid = this.wallpaperGrid_;
354 var selectedIndex = wallpaperGrid.selectionModel.selectedIndex;
355 var item = wallpaperGrid.dataModel.item(selectedIndex);
356 if (selectedIndex != this.wallpaperGrid_.dataModel.length - 1 &&
357 item && item.source == Constants.WallpaperSourceEnum.Custom) {
358 event.canExecute = true;
362 event.canExecute = false;
367 * Preset to the category which contains current wallpaper.
369 WallpaperManager.prototype.presetCategory_ = function() {
370 this.currentWallpaper_ = str('currentWallpaper');
371 // The currentWallpaper_ is either a url contains HightResolutionSuffix or a
372 // custom wallpaper file name converted from an integer value represent
373 // time (e.g., 13006377367586070).
374 if (!this.enableOnlineWallpaper_ || (this.currentWallpaper_ &&
375 this.currentWallpaper_.indexOf(Constants.HighResolutionSuffix) == -1)) {
376 // Custom is the last one in the categories list.
377 this.categoriesList_.selectionModel.selectedIndex =
378 this.categoriesList_.dataModel.length - 1;
382 var presetCategoryInner_ = function() {
383 // Selects the first category in the categories list of current
384 // wallpaper as the default selected category when showing wallpaper
386 var presetCategory = AllCategoryIndex;
387 if (self.currentWallpaper_) {
388 for (var key in self.manifest_.wallpaper_list) {
389 var url = self.manifest_.wallpaper_list[key].base_url +
390 Constants.HighResolutionSuffix;
391 if (url.indexOf(self.currentWallpaper_) != -1 &&
392 self.manifest_.wallpaper_list[key].categories.length > 0) {
393 presetCategory = self.manifest_.wallpaper_list[key].categories[0] +
394 OnlineCategoriesOffset;
399 self.categoriesList_.selectionModel.selectedIndex = presetCategory;
401 if (navigator.onLine) {
402 presetCategoryInner_();
404 // If device is offline, gets the available offline wallpaper list first.
405 // Wallpapers which are not in the list will display a grayscaled
407 chrome.wallpaperPrivate.getOfflineWallpaperList(function(lists) {
408 if (!self.downloadedListMap_)
409 self.downloadedListMap_ = {};
410 for (var i = 0; i < lists.length; i++)
411 self.downloadedListMap_[lists[i]] = true;
412 presetCategoryInner_();
418 * Constructs the thumbnails grid.
420 WallpaperManager.prototype.initThumbnailsGrid_ = function() {
421 this.wallpaperGrid_ = $('wallpaper-grid');
422 wallpapers.WallpaperThumbnailsGrid.decorate(this.wallpaperGrid_);
423 this.wallpaperGrid_.autoExpands = true;
425 this.wallpaperGrid_.addEventListener('change', this.onChange_.bind(this));
426 this.wallpaperGrid_.addEventListener('dblclick', this.onClose_.bind(this));
430 * Handles change event dispatched by wallpaper grid.
432 WallpaperManager.prototype.onChange_ = function() {
433 // splice may dispatch a change event because the position of selected
434 // element changing. But the actual selected element may not change after
435 // splice. Check if the new selected element equals to the previous selected
436 // element before continuing. Otherwise, wallpaper may reset to previous one
437 // as described in http://crbug.com/229036.
438 if (this.selectedItem_ == this.wallpaperGrid_.selectedItem)
440 this.selectedItem_ = this.wallpaperGrid_.selectedItem;
441 this.onSelectedItemChanged_();
445 * Closes window if no pending wallpaper request.
447 WallpaperManager.prototype.onClose_ = function() {
448 if (this.wallpaperRequest_) {
449 this.wallpaperRequest_.addEventListener('loadend', function() {
450 // Close window on wallpaper loading finished.
459 * Sets wallpaper to the corresponding wallpaper of selected thumbnail.
460 * @param {{baseURL: string, layout: string, source: string,
461 * availableOffline: boolean, opt_dynamicURL: string,
462 * opt_author: string, opt_authorWebsite: string}}
463 * selectedItem the selected item in WallpaperThumbnailsGrid's data
466 WallpaperManager.prototype.setSelectedWallpaper_ = function(selectedItem) {
468 switch (selectedItem.source) {
469 case Constants.WallpaperSourceEnum.Custom:
470 var errorHandler = this.onFileSystemError_.bind(this);
471 var setActive = function() {
472 self.wallpaperGrid_.activeItem = selectedItem;
473 self.currentWallpaper_ = selectedItem.baseURL;
475 var success = function(dirEntry) {
476 dirEntry.getFile(selectedItem.baseURL, {create: false},
477 function(fileEntry) {
478 fileEntry.file(function(file) {
479 var reader = new FileReader();
480 reader.readAsArrayBuffer(file);
481 reader.addEventListener('error', errorHandler);
482 reader.addEventListener('load', function(e) {
483 self.setCustomWallpaper(e.target.result,
485 false, selectedItem.baseURL,
486 setActive, errorHandler);
491 this.wallpaperDirs_.getDirectory(WallpaperDirNameEnum.ORIGINAL,
492 success, errorHandler);
494 case Constants.WallpaperSourceEnum.OEM:
495 // Resets back to default wallpaper.
496 chrome.wallpaperPrivate.resetWallpaper();
497 this.currentWallpaper_ = selectedItem.baseURL;
498 this.wallpaperGrid_.activeItem = selectedItem;
499 WallpaperUtil.saveWallpaperInfo(wallpaperURL, selectedItem.layout,
500 selectedItem.source);
502 case Constants.WallpaperSourceEnum.Online:
503 var wallpaperURL = selectedItem.baseURL +
504 Constants.HighResolutionSuffix;
505 var selectedGridItem = this.wallpaperGrid_.getListItem(selectedItem);
507 chrome.wallpaperPrivate.setWallpaperIfExists(wallpaperURL,
511 self.currentWallpaper_ = wallpaperURL;
512 self.wallpaperGrid_.activeItem = selectedItem;
513 WallpaperUtil.saveWallpaperInfo(wallpaperURL, selectedItem.layout,
514 selectedItem.source);
518 // Falls back to request wallpaper from server.
519 if (self.wallpaperRequest_)
520 self.wallpaperRequest_.abort();
522 self.wallpaperRequest_ = new XMLHttpRequest();
523 self.progressManager_.reset(self.wallpaperRequest_, selectedGridItem);
525 var onSuccess = function(xhr) {
526 var image = xhr.response;
527 chrome.wallpaperPrivate.setWallpaper(image, selectedItem.layout,
529 self.onFinished_.bind(self, selectedGridItem, selectedItem));
530 self.currentWallpaper_ = wallpaperURL;
531 WallpaperUtil.saveWallpaperInfo(wallpaperURL, selectedItem.layout,
532 selectedItem.source);
533 self.wallpaperRequest_ = null;
535 var onFailure = function() {
536 self.progressManager_.hideProgressBar(selectedGridItem);
537 self.showError_(str('downloadFailed'));
538 self.wallpaperRequest_ = null;
540 WallpaperUtil.fetchURL(wallpaperURL, 'arraybuffer', onSuccess,
541 onFailure, self.wallpaperRequest_);
545 console.error('Unsupported wallpaper source.');
550 * Removes the oldest custom wallpaper. If the oldest one is set as current
551 * wallpaper, removes the second oldest one to free some space. This should
552 * only be called when exceeding wallpaper quota.
554 WallpaperManager.prototype.removeOldestWallpaper_ = function() {
555 // Custom wallpapers should already sorted when put to the data model. The
556 // last element is the add new button, need to exclude it as well.
557 var oldestIndex = this.wallpaperGrid_.dataModel.length - 2;
558 var item = this.wallpaperGrid_.dataModel.item(oldestIndex);
559 if (!item || item.source != Constants.WallpaperSourceEnum.Custom)
561 if (item.baseURL == this.currentWallpaper_)
562 item = this.wallpaperGrid_.dataModel.item(--oldestIndex);
564 this.removeCustomWallpaper(item.baseURL);
565 this.wallpaperGrid_.dataModel.splice(oldestIndex, 1);
570 * Shows an error message to user and log the failed reason in console.
572 WallpaperManager.prototype.onFileSystemError_ = function(e) {
575 case FileError.QUOTA_EXCEEDED_ERR:
576 msg = 'QUOTA_EXCEEDED_ERR';
577 // Instead of simply remove oldest wallpaper, we should consider a
578 // better way to handle this situation. See crbug.com/180890.
579 this.removeOldestWallpaper_();
581 case FileError.NOT_FOUND_ERR:
582 msg = 'NOT_FOUND_ERR';
584 case FileError.SECURITY_ERR:
585 msg = 'SECURITY_ERR';
587 case FileError.INVALID_MODIFICATION_ERR:
588 msg = 'INVALID_MODIFICATION_ERR';
590 case FileError.INVALID_STATE_ERR:
591 msg = 'INVALID_STATE_ERR';
594 msg = 'Unknown Error';
597 console.error('Error: ' + msg);
598 this.showError_(str('accessFileFailure'));
602 * Handles changing of selectedItem in wallpaper manager.
604 WallpaperManager.prototype.onSelectedItemChanged_ = function() {
605 this.setWallpaperAttribution_(this.selectedItem_);
607 if (!this.selectedItem_ || this.selectedItem_.source == 'ADDNEW')
610 if (this.selectedItem_.baseURL && !this.wallpaperGrid_.inProgramSelection) {
611 if (this.selectedItem_.source == Constants.WallpaperSourceEnum.Custom) {
613 var key = this.selectedItem_.baseURL;
615 Constants.WallpaperLocalStorage.get(key, function(items) {
616 self.selectedItem_.layout =
617 items[key] ? items[key] : 'CENTER_CROPPED';
618 self.setSelectedWallpaper_(self.selectedItem_);
621 this.setSelectedWallpaper_(this.selectedItem_);
627 * Set attributions of wallpaper with given URL. If URL is not valid, clear
629 * @param {{baseURL: string, dynamicURL: string, layout: string,
630 * author: string, authorWebsite: string, availableOffline: boolean}}
631 * selectedItem selected wallpaper item in grid.
634 WallpaperManager.prototype.setWallpaperAttribution_ = function(selectedItem) {
635 // Only online wallpapers have author and website attributes. All other type
636 // of wallpapers should not show attributions.
638 selectedItem.source == Constants.WallpaperSourceEnum.Online) {
639 $('author-name').textContent = selectedItem.author;
640 $('author-website').textContent = $('author-website').href =
641 selectedItem.authorWebsite;
642 chrome.wallpaperPrivate.getThumbnail(selectedItem.baseURL,
645 var img = $('attribute-image');
647 var blob = new Blob([new Int8Array(data)], {'type' : 'image\/png'});
648 img.src = window.URL.createObjectURL(blob);
649 img.addEventListener('load', function(e) {
650 window.URL.revokeObjectURL(this.src);
656 $('wallpaper-attribute').hidden = false;
657 $('attribute-image').hidden = false;
660 $('wallpaper-attribute').hidden = true;
661 $('attribute-image').hidden = true;
662 $('author-name').textContent = '';
663 $('author-website').textContent = $('author-website').href = '';
664 $('attribute-image').src = '';
668 * Resize thumbnails grid and categories list to fit the new window size.
670 WallpaperManager.prototype.onResize_ = function() {
671 this.wallpaperGrid_.redraw();
672 this.categoriesList_.redraw();
676 * Close the last opened overlay on pressing the Escape key.
677 * @param {Event} event A keydown event.
679 WallpaperManager.prototype.onKeyDown_ = function(event) {
680 if (event.keyCode == 27) {
681 // The last opened overlay coincides with the first match of querySelector
682 // because the Error Container is declared in the DOM before the Wallpaper
683 // Selection Container.
684 // TODO(bshe): Make the overlay selection not dependent on the DOM.
685 var closeButtonSelector = '.overlay-container:not([hidden]) .close';
686 var closeButton = this.document_.querySelector(closeButtonSelector);
689 event.preventDefault();
695 * Constructs the categories list.
697 WallpaperManager.prototype.initCategoriesList_ = function() {
698 this.categoriesList_ = $('categories-list');
699 cr.ui.List.decorate(this.categoriesList_);
700 // cr.ui.list calculates items in view port based on client height and item
701 // height. However, categories list is displayed horizontally. So we should
702 // not calculate visible items here. Sets autoExpands to true to show every
704 // TODO(bshe): Use ul to replace cr.ui.list for category list.
705 this.categoriesList_.autoExpands = true;
708 this.categoriesList_.itemConstructor = function(entry) {
709 return self.renderCategory_(entry);
712 this.categoriesList_.selectionModel = new cr.ui.ListSingleSelectionModel();
713 this.categoriesList_.selectionModel.addEventListener(
714 'change', this.onCategoriesChange_.bind(this));
716 var categoriesDataModel = new cr.ui.ArrayDataModel([]);
717 if (this.enableOnlineWallpaper_) {
718 // Adds all category as first category.
719 categoriesDataModel.push(str('allCategoryLabel'));
720 for (var key in this.manifest_.categories) {
721 categoriesDataModel.push(this.manifest_.categories[key]);
724 // Adds custom category as last category.
725 categoriesDataModel.push(str('customCategoryLabel'));
726 this.categoriesList_.dataModel = categoriesDataModel;
730 * Constructs the element in categories list.
731 * @param {string} entry Text content of a category.
733 WallpaperManager.prototype.renderCategory_ = function(entry) {
734 var li = this.document_.createElement('li');
735 cr.defineProperty(li, 'custom', cr.PropertyKind.BOOL_ATTR);
736 li.custom = (entry == str('customCategoryLabel'));
737 cr.defineProperty(li, 'lead', cr.PropertyKind.BOOL_ATTR);
738 cr.defineProperty(li, 'selected', cr.PropertyKind.BOOL_ATTR);
739 var div = this.document_.createElement('div');
740 div.textContent = entry;
746 * Handles the custom wallpaper which user selected from file manager. Called
747 * when users select a file.
749 WallpaperManager.prototype.onFileSelectorChanged_ = function() {
750 var files = $('file-selector').files;
751 if (files.length != 1)
752 console.error('More than one files are selected or no file selected');
753 if (!files[0].type.match('image/jpeg') &&
754 !files[0].type.match('image/png')) {
755 this.showError_(str('invalidWallpaper'));
758 var layout = getSelectedLayout();
760 var errorHandler = this.onFileSystemError_.bind(this);
761 var setSelectedFile = function(file, layout, fileName) {
762 var saveThumbnail = function(thumbnail) {
763 var success = function(dirEntry) {
764 dirEntry.getFile(fileName, {create: true}, function(fileEntry) {
765 fileEntry.createWriter(function(fileWriter) {
766 fileWriter.onwriteend = function(e) {
767 $('set-wallpaper-layout').disabled = false;
768 var wallpaperInfo = {
771 source: Constants.WallpaperSourceEnum.Custom,
772 availableOffline: true
774 self.wallpaperGrid_.dataModel.splice(0, 0, wallpaperInfo);
775 self.wallpaperGrid_.selectedItem = wallpaperInfo;
776 self.wallpaperGrid_.activeItem = wallpaperInfo;
777 self.currentWallpaper_ = fileName;
778 WallpaperUtil.saveToStorage(self.currentWallpaper_, layout,
782 fileWriter.onerror = errorHandler;
784 var blob = new Blob([new Int8Array(thumbnail)],
785 {'type' : 'image\/jpeg'});
786 fileWriter.write(blob);
790 self.wallpaperDirs_.getDirectory(WallpaperDirNameEnum.THUMBNAIL,
791 success, errorHandler);
794 var success = function(dirEntry) {
795 dirEntry.getFile(fileName, {create: true}, function(fileEntry) {
796 fileEntry.createWriter(function(fileWriter) {
797 fileWriter.addEventListener('writeend', function(e) {
798 var reader = new FileReader();
799 reader.readAsArrayBuffer(file);
800 reader.addEventListener('error', errorHandler);
801 reader.addEventListener('load', function(e) {
802 self.setCustomWallpaper(e.target.result, layout, true, fileName,
803 saveThumbnail, function() {
804 self.removeCustomWallpaper(fileName);
810 fileWriter.addEventListener('error', errorHandler);
811 fileWriter.write(file);
815 self.wallpaperDirs_.getDirectory(WallpaperDirNameEnum.ORIGINAL, success,
818 setSelectedFile(files[0], layout, new Date().getTime().toString());
822 * Removes wallpaper and thumbnail with fileName from FileSystem.
823 * @param {string} fileName The file name of wallpaper and thumbnail to be
826 WallpaperManager.prototype.removeCustomWallpaper = function(fileName) {
827 var errorHandler = this.onFileSystemError_.bind(this);
829 var removeFile = function(fileName) {
830 var success = function(dirEntry) {
831 dirEntry.getFile(fileName, {create: false}, function(fileEntry) {
832 fileEntry.remove(function() {
837 // Removes copy of original.
838 self.wallpaperDirs_.getDirectory(WallpaperDirNameEnum.ORIGINAL, success,
841 // Removes generated thumbnail.
842 self.wallpaperDirs_.getDirectory(WallpaperDirNameEnum.THUMBNAIL, success,
845 removeFile(fileName);
849 * Sets current wallpaper and generate thumbnail if generateThumbnail is true.
850 * @param {ArrayBuffer} wallpaper The binary representation of wallpaper.
851 * @param {string} layout The user selected wallpaper layout.
852 * @param {boolean} generateThumbnail True if need to generate thumbnail.
853 * @param {string} fileName The unique file name of wallpaper.
854 * @param {function(thumbnail):void} success Success callback. If
855 * generateThumbnail is true, the callback parameter should have the
856 * generated thumbnail.
857 * @param {function(e):void} failure Failure callback. Called when there is an
858 * error from FileSystem.
860 WallpaperManager.prototype.setCustomWallpaper = function(wallpaper,
867 var onFinished = function(opt_thumbnail) {
868 if (chrome.runtime.lastError != undefined) {
869 self.showError_(chrome.runtime.lastError.message);
870 $('set-wallpaper-layout').disabled = true;
873 success(opt_thumbnail);
874 // Custom wallpapers are not synced yet. If login on a different
875 // computer after set a custom wallpaper, wallpaper wont change by sync.
876 WallpaperUtil.saveWallpaperInfo(fileName, layout,
877 Constants.WallpaperSourceEnum.Custom);
881 chrome.wallpaperPrivate.setCustomWallpaper(wallpaper, layout,
883 fileName, onFinished);
887 * Sets wallpaper finished. Displays error message if any.
888 * @param {WallpaperThumbnailsGridItem=} opt_selectedGridItem The wallpaper
889 * thumbnail grid item. It extends from cr.ui.ListItem.
890 * @param {{baseURL: string, layout: string, source: string,
891 * availableOffline: boolean, opt_dynamicURL: string,
892 * opt_author: string, opt_authorWebsite: string}=}
893 * opt_selectedItem the selected item in WallpaperThumbnailsGrid's data
896 WallpaperManager.prototype.onFinished_ = function(opt_selectedGridItem,
898 if (opt_selectedGridItem)
899 this.progressManager_.hideProgressBar(opt_selectedGridItem);
901 if (chrome.runtime.lastError != undefined) {
902 this.showError_(chrome.runtime.lastError.message);
903 } else if (opt_selectedItem) {
904 this.wallpaperGrid_.activeItem = opt_selectedItem;
909 * Handles the layout setting change of custom wallpaper.
911 WallpaperManager.prototype.onWallpaperLayoutChanged_ = function() {
912 var layout = getSelectedLayout();
914 chrome.wallpaperPrivate.setCustomWallpaperLayout(layout, function() {
915 if (chrome.runtime.lastError != undefined) {
916 self.showError_(chrome.runtime.lastError.message);
917 self.removeCustomWallpaper(fileName);
918 $('set-wallpaper-layout').disabled = true;
920 WallpaperUtil.saveToStorage(self.currentWallpaper_, layout, false);
926 * Handles user clicking on a different category.
928 WallpaperManager.prototype.onCategoriesChange_ = function() {
929 var categoriesList = this.categoriesList_;
930 var selectedIndex = categoriesList.selectionModel.selectedIndex;
931 if (selectedIndex == -1)
933 var selectedListItem = categoriesList.getListItemByIndex(selectedIndex);
935 bar.style.left = selectedListItem.offsetLeft + 'px';
936 bar.style.width = selectedListItem.offsetWidth + 'px';
938 var wallpapersDataModel = new cr.ui.ArrayDataModel([]);
940 if (selectedListItem.custom) {
941 this.document_.body.setAttribute('custom', '');
942 var errorHandler = this.onFileSystemError_.bind(this);
943 var toArray = function(list) {
944 return Array.prototype.slice.call(list || [], 0);
948 var processResults = function(entries) {
949 for (var i = 0; i < entries.length; i++) {
950 var entry = entries[i];
951 var wallpaperInfo = {
953 // The layout will be replaced by the actual value saved in
954 // local storage when requested later. Layout is not important
955 // for constructing thumbnails grid, we use CENTER_CROPPED here
956 // to speed up the process of constructing. So we do not need to
957 // wait for fetching correct layout.
958 layout: 'CENTER_CROPPED',
959 source: Constants.WallpaperSourceEnum.Custom,
960 availableOffline: true
962 wallpapersDataModel.push(wallpaperInfo);
964 if (loadTimeData.getBoolean('isOEMDefaultWallpaper')) {
965 var oemDefaultWallpaperElement = {
966 baseURL: 'OemDefaultWallpaper',
967 layout: 'CENTER_CROPPED',
968 source: Constants.WallpaperSourceEnum.OEM,
969 availableOffline: true
971 wallpapersDataModel.push(oemDefaultWallpaperElement);
973 for (var i = 0; i < wallpapersDataModel.length; i++) {
974 if (self.currentWallpaper_ == wallpapersDataModel.item(i).baseURL)
975 selectedItem = wallpapersDataModel.item(i);
980 source: Constants.WallpaperSourceEnum.AddNew,
981 availableOffline: true
983 wallpapersDataModel.push(lastElement);
984 self.wallpaperGrid_.dataModel = wallpapersDataModel;
985 self.wallpaperGrid_.selectedItem = selectedItem;
986 self.wallpaperGrid_.activeItem = selectedItem;
989 var success = function(dirEntry) {
990 var dirReader = dirEntry.createReader();
992 // All of a directory's entries are not guaranteed to return in a single
994 var readEntries = function() {
995 dirReader.readEntries(function(results) {
996 if (!results.length) {
997 processResults(entries.sort());
999 entries = entries.concat(toArray(results));
1004 readEntries(); // Start reading dirs.
1006 this.wallpaperDirs_.getDirectory(WallpaperDirNameEnum.ORIGINAL,
1007 success, errorHandler);
1009 this.document_.body.removeAttribute('custom');
1010 for (var key in this.manifest_.wallpaper_list) {
1011 if (selectedIndex == AllCategoryIndex ||
1012 this.manifest_.wallpaper_list[key].categories.indexOf(
1013 selectedIndex - OnlineCategoriesOffset) != -1) {
1014 var wallpaperInfo = {
1015 baseURL: this.manifest_.wallpaper_list[key].base_url,
1016 layout: this.manifest_.wallpaper_list[key].default_layout,
1017 source: Constants.WallpaperSourceEnum.Online,
1018 availableOffline: false,
1019 author: this.manifest_.wallpaper_list[key].author,
1020 authorWebsite: this.manifest_.wallpaper_list[key].author_website,
1021 dynamicURL: this.manifest_.wallpaper_list[key].dynamic_url
1023 var startIndex = wallpaperInfo.baseURL.lastIndexOf('/') + 1;
1024 var fileName = wallpaperInfo.baseURL.substring(startIndex) +
1025 Constants.HighResolutionSuffix;
1026 if (this.downloadedListMap_ &&
1027 this.downloadedListMap_.hasOwnProperty(encodeURI(fileName))) {
1028 wallpaperInfo.availableOffline = true;
1030 wallpapersDataModel.push(wallpaperInfo);
1031 var url = this.manifest_.wallpaper_list[key].base_url +
1032 Constants.HighResolutionSuffix;
1033 if (url == this.currentWallpaper_) {
1034 selectedItem = wallpaperInfo;
1038 this.wallpaperGrid_.dataModel = wallpapersDataModel;
1039 this.wallpaperGrid_.selectedItem = selectedItem;
1040 this.wallpaperGrid_.activeItem = selectedItem;