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, true, 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.WallpaperSyncStorage.get(Constants.AccessSurpriseMeEnabledKey,
263 // Surprise me has been moved from local to sync storage, prefer
264 // values from sync, but if unset check local and update synced pref
266 if (!items.hasOwnProperty(Constants.AccessSurpriseMeEnabledKey)) {
267 Constants.WallpaperLocalStorage.get(
268 Constants.AccessSurpriseMeEnabledKey, function(values) {
269 if (values.hasOwnProperty(Constants.AccessSurpriseMeEnabledKey)) {
270 WallpaperUtil.saveToStorage(Constants.AccessSurpriseMeEnabledKey,
271 values[Constants.AccessSurpriseMeEnabledKey], true);
273 if (values[Constants.AccessSurpriseMeEnabledKey]) {
274 $('surprise-me').querySelector('#checkbox').classList.add(
276 $('categories-list').disabled = true;
277 $('wallpaper-grid').disabled = true;
280 } else if (items[Constants.AccessSurpriseMeEnabledKey]) {
281 $('surprise-me').querySelector('#checkbox').classList.add('checked');
282 $('categories-list').disabled = true;
283 $('wallpaper-grid').disabled = true;
287 window.addEventListener('offline', function() {
288 chrome.wallpaperPrivate.getOfflineWallpaperList(function(lists) {
289 if (!self.downloadedListMap_)
290 self.downloadedListMap_ = {};
291 for (var i = 0; i < lists.length; i++) {
292 self.downloadedListMap_[lists[i]] = true;
294 var thumbnails = self.document_.querySelectorAll('.thumbnail');
295 for (var i = 0; i < thumbnails.length; i++) {
296 var thumbnail = thumbnails[i];
297 var url = self.wallpaperGrid_.dataModel.item(i).baseURL;
298 var fileName = url.substring(url.lastIndexOf('/') + 1) +
299 Constants.HighResolutionSuffix;
300 if (self.downloadedListMap_ &&
301 self.downloadedListMap_.hasOwnProperty(encodeURI(fileName))) {
302 thumbnail.offline = true;
306 $('wallpaper-grid').classList.add('image-picker-offline');
308 window.addEventListener('online', function() {
309 self.downloadedListMap_ = null;
310 $('wallpaper-grid').classList.remove('image-picker-offline');
315 this.initContextMenuAndCommand_();
319 * One-time initialization of context menu and command.
321 WallpaperManager.prototype.initContextMenuAndCommand_ = function() {
322 this.wallpaperContextMenu_ = $('wallpaper-context-menu');
323 cr.ui.Menu.decorate(this.wallpaperContextMenu_);
324 cr.ui.contextMenuHandler.setContextMenu(this.wallpaperGrid_,
325 this.wallpaperContextMenu_);
326 var commands = this.dialogDom_.querySelectorAll('command');
327 for (var i = 0; i < commands.length; i++)
328 cr.ui.Command.decorate(commands[i]);
330 var doc = this.document_;
331 doc.addEventListener('command', this.onCommand_.bind(this));
332 doc.addEventListener('canExecute', this.onCommandCanExecute_.bind(this));
336 * Handles a command being executed.
337 * @param {Event} event A command event.
339 WallpaperManager.prototype.onCommand_ = function(event) {
340 if (event.command.id == 'delete') {
341 var wallpaperGrid = this.wallpaperGrid_;
342 var selectedIndex = wallpaperGrid.selectionModel.selectedIndex;
343 var item = wallpaperGrid.dataModel.item(selectedIndex);
344 if (!item || item.source != Constants.WallpaperSourceEnum.Custom)
346 this.removeCustomWallpaper(item.baseURL);
347 wallpaperGrid.dataModel.splice(selectedIndex, 1);
348 // Calculate the number of remaining custom wallpapers. The add new button
349 // in data model needs to be excluded.
350 var customWallpaperCount = wallpaperGrid.dataModel.length - 1;
351 if (customWallpaperCount == 0) {
352 // Active custom wallpaper is also copied in chronos data dir. It needs
354 chrome.wallpaperPrivate.resetWallpaper();
356 selectedIndex = Math.min(selectedIndex, customWallpaperCount - 1);
357 wallpaperGrid.selectionModel.selectedIndex = selectedIndex;
359 event.cancelBubble = true;
364 * Decides if a command can be executed on current target.
365 * @param {Event} event A command event.
367 WallpaperManager.prototype.onCommandCanExecute_ = function(event) {
368 switch (event.command.id) {
370 var wallpaperGrid = this.wallpaperGrid_;
371 var selectedIndex = wallpaperGrid.selectionModel.selectedIndex;
372 var item = wallpaperGrid.dataModel.item(selectedIndex);
373 if (selectedIndex != this.wallpaperGrid_.dataModel.length - 1 &&
374 item && item.source == Constants.WallpaperSourceEnum.Custom) {
375 event.canExecute = true;
379 event.canExecute = false;
384 * Preset to the category which contains current wallpaper.
386 WallpaperManager.prototype.presetCategory_ = function() {
387 this.currentWallpaper_ = str('currentWallpaper');
388 // The currentWallpaper_ is either a url contains HightResolutionSuffix or a
389 // custom wallpaper file name converted from an integer value represent
390 // time (e.g., 13006377367586070).
391 if (!this.enableOnlineWallpaper_ || (this.currentWallpaper_ &&
392 this.currentWallpaper_.indexOf(Constants.HighResolutionSuffix) == -1)) {
393 // Custom is the last one in the categories list.
394 this.categoriesList_.selectionModel.selectedIndex =
395 this.categoriesList_.dataModel.length - 1;
399 var presetCategoryInner_ = function() {
400 // Selects the first category in the categories list of current
401 // wallpaper as the default selected category when showing wallpaper
403 var presetCategory = AllCategoryIndex;
404 if (self.currentWallpaper_) {
405 for (var key in self.manifest_.wallpaper_list) {
406 var url = self.manifest_.wallpaper_list[key].base_url +
407 Constants.HighResolutionSuffix;
408 if (url.indexOf(self.currentWallpaper_) != -1 &&
409 self.manifest_.wallpaper_list[key].categories.length > 0) {
410 presetCategory = self.manifest_.wallpaper_list[key].categories[0] +
411 OnlineCategoriesOffset;
416 self.categoriesList_.selectionModel.selectedIndex = presetCategory;
418 if (navigator.onLine) {
419 presetCategoryInner_();
421 // If device is offline, gets the available offline wallpaper list first.
422 // Wallpapers which are not in the list will display a grayscaled
424 chrome.wallpaperPrivate.getOfflineWallpaperList(function(lists) {
425 if (!self.downloadedListMap_)
426 self.downloadedListMap_ = {};
427 for (var i = 0; i < lists.length; i++)
428 self.downloadedListMap_[lists[i]] = true;
429 presetCategoryInner_();
435 * Constructs the thumbnails grid.
437 WallpaperManager.prototype.initThumbnailsGrid_ = function() {
438 this.wallpaperGrid_ = $('wallpaper-grid');
439 wallpapers.WallpaperThumbnailsGrid.decorate(this.wallpaperGrid_);
440 this.wallpaperGrid_.autoExpands = true;
442 this.wallpaperGrid_.addEventListener('change', this.onChange_.bind(this));
443 this.wallpaperGrid_.addEventListener('dblclick', this.onClose_.bind(this));
447 * Handles change event dispatched by wallpaper grid.
449 WallpaperManager.prototype.onChange_ = function() {
450 // splice may dispatch a change event because the position of selected
451 // element changing. But the actual selected element may not change after
452 // splice. Check if the new selected element equals to the previous selected
453 // element before continuing. Otherwise, wallpaper may reset to previous one
454 // as described in http://crbug.com/229036.
455 if (this.selectedItem_ == this.wallpaperGrid_.selectedItem)
457 this.selectedItem_ = this.wallpaperGrid_.selectedItem;
458 this.onSelectedItemChanged_();
462 * Closes window if no pending wallpaper request.
464 WallpaperManager.prototype.onClose_ = function() {
465 if (this.wallpaperRequest_) {
466 this.wallpaperRequest_.addEventListener('loadend', function() {
467 // Close window on wallpaper loading finished.
476 * Sets wallpaper to the corresponding wallpaper of selected thumbnail.
477 * @param {{baseURL: string, layout: string, source: string,
478 * availableOffline: boolean, opt_dynamicURL: string,
479 * opt_author: string, opt_authorWebsite: string}}
480 * selectedItem the selected item in WallpaperThumbnailsGrid's data
483 WallpaperManager.prototype.setSelectedWallpaper_ = function(selectedItem) {
485 switch (selectedItem.source) {
486 case Constants.WallpaperSourceEnum.Custom:
487 var errorHandler = this.onFileSystemError_.bind(this);
488 var setActive = function() {
489 self.wallpaperGrid_.activeItem = selectedItem;
490 self.currentWallpaper_ = selectedItem.baseURL;
492 var success = function(dirEntry) {
493 dirEntry.getFile(selectedItem.baseURL, {create: false},
494 function(fileEntry) {
495 fileEntry.file(function(file) {
496 var reader = new FileReader();
497 reader.readAsArrayBuffer(file);
498 reader.addEventListener('error', errorHandler);
499 reader.addEventListener('load', function(e) {
500 self.setCustomWallpaper(e.target.result,
502 false, selectedItem.baseURL,
503 setActive, errorHandler);
508 this.wallpaperDirs_.getDirectory(WallpaperDirNameEnum.ORIGINAL,
509 success, errorHandler);
511 case Constants.WallpaperSourceEnum.OEM:
512 // Resets back to default wallpaper.
513 chrome.wallpaperPrivate.resetWallpaper();
514 this.currentWallpaper_ = selectedItem.baseURL;
515 this.wallpaperGrid_.activeItem = selectedItem;
516 WallpaperUtil.saveWallpaperInfo(wallpaperURL, selectedItem.layout,
517 selectedItem.source);
519 case Constants.WallpaperSourceEnum.Online:
520 var wallpaperURL = selectedItem.baseURL +
521 Constants.HighResolutionSuffix;
522 var selectedGridItem = this.wallpaperGrid_.getListItem(selectedItem);
524 chrome.wallpaperPrivate.setWallpaperIfExists(wallpaperURL,
528 self.currentWallpaper_ = wallpaperURL;
529 self.wallpaperGrid_.activeItem = selectedItem;
530 WallpaperUtil.saveWallpaperInfo(wallpaperURL, selectedItem.layout,
531 selectedItem.source);
535 // Falls back to request wallpaper from server.
536 if (self.wallpaperRequest_)
537 self.wallpaperRequest_.abort();
539 self.wallpaperRequest_ = new XMLHttpRequest();
540 self.progressManager_.reset(self.wallpaperRequest_, selectedGridItem);
542 var onSuccess = function(xhr) {
543 var image = xhr.response;
544 chrome.wallpaperPrivate.setWallpaper(image, selectedItem.layout,
546 self.onFinished_.bind(self, selectedGridItem, selectedItem));
547 self.currentWallpaper_ = wallpaperURL;
548 WallpaperUtil.saveWallpaperInfo(wallpaperURL, selectedItem.layout,
549 selectedItem.source);
550 self.wallpaperRequest_ = null;
552 var onFailure = function() {
553 self.progressManager_.hideProgressBar(selectedGridItem);
554 self.showError_(str('downloadFailed'));
555 self.wallpaperRequest_ = null;
557 WallpaperUtil.fetchURL(wallpaperURL, 'arraybuffer', onSuccess,
558 onFailure, self.wallpaperRequest_);
562 console.error('Unsupported wallpaper source.');
567 * Removes the oldest custom wallpaper. If the oldest one is set as current
568 * wallpaper, removes the second oldest one to free some space. This should
569 * only be called when exceeding wallpaper quota.
571 WallpaperManager.prototype.removeOldestWallpaper_ = function() {
572 // Custom wallpapers should already sorted when put to the data model. The
573 // last element is the add new button, need to exclude it as well.
574 var oldestIndex = this.wallpaperGrid_.dataModel.length - 2;
575 var item = this.wallpaperGrid_.dataModel.item(oldestIndex);
576 if (!item || item.source != Constants.WallpaperSourceEnum.Custom)
578 if (item.baseURL == this.currentWallpaper_)
579 item = this.wallpaperGrid_.dataModel.item(--oldestIndex);
581 this.removeCustomWallpaper(item.baseURL);
582 this.wallpaperGrid_.dataModel.splice(oldestIndex, 1);
587 * Shows an error message to user and log the failed reason in console.
589 WallpaperManager.prototype.onFileSystemError_ = function(e) {
592 case FileError.QUOTA_EXCEEDED_ERR:
593 msg = 'QUOTA_EXCEEDED_ERR';
594 // Instead of simply remove oldest wallpaper, we should consider a
595 // better way to handle this situation. See crbug.com/180890.
596 this.removeOldestWallpaper_();
598 case FileError.NOT_FOUND_ERR:
599 msg = 'NOT_FOUND_ERR';
601 case FileError.SECURITY_ERR:
602 msg = 'SECURITY_ERR';
604 case FileError.INVALID_MODIFICATION_ERR:
605 msg = 'INVALID_MODIFICATION_ERR';
607 case FileError.INVALID_STATE_ERR:
608 msg = 'INVALID_STATE_ERR';
611 msg = 'Unknown Error';
614 console.error('Error: ' + msg);
615 this.showError_(str('accessFileFailure'));
619 * Handles changing of selectedItem in wallpaper manager.
621 WallpaperManager.prototype.onSelectedItemChanged_ = function() {
622 this.setWallpaperAttribution_(this.selectedItem_);
624 if (!this.selectedItem_ || this.selectedItem_.source == 'ADDNEW')
627 if (this.selectedItem_.baseURL && !this.wallpaperGrid_.inProgramSelection) {
628 if (this.selectedItem_.source == Constants.WallpaperSourceEnum.Custom) {
630 var key = this.selectedItem_.baseURL;
632 Constants.WallpaperLocalStorage.get(key, function(items) {
633 self.selectedItem_.layout =
634 items[key] ? items[key] : 'CENTER_CROPPED';
635 self.setSelectedWallpaper_(self.selectedItem_);
638 this.setSelectedWallpaper_(this.selectedItem_);
644 * Set attributions of wallpaper with given URL. If URL is not valid, clear
646 * @param {{baseURL: string, dynamicURL: string, layout: string,
647 * author: string, authorWebsite: string, availableOffline: boolean}}
648 * selectedItem selected wallpaper item in grid.
651 WallpaperManager.prototype.setWallpaperAttribution_ = function(selectedItem) {
652 // Only online wallpapers have author and website attributes. All other type
653 // of wallpapers should not show attributions.
655 selectedItem.source == Constants.WallpaperSourceEnum.Online) {
656 $('author-name').textContent = selectedItem.author;
657 $('author-website').textContent = $('author-website').href =
658 selectedItem.authorWebsite;
659 chrome.wallpaperPrivate.getThumbnail(selectedItem.baseURL,
662 var img = $('attribute-image');
664 var blob = new Blob([new Int8Array(data)], {'type' : 'image\/png'});
665 img.src = window.URL.createObjectURL(blob);
666 img.addEventListener('load', function(e) {
667 window.URL.revokeObjectURL(this.src);
673 $('wallpaper-attribute').hidden = false;
674 $('attribute-image').hidden = false;
677 $('wallpaper-attribute').hidden = true;
678 $('attribute-image').hidden = true;
679 $('author-name').textContent = '';
680 $('author-website').textContent = $('author-website').href = '';
681 $('attribute-image').src = '';
685 * Resize thumbnails grid and categories list to fit the new window size.
687 WallpaperManager.prototype.onResize_ = function() {
688 this.wallpaperGrid_.redraw();
689 this.categoriesList_.redraw();
693 * Close the last opened overlay on pressing the Escape key.
694 * @param {Event} event A keydown event.
696 WallpaperManager.prototype.onKeyDown_ = function(event) {
697 if (event.keyCode == 27) {
698 // The last opened overlay coincides with the first match of querySelector
699 // because the Error Container is declared in the DOM before the Wallpaper
700 // Selection Container.
701 // TODO(bshe): Make the overlay selection not dependent on the DOM.
702 var closeButtonSelector = '.overlay-container:not([hidden]) .close';
703 var closeButton = this.document_.querySelector(closeButtonSelector);
706 event.preventDefault();
712 * Constructs the categories list.
714 WallpaperManager.prototype.initCategoriesList_ = function() {
715 this.categoriesList_ = $('categories-list');
716 cr.ui.List.decorate(this.categoriesList_);
717 // cr.ui.list calculates items in view port based on client height and item
718 // height. However, categories list is displayed horizontally. So we should
719 // not calculate visible items here. Sets autoExpands to true to show every
721 // TODO(bshe): Use ul to replace cr.ui.list for category list.
722 this.categoriesList_.autoExpands = true;
725 this.categoriesList_.itemConstructor = function(entry) {
726 return self.renderCategory_(entry);
729 this.categoriesList_.selectionModel = new cr.ui.ListSingleSelectionModel();
730 this.categoriesList_.selectionModel.addEventListener(
731 'change', this.onCategoriesChange_.bind(this));
733 var categoriesDataModel = new cr.ui.ArrayDataModel([]);
734 if (this.enableOnlineWallpaper_) {
735 // Adds all category as first category.
736 categoriesDataModel.push(str('allCategoryLabel'));
737 for (var key in this.manifest_.categories) {
738 categoriesDataModel.push(this.manifest_.categories[key]);
741 // Adds custom category as last category.
742 categoriesDataModel.push(str('customCategoryLabel'));
743 this.categoriesList_.dataModel = categoriesDataModel;
747 * Constructs the element in categories list.
748 * @param {string} entry Text content of a category.
750 WallpaperManager.prototype.renderCategory_ = function(entry) {
751 var li = this.document_.createElement('li');
752 cr.defineProperty(li, 'custom', cr.PropertyKind.BOOL_ATTR);
753 li.custom = (entry == str('customCategoryLabel'));
754 cr.defineProperty(li, 'lead', cr.PropertyKind.BOOL_ATTR);
755 cr.defineProperty(li, 'selected', cr.PropertyKind.BOOL_ATTR);
756 var div = this.document_.createElement('div');
757 div.textContent = entry;
763 * Handles the custom wallpaper which user selected from file manager. Called
764 * when users select a file.
766 WallpaperManager.prototype.onFileSelectorChanged_ = function() {
767 var files = $('file-selector').files;
768 if (files.length != 1)
769 console.error('More than one files are selected or no file selected');
770 if (!files[0].type.match('image/jpeg') &&
771 !files[0].type.match('image/png')) {
772 this.showError_(str('invalidWallpaper'));
775 var layout = getSelectedLayout();
777 var errorHandler = this.onFileSystemError_.bind(this);
778 var setSelectedFile = function(file, layout, fileName) {
779 var saveThumbnail = function(thumbnail) {
780 var success = function(dirEntry) {
781 dirEntry.getFile(fileName, {create: true}, function(fileEntry) {
782 fileEntry.createWriter(function(fileWriter) {
783 fileWriter.onwriteend = function(e) {
784 $('set-wallpaper-layout').disabled = false;
785 var wallpaperInfo = {
788 source: Constants.WallpaperSourceEnum.Custom,
789 availableOffline: true
791 self.wallpaperGrid_.dataModel.splice(0, 0, wallpaperInfo);
792 self.wallpaperGrid_.selectedItem = wallpaperInfo;
793 self.wallpaperGrid_.activeItem = wallpaperInfo;
794 self.currentWallpaper_ = fileName;
795 WallpaperUtil.saveToStorage(self.currentWallpaper_, layout,
799 fileWriter.onerror = errorHandler;
801 var blob = new Blob([new Int8Array(thumbnail)],
802 {'type' : 'image\/jpeg'});
803 fileWriter.write(blob);
807 self.wallpaperDirs_.getDirectory(WallpaperDirNameEnum.THUMBNAIL,
808 success, errorHandler);
811 var success = function(dirEntry) {
812 dirEntry.getFile(fileName, {create: true}, function(fileEntry) {
813 fileEntry.createWriter(function(fileWriter) {
814 fileWriter.addEventListener('writeend', function(e) {
815 var reader = new FileReader();
816 reader.readAsArrayBuffer(file);
817 reader.addEventListener('error', errorHandler);
818 reader.addEventListener('load', function(e) {
819 self.setCustomWallpaper(e.target.result, layout, true, fileName,
820 saveThumbnail, function() {
821 self.removeCustomWallpaper(fileName);
827 fileWriter.addEventListener('error', errorHandler);
828 fileWriter.write(file);
832 self.wallpaperDirs_.getDirectory(WallpaperDirNameEnum.ORIGINAL, success,
835 setSelectedFile(files[0], layout, new Date().getTime().toString());
839 * Removes wallpaper and thumbnail with fileName from FileSystem.
840 * @param {string} fileName The file name of wallpaper and thumbnail to be
843 WallpaperManager.prototype.removeCustomWallpaper = function(fileName) {
844 var errorHandler = this.onFileSystemError_.bind(this);
846 var removeFile = function(fileName) {
847 var success = function(dirEntry) {
848 dirEntry.getFile(fileName, {create: false}, function(fileEntry) {
849 fileEntry.remove(function() {
854 // Removes copy of original.
855 self.wallpaperDirs_.getDirectory(WallpaperDirNameEnum.ORIGINAL, success,
858 // Removes generated thumbnail.
859 self.wallpaperDirs_.getDirectory(WallpaperDirNameEnum.THUMBNAIL, success,
862 removeFile(fileName);
866 * Sets current wallpaper and generate thumbnail if generateThumbnail is true.
867 * @param {ArrayBuffer} wallpaper The binary representation of wallpaper.
868 * @param {string} layout The user selected wallpaper layout.
869 * @param {boolean} generateThumbnail True if need to generate thumbnail.
870 * @param {string} fileName The unique file name of wallpaper.
871 * @param {function(thumbnail):void} success Success callback. If
872 * generateThumbnail is true, the callback parameter should have the
873 * generated thumbnail.
874 * @param {function(e):void} failure Failure callback. Called when there is an
875 * error from FileSystem.
877 WallpaperManager.prototype.setCustomWallpaper = function(wallpaper,
884 var onFinished = function(opt_thumbnail) {
885 if (chrome.runtime.lastError != undefined) {
886 self.showError_(chrome.runtime.lastError.message);
887 $('set-wallpaper-layout').disabled = true;
890 success(opt_thumbnail);
891 // Custom wallpapers are not synced yet. If login on a different
892 // computer after set a custom wallpaper, wallpaper wont change by sync.
893 WallpaperUtil.saveWallpaperInfo(fileName, layout,
894 Constants.WallpaperSourceEnum.Custom);
898 chrome.wallpaperPrivate.setCustomWallpaper(wallpaper, layout,
900 fileName, onFinished);
904 * Sets wallpaper finished. Displays error message if any.
905 * @param {WallpaperThumbnailsGridItem=} opt_selectedGridItem The wallpaper
906 * thumbnail grid item. It extends from cr.ui.ListItem.
907 * @param {{baseURL: string, layout: string, source: string,
908 * availableOffline: boolean, opt_dynamicURL: string,
909 * opt_author: string, opt_authorWebsite: string}=}
910 * opt_selectedItem the selected item in WallpaperThumbnailsGrid's data
913 WallpaperManager.prototype.onFinished_ = function(opt_selectedGridItem,
915 if (opt_selectedGridItem)
916 this.progressManager_.hideProgressBar(opt_selectedGridItem);
918 if (chrome.runtime.lastError != undefined) {
919 this.showError_(chrome.runtime.lastError.message);
920 } else if (opt_selectedItem) {
921 this.wallpaperGrid_.activeItem = opt_selectedItem;
926 * Handles the layout setting change of custom wallpaper.
928 WallpaperManager.prototype.onWallpaperLayoutChanged_ = function() {
929 var layout = getSelectedLayout();
931 chrome.wallpaperPrivate.setCustomWallpaperLayout(layout, function() {
932 if (chrome.runtime.lastError != undefined) {
933 self.showError_(chrome.runtime.lastError.message);
934 self.removeCustomWallpaper(fileName);
935 $('set-wallpaper-layout').disabled = true;
937 WallpaperUtil.saveToStorage(self.currentWallpaper_, layout, false);
943 * Handles user clicking on a different category.
945 WallpaperManager.prototype.onCategoriesChange_ = function() {
946 var categoriesList = this.categoriesList_;
947 var selectedIndex = categoriesList.selectionModel.selectedIndex;
948 if (selectedIndex == -1)
950 var selectedListItem = categoriesList.getListItemByIndex(selectedIndex);
952 bar.style.left = selectedListItem.offsetLeft + 'px';
953 bar.style.width = selectedListItem.offsetWidth + 'px';
955 var wallpapersDataModel = new cr.ui.ArrayDataModel([]);
957 if (selectedListItem.custom) {
958 this.document_.body.setAttribute('custom', '');
959 var errorHandler = this.onFileSystemError_.bind(this);
960 var toArray = function(list) {
961 return Array.prototype.slice.call(list || [], 0);
965 var processResults = function(entries) {
966 for (var i = 0; i < entries.length; i++) {
967 var entry = entries[i];
968 var wallpaperInfo = {
970 // The layout will be replaced by the actual value saved in
971 // local storage when requested later. Layout is not important
972 // for constructing thumbnails grid, we use CENTER_CROPPED here
973 // to speed up the process of constructing. So we do not need to
974 // wait for fetching correct layout.
975 layout: 'CENTER_CROPPED',
976 source: Constants.WallpaperSourceEnum.Custom,
977 availableOffline: true
979 wallpapersDataModel.push(wallpaperInfo);
981 if (loadTimeData.getBoolean('isOEMDefaultWallpaper')) {
982 var oemDefaultWallpaperElement = {
983 baseURL: 'OemDefaultWallpaper',
984 layout: 'CENTER_CROPPED',
985 source: Constants.WallpaperSourceEnum.OEM,
986 availableOffline: true
988 wallpapersDataModel.push(oemDefaultWallpaperElement);
990 for (var i = 0; i < wallpapersDataModel.length; i++) {
991 if (self.currentWallpaper_ == wallpapersDataModel.item(i).baseURL)
992 selectedItem = wallpapersDataModel.item(i);
997 source: Constants.WallpaperSourceEnum.AddNew,
998 availableOffline: true
1000 wallpapersDataModel.push(lastElement);
1001 self.wallpaperGrid_.dataModel = wallpapersDataModel;
1002 self.wallpaperGrid_.selectedItem = selectedItem;
1003 self.wallpaperGrid_.activeItem = selectedItem;
1006 var success = function(dirEntry) {
1007 var dirReader = dirEntry.createReader();
1009 // All of a directory's entries are not guaranteed to return in a single
1011 var readEntries = function() {
1012 dirReader.readEntries(function(results) {
1013 if (!results.length) {
1014 processResults(entries.sort());
1016 entries = entries.concat(toArray(results));
1021 readEntries(); // Start reading dirs.
1023 this.wallpaperDirs_.getDirectory(WallpaperDirNameEnum.ORIGINAL,
1024 success, errorHandler);
1026 this.document_.body.removeAttribute('custom');
1027 for (var key in this.manifest_.wallpaper_list) {
1028 if (selectedIndex == AllCategoryIndex ||
1029 this.manifest_.wallpaper_list[key].categories.indexOf(
1030 selectedIndex - OnlineCategoriesOffset) != -1) {
1031 var wallpaperInfo = {
1032 baseURL: this.manifest_.wallpaper_list[key].base_url,
1033 layout: this.manifest_.wallpaper_list[key].default_layout,
1034 source: Constants.WallpaperSourceEnum.Online,
1035 availableOffline: false,
1036 author: this.manifest_.wallpaper_list[key].author,
1037 authorWebsite: this.manifest_.wallpaper_list[key].author_website,
1038 dynamicURL: this.manifest_.wallpaper_list[key].dynamic_url
1040 var startIndex = wallpaperInfo.baseURL.lastIndexOf('/') + 1;
1041 var fileName = wallpaperInfo.baseURL.substring(startIndex) +
1042 Constants.HighResolutionSuffix;
1043 if (this.downloadedListMap_ &&
1044 this.downloadedListMap_.hasOwnProperty(encodeURI(fileName))) {
1045 wallpaperInfo.availableOffline = true;
1047 wallpapersDataModel.push(wallpaperInfo);
1048 var url = this.manifest_.wallpaper_list[key].base_url +
1049 Constants.HighResolutionSuffix;
1050 if (url == this.currentWallpaper_) {
1051 selectedItem = wallpaperInfo;
1055 this.wallpaperGrid_.dataModel = wallpapersDataModel;
1056 this.wallpaperGrid_.selectedItem = selectedItem;
1057 this.wallpaperGrid_.activeItem = selectedItem;