[session] Add ElementManager example

This adds an example session which integrates a single element proposer
and handles ProposeElement requests.

BUG: 38577

Change-Id: I7a548dc3ef2ad3600b97eee6586206f4beea67ac
diff --git a/src/session/BUILD.gn b/src/session/BUILD.gn
index 02ee52c..6234ec8 100644
--- a/src/session/BUILD.gn
+++ b/src/session/BUILD.gn
@@ -15,7 +15,8 @@
   testonly = true
 
   deps = [
-    "//src/session/example_sessions/graphical_session",
+    "//src/session/examples/elements",
+    "//src/session/examples/graphical_session",
   ]
 }
 
diff --git a/src/session/component_manager/meta/component_manager_sfw.cmx b/src/session/component_manager/meta/component_manager_sfw.cmx
index a10f74d..fa0eb05 100644
--- a/src/session/component_manager/meta/component_manager_sfw.cmx
+++ b/src/session/component_manager/meta/component_manager_sfw.cmx
@@ -11,6 +11,7 @@
             "fuchsia.pkg.PackageResolver",
             "fuchsia.process.Launcher",
             "fuchsia.logger.LogSink",
+            "fuchsia.sys.Loader",
             "fuchsia.ui.scenic.Scenic"
         ]
     }
diff --git a/src/session/element_management/src/lib.rs b/src/session/element_management/src/lib.rs
index 70ce33b..f22939a 100644
--- a/src/session/element_management/src/lib.rs
+++ b/src/session/element_management/src/lib.rs
@@ -100,7 +100,7 @@
 /// tracking which elements are running, de-duplicating elements, etc.).
 pub struct SimpleElementManager {
     /// The realm which this element manager uses to create components.
-    realm: fsys::RealmProxy
+    realm: fsys::RealmProxy,
 }
 
 impl SimpleElementManager {
diff --git a/src/session/examples/elements/BUILD.gn b/src/session/examples/elements/BUILD.gn
new file mode 100644
index 0000000..4f82154
--- /dev/null
+++ b/src/session/examples/elements/BUILD.gn
@@ -0,0 +1,11 @@
+# Copyright 2019 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+group("elements") {
+  deps = [
+    "//src/session/examples/elements/element_proposer",
+    "//src/session/examples/elements/element_session",
+    "//src/session/examples/elements/simple_element",
+  ]
+}
diff --git a/src/session/examples/elements/README.md b/src/session/examples/elements/README.md
new file mode 100644
index 0000000..44dddb3
--- /dev/null
+++ b/src/session/examples/elements/README.md
@@ -0,0 +1,74 @@
+## Overview
+
+This directory contains an example implementation of a session that 
+instantiates a single element proposer. 
+
+The element proposer connects to the [`ElementManager`]() service offered by the
+session, and uses said service to add a [`simple_element`]() to the session.
+
+The `simple_element` then connects to the [`ElementPing`] service exposed to it
+by the session, and throug it notifies the session that it has been successfully
+instantiated.
+
+## Element Session
+
+The element session is configured to:
+
+  1. Declare a static child: the [`element_proposer`]().
+  2. Declare a child collection with a name which matches the one passed to the
+  [`ElementManagement`]() library.
+  3. Expose the `ElementManager` service.
+  4. Expose the `ElementPing` service.
+  5. Offer the `ElementManager` service to the `element_proposer` child.
+  6. Offer the `ElementPing` service to the child collection mentioned above.
+
+Details of how this is done can be found in the [`element_session.cml`]() file.
+
+Once the session is launched, it exposes the aforementioned services and starts
+handling requests.
+
+## Element Proposer
+
+The element proposer is configured to:
+
+  1. Use the `ElementManager` service.
+  
+Details of how this is done can be found in the [`element_proposer.cml`]() file.
+
+Once the element proposer is started it connects to the `ElementManager` service
+and attempts to add an element (`simple_element`) to the session.
+
+## Simple Element
+
+The simple element is configured to:
+
+  1. Use the `ElementPing` service.
+  
+Details of how this is done can be found in the [`simple_element.cml`]() file.
+
+Once the simple element is started, it will call `ElementPing::Ping()`. The
+session will receive the ping and log a confirmation.
+
+
+## How to Run
+
+To run the example, first edit the session manager cml file to set the element
+session's component url as the argument:
+
+```
+"args": [ "-s", "fuchsia-pkg://fuchsia.com/element_session#meta/element_session.cm" ],
+```
+
+TODO(37028): Update this when sessions can be configured without modifying the
+session manager's CML file.
+
+Then run the following commands:
+
+```$sh
+fx set core.x64 --with //src/session:session_framework --with //src/session:examples
+fx build
+fx shell run fuchsia-pkg://fuchsia.com/component_manager_sfw#meta/component_manager_sfw.cmx fuchsia-pkg://fuchsia.com/session_manager#meta/session_manager.cm
+```
+
+The last command should output a message stating that the element's ping has
+been received.
diff --git a/src/session/examples/elements/element_proposer/BUILD.gn b/src/session/examples/elements/element_proposer/BUILD.gn
new file mode 100644
index 0000000..f045a49
--- /dev/null
+++ b/src/session/examples/elements/element_proposer/BUILD.gn
@@ -0,0 +1,38 @@
+# Copyright 2019 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/package.gni")
+import("//build/rust/rustc_binary.gni")
+
+rustc_binary("element_proposer_bin") {
+  name = "element_proposer"
+  edition = "2018"
+
+  deps = [
+    "//garnet/public/lib/fidl/rust/fidl",
+    "//garnet/public/rust/fuchsia-async",
+    "//garnet/public/rust/fuchsia-component",
+    "//sdk/fidl/fuchsia.session:fuchsia.session-rustc",
+    "//third_party/rust_crates:failure",
+  ]
+}
+
+package("element_proposer") {
+  deps = [
+    ":element_proposer_bin",
+  ]
+
+  meta = [
+    {
+      path = rebase_path("meta/element_proposer.cml")
+      dest = "element_proposer.cm"
+    },
+  ]
+
+  binaries = [
+    {
+      name = "element_proposer"
+    },
+  ]
+}
diff --git a/src/session/examples/elements/element_proposer/meta/element_proposer.cml b/src/session/examples/elements/element_proposer/meta/element_proposer.cml
new file mode 100644
index 0000000..1f65764
--- /dev/null
+++ b/src/session/examples/elements/element_proposer/meta/element_proposer.cml
@@ -0,0 +1,10 @@
+{
+    "program": {
+        "binary": "bin/element_proposer"
+    },
+    "use": [
+        {
+            "legacy_service": "/svc/fuchsia.session.ElementManager"
+        },
+    ]
+}
\ No newline at end of file
diff --git a/src/session/examples/elements/element_proposer/src/main.rs b/src/session/examples/elements/element_proposer/src/main.rs
new file mode 100644
index 0000000..313665f
--- /dev/null
+++ b/src/session/examples/elements/element_proposer/src/main.rs
@@ -0,0 +1,28 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+
+use {
+    failure::{format_err, Error, ResultExt},
+    fidl_fuchsia_session::{ElementManagerMarker, ElementSpec},
+    fuchsia_async as fasync,
+    fuchsia_component::client::connect_to_service,
+};
+
+#[fasync::run_singlethreaded]
+async fn main() -> Result<(), Error> {
+    let element_manager = connect_to_service::<ElementManagerMarker>()
+        .context("Could not connect to element manager service.")?;
+
+    element_manager
+        .propose_element(ElementSpec {
+            component_url: Some(
+                "fuchsia-pkg://fuchsia.com/simple_element#meta/simple_element.cm".to_string(),
+            ),
+        })
+        .await?
+        .map_err(|_| format_err!("Error sending ProposeElement message"))?;
+
+    Ok(())
+}
diff --git a/src/session/examples/elements/element_session/BUILD.gn b/src/session/examples/elements/element_session/BUILD.gn
new file mode 100644
index 0000000..ac39112
--- /dev/null
+++ b/src/session/examples/elements/element_session/BUILD.gn
@@ -0,0 +1,43 @@
+# Copyright 2019 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/package.gni")
+import("//build/rust/rustc_binary.gni")
+
+rustc_binary("element_session_bin") {
+  name = "element_session"
+  edition = "2018"
+
+  deps = [
+    "//garnet/public/lib/fidl/rust/fidl",
+    "//garnet/public/rust/fuchsia-async",
+    "//garnet/public/rust/fuchsia-component",
+    "//sdk/fidl/fuchsia.session:fuchsia.session-rustc",
+    "//sdk/fidl/fuchsia.sys2:fuchsia.sys2-rustc",
+    "//src/session/element_management",
+    "//src/session/examples/elements/fidl:session_examples-rustc",
+    "//third_party/rust_crates:failure",
+    "//third_party/rust_crates:futures-preview",
+    "//third_party/rust_crates:rand",
+  ]
+}
+
+package("element_session") {
+  deps = [
+    ":element_session_bin",
+  ]
+
+  meta = [
+    {
+      path = rebase_path("meta/element_session.cml")
+      dest = "element_session.cm"
+    },
+  ]
+
+  binaries = [
+    {
+      name = "element_session"
+    },
+  ]
+}
diff --git a/src/session/examples/elements/element_session/meta/element_session.cml b/src/session/examples/elements/element_session/meta/element_session.cml
new file mode 100644
index 0000000..a7cc1a9
--- /dev/null
+++ b/src/session/examples/elements/element_session/meta/element_session.cml
@@ -0,0 +1,46 @@
+{
+    "program": {
+        "binary": "bin/element_session"
+    },
+    "children": [
+        {
+            "name": "element_proposer",
+            "url": "fuchsia-pkg://fuchsia.com/element_proposer#meta/element_proposer.cm",
+            "startup": "eager",
+        }
+    ],
+    "collections": [
+        {
+            "name": "elements",
+            "durability": "transient",
+        },
+    ],
+    "expose": [
+        {
+            "legacy_service": "/svc/fuchsia.session.ElementManager",
+            "from": "self",
+        },
+        {
+            "legacy_service": "/svc/fuchsia.session.examples.ElementPing",
+            "from": "self",
+        },
+    ],
+    "use": [
+        {
+            "legacy_service": "/svc/fuchsia.sys2.Realm",
+            "from": "framework",
+        },
+    ],
+    "offer": [
+        {
+            "legacy_service": "/svc/fuchsia.session.examples.ElementPing",
+            "from": "self",
+            "to": [ "#elements" ],
+        },
+        {
+            "legacy_service": "/svc/fuchsia.session.ElementManager",
+            "from": "self",
+            "to": [ "#element_proposer" ],
+        },
+    ],
+}
\ No newline at end of file
diff --git a/src/session/examples/elements/element_session/src/main.rs b/src/session/examples/elements/element_session/src/main.rs
new file mode 100644
index 0000000..6a2a1db
--- /dev/null
+++ b/src/session/examples/elements/element_session/src/main.rs
@@ -0,0 +1,112 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use {
+    element_management::{ElementManager, ElementManagerError, SimpleElementManager},
+    failure::{Error, ResultExt},
+    fidl_fuchsia_session::{
+        ElementManagerRequest, ElementManagerRequestStream, ProposeElementError,
+    },
+    fidl_fuchsia_session_examples::{ElementPingRequest, ElementPingRequestStream},
+    fidl_fuchsia_sys2 as fsys, fuchsia_async as fasync,
+    fuchsia_component::{client::connect_to_service, server::ServiceFs},
+    futures::{StreamExt, TryStreamExt},
+    rand::{distributions::Alphanumeric, thread_rng, Rng},
+};
+
+enum ExposedServices {
+    ElementManager(ElementManagerRequestStream),
+    ElementPing(ElementPingRequestStream),
+}
+
+// TODO(38577): Write example tests for the element session.
+
+/// The child collection to add elements to. This must match a collection name declared in
+/// this session's CML file.
+const ELEMENT_COLLECTION_NAME: &str = "elements";
+
+/// The maximum number of concurrent requests.
+const NUM_CONCURRENT_REQUESTS: usize = 5;
+
+/// This session exposes one service which is offered to all elements started in the session and
+/// prints a string when an element sends a request to said service.
+///
+/// It also exposes the [`fidl_fuchsia_session.ElementManager`] service which an element proposer
+/// can connect to in order to add an element to the session.
+#[fasync::run_singlethreaded]
+async fn main() -> Result<(), Error> {
+    let mut fs = ServiceFs::new_local();
+    fs.dir("svc").add_fidl_service(ExposedServices::ElementPing);
+    fs.dir("svc").add_fidl_service(ExposedServices::ElementManager);
+
+    fs.take_and_serve_directory_handle()?;
+
+    fs.for_each_concurrent(NUM_CONCURRENT_REQUESTS, move |service_request: ExposedServices| {
+        async move {
+            match service_request {
+                ExposedServices::ElementPing(request_stream) => {
+                    handle_element_ping_requests(request_stream)
+                        .await
+                        .expect("Failed to run element ping service.");
+                }
+                ExposedServices::ElementManager(request_stream) => {
+                    handle_element_manager_requests(request_stream)
+                        .await
+                        .expect("Failed to run element manager service.");
+                }
+            }
+        }
+    })
+    .await;
+
+    Ok(())
+}
+
+async fn handle_element_ping_requests(mut stream: ElementPingRequestStream) -> Result<(), Error> {
+    while let Some(ElementPingRequest::Ping { control_handle: _ }) =
+        stream.try_next().await.context("Error handling ping request stream")?
+    {
+        println!("Element did ping session.");
+    }
+    Ok(())
+}
+
+async fn handle_element_manager_requests(
+    mut stream: ElementManagerRequestStream,
+) -> Result<(), Error> {
+    let realm =
+        connect_to_service::<fsys::RealmMarker>().context("Could not connect to Realm service.")?;
+    let element_manager = SimpleElementManager::new(realm);
+    while let Some(request) =
+        stream.try_next().await.context("Error handling element manager request stream")?
+    {
+        match request {
+            ElementManagerRequest::ProposeElement { spec, responder } => {
+                let mut child_name: String =
+                    thread_rng().sample_iter(&Alphanumeric).take(16).collect();
+                child_name.make_ascii_lowercase();
+
+                let mut result = match element_manager
+                    .add_element(spec, &child_name, ELEMENT_COLLECTION_NAME)
+                    .await
+                {
+                    // Most of the errors which could be encountered when adding an element are
+                    // not the result of an error by the FIDL client. This lists all the cases
+                    // explicitly, but it's up to each session to decide how to map the errors.
+                    Ok(_) => Ok(()),
+                    Err(ElementManagerError::UrlMissing { .. }) => {
+                        Err(ProposeElementError::NotFound)
+                    }
+                    Err(ElementManagerError::NotCreated { .. }) => {
+                        Err(ProposeElementError::Rejected)
+                    }
+                    Err(ElementManagerError::NotBound { .. }) => Err(ProposeElementError::Rejected),
+                };
+
+                let _ = responder.send(&mut result);
+            }
+        }
+    }
+    Ok(())
+}
diff --git a/src/session/examples/elements/fidl/BUILD.gn b/src/session/examples/elements/fidl/BUILD.gn
new file mode 100644
index 0000000..1cb9ca3
--- /dev/null
+++ b/src/session/examples/elements/fidl/BUILD.gn
@@ -0,0 +1,13 @@
+# Copyright 2019 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/fidl/fidl.gni")
+
+fidl("session_examples") {
+  name = "fuchsia.session.examples"
+
+  sources = [
+    "element_ping.fidl",
+  ]
+}
diff --git a/src/session/examples/elements/fidl/element_ping.fidl b/src/session/examples/elements/fidl/element_ping.fidl
new file mode 100644
index 0000000..a175eae
--- /dev/null
+++ b/src/session/examples/elements/fidl/element_ping.fidl
@@ -0,0 +1,13 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+library fuchsia.session.examples;
+
+/// A protocol which is used by an example element to ping the session it was
+/// added to.
+[Discoverable]
+protocol ElementPing {
+    /// Sends a ping to the example session.
+    Ping();
+};
diff --git a/src/session/examples/elements/simple_element/BUILD.gn b/src/session/examples/elements/simple_element/BUILD.gn
new file mode 100644
index 0000000..36a1bbd
--- /dev/null
+++ b/src/session/examples/elements/simple_element/BUILD.gn
@@ -0,0 +1,38 @@
+# Copyright 2019 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/package.gni")
+import("//build/rust/rustc_binary.gni")
+
+rustc_binary("simple_element_bin") {
+  name = "simple_element"
+  edition = "2018"
+
+  deps = [
+    "//garnet/public/lib/fidl/rust/fidl",
+    "//garnet/public/rust/fuchsia-async",
+    "//garnet/public/rust/fuchsia-component",
+    "//src/session/examples/elements/fidl:session_examples-rustc",
+    "//third_party/rust_crates:failure",
+  ]
+}
+
+package("simple_element") {
+  deps = [
+    ":simple_element_bin",
+  ]
+
+  meta = [
+    {
+      path = rebase_path("meta/simple_element.cml")
+      dest = "simple_element.cm"
+    },
+  ]
+
+  binaries = [
+    {
+      name = "simple_element"
+    },
+  ]
+}
diff --git a/src/session/examples/elements/simple_element/meta/simple_element.cml b/src/session/examples/elements/simple_element/meta/simple_element.cml
new file mode 100644
index 0000000..cd283f6
--- /dev/null
+++ b/src/session/examples/elements/simple_element/meta/simple_element.cml
@@ -0,0 +1,10 @@
+{
+    "program": {
+        "binary": "bin/simple_element"
+    },
+    "use": [
+        {
+            "legacy_service": "/svc/fuchsia.session.examples.ElementPing",
+        },
+    ],
+}
\ No newline at end of file
diff --git a/src/session/examples/elements/simple_element/src/main.rs b/src/session/examples/elements/simple_element/src/main.rs
new file mode 100644
index 0000000..7b24978
--- /dev/null
+++ b/src/session/examples/elements/simple_element/src/main.rs
@@ -0,0 +1,20 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use failure::Error;
+
+use {
+    failure::ResultExt, fidl_fuchsia_session_examples::ElementPingMarker, fuchsia_async as fasync,
+    fuchsia_component::client::connect_to_service,
+};
+
+#[fasync::run_singlethreaded]
+async fn main() -> Result<(), Error> {
+    let element_ping = connect_to_service::<ElementPingMarker>()
+        .context("Could not connect to ElementPing service.")?;
+
+    element_ping.ping()?;
+
+    Ok(())
+}
diff --git a/src/session/example_sessions/graphical_session/BUILD.gn b/src/session/examples/graphical_session/BUILD.gn
similarity index 94%
rename from src/session/example_sessions/graphical_session/BUILD.gn
rename to src/session/examples/graphical_session/BUILD.gn
index 4ec34ee..b313cc7 100644
--- a/src/session/example_sessions/graphical_session/BUILD.gn
+++ b/src/session/examples/graphical_session/BUILD.gn
@@ -9,13 +9,12 @@
   name = "graphical_session"
   edition = "2018"
 
-  visibility = [ "//src/session/example_sessions/graphical_session/*" ]
-
   deps = [
     "//garnet/public/lib/fidl/rust/fidl",
     "//garnet/public/rust/fuchsia-async",
     "//garnet/public/rust/fuchsia-component",
     "//garnet/public/rust/fuchsia-scenic",
+    "//garnet/public/rust/fuchsia-syslog",
     "//garnet/public/rust/fuchsia-zircon",
     "//sdk/fidl/fuchsia.images:fuchsia.images-rustc",
     "//sdk/fidl/fuchsia.ui.gfx:fuchsia.ui.gfx-rustc",
diff --git a/src/session/example_sessions/graphical_session/meta/graphical_session.cml b/src/session/examples/graphical_session/meta/graphical_session.cml
similarity index 100%
rename from src/session/example_sessions/graphical_session/meta/graphical_session.cml
rename to src/session/examples/graphical_session/meta/graphical_session.cml
diff --git a/src/session/example_sessions/graphical_session/resources/whale.png b/src/session/examples/graphical_session/resources/whale.png
similarity index 100%
rename from src/session/example_sessions/graphical_session/resources/whale.png
rename to src/session/examples/graphical_session/resources/whale.png
Binary files differ
diff --git a/src/session/example_sessions/graphical_session/src/app.rs b/src/session/examples/graphical_session/src/app.rs
similarity index 95%
rename from src/session/example_sessions/graphical_session/src/app.rs
rename to src/session/examples/graphical_session/src/app.rs
index 83164ec..a66924e 100644
--- a/src/session/example_sessions/graphical_session/src/app.rs
+++ b/src/session/examples/graphical_session/src/app.rs
@@ -8,7 +8,7 @@
     fidl_fuchsia_ui_scenic::{ScenicMarker, ScenicProxy},
     fuchsia_async::{self as fasync},
     fuchsia_component::client::connect_to_service,
-    fuchsia_scenic,
+    fuchsia_scenic, fuchsia_syslog as syslog,
     fuchsia_zircon::{ClockId, Duration, Time},
     futures::{StreamExt, TryFutureExt},
 };
@@ -94,7 +94,7 @@
                 .lock()
                 .present(self.context.lock().unwrap().presentation_time.into_nanos() as u64)
                 .map_ok(|_| ())
-                .unwrap_or_else(|e| eprintln!("present error: {:?}", e)),
+                .unwrap_or_else(|error| syslog::fx_log_err!("Present error: {:?}", error)),
         );
     }
 }
diff --git a/src/session/example_sessions/graphical_session/src/graphics_util.rs b/src/session/examples/graphical_session/src/graphics_util.rs
similarity index 100%
rename from src/session/example_sessions/graphical_session/src/graphics_util.rs
rename to src/session/examples/graphical_session/src/graphics_util.rs
diff --git a/src/session/example_sessions/graphical_session/src/main.rs b/src/session/examples/graphical_session/src/main.rs
similarity index 84%
rename from src/session/example_sessions/graphical_session/src/main.rs
rename to src/session/examples/graphical_session/src/main.rs
index dc3f804..18b7030 100644
--- a/src/session/example_sessions/graphical_session/src/main.rs
+++ b/src/session/examples/graphical_session/src/main.rs
@@ -8,6 +8,8 @@
 mod graphics_util;
 mod view;
 
+// TODO(38577): Write example tests for the graphical session.
+
 #[fuchsia_async::run_singlethreaded]
 async fn main() -> Result<(), Error> {
     let mut app = app::App::new().await?;
diff --git a/src/session/example_sessions/graphical_session/src/view.rs b/src/session/examples/graphical_session/src/view.rs
similarity index 100%
rename from src/session/example_sessions/graphical_session/src/view.rs
rename to src/session/examples/graphical_session/src/view.rs