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.
58 * @param {string} id The id of the string to return.
59 * @return {string} The translated string.
62 return loadTimeData.getString(id);
66 * Retruns the current selected layout.
67 * @return {string} The selected layout.
69 function getSelectedLayout() {
70 var setWallpaperLayout = $('set-wallpaper-layout');
71 return setWallpaperLayout.options[setWallpaperLayout.selectedIndex].value;
75 * Loads translated strings.
77 WallpaperManager.initStrings = function(callback) {
78 chrome.wallpaperPrivate.getStrings(function(strings) {
79 loadTimeData.data = strings;
86 * Requests wallpaper manifest file from server.
88 WallpaperManager.prototype.fetchManifest_ = function() {
89 var locale = navigator.language;
90 if (!this.enableOnlineWallpaper_) {
91 this.postManifestDomInit_();
96 str('manifestBaseURL') + locale + '.json',
97 // Fallback url. Use 'en' locale by default.
98 str('manifestBaseURL') + 'en.json'];
100 var asyncFetchManifestFromUrls = function(urls, func, successCallback,
105 if (index < urls.length) {
106 func(loop, urls[index]);
113 success: function(response) {
114 successCallback(response);
117 failure: function() {
124 var fetchManifestAsync = function(loop, url) {
125 var xhr = new XMLHttpRequest();
127 xhr.addEventListener('loadend', function(e) {
128 if (this.status == 200 && this.responseText != null) {
130 var manifest = JSON.parse(this.responseText);
131 loop.success(manifest);
139 xhr.open('GET', url, true);
146 if (navigator.onLine) {
147 asyncFetchManifestFromUrls(urls, fetchManifestAsync,
148 this.onLoadManifestSuccess_.bind(this),
149 this.onLoadManifestFailed_.bind(this));
151 // If device is offline, fetches manifest from local storage.
152 // TODO(bshe): Always loading the offline manifest first and replacing
153 // with the online one when available.
154 this.onLoadManifestFailed_();
159 * Shows error message in a centered dialog.
161 * @param {string} errroMessage The string to show in the error dialog.
163 WallpaperManager.prototype.showError_ = function(errorMessage) {
164 document.querySelector('.error-message').textContent = errorMessage;
165 $('error-container').hidden = false;
169 * Sets manifest loaded from server. Called after manifest is successfully
171 * @param {object} manifest The parsed manifest file.
173 WallpaperManager.prototype.onLoadManifestSuccess_ = function(manifest) {
174 this.manifest_ = manifest;
175 WallpaperUtil.saveToStorage(Constants.AccessManifestKey, manifest, false);
176 this.postManifestDomInit_();
179 // Sets manifest to previously saved object if any and shows connection error.
180 // Called after manifest failed to load.
181 WallpaperManager.prototype.onLoadManifestFailed_ = function() {
182 var accessManifestKey = Constants.AccessManifestKey;
184 Constants.WallpaperLocalStorage.get(accessManifestKey, function(items) {
185 self.manifest_ = items[accessManifestKey] ? items[accessManifestKey] : {};
186 self.showError_(str('connectionFailed'));
187 self.postManifestDomInit_();
188 $('wallpaper-grid').classList.add('image-picker-offline');
193 * Toggle surprise me feature of wallpaper picker. It fires an storage
194 * onChanged event. Event handler for that event is in event_page.js.
197 WallpaperManager.prototype.toggleSurpriseMe_ = function() {
198 var checkbox = $('surprise-me').querySelector('#checkbox');
199 var shouldEnable = !checkbox.classList.contains('checked');
200 WallpaperUtil.saveToStorage(Constants.AccessSurpriseMeEnabledKey,
201 shouldEnable, true, function() {
202 if (chrome.runtime.lastError == null) {
204 checkbox.classList.add('checked');
206 checkbox.classList.remove('checked');
208 $('categories-list').disabled = shouldEnable;
209 $('wallpaper-grid').disabled = shouldEnable;
211 // TODO(bshe): show error message to user.
212 console.error('Failed to save surprise me option to chrome storage.');
218 * One-time initialization of various DOM nodes. Fetching manifest may take a
219 * long time due to slow connection. Dom nodes that do not depend on manifest
220 * should be initialized here to unblock from manifest fetching.
222 WallpaperManager.prototype.preManifestDomInit_ = function() {
223 $('window-close-button').addEventListener('click', function() {
226 this.document_.defaultView.addEventListener(
227 'resize', this.onResize_.bind(this));
228 this.document_.defaultView.addEventListener(
229 'keydown', this.onKeyDown_.bind(this));
230 $('learn-more').href = LearnMoreURL;
231 $('close-error').addEventListener('click', function() {
232 $('error-container').hidden = true;
234 $('close-wallpaper-selection').addEventListener('click', function() {
235 $('wallpaper-selection-container').hidden = true;
236 $('set-wallpaper-layout').disabled = true;
241 * One-time initialization of various DOM nodes. Dom nodes that do depend on
242 * manifest should be initialized here.
244 WallpaperManager.prototype.postManifestDomInit_ = function() {
245 i18nTemplate.process(this.document_, loadTimeData);
246 this.initCategoriesList_();
247 this.initThumbnailsGrid_();
248 this.presetCategory_();
250 $('file-selector').addEventListener(
251 'change', this.onFileSelectorChanged_.bind(this));
252 $('set-wallpaper-layout').addEventListener(
253 'change', this.onWallpaperLayoutChanged_.bind(this));
255 if (loadTimeData.valueExists('wallpaperAppName')) {
256 $('wallpaper-set-by-message').textContent = loadTimeData.getStringF(
257 'currentWallpaperSetByMessage', str('wallpaperAppName'));
260 if (this.enableOnlineWallpaper_) {
262 $('surprise-me').hidden = false;
263 $('surprise-me').addEventListener('click',
264 this.toggleSurpriseMe_.bind(this));
265 Constants.WallpaperSyncStorage.get(Constants.AccessSurpriseMeEnabledKey,
267 // Surprise me has been moved from local to sync storage, prefer
268 // values from sync, but if unset check local and update synced pref
270 if (!items.hasOwnProperty(Constants.AccessSurpriseMeEnabledKey)) {
271 Constants.WallpaperLocalStorage.get(
272 Constants.AccessSurpriseMeEnabledKey, function(values) {
273 if (values.hasOwnProperty(Constants.AccessSurpriseMeEnabledKey)) {
274 WallpaperUtil.saveToStorage(Constants.AccessSurpriseMeEnabledKey,
275 values[Constants.AccessSurpriseMeEnabledKey], true);
277 if (values[Constants.AccessSurpriseMeEnabledKey]) {
278 $('surprise-me').querySelector('#checkbox').classList.add(
280 $('categories-list').disabled = true;
281 $('wallpaper-grid').disabled = true;
284 } else if (items[Constants.AccessSurpriseMeEnabledKey]) {
285 $('surprise-me').querySelector('#checkbox').classList.add('checked');
286 $('categories-list').disabled = true;
287 $('wallpaper-grid').disabled = true;
291 window.addEventListener('offline', function() {
292 chrome.wallpaperPrivate.getOfflineWallpaperList(function(lists) {
293 if (!self.downloadedListMap_)
294 self.downloadedListMap_ = {};
295 for (var i = 0; i < lists.length; i++) {
296 self.downloadedListMap_[lists[i]] = true;
298 var thumbnails = self.document_.querySelectorAll('.thumbnail');
299 for (var i = 0; i < thumbnails.length; i++) {
300 var thumbnail = thumbnails[i];
301 var url = self.wallpaperGrid_.dataModel.item(i).baseURL;
302 var fileName = url.substring(url.lastIndexOf('/') + 1) +
303 Constants.HighResolutionSuffix;
304 if (self.downloadedListMap_ &&
305 self.downloadedListMap_.hasOwnProperty(encodeURI(fileName))) {
306 thumbnail.offline = true;
310 $('wallpaper-grid').classList.add('image-picker-offline');
312 window.addEventListener('online', function() {
313 self.downloadedListMap_ = null;
314 $('wallpaper-grid').classList.remove('image-picker-offline');
319 this.initContextMenuAndCommand_();
320 WallpaperUtil.testSendMessage('launched');
324 * One-time initialization of context menu and command.
326 WallpaperManager.prototype.initContextMenuAndCommand_ = function() {
327 this.wallpaperContextMenu_ = $('wallpaper-context-menu');
328 cr.ui.Menu.decorate(this.wallpaperContextMenu_);
329 cr.ui.contextMenuHandler.setContextMenu(this.wallpaperGrid_,
330 this.wallpaperContextMenu_);
331 var commands = this.dialogDom_.querySelectorAll('command');
332 for (var i = 0; i < commands.length; i++)
333 cr.ui.Command.decorate(commands[i]);
335 var doc = this.document_;
336 doc.addEventListener('command', this.onCommand_.bind(this));
337 doc.addEventListener('canExecute', this.onCommandCanExecute_.bind(this));
341 * Handles a command being executed.
342 * @param {Event} event A command event.
344 WallpaperManager.prototype.onCommand_ = function(event) {
345 if (event.command.id == 'delete') {
346 var wallpaperGrid = this.wallpaperGrid_;
347 var selectedIndex = wallpaperGrid.selectionModel.selectedIndex;
348 var item = wallpaperGrid.dataModel.item(selectedIndex);
349 if (!item || item.source != Constants.WallpaperSourceEnum.Custom)
351 this.removeCustomWallpaper(item.baseURL);
352 wallpaperGrid.dataModel.splice(selectedIndex, 1);
353 // Calculate the number of remaining custom wallpapers. The add new button
354 // in data model needs to be excluded.
355 var customWallpaperCount = wallpaperGrid.dataModel.length - 1;
356 if (customWallpaperCount == 0) {
357 // Active custom wallpaper is also copied in chronos data dir. It needs
359 chrome.wallpaperPrivate.resetWallpaper();
360 this.onWallpaperChanged_(null, null);
362 selectedIndex = Math.min(selectedIndex, customWallpaperCount - 1);
363 wallpaperGrid.selectionModel.selectedIndex = selectedIndex;
365 event.cancelBubble = true;
370 * Decides if a command can be executed on current target.
371 * @param {Event} event A command event.
373 WallpaperManager.prototype.onCommandCanExecute_ = function(event) {
374 switch (event.command.id) {
376 var wallpaperGrid = this.wallpaperGrid_;
377 var selectedIndex = wallpaperGrid.selectionModel.selectedIndex;
378 var item = wallpaperGrid.dataModel.item(selectedIndex);
379 if (selectedIndex != this.wallpaperGrid_.dataModel.length - 1 &&
380 item && item.source == Constants.WallpaperSourceEnum.Custom) {
381 event.canExecute = true;
385 event.canExecute = false;
390 * Preset to the category which contains current wallpaper.
392 WallpaperManager.prototype.presetCategory_ = function() {
393 this.currentWallpaper_ = str('currentWallpaper');
394 // The currentWallpaper_ is either a url contains HightResolutionSuffix or a
395 // custom wallpaper file name converted from an integer value represent
396 // time (e.g., 13006377367586070).
397 if (!this.enableOnlineWallpaper_ || (this.currentWallpaper_ &&
398 this.currentWallpaper_.indexOf(Constants.HighResolutionSuffix) == -1)) {
399 // Custom is the last one in the categories list.
400 this.categoriesList_.selectionModel.selectedIndex =
401 this.categoriesList_.dataModel.length - 1;
405 var presetCategoryInner_ = function() {
406 // Selects the first category in the categories list of current
407 // wallpaper as the default selected category when showing wallpaper
409 var presetCategory = AllCategoryIndex;
410 if (self.currentWallpaper_) {
411 for (var key in self.manifest_.wallpaper_list) {
412 var url = self.manifest_.wallpaper_list[key].base_url +
413 Constants.HighResolutionSuffix;
414 if (url.indexOf(self.currentWallpaper_) != -1 &&
415 self.manifest_.wallpaper_list[key].categories.length > 0) {
416 presetCategory = self.manifest_.wallpaper_list[key].categories[0] +
417 OnlineCategoriesOffset;
422 self.categoriesList_.selectionModel.selectedIndex = presetCategory;
424 if (navigator.onLine) {
425 presetCategoryInner_();
427 // If device is offline, gets the available offline wallpaper list first.
428 // Wallpapers which are not in the list will display a grayscaled
430 chrome.wallpaperPrivate.getOfflineWallpaperList(function(lists) {
431 if (!self.downloadedListMap_)
432 self.downloadedListMap_ = {};
433 for (var i = 0; i < lists.length; i++)
434 self.downloadedListMap_[lists[i]] = true;
435 presetCategoryInner_();
441 * Constructs the thumbnails grid.
443 WallpaperManager.prototype.initThumbnailsGrid_ = function() {
444 this.wallpaperGrid_ = $('wallpaper-grid');
445 wallpapers.WallpaperThumbnailsGrid.decorate(this.wallpaperGrid_);
446 this.wallpaperGrid_.autoExpands = true;
448 this.wallpaperGrid_.addEventListener('change', this.onChange_.bind(this));
449 this.wallpaperGrid_.addEventListener('dblclick', this.onClose_.bind(this));
453 * Handles change event dispatched by wallpaper grid.
455 WallpaperManager.prototype.onChange_ = function() {
456 // splice may dispatch a change event because the position of selected
457 // element changing. But the actual selected element may not change after
458 // splice. Check if the new selected element equals to the previous selected
459 // element before continuing. Otherwise, wallpaper may reset to previous one
460 // as described in http://crbug.com/229036.
461 if (this.selectedItem_ == this.wallpaperGrid_.selectedItem)
463 this.selectedItem_ = this.wallpaperGrid_.selectedItem;
464 this.onSelectedItemChanged_();
468 * Closes window if no pending wallpaper request.
470 WallpaperManager.prototype.onClose_ = function() {
471 if (this.wallpaperRequest_) {
472 this.wallpaperRequest_.addEventListener('loadend', function() {
473 // Close window on wallpaper loading finished.
482 * Moves the check mark to |activeItem| and hides the wallpaper set by third
483 * party message if any. Called when wallpaper changed successfully.
484 * @param {?Object} activeItem The active item in WallpaperThumbnailsGrid's
486 * @param {?string} currentWallpaperURL The URL or filename of current
489 WallpaperManager.prototype.onWallpaperChanged_ = function(
490 activeItem, currentWallpaperURL) {
491 this.wallpaperGrid_.activeItem = activeItem;
492 this.currentWallpaper_ = currentWallpaperURL;
493 // Hides the wallpaper set by message.
494 $('wallpaper-set-by-message').textContent = '';
498 * Sets wallpaper to the corresponding wallpaper of selected thumbnail.
499 * @param {{baseURL: string, layout: string, source: string,
500 * availableOffline: boolean, opt_dynamicURL: string,
501 * opt_author: string, opt_authorWebsite: string}}
502 * selectedItem the selected item in WallpaperThumbnailsGrid's data
505 WallpaperManager.prototype.setSelectedWallpaper_ = function(selectedItem) {
507 switch (selectedItem.source) {
508 case Constants.WallpaperSourceEnum.Custom:
509 var errorHandler = this.onFileSystemError_.bind(this);
510 var success = function(dirEntry) {
511 dirEntry.getFile(selectedItem.baseURL, {create: false},
512 function(fileEntry) {
513 fileEntry.file(function(file) {
514 var reader = new FileReader();
515 reader.readAsArrayBuffer(file);
516 reader.addEventListener('error', errorHandler);
517 reader.addEventListener('load', function(e) {
518 self.setCustomWallpaper(e.target.result,
520 false, selectedItem.baseURL,
521 self.onWallpaperChanged_.bind(self,
522 selectedItem, selectedItem.baseURL),
528 this.wallpaperDirs_.getDirectory(WallpaperDirNameEnum.ORIGINAL,
529 success, errorHandler);
531 case Constants.WallpaperSourceEnum.OEM:
532 // Resets back to default wallpaper.
533 chrome.wallpaperPrivate.resetWallpaper();
534 this.onWallpaperChanged_(selectedItem, selectedItem.baseURL);
535 WallpaperUtil.saveWallpaperInfo(wallpaperURL, selectedItem.layout,
536 selectedItem.source);
538 case Constants.WallpaperSourceEnum.Online:
539 var wallpaperURL = selectedItem.baseURL +
540 Constants.HighResolutionSuffix;
541 var selectedGridItem = this.wallpaperGrid_.getListItem(selectedItem);
543 chrome.wallpaperPrivate.setWallpaperIfExists(wallpaperURL,
547 self.onWallpaperChanged_(selectedItem, wallpaperURL);
548 WallpaperUtil.saveWallpaperInfo(wallpaperURL, selectedItem.layout,
549 selectedItem.source);
553 // Falls back to request wallpaper from server.
554 if (self.wallpaperRequest_)
555 self.wallpaperRequest_.abort();
557 self.wallpaperRequest_ = new XMLHttpRequest();
558 self.progressManager_.reset(self.wallpaperRequest_, selectedGridItem);
560 var onSuccess = function(xhr) {
561 var image = xhr.response;
562 chrome.wallpaperPrivate.setWallpaper(image, selectedItem.layout,
565 self.progressManager_.hideProgressBar(selectedGridItem);
567 if (chrome.runtime.lastError != undefined &&
568 chrome.runtime.lastError.message !=
569 str('canceledWallpaper')) {
570 self.showError_(chrome.runtime.lastError.message);
572 self.onWallpaperChanged_(selectedItem, wallpaperURL);
575 WallpaperUtil.saveWallpaperInfo(wallpaperURL, selectedItem.layout,
576 selectedItem.source);
577 self.wallpaperRequest_ = null;
579 var onFailure = function() {
580 self.progressManager_.hideProgressBar(selectedGridItem);
581 self.showError_(str('downloadFailed'));
582 self.wallpaperRequest_ = null;
584 WallpaperUtil.fetchURL(wallpaperURL, 'arraybuffer', onSuccess,
585 onFailure, self.wallpaperRequest_);
589 console.error('Unsupported wallpaper source.');
594 * Removes the oldest custom wallpaper. If the oldest one is set as current
595 * wallpaper, removes the second oldest one to free some space. This should
596 * only be called when exceeding wallpaper quota.
598 WallpaperManager.prototype.removeOldestWallpaper_ = function() {
599 // Custom wallpapers should already sorted when put to the data model. The
600 // last element is the add new button, need to exclude it as well.
601 var oldestIndex = this.wallpaperGrid_.dataModel.length - 2;
602 var item = this.wallpaperGrid_.dataModel.item(oldestIndex);
603 if (!item || item.source != Constants.WallpaperSourceEnum.Custom)
605 if (item.baseURL == this.currentWallpaper_)
606 item = this.wallpaperGrid_.dataModel.item(--oldestIndex);
608 this.removeCustomWallpaper(item.baseURL);
609 this.wallpaperGrid_.dataModel.splice(oldestIndex, 1);
614 * Shows an error message to user and log the failed reason in console.
616 WallpaperManager.prototype.onFileSystemError_ = function(e) {
619 case FileError.QUOTA_EXCEEDED_ERR:
620 msg = 'QUOTA_EXCEEDED_ERR';
621 // Instead of simply remove oldest wallpaper, we should consider a
622 // better way to handle this situation. See crbug.com/180890.
623 this.removeOldestWallpaper_();
625 case FileError.NOT_FOUND_ERR:
626 msg = 'NOT_FOUND_ERR';
628 case FileError.SECURITY_ERR:
629 msg = 'SECURITY_ERR';
631 case FileError.INVALID_MODIFICATION_ERR:
632 msg = 'INVALID_MODIFICATION_ERR';
634 case FileError.INVALID_STATE_ERR:
635 msg = 'INVALID_STATE_ERR';
638 msg = 'Unknown Error';
641 console.error('Error: ' + msg);
642 this.showError_(str('accessFileFailure'));
646 * Handles changing of selectedItem in wallpaper manager.
648 WallpaperManager.prototype.onSelectedItemChanged_ = function() {
649 this.setWallpaperAttribution_(this.selectedItem_);
651 if (!this.selectedItem_ || this.selectedItem_.source == 'ADDNEW')
654 if (this.selectedItem_.baseURL && !this.wallpaperGrid_.inProgramSelection) {
655 if (this.selectedItem_.source == Constants.WallpaperSourceEnum.Custom) {
657 var key = this.selectedItem_.baseURL;
659 Constants.WallpaperLocalStorage.get(key, function(items) {
660 self.selectedItem_.layout =
661 items[key] ? items[key] : 'CENTER_CROPPED';
662 self.setSelectedWallpaper_(self.selectedItem_);
665 this.setSelectedWallpaper_(this.selectedItem_);
671 * Set attributions of wallpaper with given URL. If URL is not valid, clear
673 * @param {{baseURL: string, dynamicURL: string, layout: string,
674 * author: string, authorWebsite: string, availableOffline: boolean}}
675 * selectedItem selected wallpaper item in grid.
678 WallpaperManager.prototype.setWallpaperAttribution_ = function(selectedItem) {
679 // Only online wallpapers have author and website attributes. All other type
680 // of wallpapers should not show attributions.
682 selectedItem.source == Constants.WallpaperSourceEnum.Online) {
683 $('author-name').textContent = selectedItem.author;
684 $('author-website').textContent = $('author-website').href =
685 selectedItem.authorWebsite;
686 chrome.wallpaperPrivate.getThumbnail(selectedItem.baseURL,
689 var img = $('attribute-image');
691 var blob = new Blob([new Int8Array(data)], {'type' : 'image\/png'});
692 img.src = window.URL.createObjectURL(blob);
693 img.addEventListener('load', function(e) {
694 window.URL.revokeObjectURL(this.src);
700 $('wallpaper-attribute').hidden = false;
701 $('attribute-image').hidden = false;
704 $('wallpaper-attribute').hidden = true;
705 $('attribute-image').hidden = true;
706 $('author-name').textContent = '';
707 $('author-website').textContent = $('author-website').href = '';
708 $('attribute-image').src = '';
712 * Resize thumbnails grid and categories list to fit the new window size.
714 WallpaperManager.prototype.onResize_ = function() {
715 this.wallpaperGrid_.redraw();
716 this.categoriesList_.redraw();
720 * Close the last opened overlay on pressing the Escape key.
721 * @param {Event} event A keydown event.
723 WallpaperManager.prototype.onKeyDown_ = function(event) {
724 if (event.keyCode == 27) {
725 // The last opened overlay coincides with the first match of querySelector
726 // because the Error Container is declared in the DOM before the Wallpaper
727 // Selection Container.
728 // TODO(bshe): Make the overlay selection not dependent on the DOM.
729 var closeButtonSelector = '.overlay-container:not([hidden]) .close';
730 var closeButton = this.document_.querySelector(closeButtonSelector);
733 event.preventDefault();
739 * Constructs the categories list.
741 WallpaperManager.prototype.initCategoriesList_ = function() {
742 this.categoriesList_ = $('categories-list');
743 cr.ui.List.decorate(this.categoriesList_);
744 // cr.ui.list calculates items in view port based on client height and item
745 // height. However, categories list is displayed horizontally. So we should
746 // not calculate visible items here. Sets autoExpands to true to show every
748 // TODO(bshe): Use ul to replace cr.ui.list for category list.
749 this.categoriesList_.autoExpands = true;
752 this.categoriesList_.itemConstructor = function(entry) {
753 return self.renderCategory_(entry);
756 this.categoriesList_.selectionModel = new cr.ui.ListSingleSelectionModel();
757 this.categoriesList_.selectionModel.addEventListener(
758 'change', this.onCategoriesChange_.bind(this));
760 var categoriesDataModel = new cr.ui.ArrayDataModel([]);
761 if (this.enableOnlineWallpaper_) {
762 // Adds all category as first category.
763 categoriesDataModel.push(str('allCategoryLabel'));
764 for (var key in this.manifest_.categories) {
765 categoriesDataModel.push(this.manifest_.categories[key]);
768 // Adds custom category as last category.
769 categoriesDataModel.push(str('customCategoryLabel'));
770 this.categoriesList_.dataModel = categoriesDataModel;
774 * Constructs the element in categories list.
775 * @param {string} entry Text content of a category.
777 WallpaperManager.prototype.renderCategory_ = function(entry) {
778 var li = this.document_.createElement('li');
779 cr.defineProperty(li, 'custom', cr.PropertyKind.BOOL_ATTR);
780 li.custom = (entry == str('customCategoryLabel'));
781 cr.defineProperty(li, 'lead', cr.PropertyKind.BOOL_ATTR);
782 cr.defineProperty(li, 'selected', cr.PropertyKind.BOOL_ATTR);
783 var div = this.document_.createElement('div');
784 div.textContent = entry;
790 * Handles the custom wallpaper which user selected from file manager. Called
791 * when users select a file.
793 WallpaperManager.prototype.onFileSelectorChanged_ = function() {
794 var files = $('file-selector').files;
795 if (files.length != 1)
796 console.error('More than one files are selected or no file selected');
797 if (!files[0].type.match('image/jpeg') &&
798 !files[0].type.match('image/png')) {
799 this.showError_(str('invalidWallpaper'));
802 var layout = getSelectedLayout();
804 var errorHandler = this.onFileSystemError_.bind(this);
805 var setSelectedFile = function(file, layout, fileName) {
806 var saveThumbnail = function(thumbnail) {
807 var success = function(dirEntry) {
808 dirEntry.getFile(fileName, {create: true}, function(fileEntry) {
809 fileEntry.createWriter(function(fileWriter) {
810 fileWriter.onwriteend = function(e) {
811 $('set-wallpaper-layout').disabled = false;
812 var wallpaperInfo = {
815 source: Constants.WallpaperSourceEnum.Custom,
816 availableOffline: true
818 self.wallpaperGrid_.dataModel.splice(0, 0, wallpaperInfo);
819 self.wallpaperGrid_.selectedItem = wallpaperInfo;
820 self.onWallpaperChanged_(wallpaperInfo, fileName);
821 WallpaperUtil.saveToStorage(self.currentWallpaper_, layout,
825 fileWriter.onerror = errorHandler;
827 var blob = new Blob([new Int8Array(thumbnail)],
828 {'type' : 'image\/jpeg'});
829 fileWriter.write(blob);
833 self.wallpaperDirs_.getDirectory(WallpaperDirNameEnum.THUMBNAIL,
834 success, errorHandler);
837 var success = function(dirEntry) {
838 dirEntry.getFile(fileName, {create: true}, function(fileEntry) {
839 fileEntry.createWriter(function(fileWriter) {
840 fileWriter.addEventListener('writeend', function(e) {
841 var reader = new FileReader();
842 reader.readAsArrayBuffer(file);
843 reader.addEventListener('error', errorHandler);
844 reader.addEventListener('load', function(e) {
845 self.setCustomWallpaper(e.target.result, layout, true, fileName,
846 saveThumbnail, function() {
847 self.removeCustomWallpaper(fileName);
853 fileWriter.addEventListener('error', errorHandler);
854 fileWriter.write(file);
858 self.wallpaperDirs_.getDirectory(WallpaperDirNameEnum.ORIGINAL, success,
861 setSelectedFile(files[0], layout, new Date().getTime().toString());
865 * Removes wallpaper and thumbnail with fileName from FileSystem.
866 * @param {string} fileName The file name of wallpaper and thumbnail to be
869 WallpaperManager.prototype.removeCustomWallpaper = function(fileName) {
870 var errorHandler = this.onFileSystemError_.bind(this);
872 var removeFile = function(fileName) {
873 var success = function(dirEntry) {
874 dirEntry.getFile(fileName, {create: false}, function(fileEntry) {
875 fileEntry.remove(function() {
880 // Removes copy of original.
881 self.wallpaperDirs_.getDirectory(WallpaperDirNameEnum.ORIGINAL, success,
884 // Removes generated thumbnail.
885 self.wallpaperDirs_.getDirectory(WallpaperDirNameEnum.THUMBNAIL, success,
888 removeFile(fileName);
892 * Sets current wallpaper and generate thumbnail if generateThumbnail is true.
893 * @param {ArrayBuffer} wallpaper The binary representation of wallpaper.
894 * @param {string} layout The user selected wallpaper layout.
895 * @param {boolean} generateThumbnail True if need to generate thumbnail.
896 * @param {string} fileName The unique file name of wallpaper.
897 * @param {function(thumbnail):void} success Success callback. If
898 * generateThumbnail is true, the callback parameter should have the
899 * generated thumbnail.
900 * @param {function(e):void} failure Failure callback. Called when there is an
901 * error from FileSystem.
903 WallpaperManager.prototype.setCustomWallpaper = function(wallpaper,
910 var onFinished = function(opt_thumbnail) {
911 if (chrome.runtime.lastError != undefined &&
912 chrome.runtime.lastError.message != str('canceledWallpaper')) {
913 self.showError_(chrome.runtime.lastError.message);
914 $('set-wallpaper-layout').disabled = true;
917 success(opt_thumbnail);
918 // Custom wallpapers are not synced yet. If login on a different
919 // computer after set a custom wallpaper, wallpaper wont change by sync.
920 WallpaperUtil.saveWallpaperInfo(fileName, layout,
921 Constants.WallpaperSourceEnum.Custom);
925 chrome.wallpaperPrivate.setCustomWallpaper(wallpaper, layout,
927 fileName, onFinished);
931 * Handles the layout setting change of custom wallpaper.
933 WallpaperManager.prototype.onWallpaperLayoutChanged_ = function() {
934 var layout = getSelectedLayout();
936 chrome.wallpaperPrivate.setCustomWallpaperLayout(layout, function() {
937 if (chrome.runtime.lastError != undefined &&
938 chrome.runtime.lastError.message != str('canceledWallpaper')) {
939 self.showError_(chrome.runtime.lastError.message);
940 self.removeCustomWallpaper(fileName);
941 $('set-wallpaper-layout').disabled = true;
943 WallpaperUtil.saveToStorage(self.currentWallpaper_, layout, false);
944 self.onWallpaperChanged_(self.wallpaperGrid_.activeItem,
945 self.currentWallpaper_);
951 * Handles user clicking on a different category.
953 WallpaperManager.prototype.onCategoriesChange_ = function() {
954 var categoriesList = this.categoriesList_;
955 var selectedIndex = categoriesList.selectionModel.selectedIndex;
956 if (selectedIndex == -1)
958 var selectedListItem = categoriesList.getListItemByIndex(selectedIndex);
960 bar.style.left = selectedListItem.offsetLeft + 'px';
961 bar.style.width = selectedListItem.offsetWidth + 'px';
963 var wallpapersDataModel = new cr.ui.ArrayDataModel([]);
965 if (selectedListItem.custom) {
966 this.document_.body.setAttribute('custom', '');
967 var errorHandler = this.onFileSystemError_.bind(this);
968 var toArray = function(list) {
969 return Array.prototype.slice.call(list || [], 0);
973 var processResults = function(entries) {
974 for (var i = 0; i < entries.length; i++) {
975 var entry = entries[i];
976 var wallpaperInfo = {
978 // The layout will be replaced by the actual value saved in
979 // local storage when requested later. Layout is not important
980 // for constructing thumbnails grid, we use CENTER_CROPPED here
981 // to speed up the process of constructing. So we do not need to
982 // wait for fetching correct layout.
983 layout: 'CENTER_CROPPED',
984 source: Constants.WallpaperSourceEnum.Custom,
985 availableOffline: true
987 wallpapersDataModel.push(wallpaperInfo);
989 if (loadTimeData.getBoolean('isOEMDefaultWallpaper')) {
990 var oemDefaultWallpaperElement = {
991 baseURL: 'OemDefaultWallpaper',
992 layout: 'CENTER_CROPPED',
993 source: Constants.WallpaperSourceEnum.OEM,
994 availableOffline: true
996 wallpapersDataModel.push(oemDefaultWallpaperElement);
998 for (var i = 0; i < wallpapersDataModel.length; i++) {
999 if (self.currentWallpaper_ == wallpapersDataModel.item(i).baseURL)
1000 selectedItem = wallpapersDataModel.item(i);
1005 source: Constants.WallpaperSourceEnum.AddNew,
1006 availableOffline: true
1008 wallpapersDataModel.push(lastElement);
1009 self.wallpaperGrid_.dataModel = wallpapersDataModel;
1010 self.wallpaperGrid_.selectedItem = selectedItem;
1011 self.wallpaperGrid_.activeItem = selectedItem;
1014 var success = function(dirEntry) {
1015 var dirReader = dirEntry.createReader();
1017 // All of a directory's entries are not guaranteed to return in a single
1019 var readEntries = function() {
1020 dirReader.readEntries(function(results) {
1021 if (!results.length) {
1022 processResults(entries.sort());
1024 entries = entries.concat(toArray(results));
1029 readEntries(); // Start reading dirs.
1031 this.wallpaperDirs_.getDirectory(WallpaperDirNameEnum.ORIGINAL,
1032 success, errorHandler);
1034 this.document_.body.removeAttribute('custom');
1035 for (var key in this.manifest_.wallpaper_list) {
1036 if (selectedIndex == AllCategoryIndex ||
1037 this.manifest_.wallpaper_list[key].categories.indexOf(
1038 selectedIndex - OnlineCategoriesOffset) != -1) {
1039 var wallpaperInfo = {
1040 baseURL: this.manifest_.wallpaper_list[key].base_url,
1041 layout: this.manifest_.wallpaper_list[key].default_layout,
1042 source: Constants.WallpaperSourceEnum.Online,
1043 availableOffline: false,
1044 author: this.manifest_.wallpaper_list[key].author,
1045 authorWebsite: this.manifest_.wallpaper_list[key].author_website,
1046 dynamicURL: this.manifest_.wallpaper_list[key].dynamic_url
1048 var startIndex = wallpaperInfo.baseURL.lastIndexOf('/') + 1;
1049 var fileName = wallpaperInfo.baseURL.substring(startIndex) +
1050 Constants.HighResolutionSuffix;
1051 if (this.downloadedListMap_ &&
1052 this.downloadedListMap_.hasOwnProperty(encodeURI(fileName))) {
1053 wallpaperInfo.availableOffline = true;
1055 wallpapersDataModel.push(wallpaperInfo);
1056 var url = this.manifest_.wallpaper_list[key].base_url +
1057 Constants.HighResolutionSuffix;
1058 if (url == this.currentWallpaper_) {
1059 selectedItem = wallpaperInfo;
1063 this.wallpaperGrid_.dataModel = wallpapersDataModel;
1064 this.wallpaperGrid_.selectedItem = selectedItem;
1065 this.wallpaperGrid_.activeItem = selectedItem;