text: // Copyright (c) 2013 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  var wallpaperPickerWindow = null;
  
  var surpriseWallpaper = null;
  
  /**
   * Returns the highResolutionSuffix.
   * @param {function} callback A callback function that takes one value:
   *     |highResolutionSuffix|: the suffix to append to the wallpaper urls.
   */
  function getHighResolutionSuffix(callback) {
    chrome.wallpaperPrivate.getStrings(strings => {
      callback(strings['highResolutionSuffix']);
    });
  }
  
  function SurpriseWallpaper() {}
  
  /**
   * Gets SurpriseWallpaper instance. In case it hasn't been initialized, a new
   * instance is created.
   * @return {SurpriseWallpaper} A SurpriseWallpaper instance.
   */
  SurpriseWallpaper.getInstance = function() {
    if (!surpriseWallpaper)
      surpriseWallpaper = new SurpriseWallpaper();
    return surpriseWallpaper;
  };
  
  /**
   * Retries changing the wallpaper 1 hour later. This is called when fetching the
   * wallpaper from server fails.
   * @private
   */
  SurpriseWallpaper.prototype.retryLater_ = function() {
    chrome.alarms.create('RetryAlarm', {delayInMinutes: 60});
  };
  
  /**
   * Sets a new random wallpaper if one has not already been set today.
   * @private
   */
  SurpriseWallpaper.prototype.updateRandomWallpaper_ = function() {
    var self = this;
    var onSuccess = function(items) {
      var dateString = new Date().toDateString();
      // At most one random wallpaper per day.
      if (items[Constants.AccessLastSurpriseWallpaperChangedDate] != dateString) {
        self.setRandomWallpaper_(dateString);
      }
    };
    WallpaperUtil.enabledSyncThemesCallback(function(syncEnabled) {
      if (syncEnabled) {
        Constants.WallpaperSyncStorage.get(
            Constants.AccessLastSurpriseWallpaperChangedDate, onSuccess);
      } else {
        Constants.WallpaperLocalStorage.get(
            Constants.AccessLastSurpriseWallpaperChangedDate, onSuccess);
      }
    });
  };
  
  /**
   * Sets wallpaper to be a random one. The wallpaper url is retrieved either from
   * the stored manifest file, or by fetching the wallpaper info from the server.
   * @param {string} dateString String representation of current local date.
   * @private
   */
  SurpriseWallpaper.prototype.setRandomWallpaper_ = function(dateString) {
    var onSuccess = function(url, layout) {
      WallpaperUtil.saveWallpaperInfo(
          url, layout, Constants.WallpaperSourceEnum.Daily, '');
      WallpaperUtil.saveToLocalStorage(
          Constants.AccessLastSurpriseWallpaperChangedDate, dateString,
          function() {
            WallpaperUtil.saveToSyncStorage(
                Constants.AccessLastSurpriseWallpaperChangedDate, dateString);
          });
    };
  
    getHighResolutionSuffix(highResolutionSuffix => {
      this.setRandomWallpaperFromServer_(onSuccess, highResolutionSuffix);
    });
  };
  
  /**
   * Sets wallpaper to be a random one retrieved from the backend service. If the
   * wallpaper download fails, retry one hour later.
   * @param {function} onSuccess The success callback.
   * @param {string} suffix The url suffix for high resolution wallpaper.
   * @private
   */
  SurpriseWallpaper.prototype.setRandomWallpaperFromServer_ = function(
      onSuccess, suffix) {
    var onDailyRefreshInfoReturned = dailyRefreshInfo => {
      var setRandomWallpaperFromServerImpl = dailyRefreshInfo => {
        chrome.wallpaperPrivate.getSurpriseMeImage(
            dailyRefreshInfo.collectionId, dailyRefreshInfo.resumeToken,
            (imageInfo, nextResumeToken) => {
              if (chrome.runtime.lastError) {
                this.retryLater_();
                return;
              }
              dailyRefreshInfo.resumeToken = nextResumeToken;
              WallpaperUtil.saveDailyRefreshInfo(dailyRefreshInfo);
  
              var wallpaperUrl = imageInfo['imageUrl'] + suffix;
              var layout = Constants.WallpaperThumbnailDefaultLayout;
              WallpaperUtil.setOnlineWallpaperWithoutPreview(
                  wallpaperUrl, layout,
                  onSuccess.bind(null, wallpaperUrl, layout),
                  this.retryLater_.bind(this));
            });
      };
  
      if (dailyRefreshInfo) {
        if (dailyRefreshInfo.enabled) {
          setRandomWallpaperFromServerImpl(dailyRefreshInfo);
        } else {
          console.error(
              'Daily refresh is disabled when the alarm goes off. ' +
              'This should never happen!');
        }
        return;
      }
  
      // Migration: we reach here if the old picker set an alarm and by the time
      // the alarm goes off, the new picker is already in use. We should ensure
      // the user transitions to the daily refresh feature.
      chrome.wallpaperPrivate.getCollectionsInfo(collectionsInfo => {
        if (chrome.runtime.lastError) {
          this.retryLater_();
          return;
        }
        if (collectionsInfo.length == 0) {
          // Although the fetch succeeds, it's theoretically possible that the
          // collection list is empty, in this case do nothing.
          return;
        }
        dailyRefreshInfo = {
          enabled: true,
          // Use the first collection (an arbitrary choice).
          collectionId: collectionsInfo[0]['collectionId'],
          resumeToken: null
        };
        setRandomWallpaperFromServerImpl(dailyRefreshInfo);
      });
    };
  
    WallpaperUtil.getDailyRefreshInfo(onDailyRefreshInfoReturned.bind(null));
  };
  
  /**
   * Disables the wallpaper surprise me feature. Clear all alarms and states.
   */
  SurpriseWallpaper.prototype.disable = function() {
    chrome.alarms.clearAll();
    // Makes last changed date invalid.
    WallpaperUtil.saveToLocalStorage(
        Constants.AccessLastSurpriseWallpaperChangedDate, '', function() {
          WallpaperUtil.saveToSyncStorage(
              Constants.AccessLastSurpriseWallpaperChangedDate, '');
        });
  };
  
  /**
   * Changes current wallpaper and sets up an alarm to schedule next change around
   * midnight.
   */
  SurpriseWallpaper.prototype.next = function() {
    var nextUpdate = this.nextUpdateTime(new Date());
    chrome.alarms.create({when: nextUpdate});
    this.updateRandomWallpaper_();
  };
  
  /**
   * Calculates when the next wallpaper change should be triggered.
   * @param {Date} now Current time.
   * @return {number} The time when next wallpaper change should happen.
   */
  SurpriseWallpaper.prototype.nextUpdateTime = function(now) {
    var nextUpdate = new Date(now.setDate(now.getDate() + 1)).toDateString();
    return new Date(nextUpdate).getTime();
  };
  
  chrome.app.runtime.onLaunched.addListener(function() {
    if (wallpaperPickerWindow && !wallpaperPickerWindow.contentWindow.closed) {
      wallpaperPickerWindow.focus();
      chrome.wallpaperPrivate.minimizeInactiveWindows();
      return;
    }
  
    var options = {
      frame: 'none',
      innerBounds: {width: 768, height: 512, minWidth: 768, minHeight: 512},
      resizable: true,
      alphaEnabled: true
    };
  
    chrome.app.window.create('main.html', options, function(window) {
      wallpaperPickerWindow = window;
      chrome.wallpaperPrivate.minimizeInactiveWindows();
      window.onClosed.addListener(function() {
        wallpaperPickerWindow = null;
        const isDuringPreview =
            window.contentWindow.document.body.classList.contains('preview-mode');
        const isWallpaperSet =
            window.contentWindow.document.body.classList.contains(
                'wallpaper-set-successfully');
        // Cancel preview if the app exits before user confirming the
        // wallpaper (e.g. when the app is closed in overview mode).
        if (isDuringPreview && !isWallpaperSet)
          chrome.wallpaperPrivate.cancelPreviewWallpaper(() => {});
        // Do not restore the minimized windows if the app exits because of
        // confirming preview: prefer to continue showing the new wallpaper to
        // user.
        const isExitingAfterPreviewConfirm = isDuringPreview && isWallpaperSet;
        if (!isExitingAfterPreviewConfirm)
          chrome.wallpaperPrivate.restoreMinimizedWindows();
      });
      // By design, the wallpaper picker should never be shown on top of
      // another window.
      wallpaperPickerWindow.contentWindow.addEventListener('focus', function() {
        chrome.wallpaperPrivate.minimizeInactiveWindows();
      });
      WallpaperUtil.testSendMessage('wallpaper-window-created');
    });
  });
  
  chrome.syncFileSystem.onFileStatusChanged.addListener(function(detail) {
    WallpaperUtil.enabledSyncThemesCallback(function(syncEnabled) {
      if (!syncEnabled)
        return;
      if (detail.status != 'synced' || detail.direction != 'remote_to_local')
        return;
      if (detail.action == 'added') {
        // TODO(xdai): Get rid of this setCustomWallpaperFromSyncFS logic.
        // WallpaperInfo might have been saved in the sync filesystem before the
        // corresonding custom wallpaper and thumbnail are saved, thus the
        // onChanged() might not set the custom wallpaper correctly. So we need
        // setCustomWallpaperFromSyncFS() to be called here again to make sure
        // custom wallpaper is set.
        Constants.WallpaperLocalStorage.get(
            Constants.AccessLocalWallpaperInfoKey, function(items) {
              var localData = items[Constants.AccessLocalWallpaperInfoKey];
              if (localData && localData.url == detail.fileEntry.name &&
                  localData.source == Constants.WallpaperSourceEnum.Custom) {
                WallpaperUtil.setCustomWallpaperFromSyncFS(
                    localData.url, localData.layout);
              }
            });
        // We only need to store the custom wallpaper if it was set by the
        // built-in wallpaper picker.
        if (!detail.fileEntry.name.startsWith(
                Constants.ThirdPartyWallpaperPrefix)) {
          WallpaperUtil.storeWallpaperFromSyncFSToLocalFS(detail.fileEntry);
        }
      } else if (detail.action == 'deleted') {
        var fileName = detail.fileEntry.name.replace(
            Constants.CustomWallpaperThumbnailSuffix, '');
        WallpaperUtil.deleteWallpaperFromLocalFS(fileName);
      }
    });
  });
  
  chrome.storage.onChanged.addListener(function(changes, namespace) {
    WallpaperUtil.enabledSyncThemesCallback(function(syncEnabled) {
      var updateDailyRefreshStates = key => {
        if (!changes[key])
          return;
  
        // If the user did not change Daily Refresh in this sync update,
        // changes[key].oldValue will be empty
        var oldDailyRefreshInfo =
            changes[key].oldValue ? JSON.parse(changes[key].oldValue) : '';
        var newDailyRefreshInfo = JSON.parse(changes[key].newValue);
  
        // The resume token is expected to change after a new daily refresh
        // wallpaper is set. Ignore it if it's the only change.
        if (oldDailyRefreshInfo.enabled === newDailyRefreshInfo.enabled &&
            oldDailyRefreshInfo.collectionId ===
                newDailyRefreshInfo.collectionId) {
          return;
        }
        // Although the old and new values may both have enabled == true, they can
        // have different collection ids, so the old alarm should always be
        // cleared.
        chrome.alarms.clearAll();
        if (newDailyRefreshInfo.enabled)
          SurpriseWallpaper.getInstance().next();
      };
      updateDailyRefreshStates(
          syncEnabled ? Constants.AccessSyncDailyRefreshInfoKey :
                        Constants.AccessLocalDailyRefreshInfoKey);
  
      if (syncEnabled) {
        // If sync theme is enabled, use values from chrome.storage.sync to sync
        // wallpaper changes.
        if (changes[Constants.AccessSyncSurpriseMeEnabledKey]) {
          if (changes[Constants.AccessSyncSurpriseMeEnabledKey].newValue) {
            SurpriseWallpaper.getInstance().next();
          } else {
            SurpriseWallpaper.getInstance().disable();
          }
        }
  
        // If the built-in Wallpaper Picker App is open, update the check mark
        // and the corresponding message in time.
        var updateCheckMarkAndAppNameIfAppliable = function(appName) {
          if (!wallpaperPickerWindow)
            return;
          var wpDocument = wallpaperPickerWindow.contentWindow.document;
          var messageContent = wpDocument.querySelector('#message-content');
  
          chrome.wallpaperPrivate.getStrings(strings => {
            if (appName) {
              wpDocument.querySelector('#message-container').display = 'block';
              var message =
                  strings.currentWallpaperSetByMessage.replace(/\$1/g, appName);
              messageContent.textContent = message;
              wpDocument.querySelector('#checkbox').classList.remove('checked');
              wpDocument.querySelector('#categories-list').disabled = false;
              wpDocument.querySelector('#wallpaper-grid').disabled = false;
            } else {
              Constants.WallpaperSyncStorage.get(
                  Constants.AccessSyncSurpriseMeEnabledKey, function(item) {
                    // TODO(crbug.com/810169): Try to combine this part with
                    // |WallpaperManager.onSurpriseMeStateChanged_|. The logic is
                    // duplicate.
                    var enable = item[Constants.AccessSyncSurpriseMeEnabledKey];
                    if (enable) {
                      wpDocument.querySelector('#checkbox')
                          .classList.add('checked');
                    } else {
                      wpDocument.querySelector('#checkbox')
                          .classList.remove('checked');
                      if (wpDocument.querySelector('.check'))
                        wpDocument.querySelector('.check').style.visibility =
                            'visible';
                    }
                  });
            }
          });
        };
  
        if (changes[Constants.AccessLocalWallpaperInfoKey]) {
          // If the old wallpaper is a third party wallpaper we should remove it
          // from the local & sync file system to free space.
          var oldInfo = changes[Constants.AccessLocalWallpaperInfoKey].oldValue;
          if (oldInfo &&
              oldInfo.url.indexOf(Constants.ThirdPartyWallpaperPrefix) != -1) {
            WallpaperUtil.deleteWallpaperFromLocalFS(oldInfo.url);
            WallpaperUtil.deleteWallpaperFromSyncFS(oldInfo.url);
          }
  
          var newInfo = changes[Constants.AccessLocalWallpaperInfoKey].newValue;
          if (newInfo && newInfo.hasOwnProperty('appName'))
            updateCheckMarkAndAppNameIfAppliable(newInfo.appName);
        }
  
        if (changes[Constants.AccessSyncWallpaperInfoKey]) {
          var syncInfo = changes[Constants.AccessSyncWallpaperInfoKey].newValue;
  
          Constants.WallpaperSyncStorage.get(
              Constants.AccessSyncSurpriseMeEnabledKey, function(enabledItems) {
                var syncSurpriseMeEnabled =
                    enabledItems[Constants.AccessSyncSurpriseMeEnabledKey];
  
                Constants.WallpaperSyncStorage.get(
                    Constants.AccessLastSurpriseWallpaperChangedDate,
                    function(items) {
                      var syncLastSurpriseMeChangedDate =
                          items[Constants.AccessLastSurpriseWallpaperChangedDate];
  
                      var today = new Date().toDateString();
                      // If SurpriseMe is enabled and surprise wallpaper hasn't
                      // been changed today, we should not sync the change,
                      // instead onAlarm() will be triggered to update a surprise
                      // me wallpaper.
                      if (!syncSurpriseMeEnabled ||
                          (syncSurpriseMeEnabled &&
                           syncLastSurpriseMeChangedDate == today)) {
                        Constants.WallpaperLocalStorage.get(
                            Constants.AccessLocalWallpaperInfoKey,
                            function(infoItems) {
                              var localInfo =
                                  infoItems[Constants
                                                .AccessLocalWallpaperInfoKey];
                              // Normally, the wallpaper info saved in local
                              // storage and sync storage are the same. If the
                              // synced value changed by sync service, they may
                              // different. In that case, change wallpaper to the
                              // one saved in sync storage and update the local
                              // value.
                              if (!localInfo || localInfo.url != syncInfo.url ||
                                  localInfo.layout != syncInfo.layout ||
                                  localInfo.source != syncInfo.source) {
                                if (syncInfo.source ==
                                    Constants.WallpaperSourceEnum.Online) {
                                  // TODO(bshe): Consider schedule an alarm to set
                                  // online wallpaper later when failed. Note that
                                  // we need to cancel the retry if user set
                                  // another wallpaper before retry alarm invoked.
                                  WallpaperUtil.setOnlineWallpaperWithoutPreview(
                                      syncInfo.url, syncInfo.layout,
                                      function() {}, function() {});
                                } else if (
                                    syncInfo.source ==
                                    Constants.WallpaperSourceEnum.Custom) {
                                  WallpaperUtil.setCustomWallpaperFromSyncFS(
                                      syncInfo.url, syncInfo.layout);
                                } else if (
                                    syncInfo.source ==
                                    Constants.WallpaperSourceEnum.Default) {
                                  chrome.wallpaperPrivate.resetWallpaper();
                                }
  
                                // If the old wallpaper is a third party wallpaper
                                // we should remove it from the local & sync file
                                // system to free space.
                                if (localInfo &&
                                    localInfo.url.indexOf(
                                        Constants.ThirdPartyWallpaperPrefix) !=
                                        -1) {
                                  WallpaperUtil.deleteWallpaperFromLocalFS(
                                      localInfo.url);
                                  WallpaperUtil.deleteWallpaperFromSyncFS(
                                      localInfo.url);
                                }
  
                                if (syncInfo &&
                                    syncInfo.hasOwnProperty('appName'))
                                  updateCheckMarkAndAppNameIfAppliable(
                                      syncInfo.appName);
  
                                WallpaperUtil.saveToLocalStorage(
                                    Constants.AccessLocalWallpaperInfoKey,
                                    syncInfo);
                              }
                            });
                      }
                    });
              });
        }
      } else {
        // If sync theme is disabled, use values from chrome.storage.local to
        // track wallpaper changes.
        if (changes[Constants.AccessLocalSurpriseMeEnabledKey]) {
          if (changes[Constants.AccessLocalSurpriseMeEnabledKey].newValue) {
            SurpriseWallpaper.getInstance().next();
          } else {
            SurpriseWallpaper.getInstance().disable();
          }
        }
      }
    });
  });
  
  chrome.alarms.onAlarm.addListener(function() {
    SurpriseWallpaper.getInstance().next();
  });
  
  chrome.wallpaperPrivate.onWallpaperChangedBy3rdParty.addListener(function(
      wallpaper, thumbnail, layout, appName) {
    WallpaperUtil.saveToLocalStorage(
        Constants.AccessLocalSurpriseMeEnabledKey, false, function() {
          WallpaperUtil.saveToSyncStorage(
              Constants.AccessSyncSurpriseMeEnabledKey, false);
        });
  
    // Make third party wallpaper syncable through different devices.
    var fileName = Constants.ThirdPartyWallpaperPrefix + new Date().getTime();
    var thumbnailFileName = fileName + Constants.CustomWallpaperThumbnailSuffix;
    WallpaperUtil.storeWallpaperToSyncFS(fileName, wallpaper);
    WallpaperUtil.storeWallpaperToSyncFS(thumbnailFileName, thumbnail);
    WallpaperUtil.saveWallpaperInfo(
        fileName, layout, Constants.WallpaperSourceEnum.ThirdParty, appName);
  
      WallpaperUtil.saveDailyRefreshInfo(
          {enabled: false, collectionId: null, resumeToken: null});
  
      if (wallpaperPickerWindow) {
        var event = new CustomEvent(
            Constants.WallpaperChangedBy3rdParty,
            {detail: {wallpaperFileName: fileName}});
        wallpaperPickerWindow.contentWindow.dispatchEvent(event);
      }
  });
  
  chrome.wallpaperPrivate.onClosePreviewWallpaper.addListener(function() {
    if (wallpaperPickerWindow) {
      var event = new CustomEvent(Constants.ClosePreviewWallpaper);
      wallpaperPickerWindow.contentWindow.dispatchEvent(event);
    }
  });
  