blob: 6aef879c7a19a1e6885f020d82970858bfbc8703 [file] [log] [blame]
// Copyright 2020 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/gfx/engine/annotation_manager.h"
#include <lib/sys/cpp/testing/component_context_provider.h>
#include <lib/ui/scenic/cpp/commands.h>
#include <lib/ui/scenic/cpp/view_ref_pair.h>
#include <lib/ui/scenic/cpp/view_token_pair.h>
#include <lib/zx/eventpair.h>
#include "src/ui/scenic/lib/gfx/resources/compositor/compositor.h"
#include "src/ui/scenic/lib/gfx/tests/view_tree_session_test.h"
namespace scenic_impl {
namespace gfx {
namespace test {
// Test fixture which tests creating and handling of Annotation ViewHolders and
// Views.
//
// We use the ViewTreeSessionTest which supports handling multiple Sessions. The
// class internal session_ is used for setting up the main Scene defined in
// SetUpScene() which contains ViewHolders of client Views. For each other
// client View, a separate Session is created and registered in test body.
//
class AnnotationManagerTest : public ViewTreeSessionTest {
public:
enum : uint32_t {
kCompositorId = 20001,
kLayerStackId,
kLayerId,
kSceneId,
kCameraId,
kRendererId,
kEntityNodeId,
kViewHolder1Id,
kView1Id,
kViewHolder2Id,
kView2Id,
kAnnotationViewId = 30001,
kAnnotationShapeId
};
AnnotationManagerTest() = default;
void SetUp() override {
ViewTreeSessionTest::SetUp();
SetUpScene();
constexpr auto kAnnotationSessionId = 0U;
auto annotation_session = CreateAndRegisterSession();
annotation_manager_ = std::make_unique<AnnotationManager>(
scene_graph_->GetWeakPtr(), view_linker_.get(), std::move(annotation_session));
}
void TearDown() override {
ViewTreeSessionTest::TearDown();
scene_graph_.reset();
}
bool Apply(fuchsia::ui::gfx::Command command) {
bool result = ViewTreeSessionTest::Apply(std::move(command));
StageAndUpdateViewTree(scene_graph_.get());
return result;
}
SessionContext CreateSessionContext() override {
SessionContext session_context = ViewTreeSessionTest::CreateSessionContext();
FXL_DCHECK(!view_linker_);
FXL_DCHECK(!scene_graph_);
view_linker_ = std::make_unique<ViewLinker>();
scene_graph_ = std::make_unique<SceneGraph>(context_provider_.context());
session_context.view_linker = view_linker_.get();
session_context.scene_graph = scene_graph_->GetWeakPtr();
return session_context;
}
CommandContext CreateCommandContext() {
return CommandContext(/*uploader=*/nullptr, /*sysmem=*/nullptr,
/*display_manager=*/nullptr, scene_graph_->GetWeakPtr());
}
SceneGraph* scene_graph() const { return scene_graph_.get(); }
AnnotationManager* annotation_manager() const { return annotation_manager_.get(); }
private:
void SetUpScene() {
// Create the following Resource Graph:
//
// Compositor --> LayerStack --> Layer --> Renderer --> Camera --> Scene
// |
// v
// EntityNode
Apply(scenic::NewCreateCompositorCmd(kCompositorId));
Apply(scenic::NewCreateLayerStackCmd(kLayerStackId));
Apply(scenic::NewSetLayerStackCmd(kCompositorId, kLayerStackId));
Apply(scenic::NewCreateLayerCmd(kLayerId));
Apply(scenic::NewSetSizeCmd(kLayerId, {1024, 768}));
Apply(scenic::NewAddLayerCmd(kLayerStackId, kLayerId));
Apply(scenic::NewCreateSceneCmd(kSceneId));
Apply(scenic::NewCreateCameraCmd(kCameraId, kSceneId));
Apply(scenic::NewCreateRendererCmd(kRendererId));
Apply(scenic::NewSetCameraCmd(kRendererId, kCameraId));
Apply(scenic::NewSetRendererCmd(kLayerId, kRendererId));
Apply(scenic::NewCreateEntityNodeCmd(kEntityNodeId));
Apply(scenic::NewAddChildCmd(kSceneId, kEntityNodeId));
}
sys::testing::ComponentContextProvider context_provider_;
std::unique_ptr<SceneGraph> scene_graph_;
std::unique_ptr<ViewLinker> view_linker_;
std::unique_ptr<AnnotationManager> annotation_manager_;
};
TEST_F(AnnotationManagerTest, SuccessfulLookup) {
// Consider the following Resource Graph:
//
// Scene
// |
// EntityNode
// /------------------|----------------\
// | |
// v v
// ViewHolder1 ViewHolder2
// .` | .` |
// .` v .` v
// View1 ==> ViewNode1 View2 ==> ViewNode2
// ||
// V
// Annotation
// ViewHolder
//
// We should be able to create an annotation ViewHolder for View2 given
// |view2_ref|.
// Create Views.
auto [view1_token, view_holder1_token] = scenic::ViewTokenPair::New();
auto [view2_token, view_holder2_token] = scenic::ViewTokenPair::New();
auto [view2_ctrl_ref, view2_ref] = scenic::ViewRefPair::New();
fuchsia::ui::views::ViewRef view2_ref_for_creation;
view2_ref.Clone(&view2_ref_for_creation);
auto session_view1 = CreateAndRegisterSession();
auto session_view2 = CreateAndRegisterSession();
CommandContext cmds = CreateCommandContext();
session_view1->ApplyCommand(&cmds,
scenic::NewCreateViewCmd(kView1Id, std::move(view1_token), "view 1"));
session_view2->ApplyCommand(
&cmds, scenic::NewCreateViewCmd(kView2Id, std::move(view2_token), std::move(view2_ctrl_ref),
std::move(view2_ref_for_creation), "view 2"));
cmds.Flush();
Apply(scenic::NewCreateViewHolderCmd(kViewHolder1Id, std::move(view_holder1_token), "holder 1"));
Apply(scenic::NewCreateViewHolderCmd(kViewHolder2Id, std::move(view_holder2_token), "holder 2"));
// Attach ViewHolder1 and ViewHolder2 to scene.
Apply(scenic::NewAddChildCmd(kEntityNodeId, kViewHolder1Id));
Apply(scenic::NewAddChildCmd(kEntityNodeId, kViewHolder2Id));
// Lookup View1 and View2 in the ResourceMap of their Sessions to verify that
// it is created successfully.
ViewPtr view1_ptr = session_view1->resources()->FindResource<View>(kView1Id);
ViewPtr view2_ptr = session_view2->resources()->FindResource<View>(kView2Id);
EXPECT_TRUE(view1_ptr);
EXPECT_TRUE(view2_ptr && view2_ptr->GetViewNode());
// Create Annotation ViewHolder for View2 only.
auto [annotation_view_token, annotation_view_holder_token] = scenic::ViewTokenPair::New();
fuchsia::ui::views::ViewRef view2_ref_for_lookup;
view2_ref.Clone(&view2_ref_for_lookup);
bool created = false;
bool handler_removed = false;
constexpr AnnotationHandlerId kAnnotationHandlerId = 0;
annotation_manager()->RegisterHandler(kAnnotationHandlerId,
[&handler_removed](auto) { handler_removed = true; });
annotation_manager()->RequestCreate(kAnnotationHandlerId, std::move(view2_ref_for_lookup),
std::move(annotation_view_holder_token),
[&created]() { created = true; });
EXPECT_EQ(view1_ptr->annotation_view_holders().size(), 0U);
EXPECT_EQ(view2_ptr->annotation_view_holders().size(), 0U);
EXPECT_EQ(view2_ptr->GetViewNode()->children().size(), 0U);
EXPECT_FALSE(created);
EXPECT_FALSE(handler_removed);
annotation_manager()->FulfillCreateRequests();
ASSERT_TRUE(created);
EXPECT_FALSE(handler_removed);
EXPECT_EQ(view1_ptr->annotation_view_holders().size(), 0U);
EXPECT_EQ(view2_ptr->annotation_view_holders().size(), 1U);
fxl::WeakPtr<ViewHolder> annotation_view_holder_weak_ptr =
(*view2_ptr->annotation_view_holders().begin())->GetWeakPtr();
EXPECT_EQ(view2_ptr->GetViewNode()->children().size(), 1U);
EXPECT_EQ(view2_ptr->GetViewNode()->children().front().get(),
annotation_view_holder_weak_ptr.get());
EXPECT_EQ(annotation_view_holder_weak_ptr->parent(), view2_ptr->GetViewNode());
}
TEST_F(AnnotationManagerTest, InvalidAndNonExistentViewRef) {
// Consider the following Resource Graph:
//
// Scene
// |
// EntityNode
// /------------------|----------------\
// | |
// v v
// ViewHolder1 ViewHolder2
// .` | .` |
// .` v .` v
// View1 ==> ViewNode1 View2 ==> ViewNode2
//
// We should not create an annotation ViewHolder if the |client_view_ref|
// doesn't refer to any existing View, or the |client_view_ref| is invalid.
// Create Views.
auto [view1_token, view_holder1_token] = scenic::ViewTokenPair::New();
auto [view2_token, view_holder2_token] = scenic::ViewTokenPair::New();
auto session_view1 = CreateAndRegisterSession();
auto session_view2 = CreateAndRegisterSession();
CommandContext cmds = CreateCommandContext();
session_view1->ApplyCommand(&cmds,
scenic::NewCreateViewCmd(kView1Id, std::move(view1_token), "view 1"));
session_view2->ApplyCommand(&cmds,
scenic::NewCreateViewCmd(kView2Id, std::move(view2_token), "view 2"));
cmds.Flush();
Apply(scenic::NewCreateViewHolderCmd(kViewHolder1Id, std::move(view_holder1_token), "holder 1"));
Apply(scenic::NewCreateViewHolderCmd(kViewHolder2Id, std::move(view_holder2_token), "holder 2"));
// Attach ViewHolder1 and ViewHolder2 to scene.
Apply(scenic::NewAddChildCmd(kEntityNodeId, kViewHolder1Id));
Apply(scenic::NewAddChildCmd(kEntityNodeId, kViewHolder2Id));
// Lookup View1 and View2 in the ResourceMap of their Sessions to verify that
// it is created successfully.
ViewPtr view1_ptr = session_view1->resources()->FindResource<View>(kView1Id);
ViewPtr view2_ptr = session_view2->resources()->FindResource<View>(kView2Id);
EXPECT_TRUE(view1_ptr);
EXPECT_TRUE(view2_ptr);
bool handler_removed = false;
zx_status_t epitaph = ZX_OK;
constexpr AnnotationHandlerId kAnnotationHandlerId = 0;
annotation_manager()->RegisterHandler(kAnnotationHandlerId,
[&handler_removed, &epitaph](zx_status_t handler_epitaph) {
handler_removed = true;
epitaph = handler_epitaph;
});
// Create Annotation ViewHolder using an new created ViewRef.
{
auto [annotation_view_ctrl_ref, annotation_view_ref] = scenic::ViewRefPair::New();
auto [annotation_view_token, annotation_view_holder_token] = scenic::ViewTokenPair::New();
bool created = false;
annotation_manager()->RequestCreate(kAnnotationHandlerId, std::move(annotation_view_ref),
std::move(annotation_view_holder_token),
[&created]() { created = true; });
EXPECT_EQ(view1_ptr->annotation_view_holders().size(), 0U);
EXPECT_EQ(view2_ptr->annotation_view_holders().size(), 0U);
EXPECT_FALSE(created);
EXPECT_FALSE(handler_removed);
annotation_manager()->FulfillCreateRequests();
ASSERT_FALSE(created);
ASSERT_FALSE(handler_removed);
EXPECT_EQ(view1_ptr->annotation_view_holders().size(), 0U);
EXPECT_EQ(view2_ptr->annotation_view_holders().size(), 0U);
}
// Create Annotation ViewHolder using an empty ViewRef.
{
auto annotation_view_ref = fuchsia::ui::views::ViewRef::New();
auto [annotation_view_token, annotation_view_holder_token] = scenic::ViewTokenPair::New();
bool created = false;
annotation_manager()->RequestCreate(kAnnotationHandlerId, std::move(*annotation_view_ref),
std::move(annotation_view_holder_token),
[&created]() { created = true; });
EXPECT_FALSE(created);
EXPECT_FALSE(handler_removed);
annotation_manager()->FulfillCreateRequests();
ASSERT_FALSE(created);
ASSERT_TRUE(handler_removed);
ASSERT_EQ(epitaph, ZX_ERR_INVALID_ARGS);
}
}
TEST_F(AnnotationManagerTest, LinkerTest_AnnotationViewCreatedFirst) {
// Consider the following Resource Graph:
//
// Scene -----> EntityNode ----------\
// v
// ViewHolder1
// = = = = = = = = = = = = = = = = .` = =| = = = = = = = = = = = =
// . Session_View1 .` v .
// . View1 ==> ViewNode1 .
// . || .
// . V .
// . Annotation .
// . ViewHolder ------\ .
// . .` \ .
// = = = = = = = = = = = = = = .` = = = = = = = \ = = = = = = = =
// . Session_Annotation .` V .
// . Annotation View ==> Annotation ViewNode .
// . .
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
//
// No matter if we create Annotation ViewHolder first or create Annotation
// View first, the ViewHolder should always be able to link with the
// Annotation View.
//
// In this test case we first create Annotation View, then use the Annotation
// API to create Annotation ViewHolder, and verify if they are linked with
// each other.
// Create View1 and ViewHolder1 and attach it to the scene1.
auto [view1_token, view_holder1_token] = scenic::ViewTokenPair::New();
auto [view1_ctrl_ref, view1_ref] = scenic::ViewRefPair::New();
fuchsia::ui::views::ViewRef view1_ref_for_creation;
view1_ref.Clone(&view1_ref_for_creation);
auto session_view1 = CreateAndRegisterSession();
CommandContext cmds = CreateCommandContext();
session_view1->ApplyCommand(
&cmds, scenic::NewCreateViewCmd(kView1Id, std::move(view1_token), std::move(view1_ctrl_ref),
std::move(view1_ref_for_creation), "view 1"));
cmds.Flush();
Apply(scenic::NewCreateViewHolderCmd(kViewHolder1Id, std::move(view_holder1_token), "holder 1"));
Apply(scenic::NewAddChildCmd(kEntityNodeId, kViewHolder1Id));
// Lookup View1 in the ResourceMap to verify that it is created successfully.
ViewPtr view1_ptr = session_view1->resources()->FindResource<View>(kView1Id);
EXPECT_TRUE(view1_ptr);
// Create Annotation View.
auto [annotation_view_token, annotation_view_holder_token] = scenic::ViewTokenPair::New();
auto session_annotation = CreateAndRegisterSession();
session_annotation->ApplyCommand(
&cmds, scenic::NewCreateViewCmd(kAnnotationViewId, std::move(annotation_view_token),
"annotation view"));
cmds.Flush();
// Lookup Annotation View in the ResourceMap to verify that it is created
// successfully.
ViewPtr annotation_view_ptr =
session_annotation->resources()->FindResource<View>(kAnnotationViewId);
EXPECT_TRUE(annotation_view_ptr);
// Create Annotation ViewHolder.
bool created = false;
constexpr AnnotationHandlerId kAnnotationHandlerId = 0;
annotation_manager()->RegisterHandler(kAnnotationHandlerId, [](auto) {});
annotation_manager()->RequestCreate(kAnnotationHandlerId, std::move(view1_ref),
std::move(annotation_view_holder_token),
[&created]() { created = true; });
EXPECT_FALSE(created);
annotation_manager()->FulfillCreateRequests();
ASSERT_TRUE(created);
// Lookup Annotation ViewHolder in View1.
EXPECT_EQ(view1_ptr->annotation_view_holders().size(), 1U);
ViewHolderPtr annotation_view_holder_ptr = *(view1_ptr->annotation_view_holders().begin());
EXPECT_TRUE(annotation_view_holder_ptr);
EXPECT_EQ(annotation_view_holder_ptr->view(), annotation_view_ptr.get());
EXPECT_EQ(annotation_view_ptr->view_holder(), annotation_view_holder_ptr.get());
EXPECT_TRUE(annotation_view_ptr->GetViewNode());
EXPECT_EQ(annotation_view_ptr->GetViewNode()->parent(), annotation_view_holder_ptr.get());
annotation_view_holder_ptr = nullptr;
annotation_view_ptr = nullptr;
}
TEST_F(AnnotationManagerTest, LinkerTest_AnnotationViewHolderCreatedFirst) {
// Consider the following Resource Graph:
//
// Scene -----> EntityNode ----------\
// v
// ViewHolder1
// = = = = = = = = = = = = = = = = .` = =| = = = = = = = = = = = =
// . Session_View1 .` v .
// . View1 ==> ViewNode1 .
// . || .
// . V .
// . Annotation .
// . ViewHolder ------\ .
// . .` \ .
// = = = = = = = = = = = = = = .` = = = = = = = \ = = = = = = = =
// . Session_Annotation .` V .
// . Annotation View ==> Annotation ViewNode .
// . .
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
//
// No matter if we create Annotation ViewHolder first or create Annotation
// View first, the ViewHolder should always be able to link with the
// Annotation View.
//
// In this test case we first create Annotation ViewHolder using the
// Annotation API, then we create Annotation View in Annotation session,
// and verify if they are linked with each other correctly.
// Create View1 and ViewHolder1 and attach it to the scene1.
auto [view1_token, view_holder1_token] = scenic::ViewTokenPair::New();
auto [view1_ctrl_ref, view1_ref] = scenic::ViewRefPair::New();
fuchsia::ui::views::ViewRef view1_ref_for_creation;
view1_ref.Clone(&view1_ref_for_creation);
auto session_view1 = CreateAndRegisterSession();
CommandContext cmds = CreateCommandContext();
session_view1->ApplyCommand(
&cmds, scenic::NewCreateViewCmd(kView1Id, std::move(view1_token), std::move(view1_ctrl_ref),
std::move(view1_ref_for_creation), "view 1"));
cmds.Flush();
Apply(scenic::NewCreateViewHolderCmd(kViewHolder1Id, std::move(view_holder1_token), "holder 1"));
Apply(scenic::NewAddChildCmd(kEntityNodeId, kViewHolder1Id));
// Lookup View1 in the ResourceMap to verify that it is created successfully.
ViewPtr view1_ptr = session_view1->resources()->FindResource<View>(kView1Id);
EXPECT_TRUE(view1_ptr);
// Create Annotation View Holder.
auto [annotation_view_token, annotation_view_holder_token] = scenic::ViewTokenPair::New();
bool created = false;
constexpr AnnotationHandlerId kAnnotationHandlerId = 0;
annotation_manager()->RegisterHandler(kAnnotationHandlerId, [](auto) {});
annotation_manager()->RequestCreate(kAnnotationHandlerId, std::move(view1_ref),
std::move(annotation_view_holder_token),
[&created]() { created = true; });
// Create Annotation View.
auto session_annotation = CreateAndRegisterSession();
session_annotation->ApplyCommand(
&cmds, scenic::NewCreateViewCmd(kAnnotationViewId, std::move(annotation_view_token),
"annotation view"));
cmds.Flush();
// Lookup Annotation View in the ResourceMap to verify that it is created
// successfully.
ViewPtr annotation_view_ptr =
session_annotation->resources()->FindResource<View>(kAnnotationViewId);
EXPECT_TRUE(annotation_view_ptr);
EXPECT_FALSE(created);
annotation_manager()->FulfillCreateRequests();
ASSERT_TRUE(created);
// Lookup Annotation ViewHolder in View1.
EXPECT_EQ(view1_ptr->annotation_view_holders().size(), 1U);
ViewHolderPtr annotation_view_holder_ptr = *(view1_ptr->annotation_view_holders().begin());
EXPECT_TRUE(annotation_view_holder_ptr);
EXPECT_EQ(annotation_view_holder_ptr->view(), annotation_view_ptr.get());
EXPECT_EQ(annotation_view_ptr->view_holder(), annotation_view_holder_ptr.get());
annotation_view_holder_ptr = nullptr;
annotation_view_ptr = nullptr;
}
TEST_F(AnnotationManagerTest, RemoveAnnotationView) {
// Consider the following Resource Graph:
//
// Scene -----> EntityNode ----------\
// v
// ViewHolder1
// = = = = = = = = = = = = = = = = .` = =| = = = = = = = = = = = =
// . Session_View1 .` v .
// . View1 ==> ViewNode1 .
// . || .
// . V .
// . Annotation .
// . ViewHolder ------\ .
// . .` \ .
// = = = = = = = = = = = = = = .` = = = = = = = \ = = = = = = = =
// . Session_Annotation .` V .
// . Annotation View ==> Annotation ViewNode .
// . .
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
//
// If the Annotation View is removed from the ResourceMap, the Annotation
// ViewHolder will be automatically removed from the View1 as well.
//
// Create View1 and ViewHolder1 and attach it to the scene1.
auto [view1_token, view_holder1_token] = scenic::ViewTokenPair::New();
auto [view1_ctrl_ref, view1_ref] = scenic::ViewRefPair::New();
fuchsia::ui::views::ViewRef view1_ref_for_creation;
view1_ref.Clone(&view1_ref_for_creation);
auto session_view1 = CreateAndRegisterSession();
CommandContext cmds = CreateCommandContext();
session_view1->ApplyCommand(
&cmds, scenic::NewCreateViewCmd(kView1Id, std::move(view1_token), std::move(view1_ctrl_ref),
std::move(view1_ref_for_creation), "view 1"));
cmds.Flush();
Apply(scenic::NewCreateViewHolderCmd(kViewHolder1Id, std::move(view_holder1_token), "holder 1"));
Apply(scenic::NewAddChildCmd(kEntityNodeId, kViewHolder1Id));
// Lookup View1 in the ResourceMap to verify that it is created successfully.
ViewPtr view1_ptr = session_view1->resources()->FindResource<View>(kView1Id);
EXPECT_TRUE(view1_ptr);
EXPECT_EQ(view1_ptr->annotation_view_holders().size(), 0U);
// Create Annotation View Holder.
auto [annotation_view_token, annotation_view_holder_token] = scenic::ViewTokenPair::New();
constexpr AnnotationHandlerId kAnnotationHandlerId = 0;
annotation_manager()->RegisterHandler(kAnnotationHandlerId, [](auto) {});
annotation_manager()->RequestCreate(kAnnotationHandlerId, std::move(view1_ref),
std::move(annotation_view_holder_token), []() {});
annotation_manager()->FulfillCreateRequests();
// Create Annotation View.
auto session_annotation = CreateAndRegisterSession();
session_annotation->ApplyCommand(
&cmds, scenic::NewCreateViewCmd(kAnnotationViewId, std::move(annotation_view_token),
"annotation view"));
cmds.Flush();
// Lookup Annotation View in the ResourceMap to verify that it is created
// successfully.
fxl::WeakPtr<View> annotation_view_weak_ptr =
session_annotation->resources()->FindResource<View>(kAnnotationViewId)->GetWeakPtr();
EXPECT_TRUE(annotation_view_weak_ptr);
EXPECT_EQ(view1_ptr->annotation_view_holders().size(), 1U);
fxl::WeakPtr<ViewHolder> annotation_view_holder_weak_ptr =
(*view1_ptr->annotation_view_holders().begin())->GetWeakPtr();
EXPECT_TRUE(annotation_view_holder_weak_ptr);
// Destroy Annotation View.
session_annotation->ApplyCommand(&cmds, scenic::NewReleaseResourceCmd(kAnnotationViewId));
cmds.Flush();
EXPECT_FALSE(annotation_view_weak_ptr);
EXPECT_FALSE(annotation_view_holder_weak_ptr);
EXPECT_EQ(view1_ptr->annotation_view_holders().size(), 0U);
}
TEST_F(AnnotationManagerTest, RemoveClientView) {
// Consider the following Resource Graph:
//
// Scene -----> EntityNode ----------\
// v
// ViewHolder1
// = = = = = = = = = = = = = = = = .` = =| = = = = = = = = = = = =
// . Session_View1 .` v .
// . View1 ==> ViewNode1 .
// . || .
// . V .
// . Annotation .
// . ViewHolder ------\ .
// . .` \ .
// = = = = = = = = = = = = = = .` = = = = = = = \ = = = = = = = =
// . Session_Annotation .` V .
// . Annotation View ==> Annotation ViewNode .
// . .
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
//
// If the Client View (View1) is removed from the ResourceMap, the Annotation
// ViewHolder will be removed, and the link between Annotation ViewHolder
// and Annotation View will be destroyed. The Annotation View is still
// available, and Session_Annotation will receive an ViewHolderDisconnected
// event so that it could delete the Annotation View and all related
// resources.
//
// Create View1 and ViewHolder1 and attach it to the scene1.
auto [view1_token, view_holder1_token] = scenic::ViewTokenPair::New();
auto [view1_ctrl_ref, view1_ref] = scenic::ViewRefPair::New();
fuchsia::ui::views::ViewRef view1_ref_for_creation;
view1_ref.Clone(&view1_ref_for_creation);
auto session_view1 = CreateAndRegisterSession();
CommandContext cmds = CreateCommandContext();
session_view1->ApplyCommand(
&cmds, scenic::NewCreateViewCmd(kView1Id, std::move(view1_token), std::move(view1_ctrl_ref),
std::move(view1_ref_for_creation), "view 1"));
cmds.Flush();
Apply(scenic::NewCreateViewHolderCmd(kViewHolder1Id, std::move(view_holder1_token), "holder 1"));
Apply(scenic::NewAddChildCmd(kEntityNodeId, kViewHolder1Id));
// Lookup View1 in the ResourceMap to verify that it is created successfully.
fxl::WeakPtr<View> view1_weak_ptr =
session_view1->resources()->FindResource<View>(kView1Id)->GetWeakPtr();
EXPECT_TRUE(view1_weak_ptr);
EXPECT_EQ(view1_weak_ptr->annotation_view_holders().size(), 0U);
// Create Annotation View Holder.
auto [annotation_view_token, annotation_view_holder_token] = scenic::ViewTokenPair::New();
constexpr AnnotationHandlerId kAnnotationHandlerId = 0;
annotation_manager()->RegisterHandler(kAnnotationHandlerId, [](auto) {});
annotation_manager()->RequestCreate(kAnnotationHandlerId, std::move(view1_ref),
std::move(annotation_view_holder_token), []() {});
annotation_manager()->FulfillCreateRequests();
// Create Annotation View.
auto session_annotation = CreateAndRegisterSession();
session_annotation->ApplyCommand(
&cmds, scenic::NewCreateViewCmd(kAnnotationViewId, std::move(annotation_view_token),
"annotation view"));
cmds.Flush();
// Lookup Annotation View in the ResourceMap to verify that it is created
// successfully.
fxl::WeakPtr<View> annotation_view_weak_ptr =
session_annotation->resources()->FindResource<View>(kAnnotationViewId)->GetWeakPtr();
EXPECT_TRUE(annotation_view_weak_ptr);
EXPECT_EQ(view1_weak_ptr->annotation_view_holders().size(), 1U);
fxl::WeakPtr<ViewHolder> annotation_view_holder_weak_ptr =
(*view1_weak_ptr->annotation_view_holders().begin())->GetWeakPtr();
EXPECT_TRUE(annotation_view_holder_weak_ptr);
// Destroy Client View.
ClearEvents();
session_view1->ApplyCommand(&cmds, scenic::NewReleaseResourceCmd(kView1Id));
cmds.Flush();
EXPECT_FALSE(view1_weak_ptr);
EXPECT_FALSE(annotation_view_holder_weak_ptr);
EXPECT_TRUE(annotation_view_weak_ptr);
// There should be only one ViewHolderDisconnected events.
bool annotation_view_holder_disconnected = false;
size_t view_holder_disconnected_events_count = 0U;
for (const auto& scenic_event : events()) {
if (scenic_event.is_gfx() && scenic_event.gfx().is_view_holder_disconnected()) {
++view_holder_disconnected_events_count;
if (scenic_event.gfx().view_holder_disconnected().view_id == kAnnotationViewId) {
annotation_view_holder_disconnected = true;
}
}
}
EXPECT_TRUE(annotation_view_holder_disconnected);
EXPECT_EQ(view_holder_disconnected_events_count, 1U);
}
TEST_F(AnnotationManagerTest, RemoveClientViewHolder) {
// Consider the following Resource Graph:
//
// Scene -----> EntityNode ----------\
// v
// ViewHolder1
// = = = = = = = = = = = = = = = = .` = =| = = = = = = = = = = = =
// . Session_View1 .` v .
// . View1 ==> ViewNode1 .
// . || .
// . V .
// . Annotation .
// . ViewHolder ------\ .
// . .` \ .
// = = = = = = = = = = = = = = .` = = = = = = = \ = = = = = = = =
// . Session_Annotation .` V .
// . Annotation View ==> Annotation ViewNode .
// . .
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
//
// If the client View is detached from the SceneGraph (e.g. the ViewHolder
// is released), the Annotation ViewHolder will be still a child of the
// client ViewNode, but it will be removed from the SceneGraph.
// Create View1 and ViewHolder1 and attach it to the scene1.
auto [view1_token, view_holder1_token] = scenic::ViewTokenPair::New();
auto [view1_ctrl_ref, view1_ref] = scenic::ViewRefPair::New();
fuchsia::ui::views::ViewRef view1_ref_for_creation;
view1_ref.Clone(&view1_ref_for_creation);
auto session_view1 = CreateAndRegisterSession();
CommandContext cmds = CreateCommandContext();
session_view1->ApplyCommand(
&cmds, scenic::NewCreateViewCmd(kView1Id, std::move(view1_token), std::move(view1_ctrl_ref),
std::move(view1_ref_for_creation), "view 111"));
cmds.Flush();
Apply(scenic::NewCreateViewHolderCmd(kViewHolder1Id, std::move(view_holder1_token),
"holder "
"111"));
Apply(scenic::NewAddChildCmd(kEntityNodeId, kViewHolder1Id));
// Lookup View1 in the ResourceMap to verify that it is created successfully.
fxl::WeakPtr<View> view1_weak_ptr =
session_view1->resources()->FindResource<View>(kView1Id)->GetWeakPtr();
EXPECT_TRUE(view1_weak_ptr);
EXPECT_EQ(view1_weak_ptr->annotation_view_holders().size(), 0U);
// Create Annotation View Holder.
auto [annotation_view_token, annotation_view_holder_token] = scenic::ViewTokenPair::New();
constexpr AnnotationHandlerId kAnnotationHandlerId = 0;
annotation_manager()->RegisterHandler(kAnnotationHandlerId, [](auto) {});
annotation_manager()->RequestCreate(kAnnotationHandlerId, std::move(view1_ref),
std::move(annotation_view_holder_token), []() {});
annotation_manager()->FulfillCreateRequests();
// Create Annotation View.
auto session_annotation = CreateAndRegisterSession();
session_annotation->ApplyCommand(
&cmds, scenic::NewCreateViewCmd(kAnnotationViewId, std::move(annotation_view_token),
"annotation view 111"));
cmds.Flush();
StageAndUpdateViewTree(scene_graph());
// Lookup Annotation View in the ResourceMap to verify that it is created
// successfully.
fxl::WeakPtr<View> annotation_view_weak_ptr =
session_annotation->resources()->FindResource<View>(kAnnotationViewId)->GetWeakPtr();
EXPECT_TRUE(annotation_view_weak_ptr);
EXPECT_EQ(view1_weak_ptr->annotation_view_holders().size(), 1U);
fxl::WeakPtr<ViewHolder> annotation_view_holder_weak_ptr =
(*view1_weak_ptr->annotation_view_holders().begin())->GetWeakPtr();
EXPECT_TRUE(annotation_view_holder_weak_ptr);
// Destroy client ViewHolder.
ClearEvents();
Apply(scenic::NewDetachCmd(kViewHolder1Id));
Apply(scenic::NewReleaseResourceCmd(kViewHolder1Id));
// Annotation ViewHolder should be still a child of client View.
EXPECT_TRUE(view1_weak_ptr && annotation_view_weak_ptr && annotation_view_holder_weak_ptr);
EXPECT_EQ(view1_weak_ptr->annotation_view_holders().size(), 1U);
EXPECT_EQ(view1_weak_ptr->annotation_view_holders().begin()->get(),
annotation_view_holder_weak_ptr.get());
EXPECT_EQ(annotation_view_holder_weak_ptr->view(), annotation_view_weak_ptr.get());
EXPECT_EQ(annotation_view_holder_weak_ptr.get(), annotation_view_weak_ptr->view_holder());
// There should be only one ViewHolderDisconnected events.
bool client_view_holder_disconnected = false;
size_t view_holder_disconnected_events_count = 0U;
for (const auto& scenic_event : events()) {
if (scenic_event.is_gfx() && scenic_event.gfx().is_view_holder_disconnected()) {
++view_holder_disconnected_events_count;
if (scenic_event.gfx().view_holder_disconnected().view_id == kView1Id) {
client_view_holder_disconnected = true;
}
}
}
EXPECT_TRUE(client_view_holder_disconnected);
EXPECT_EQ(view_holder_disconnected_events_count, 1U);
}
TEST_F(AnnotationManagerTest, ViewPropertiesPropagation) {
// Consider the following Resource Graph:
//
// Scene -----> EntityNode ----------\
// v
// ViewHolder1
// = = = = = = = = = = = = = = = = .` = =| = = = = = = = = = = = =
// . Session_View1 .` v .
// . View1 ==> ViewNode1 .
// . || .
// . V .
// . Annotation .
// . ViewHolder ------\ .
// . .` \ .
// = = = = = = = = = = = = = = .` = = = = = = = \ = = = = = = = =
// . Session_Annotation .` V .
// . Annotation View ==> Annotation ViewNode .
// . .
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
//
// When the Annotation ViewHolder is created, it should have the same
// ViewProperties (except for focus_change = false) as the client ViewHolder.
//
// When client ViewHolder chnages its ViewProperties, the same properties
// should be propagated to the annotation ViewHolder as well.
//
// Create View1 and ViewHolder1 and attach it to the scene1.
auto [view1_token, view_holder1_token] = scenic::ViewTokenPair::New();
auto [view1_ctrl_ref, view1_ref] = scenic::ViewRefPair::New();
fuchsia::ui::views::ViewRef view1_ref_for_creation;
view1_ref.Clone(&view1_ref_for_creation);
auto session_view1 = CreateAndRegisterSession();
CommandContext cmds = CreateCommandContext();
session_view1->ApplyCommand(
&cmds, scenic::NewCreateViewCmd(kView1Id, std::move(view1_token), std::move(view1_ctrl_ref),
std::move(view1_ref_for_creation), "view 1"));
cmds.Flush();
Apply(scenic::NewCreateViewHolderCmd(kViewHolder1Id, std::move(view_holder1_token), "holder 1"));
Apply(scenic::NewAddChildCmd(kEntityNodeId, kViewHolder1Id));
// Set up initial View properties.
fuchsia::ui::gfx::ViewProperties view_properties = {.bounding_box =
{
.min = {0, 0, 0},
.max = {600, 400, 1},
},
.inset_from_min = {10, 10, 0},
.inset_from_max = {10, 10, 0},
.focus_change = true,
.downward_input = true};
Apply(scenic::NewSetViewPropertiesCmd(kViewHolder1Id, view_properties));
// Lookup View1 in the ResourceMap to verify that it is created successfully.
ViewPtr view1_ptr = session_view1->resources()->FindResource<View>(kView1Id);
EXPECT_TRUE(view1_ptr);
EXPECT_EQ(view1_ptr->annotation_view_holders().size(), 0U);
// Create Annotation View Holder.
auto [annotation_view_token, annotation_view_holder_token] = scenic::ViewTokenPair::New();
constexpr AnnotationHandlerId kAnnotationHandlerId = 0;
annotation_manager()->RegisterHandler(kAnnotationHandlerId, [](auto) {});
annotation_manager()->RequestCreate(kAnnotationHandlerId, std::move(view1_ref),
std::move(annotation_view_holder_token), []() {});
annotation_manager()->FulfillCreateRequests();
// Create Annotation View.
ClearEvents();
auto session_annotation = CreateAndRegisterSession();
session_annotation->ApplyCommand(
&cmds, scenic::NewCreateViewCmd(kAnnotationViewId, std::move(annotation_view_token),
"annotation view"));
cmds.Flush();
// Verify that Annotation ViewHolder is created correctly.
EXPECT_EQ(view1_ptr->annotation_view_holders().size(), 1U);
fxl::WeakPtr<ViewHolder> annotation_view_holder_weak_ptr =
(*view1_ptr->annotation_view_holders().begin())->GetWeakPtr();
EXPECT_TRUE(annotation_view_holder_weak_ptr);
// Verify the Annotation ViewHolder has correct properties.
auto annotation_view_holder_properties = annotation_view_holder_weak_ptr->GetViewProperties();
EXPECT_TRUE(
fidl::Equals(annotation_view_holder_properties.bounding_box, view_properties.bounding_box));
EXPECT_TRUE(fidl::Equals(annotation_view_holder_properties.inset_from_min,
view_properties.inset_from_min));
EXPECT_TRUE(fidl::Equals(annotation_view_holder_properties.inset_from_max,
view_properties.inset_from_max));
EXPECT_EQ(annotation_view_holder_properties.focus_change, false);
// Verify that the session receives ViewPropertiesChangedEvent when creating
// the Annotation View.
auto view_properties_changed_event =
std::find_if(events().begin(), events().end(), [](const fuchsia::ui::scenic::Event& event) {
return event.is_gfx() && event.gfx().is_view_properties_changed() &&
event.gfx().view_properties_changed().view_id == kAnnotationViewId;
});
ASSERT_NE(view_properties_changed_event, events().end());
EXPECT_TRUE(
fidl::Equals(view_properties_changed_event->gfx().view_properties_changed().properties,
annotation_view_holder_properties));
// Modify the ViewProperties of View1.
ClearEvents();
view_properties.bounding_box = {.min = {0, 0, 0}, .max = {300, 200, 50}};
view_properties.inset_from_min = {20, 20, 0};
view_properties.inset_from_max = {20, 20, 0};
Apply(scenic::NewSetViewPropertiesCmd(kViewHolder1Id, view_properties));
// Verify the Annotation ViewHolder has correct properties.
annotation_view_holder_properties = annotation_view_holder_weak_ptr->GetViewProperties();
EXPECT_TRUE(
fidl::Equals(annotation_view_holder_properties.bounding_box, view_properties.bounding_box));
EXPECT_TRUE(fidl::Equals(annotation_view_holder_properties.inset_from_min,
view_properties.inset_from_min));
EXPECT_TRUE(fidl::Equals(annotation_view_holder_properties.inset_from_max,
view_properties.inset_from_max));
EXPECT_EQ(annotation_view_holder_properties.focus_change, false);
// Verify that the session receives ViewPropertiesChangedEvent when updating
// the ViewProperties of ViewHolder1.
view_properties_changed_event =
std::find_if(events().begin(), events().end(), [](const fuchsia::ui::scenic::Event& event) {
return event.is_gfx() && event.gfx().is_view_properties_changed() &&
event.gfx().view_properties_changed().view_id == kAnnotationViewId;
});
ASSERT_NE(view_properties_changed_event, events().end());
EXPECT_TRUE(
fidl::Equals(view_properties_changed_event->gfx().view_properties_changed().properties,
annotation_view_holder_properties));
}
TEST_F(AnnotationManagerTest, GlobalTransformPropagation) {
// Consider the following Resource Graph:
//
// Scene -----> EntityNode ----------\
// v
// ViewHolder1
// = = = = = = = = = = = = = = = = .` = =| = = = = = = = = = = = =
// . Session_View1 .` v .
// . View1 ==> ViewNode1 .
// . || .
// . V .
// . Annotation .
// . ViewHolder ------\ .
// . .` \ .
// = = = = = = = = = = = = = = .` = = = = = = = \ = = = = = = = =
// . Session_Annotation .` V .
// . Annotation View ==> Annotation ViewNode .
// . .
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
//
// When the Annotation ViewHolder is created, it should have the same global
// transformation (including translation and rotation) as the client
// ViewHolder.
//
// When client ViewHolder's transformation matrix changes, the same change
// should be made to the annotation ViewHolder as well.
//
// Create View1 and ViewHolder1 and attach it to the scene1.
auto [view1_token, view_holder1_token] = scenic::ViewTokenPair::New();
auto [view1_ctrl_ref, view1_ref] = scenic::ViewRefPair::New();
fuchsia::ui::views::ViewRef view1_ref_for_creation;
view1_ref.Clone(&view1_ref_for_creation);
auto session_view1 = CreateAndRegisterSession();
CommandContext cmds = CreateCommandContext();
session_view1->ApplyCommand(
&cmds, scenic::NewCreateViewCmd(kView1Id, std::move(view1_token), std::move(view1_ctrl_ref),
std::move(view1_ref_for_creation), "view 1"));
cmds.Flush();
Apply(scenic::NewCreateViewHolderCmd(kViewHolder1Id, std::move(view_holder1_token), "holder 1"));
Apply(scenic::NewAddChildCmd(kEntityNodeId, kViewHolder1Id));
// Set up initial View translation and rotation.
std::array<float, 3> translation = {100, 200, 0};
glm::quat glm_quat = glm::angleAxis(1.0f, glm::vec3(0, 0, 1));
std::array<float, 4> quaternion = {glm_quat.x, glm_quat.y, glm_quat.z, glm_quat.w};
Apply(scenic::NewSetTranslationCmd(kViewHolder1Id, translation));
Apply(scenic::NewSetRotationCmd(kViewHolder1Id, quaternion));
// Lookup View1 in the ResourceMap to verify that it is created successfully.
ViewPtr view1_ptr = session_view1->resources()->FindResource<View>(kView1Id);
EXPECT_TRUE(view1_ptr);
EXPECT_EQ(view1_ptr->annotation_view_holders().size(), 0U);
// Create Annotation View Holder.
auto [annotation_view_token, annotation_view_holder_token] = scenic::ViewTokenPair::New();
constexpr AnnotationHandlerId kAnnotationHandlerId = 0;
annotation_manager()->RegisterHandler(kAnnotationHandlerId, [](auto) {});
annotation_manager()->RequestCreate(kAnnotationHandlerId, std::move(view1_ref),
std::move(annotation_view_holder_token), []() {});
annotation_manager()->FulfillCreateRequests();
// Create Annotation View.
auto session_annotation = CreateAndRegisterSession();
session_annotation->ApplyCommand(
&cmds, scenic::NewCreateViewCmd(kAnnotationViewId, std::move(annotation_view_token),
"annotation view"));
cmds.Flush();
// Verify that Annotation ViewHolder is created correctly.
EXPECT_EQ(view1_ptr->annotation_view_holders().size(), 1U);
fxl::WeakPtr<ViewHolder> annotation_view_holder_weak_ptr =
(*view1_ptr->annotation_view_holders().begin())->GetWeakPtr();
EXPECT_TRUE(annotation_view_holder_weak_ptr);
// Verify the Annotation ViewHolder has correct transform matrix.
EXPECT_EQ(view1_ptr->view_holder()->GetGlobalTransform(),
annotation_view_holder_weak_ptr->GetGlobalTransform());
// Modify the translation and rotation of ViewHolder1.
translation = {-100, -200, 0};
glm_quat = glm::angleAxis(2.0f, glm::vec3(0, 1, 0));
quaternion = {glm_quat.x, glm_quat.y, glm_quat.z, glm_quat.w};
Apply(scenic::NewSetTranslationCmd(kViewHolder1Id, translation));
Apply(scenic::NewSetRotationCmd(kViewHolder1Id, quaternion));
// Verify the Annotation ViewHolder has correct transform matrix.
EXPECT_EQ(view1_ptr->view_holder()->GetGlobalTransform(),
annotation_view_holder_weak_ptr->GetGlobalTransform());
}
} // namespace test
} // namespace gfx
} // namespace scenic_impl