[scenic][ui-input] Pull out registries into separate classes

Creates separate classes for the implementations of
fuchsia::ui::pointerinjector::Registry and
fuchsia::ui::policy:accessibility::PointerEventRegistry, letting
InputSystem own instances of them instead of implementing them directly.
(Also meant to split out fuchsia.ui.pointercapture.ListenerRegistry,
but it was too closely entwined with InputSystem to be worth it)

This helps separate concerns and will let us split out unit tests
better.

No functional changes. Separate unit tests for the new classes to come
later, currently covered by the old tests.

Fixed: 74842
Test: input_unittests

Change-Id: I1fdda789ff2bdf51d2b8e83256eb14daf06161a8
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/523996
Commit-Queue: Mikael Pessa <mikaelpessa@google.com>
Reviewed-by: Jaeheon Yi <jaeheon@google.com>
diff --git a/src/ui/scenic/lib/input/BUILD.gn b/src/ui/scenic/lib/input/BUILD.gn
index 37b5be1..e30aedf 100644
--- a/src/ui/scenic/lib/input/BUILD.gn
+++ b/src/ui/scenic/lib/input/BUILD.gn
@@ -6,6 +6,8 @@
   sources = [
     "a11y_legacy_contender.cc",
     "a11y_legacy_contender.h",
+    "a11y_registry.cc",
+    "a11y_registry.h",
     "constants.cc",
     "constants.h",
     "gesture_arena.cc",
@@ -23,6 +25,8 @@
     "input_system.cc",
     "input_system.h",
     "internal_pointer_event.h",
+    "pointerinjector_registry.cc",
+    "pointerinjector_registry.h",
     "touch_source.cc",
     "touch_source.h",
   ]
diff --git a/src/ui/scenic/lib/input/a11y_registry.cc b/src/ui/scenic/lib/input/a11y_registry.cc
new file mode 100644
index 0000000..fdbbdd7
--- /dev/null
+++ b/src/ui/scenic/lib/input/a11y_registry.cc
@@ -0,0 +1,38 @@
+// Copyright 2021 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.
+
+#include "src/ui/scenic/lib/input/a11y_registry.h"
+
+#include <lib/syslog/cpp/macros.h>
+
+namespace scenic_impl::input {
+
+A11yPointerEventRegistry::A11yPointerEventRegistry(sys::ComponentContext* context,
+                                                   fit::function<void()> on_register,
+                                                   fit::function<void()> on_disconnect)
+    : on_register_(std::move(on_register)), on_disconnect_(std::move(on_disconnect)) {
+  FX_DCHECK(on_register_);
+  FX_DCHECK(on_disconnect_);
+  // Adding the service here is safe since the A11yPointerEventRegistry instance in InputSystem is
+  // created at construction time.
+  context->outgoing()->AddPublicService(accessibility_pointer_event_registry_.GetHandler(this));
+}
+
+void A11yPointerEventRegistry::Register(
+    fidl::InterfaceHandle<fuchsia::ui::input::accessibility::PointerEventListener>
+        pointer_event_listener,
+    RegisterCallback callback) {
+  if (!accessibility_pointer_event_listener()) {
+    accessibility_pointer_event_listener_.Bind(std::move(pointer_event_listener));
+    accessibility_pointer_event_listener_.set_error_handler(
+        [this](zx_status_t) { on_disconnect_(); });
+    on_register_();
+    callback(/*success=*/true);
+  } else {
+    // An accessibility listener is already registered.
+    callback(/*success=*/false);
+  }
+}
+
+}  // namespace scenic_impl::input
diff --git a/src/ui/scenic/lib/input/a11y_registry.h b/src/ui/scenic/lib/input/a11y_registry.h
new file mode 100644
index 0000000..5b8fc5d
--- /dev/null
+++ b/src/ui/scenic/lib/input/a11y_registry.h
@@ -0,0 +1,47 @@
+// Copyright 2021 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.
+
+#ifndef SRC_UI_SCENIC_LIB_INPUT_A11Y_REGISTRY_H_
+#define SRC_UI_SCENIC_LIB_INPUT_A11Y_REGISTRY_H_
+
+#include <fuchsia/ui/policy/accessibility/cpp/fidl.h>
+#include <lib/fidl/cpp/binding_set.h>
+#include <lib/fit/function.h>
+#include <lib/sys/cpp/component_context.h>
+
+namespace scenic_impl::input {
+
+// Implementation of PointerEventRegistry API.
+class A11yPointerEventRegistry : public fuchsia::ui::policy::accessibility::PointerEventRegistry {
+ public:
+  A11yPointerEventRegistry(sys::ComponentContext* context, fit::function<void()> on_register,
+                           fit::function<void()> on_disconnect);
+
+  // |fuchsia.ui.policy.accessibility.PointerEventRegistry|
+  void Register(fidl::InterfaceHandle<fuchsia::ui::input::accessibility::PointerEventListener>
+                    pointer_event_listener,
+                RegisterCallback callback) override;
+
+  fuchsia::ui::input::accessibility::PointerEventListenerPtr&
+  accessibility_pointer_event_listener() {
+    return accessibility_pointer_event_listener_;
+  }
+
+ private:
+  fidl::BindingSet<fuchsia::ui::policy::accessibility::PointerEventRegistry>
+      accessibility_pointer_event_registry_;
+  // We honor the first accessibility listener to register. A call to Register()
+  // above will fail if there is already a registered listener.
+  fuchsia::ui::input::accessibility::PointerEventListenerPtr accessibility_pointer_event_listener_;
+
+  // Function called when a new listener successfully registers.
+  fit::function<void()> on_register_;
+
+  // Function called when an active listener disconnects.
+  fit::function<void()> on_disconnect_;
+};
+
+}  // namespace scenic_impl::input
+
+#endif  // SRC_UI_SCENIC_LIB_INPUT_A11Y_REGISTRY_H_
diff --git a/src/ui/scenic/lib/input/injector.cc b/src/ui/scenic/lib/input/injector.cc
index cb7dca39..3b5a766 100644
--- a/src/ui/scenic/lib/input/injector.cc
+++ b/src/ui/scenic/lib/input/injector.cc
@@ -95,40 +95,6 @@
   return std::isless(min_x, max_x) && std::isless(min_y, max_y);
 }
 
-zx_status_t IsValidViewport(const fuchsia::ui::pointerinjector::Viewport& viewport) {
-  if (!viewport.has_extents() || !viewport.has_viewport_to_context_transform()) {
-    FX_LOGS(ERROR) << "Provided fuchsia::ui::pointerinjector::Viewport had missing fields";
-    return ZX_ERR_INVALID_ARGS;
-  }
-
-  if (!AreValidExtents(viewport.extents())) {
-    FX_LOGS(ERROR)
-        << "Provided fuchsia::ui::pointerinjector::Viewport had invalid extents. Extents min: {"
-        << viewport.extents()[0][0] << ", " << viewport.extents()[0][1] << "} max: {"
-        << viewport.extents()[1][0] << ", " << viewport.extents()[1][1] << "}";
-    return ZX_ERR_INVALID_ARGS;
-  }
-
-  if (std::any_of(viewport.viewport_to_context_transform().begin(),
-                  viewport.viewport_to_context_transform().end(),
-                  [](float f) { return !std::isfinite(f); })) {
-    FX_LOGS(ERROR) << "Provided fuchsia::ui::pointerinjector::Viewport "
-                      "viewport_to_context_transform contained a NaN or infinity";
-    return ZX_ERR_INVALID_ARGS;
-  }
-
-  // Must be invertible, i.e. determinant must be non-zero.
-  const glm::mat4 viewport_to_context_transform =
-      ColumnMajorMat3VectorToMat4(viewport.viewport_to_context_transform());
-  if (fabs(glm::determinant(viewport_to_context_transform)) <=
-      std::numeric_limits<float>::epsilon()) {
-    FX_LOGS(ERROR) << "Provided fuchsia::ui::pointerinjector::Viewport had a non-invertible matrix";
-    return ZX_ERR_INVALID_ARGS;
-  }
-
-  return ZX_OK;
-}
-
 void ChattyLog(const fuchsia::ui::pointerinjector::Event& event) {
   static uint32_t chatty = 0;
   if (chatty++ < ChattyMax()) {
@@ -138,40 +104,6 @@
 
 }  // namespace
 
-bool Injector::IsValidConfig(const fuchsia::ui::pointerinjector::Config& config) {
-  if (!config.has_device_id() || !config.has_device_type() || !config.has_context() ||
-      !config.has_target() || !config.has_viewport() || !config.has_dispatch_policy()) {
-    FX_LOGS(ERROR) << "InjectorRegistry::Register : Argument |config| is incomplete.";
-    return false;
-  }
-
-  if (config.dispatch_policy() != fuchsia::ui::pointerinjector::DispatchPolicy::EXCLUSIVE_TARGET &&
-      config.dispatch_policy() !=
-          fuchsia::ui::pointerinjector::DispatchPolicy::TOP_HIT_AND_ANCESTORS_IN_TARGET) {
-    FX_LOGS(ERROR) << "InjectorRegistry::Register : Only EXCLUSIVE_TARGET and "
-                      "TOP_HIT_AND_ANCESTORS_IN_TARGET DispatchPolicy is supported.";
-    return false;
-  }
-
-  if (config.device_type() != fuchsia::ui::pointerinjector::DeviceType::TOUCH) {
-    FX_LOGS(ERROR) << "InjectorRegistry::Register : Only DeviceType TOUCH is supported.";
-    return false;
-  }
-
-  if (!config.context().is_view() || !config.target().is_view()) {
-    FX_LOGS(ERROR) << "InjectorRegistry::Register : Argument |config.context| or |config.target| "
-                      "is not a view. Only views are supported.";
-    return false;
-  }
-
-  if (IsValidViewport(config.viewport()) != ZX_OK) {
-    // Errors printed in IsValidViewport. Just return result here.
-    return false;
-  }
-
-  return true;
-}
-
 Injector::Injector(inspect::Node inspect_node, InjectorSettings settings, Viewport viewport,
                    fidl::InterfaceRequest<fuchsia::ui::pointerinjector::Device> device,
                    fit::function<bool(/*descendant*/ zx_koid_t, /*ancestor*/ zx_koid_t)>
@@ -353,5 +285,39 @@
   on_channel_closed_();
 }
 
+zx_status_t Injector::IsValidViewport(const fuchsia::ui::pointerinjector::Viewport& viewport) {
+  if (!viewport.has_extents() || !viewport.has_viewport_to_context_transform()) {
+    FX_LOGS(ERROR) << "Provided fuchsia::ui::pointerinjector::Viewport had missing fields";
+    return ZX_ERR_INVALID_ARGS;
+  }
+
+  if (!AreValidExtents(viewport.extents())) {
+    FX_LOGS(ERROR)
+        << "Provided fuchsia::ui::pointerinjector::Viewport had invalid extents. Extents min: {"
+        << viewport.extents()[0][0] << ", " << viewport.extents()[0][1] << "} max: {"
+        << viewport.extents()[1][0] << ", " << viewport.extents()[1][1] << "}";
+    return ZX_ERR_INVALID_ARGS;
+  }
+
+  if (std::any_of(viewport.viewport_to_context_transform().begin(),
+                  viewport.viewport_to_context_transform().end(),
+                  [](float f) { return !std::isfinite(f); })) {
+    FX_LOGS(ERROR) << "Provided fuchsia::ui::pointerinjector::Viewport "
+                      "viewport_to_context_transform contained a NaN or infinity";
+    return ZX_ERR_INVALID_ARGS;
+  }
+
+  // Must be invertible, i.e. determinant must be non-zero.
+  const glm::mat4 viewport_to_context_transform =
+      ColumnMajorMat3VectorToMat4(viewport.viewport_to_context_transform());
+  if (fabs(glm::determinant(viewport_to_context_transform)) <=
+      std::numeric_limits<float>::epsilon()) {
+    FX_LOGS(ERROR) << "Provided fuchsia::ui::pointerinjector::Viewport had a non-invertible matrix";
+    return ZX_ERR_INVALID_ARGS;
+  }
+
+  return ZX_OK;
+}
+
 }  // namespace input
 }  // namespace scenic_impl
diff --git a/src/ui/scenic/lib/input/injector.h b/src/ui/scenic/lib/input/injector.h
index 2268338..f83378e 100644
--- a/src/ui/scenic/lib/input/injector.h
+++ b/src/ui/scenic/lib/input/injector.h
@@ -55,7 +55,9 @@
            fit::function<void(const InternalPointerEvent&, StreamId stream_id)> inject,
            fit::function<void()> on_channel_closed);
 
-  static bool IsValidConfig(const fuchsia::ui::pointerinjector::Config& config);
+  // Check the validity of a Viewport.
+  // Returns ZX_OK if valid, otherwise logs an error message and return appropriate error code.
+  static zx_status_t IsValidViewport(const fuchsia::ui::pointerinjector::Viewport& viewport);
 
   // |fuchsia::ui::pointerinjector::Device|
   void Inject(std::vector<fuchsia::ui::pointerinjector::Event> events,
diff --git a/src/ui/scenic/lib/input/input_system.cc b/src/ui/scenic/lib/input/input_system.cc
index 1da3ced..2d4a17c 100644
--- a/src/ui/scenic/lib/input/input_system.cc
+++ b/src/ui/scenic/lib/input/input_system.cc
@@ -107,8 +107,8 @@
       request_focus_(std::move(request_focus)) {
   FX_CHECK(scene_graph);
 
-  pointer_event_registry_ = std::make_unique<A11yPointerEventRegistry>(
-      this->context(),
+  a11y_pointer_event_registry_ = std::make_unique<A11yPointerEventRegistry>(
+      this->context()->app_context(),
       /*on_register=*/
       [this] {
         FX_CHECK(!a11y_legacy_contender_)
@@ -145,7 +145,16 @@
         FX_LOGS(INFO) << "A11yLegacyContender destroyed";
       });
 
-  this->context()->app_context()->outgoing()->AddPublicService(injector_registry_.GetHandler(this));
+  pointerinjector_registry_ = std::make_unique<PointerinjectorRegistry>(
+      this->context()->app_context(),
+      /*inject_touch_exclusive*/
+      [this](const InternalPointerEvent& event, StreamId stream_id) {
+        InjectTouchEventExclusive(event);
+      },
+      /*inject_touch_hit_tested*/
+      [this](const InternalPointerEvent& event, StreamId stream_id) {
+        InjectTouchEventHitTested(event, stream_id);
+      });
 
   this->context()->app_context()->outgoing()->AddPublicService(
       pointer_capture_registry_.GetHandler(this));
@@ -161,32 +170,6 @@
                                     [](CommandDispatcher* cd) { delete cd; });
 }
 
-A11yPointerEventRegistry::A11yPointerEventRegistry(SystemContext* context,
-                                                   fit::function<void()> on_register,
-                                                   fit::function<void()> on_disconnect)
-    : on_register_(std::move(on_register)), on_disconnect_(std::move(on_disconnect)) {
-  FX_DCHECK(on_register_);
-  FX_DCHECK(on_disconnect_);
-  context->app_context()->outgoing()->AddPublicService(
-      accessibility_pointer_event_registry_.GetHandler(this));
-}
-
-void A11yPointerEventRegistry::Register(
-    fidl::InterfaceHandle<fuchsia::ui::input::accessibility::PointerEventListener>
-        pointer_event_listener,
-    RegisterCallback callback) {
-  if (!accessibility_pointer_event_listener()) {
-    accessibility_pointer_event_listener_.Bind(std::move(pointer_event_listener));
-    accessibility_pointer_event_listener_.set_error_handler(
-        [this](zx_status_t) { on_disconnect_(); });
-    on_register_();
-    callback(/*success=*/true);
-  } else {
-    // An accessibility listener is already registered.
-    callback(/*success=*/false);
-  }
-}
-
 fuchsia::ui::input::accessibility::PointerEvent InputSystem::CreateAccessibilityEvent(
     const InternalPointerEvent& event) {
   zx_koid_t view_ref_koid = ZX_KOID_INVALID;
@@ -215,75 +198,6 @@
   return BuildAccessibilityPointerEvent(event, ndc, top_hit_view_local, view_ref_koid);
 }
 
-void InputSystem::Register(fuchsia::ui::pointerinjector::Config config,
-                           fidl::InterfaceRequest<fuchsia::ui::pointerinjector::Device> injector,
-                           RegisterCallback callback) {
-  if (!Injector::IsValidConfig(config)) {
-    // Errors printed inside IsValidConfig. Just return here.
-    return;
-  }
-
-  // Check connectivity here, since injector doesn't have access to it.
-  const zx_koid_t context_koid = utils::ExtractKoid(config.context().view());
-  const zx_koid_t target_koid = utils::ExtractKoid(config.target().view());
-  if (context_koid == ZX_KOID_INVALID || target_koid == ZX_KOID_INVALID) {
-    FX_LOGS(ERROR) << "InjectorRegistry::Register : Argument |config.context| or |config.target| "
-                      "was invalid.";
-    return;
-  }
-  if (!view_tree_snapshot_->IsDescendant(target_koid, context_koid)) {
-    FX_LOGS(ERROR) << "InjectorRegistry::Register : Argument |config.context| must be connected to "
-                      "the Scene, and |config.target| must be a descendant of |config.context|";
-    return;
-  }
-
-  // TODO(fxbug.dev/50348): Add a callback to kill the channel immediately if connectivity breaks.
-
-  const InjectorId id = ++last_injector_id_;
-  InjectorSettings settings{.dispatch_policy = config.dispatch_policy(),
-                            .device_id = config.device_id(),
-                            .device_type = config.device_type(),
-                            .context_koid = context_koid,
-                            .target_koid = target_koid};
-  Viewport viewport{
-      .extents = {config.viewport().extents()},
-      .context_from_viewport_transform =
-          ColumnMajorMat3VectorToMat4(config.viewport().viewport_to_context_transform()),
-  };
-
-  fit::function<void(const InternalPointerEvent&, StreamId)> inject_func;
-  switch (settings.dispatch_policy) {
-    case fuchsia::ui::pointerinjector::DispatchPolicy::EXCLUSIVE_TARGET:
-      inject_func = [this](const InternalPointerEvent& event, StreamId stream_id) {
-        InjectTouchEventExclusive(event);
-      };
-      break;
-    case fuchsia::ui::pointerinjector::DispatchPolicy::TOP_HIT_AND_ANCESTORS_IN_TARGET:
-      inject_func = [this](const InternalPointerEvent& event, StreamId stream_id) {
-        InjectTouchEventHitTested(event, stream_id);
-      };
-      break;
-    default:
-      FX_CHECK(false) << "Should never be reached.";
-      break;
-  }
-
-  const auto [it, success] = injectors_.try_emplace(
-      id,
-      context()->inspect_node()->CreateChild(context()->inspect_node()->UniqueName("injector-")),
-      std::move(settings), std::move(viewport), std::move(injector),
-      /*is_descendant_and_connected*/
-      [this](zx_koid_t descendant, zx_koid_t ancestor) {
-        return view_tree_snapshot_->IsDescendant(descendant, ancestor);
-      },
-      std::move(inject_func),
-      /*on_channel_closed*/
-      [this, id] { injectors_.erase(id); });
-  FX_CHECK(success) << "Injector already exists.";
-
-  callback();
-}
-
 ContenderId InputSystem::AddGfxLegacyContender(StreamId stream_id, zx_koid_t view_ref_koid) {
   FX_DCHECK(view_ref_koid != ZX_KOID_INVALID);
 
diff --git a/src/ui/scenic/lib/input/input_system.h b/src/ui/scenic/lib/input/input_system.h
index 31ff014..7480820 100644
--- a/src/ui/scenic/lib/input/input_system.h
+++ b/src/ui/scenic/lib/input/input_system.h
@@ -7,24 +7,23 @@
 
 #include <fuchsia/ui/input/accessibility/cpp/fidl.h>
 #include <fuchsia/ui/input/cpp/fidl.h>
-#include <fuchsia/ui/pointerinjector/cpp/fidl.h>
-#include <fuchsia/ui/policy/accessibility/cpp/fidl.h>
 #include <fuchsia/ui/scenic/cpp/fidl.h>
 
 #include <map>
 #include <optional>
 
 #include "src/ui/scenic/lib/input/a11y_legacy_contender.h"
+#include "src/ui/scenic/lib/input/a11y_registry.h"
 #include "src/ui/scenic/lib/input/gesture_arena.h"
 #include "src/ui/scenic/lib/input/gfx_legacy_contender.h"
 #include "src/ui/scenic/lib/input/injector.h"
 #include "src/ui/scenic/lib/input/input_command_dispatcher.h"
+#include "src/ui/scenic/lib/input/pointerinjector_registry.h"
 #include "src/ui/scenic/lib/scenic/system.h"
 #include "src/ui/scenic/lib/scheduling/id.h"
 #include "src/ui/scenic/lib/view_tree/snapshot_types.h"
 
-namespace scenic_impl {
-namespace input {
+namespace scenic_impl::input {
 
 // RequestFocusFunc should attempt to move focus to the passed in zx_koid_t.
 // If the passed in koid is ZX_KOID_INVALID, then focus should be moved to
@@ -32,40 +31,8 @@
 // silently fail.
 using RequestFocusFunc = fit::function<void(zx_koid_t)>;
 
-// Implementation of PointerEventRegistry API.
-class A11yPointerEventRegistry : public fuchsia::ui::policy::accessibility::PointerEventRegistry {
- public:
-  A11yPointerEventRegistry(SystemContext* context, fit::function<void()> on_register,
-                           fit::function<void()> on_disconnect);
-
-  // |fuchsia.ui.policy.accessibility.PointerEventRegistry|
-  void Register(fidl::InterfaceHandle<fuchsia::ui::input::accessibility::PointerEventListener>
-                    pointer_event_listener,
-                RegisterCallback callback) override;
-
-  fuchsia::ui::input::accessibility::PointerEventListenerPtr&
-  accessibility_pointer_event_listener() {
-    return accessibility_pointer_event_listener_;
-  }
-
- private:
-  fidl::BindingSet<fuchsia::ui::policy::accessibility::PointerEventRegistry>
-      accessibility_pointer_event_registry_;
-  // We honor the first accessibility listener to register. A call to Register()
-  // above will fail if there is already a registered listener.
-  fuchsia::ui::input::accessibility::PointerEventListenerPtr accessibility_pointer_event_listener_;
-
-  // Function called when a new listener successfully registers.
-  fit::function<void()> on_register_;
-
-  // Function called when an active listener disconnects.
-  fit::function<void()> on_disconnect_;
-};
-
 // Tracks input APIs.
-class InputSystem : public System,
-                    public fuchsia::ui::pointerinjector::Registry,
-                    public fuchsia::ui::input::PointerCaptureListenerRegistry {
+class InputSystem : public System, public fuchsia::ui::input::PointerCaptureListenerRegistry {
  public:
   struct PointerCaptureListener {
     fuchsia::ui::input::PointerCaptureListenerPtr listener_ptr;
@@ -83,17 +50,13 @@
       scheduling::SessionId session_id, std::shared_ptr<EventReporter> event_reporter,
       std::shared_ptr<ErrorReporter> error_reporter) override;
 
-  // |fuchsia.ui.pointerinjector.Registry|
-  void Register(fuchsia::ui::pointerinjector::Config config,
-                fidl::InterfaceRequest<fuchsia::ui::pointerinjector::Device> injector,
-                RegisterCallback callback) override;
-
   fuchsia::ui::input::accessibility::PointerEventListenerPtr& accessibility_pointer_event_listener()
       const {
-    return pointer_event_registry_->accessibility_pointer_event_listener();
+    return a11y_pointer_event_registry_->accessibility_pointer_event_listener();
   }
 
   void OnNewViewTreeSnapshot(std::shared_ptr<const view_tree::Snapshot> snapshot) {
+    pointerinjector_registry_->OnNewViewTreeSnapshot(snapshot);
     view_tree_snapshot_ = std::move(snapshot);
   }
 
@@ -106,10 +69,20 @@
                               scheduling::SessionId session_id);
 
   // For tests.
+  // TODO(fxbug.dev/72919): Remove when integration tests are properly separated out.
   void RegisterA11yListener(
       fidl::InterfaceHandle<fuchsia::ui::input::accessibility::PointerEventListener> listener,
       A11yPointerEventRegistry::RegisterCallback callback) {
-    pointer_event_registry_->Register(std::move(listener), std::move(callback));
+    a11y_pointer_event_registry_->Register(std::move(listener), std::move(callback));
+  }
+  // For tests.
+  // TODO(fxbug.dev/72919): Remove when integration tests are properly separated out.
+  void RegisterPointerinjector(
+      fuchsia::ui::pointerinjector::Config config,
+      fidl::InterfaceRequest<fuchsia::ui::pointerinjector::Device> injector,
+      fuchsia::ui::pointerinjector::Registry::RegisterCallback callback) {
+    pointerinjector_registry_->Register(std::move(config), std::move(injector),
+                                        std::move(callback));
   }
 
  private:
@@ -164,18 +137,13 @@
   std::optional<glm::mat4> GetDestinationViewFromSourceViewTransform(zx_koid_t source,
                                                                      zx_koid_t destination) const;
 
-  using InjectorId = uint64_t;
-  InjectorId last_injector_id_ = 0;
-  std::map<InjectorId, Injector> injectors_;
-
   // TODO(fxbug.dev/64206): Remove when we no longer have any legacy clients.
   fxl::WeakPtr<gfx::SceneGraph> scene_graph_;
 
   const RequestFocusFunc request_focus_;
 
-  std::unique_ptr<A11yPointerEventRegistry> pointer_event_registry_;
-
-  fidl::BindingSet<fuchsia::ui::pointerinjector::Registry> injector_registry_;
+  std::unique_ptr<A11yPointerEventRegistry> a11y_pointer_event_registry_;
+  std::unique_ptr<PointerinjectorRegistry> pointerinjector_registry_;
 
   fidl::BindingSet<fuchsia::ui::input::PointerCaptureListenerRegistry> pointer_capture_registry_;
   // A singleton listener who wants to be notified when pointer events happen.
@@ -209,7 +177,6 @@
       std::make_shared<const view_tree::Snapshot>();
 };
 
-}  // namespace input
-}  // namespace scenic_impl
+}  // namespace scenic_impl::input
 
 #endif  // SRC_UI_SCENIC_LIB_INPUT_INPUT_SYSTEM_H_
diff --git a/src/ui/scenic/lib/input/pointerinjector_registry.cc b/src/ui/scenic/lib/input/pointerinjector_registry.cc
new file mode 100644
index 0000000..df8c747
--- /dev/null
+++ b/src/ui/scenic/lib/input/pointerinjector_registry.cc
@@ -0,0 +1,134 @@
+// Copyright 2021 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.
+
+#include "src/ui/scenic/lib/input/pointerinjector_registry.h"
+
+#include <lib/syslog/cpp/macros.h>
+
+#include "src/ui/scenic/lib/utils/helpers.h"
+
+namespace scenic_impl::input {
+
+namespace {
+
+bool IsValidConfig(const fuchsia::ui::pointerinjector::Config& config) {
+  if (!config.has_device_id() || !config.has_device_type() || !config.has_context() ||
+      !config.has_target() || !config.has_viewport() || !config.has_dispatch_policy()) {
+    FX_LOGS(ERROR) << "InjectorRegistry::Register : Argument |config| is incomplete.";
+    return false;
+  }
+
+  if (config.dispatch_policy() != fuchsia::ui::pointerinjector::DispatchPolicy::EXCLUSIVE_TARGET &&
+      config.dispatch_policy() !=
+          fuchsia::ui::pointerinjector::DispatchPolicy::TOP_HIT_AND_ANCESTORS_IN_TARGET) {
+    FX_LOGS(ERROR) << "InjectorRegistry::Register : Only EXCLUSIVE_TARGET and "
+                      "TOP_HIT_AND_ANCESTORS_IN_TARGET DispatchPolicy is supported.";
+    return false;
+  }
+
+  if (config.device_type() != fuchsia::ui::pointerinjector::DeviceType::TOUCH) {
+    FX_LOGS(ERROR) << "InjectorRegistry::Register : Only DeviceType TOUCH is supported.";
+    return false;
+  }
+
+  if (!config.context().is_view() || !config.target().is_view()) {
+    FX_LOGS(ERROR) << "InjectorRegistry::Register : Argument |config.context| or |config.target| "
+                      "is not a view. Only views are supported.";
+    return false;
+  }
+
+  if (Injector::IsValidViewport(config.viewport()) != ZX_OK) {
+    // Errors printed in IsValidViewport. Just return result here.
+    return false;
+  }
+
+  return true;
+}
+
+}  // namespace
+
+PointerinjectorRegistry::PointerinjectorRegistry(sys::ComponentContext* context,
+                                                 InjectFunc inject_touch_exclusive,
+                                                 InjectFunc inject_touch_hit_tested,
+                                                 inspect::Node inspect_node)
+    : inject_touch_exclusive_(std::move(inject_touch_exclusive)),
+      inject_touch_hit_tested_(std::move(inject_touch_hit_tested)),
+      inspect_node_(std::move(inspect_node)) {
+  if (context) {
+    // Adding the service here is safe since the PointerinjectorRegistry instance in InputSystem is
+    // created at construction time..
+    context->outgoing()->AddPublicService(injector_registry_.GetHandler(this));
+  }
+}
+
+void PointerinjectorRegistry::Register(
+    fuchsia::ui::pointerinjector::Config config,
+    fidl::InterfaceRequest<fuchsia::ui::pointerinjector::Device> injector,
+    RegisterCallback callback) {
+  if (!IsValidConfig(config)) {
+    // Errors printed inside IsValidConfig. Just return here.
+    return;
+  }
+
+  // Check connectivity here, since injector doesn't have access to it.
+  const zx_koid_t context_koid = utils::ExtractKoid(config.context().view());
+  const zx_koid_t target_koid = utils::ExtractKoid(config.target().view());
+  if (context_koid == ZX_KOID_INVALID || target_koid == ZX_KOID_INVALID) {
+    FX_LOGS(ERROR) << "InjectorRegistry::Register : Argument |config.context| or |config.target| "
+                      "was invalid.";
+    return;
+  }
+  if (!view_tree_snapshot_->IsDescendant(target_koid, context_koid)) {
+    FX_LOGS(ERROR) << "InjectorRegistry::Register : Argument |config.context| must be connected to "
+                      "the Scene, and |config.target| must be a descendant of |config.context|";
+    return;
+  }
+
+  // TODO(fxbug.dev/50348): Add a callback to kill the channel immediately if connectivity breaks.
+
+  const InjectorId id = ++last_injector_id_;
+  InjectorSettings settings{.dispatch_policy = config.dispatch_policy(),
+                            .device_id = config.device_id(),
+                            .device_type = config.device_type(),
+                            .context_koid = context_koid,
+                            .target_koid = target_koid};
+  Viewport viewport{
+      .extents = {config.viewport().extents()},
+      .context_from_viewport_transform =
+          ColumnMajorMat3VectorToMat4(config.viewport().viewport_to_context_transform()),
+  };
+
+  fit::function<void(const InternalPointerEvent&, StreamId)> inject_func;
+  switch (settings.dispatch_policy) {
+    case fuchsia::ui::pointerinjector::DispatchPolicy::EXCLUSIVE_TARGET:
+      inject_func = [this](const InternalPointerEvent& event, StreamId stream_id) {
+        inject_touch_exclusive_(event, stream_id);
+      };
+      break;
+    case fuchsia::ui::pointerinjector::DispatchPolicy::TOP_HIT_AND_ANCESTORS_IN_TARGET:
+      inject_func = [this](const InternalPointerEvent& event, StreamId stream_id) {
+        inject_touch_hit_tested_(event, stream_id);
+      };
+      break;
+    default:
+      FX_CHECK(false) << "Should never be reached.";
+      break;
+  }
+
+  const auto [it, success] = injectors_.try_emplace(
+      id, inspect_node_.CreateChild(inspect_node_.UniqueName("injector-")), std::move(settings),
+      std::move(viewport), std::move(injector),
+      /*is_descendant_and_connected*/
+      [this](zx_koid_t descendant, zx_koid_t ancestor) {
+        return view_tree_snapshot_->IsDescendant(descendant, ancestor);
+      },
+      std::move(inject_func),
+      /*on_channel_closed*/
+      [this, id] { injectors_.erase(id); });
+  FX_CHECK(success) << "Injector already exists.";
+
+  callback();
+}
+
+}  // namespace scenic_impl::input
diff --git a/src/ui/scenic/lib/input/pointerinjector_registry.h b/src/ui/scenic/lib/input/pointerinjector_registry.h
new file mode 100644
index 0000000..d448f47
--- /dev/null
+++ b/src/ui/scenic/lib/input/pointerinjector_registry.h
@@ -0,0 +1,56 @@
+// Copyright 2021 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.
+
+#ifndef SRC_UI_SCENIC_LIB_INPUT_POINTERINJECTOR_REGISTRY_H_
+#define SRC_UI_SCENIC_LIB_INPUT_POINTERINJECTOR_REGISTRY_H_
+
+#include <fuchsia/ui/pointerinjector/cpp/fidl.h>
+#include <lib/fit/function.h>
+#include <lib/sys/cpp/component_context.h>
+
+#include <unordered_map>
+
+#include "lib/inspect/cpp/inspect.h"
+#include "src/ui/scenic/lib/input/injector.h"
+#include "src/ui/scenic/lib/view_tree/snapshot_types.h"
+
+namespace scenic_impl::input {
+
+using InjectFunc = fit::function<void(const InternalPointerEvent& event, StreamId stream_id)>;
+
+// Handles the registration and config validation of fuchsia::ui::pointerinjector clients.
+class PointerinjectorRegistry : public fuchsia::ui::pointerinjector::Registry {
+ public:
+  PointerinjectorRegistry(sys::ComponentContext* context, InjectFunc inject_touch_exclusive,
+                          InjectFunc inject_touch_hit_tested,
+                          inspect::Node inspect_node = inspect::Node());
+
+  // |fuchsia.ui.pointerinjector.Registry|
+  void Register(fuchsia::ui::pointerinjector::Config config,
+                fidl::InterfaceRequest<fuchsia::ui::pointerinjector::Device> injector,
+                RegisterCallback callback) override;
+
+  void OnNewViewTreeSnapshot(std::shared_ptr<const view_tree::Snapshot> snapshot) {
+    view_tree_snapshot_ = std::move(snapshot);
+  }
+
+ private:
+  using InjectorId = uint64_t;
+  InjectorId last_injector_id_ = 0;
+  std::unordered_map<InjectorId, Injector> injectors_;
+
+  fidl::BindingSet<fuchsia::ui::pointerinjector::Registry> injector_registry_;
+
+  const InjectFunc inject_touch_exclusive_;
+  const InjectFunc inject_touch_hit_tested_;
+
+  std::shared_ptr<const view_tree::Snapshot> view_tree_snapshot_ =
+      std::make_shared<const view_tree::Snapshot>();
+
+  inspect::Node inspect_node_;
+};
+
+}  // namespace scenic_impl::input
+
+#endif  // SRC_UI_SCENIC_LIB_INPUT_POINTERINJECTOR_REGISTRY_H_
diff --git a/src/ui/scenic/lib/input/tests/coordinate_transform_test2.cc b/src/ui/scenic/lib/input/tests/coordinate_transform_test2.cc
index 7c3857e..fcc934b 100644
--- a/src/ui/scenic/lib/input/tests/coordinate_transform_test2.cc
+++ b/src/ui/scenic/lib/input/tests/coordinate_transform_test2.cc
@@ -92,8 +92,9 @@
       error_callback_fired = true;
     });
     bool register_callback_fired = false;
-    input_system()->Register(std::move(config), injector_.NewRequest(),
-                             [&register_callback_fired] { register_callback_fired = true; });
+    input_system()->RegisterPointerinjector(
+        std::move(config), injector_.NewRequest(),
+        [&register_callback_fired] { register_callback_fired = true; });
     RunLoopUntilIdle();
     ASSERT_TRUE(register_callback_fired);
     ASSERT_FALSE(error_callback_fired);
diff --git a/src/ui/scenic/lib/input/tests/input_injection_test.cc b/src/ui/scenic/lib/input/tests/input_injection_test.cc
index 2c69446..984b53e 100644
--- a/src/ui/scenic/lib/input/tests/input_injection_test.cc
+++ b/src/ui/scenic/lib/input/tests/input_injection_test.cc
@@ -169,8 +169,9 @@
   });
   {
     fuchsia::ui::pointerinjector::Config config = ConfigTemplate(parent_view_ref, child_view_ref);
-    input_system()->Register(std::move(config), injector.NewRequest(),
-                             [&register_callback_fired] { register_callback_fired = true; });
+    input_system()->RegisterPointerinjector(
+        std::move(config), injector.NewRequest(),
+        [&register_callback_fired] { register_callback_fired = true; });
   }
 
   RunLoopUntilIdle();
@@ -195,8 +196,9 @@
     fidl::Clone(base_config, &config);
     config.clear_device_id();
 
-    input_system()->Register(std::move(config), injector.NewRequest(),
-                             [&register_callback_fired] { register_callback_fired = true; });
+    input_system()->RegisterPointerinjector(
+        std::move(config), injector.NewRequest(),
+        [&register_callback_fired] { register_callback_fired = true; });
 
     RunLoopUntilIdle();
 
@@ -215,8 +217,9 @@
     fidl::Clone(base_config, &config);
     config.clear_device_type();
 
-    input_system()->Register(std::move(config), injector.NewRequest(),
-                             [&register_callback_fired] { register_callback_fired = true; });
+    input_system()->RegisterPointerinjector(
+        std::move(config), injector.NewRequest(),
+        [&register_callback_fired] { register_callback_fired = true; });
 
     RunLoopUntilIdle();
 
@@ -236,8 +239,9 @@
     // Set to not TOUCH.
     config.set_device_type(static_cast<DeviceType>(static_cast<uint32_t>(12421)));
 
-    input_system()->Register(std::move(config), injector.NewRequest(),
-                             [&register_callback_fired] { register_callback_fired = true; });
+    input_system()->RegisterPointerinjector(
+        std::move(config), injector.NewRequest(),
+        [&register_callback_fired] { register_callback_fired = true; });
 
     RunLoopUntilIdle();
 
@@ -262,8 +266,9 @@
     fidl::Clone(base_config, &config);
     config.clear_context();
 
-    input_system()->Register(std::move(config), injector.NewRequest(),
-                             [&register_callback_fired] { register_callback_fired = true; });
+    input_system()->RegisterPointerinjector(
+        std::move(config), injector.NewRequest(),
+        [&register_callback_fired] { register_callback_fired = true; });
 
     RunLoopUntilIdle();
 
@@ -282,8 +287,9 @@
     fidl::Clone(base_config, &config);
     config.clear_target();
 
-    input_system()->Register(std::move(config), injector.NewRequest(),
-                             [&register_callback_fired] { register_callback_fired = true; });
+    input_system()->RegisterPointerinjector(
+        std::move(config), injector.NewRequest(),
+        [&register_callback_fired] { register_callback_fired = true; });
 
     RunLoopUntilIdle();
 
@@ -314,8 +320,9 @@
       config.set_target(std::move(target));
     }
 
-    input_system()->Register(std::move(config), injector.NewRequest(),
-                             [&register_callback_fired] { register_callback_fired = true; });
+    input_system()->RegisterPointerinjector(
+        std::move(config), injector.NewRequest(),
+        [&register_callback_fired] { register_callback_fired = true; });
 
     RunLoopUntilIdle();
 
@@ -348,8 +355,9 @@
       config.set_target(std::move(target));
     }
 
-    input_system()->Register(std::move(config), injector.NewRequest(),
-                             [&register_callback_fired] { register_callback_fired = true; });
+    input_system()->RegisterPointerinjector(
+        std::move(config), injector.NewRequest(),
+        [&register_callback_fired] { register_callback_fired = true; });
 
     RunLoopUntilIdle();
 
@@ -380,8 +388,9 @@
       config.set_target(std::move(target));
     }
 
-    input_system()->Register(std::move(config), injector.NewRequest(),
-                             [&register_callback_fired] { register_callback_fired = true; });
+    input_system()->RegisterPointerinjector(
+        std::move(config), injector.NewRequest(),
+        [&register_callback_fired] { register_callback_fired = true; });
 
     RunLoopUntilIdle();
 
@@ -412,8 +421,9 @@
       config.set_target(std::move(target));
     }
 
-    input_system()->Register(std::move(config), injector.NewRequest(),
-                             [&register_callback_fired] { register_callback_fired = true; });
+    input_system()->RegisterPointerinjector(
+        std::move(config), injector.NewRequest(),
+        [&register_callback_fired] { register_callback_fired = true; });
 
     RunLoopUntilIdle();
 
@@ -449,8 +459,9 @@
     root_resources_->scene.DetachChildren();
     RequestToPresent(root_session_->session());
 
-    input_system()->Register(std::move(config), injector.NewRequest(),
-                             [&register_callback_fired] { register_callback_fired = true; });
+    input_system()->RegisterPointerinjector(
+        std::move(config), injector.NewRequest(),
+        [&register_callback_fired] { register_callback_fired = true; });
 
     RunLoopUntilIdle();
 
@@ -475,8 +486,9 @@
     fidl::Clone(base_config, &config);
     config.clear_dispatch_policy();
 
-    input_system()->Register(std::move(config), injector.NewRequest(),
-                             [&register_callback_fired] { register_callback_fired = true; });
+    input_system()->RegisterPointerinjector(
+        std::move(config), injector.NewRequest(),
+        [&register_callback_fired] { register_callback_fired = true; });
 
     RunLoopUntilIdle();
 
@@ -495,8 +507,9 @@
     fidl::Clone(base_config, &config);
     config.set_dispatch_policy(static_cast<fuchsia::ui::pointerinjector::DispatchPolicy>(6323));
 
-    input_system()->Register(std::move(config), injector.NewRequest(),
-                             [&register_callback_fired] { register_callback_fired = true; });
+    input_system()->RegisterPointerinjector(
+        std::move(config), injector.NewRequest(),
+        [&register_callback_fired] { register_callback_fired = true; });
 
     RunLoopUntilIdle();
 
@@ -516,8 +529,9 @@
         [&error_callback_fired](zx_status_t status) { error_callback_fired = true; });
 
     fuchsia::ui::pointerinjector::Config config = ConfigTemplate(parent_view_ref, child_view_ref);
-    input_system()->Register(std::move(config), injector.NewRequest(),
-                             [&register_callback_fired] { register_callback_fired = true; });
+    input_system()->RegisterPointerinjector(
+        std::move(config), injector.NewRequest(),
+        [&register_callback_fired] { register_callback_fired = true; });
 
     RunLoopUntilIdle();
 
@@ -538,8 +552,9 @@
     injector.set_error_handler(
         [&error_callback_fired](zx_status_t status) { error_callback_fired = true; });
     fuchsia::ui::pointerinjector::Config config = ConfigTemplate(parent_view_ref, child_view_ref);
-    input_system()->Register(std::move(config), injector.NewRequest(),
-                             [&register_callback_fired] { register_callback_fired = true; });
+    input_system()->RegisterPointerinjector(
+        std::move(config), injector.NewRequest(),
+        [&register_callback_fired] { register_callback_fired = true; });
     RunLoopUntilIdle();
     EXPECT_TRUE(register_callback_fired);
     EXPECT_FALSE(error_callback_fired);
@@ -553,8 +568,9 @@
         [&error_callback_fired](zx_status_t status) { error_callback_fired = true; });
 
     fuchsia::ui::pointerinjector::Config config = ConfigTemplate(parent_view_ref, child_view_ref);
-    input_system()->Register(std::move(config), injector2.NewRequest(),
-                             [&register_callback_fired] { register_callback_fired = true; });
+    input_system()->RegisterPointerinjector(
+        std::move(config), injector2.NewRequest(),
+        [&register_callback_fired] { register_callback_fired = true; });
     RunLoopUntilIdle();
     EXPECT_TRUE(register_callback_fired);
     EXPECT_FALSE(error_callback_fired);
@@ -1493,8 +1509,9 @@
     config.set_viewport(std::move(viewport));
   }
 
-  input_system()->Register(std::move(config), injector.NewRequest(),
-                           [&register_callback_fired] { register_callback_fired = true; });
+  input_system()->RegisterPointerinjector(
+      std::move(config), injector.NewRequest(),
+      [&register_callback_fired] { register_callback_fired = true; });
 
   RunLoopUntilIdle();
 
diff --git a/src/ui/scenic/lib/input/tests/util.cc b/src/ui/scenic/lib/input/tests/util.cc
index c63bd4e..16aee22 100644
--- a/src/ui/scenic/lib/input/tests/util.cc
+++ b/src/ui/scenic/lib/input/tests/util.cc
@@ -258,8 +258,9 @@
     error_callback_fired = true;
   });
   bool register_callback_fired = false;
-  input_system()->Register(std::move(config), injector_.NewRequest(),
-                           [&register_callback_fired] { register_callback_fired = true; });
+  input_system()->RegisterPointerinjector(
+      std::move(config), injector_.NewRequest(),
+      [&register_callback_fired] { register_callback_fired = true; });
   RunLoopUntilIdle();
   ASSERT_TRUE(register_callback_fired);
   ASSERT_FALSE(error_callback_fired);