diff --git a/core/modules/update/lib/Drupal/update/UpdateParser.php b/core/modules/update/lib/Drupal/update/UpdateParser.php
new file mode 100644
index 0000000..0f89e7f
--- /dev/null
+++ b/core/modules/update/lib/Drupal/update/UpdateParser.php
@@ -0,0 +1,488 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\update\UpdateParser.
+ */
+
+namespace Drupal\update;
+
+use Drupal\Core\StringTranslation\TranslationInterface;
+
+class UpdateParser implements UpdateParserInterface {
+
+  /**
+   * The translation interface
+   *
+   * @var \Drupal\Core\StringTranslation\TranslationInterface
+   */
+  protected $translation;
+
+
+  /**
+   * Constructs an UpdateParser.
+   *
+   * @param \Drupal\Core\StringTranslation\TranslationInterface $translation
+   *   The translation service.
+   */
+  public function __construct(TranslationInterface $translation) {
+    $this->translation = $translation;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function parseXml($raw_xml) {
+    try {
+      $xml = new \SimpleXMLElement($raw_xml);
+    }
+    catch (\Exception $e) {
+      // SimpleXMLElement::__construct produces an E_WARNING error message for
+      // each error found in the XML data and throws an exception if errors
+      // were detected. Catch any exception and return failure (NULL).
+      return NULL;
+    }
+    // If there is no valid project data, the XML is invalid, so return failure.
+    if (!isset($xml->short_name)) {
+      return NULL;
+    }
+    $data = array();
+    foreach ($xml as $k => $v) {
+      $data[$k] = (string) $v;
+    }
+    $data['releases'] = array();
+    if (isset($xml->releases)) {
+      foreach ($xml->releases->children() as $release) {
+        $version = (string) $release->version;
+        $data['releases'][$version] = array();
+        foreach ($release->children() as $k => $v) {
+          $data['releases'][$version][$k] = (string) $v;
+        }
+        $data['releases'][$version]['terms'] = array();
+        if ($release->terms) {
+          foreach ($release->terms->children() as $term) {
+            if (!isset($data['releases'][$version]['terms'][(string) $term->name])) {
+              $data['releases'][$version]['terms'][(string) $term->name] = array();
+            }
+            $data['releases'][$version]['terms'][(string) $term->name][] = (string) $term->value;
+          }
+        }
+      }
+    }
+    return $data;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function calculateProjectUpdateStatus(array &$project_data, array $available) {
+    foreach (array('title', 'link') as $attribute) {
+      if (!isset($project_data[$attribute]) && isset($available[$attribute])) {
+        $project_data[$attribute] = $available[$attribute];
+      }
+    }
+
+    // If the project status is marked as something bad, there's nothing else
+    // to consider.
+    if (isset($available['project_status'])) {
+      switch ($available['project_status']) {
+        case 'insecure':
+          $project_data['status'] = UPDATE_NOT_SECURE;
+          if (empty($project_data['extra'])) {
+            $project_data['extra'] = array();
+          }
+          $project_data['extra'][] = array(
+            'class' => array('project-not-secure'),
+            'label' => $this->t('Project not secure'),
+            'data' => $this->t('This project has been labeled insecure by the Drupal security team, and is no longer available for download. Immediately disabling everything included by this project is strongly recommended!'),
+          );
+          break;
+        case 'unpublished':
+        case 'revoked':
+          $project_data['status'] = UPDATE_REVOKED;
+          if (empty($project_data['extra'])) {
+            $project_data['extra'] = array();
+          }
+          $project_data['extra'][] = array(
+            'class' => array('project-revoked'),
+            'label' => $this->t('Project revoked'),
+            'data' => $this->t('This project has been revoked, and is no longer available for download. Disabling everything included by this project is strongly recommended!'),
+          );
+          break;
+        case 'unsupported':
+          $project_data['status'] = UPDATE_NOT_SUPPORTED;
+          if (empty($project_data['extra'])) {
+            $project_data['extra'] = array();
+          }
+          $project_data['extra'][] = array(
+            'class' => array('project-not-supported'),
+            'label' => $this->t('Project not supported'),
+            'data' => $this->t('This project is no longer supported, and is no longer available for download. Disabling everything included by this project is strongly recommended!'),
+          );
+          break;
+        case 'not-fetched':
+          $project_data['status'] = UPDATE_NOT_FETCHED;
+          $project_data['reason'] = $this->t('Failed to get available update data.');
+          break;
+
+        default:
+          // Assume anything else (e.g. 'published') is valid and we should
+          // perform the rest of the logic in this function.
+          break;
+      }
+    }
+
+    if (!empty($project_data['status'])) {
+      // We already know the status for this project, so there's nothing else to
+      // compute. Record the project status into $project_data and we're done.
+      $project_data['project_status'] = $available['project_status'];
+      return;
+    }
+
+    // Figure out the target major version.
+    $existing_major = $project_data['existing_major'];
+    $supported_majors = array();
+    if (isset($available['supported_majors'])) {
+      $supported_majors = explode(',', $available['supported_majors']);
+    }
+    elseif (isset($available['default_major'])) {
+      // Older release history XML file without supported or recommended.
+      $supported_majors[] = $available['default_major'];
+    }
+
+    if (in_array($existing_major, $supported_majors)) {
+      // Still supported, stay at the current major version.
+      $target_major = $existing_major;
+    }
+    elseif (isset($available['recommended_major'])) {
+      // Since 'recommended_major' is defined, we know this is the new XML
+      // format. Therefore, we know the current release is unsupported since
+      // its major version was not in the 'supported_majors' list. We should
+      // find the best release from the recommended major version.
+      $target_major = $available['recommended_major'];
+      $project_data['status'] = UPDATE_NOT_SUPPORTED;
+    }
+    elseif (isset($available['default_major'])) {
+      // Older release history XML file without recommended, so recommend
+      // the currently defined "default_major" version.
+      $target_major = $available['default_major'];
+    }
+    else {
+      // Malformed XML file? Stick with the current version.
+      $target_major = $existing_major;
+    }
+
+    // Make sure we never tell the admin to downgrade. If we recommended an
+    // earlier version than the one they're running, they'd face an
+    // impossible data migration problem, since Drupal never supports a DB
+    // downgrade path. In the unfortunate case that what they're running is
+    // unsupported, and there's nothing newer for them to upgrade to, we
+    // can't print out a "Recommended version", but just have to tell them
+    // what they have is unsupported and let them figure it out.
+    $target_major = max($existing_major, $target_major);
+
+    $release_patch_changed = '';
+    $patch = '';
+
+    // If the project is marked as UPDATE_FETCH_PENDING, it means that the
+    // data we currently have (if any) is stale, and we've got a task queued
+    // up to (re)fetch the data. In that case, we mark it as such, merge in
+    // whatever data we have (e.g. project title and link), and move on.
+    if (!empty($available['fetch_status']) && $available['fetch_status'] == UPDATE_FETCH_PENDING) {
+      $project_data['status'] = UPDATE_FETCH_PENDING;
+      $project_data['reason'] = $this->t('No available update data');
+      $project_data['fetch_status'] = $available['fetch_status'];
+      return;
+    }
+
+    // Defend ourselves from XML history files that contain no releases.
+    if (empty($available['releases'])) {
+      $project_data['status'] = UPDATE_UNKNOWN;
+      $project_data['reason'] = $this->t('No available releases found');
+      return;
+    }
+    foreach ($available['releases'] as $version => $release) {
+      // First, if this is the existing release, check a few conditions.
+      if ($project_data['existing_version'] === $version) {
+        if (isset($release['terms']['Release type']) &&
+          in_array('Insecure', $release['terms']['Release type'])) {
+          $project_data['status'] = UPDATE_NOT_SECURE;
+        }
+        elseif ($release['status'] == 'unpublished') {
+          $project_data['status'] = UPDATE_REVOKED;
+          if (empty($project_data['extra'])) {
+            $project_data['extra'] = array();
+          }
+          $project_data['extra'][] = array(
+            'class' => array('release-revoked'),
+            'label' => $this->t('Release revoked'),
+            'data' => $this->t('Your currently installed release has been revoked, and is no longer available for download. Disabling everything included in this release or upgrading is strongly recommended!'),
+          );
+        }
+        elseif (isset($release['terms']['Release type']) &&
+          in_array('Unsupported', $release['terms']['Release type'])) {
+          $project_data['status'] = UPDATE_NOT_SUPPORTED;
+          if (empty($project_data['extra'])) {
+            $project_data['extra'] = array();
+          }
+          $project_data['extra'][] = array(
+            'class' => array('release-not-supported'),
+            'label' => $this->t('Release not supported'),
+            'data' => $this->t('Your currently installed release is now unsupported, and is no longer available for download. Disabling everything included in this release or upgrading is strongly recommended!'),
+          );
+        }
+      }
+
+      // Otherwise, ignore unpublished, insecure, or unsupported releases.
+      if ($release['status'] == 'unpublished' ||
+        (isset($release['terms']['Release type']) &&
+          (in_array('Insecure', $release['terms']['Release type']) ||
+            in_array('Unsupported', $release['terms']['Release type'])))) {
+        continue;
+      }
+
+      // See if this is a higher major version than our target and yet still
+      // supported. If so, record it as an "Also available" release.
+      // Note: Some projects have a HEAD release from CVS days, which could
+      // be one of those being compared. They would not have version_major
+      // set, so we must call isset first.
+      if (isset($release['version_major']) && $release['version_major'] > $target_major) {
+        if (in_array($release['version_major'], $supported_majors)) {
+          if (!isset($project_data['also'])) {
+            $project_data['also'] = array();
+          }
+          if (!isset($project_data['also'][$release['version_major']])) {
+            $project_data['also'][$release['version_major']] = $version;
+            $project_data['releases'][$version] = $release;
+          }
+        }
+        // Otherwise, this release can't matter to us, since it's neither
+        // from the release series we're currently using nor the recommended
+        // release. We don't even care about security updates for this
+        // branch, since if a project maintainer puts out a security release
+        // at a higher major version and not at the lower major version,
+        // they must remove the lower version from the supported major
+        // versions at the same time, in which case we won't hit this code.
+        continue;
+      }
+
+      // Look for the 'latest version' if we haven't found it yet. Latest is
+      // defined as the most recent version for the target major version.
+      if (!isset($project_data['latest_version'])
+        && $release['version_major'] == $target_major) {
+        $project_data['latest_version'] = $version;
+        $project_data['releases'][$version] = $release;
+      }
+
+      // Look for the development snapshot release for this branch.
+      if (!isset($project_data['dev_version'])
+        && $release['version_major'] == $target_major
+        && isset($release['version_extra'])
+        && $release['version_extra'] == 'dev') {
+        $project_data['dev_version'] = $version;
+        $project_data['releases'][$version] = $release;
+      }
+
+      // Look for the 'recommended' version if we haven't found it yet (see
+      // phpdoc at the top of this function for the definition).
+      if (!isset($project_data['recommended'])
+        && $release['version_major'] == $target_major
+        && isset($release['version_patch'])) {
+        if ($patch != $release['version_patch']) {
+          $patch = $release['version_patch'];
+          $release_patch_changed = $release;
+        }
+        if (empty($release['version_extra']) && $patch == $release['version_patch']) {
+          $project_data['recommended'] = $release_patch_changed['version'];
+          $project_data['releases'][$release_patch_changed['version']] = $release_patch_changed;
+        }
+      }
+
+      // Stop searching once we hit the currently installed version.
+      if ($project_data['existing_version'] === $version) {
+        break;
+      }
+
+      // If we're running a dev snapshot and have a timestamp, stop
+      // searching for security updates once we hit an official release
+      // older than what we've got. Allow 100 seconds of leeway to handle
+      // differences between the datestamp in the .info.yml file and the
+      // timestamp of the tarball itself (which are usually off by 1 or 2
+      // seconds) so that we don't flag that as a new release.
+      if ($project_data['install_type'] == 'dev') {
+        if (empty($project_data['datestamp'])) {
+          // We don't have current timestamp info, so we can't know.
+          continue;
+        }
+        elseif (isset($release['date']) && ($project_data['datestamp'] + 100 > $release['date'])) {
+          // We're newer than this, so we can skip it.
+          continue;
+        }
+      }
+
+      // See if this release is a security update.
+      if (isset($release['terms']['Release type'])
+        && in_array('Security update', $release['terms']['Release type'])) {
+        $project_data['security updates'][] = $release;
+      }
+    }
+
+    // If we were unable to find a recommended version, then make the latest
+    // version the recommended version if possible.
+    if (!isset($project_data['recommended']) && isset($project_data['latest_version'])) {
+      $project_data['recommended'] = $project_data['latest_version'];
+    }
+
+    //
+    // Check to see if we need an update or not.
+    //
+
+    if (!empty($project_data['security updates'])) {
+      // If we found security updates, that always trumps any other status.
+      $project_data['status'] = UPDATE_NOT_SECURE;
+    }
+
+    if (isset($project_data['status'])) {
+      // If we already know the status, we're done.
+      return;
+    }
+
+    // If we don't know what to recommend, there's nothing we can report.
+    // Bail out early.
+    if (!isset($project_data['recommended'])) {
+      $project_data['status'] = UPDATE_UNKNOWN;
+      $project_data['reason'] = $this->t('No available releases found');
+      return;
+    }
+
+    // If we're running a dev snapshot, compare the date of the dev snapshot
+    // with the latest official version, and record the absolute latest in
+    // 'latest_dev' so we can correctly decide if there's a newer release
+    // than our current snapshot.
+    if ($project_data['install_type'] == 'dev') {
+      if (isset($project_data['dev_version']) && $available['releases'][$project_data['dev_version']]['date'] > $available['releases'][$project_data['latest_version']]['date']) {
+        $project_data['latest_dev'] = $project_data['dev_version'];
+      }
+      else {
+        $project_data['latest_dev'] = $project_data['latest_version'];
+      }
+    }
+
+    // Figure out the status, based on what we've seen and the install type.
+    switch ($project_data['install_type']) {
+      case 'official':
+        if ($project_data['existing_version'] === $project_data['recommended'] || $project_data['existing_version'] === $project_data['latest_version']) {
+          $project_data['status'] = UPDATE_CURRENT;
+        }
+        else {
+          $project_data['status'] = UPDATE_NOT_CURRENT;
+        }
+        break;
+
+      case 'dev':
+        $latest = $available['releases'][$project_data['latest_dev']];
+        if (empty($project_data['datestamp'])) {
+          $project_data['status'] = UPDATE_NOT_CHECKED;
+          $project_data['reason'] = $this->t('Unknown release date');
+        }
+        elseif (($project_data['datestamp'] + 100 > $latest['date'])) {
+          $project_data['status'] = UPDATE_CURRENT;
+        }
+        else {
+          $project_data['status'] = UPDATE_NOT_CURRENT;
+        }
+        break;
+
+      default:
+        $project_data['status'] = UPDATE_UNKNOWN;
+        $project_data['reason'] = $this->t('Invalid info');
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  function calculateAllProjectUpdateStatus(array $projects, array $available) {
+
+    $this->processProjectInfo($projects);
+    foreach ($projects as $project => $project_info) {
+      if (isset($available[$project])) {
+        $this->calculateProjectUpdateStatus($projects[$project], $available[$project]);
+      }
+      else {
+        $projects[$project]['status'] = UPDATE_UNKNOWN;
+        $projects[$project]['reason'] = t('No available releases found');
+      }
+    }
+    return $projects;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processProjectInfo(array &$projects) {
+    foreach ($projects as $key => $project) {
+      // Assume an official release until we see otherwise.
+      $install_type = 'official';
+
+      $info = $project['info'];
+
+      if (isset($info['version'])) {
+        // Check for development snapshots
+        if (preg_match('@(dev|HEAD)@', $info['version'])) {
+          $install_type = 'dev';
+        }
+
+        // Figure out what the currently installed major version is. We need
+        // to handle both contribution (e.g. "5.x-1.3", major = 1) and core
+        // (e.g. "5.1", major = 5) version strings.
+        $matches = array();
+        if (preg_match('/^(\d+\.x-)?(\d+)\..*$/', $info['version'], $matches)) {
+          $info['major'] = $matches[2];
+        }
+        elseif (!isset($info['major'])) {
+          // This would only happen for version strings that don't follow the
+          // drupal.org convention. We let contribs define "major" in their
+          // .info.yml in this case, and only if that's missing would we hit this.
+          $info['major'] = -1;
+        }
+      }
+      else {
+        // No version info available at all.
+        $install_type = 'unknown';
+        $info['version'] = t('Unknown');
+        $info['major'] = -1;
+      }
+
+      // Finally, save the results we care about into the $projects array.
+      $projects[$key]['existing_version'] = $info['version'];
+      $projects[$key]['existing_major'] = $info['major'];
+      $projects[$key]['install_type'] = $install_type;
+    }
+  }
+
+  /**
+   * Utility wrapper for translate function.
+   *
+   * @param string $string
+   *   A string containing the English string to translate.
+   * @param array $args
+   *   An associative array of replacements to make after translation. Based
+   *   on the first character of the key, the value is escaped and/or themed.
+   *   See \Drupal\Component\Utility\String::format() for details.
+   * @param array $options
+   *   An associative array of additional options, with the following elements:
+   *   - 'langcode': The language code to translate to a language other than
+   *      what is used to display the page.
+   *   - 'context': The context the source string belongs to.
+   *
+   * @return string
+   *   The translated string.
+   *
+   * @see \Drupal\Core\StringTranslation\TranslationInterface::translate();
+   */
+  private function t($string, array $args = array(), array $options = array()) {
+    return $this->translation->translate($string, $args, $options);
+  }
+
+}
\ No newline at end of file
diff --git a/core/modules/update/lib/Drupal/update/UpdateParserInterface.php b/core/modules/update/lib/Drupal/update/UpdateParserInterface.php
new file mode 100644
index 0000000..1f130bd
--- /dev/null
+++ b/core/modules/update/lib/Drupal/update/UpdateParserInterface.php
@@ -0,0 +1,120 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\update\UpdateParserInterface.
+ */
+
+namespace Drupal\update;
+
+
+interface UpdateParserInterface {
+
+  /**
+   * Parses the XML of the Drupal release history info files.
+   *
+   * @param string $raw_xml
+   *   A raw XML string of available release data for a given project.
+   *
+   * @return array
+   *   Array of parsed data about releases for a given project, or NULL if there
+   *   was an error parsing the string.
+   */
+  public function parseXml($raw_xml);
+
+  /**
+   * Calculates the current update status of a specific project.
+   *
+   * This function is the heart of the update status feature. For each project it
+   * is invoked with, it first checks if the project has been flagged with a
+   * special status like "unsupported" or "insecure", or if the project node
+   * itself has been unpublished. In any of those cases, the project is marked
+   * with an error and the next project is considered.
+   *
+   * If the project itself is valid, the function decides what major release
+   * series to consider. The project defines what the currently supported major
+   * versions are for each version of core, so the first step is to make sure the
+   * current version is still supported. If so, that's the target version. If the
+   * current version is unsupported, the project maintainer's recommended major
+   * version is used. There's also a check to make sure that this function never
+   * recommends an earlier release than the currently installed major version.
+   *
+   * Given a target major version, the available releases are scanned looking for
+   * the specific release to recommend (avoiding beta releases and development
+   * snapshots if possible). For the target major version, the highest patch level
+   * is found. If there is a release at that patch level with no extra ("beta",
+   * etc.), then the release at that patch level with the most recent release date
+   * is recommended. If every release at that patch level has extra (only betas),
+   * then the latest release from the previous patch level is recommended. For
+   * example:
+   *
+   * - 1.6-bugfix <-- recommended version because 1.6 already exists.
+   * - 1.6
+   *
+   * or
+   *
+   * - 1.6-beta
+   * - 1.5 <-- recommended version because no 1.6 exists.
+   * - 1.4
+   *
+   * Also, the latest release from the same major version is looked for, even beta
+   * releases, to display to the user as the "Latest version" option.
+   * Additionally, the latest official release from any higher major versions that
+   * have been released is searched for to provide a set of "Also available"
+   * options.
+   *
+   * Finally, and most importantly, the release history continues to be scanned
+   * until the currently installed release is reached, searching for anything
+   * marked as a security update. If any security updates have been found between
+   * the recommended release and the installed version, all of the releases that
+   * included a security fix are recorded so that the site administrator can be
+   * warned their site is insecure, and links pointing to the release notes for
+   * each security update can be included (which, in turn, will link to the
+   * official security announcements for each vulnerability).
+   *
+   * This function relies on the fact that the .xml release history data comes
+   * sorted based on major version and patch level, then finally by release date
+   * if there are multiple releases such as betas from the same major.patch
+   * version (e.g., 5.x-1.5-beta1, 5.x-1.5-beta2, and 5.x-1.5). Development
+   * snapshots for a given major version are always listed last.
+   *
+   * @param $project_data
+   *   An array containing information about a specific project.
+   * @param $available
+   *   Data about available project releases of a specific project.
+   */
+  function calculateProjectUpdateStatus(array &$project_data, array $available);
+
+  /**
+   * Calculates the current update status of all projects on the site.
+   *
+   * The results of this function are expensive to compute, especially on sites
+   * with lots of modules or themes, since it involves a lot of comparisons and
+   * other operations.
+   *
+   * @param array $available
+   *   Data about available project releases.
+   *
+   * @return
+   *   An array of installed projects with current update status information.
+   *
+   * @see update_get_available()
+   * @see update_get_projects()
+   * @see update_process_project_info()
+   * @see update_project_storage()
+   */
+  function calculateAllProjectUpdateStatus(array $projects, array $available);
+
+  /**
+   * Determines version and type information for currently installed projects.
+   *
+   * Processes the list of projects on the system to figure out the currently
+   * installed versions, and other information that is required before we can
+   * compare against the available releases to produce the status report.
+   *
+   * @param $projects
+   *   Array of project information from update_get_projects().
+   */
+  function processProjectInfo(array &$projects);
+
+}
\ No newline at end of file
diff --git a/core/modules/update/src/Controller/UpdateController.php b/core/modules/update/src/Controller/UpdateController.php
index 59ef8ce..f9c273d 100644
--- a/core/modules/update/src/Controller/UpdateController.php
+++ b/core/modules/update/src/Controller/UpdateController.php
@@ -7,7 +7,9 @@
 
 namespace Drupal\update\Controller;
 
+use Drupal\Core\StringTranslation\TranslationInterface;
 use Drupal\update\UpdateManagerInterface;
+use Drupal\update\UpdateProcessor;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Drupal\Core\Controller\ControllerBase;
 
@@ -24,13 +26,21 @@ class UpdateController extends ControllerBase {
   protected $updateManager;
 
   /**
+   * Update processor service.
+   *
+   * @var \Drupal\update\UpdateProcessorInterface
+   */
+  protected $updateProcessor;
+
+  /**
    * Constructs update status data.
    *
    * @param \Drupal\update\UpdateManagerInterface $update_manager
    *   Update Manager Service.
    */
-  public function __construct(UpdateManagerInterface $update_manager) {
+  public function __construct(UpdateManagerInterface $update_manager, UpdateProcessor $update_processor) {
     $this->updateManager = $update_manager;
+    $this->updateProcessor = $update_processor;
   }
 
   /**
@@ -38,7 +48,8 @@ public function __construct(UpdateManagerInterface $update_manager) {
    */
   public static function create(ContainerInterface $container) {
     return new static(
-      $container->get('update.manager')
+      $container->get('update.manager'),
+      $container->get('update.processor')
     );
   }
 
@@ -63,18 +74,64 @@ public function updateStatus() {
    * Manually checks the update status without the use of cron.
    */
   public function updateStatusManually() {
-    $this->updateManager->refreshUpdateData();
+    $this->updateProcessor->refreshUpdateData();
     $batch = array(
       'operations' => array(
-        array(array($this->updateManager, 'fetchDataBatch'), array()),
+        array(array($this, 'fetchDataUsingBatch'), array()),
       ),
       'finished' => 'update_fetch_data_finished',
-      'title' => t('Checking available update data'),
-      'progress_message' => t('Trying to check available update data ...'),
-      'error_message' => t('Error checking available update data.'),
+      'title' => $this->t('Checking available update data'),
+      'progress_message' => $this->t('Trying to check available update data ...'),
+      'error_message' => $this->t('Error checking available update data.'),
     );
     batch_set($batch);
     return batch_process('admin/reports/updates');
   }
 
+  /**
+   * Processes a step in batch for fetching available update data.
+   *
+   * @param array $context
+   *   Reference to an array used for Batch API storage.
+   */
+  public function fetchDataUsingBatch(&$context) {
+    if (empty($context['sandbox']['max'])) {
+      $context['finished'] = 0;
+      $context['sandbox']['max'] = $this->updateProcessor->getQueuedFetchTasksCount();
+      $context['sandbox']['progress'] = 0;
+      $context['message'] = t('Checking available update data ...');
+      $context['results']['updated'] = 0;
+      $context['results']['failures'] = 0;
+      $context['results']['processed'] = 0;
+    }
+
+    // Grab another item from the fetch queue.
+    for ($i = 0; $i < 5; $i++) {
+      if ($item = $this->updateProcessor->processNextFetchTask()) {
+        if ($item->success) {
+          $context['results']['updated']++;
+          $context['message'] = $this->t('Checked available update data for %title.', array('%title' => $item->data['info']['name']));
+        }
+        else {
+          $context['message'] = $this->t('Failed to check available update data for %title.', array('%title' => $item->data['info']['name']));
+          $context['results']['failures']++;
+        }
+        $context['sandbox']['progress']++;
+        $context['results']['processed']++;
+        $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
+      }
+      else {
+        // If the queue is currently empty, we're done. It's possible that
+        // another thread might have added new fetch tasks while we were
+        // processing this batch. In that case, the usual 'finished' math could
+        // get confused, since we'd end up processing more tasks that we thought
+        // we had when we started and initialized 'max' with numberOfItems(). By
+        // forcing 'finished' to be exactly 1 here, we ensure that batch
+        // processing is terminated.
+        $context['finished'] = 1;
+        return;
+      }
+    }
+  }
+
 }
diff --git a/core/modules/update/src/UpdateFetcher.php b/core/modules/update/src/UpdateFetcher.php
index 118d3a9..c56281b 100644
--- a/core/modules/update/src/UpdateFetcher.php
+++ b/core/modules/update/src/UpdateFetcher.php
@@ -32,13 +32,6 @@ class UpdateFetcher implements UpdateFetcherInterface {
   protected $fetchUrl;
 
   /**
-   * The update settings
-   *
-   * @var \Drupal\Core\Config\Config
-   */
-  protected $updateSettings;
-
-  /**
    * The HTTP client to fetch the feed data with.
    *
    * @var \GuzzleHttp\ClientInterface
@@ -56,7 +49,6 @@ class UpdateFetcher implements UpdateFetcherInterface {
   public function __construct(ConfigFactoryInterface $config_factory, ClientInterface $http_client) {
     $this->fetchUrl = $config_factory->get('update.settings')->get('fetch.url');
     $this->httpClient = $http_client;
-    $this->updateSettings = $config_factory->get('update.settings');
   }
 
   /**
@@ -81,7 +73,7 @@ public function fetchProjectData(array $project, $site_key = '') {
    */
   public function buildFetchUrl(array $project, $site_key = '') {
     $name = $project['name'];
-    $url = $this->getFetchBaseUrl($project);
+    $url = $this->getBaseUrl($project);
     $url .= '/' . $name . '/' . \Drupal::CORE_COMPATIBILITY;
 
     // Only append usage information if we have a site key and the project is
@@ -109,7 +101,7 @@ public function buildFetchUrl(array $project, $site_key = '') {
   /**
    * {@inheritdoc}
    */
-  public function getFetchBaseUrl($project) {
+  public function getBaseUrl($project) {
     if (isset($project['info']['project status url'])) {
       $url = $project['info']['project status url'];
     }
diff --git a/core/modules/update/src/UpdateFetcherInterface.php b/core/modules/update/src/UpdateFetcherInterface.php
index 461a4c8..6b9df93 100644
--- a/core/modules/update/src/UpdateFetcherInterface.php
+++ b/core/modules/update/src/UpdateFetcherInterface.php
@@ -23,7 +23,7 @@
    *   not include the path elements to specify a particular project, version,
    *   site_key, etc.
    */
-  public function getFetchBaseUrl($project);
+  public function getBaseUrl($project);
 
   /**
    * Retrieves the project information.
diff --git a/core/modules/update/src/UpdateManager.php b/core/modules/update/src/UpdateManager.php
index 007b040..7d39f4c 100644
--- a/core/modules/update/src/UpdateManager.php
+++ b/core/modules/update/src/UpdateManager.php
@@ -9,10 +9,9 @@
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\DependencyInjection\DependencySerializationTrait;
 use Drupal\Core\Extension\ModuleHandlerInterface;
-use Drupal\Core\Extension\ThemeHandlerInterface;
+use Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface;
 use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
-use Drupal\Core\StringTranslation\TranslationInterface;
-use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\KeyValueStore\StateInterface;
 use Drupal\Core\Utility\ProjectInfo;
 
 /**
@@ -30,6 +29,20 @@ class UpdateManager implements UpdateManagerInterface {
   protected $updateSettings;
 
   /**
+   * The key/value store.
+   *
+   * @var \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface
+   */
+  protected $updateTempStore;
+
+  /**
+   * Update available releases key/value store.
+   *
+   * @var \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface
+   */
+  protected $availableReleasesTempStore;
+
+  /**
    * Module Handler Service.
    *
    * @var \Drupal\Core\Extension\ModuleHandlerInterface
@@ -44,93 +57,68 @@ class UpdateManager implements UpdateManagerInterface {
   protected $updateProcessor;
 
   /**
-   * An array of installed and enabled projects.
+   * Update Parser Service.
    *
-   * @var array
+   * @var \Drupal\update\UpdateParserInterface
    */
-  protected $projects;
+  protected $updateParser;
 
   /**
-   * The key/value store.
+   * An array of installed and enabled projects.
    *
-   * @var \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface
+   * @var array
    */
-  protected $keyValueStore;
+  protected $projects;
 
   /**
-   * Update available releases key/value store.
+   * Update Fetch Task store
    *
-   * @var \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface
+   * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
    */
-  protected $availableReleasesTempStore;
+  protected $fetchTaskStore;
 
   /**
-   * The theme handler.
+   * The state service.
    *
-   * @var \Drupal\Core\Extension\ThemeHandlerInterface
+   * @var \Drupal\Core\KeyValueStore\StateInterface
    */
-  protected $themeHandler;
+  protected $stateStore;
 
   /**
    * Constructs a UpdateManager.
    *
-   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
-   *   The config factory.
+   * @param \Drupal\update\UpdateParserInterface
+   *   The update parser service
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The Module Handler service
-   * @param \Drupal\update\UpdateProcessorInterface $update_processor
-   *   The Update Processor service.
-   * @param \Drupal\Core\StringTranslation\TranslationInterface $translation
-   *   The translation service.
-   * @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $key_value_expirable_factory
-   *   The expirable key/value factory.
-   * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
-   *   The theme handler.
-   */
-  public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, UpdateProcessorInterface $update_processor, TranslationInterface $translation, KeyValueFactoryInterface $key_value_expirable_factory, ThemeHandlerInterface $theme_handler) {
-    $this->updateSettings = $config_factory->get('update.settings');
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The config factory
+   * @param \Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface $key_value_expirable_factory
+   *   The expirable key/value factory
+   * @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface
+   *   The key/value factory
+   * @param \Drupal\Core\KeyValueStore\StateInterface
+   *   The state service
+   */
+  public function __construct(UpdateParser $update_parser, ModuleHandlerInterface $module_handler, ConfigFactoryInterface $config_factory, KeyValueExpirableFactoryInterface $key_value_expirable_factory, KeyValueFactoryInterface $key_value_factory, StateInterface $state_store) {
     $this->moduleHandler = $module_handler;
-    $this->updateProcessor = $update_processor;
-    $this->stringTranslation = $translation;
-    $this->keyValueStore = $key_value_expirable_factory->get('update');
-    $this->themeHandler = $theme_handler;
+    $this->updateSettings = $config_factory->get('update.settings');
+    $this->updateTempStore = $key_value_expirable_factory->get('update');
     $this->availableReleasesTempStore = $key_value_expirable_factory->get('update_available_releases');
+    $this->fetchTaskStore = $key_value_factory->get('update_fetch_task');
+    $this->stateStore = $state_store;
+    $this->updateParser = $update_parser;
     $this->projects = array();
   }
 
   /**
    * {@inheritdoc}
    */
-  public function refreshUpdateData() {
-
-    // Since we're fetching new available update data, we want to clear
-    // of both the projects we care about, and the current update status of the
-    // site. We do *not* want to clear the cache of available releases just yet,
-    // since that data (even if it's stale) can be useful during
-    // \Drupal\Update\UpdateManager::getProjects(); for example, to modules
-    // that implement hook_system_info_alter() such as cvs_deploy.
-    $this->keyValueStore->delete('update_project_projects');
-    $this->keyValueStore->delete('update_project_data');
-
-    $projects = $this->getProjects();
 
-    // Now that we have the list of projects, we should also clear the available
-    // release data, since even if we fail to fetch new data, we need to clear
-    // out the stale data at this point.
-    $this->availableReleasesTempStore->deleteAll();
-
-    foreach ($projects as $project) {
-      $this->updateProcessor->createFetchTask($project);
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function getProjects() {
     if (empty($this->projects)) {
       // Retrieve the projects from storage, if present.
-      $this->projects = $this->projectStorage('update_project_projects');
+      $this->projects = $this->getProjectStorage('update_project_projects');
       if (empty($this->projects)) {
         // Still empty, so we have to rebuild.
         $module_data = system_rebuild_module_data();
@@ -145,7 +133,7 @@ public function getProjects() {
         // Allow other modules to alter projects before fetching and comparing.
         $this->moduleHandler->alter('update_projects', $this->projects);
         // Store the site's project data for at most 1 hour.
-        $this->keyValueStore->setWithExpire('update_project_projects', $this->projects, 3600);
+        $this->updateTempStore->setWithExpire('update_project_projects', $this->projects, 3600);
       }
     }
     return $this->projects;
@@ -154,7 +142,7 @@ public function getProjects() {
   /**
    * {@inheritdoc}
    */
-  public function projectStorage($key) {
+  public function getProjectStorage($key) {
     $projects = array();
 
     // On certain paths, we should clear the data and recompute the projects for
@@ -174,11 +162,11 @@ public function projectStorage($key) {
       'update.confirmation_page',
       'system.themes_page',
     );
-    if (in_array(\Drupal::routeMatch()->getRouteName(), $route_names)) {
-      $this->keyValueStore->delete($key);
+    if (in_array(current_path(), $paths)) {
+      $this->updateTempStore->delete($key);
     }
     else {
-      $projects = $this->keyValueStore->get($key, array());
+      $projects = $this->updateTempStore->get($key);
     }
     return $projects;
   }
@@ -186,45 +174,109 @@ public function projectStorage($key) {
   /**
    * {@inheritdoc}
    */
-  public function fetchDataBatch(&$context) {
-    if (empty($context['sandbox']['max'])) {
-      $context['finished'] = 0;
-      $context['sandbox']['max'] = $this->updateProcessor->numberOfQueueItems();
-      $context['sandbox']['progress'] = 0;
-      $context['message'] = $this->t('Checking available update data ...');
-      $context['results']['updated'] = 0;
-      $context['results']['failures'] = 0;
-      $context['results']['processed'] = 0;
-    }
+  public function getUpdateSettings() {
+    return $this->updateSettings;
+  }
 
-    // Grab another item from the fetch queue.
-    for ($i = 0; $i < 5; $i++) {
-      if ($item = $this->updateProcessor->claimQueueItem()) {
-        if ($this->updateProcessor->processFetchTask($item->data)) {
-          $context['results']['updated']++;
-          $context['message'] = $this->t('Checked available update data for %title.', array('%title' => $item->data['info']['name']));
-        }
-        else {
-          $context['message'] = $this->t('Failed to check available update data for %title.', array('%title' => $item->data['info']['name']));
-          $context['results']['failures']++;
-        }
-        $context['sandbox']['progress']++;
-        $context['results']['processed']++;
-        $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
-        $this->updateProcessor->deleteQueueItem($item);
+  /**
+   * {@inheritdoc}
+   */
+  public function getUpdateTempStore() {
+    return $this->updateTempStore;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAvailableReleasesTempStore() {
+    return $this->availableReleasesTempStore;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getUpdateFetchTaskStore() {
+    return $this->fetchTaskStore;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFetchUrl() {
+    return $this->updateSettings->get('fetch.url');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function cronNotify() {
+    module_load_install('update');
+    $status = update_requirements('runtime');
+    $params = array();
+    $notify_all = ($this->updateSettings->get('notification.threshold') == 'all');
+    foreach (array('core', 'contrib') as $report_type) {
+      $type = 'update_' . $report_type;
+      if (isset($status[$type]['severity'])
+        && ($status[$type]['severity'] == REQUIREMENT_ERROR || ($notify_all && $status[$type]['reason'] == UPDATE_NOT_CURRENT))) {
+        $params[$report_type] = $status[$type]['reason'];
       }
-      else {
-        // If the queue is currently empty, we're done. It's possible that
-        // another thread might have added new fetch tasks while we were
-        // processing this batch. In that case, the usual 'finished' math could
-        // get confused, since we'd end up processing more tasks that we thought
-        // we had when we started and initialized 'max' with numberOfItems(). By
-        // forcing 'finished' to be exactly 1 here, we ensure that batch
-        // processing is terminated.
-        $context['finished'] = 1;
-        return;
+    }
+    if (!empty($params)) {
+      $notify_list = $this->updateSettings->get('notification.emails');
+      if (!empty($notify_list)) {
+        $default_langcode = language_default()->id;
+        foreach ($notify_list as $target) {
+          if ($target_user = user_load_by_mail($target)) {
+            $target_langcode = $target_user->getPreferredLangcode();
+          }
+          else {
+            $target_langcode = $default_langcode;
+          }
+          $message = drupal_mail('update', 'status_notify', $target, $target_langcode, $params);
+          // Track when the last mail was successfully sent to avoid sending
+          // too many e-mails.
+          if ($message['result']) {
+            $this->stateStore->set('update.last_email_notification', REQUEST_TIME);
+          }
+        }
       }
     }
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getProjectsUpdateStatus(array $available) {
+    // Retrieve the projects from storage, if present.
+    $projects = $this->getProjectStorage('update_project_data');
+    // If $projects is empty, then the data must be rebuilt.
+    // Otherwise, return the data and skip the rest of the function.
+    if (!empty($projects)) {
+      return $projects;
+    }
+    $projects = $this->updateParser->calculateAllProjectUpdateStatus($this->getProjects(), $available);
+
+    // Give other modules a chance to alter the status (for example, to allow a
+    // contrib module to provide fine-grained settings to ignore specific
+    // projects or releases).
+    $this->moduleHandler->alter('update_status', $projects);
+
+    // Store the site's update status for at most 1 hour.
+    $this->updateTempStore->setWithExpire('update_project_data', $projects, 3600);
+    return $projects;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLastCheck() {
+    $this->stateStore->get('update.last_check');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setLastCheck($time) {
+    $this->stateStore->set('update.last_check', $time);
+  }
 }
diff --git a/core/modules/update/src/UpdateManagerInterface.php b/core/modules/update/src/UpdateManagerInterface.php
index f938bf7..680fce7 100644
--- a/core/modules/update/src/UpdateManagerInterface.php
+++ b/core/modules/update/src/UpdateManagerInterface.php
@@ -1,7 +1,7 @@
 <?php
 /**
  * @file
- * Contains  \Drupal\update\UpdateManagerInterface.
+ * Contains \Drupal\update\UpdateManagerInterface.
  */
 
 namespace Drupal\update;
@@ -58,24 +58,11 @@
    *
    * @see update_process_project_info()
    * @see update_calculate_project_data()
-   * @see \Drupal\update\UpdateManager::projectStorage()
+   * @see \Drupal\update\UpdateManager::getProjectStorage()
    */
   public function getProjects();
 
   /**
-   * Processes a step in batch for fetching available update data.
-   *
-   * @param array $context
-   *   Reference to an array used for Batch API storage.
-   */
-  public function fetchDataBatch(&$context);
-
-  /**
-   * Clears out all the available update data and initiates re-fetching.
-   */
-  public function refreshUpdateData();
-
-  /**
    * Retrieves update storage data or empties it.
    *
    * Two very expensive arrays computed by this module are the list of all
@@ -105,5 +92,86 @@ public function refreshUpdateData();
    *   storage is cleared.
    *   array when the storage is cleared.
    */
-  public function projectStorage($key);
+  public function getProjectStorage($key);
+
+  /**
+   * Calculates the current update status of all projects on the site.
+   *
+   * The results of this function are expensive to compute, especially on sites
+   * with lots of modules or themes, since it involves a lot of comparisons and
+   * other operations. Therefore, we store the results. However, since this is not
+   * the data about available updates fetched from the network, it is ok to
+   * invalidate it somewhat quickly. If we keep this data for very long, site
+   * administrators are more likely to see incorrect results if they upgrade to a
+   * newer version of a module or theme but do not visit certain pages that
+   * automatically clear this.
+   *
+   * @param array $available
+   *   Data about available project releases.
+   *
+   * @return
+   *   An array of installed projects with current update status information.
+   *
+   * @see update_get_available()
+   * @see update_get_projects()
+   * @see update_process_project_info()
+   * @see update_project_storage()
+   */
+  public function getProjectsUpdateStatus(array $available);
+
+  /**
+   * Retrieves the update settings.
+   *
+   * @return \Drupal\Core\Config\Config
+   */
+  public function getUpdateSettings();
+
+  /**
+   * Retrieves the update expirable key/value store.
+   *
+   * @return \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface
+   */
+  public function getUpdateTempStore();
+
+  /**
+   * Retrieves the update available releases key/value store.
+   *
+   * @return \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface
+   */
+  public function getAvailableReleasesTempStore();
+
+  /**
+   *  Retrieves the store of existing update fetch tasks
+   *
+   * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
+   */
+  public function getUpdateFetchTaskStore();
+
+  /**
+   * Retrieves the last time an update fetch was attempted
+   *
+   * @return int
+   *   The unix timestamp of the last time an update fetch was attempted
+   */
+  public function getLastCheck();
+
+  /**
+   * Sets the last time an update fetch was attempted
+   *
+   * @param $time
+   *   The unix timestamp of the last time an update fetch was attempted
+   */
+  public function setLastCheck($time);
+
+  /**
+   * Performs any notifications that should be done once cron fetches new data.
+   *
+   * This method checks the status of the site using the new data and, depending
+   * on the configuration of the site, notifies administrators via e-mail if there
+   * are new releases or missing security updates.
+   *
+   * @see update_requirements()
+   */
+  public function cronNotify();
+
 }
diff --git a/core/modules/update/src/UpdateProcessor.php b/core/modules/update/src/UpdateProcessor.php
index b5cf745..3832755 100644
--- a/core/modules/update/src/UpdateProcessor.php
+++ b/core/modules/update/src/UpdateProcessor.php
@@ -7,9 +7,6 @@
 namespace Drupal\update;
 
 use Drupal\Component\Utility\Crypt;
-use Drupal\Core\Config\ConfigFactoryInterface;
-use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
-use Drupal\Core\State\StateInterface;
 use Drupal\Core\PrivateKey;
 use Drupal\Core\Queue\QueueFactory;
 
@@ -26,6 +23,13 @@ class UpdateProcessor implements UpdateProcessorInterface {
   protected $updateSettings;
 
   /**
+   * The UpdateStorageManager service.
+   *
+   * @var \Drupal\update\UpdateManagerInterface
+   */
+  protected $updateManager;
+
+  /**
    * The UpdateFetcher service.
    *
    * @var \Drupal\update\UpdateFetcherInterface
@@ -33,6 +37,13 @@ class UpdateProcessor implements UpdateProcessorInterface {
   protected $updateFetcher;
 
   /**
+   * The UpdateParser service.
+   *
+   * @var \Drupal\update\UpdateParserInterface
+   */
+  protected $updateParser;
+
+  /**
    * The update fetch queue.
    *
    * @var \Drupal\Core\Queue\QueueInterface
@@ -84,29 +95,26 @@ class UpdateProcessor implements UpdateProcessorInterface {
   /**
    * Constructs a UpdateProcessor.
    *
-   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
-   *   The config factory.
+   * @param \Drupal\update\UpdateFetcherInterface
+   *   The update fetcher service.
+   * @param \Drupal\update\UpdateParserInterface
+   *   The update parser service.
+   * @param \Drupal\update\UpdateManagerInterface
+   *   The update manager service.
    * @param \Drupal\Core\Queue\QueueFactory $queue_factory
    *   The queue factory
-   * @param \Drupal\update\UpdateFetcherInterface $update_fetcher
-   *   The update fetcher service
-   * @param \Drupal\Core\State\StateInterface $state_store
-   *   The state service.
    * @param \Drupal\Core\PrivateKey $private_key
    *   The private key factory service.
-   * @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $key_value_factory
-   *   The key/value factory.
-   * @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $key_value_expirable_factory
-   *   The expirable key/value factory.
    */
-  public function __construct(ConfigFactoryInterface $config_factory, QueueFactory $queue_factory, UpdateFetcherInterface $update_fetcher, StateInterface $state_store, PrivateKey $private_key, KeyValueFactoryInterface $key_value_factory, KeyValueFactoryInterface $key_value_expirable_factory) {
+  public function __construct(UpdateFetcherInterface $update_fetcher, UpdateParserInterface $update_parser, UpdateManagerInterface $update_manager, QueueFactory $queue_factory, PrivateKey $private_key) {
     $this->updateFetcher = $update_fetcher;
-    $this->updateSettings = $config_factory->get('update.settings');
+    $this->updateParser = $update_parser;
+    $this->updateManager = $update_manager;
+    $this->updateSettings = $this->updateManager->getUpdateSettings();
     $this->fetchQueue = $queue_factory->get('update_fetch_tasks');
-    $this->tempStore = $key_value_expirable_factory->get('update');
-    $this->fetchTaskStore = $key_value_factory->get('update_fetch_task');
-    $this->availableReleasesTempStore = $key_value_expirable_factory->get('update_available_releases');
-    $this->stateStore = $state_store;
+    $this->tempStore = $this->updateManager->getUpdateTempStore();
+    $this->fetchTaskStore = $this->updateManager->getUpdateFetchTaskStore();
+    $this->availableReleasesTempStore = $this->updateManager->getAvailableReleasesTempStore();
     $this->privateKey = $private_key;
     $this->fetchTasks = array();
     $this->failed = array();
@@ -129,12 +137,26 @@ public function createFetchTask($project) {
   /**
    * {@inheritdoc}
    */
-  public function fetchData() {
+  public function processFetchTasks() {
     $end = time() + $this->updateSettings->get('fetch.timeout');
-    while (time() < $end && ($item = $this->fetchQueue->claimItem())) {
-      $this->processFetchTask($item->data);
+    while (time() < $end) {
+      $item = $this->processNextFetchTask();
+      if (empty($item)) {
+        break;
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processNextFetchTask() {
+    if ($item = $this->fetchQueue->claimItem()) {
+      $item->success = $this->processFetchTask($item->data);
       $this->fetchQueue->deleteItem($item);
+      return $item;
     }
+    return FALSE;
   }
 
   /**
@@ -157,16 +179,15 @@ public function processFetchTask($project) {
     $success = FALSE;
     $available = array();
     $site_key = Crypt::hmacBase64($base_url, $this->privateKey->get());
-    $fetch_url_base = $this->updateFetcher->getFetchBaseUrl($project);
+    $fetch_url_base = $this->updateFetcher->getBaseUrl($project);
     $project_name = $project['name'];
 
     if (empty($this->failed[$fetch_url_base]) || $this->failed[$fetch_url_base] < $max_fetch_attempts) {
       $data = $this->updateFetcher->fetchProjectData($project, $site_key);
     }
     if (!empty($data)) {
-      $available = $this->parseXml($data);
-      // @todo: Purge release data we don't need. See
-      //   https://www.drupal.org/node/238950.
+      $available = $this->updateParser->parseXml($data);
+      // @todo: Purge release data we don't need (http://drupal.org/node/238950).
       if (!empty($available)) {
         // Only if we fetched and parsed something sane do we return success.
         $success = TRUE;
@@ -190,7 +211,7 @@ public function processFetchTask($project) {
     $this->tempStore->setWithExpire('fetch_failures', $this->failed, $request_time_difference + (60 * 5));
 
     // Whether this worked or not, we did just (try to) check for updates.
-    $this->stateStore->set('update.last_check', REQUEST_TIME + $request_time_difference);
+    $this->updateManager->setLastCheck(REQUEST_TIME + $request_time_difference);
 
     // Now that we processed the fetch task for this project, clear out the
     // record for this task so we're willing to fetch again.
@@ -200,74 +221,38 @@ public function processFetchTask($project) {
   }
 
   /**
-   * Parses the XML of the Drupal release history info files.
-   *
-   * @param string $raw_xml
-   *   A raw XML string of available release data for a given project.
-   *
-   * @return array
-   *   Array of parsed data about releases for a given project, or NULL if there
-   *   was an error parsing the string.
-   */
-  protected function parseXml($raw_xml) {
-    try {
-      $xml = new \SimpleXMLElement($raw_xml);
-    }
-    catch (\Exception $e) {
-      // SimpleXMLElement::__construct produces an E_WARNING error message for
-      // each error found in the XML data and throws an exception if errors
-      // were detected. Catch any exception and return failure (NULL).
-      return NULL;
-    }
-    // If there is no valid project data, the XML is invalid, so return failure.
-    if (!isset($xml->short_name)) {
-      return NULL;
-    }
-    $data = array();
-    foreach ($xml as $k => $v) {
-      $data[$k] = (string) $v;
-    }
-    $data['releases'] = array();
-    if (isset($xml->releases)) {
-      foreach ($xml->releases->children() as $release) {
-        $version = (string) $release->version;
-        $data['releases'][$version] = array();
-        foreach ($release->children() as $k => $v) {
-          $data['releases'][$version][$k] = (string) $v;
-        }
-        $data['releases'][$version]['terms'] = array();
-        if ($release->terms) {
-          foreach ($release->terms->children() as $term) {
-            if (!isset($data['releases'][$version]['terms'][(string) $term->name])) {
-              $data['releases'][$version]['terms'][(string) $term->name] = array();
-            }
-            $data['releases'][$version]['terms'][(string) $term->name][] = (string) $term->value;
-          }
-        }
-      }
-    }
-    return $data;
-  }
-
-  /**
    * {@inheritdoc}
    */
-  public function numberOfQueueItems() {
+  public function getQueuedFetchTasksCount() {
     return $this->fetchQueue->numberOfItems();
   }
 
   /**
    * {@inheritdoc}
    */
-  public function claimQueueItem() {
-    return $this->fetchQueue->claimItem();
-  }
+  public function refreshUpdateData() {
 
-  /**
-   * {@inheritdoc}
-   */
-  public function deleteQueueItem($item) {
-    return $this->fetchQueue->deleteItem($item);
+    // Since we're fetching new available update data, we want to clear
+    // of both the projects we care about, and the current update status of the
+    // site. We do *not* want to clear the cache of available releases just yet,
+    // since that data (even if it's stale) can be useful during
+    // update_get_projects(); for example, to modules that implement
+    // hook_system_info_alter() such as cvs_deploy.
+    $this->tempStore->delete('update_project_projects');
+    $this->tempStore->delete('update_project_data');
+
+    $projects = $this->updateManager->getProjects();
+
+    // Now that we have the list of projects, we should also clear the available
+    // release data, since even if we fail to fetch new data, we need to clear
+    // out the stale data at this point.
+    $this->availableReleasesTempStore->deleteAll();
+
+    foreach ($projects as $project) {
+      $this->createFetchTask($project);
+    }
   }
 
+
+
 }
diff --git a/core/modules/update/src/UpdateProcessorInterface.php b/core/modules/update/src/UpdateProcessorInterface.php
index e2139c6..273583b 100644
--- a/core/modules/update/src/UpdateProcessorInterface.php
+++ b/core/modules/update/src/UpdateProcessorInterface.php
@@ -6,27 +6,26 @@
 
 namespace Drupal\update;
 
-
 /**
  * Processor of project update information.
  */
 interface UpdateProcessorInterface {
 
+
   /**
-   * Claims an item in the update fetch queue for processing.
-   *
-   * @return bool|\stdClass
-   *   On success we return an item object. If the queue is unable to claim an
-   *   item it returns false.
-   *
-   * @see \Drupal\Core\Queue\QueueInterface::claimItem()
+   * Attempts to drain the queue of tasks for release history data to fetch.
    */
-  public function claimQueueItem();
+  public function processFetchTasks();
 
   /**
-   * Attempts to drain the queue of tasks for release history data to fetch.
+   * Process a single fetch task from the queue.
+   *
+   * @return mixed
+   *   On success we return an item object. If there are no queued items
+   *   it returns false. Otherwise, item->success holds the status of the update
+   *   processing.
    */
-  public function fetchData();
+  public function processNextFetchTask();
 
   /**
    * Adds a task to the queue for fetching release history data for a project.
@@ -71,15 +70,11 @@ public function processFetchTask($project);
    *
    * @see \Drupal\Core\Queue\QueueInterface::numberOfItems()
    */
-  public function numberOfQueueItems();
+  public function getQueuedFetchTasksCount();
 
   /**
-   * Deletes a finished item from the update fetch queue.
-   *
-   * @param \stdClass $item
-   *   The item returned by \Drupal\Core\Queue\QueueInterface::claimItem().
-   *
-   * @see \Drupal\Core\Queue\QueueInterface::deleteItem()
+   * Clears out all the available update data and initiates re-fetching.
    */
-  public function deleteQueueItem($item);
+  public function refreshUpdateData();
+
 }
diff --git a/core/modules/update/update.compare.inc b/core/modules/update/update.compare.inc
index a077c38..577a2e9 100644
--- a/core/modules/update/update.compare.inc
+++ b/core/modules/update/update.compare.inc
@@ -13,48 +13,15 @@
  * compare against the available releases to produce the status report.
  *
  * @param $projects
- *   Array of project information from
- *   \Drupal\Update\UpdateManager::getProjects().
+ *   Array of project information from update_get_projects().
+ *
+ * @deprecated as of Drupal 8.0. Use
+ *   \Drupal\update\UpdateParser::processProjectInfo()
+ *
+ * @see \Drupal\update\UpdateParser::processProjectInfo()
  */
 function update_process_project_info(&$projects) {
-  foreach ($projects as $key => $project) {
-    // Assume an official release until we see otherwise.
-    $install_type = 'official';
-
-    $info = $project['info'];
-
-    if (isset($info['version'])) {
-      // Check for development snapshots
-      if (preg_match('@(dev|HEAD)@', $info['version'])) {
-        $install_type = 'dev';
-      }
-
-      // Figure out what the currently installed major version is. We need
-      // to handle both contribution (e.g. "5.x-1.3", major = 1) and core
-      // (e.g. "5.1", major = 5) version strings.
-      $matches = array();
-      if (preg_match('/^(\d+\.x-)?(\d+)\..*$/', $info['version'], $matches)) {
-        $info['major'] = $matches[2];
-      }
-      elseif (!isset($info['major'])) {
-        // This would only happen for version strings that don't follow the
-        // drupal.org convention. We let contribs define "major" in their
-        // .info.yml in this case, and only if that's missing would we hit this.
-        $info['major'] = -1;
-      }
-    }
-    else {
-      // No version info available at all.
-      $install_type = 'unknown';
-      $info['version'] = t('Unknown');
-      $info['major'] = -1;
-    }
-
-    // Finally, save the results we care about into the $projects array.
-    $projects[$key]['existing_version'] = $info['version'];
-    $projects[$key]['existing_major'] = $info['major'];
-    $projects[$key]['install_type'] = $install_type;
-  }
+  return \Drupal::service('update.parser')->processProjectInfo($projects);
 }
 
 /**
@@ -78,35 +45,15 @@ function update_process_project_info(&$projects) {
  * @see update_get_available()
  * @see \Drupal\Update\UpdateManager::getProjects()
  * @see update_process_project_info()
- * @see \Drupal\update\UpdateManagerInterface::projectStorage()
+ * @see update_project_storage()
+ *
+ * @deprecated as of Drupal 8.0. Use
+ *   \Drupal\update\UpdateManager::getProjectsUpdateStatus()
+ *
+ * @see \Drupal\update\UpdateManager::getProjectsUpdateStatus()
  */
 function update_calculate_project_data($available) {
-  // Retrieve the projects from storage, if present.
-  $projects = \Drupal::service('update.manager')->projectStorage('update_project_data');
-  // If $projects is empty, then the data must be rebuilt.
-  // Otherwise, return the data and skip the rest of the function.
-  if (!empty($projects)) {
-    return $projects;
-  }
-  $projects = \Drupal::service('update.manager')->getProjects();
-  update_process_project_info($projects);
-  foreach ($projects as $project => $project_info) {
-    if (isset($available[$project])) {
-      update_calculate_project_update_status($projects[$project], $available[$project]);
-    }
-    else {
-      $projects[$project]['status'] = UPDATE_UNKNOWN;
-      $projects[$project]['reason'] = t('No available releases found');
-    }
-  }
-  // Give other modules a chance to alter the status (for example, to allow a
-  // contrib module to provide fine-grained settings to ignore specific
-  // projects or releases).
-  \Drupal::moduleHandler()->alter('update_status', $projects);
-
-  // Store the site's update status for at most 1 hour.
-  \Drupal::keyValueExpirable('update')->setWithExpire('update_project_data', $projects, 3600);
-  return $projects;
+  return \Drupal::service('update.manager')->getProjectsUpdateStatus($available);
 }
 
 /**
@@ -169,324 +116,48 @@ function update_calculate_project_data($available) {
  *   An array containing information about a specific project.
  * @param $available
  *   Data about available project releases of a specific project.
+ *
+ * @deprecated as of Drupal 8.0. Use
+ *   \Drupal\update\UpdateParser::calculateProjectUpdateStatus()
+ *
+ * @see \Drupal\update\UpdateParser::calculateProjectUpdateStatus()
  */
 function update_calculate_project_update_status(&$project_data, $available) {
-  foreach (array('title', 'link') as $attribute) {
-    if (!isset($project_data[$attribute]) && isset($available[$attribute])) {
-      $project_data[$attribute] = $available[$attribute];
-    }
-  }
-
-  // If the project status is marked as something bad, there's nothing else
-  // to consider.
-  if (isset($available['project_status'])) {
-    switch ($available['project_status']) {
-      case 'insecure':
-        $project_data['status'] = UPDATE_NOT_SECURE;
-        if (empty($project_data['extra'])) {
-          $project_data['extra'] = array();
-        }
-        $project_data['extra'][] = array(
-          'label' => t('Project not secure'),
-          'data' => t('This project has been labeled insecure by the Drupal security team, and is no longer available for download. Immediately disabling everything included by this project is strongly recommended!'),
-        );
-        break;
-      case 'unpublished':
-      case 'revoked':
-        $project_data['status'] = UPDATE_REVOKED;
-        if (empty($project_data['extra'])) {
-          $project_data['extra'] = array();
-        }
-        $project_data['extra'][] = array(
-          'label' => t('Project revoked'),
-          'data' => t('This project has been revoked, and is no longer available for download. Disabling everything included by this project is strongly recommended!'),
-        );
-        break;
-      case 'unsupported':
-        $project_data['status'] = UPDATE_NOT_SUPPORTED;
-        if (empty($project_data['extra'])) {
-          $project_data['extra'] = array();
-        }
-        $project_data['extra'][] = array(
-          'label' => t('Project not supported'),
-          'data' => t('This project is no longer supported, and is no longer available for download. Disabling everything included by this project is strongly recommended!'),
-        );
-        break;
-      case 'not-fetched':
-        $project_data['status'] = UPDATE_NOT_FETCHED;
-        $project_data['reason'] = t('Failed to get available update data.');
-        break;
-
-      default:
-        // Assume anything else (e.g. 'published') is valid and we should
-        // perform the rest of the logic in this function.
-        break;
-    }
-  }
-
-  if (!empty($project_data['status'])) {
-    // We already know the status for this project, so there's nothing else to
-    // compute. Record the project status into $project_data and we're done.
-    $project_data['project_status'] = $available['project_status'];
-    return;
-  }
-
-  // Figure out the target major version.
-  $existing_major = $project_data['existing_major'];
-  $supported_majors = array();
-  if (isset($available['supported_majors'])) {
-    $supported_majors = explode(',', $available['supported_majors']);
-  }
-  elseif (isset($available['default_major'])) {
-    // Older release history XML file without supported or recommended.
-    $supported_majors[] = $available['default_major'];
-  }
-
-  if (in_array($existing_major, $supported_majors)) {
-    // Still supported, stay at the current major version.
-    $target_major = $existing_major;
-  }
-  elseif (isset($available['recommended_major'])) {
-    // Since 'recommended_major' is defined, we know this is the new XML
-    // format. Therefore, we know the current release is unsupported since
-    // its major version was not in the 'supported_majors' list. We should
-    // find the best release from the recommended major version.
-    $target_major = $available['recommended_major'];
-    $project_data['status'] = UPDATE_NOT_SUPPORTED;
-  }
-  elseif (isset($available['default_major'])) {
-    // Older release history XML file without recommended, so recommend
-    // the currently defined "default_major" version.
-    $target_major = $available['default_major'];
-  }
-  else {
-    // Malformed XML file? Stick with the current version.
-    $target_major = $existing_major;
-  }
-
-  // Make sure we never tell the admin to downgrade. If we recommended an
-  // earlier version than the one they're running, they'd face an
-  // impossible data migration problem, since Drupal never supports a DB
-  // downgrade path. In the unfortunate case that what they're running is
-  // unsupported, and there's nothing newer for them to upgrade to, we
-  // can't print out a "Recommended version", but just have to tell them
-  // what they have is unsupported and let them figure it out.
-  $target_major = max($existing_major, $target_major);
-
-  $release_patch_changed = '';
-  $patch = '';
-
-  // If the project is marked as UPDATE_FETCH_PENDING, it means that the
-  // data we currently have (if any) is stale, and we've got a task queued
-  // up to (re)fetch the data. In that case, we mark it as such, merge in
-  // whatever data we have (e.g. project title and link), and move on.
-  if (!empty($available['fetch_status']) && $available['fetch_status'] == UPDATE_FETCH_PENDING) {
-    $project_data['status'] = UPDATE_FETCH_PENDING;
-    $project_data['reason'] = t('No available update data');
-    $project_data['fetch_status'] = $available['fetch_status'];
-    return;
-  }
-
-  // Defend ourselves from XML history files that contain no releases.
-  if (empty($available['releases'])) {
-    $project_data['status'] = UPDATE_UNKNOWN;
-    $project_data['reason'] = t('No available releases found');
-    return;
-  }
-  foreach ($available['releases'] as $version => $release) {
-    // First, if this is the existing release, check a few conditions.
-    if ($project_data['existing_version'] === $version) {
-      if (isset($release['terms']['Release type']) &&
-          in_array('Insecure', $release['terms']['Release type'])) {
-        $project_data['status'] = UPDATE_NOT_SECURE;
-      }
-      elseif ($release['status'] == 'unpublished') {
-        $project_data['status'] = UPDATE_REVOKED;
-        if (empty($project_data['extra'])) {
-          $project_data['extra'] = array();
-        }
-        $project_data['extra'][] = array(
-          'class' => array('release-revoked'),
-          'label' => t('Release revoked'),
-          'data' => t('Your currently installed release has been revoked, and is no longer available for download. Disabling everything included in this release or upgrading is strongly recommended!'),
-        );
-      }
-      elseif (isset($release['terms']['Release type']) &&
-              in_array('Unsupported', $release['terms']['Release type'])) {
-        $project_data['status'] = UPDATE_NOT_SUPPORTED;
-        if (empty($project_data['extra'])) {
-          $project_data['extra'] = array();
-        }
-        $project_data['extra'][] = array(
-          'class' => array('release-not-supported'),
-          'label' => t('Release not supported'),
-          'data' => t('Your currently installed release is now unsupported, and is no longer available for download. Disabling everything included in this release or upgrading is strongly recommended!'),
-        );
-      }
-    }
-
-    // Otherwise, ignore unpublished, insecure, or unsupported releases.
-    if ($release['status'] == 'unpublished' ||
-        (isset($release['terms']['Release type']) &&
-         (in_array('Insecure', $release['terms']['Release type']) ||
-          in_array('Unsupported', $release['terms']['Release type'])))) {
-      continue;
-    }
-
-    // See if this is a higher major version than our target and yet still
-    // supported. If so, record it as an "Also available" release.
-    // Note: Some projects have a HEAD release from CVS days, which could
-    // be one of those being compared. They would not have version_major
-    // set, so we must call isset first.
-    if (isset($release['version_major']) && $release['version_major'] > $target_major) {
-      if (in_array($release['version_major'], $supported_majors)) {
-        if (!isset($project_data['also'])) {
-          $project_data['also'] = array();
-        }
-        if (!isset($project_data['also'][$release['version_major']])) {
-          $project_data['also'][$release['version_major']] = $version;
-          $project_data['releases'][$version] = $release;
-        }
-      }
-      // Otherwise, this release can't matter to us, since it's neither
-      // from the release series we're currently using nor the recommended
-      // release. We don't even care about security updates for this
-      // branch, since if a project maintainer puts out a security release
-      // at a higher major version and not at the lower major version,
-      // they must remove the lower version from the supported major
-      // versions at the same time, in which case we won't hit this code.
-      continue;
-    }
-
-    // Look for the 'latest version' if we haven't found it yet. Latest is
-    // defined as the most recent version for the target major version.
-    if (!isset($project_data['latest_version'])
-        && $release['version_major'] == $target_major) {
-      $project_data['latest_version'] = $version;
-      $project_data['releases'][$version] = $release;
-    }
-
-    // Look for the development snapshot release for this branch.
-    if (!isset($project_data['dev_version'])
-        && $release['version_major'] == $target_major
-        && isset($release['version_extra'])
-        && $release['version_extra'] == 'dev') {
-      $project_data['dev_version'] = $version;
-      $project_data['releases'][$version] = $release;
-    }
-
-    // Look for the 'recommended' version if we haven't found it yet (see
-    // phpdoc at the top of this function for the definition).
-    if (!isset($project_data['recommended'])
-        && $release['version_major'] == $target_major
-        && isset($release['version_patch'])) {
-      if ($patch != $release['version_patch']) {
-        $patch = $release['version_patch'];
-        $release_patch_changed = $release;
-      }
-      if (empty($release['version_extra']) && $patch == $release['version_patch']) {
-        $project_data['recommended'] = $release_patch_changed['version'];
-        $project_data['releases'][$release_patch_changed['version']] = $release_patch_changed;
-      }
-    }
-
-    // Stop searching once we hit the currently installed version.
-    if ($project_data['existing_version'] === $version) {
-      break;
-    }
-
-    // If we're running a dev snapshot and have a timestamp, stop
-    // searching for security updates once we hit an official release
-    // older than what we've got. Allow 100 seconds of leeway to handle
-    // differences between the datestamp in the .info.yml file and the
-    // timestamp of the tarball itself (which are usually off by 1 or 2
-    // seconds) so that we don't flag that as a new release.
-    if ($project_data['install_type'] == 'dev') {
-      if (empty($project_data['datestamp'])) {
-        // We don't have current timestamp info, so we can't know.
-        continue;
-      }
-      elseif (isset($release['date']) && ($project_data['datestamp'] + 100 > $release['date'])) {
-        // We're newer than this, so we can skip it.
-        continue;
-      }
-    }
-
-    // See if this release is a security update.
-    if (isset($release['terms']['Release type'])
-        && in_array('Security update', $release['terms']['Release type'])) {
-      $project_data['security updates'][] = $release;
-    }
-  }
-
-  // If we were unable to find a recommended version, then make the latest
-  // version the recommended version if possible.
-  if (!isset($project_data['recommended']) && isset($project_data['latest_version'])) {
-    $project_data['recommended'] = $project_data['latest_version'];
-  }
-
-  //
-  // Check to see if we need an update or not.
-  //
-
-  if (!empty($project_data['security updates'])) {
-    // If we found security updates, that always trumps any other status.
-    $project_data['status'] = UPDATE_NOT_SECURE;
-  }
-
-  if (isset($project_data['status'])) {
-    // If we already know the status, we're done.
-    return;
-  }
-
-  // If we don't know what to recommend, there's nothing we can report.
-  // Bail out early.
-  if (!isset($project_data['recommended'])) {
-    $project_data['status'] = UPDATE_UNKNOWN;
-    $project_data['reason'] = t('No available releases found');
-    return;
-  }
-
-  // If we're running a dev snapshot, compare the date of the dev snapshot
-  // with the latest official version, and record the absolute latest in
-  // 'latest_dev' so we can correctly decide if there's a newer release
-  // than our current snapshot.
-  if ($project_data['install_type'] == 'dev') {
-    if (isset($project_data['dev_version']) && $available['releases'][$project_data['dev_version']]['date'] > $available['releases'][$project_data['latest_version']]['date']) {
-      $project_data['latest_dev'] = $project_data['dev_version'];
-    }
-    else {
-      $project_data['latest_dev'] = $project_data['latest_version'];
-    }
-  }
-
-  // Figure out the status, based on what we've seen and the install type.
-  switch ($project_data['install_type']) {
-    case 'official':
-      if ($project_data['existing_version'] === $project_data['recommended'] || $project_data['existing_version'] === $project_data['latest_version']) {
-        $project_data['status'] = UPDATE_CURRENT;
-      }
-      else {
-        $project_data['status'] = UPDATE_NOT_CURRENT;
-      }
-      break;
-
-    case 'dev':
-      $latest = $available['releases'][$project_data['latest_dev']];
-      if (empty($project_data['datestamp'])) {
-        $project_data['status'] = UPDATE_NOT_CHECKED;
-        $project_data['reason'] = t('Unknown release date');
-      }
-      elseif (($project_data['datestamp'] + 100 > $latest['date'])) {
-        $project_data['status'] = UPDATE_CURRENT;
-      }
-      else {
-        $project_data['status'] = UPDATE_NOT_CURRENT;
-      }
-      break;
+  return \Drupal::service('update.parser')->calculateProjectUpdateStatus($project_data, $available);
+}
 
-    default:
-      $project_data['status'] = UPDATE_UNKNOWN;
-      $project_data['reason'] = t('Invalid info');
-  }
+/**
+ * Retrieves update storage data or empties it.
+ *
+ * Two very expensive arrays computed by this module are the list of all
+ * installed modules and themes (and .info.yml data, project associations, etc), and
+ * the current status of the site relative to the currently available releases.
+ * These two arrays are stored and used whenever possible. The data is cleared
+ * whenever the administrator visits the status report, available updates
+ * report, or the module or theme administration pages, since we should always
+ * recompute the most current values on any of those pages.
+ *
+ * Note: while both of these arrays are expensive to compute (in terms of disk
+ * I/O and some fairly heavy CPU processing), neither of these is the actual
+ * data about available updates that we have to fetch over the network from
+ * updates.drupal.org. That information is stored in the
+ * 'update_available_releases' collection -- it needs to persist longer than 1
+ * hour and never get invalidated just by visiting a page on the site.
+ *
+ * @param $key
+ *   The key of data to return. Valid options are 'update_project_data' and
+ *   'update_project_projects'.
+ *
+ * @return
+ *   The stored value of the $projects array generated by
+ *   update_calculate_project_data() or update_get_projects(), or an empty array
+ *   when the storage is cleared.
+ *
+ * @deprecated as of Drupal 8.0. Use
+ *   \Drupal\update\UpdateManager::getProjectStorage()
+ *
+ * @see \Drupal\update\UpdateManager::getProjectStorage()
+ */
+function update_project_storage($key) {
+  return \Drupal::service('update.manager')->getProjectStorage($key);
 }
diff --git a/core/modules/update/update.fetch.inc b/core/modules/update/update.fetch.inc
index 1b111e5..46f3f71 100644
--- a/core/modules/update/update.fetch.inc
+++ b/core/modules/update/update.fetch.inc
@@ -6,46 +6,119 @@
  */
 
 /**
+ * Batch callback: Processes a step in batch for fetching available update data.
+ *
+ * @param $context
+ *   Reference to an array used for Batch API storage.
+ *
+ * @see \Drupal\update\UpdateController::fetchDataBatch()
+ *
+ * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
+ *   Use \Drupal\update\UpdateController::fetchDataBatch().
+ */
+function update_fetch_data_batch(&$context) {
+  $update_controller = new \Drupal\update\Controller\UpdateController(
+      \Drupal::service('update.manager'),
+      \Drupal::service('update.processor')
+  );
+  $update_controller->fetchDataUsingBatch($context);
+}
+
+/**
+ * Attempts to drain the queue of tasks for release history data to fetch.
+ *
+ * @see \Drupal\update\UpdateProcessor::processFetchTasks()
+ *
+ * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
+ *   Use \Drupal::service('update.processor')->processFetchTasks();.
+ */
+function _update_fetch_data() {
+  return \Drupal::service('update.processor')->processFetchTasks();
+}
+
+/**
+ * Processes a task to fetch available update data for a single project.
+ *
+ * Once the release history XML data is downloaded, it is parsed and saved in an
+ * entry just for that project.
+ *
+ * @param $project
+ *   Associative array of information about the project to fetch data for.
+ *
+ * @return
+ *   TRUE if we fetched parsable XML, otherwise FALSE.
+ *
+ * @see \Drupal\update\UpdateProcessor::processFetchTask()
+ *
+ * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
+ *   Use \Drupal::service('update.processor')->processFetchTask().
+ */
+function _update_process_fetch_task($project) {
+  return \Drupal::service('update.processor')->processFetchTask($project);
+}
+
+/**
+ * Clears out all the available update data and initiates re-fetching.
+ *
+ * @see \Drupal\update\UpdateProcessor::refreshUpdateData()
+ *
+ * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
+ *   Use \Drupal::service('update.processor')->refreshUpdateData().
+ */
+function _update_refresh() {
+  \Drupal::service('update.processor')->refreshUpdateData();
+}
+
+/**
+ * Adds a task to the queue for fetching release history data for a project.
+ *
+ * We only create a new fetch task if there's no task already in the queue for
+ * this particular project (based on 'update_fetch_task' key-value collection).
+ *
+ * @param $project
+ *   Associative array of information about a project as created by
+ *   update_get_projects(), including keys such as 'name' (short name), and the
+ *   'info' array with data from a .info.yml file for the project.
+ *
+ * @see \Drupal\update\UpdateProcessor::createFetchTask()
+ *
+ * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
+ *   Use \Drupal::service('update.processor')->createFetchTask().
+ */
+function _update_create_fetch_task($project) {
+  \Drupal::service('update.processor')->createFetchTask($project);
+}
+
+/**
  * Performs any notifications that should be done once cron fetches new data.
  *
  * This method checks the status of the site using the new data and, depending
  * on the configuration of the site, notifies administrators via email if there
  * are new releases or missing security updates.
  *
- * @see update_requirements()
+ * @deprecated as of Drupal 8.0. Use
+ *   \Drupal\update\UpdateManager::cronNotify()
+ *
+ * @see \Drupal\update\UpdateManager::cronNotify()
  */
 function _update_cron_notify() {
-  $update_config = \Drupal::config('update.settings');
-  module_load_install('update');
-  $status = update_requirements('runtime');
-  $params = array();
-  $notify_all = ($update_config->get('notification.threshold') == 'all');
-  foreach (array('core', 'contrib') as $report_type) {
-    $type = 'update_' . $report_type;
-    if (isset($status[$type]['severity'])
-        && ($status[$type]['severity'] == REQUIREMENT_ERROR || ($notify_all && $status[$type]['reason'] == UPDATE_NOT_CURRENT))) {
-      $params[$report_type] = $status[$type]['reason'];
-    }
-  }
-  if (!empty($params)) {
-    $notify_list = $update_config->get('notification.emails');
-    if (!empty($notify_list)) {
-      $default_langcode = \Drupal::languageManager()->getDefaultLanguage()->getId();
-      foreach ($notify_list as $target) {
-        if ($target_user = user_load_by_mail($target)) {
-          $target_langcode = $target_user->getPreferredLangcode();
-        }
-        else {
-          $target_langcode = $default_langcode;
-        }
-        $message = \Drupal::service('plugin.manager.mail')->mail('update', 'status_notify', $target, $target_langcode, $params);
-        // Track when the last mail was successfully sent to avoid sending
-        // too many emails.
-        if ($message['result']) {
-          \Drupal::state()->set('update.last_email_notification', REQUEST_TIME);
-        }
-      }
-    }
-  }
+  \Drupal::service('update.manager')->cronNotify();
 }
 
+/**
+ * Parses the XML of the Drupal release history info files.
+ *
+ * @param $raw_xml
+ *   A raw XML string of available release data for a given project.
+ *
+ * @return
+ *   Array of parsed data about releases for a given project, or NULL if there
+ *   was an error parsing the string.
+ *
+ * @deprecated as of Drupal 8.0. Use \Drupal\update\UpdateParser::parseXml()
+ *
+ * @see \Drupal\update\UpdateParser::parseXml()
+ */
+function update_parse_xml($raw_xml) {
+  \Drupal::service('update.parser')->parseXml($raw_xml);
+}
diff --git a/core/modules/update/update.module b/core/modules/update/update.module
index 4aed1ef..46178b4 100644
--- a/core/modules/update/update.module
+++ b/core/modules/update/update.module
@@ -203,7 +203,7 @@ function update_cron() {
   $update_config = \Drupal::config('update.settings');
   $frequency = $update_config->get('check.interval_days');
   $interval = 60 * 60 * 24 * $frequency;
-  $last_check = \Drupal::state()->get('update.last_check') ?: 0;
+  $last_check = \Drupal::service('update.manager')->getLastCheck() ?: 0;
   if ((REQUEST_TIME - $last_check) > $interval) {
     // If the configured update interval has elapsed, we want to invalidate
     // the data for all projects, attempt to re-fetch, and trigger any
diff --git a/core/modules/update/update.services.yml b/core/modules/update/update.services.yml
index c14b8cc..1ef61d8 100644
--- a/core/modules/update/update.services.yml
+++ b/core/modules/update/update.services.yml
@@ -6,10 +6,13 @@ services:
       - { name: access_check, applies_to: _access_update_manager }
   update.manager:
     class: Drupal\update\UpdateManager
-    arguments: ['@config.factory', '@module_handler', '@update.processor', '@string_translation', '@keyvalue.expirable', '@theme_handler']
+    arguments: ['@update.parser', '@module_handler', '@config.factory', '@keyvalue.expirable', '@keyvalue', '@state']
   update.processor:
     class: Drupal\update\UpdateProcessor
-    arguments: ['@config.factory', '@queue', '@update.fetcher', '@state', '@private_key', '@keyvalue', '@keyvalue.expirable']
+    arguments: ['@update.fetcher', '@update.parser', '@update.manager', '@queue', '@private_key']
   update.fetcher:
     class: Drupal\update\UpdateFetcher
-    arguments: ['@config.factory', '@http_client']
+    arguments: ['@config.factory', '@http_default_client']
+  update.parser:
+    class: Drupal\update\UpdateParser
+    arguments: ['@string_translation']
