[session_shell] sessionmgr uses SessionShell

Use the SessionShell service of the session shell to forward
views of stories started by RequestStart() calling AttachView().

If a story was started by RequestStart(), and it's stopped,
then the session shell is also called with DetachView().

Use the RequestStart() method in one test case in session_shell_test.
It is not yet used anywhere else. After this is submitted, clients
can be migrated from using Start() to RequestStart(). Once Start()
is no longer used, it can be removed.

MF-121 #comment sessionmgr uses SessionShell

TESTED=Added test case to session_shell integration test.

Change-Id: I03704d87b64708c61ee2959e7b1f8ee6991274c3
diff --git a/bin/sessionmgr/sessionmgr_impl.cc b/bin/sessionmgr/sessionmgr_impl.cc
index 12e2d08..305eed8 100644
--- a/bin/sessionmgr/sessionmgr_impl.cc
+++ b/bin/sessionmgr/sessionmgr_impl.cc
@@ -680,7 +680,7 @@
 }
 
 void SessionmgrImpl::RunSessionShell(
-    fuchsia::modular::AppConfig session_shell) {
+    fuchsia::modular::AppConfig session_shell_config) {
   // |session_shell_services_| is a ServiceProvider (aka a Directory) that will
   // be used to augment the session shell's namespace.
   session_shell_services_.AddService<fuchsia::modular::SessionShellContext>(
@@ -715,7 +715,7 @@
   service_list->provider = std::move(session_shell_service_provider_ptr_);
 
   session_shell_app_ = std::make_unique<AppClient<fuchsia::modular::Lifecycle>>(
-      user_environment_->GetLauncher(), std::move(session_shell),
+      user_environment_->GetLauncher(), std::move(session_shell_config),
       /* data_origin = */ "", std::move(service_list));
 
   session_shell_app_->SetAppErrorHandler([this] {
@@ -729,6 +729,10 @@
   session_shell_app_->services().ConnectToService(view_provider.NewRequest());
   view_provider->CreateView(view_owner.NewRequest(), nullptr);
   session_shell_view_host_->ConnectView(std::move(view_owner));
+
+  fuchsia::modular::SessionShellPtr session_shell;
+  session_shell_app_->services().ConnectToService(session_shell.NewRequest());
+  story_provider_impl_->SetSessionShell(std::move(session_shell));
 }
 
 void SessionmgrImpl::TerminateSessionShell(const std::function<void()>& done) {
diff --git a/bin/sessionmgr/sessionmgr_impl.h b/bin/sessionmgr/sessionmgr_impl.h
index c2a67f8..2b9d5b8 100644
--- a/bin/sessionmgr/sessionmgr_impl.h
+++ b/bin/sessionmgr/sessionmgr_impl.h
@@ -209,6 +209,7 @@
 
   fidl::BindingSet<fuchsia::modular::internal::Sessionmgr> bindings_;
   component::ServiceProviderImpl session_shell_services_;
+
   fidl::BindingSet<fuchsia::modular::SessionShellContext>
       session_shell_context_bindings_;
 
diff --git a/bin/sessionmgr/story_runner/story_controller_impl.cc b/bin/sessionmgr/story_runner/story_controller_impl.cc
index c3fcd5f..3b16646 100644
--- a/bin/sessionmgr/story_runner/story_controller_impl.cc
+++ b/bin/sessionmgr/story_runner/story_controller_impl.cc
@@ -504,6 +504,14 @@
     }
     story_controller_impl_->SetState(fuchsia::modular::StoryState::STOPPING);
 
+    story_controller_impl_->DetachView([this, weak_this = GetWeakPtr()] {
+                                         if (weak_this) {
+                                           StopStory();
+                                         }
+                                       });
+  }
+
+  void StopStory() {
     std::vector<FuturePtr<>> did_teardowns;
     did_teardowns.reserve(story_controller_impl_->running_mod_infos_.size());
 
@@ -957,12 +965,13 @@
   void Run() override {
     FlowToken flow{this};
 
-    // If the story is running, we do nothing and close the view owner request.
+    // If the story is running, we do nothing.
     if (story_controller_impl_->IsRunning()) {
       FXL_LOG(INFO)
           << "StoryControllerImpl::StartCall() while already running: ignored.";
       return;
     }
+
     story_controller_impl_->StartStoryShell(std::move(request_));
 
     // Start all modules that were not themselves explicitly started by another
@@ -1421,6 +1430,11 @@
   operation_queue_.Add(new StartCall(this, story_storage_, std::move(request)));
 }
 
+void StoryControllerImpl::RequestStart() {
+  operation_queue_.Add(new StartCall(this, story_storage_,
+                                     nullptr /* ViewOwner request */));
+}
+
 void StoryControllerImpl::Stop(StopCallback done) {
   operation_queue_.Add(new StopCall(this, done));
 }
@@ -1544,6 +1558,14 @@
 
 void StoryControllerImpl::StartStoryShell(
     fidl::InterfaceRequest<fuchsia::ui::viewsv1token::ViewOwner> request) {
+  if (!request) {
+    // The start call originated in RequestStart() rather than Start().
+    fuchsia::ui::viewsv1token::ViewOwnerPtr view_owner;
+    request = view_owner.NewRequest();
+    story_provider_impl_->AttachView(story_id_, std::move(view_owner));
+    needs_detach_view_ = true;
+  }
+
   story_shell_app_ =
       story_provider_impl_->StartStoryShell(story_id_, std::move(request));
   story_shell_app_->services().ConnectToService(story_shell_.NewRequest());
@@ -1554,6 +1576,15 @@
       fit::bind_member(this, &StoryControllerImpl::OnSurfaceFocused);
 }
 
+void StoryControllerImpl::DetachView(std::function<void()> done) {
+  if (needs_detach_view_) {
+    story_provider_impl_->DetachView(story_id_, std::move(done));
+    needs_detach_view_ = false;
+  } else {
+    done();
+  }
+}
+
 void StoryControllerImpl::SetState(
     const fuchsia::modular::StoryState new_state) {
   if (new_state == state_) {
diff --git a/bin/sessionmgr/story_runner/story_controller_impl.h b/bin/sessionmgr/story_runner/story_controller_impl.h
index 1dd366f..6d9ca7c 100644
--- a/bin/sessionmgr/story_runner/story_controller_impl.h
+++ b/bin/sessionmgr/story_runner/story_controller_impl.h
@@ -174,6 +174,7 @@
   void GetInfo(GetInfoCallback callback) override;
   void Start(fidl::InterfaceRequest<fuchsia::ui::viewsv1token::ViewOwner>
                  request) override;
+  void RequestStart() override;
   void TakeAndLoadSnapshot(
       fidl::InterfaceRequest<fuchsia::ui::viewsv1token::ViewOwner> request,
       TakeAndLoadSnapshotCallback done) override;
@@ -193,9 +194,10 @@
   void GetLink(fuchsia::modular::LinkPath link_path,
                fidl::InterfaceRequest<fuchsia::modular::Link> request) override;
 
-  // Phases of Start() broken out into separate methods.
+  // Communicates with SessionShell.
   void StartStoryShell(
       fidl::InterfaceRequest<fuchsia::ui::viewsv1token::ViewOwner> request);
+  void DetachView(std::function<void()> done);
 
   // Called whenever |story_storage_| sees an updated ModuleData from another
   // device.
@@ -226,6 +228,11 @@
   // persist it to.
   const fidl::StringPtr story_id_;
 
+  // True once AttachView() was called. Temporarily needed during transition
+  // from Start() to RequestStart(), will be removed once Start() is removed.
+  // Cf. MF-121.
+  bool needs_detach_view_{};
+
   // This is the canonical source for state. This state is per device and only
   // kept in memory.
   fuchsia::modular::StoryState state_{fuchsia::modular::StoryState::STOPPED};
diff --git a/bin/sessionmgr/story_runner/story_provider_impl.cc b/bin/sessionmgr/story_runner/story_provider_impl.cc
index bf0723b..92fc38a 100644
--- a/bin/sessionmgr/story_runner/story_provider_impl.cc
+++ b/bin/sessionmgr/story_runner/story_provider_impl.cc
@@ -333,6 +333,22 @@
   operation_queue_.Add(new StopAllStoriesCall(this, callback));
 }
 
+void StoryProviderImpl::SetSessionShell(
+    fuchsia::modular::SessionShellPtr session_shell) {
+  // Not on operation queue, because it's called only after all stories have
+  // been stopped or none are running yet, i.e. when no Operations that would
+  // call this interface are scheduled. If there is an operation pending here,
+  // then it would pertain to a story running in the new session shell started
+  // by puppet master or an agent, so we must assign this now.
+  //
+  // TODO(mesch): It may well be that we need to revisit this when we support
+  // starting stories, or swapping session shells, through puppet master, i.e.
+  // from outside the session shell.
+  //
+  // TODO(mesch): Add a WARNING log if the operation is not empty.
+  session_shell_ = std::move(session_shell);
+}
+
 void StoryProviderImpl::Teardown(const std::function<void()>& callback) {
   // Closing all binding to this instance ensures that no new messages come
   // in, though previous messages need to be processed. The stopping of
@@ -448,6 +464,22 @@
   focus_provider_->Request(story_id);
 }
 
+void StoryProviderImpl::AttachView(
+    fidl::StringPtr story_id, fuchsia::ui::viewsv1token::ViewOwnerPtr view_owner) {
+  FXL_CHECK(session_shell_);
+  fuchsia::modular::ViewIdentifier view_id;
+  view_id.story_id = std::move(story_id);
+  session_shell_->AttachView(std::move(view_id), std::move(view_owner));
+}
+
+void StoryProviderImpl::DetachView(fidl::StringPtr story_id,
+                                   std::function<void()> done) {
+  FXL_CHECK(session_shell_);
+  fuchsia::modular::ViewIdentifier view_id;
+  view_id.story_id = std::move(story_id);
+  session_shell_->DetachView(std::move(view_id), std::move(done));
+}
+
 void StoryProviderImpl::NotifyStoryStateChange(
     fidl::StringPtr story_id, const fuchsia::modular::StoryState story_state,
     const fuchsia::modular::StoryVisibilityState story_visibility_state) {
diff --git a/bin/sessionmgr/story_runner/story_provider_impl.h b/bin/sessionmgr/story_runner/story_provider_impl.h
index 8aac267..f6cdb10 100644
--- a/bin/sessionmgr/story_runner/story_provider_impl.h
+++ b/bin/sessionmgr/story_runner/story_provider_impl.h
@@ -64,8 +64,13 @@
 
   void Connect(fidl::InterfaceRequest<fuchsia::modular::StoryProvider> request);
 
+  // Used when the session shell is swapped.
   void StopAllStories(const std::function<void()>& callback);
 
+  // The session shell to send story views to. It is not a constructor argument
+  // because it is updated when the session shell is swapped.
+  void SetSessionShell(fuchsia::modular::SessionShellPtr session_shell);
+
   // Stops serving the fuchsia::modular::StoryProvider interface and stops all
   // stories.
   void Teardown(const std::function<void()>& callback);
@@ -120,6 +125,16 @@
   // fuchsia::modular::FocusProvider
   void RequestStoryFocus(fidl::StringPtr story_id);
 
+  // Called by StoryControllerImpl. Sends, using AttachView(), the view of the
+  // story identified by |story_id| to the current session shell.
+  void AttachView(
+      fidl::StringPtr story_id, fuchsia::ui::viewsv1token::ViewOwnerPtr view_owner);
+
+  // Called by StoryControllerImpl. Notifies, using DetachView(), the current
+  // session shell that the view of the story identified by |story_id| is about
+  // to close.
+  void DetachView(fidl::StringPtr story_id, std::function<void()> done);
+
   // Called by StoryControllerImpl.
   void NotifyStoryStateChange(
       fidl::StringPtr story_id, fuchsia::modular::StoryState story_state,
@@ -222,6 +237,10 @@
 
   SessionStorage* session_storage_;  // Not owned.
 
+  // The service from the session shell run by the sessionmgr. Owned here
+  // because only used from here.
+  fuchsia::modular::SessionShellPtr session_shell_;
+
   // Unique ID generated for this user/device combination.
   const std::string device_id_;
 
diff --git a/lib/testing/story_controller_mock.h b/lib/testing/story_controller_mock.h
index 3246eef..b8e4ac8 100644
--- a/lib/testing/story_controller_mock.h
+++ b/lib/testing/story_controller_mock.h
@@ -40,6 +40,11 @@
   }
 
   // |fuchsia::modular::StoryController|
+  void RequestStart() override {
+    FXL_NOTIMPLEMENTED();
+  }
+
+  // |fuchsia::modular::StoryController|
   void Stop(StopCallback done) override { FXL_NOTIMPLEMENTED(); }
 
   // |fuchsia::modular::StoryController|
diff --git a/public/fidl/fuchsia.modular/story/story_controller.fidl b/public/fidl/fuchsia.modular/story/story_controller.fidl
index 9492818..0be4264 100644
--- a/public/fidl/fuchsia.modular/story/story_controller.fidl
+++ b/public/fidl/fuchsia.modular/story/story_controller.fidl
@@ -15,6 +15,8 @@
     // Gets information associated with the story.
     1: GetInfo() -> (StoryInfo info, StoryState state);
 
+    // DEPRECATED in favor of RequestStart().
+    //
     // Runs the story controlled by this |StoryController| instance if not yet
     // running or requested to start, else does nothing. |view_owner| is an
     // interface request for the view of the story shell of this story. If the
@@ -23,6 +25,11 @@
     // until the Stop() requests complete.
     3: Start(request<fuchsia.ui.viewsv1token.ViewOwner> view_owner);
 
+    // Requests to runsthe story controlled by this |StoryController| instance.
+    // When the story starts, if not yet running, the view of the newly started
+    // story shell will be passed in a call to SessionShell.AttachView().
+    14: RequestStart();
+
     // Requests to stop the story controlled by this |StoryController|. If Start()
     // requests are pending when this request is issued, the request is queued
     // until the Start() requests complete. Before stopping the story, a snapshot
diff --git a/tests/session_shell/README.md b/tests/session_shell/README.md
index a75495e..e3c30d3 100644
--- a/tests/session_shell/README.md
+++ b/tests/session_shell/README.md
@@ -6,10 +6,15 @@
 
 * See existing stories as long as they are not deleted.
 
-* Add modules to existign stories.
+* Add modules to existing stories.
 
 * Be notified when story state changes.
 
+* Be notified through SessionShell.AttachView() of a new story view when story
+  start is requested by RequestStart() rather than Start().
+
+* Be notified through SessionShell.DetachView() of a story going away.
+
 The test code is invoked as a session shell from basemgr and executes a
 predefined sequence of steps, rather than to expose a UI to be driven by user
 interaction, as a session shell normally would. I.e. the test is implemented as a
diff --git a/tests/session_shell/session_shell_test_session_shell.cc b/tests/session_shell/session_shell_test_session_shell.cc
index 757e267..701c6d0 100644
--- a/tests/session_shell/session_shell_test_session_shell.cc
+++ b/tests/session_shell/session_shell_test_session_shell.cc
@@ -13,6 +13,7 @@
 #include <lib/async/default.h>
 #include <lib/component/cpp/startup_context.h>
 #include <lib/fidl/cpp/binding.h>
+#include <lib/fidl/cpp/binding_set.h>
 #include <lib/fsl/vmo/sized_vmo.h>
 #include <lib/fsl/vmo/strings.h>
 #include <lib/fxl/command_line.h>
@@ -28,9 +29,13 @@
 #include "peridot/tests/session_shell/defs.h"
 
 using modular::testing::Await;
+using modular::testing::Fail;
 using modular::testing::Signal;
 using modular::testing::TestPoint;
 
+using fuchsia::modular::SessionShell;
+using fuchsia::modular::ViewIdentifier;
+
 namespace {
 
 // A simple story provider watcher implementation. Just logs observed state
@@ -135,9 +140,54 @@
 
 // Cf. README.md for what this test does in general and how. The test cases are
 // described in detail in comments below.
+class SessionShellImpl : fuchsia::modular::SessionShell {
+ public:
+  SessionShellImpl() = default;
+  ~SessionShellImpl() = default;
+
+  using ViewId = fuchsia::modular::ViewIdentifier;
+
+  fidl::InterfaceRequestHandler<fuchsia::modular::SessionShell> GetHandler() {
+    return bindings_.GetHandler(this);
+  }
+
+  void set_on_attach_view(std::function<void(ViewId view_id)> callback) {
+    on_attach_view_ = callback;
+  }
+
+  void set_on_detach_view(std::function<void(ViewId view_id)> callback) {
+    on_detach_view_ = callback;
+  }
+
+ private:
+  // |SessionShell|
+  void AttachView(fuchsia::modular::ViewIdentifier view_id,
+                  fidl::InterfaceHandle<fuchsia::ui::viewsv1token::ViewOwner>
+                  view_owner) override {
+    on_attach_view_(std::move(view_id));
+  }
+
+  // |SessionShell|
+  void DetachView(fuchsia::modular::ViewIdentifier view_id,
+                  std::function<void()> done) override {
+    on_detach_view_(std::move(view_id));
+    done();
+  }
+
+  fidl::BindingSet<fuchsia::modular::SessionShell> bindings_;
+  std::function<void(ViewId view_id)> on_attach_view_{[](ViewId) {}};
+  std::function<void(ViewId view_id)> on_detach_view_{[](ViewId) {}};
+
+  FXL_DISALLOW_COPY_AND_ASSIGN(SessionShellImpl);
+};
+
+
+// Cf. README.md for what this test does and how.
 class TestApp : public modular::testing::ComponentBase<void>
 {
  public:
+  using ViewId = fuchsia::modular::ViewIdentifier;
+
   explicit TestApp(component::StartupContext* const startup_context)
       : ComponentBase(startup_context) {
     TestInit(__FILE__);
@@ -149,6 +199,20 @@
     session_shell_context_->GetStoryProvider(story_provider_.NewRequest());
     story_provider_state_watcher_.Watch(&story_provider_);
 
+    startup_context->outgoing().AddPublicService(
+        session_shell_impl_.GetHandler());
+
+    // Until we use RequestStart() for the first time, there must be no calls on
+    // the SessionShell service.
+    session_shell_impl_.set_on_attach_view(
+        [](ViewId view_id) {
+          Fail("AttachView() called without RequestStart().");
+        });
+    session_shell_impl_.set_on_detach_view(
+        [](ViewId view_id) {
+          Fail("DetachView() called without RequestStart().");
+        });
+
     TestStoryProvider_GetStoryInfo_Null();
   }
 
@@ -502,11 +566,106 @@
       story3_info_after_delete_.Pass();
     }
 
+    TestStory4();
+  }
+
+  // Test Case Story4:
+  //
+  // Create a story and start it with RequestStart() rather than Start().
+  //
+  // Verify the view is received through SessionShell.AttachView().
+  //
+  // Verify that, when the story is stopped, a request for
+  // SessionShell.DetachView() is received.
+
+  TestPoint story4_create_{"Story4 Create"};
+
+  void TestStory4() {
+    puppet_master_->ControlStory("story4", story_puppet_master_.NewRequest());
+
+    fuchsia::modular::AddMod add_mod;
+    add_mod.mod_name.push_back("mod1");
+    add_mod.intent.handler = kCommonNullModule;
+
+    fuchsia::modular::StoryCommand command;
+    command.set_add_mod(std::move(add_mod));
+
+    fidl::VectorPtr<fuchsia::modular::StoryCommand> commands;
+    commands.push_back(std::move(command));
+
+    story_puppet_master_->Enqueue(std::move(commands));
+    story_puppet_master_->Execute(
+        [this](fuchsia::modular::ExecuteResult result) {
+          story4_create_.Pass();
+          TestStory4_Run();
+        });
+  }
+
+  TestPoint story4_state_before_run_{"Story4 State before Run"};
+  TestPoint story4_state_after_run_{"Story4 State after Run"};
+
+  TestPoint story4_attach_view_{"Story4 attach View"};
+  TestPoint story4_detach_view_{"Story4 detach View"};
+
+  void TestStory4_Run() {
+    story_provider_->GetController("story4", story_controller_.NewRequest());
+
+    story_controller_->GetInfo([this](fuchsia::modular::StoryInfo info,
+                                      fuchsia::modular::StoryState state) {
+      story_info_ = std::move(info);
+      if (state == fuchsia::modular::StoryState::STOPPED) {
+        story4_state_before_run_.Pass();
+      }
+    });
+
+    // Start and show the new story using RequestStart().
+    story_controller_->RequestStart();
+
+    session_shell_impl_.set_on_attach_view(
+        [this](ViewId) {
+          story4_attach_view_.Pass();
+        });
+
+    story_controller_->GetInfo([this](fuchsia::modular::StoryInfo info,
+                                      fuchsia::modular::StoryState state) {
+      if (state == fuchsia::modular::StoryState::RUNNING) {
+        story4_state_after_run_.Pass();
+        TestStory4_DeleteStory();
+      }
+    });
+  }
+
+  TestPoint story4_delete_{"Story4 Delete"};
+
+  void TestStory4_DeleteStory() {
+    puppet_master_->DeleteStory(story_info_.id,
+                                [this] { story4_delete_.Pass(); });
+
+    session_shell_impl_.set_on_detach_view(
+        [this](ViewId) {
+          story4_detach_view_.Pass();
+        });
+
+    story_provider_->GetStoryInfo(
+        story_info_.id, [this](fuchsia::modular::StoryInfoPtr info) {
+          TestStory4_InfoAfterDeleteIsNull(std::move(info));
+        });
+
+  }
+
+  TestPoint story4_info_after_delete_{"Story4 Info after Delete is null"};
+
+  void TestStory4_InfoAfterDeleteIsNull(fuchsia::modular::StoryInfoPtr info) {
+    if (!info) {
+      story4_info_after_delete_.Pass();
+    }
+
     Signal(modular::testing::kTestShutdown);
   }
 
   void TeardownStoryController() { story_controller_.Unbind(); }
 
+  SessionShellImpl session_shell_impl_;
   StoryProviderStateWatcherImpl story_provider_state_watcher_;
 
   fuchsia::modular::SessionShellContextPtr session_shell_context_;