Chrome Cleaner: Add a skeleton controller class.

Adds a skeleton ChromeCleanerController class intended to control the
execution of the Chrome Cleaner. The full implementation will be added
in subsequent CLs.

BUG=690020

Review-Url: https://codereview.chromium.org/2865413002
Cr-Commit-Position: refs/heads/master@{#471932}
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index cf3bf21c..d49c04d 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1155,6 +1155,8 @@
     "resource_delegate_mac.mm",
     "resources_util.cc",
     "resources_util.h",
+    "safe_browsing/chrome_cleaner/chrome_cleaner_controller_win.cc",
+    "safe_browsing/chrome_cleaner/chrome_cleaner_controller_win.h",
     "safe_browsing/chrome_cleaner/chrome_cleaner_fetcher_win.cc",
     "safe_browsing/chrome_cleaner/chrome_cleaner_fetcher_win.h",
     "safe_browsing/chrome_cleaner/reporter_runner_win.cc",
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_controller_win.cc b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_controller_win.cc
new file mode 100644
index 0000000..0dcb88d
--- /dev/null
+++ b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_controller_win.cc
@@ -0,0 +1,87 @@
+// Copyright 2017 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.
+
+#include "chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_controller_win.h"
+
+#include "base/logging.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace safe_browsing {
+
+namespace {
+
+// Global instance that is set and unset by ChromeCleanerController's
+// constructor and destructor.
+ChromeCleanerController* g_chrome_cleaner_controller = nullptr;
+
+}  // namespace
+
+// static
+scoped_refptr<ChromeCleanerController> ChromeCleanerController::GetInstance() {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  if (g_chrome_cleaner_controller)
+    return make_scoped_refptr(g_chrome_cleaner_controller);
+
+  return make_scoped_refptr(new ChromeCleanerController());
+}
+
+ChromeCleanerController* ChromeCleanerController::GetRawInstanceForTesting() {
+  return g_chrome_cleaner_controller;
+}
+
+void ChromeCleanerController::AddObserver(Observer* observer) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  // TODO(alito): Implement this. Add to |observer_list_| and notify |observer|
+  //              of the current state.
+}
+
+void ChromeCleanerController::RemoveObserver(Observer* observer) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  // TODO(alito): Implement this.
+}
+
+ChromeCleanerController::ChromeCleanerController() {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  DCHECK(!g_chrome_cleaner_controller);
+
+  g_chrome_cleaner_controller = this;
+}
+
+ChromeCleanerController::~ChromeCleanerController() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  // An instance of ChromeCleanerController must keep itself alive while in any
+  // state other than |kIdle| and |kRebootRequired|.
+  DCHECK(state_ == State::kIdle || state_ == State::kRebootRequired);
+  DCHECK_EQ(this, g_chrome_cleaner_controller);
+
+  g_chrome_cleaner_controller = nullptr;
+}
+
+void ChromeCleanerController::NotifyObserver(Observer* observer) const {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  // TODO(alito): Implement this. Call |observer|'s function corresponding to
+  //              the current state.
+}
+
+void ChromeCleanerController::NotifyAllObservers() const {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  for (auto& observer : observer_list_)
+    NotifyObserver(&observer);
+}
+
+void ChromeCleanerController::SetKeepAlive(bool keep_alive) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+  if (keep_alive == keep_alive_)
+    return;
+
+  keep_alive_ = keep_alive;
+  if (keep_alive_)
+    AddRef();
+  else
+    Release();
+}
+
+}  // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_controller_win.h b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_controller_win.h
new file mode 100644
index 0000000..418fcaf2
--- /dev/null
+++ b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_controller_win.h
@@ -0,0 +1,101 @@
+// Copyright 2017 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.
+
+#ifndef CHROME_BROWSER_SAFE_BROWSING_CHROME_CLEANER_CHROME_CLEANER_CONTROLLER_WIN_H_
+#define CHROME_BROWSER_SAFE_BROWSING_CHROME_CLEANER_CHROME_CLEANER_CONTROLLER_WIN_H_
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/observer_list.h"
+#include "base/threading/thread_checker.h"
+
+namespace safe_browsing {
+
+// Controller class that keeps track of the execution of the Chrome Cleaner and
+// the various states through which the execution will transition. Observers can
+// register themselves to be notified of state changes. Intended to be used by
+// the Chrome Cleaner webui page and the Chrome Cleaner prompt dialog.
+//
+// This class lives on, and all its members should be called only on, the UI
+// thread.
+class ChromeCleanerController
+    : public base::RefCounted<ChromeCleanerController> {
+ public:
+  enum class State {
+    // The default state where there is no Chrome Cleaner process or IPC to keep
+    // track of and a reboot of the machine is not required to complete previous
+    // cleaning attempts. This is also the state the controller will end up in
+    // if any errors occur during execution of the Chrome Cleaner process.
+    kIdle,
+    // All steps up to and including scanning the machine occur in this
+    // state. The steps include downloading the Chrome Cleaner binary, setting
+    // up an IPC between Chrome and the Cleaner process, and the actual
+    // scanning done by the Cleaner.
+    kScanning,
+    // Scanning has been completed and harmful or unwanted software was found.
+    kInfected,
+    // The Cleaner process is cleaning the machine.
+    kCleaning,
+    // The cleaning has finished, but a reboot of the machine is required to
+    // complete cleanup. This state will persist across restarts of Chrome until
+    // a reboot is detected.
+    kRebootRequired,
+  };
+
+  class Observer {
+   public:
+    virtual void OnIdle() {}
+    virtual void OnScanning() {}
+    virtual void OnInfected() {}
+    virtual void OnCleaning() {}
+    virtual void OnRebootRequired() {}
+
+   protected:
+    virtual ~Observer() = default;
+  };
+
+  // Returns an existing instance of the controller if there is one, or else
+  // will create a new instance. There is at most one instance of the controller
+  // class at any time.
+  static scoped_refptr<ChromeCleanerController> GetInstance();
+
+  State state() const { return state_; }
+
+  // |AddObserver()| immediately notifies |observer| of the controller's state
+  // by calling the corresponding |On*()| function.
+  void AddObserver(Observer* observer);
+  void RemoveObserver(Observer* observer);
+
+  // TODO(alito): add other functions, such as Scan(), Clean() etc.
+
+  static ChromeCleanerController* GetRawInstanceForTesting();
+
+ private:
+  friend class base::RefCounted<ChromeCleanerController>;
+
+  ChromeCleanerController();
+  ~ChromeCleanerController();
+
+  void NotifyObserver(Observer* observer) const;
+  void NotifyAllObservers() const;
+  void SetKeepAlive(bool keep_alive);
+
+  State state_ = State::kIdle;
+
+  base::ObserverList<Observer> observer_list_;
+
+  // Used to indicate that this instance needs to be kept alive. The instance
+  // should not be destroyed while in any of the |kScanning|, |kInfected|, or
+  // |kCleaning| states, which are the states where we have an active IPC to the
+  // Chrome Cleaner process.
+  bool keep_alive_ = false;
+
+  THREAD_CHECKER(thread_checker_);
+
+  DISALLOW_COPY_AND_ASSIGN(ChromeCleanerController);
+};
+
+}  // namespace safe_browsing
+
+#endif  // CHROME_BROWSER_SAFE_BROWSING_CHROME_CLEANER_CHROME_CLEANER_CONTROLLER_WIN_H_
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_controller_win_unittest.cc b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_controller_win_unittest.cc
new file mode 100644
index 0000000..8485cfa
--- /dev/null
+++ b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_controller_win_unittest.cc
@@ -0,0 +1,51 @@
+// Copyright 2017 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.
+
+#include "chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_controller_win.h"
+
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace safe_browsing {
+namespace {
+
+class ChromeCleanerControllerTest : public ::testing::Test {
+ private:
+  // We need this because we need a UI thread during tests. The thread bundle
+  // must be the first member of the class so that it will be destroyed last.
+  content::TestBrowserThreadBundle thread_bundle_;
+};
+
+TEST_F(ChromeCleanerControllerTest, GetInstance) {
+  // Ensure that GetInstance() returns the same controller object between
+  // invocations as long someone is holding on to a reference to the controller.
+  //
+  // Also ensure that the controller object is destroyed if it is in the IDLE
+  // state and there are no more references to it.
+
+  {
+    scoped_refptr<ChromeCleanerController> controller =
+        ChromeCleanerController::GetInstance();
+    EXPECT_TRUE(controller);
+    EXPECT_EQ(controller, ChromeCleanerController::GetRawInstanceForTesting());
+    EXPECT_EQ(controller->state(), ChromeCleanerController::State::kIdle);
+
+    // The same controller instance is returned as long as there are references
+    // to it.
+    EXPECT_EQ(controller, ChromeCleanerController::GetInstance());
+  }
+  // All references have now gone out of scope and the controller object should
+  // be destroyed.
+  EXPECT_FALSE(ChromeCleanerController::GetRawInstanceForTesting());
+
+  // GetInstance() always returns a valid object.
+  scoped_refptr<ChromeCleanerController> controller =
+      ChromeCleanerController::GetInstance();
+  EXPECT_TRUE(controller);
+  EXPECT_EQ(controller, ChromeCleanerController::GetRawInstanceForTesting());
+}
+
+}  // namespace
+}  // namespace safe_browsing