[Nearby] Define NearbyProcessManager singleton

This class manages the lifetime and profile affinity of the sandbox
process that runs Nearby Connections. Only one instance of the process
will run at a time and it will be associated with one profile only. A
profile can be set as the active profile (usually via an explicit user
action) after which it gets access to the mojo interfaces running in the
sandboxed process.

Bug: 1084576
Change-Id: I50c8c61e1b7b85a078f4fdea96e0ecda1ac57123
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2196486
Reviewed-by: Dominic Battré <[email protected]>
Reviewed-by: Alex Chau <[email protected]>
Commit-Queue: Richard Knoll <[email protected]>
Cr-Commit-Position: refs/heads/master@{#772209}
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 07b4aee..ccd164b 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -3411,6 +3411,8 @@
       "nearby_sharing/nearby_connection.h",
       "nearby_sharing/nearby_connections_manager.h",
       "nearby_sharing/nearby_constants.h",
+      "nearby_sharing/nearby_process_manager.cc",
+      "nearby_sharing/nearby_process_manager.h",
       "nearby_sharing/nearby_sharing_prefs.cc",
       "nearby_sharing/nearby_sharing_prefs.h",
       "nearby_sharing/nearby_sharing_service.h",
diff --git a/chrome/browser/nearby_sharing/nearby_process_manager.cc b/chrome/browser/nearby_sharing/nearby_process_manager.cc
new file mode 100644
index 0000000..d8b749d
--- /dev/null
+++ b/chrome/browser/nearby_sharing/nearby_process_manager.cc
@@ -0,0 +1,220 @@
+// Copyright 2020 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/nearby_sharing/nearby_process_manager.h"
+
+#include "base/files/file_path.h"
+#include "build/build_config.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/nearby_sharing/nearby_sharing_prefs.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_attributes_entry.h"
+#include "chrome/browser/profiles/profile_attributes_storage.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/sharing/webrtc/sharing_mojo_service.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/browser/service_process_host.h"
+
+namespace {
+
+ProfileAttributesEntry* GetStoredNearbyProfile() {
+  PrefService* local_state = g_browser_process->local_state();
+  if (!local_state)
+    return nullptr;
+
+  base::FilePath advertising_profile_path =
+      local_state->GetFilePath(kNearbySharingActiveProfilePrefName);
+  if (advertising_profile_path.empty())
+    return nullptr;
+
+  ProfileManager* profile_manager = g_browser_process->profile_manager();
+  if (!profile_manager)
+    return nullptr;
+
+  ProfileAttributesStorage& storage =
+      profile_manager->GetProfileAttributesStorage();
+
+  ProfileAttributesEntry* entry;
+  if (!storage.GetProfileAttributesWithPath(advertising_profile_path, &entry)) {
+    // Stored profile path is invalid so remove it.
+    local_state->ClearPref(kNearbySharingActiveProfilePrefName);
+    return nullptr;
+  }
+  return entry;
+}
+
+void SetStoredNearbyProfile(Profile* profile) {
+  PrefService* local_state = g_browser_process->local_state();
+  if (!local_state)
+    return;
+
+  if (profile) {
+    local_state->SetFilePath(kNearbySharingActiveProfilePrefName,
+                             profile->GetPath());
+  } else {
+    local_state->ClearPref(kNearbySharingActiveProfilePrefName);
+  }
+}
+
+bool IsStoredNearbyProfile(Profile* profile) {
+  ProfileAttributesEntry* entry = GetStoredNearbyProfile();
+  if (!entry)
+    return profile == nullptr;
+  return profile && entry->GetPath() == profile->GetPath();
+}
+
+}  // namespace
+
+// static
+NearbyProcessManager& NearbyProcessManager::GetInstance() {
+  static base::NoDestructor<NearbyProcessManager> instance;
+  return *instance;
+}
+
+void NearbyProcessManager::AddObserver(Observer* observer) {
+  observers_.AddObserver(observer);
+}
+
+void NearbyProcessManager::RemoveObserver(Observer* observer) {
+  observers_.RemoveObserver(observer);
+}
+
+ProfileAttributesEntry* NearbyProcessManager::GetActiveProfile() const {
+  return GetStoredNearbyProfile();
+}
+
+bool NearbyProcessManager::IsActiveProfile(Profile* profile) const {
+  // If the active profile is not loaded yet, try looking in prefs.
+  // TODO(knollr): Add a test for this.
+  if (!active_profile_)
+    return IsStoredNearbyProfile(profile);
+
+  return active_profile_ == profile;
+}
+
+bool NearbyProcessManager::IsAnyProfileActive() const {
+  return !IsActiveProfile(/*profile=*/nullptr);
+}
+
+void NearbyProcessManager::SetActiveProfile(Profile* profile) {
+  if (IsActiveProfile(profile))
+    return;
+
+  active_profile_ = profile;
+  SetStoredNearbyProfile(active_profile_);
+  StopProcess(active_profile_);
+
+  for (auto& observer : observers_)
+    observer.OnNearbyProfileChanged(profile);
+}
+
+void NearbyProcessManager::ClearActiveProfile() {
+  SetActiveProfile(/*profile=*/nullptr);
+}
+
+location::nearby::connections::mojom::NearbyConnections*
+NearbyProcessManager::GetOrStartNearbyConnections(Profile* profile) {
+  if (!IsActiveProfile(profile))
+    return nullptr;
+
+  // Launch a new Nearby Connections interface if required.
+  if (!connections_.is_bound())
+    BindNearbyConnections();
+
+  return connections_.get();
+}
+
+void NearbyProcessManager::StopProcess(Profile* profile) {
+  if (!IsActiveProfile(profile))
+    return;
+
+  bool was_running = sharing_process_.is_bound();
+
+  connections_host_.reset();
+  connections_.reset();
+  sharing_process_.reset();
+
+  if (was_running) {
+    for (auto& observer : observers_)
+      observer.OnNearbyProcessStopped();
+  }
+}
+
+void NearbyProcessManager::OnProfileAdded(Profile* profile) {
+  // Cache active |profile| once it loads so we don't have to check prefs.
+  if (IsActiveProfile(profile))
+    active_profile_ = profile;
+}
+
+void NearbyProcessManager::OnProfileMarkedForPermanentDeletion(
+    Profile* profile) {
+  if (IsActiveProfile(profile))
+    SetActiveProfile(nullptr);
+}
+
+void NearbyProcessManager::BindSharingProcess(
+    mojo::PendingRemote<sharing::mojom::Sharing> sharing) {
+  sharing_process_.Bind(std::move(sharing));
+  // base::Unretained() is safe as |this| is a singleton.
+  sharing_process_.set_disconnect_handler(base::BindOnce(
+      &NearbyProcessManager::OnNearbyProcessStopped, base::Unretained(this)));
+}
+
+NearbyProcessManager::NearbyProcessManager() {
+  // profile_manager() might be null in tests or during shutdown.
+  if (auto* manager = g_browser_process->profile_manager())
+    manager->AddObserver(this);
+}
+
+NearbyProcessManager::~NearbyProcessManager() {
+  if (auto* manager = g_browser_process->profile_manager())
+    manager->RemoveObserver(this);
+}
+
+void NearbyProcessManager::LaunchNewProcess() {
+  // Stop any running process and mojo pipes.
+  StopProcess(active_profile_);
+
+  // Launch a new sandboxed process.
+  // TODO(knollr): Set process name to "Nearby Sharing".
+  BindSharingProcess(sharing::LaunchSharing());
+}
+
+void NearbyProcessManager::BindNearbyConnections() {
+  // Start a new process if there is none running yet.
+  if (!sharing_process_.is_bound())
+    LaunchNewProcess();
+
+  // Create the Nearby Connections stack in the sandboxed process.
+  // base::Unretained() calls below are safe as |this| is a singleton.
+  sharing_process_->CreateNearbyConnections(
+      connections_host_.BindNewPipeAndPassRemote(),
+      base::BindOnce(&NearbyProcessManager::OnNearbyConnections,
+                     base::Unretained(this),
+                     connections_.BindNewPipeAndPassReceiver()));
+
+  // Terminate the process if the Nearby Connections interface disconnects as
+  // that indicated an incorrect state and we have to restart the process.
+  connections_host_.set_disconnect_handler(base::BindOnce(
+      &NearbyProcessManager::OnNearbyProcessStopped, base::Unretained(this)));
+  connections_.set_disconnect_handler(base::BindOnce(
+      &NearbyProcessManager::OnNearbyProcessStopped, base::Unretained(this)));
+}
+
+void NearbyProcessManager::OnNearbyConnections(
+    mojo::PendingReceiver<NearbyConnectionsMojom> receiver,
+    mojo::PendingRemote<NearbyConnectionsMojom> remote) {
+  if (!mojo::FusePipes(std::move(receiver), std::move(remote))) {
+    LOG(WARNING) << "Failed to initialize Nearby Connectins process";
+    StopProcess(active_profile_);
+    return;
+  }
+
+  for (auto& observer : observers_)
+    observer.OnNearbyProcessStarted();
+}
+
+void NearbyProcessManager::OnNearbyProcessStopped() {
+  StopProcess(active_profile_);
+}
diff --git a/chrome/browser/nearby_sharing/nearby_process_manager.h b/chrome/browser/nearby_sharing/nearby_process_manager.h
new file mode 100644
index 0000000..5722f3e
--- /dev/null
+++ b/chrome/browser/nearby_sharing/nearby_process_manager.h
@@ -0,0 +1,160 @@
+// Copyright 2020 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_NEARBY_SHARING_NEARBY_PROCESS_MANAGER_H_
+#define CHROME_BROWSER_NEARBY_SHARING_NEARBY_PROCESS_MANAGER_H_
+
+#include "base/gtest_prod_util.h"
+#include "base/no_destructor.h"
+#include "base/observer_list.h"
+#include "base/observer_list_types.h"
+#include "base/scoped_observer.h"
+#include "chrome/browser/profiles/profile_manager_observer.h"
+#include "chrome/services/sharing/public/mojom/nearby_connections.mojom.h"
+#include "chrome/services/sharing/public/mojom/sharing.mojom.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+
+class Profile;
+class ProfileAttributesEntry;
+
+// Manages the lifetime of the Nearby process. It runs the Nearby Connections
+// library and Nearby Sharing data decoding. Only one instance of the process is
+// supported at a time.
+class NearbyProcessManager
+    : public ProfileManagerObserver,
+      public location::nearby::connections::mojom::NearbyConnectionsHost {
+ public:
+  using NearbyConnectionsMojom =
+      location::nearby::connections::mojom::NearbyConnections;
+  using NearbyConnectionsHostMojom =
+      location::nearby::connections::mojom::NearbyConnectionsHost;
+
+  // Returns an instance to the singleton of this class. This is used from
+  // multiple BCKS and only allows the first one to launch a process.
+  static NearbyProcessManager& GetInstance();
+
+  // Observes the global state of the NearbyProcessManager.
+  class Observer : public base::CheckedObserver {
+   public:
+    // Called when the |profile| got set as the active profile.
+    virtual void OnNearbyProfileChanged(Profile* profile) = 0;
+    // Called when the Nearby process has started. This happens after a profile
+    // called one of the GetOrStart*() methods.
+    virtual void OnNearbyProcessStarted() = 0;
+    // Called when the Nearby process has stopped. This can happen when the
+    // process gets stopped to switch to a different profile or when the process
+    // gets killed by the system.
+    virtual void OnNearbyProcessStopped() = 0;
+  };
+
+  void AddObserver(Observer* observer);
+  void RemoveObserver(Observer* observer);
+
+  // Gets the entry for the currently active profile or nullptr if no profile is
+  // set. We return a ProfileAttributesEntry instead of a Profile as the active
+  // profile might not be loaded yet and we do not want to load it here.
+  ProfileAttributesEntry* GetActiveProfile() const;
+
+  // Returns whether the |profile| is the active profile to use the Nearby
+  // process. Convenience method to calling GetActiveProfile() and manually
+  // comparing if they match.
+  bool IsActiveProfile(Profile* profile) const;
+
+  // Returns if any profile is currently set as the active profile. Note that
+  // the active profile might not be loaded yet.
+  bool IsAnyProfileActive() const;
+
+  // Starts an exclusive usage of the Nearby process for the given |profile|.
+  // This will stop the process if it is currently running for a different
+  // profile. After calling this the client may call any of the GetOrStart*()
+  // methods below to start up a new sandboxed process.
+  void SetActiveProfile(Profile* profile);
+
+  // Removes any stored active profile. This will stop the process if it is
+  // currently running for that profile.
+  void ClearActiveProfile();
+
+  // Gets a pointer to the Nearby Connections interface. If there is currently
+  // no process running this will start a new sandboxed process. This will
+  // only work if the |profile| is currently set as the active profile.
+  // Returns a mojo interface to the Nearby Connections library inside the
+  // sandbox if this |profile| is allowed to access it and nullptr otherwise.
+  // Don't store this pointer as it might get invalid if the process gets
+  // stopped (via the OS or StopProcess()). That event can be observed via
+  // Observer::OnNearbyProcessStopped() and a client can decide to restart the
+  // process (e.g. via backoff timer) if it is still the active profile.
+  NearbyConnectionsMojom* GetOrStartNearbyConnections(Profile* profile);
+
+  // TODO(knollr): Add once the mojo interface for the decoder is submitted.
+  // Gets a pointer to the Nearby Decoder interface. Starts a new process if
+  // there is none running already or reuses an existing one. The same
+  // limitations around profiles and lifetime in GetOrStartNearbyConnections()
+  // apply here as well.
+  // NearbySharingDecoderMojom* GetOrStartNearbyDecoder(Profile* profile);
+
+  // Stops the Nearby process if the |profile| is the active profile. This may
+  // be used to save resources or to force stop any communication of the
+  // Nearby Connections library if it should not be used right now. This will
+  // not change the active profile and can be used to temporarily stop the
+  // process (e.g. on screen lock) while keeping the active profile.
+  void StopProcess(Profile* profile);
+
+  // ProfileManagerObserver:
+  void OnProfileAdded(Profile* profile) override;
+  void OnProfileMarkedForPermanentDeletion(Profile* profile) override;
+
+  // Binds the given |sharing| remote to be used as the interface to the Sharing
+  // process running in a sandbox.
+  void BindSharingProcess(mojo::PendingRemote<sharing::mojom::Sharing> sharing);
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(NearbyProcessManagerTest, AddRemoveObserver);
+  friend class base::NoDestructor<NearbyProcessManager>;
+
+  // This class is a singleton.
+  NearbyProcessManager();
+  ~NearbyProcessManager() override;
+
+  // Launches a new sandboxed process and stops any currently running one. This
+  // process is then used to run the Nearby Connections library. The process
+  // will use the current profile to initialize Nearby Connections as returned
+  // by UsedProfile().
+  void LaunchNewProcess();
+
+  // Binds a new pipe to the Nearby Connections library. May start a new process
+  // if there is none running yet.
+  void BindNearbyConnections();
+
+  // Called by the sandboxed process after initializing the Nearby Connections
+  // library.
+  void OnNearbyConnections(
+      mojo::PendingReceiver<NearbyConnectionsMojom> receiver,
+      mojo::PendingRemote<NearbyConnectionsMojom> remote);
+
+  // Called if any of the mojo interfaces to the sandboxed process disconnects.
+  // If that happens we stop the process and notify all observers via
+  // Observer::OnNearbyProcessStopped().
+  void OnNearbyProcessStopped();
+
+  // The bound remote to a sandboxed process.
+  mojo::Remote<sharing::mojom::Sharing> sharing_process_;
+  // The bound remote to the Nearby Connections library inside the sandbox.
+  mojo::Remote<NearbyConnectionsMojom> connections_;
+  // TODO(knollr): Add once the mojo interface for the decoder is submitted.
+  // The bound remote to the Nearby Decoder interface inside the sandbox.
+  // mojo::Remote<NearbySharingDecoderMojom> decoder_;
+
+  // Host interface for the Nearby Connections interface.
+  mojo::Receiver<NearbyConnectionsHostMojom> connections_host_{this};
+  // All registered observers, typically one per loaded profile.
+  base::ObserverList<Observer> observers_;
+  // Profile using the Nearby process. This might be nullptr if the active
+  // profile has not been loaded yet.
+  Profile* active_profile_ = nullptr;
+};
+
+#endif  // CHROME_BROWSER_NEARBY_SHARING_NEARBY_PROCESS_MANAGER_H_
diff --git a/chrome/browser/nearby_sharing/nearby_process_manager_unittest.cc b/chrome/browser/nearby_sharing/nearby_process_manager_unittest.cc
new file mode 100644
index 0000000..e2f3f6e20
--- /dev/null
+++ b/chrome/browser/nearby_sharing/nearby_process_manager_unittest.cc
@@ -0,0 +1,278 @@
+// Copyright 2020 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/nearby_sharing/nearby_process_manager.h"
+
+#include <memory>
+#include <set>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/run_loop.h"
+#include "chrome/browser/profiles/profile_attributes_entry.h"
+#include "chrome/services/sharing/public/mojom/nearby_connections.mojom.h"
+#include "chrome/services/sharing/public/mojom/nearby_connections_types.mojom.h"
+#include "chrome/services/sharing/public/mojom/sharing.mojom.h"
+#include "chrome/services/sharing/public/mojom/webrtc.mojom.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "chrome/test/base/testing_profile.h"
+#include "chrome/test/base/testing_profile_manager.h"
+#include "content/public/test/browser_task_environment.h"
+#include "content/public/test/test_utils.h"
+#include "mojo/public/cpp/bindings/self_owned_receiver.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using NearbyConnectionsMojom =
+    location::nearby::connections::mojom::NearbyConnections;
+using NearbyConnectionsHostMojom =
+    location::nearby::connections::mojom::NearbyConnectionsHost;
+
+namespace {
+
+class MockNearbyConnections : public NearbyConnectionsMojom {
+ public:
+};
+
+class FakeSharingMojoService : public sharing::mojom::Sharing {
+ public:
+  FakeSharingMojoService() = default;
+  ~FakeSharingMojoService() override = default;
+
+  // sharing::mojom::Sharing:
+  void CreateSharingWebRtcConnection(
+      mojo::PendingRemote<sharing::mojom::SignallingSender>,
+      mojo::PendingReceiver<sharing::mojom::SignallingReceiver>,
+      mojo::PendingRemote<sharing::mojom::SharingWebRtcConnectionDelegate>,
+      mojo::PendingReceiver<sharing::mojom::SharingWebRtcConnection>,
+      mojo::PendingRemote<network::mojom::P2PSocketManager>,
+      mojo::PendingRemote<network::mojom::MdnsResponder>,
+      std::vector<sharing::mojom::IceServerPtr>) override {
+    NOTIMPLEMENTED();
+  }
+  void CreateNearbyConnections(
+      mojo::PendingRemote<NearbyConnectionsHostMojom> host,
+      CreateNearbyConnectionsCallback callback) override {
+    connections_host.Bind(std::move(host));
+
+    mojo::PendingRemote<NearbyConnectionsMojom> remote;
+    mojo::MakeSelfOwnedReceiver(std::make_unique<MockNearbyConnections>(),
+                                remote.InitWithNewPipeAndPassReceiver());
+    std::move(callback).Run(std::move(remote));
+
+    run_loop_connections.Quit();
+  }
+
+  mojo::PendingRemote<sharing::mojom::Sharing> BindSharingService() {
+    return receiver.BindNewPipeAndPassRemote();
+  }
+
+  void WaitForNearbyConnections() { run_loop_connections.Run(); }
+
+  void Reset() { receiver.reset(); }
+
+ private:
+  base::RunLoop run_loop_connections;
+  mojo::Receiver<sharing::mojom::Sharing> receiver{this};
+  mojo::Remote<NearbyConnectionsHostMojom> connections_host;
+};
+
+class MockNearbyProcessManagerObserver : public NearbyProcessManager::Observer {
+ public:
+  MOCK_METHOD1(OnNearbyProfileChanged, void(Profile* profile));
+  MOCK_METHOD0(OnNearbyProcessStarted, void());
+  MOCK_METHOD0(OnNearbyProcessStopped, void());
+};
+
+class NearbyProcessManagerTest : public testing::Test {
+ public:
+  NearbyProcessManagerTest() = default;
+  ~NearbyProcessManagerTest() override = default;
+
+  void SetUp() override { ASSERT_TRUE(testing_profile_manager_.SetUp()); }
+
+  void TearDown() override { DeleteAllProfiles(); }
+
+  Profile* CreateProfile(const std::string& name) {
+    Profile* profile = testing_profile_manager_.CreateTestingProfile(name);
+    profiles_.insert(profile);
+    return profile;
+  }
+
+  void DeleteProfile(Profile* profile) {
+    DoDeleteProfile(profile);
+    profiles_.erase(profile);
+  }
+
+  void DeleteAllProfiles() {
+    for (Profile* profile : profiles_)
+      DoDeleteProfile(profile);
+    profiles_.clear();
+  }
+
+ private:
+  void DoDeleteProfile(Profile* profile) {
+    NearbyProcessManager::GetInstance().OnProfileMarkedForPermanentDeletion(
+        profile);
+    testing_profile_manager_.DeleteTestingProfile(
+        profile->GetProfileUserName());
+  }
+
+  content::BrowserTaskEnvironment task_environment_;
+  content::InProcessUtilityThreadHelper in_process_utility_thread_helper_;
+  TestingProfileManager testing_profile_manager_{
+      TestingBrowserProcess::GetGlobal()};
+  std::set<Profile*> profiles_;
+};
+
+}  // namespace
+
+TEST_F(NearbyProcessManagerTest, AddRemoveObserver) {
+  MockNearbyProcessManagerObserver observer;
+  auto& manager = NearbyProcessManager::GetInstance();
+
+  manager.AddObserver(&observer);
+  EXPECT_TRUE(manager.observers_.HasObserver(&observer));
+
+  manager.RemoveObserver(&observer);
+  EXPECT_FALSE(manager.observers_.HasObserver(&observer));
+}
+
+TEST_F(NearbyProcessManagerTest, SetGetActiveProfile) {
+  auto& manager = NearbyProcessManager::GetInstance();
+  Profile* profile = CreateProfile("name");
+  EXPECT_EQ(nullptr, manager.GetActiveProfile());
+
+  manager.SetActiveProfile(profile);
+  ASSERT_NE(nullptr, manager.GetActiveProfile());
+  EXPECT_EQ(profile->GetPath(), manager.GetActiveProfile()->GetPath());
+}
+
+TEST_F(NearbyProcessManagerTest, IsActiveProfile) {
+  auto& manager = NearbyProcessManager::GetInstance();
+  Profile* profile_1 = CreateProfile("name 1");
+  Profile* profile_2 = CreateProfile("name 2");
+  EXPECT_FALSE(manager.IsActiveProfile(profile_1));
+  EXPECT_FALSE(manager.IsActiveProfile(profile_2));
+
+  manager.SetActiveProfile(profile_1);
+  EXPECT_TRUE(manager.IsActiveProfile(profile_1));
+  EXPECT_FALSE(manager.IsActiveProfile(profile_2));
+
+  manager.SetActiveProfile(profile_2);
+  EXPECT_FALSE(manager.IsActiveProfile(profile_1));
+  EXPECT_TRUE(manager.IsActiveProfile(profile_2));
+
+  manager.ClearActiveProfile();
+  EXPECT_FALSE(manager.IsActiveProfile(profile_1));
+  EXPECT_FALSE(manager.IsActiveProfile(profile_2));
+}
+
+TEST_F(NearbyProcessManagerTest, IsAnyProfileActive) {
+  auto& manager = NearbyProcessManager::GetInstance();
+  Profile* profile = CreateProfile("name");
+
+  EXPECT_FALSE(manager.IsAnyProfileActive());
+
+  manager.SetActiveProfile(profile);
+  EXPECT_TRUE(manager.IsAnyProfileActive());
+
+  manager.ClearActiveProfile();
+  EXPECT_FALSE(manager.IsAnyProfileActive());
+}
+
+TEST_F(NearbyProcessManagerTest, OnProfileDeleted_ActiveProfile) {
+  auto& manager = NearbyProcessManager::GetInstance();
+  Profile* profile_1 = CreateProfile("name 1");
+  Profile* profile_2 = CreateProfile("name 2");
+
+  // Set active profile and delete it.
+  manager.SetActiveProfile(profile_1);
+  manager.OnProfileMarkedForPermanentDeletion(profile_1);
+
+  // No profile should be active now.
+  EXPECT_FALSE(manager.IsActiveProfile(profile_1));
+  EXPECT_FALSE(manager.IsActiveProfile(profile_2));
+}
+
+TEST_F(NearbyProcessManagerTest, OnProfileDeleted_InactiveProfile) {
+  auto& manager = NearbyProcessManager::GetInstance();
+  Profile* profile_1 = CreateProfile("name 1");
+  Profile* profile_2 = CreateProfile("name 2");
+
+  // Set active profile and delete inactive one.
+  manager.SetActiveProfile(profile_1);
+  manager.OnProfileMarkedForPermanentDeletion(profile_2);
+
+  // Active profile should still be active.
+  EXPECT_TRUE(manager.IsActiveProfile(profile_1));
+  EXPECT_FALSE(manager.IsActiveProfile(profile_2));
+}
+
+TEST_F(NearbyProcessManagerTest, StartStopProcess) {
+  auto& manager = NearbyProcessManager::GetInstance();
+  Profile* profile = CreateProfile("name");
+  manager.SetActiveProfile(profile);
+
+  MockNearbyProcessManagerObserver observer;
+  base::RunLoop run_loop_started;
+  base::RunLoop run_loop_stopped;
+  EXPECT_CALL(observer, OnNearbyProcessStarted())
+      .WillOnce(testing::Invoke(&run_loop_started, &base::RunLoop::Quit));
+  EXPECT_CALL(observer, OnNearbyProcessStopped())
+      .WillOnce(testing::Invoke(&run_loop_stopped, &base::RunLoop::Quit));
+  manager.AddObserver(&observer);
+
+  // Start up a new process and wait for it to launch.
+  EXPECT_NE(nullptr, manager.GetOrStartNearbyConnections(profile));
+  run_loop_started.Run();
+
+  // Stop the process and wait for it to finish.
+  manager.StopProcess(profile);
+  run_loop_stopped.Run();
+
+  // Active profile should still be active.
+  EXPECT_TRUE(manager.IsActiveProfile(profile));
+
+  manager.RemoveObserver(&observer);
+}
+
+TEST_F(NearbyProcessManagerTest, GetOrStartNearbyConnections) {
+  auto& manager = NearbyProcessManager::GetInstance();
+  Profile* profile = CreateProfile("name");
+  manager.SetActiveProfile(profile);
+
+  // Inject fake Nearby process mojo connection.
+  FakeSharingMojoService fake_sharing_service;
+  manager.BindSharingProcess(fake_sharing_service.BindSharingService());
+
+  // Request a new Nearby Connections interface.
+  EXPECT_NE(nullptr, manager.GetOrStartNearbyConnections(profile));
+  // Expect the manager to bind a new Nearby Connections pipe.
+  fake_sharing_service.WaitForNearbyConnections();
+}
+
+TEST_F(NearbyProcessManagerTest, ResetNearbyProcess) {
+  auto& manager = NearbyProcessManager::GetInstance();
+  Profile* profile = CreateProfile("name");
+  manager.SetActiveProfile(profile);
+
+  // Inject fake Nearby process mojo connection.
+  FakeSharingMojoService fake_sharing_service;
+  manager.BindSharingProcess(fake_sharing_service.BindSharingService());
+
+  MockNearbyProcessManagerObserver observer;
+  base::RunLoop run_loop;
+  EXPECT_CALL(observer, OnNearbyProcessStopped())
+      .WillOnce(testing::Invoke(&run_loop, &base::RunLoop::Quit));
+  manager.AddObserver(&observer);
+
+  // Simulate a dropped mojo connection to the Nearby process.
+  fake_sharing_service.Reset();
+
+  // Expect the OnNearbyProcessStopped() callback to run.
+  run_loop.Run();
+
+  manager.RemoveObserver(&observer);
+}
diff --git a/chrome/browser/nearby_sharing/nearby_sharing_prefs.cc b/chrome/browser/nearby_sharing/nearby_sharing_prefs.cc
index 646028d..5b17e83 100644
--- a/chrome/browser/nearby_sharing/nearby_sharing_prefs.cc
+++ b/chrome/browser/nearby_sharing/nearby_sharing_prefs.cc
@@ -4,10 +4,16 @@
 
 #include "chrome/browser/nearby_sharing/nearby_sharing_prefs.h"
 
+#include <string>
+
+#include "base/files/file_path.h"
 #include "components/pref_registry/pref_registry_syncable.h"
 #include "components/prefs/pref_registry.h"
+#include "components/prefs/pref_registry_simple.h"
 
 const char kNearbySharingEnabledPrefName[] = "nearby_sharing.enabled";
+const char kNearbySharingActiveProfilePrefName[] =
+    "nearby_sharing.active_profile";
 
 void RegisterNearbySharingPrefs(user_prefs::PrefRegistrySyncable* registry) {
   // This pref is not synced.
@@ -17,3 +23,8 @@
       kNearbySharingEnabledPrefName, true /* default_value */,
       PrefRegistry::PrefRegistrationFlags::NO_REGISTRATION_FLAGS /* flags */);
 }
+
+void RegisterNearbySharingLocalPrefs(PrefRegistrySimple* local_state) {
+  local_state->RegisterFilePathPref(kNearbySharingActiveProfilePrefName,
+                                    base::FilePath() /* default_value */);
+}
diff --git a/chrome/browser/nearby_sharing/nearby_sharing_prefs.h b/chrome/browser/nearby_sharing/nearby_sharing_prefs.h
index 87965f7..487dbed 100644
--- a/chrome/browser/nearby_sharing/nearby_sharing_prefs.h
+++ b/chrome/browser/nearby_sharing/nearby_sharing_prefs.h
@@ -9,8 +9,13 @@
 class PrefRegistrySyncable;
 }  // namespace user_prefs
 
+class PrefRegistrySimple;
+
 extern const char kNearbySharingEnabledPrefName[];
+extern const char kNearbySharingActiveProfilePrefName[];
 
 void RegisterNearbySharingPrefs(user_prefs::PrefRegistrySyncable* registry);
 
+void RegisterNearbySharingLocalPrefs(PrefRegistrySimple* local_state);
+
 #endif  // CHROME_BROWSER_NEARBY_SHARING_NEARBY_SHARING_PREFS_H_
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 6c67f2e9..51b7ecbeb 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -241,6 +241,7 @@
 #include "chrome/browser/gcm/gcm_product_util.h"
 #include "chrome/browser/media/unified_autoplay_config.h"
 #include "chrome/browser/metrics/tab_stats_tracker.h"
+#include "chrome/browser/nearby_sharing/nearby_sharing_prefs.h"
 #include "chrome/browser/search/instant_service.h"
 #include "chrome/browser/search/promos/promo_service.h"
 #include "chrome/browser/search/search_suggest/search_suggest_service.h"
@@ -704,6 +705,7 @@
   media_router::RegisterLocalStatePrefs(registry);
   metrics::TabStatsTracker::RegisterPrefs(registry);
   RegisterBrowserPrefs(registry);
+  RegisterNearbySharingLocalPrefs(registry);
   StartupBrowserCreator::RegisterLocalStatePrefs(registry);
   task_manager::TaskManagerInterface::RegisterPrefs(registry);
   UpgradeDetector::RegisterPrefs(registry);
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 4f22613..cf80551 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3560,6 +3560,7 @@
       "../browser/content_settings/generated_cookie_prefs_unittest.cc",
       "../browser/device_identity/device_oauth2_token_service_unittest.cc",
       "../browser/nearby_sharing/fast_initiation_manager_unittest.cc",
+      "../browser/nearby_sharing/nearby_process_manager_unittest.cc",
       "../browser/profiles/profile_avatar_icon_util_unittest.cc",
       "../browser/sharing/webrtc/sharing_service_host_unittest.cc",
       "../browser/sharing/webrtc/sharing_webrtc_connection_host_unittest.cc",