blob: 66561998d8938d3976b85f14515a572489ae6f5a [file] [log] [blame]
// Copyright 2015 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 "garnet/bin/ui/view_manager/view_registry.h"
#include <algorithm>
#include <cmath>
#include <utility>
#include <fuchsia/accessibility/cpp/fidl.h>
#include <fuchsia/ui/input/cpp/fidl.h>
#include <lib/async/cpp/task.h>
#include <lib/async/default.h>
#include "garnet/bin/ui/view_manager/view_impl.h"
#include "garnet/bin/ui/view_manager/view_tree_impl.h"
#include "garnet/public/lib/escher/util/type_utils.h"
#include "lib/component/cpp/connect.h"
#include "lib/fsl/handles/object_info.h"
#include "lib/fxl/logging.h"
#include "lib/fxl/memory/weak_ptr.h"
#include "lib/fxl/strings/string_printf.h"
#include "lib/ui/scenic/cpp/commands.h"
#include "lib/ui/scenic/cpp/resources.h"
#include "lib/ui/views/cpp/formatting.h"
namespace view_manager {
namespace {
class SnapshotCallbackImpl : public fuchsia::ui::gfx::SnapshotCallbackHACK {
private:
fit::function<void(::fuchsia::mem::Buffer)> callback_;
fidl::Binding<::fuchsia::ui::gfx::SnapshotCallbackHACK> binding_;
fit::function<void()> clear_fn_;
public:
explicit SnapshotCallbackImpl(
fidl::InterfaceRequest<fuchsia::ui::gfx::SnapshotCallbackHACK> request,
fit::function<void(::fuchsia::mem::Buffer)> callback)
: callback_(std::move(callback)), binding_(this, std::move(request)) {}
~SnapshotCallbackImpl() {}
void SetClear(fit::function<void()> clear_fn) {
clear_fn_ = std::move(clear_fn);
}
virtual void OnData(::fuchsia::mem::Buffer data) override {
callback_(std::move(data));
if (clear_fn_)
clear_fn_();
}
};
bool Validate(const ::fuchsia::ui::viewsv1::ViewLayout& value) {
return value.size.width >= 0 && value.size.height >= 0;
}
bool Validate(const ::fuchsia::ui::viewsv1::ViewProperties& value) {
if (value.view_layout && !Validate(*value.view_layout))
return false;
return true;
}
std::string SanitizeLabel(fidl::StringPtr label) {
return label.get().substr(0, ::fuchsia::ui::viewsv1::kLabelMaxLength);
}
} // namespace
ViewRegistry::ViewRegistry(component::StartupContext* startup_context)
: startup_context_(startup_context),
scenic_(startup_context_
->ConnectToEnvironmentService<fuchsia::ui::scenic::Scenic>()),
session_(scenic_.get()),
weak_factory_(this) {
// TODO(MZ-128): Register session listener and destroy views if their
// content nodes become unavailable.
scenic_.set_error_handler([](zx_status_t error) {
FXL_LOG(ERROR) << "Exiting due to scene manager connection error.";
exit(1);
});
session_.set_error_handler([](zx_status_t error) {
FXL_LOG(ERROR) << "Exiting due to session connection error.";
exit(1);
});
}
ViewRegistry::~ViewRegistry() {}
void ViewRegistry::GetScenic(
fidl::InterfaceRequest<fuchsia::ui::scenic::Scenic> scenic_request) {
// TODO(jeffbrown): We should have a better way to duplicate the
// SceneManager connection without going back out through the environment.
startup_context_->ConnectToEnvironmentService(std::move(scenic_request));
}
// CREATE / DESTROY VIEWS
void ViewRegistry::CreateView(
fidl::InterfaceRequest<::fuchsia::ui::viewsv1::View> view_request,
zx::eventpair view_token,
::fuchsia::ui::viewsv1::ViewListenerPtr view_listener,
zx::eventpair parent_export_token, fidl::StringPtr label) {
FXL_DCHECK(view_request.is_valid());
FXL_DCHECK(view_token);
FXL_DCHECK(view_listener);
FXL_DCHECK(parent_export_token);
uint32_t view_id = next_view_id_value_++;
FXL_CHECK(view_id);
FXL_CHECK(!FindView(view_id));
// Create the state.
auto view_state = std::make_unique<ViewState>(
this, view_id, std::move(view_request), std::move(view_listener),
std::move(view_token), std::move(parent_export_token), scenic_.get(),
SanitizeLabel(label));
ViewState* view_state_ptr = view_state.get();
views_by_token_.emplace(view_id, std::move(view_state));
FXL_VLOG(1) << "CreateView: view=" << view_state_ptr;
}
void ViewRegistry::OnViewDied(ViewState* view_state,
const std::string& reason) {
FXL_DCHECK(IsViewStateRegisteredDebug(view_state));
FXL_VLOG(1) << "OnViewDied: view=" << view_state << ", reason=" << reason;
UnregisterView(view_state);
}
void ViewRegistry::UnregisterView(ViewState* view_state) {
FXL_DCHECK(IsViewStateRegisteredDebug(view_state));
FXL_VLOG(1) << "UnregisterView: view=" << view_state;
if (ViewStub* view_stub = view_state->view_stub()) {
view_stub->ReleaseView();
}
UnregisterChildren(view_state);
// Remove the view's content node from the session.
view_state->ReleaseScenicResources();
// Remove from registry.
views_by_token_.erase(view_state->view_token());
}
// CREATE / DESTROY VIEW TREES
void ViewRegistry::CreateViewTree(
fidl::InterfaceRequest<::fuchsia::ui::viewsv1::ViewTree> view_tree_request,
::fuchsia::ui::viewsv1::ViewTreeListenerPtr view_tree_listener,
fidl::StringPtr label) {
FXL_DCHECK(view_tree_request.is_valid());
FXL_DCHECK(view_tree_listener);
::fuchsia::ui::viewsv1::ViewTreeToken view_tree_token;
view_tree_token.value = next_view_tree_token_value_++;
FXL_CHECK(view_tree_token.value);
FXL_CHECK(!FindViewTree(view_tree_token.value));
// Create the state and bind the interfaces to it.
auto tree_state = std::make_unique<ViewTreeState>(
this, view_tree_token, std::move(view_tree_request),
std::move(view_tree_listener), scenic_.get(), SanitizeLabel(label));
// Add to registry.
ViewTreeState* tree_state_ptr = tree_state.get();
view_trees_by_token_.emplace(tree_state->view_tree_token().value,
std::move(tree_state));
FXL_VLOG(1) << "CreateViewTree: tree=" << tree_state_ptr;
}
void ViewRegistry::OnViewTreeDied(ViewTreeState* tree_state,
const std::string& reason) {
FXL_DCHECK(IsViewTreeStateRegisteredDebug(tree_state));
FXL_VLOG(1) << "OnViewTreeDied: tree=" << tree_state << ", reason=" << reason;
UnregisterViewTree(tree_state);
}
void ViewRegistry::UnregisterViewTree(ViewTreeState* tree_state) {
FXL_DCHECK(IsViewTreeStateRegisteredDebug(tree_state));
FXL_VLOG(1) << "UnregisterViewTree: tree=" << tree_state;
UnregisterChildren(tree_state);
// Remove from registry.
view_trees_by_token_.erase(tree_state->view_tree_token().value);
}
// LIFETIME
void ViewRegistry::UnregisterViewContainer(
ViewContainerState* container_state) {
FXL_DCHECK(IsViewContainerStateRegisteredDebug(container_state));
ViewState* view_state = container_state->AsViewState();
if (view_state)
UnregisterView(view_state);
else
UnregisterViewTree(container_state->AsViewTreeState());
}
void ViewRegistry::UnregisterViewStub(std::unique_ptr<ViewStub> view_stub) {
FXL_DCHECK(view_stub);
ViewState* view_state = view_stub->ReleaseView();
if (view_state)
UnregisterView(view_state);
ReleaseViewStubChildHost(view_stub.get());
}
void ViewRegistry::UnregisterChildren(ViewContainerState* container_state) {
FXL_DCHECK(IsViewContainerStateRegisteredDebug(container_state));
container_state->RemoveAllChildren();
}
void ViewRegistry::ReleaseViewStubChildHost(ViewStub* view_stub) {
view_stub->ReleaseHost();
SchedulePresentSession();
}
// TREE MANIPULATION
void ViewRegistry::AddChild(ViewContainerState* container_state,
uint32_t child_key, zx::eventpair view_holder_token,
zx::eventpair host_import_token) {
FXL_DCHECK(IsViewContainerStateRegisteredDebug(container_state));
FXL_DCHECK(view_holder_token);
FXL_DCHECK(host_import_token);
FXL_VLOG(1) << "AddChild: container=" << container_state
<< ", child_key=" << child_key;
// Ensure there are no other children with the same key.
if (container_state->children().find(child_key) !=
container_state->children().end()) {
FXL_LOG(ERROR) << "Attempted to add a child with a duplicate key: "
<< "container=" << container_state
<< ", child_key=" << child_key;
UnregisterViewContainer(container_state);
return;
}
// If this is a view tree, ensure it only has one root.
ViewTreeState* view_tree_state = container_state->AsViewTreeState();
if (view_tree_state && !container_state->children().empty()) {
FXL_LOG(ERROR) << "Attempted to add a second child to a view tree: "
<< "container=" << container_state
<< ", child_key=" << child_key;
UnregisterViewContainer(container_state);
return;
}
// Add a stub, pending resolution of the view owner.
// Assuming the stub isn't removed prematurely, |OnViewResolved| will be
// called asynchronously with the result of the resolution.
container_state->AddChild(child_key, std::move(view_holder_token),
std::move(host_import_token));
}
void ViewRegistry::RemoveChild(ViewContainerState* container_state,
uint32_t child_key,
zx::eventpair transferred_view_holder_token) {
FXL_DCHECK(IsViewContainerStateRegisteredDebug(container_state));
FXL_VLOG(1) << "RemoveChild: container=" << container_state
<< ", child_key=" << child_key;
// Ensure the child key exists in the container.
auto child_it = container_state->children().find(child_key);
if (child_it == container_state->children().end()) {
FXL_LOG(ERROR) << "Attempted to remove a child with an invalid key: "
<< "container=" << container_state
<< ", child_key=" << child_key;
UnregisterViewContainer(container_state);
return;
}
container_state->RemoveChild(child_key,
std::move(transferred_view_holder_token));
}
void ViewRegistry::SetChildProperties(
ViewContainerState* container_state, uint32_t child_key,
::fuchsia::ui::viewsv1::ViewPropertiesPtr child_properties) {
FXL_DCHECK(IsViewContainerStateRegisteredDebug(container_state));
FXL_VLOG(1) << "SetChildProperties: container=" << container_state
<< ", child_key=" << child_key
<< ", child_properties=" << child_properties;
// Check whether the properties are well-formed.
if (child_properties && !Validate(*child_properties)) {
FXL_LOG(ERROR) << "Attempted to set invalid child view properties: "
<< "container=" << container_state
<< ", child_key=" << child_key
<< ", child_properties=" << child_properties;
UnregisterViewContainer(container_state);
return;
}
// Check whether the child key exists in the container.
auto child_it = container_state->children().find(child_key);
if (child_it == container_state->children().end()) {
FXL_LOG(ERROR) << "Attempted to modify child with an invalid key: "
<< "container=" << container_state
<< ", child_key=" << child_key
<< ", child_properties=" << child_properties;
UnregisterViewContainer(container_state);
return;
}
container_state->SetChildProperties(child_key, std::move(child_properties));
}
void ViewRegistry::RequestSnapshotHACK(
ViewContainerState* container_state, uint32_t child_key,
fit::function<void(::fuchsia::mem::Buffer)> callback) {
FXL_DCHECK(IsViewContainerStateRegisteredDebug(container_state));
// Check whether the child key exists in the container.
auto child_it = container_state->children().find(child_key);
if (child_it == container_state->children().end()) {
FXL_LOG(ERROR) << "Attempted to modify child with an invalid key: "
<< "container=" << container_state
<< ", child_key=" << child_key;
UnregisterViewContainer(container_state);
// TODO(SCN-978): Return an error to the caller for invalid data.
callback(fuchsia::mem::Buffer{});
return;
}
fuchsia::ui::gfx::SnapshotCallbackHACKPtr snapshot_callback;
auto snapshot_callback_impl = std::make_shared<SnapshotCallbackImpl>(
snapshot_callback.NewRequest(), std::move(callback));
snapshot_callback_impl->SetClear([this, snapshot_callback_impl]() {
snapshot_bindings_.remove(snapshot_callback_impl);
});
snapshot_bindings_.push_back(std::move(snapshot_callback_impl));
// Snapshot the child.
child_it->second->host_node->Snapshot(std::move(snapshot_callback));
SchedulePresentSession();
}
void ViewRegistry::SendSizeChangeHintHACK(ViewContainerState* container_state,
uint32_t child_key,
float width_change_factor,
float height_change_factor) {
FXL_DCHECK(IsViewContainerStateRegisteredDebug(container_state));
FXL_VLOG(1) << "SendSizeChangeHintHACK: container=" << container_state
<< ", width_change_factor=" << width_change_factor
<< ", height_change_factor=" << height_change_factor << "}";
// Check whether the child key exists in the container.
auto child_it = container_state->children().find(child_key);
if (child_it == container_state->children().end()) {
FXL_LOG(ERROR) << "Attempted to modify child with an invalid key: "
<< "container=" << container_state
<< ", child_key=" << child_key;
UnregisterViewContainer(container_state);
return;
}
child_it->second->host_node->SendSizeChangeHint(width_change_factor,
height_change_factor);
SchedulePresentSession();
}
void ViewRegistry::SchedulePresentSession() {
if (!present_session_scheduled_) {
present_session_scheduled_ = true;
async::PostTask(async_get_default_dispatcher(),
[weak = weak_factory_.GetWeakPtr()] {
if (weak)
weak->PresentSession();
});
}
}
void ViewRegistry::PresentSession() {
FXL_DCHECK(present_session_scheduled_);
present_session_scheduled_ = false;
session_.Present(0, [this](fuchsia::images::PresentationInfo info) {});
}
// VIEW AND VIEW TREE SERVICE PROVIDERS
void ViewRegistry::ConnectToViewService(ViewState* view_state,
const fidl::StringPtr& service_name,
zx::channel client_handle) {
FXL_DCHECK(IsViewStateRegisteredDebug(view_state));
}
void ViewRegistry::ConnectToViewTreeService(ViewTreeState* tree_state,
const fidl::StringPtr& service_name,
zx::channel client_handle) {
FXL_DCHECK(IsViewTreeStateRegisteredDebug(tree_state));
}
// EXTERNAL SIGNALING
void ViewRegistry::SendChildAttached(
ViewContainerState* container_state, uint32_t child_key,
::fuchsia::ui::viewsv1::ViewInfo child_view_info) {
FXL_DCHECK(container_state);
if (!container_state->view_container_listener())
return;
// TODO: Detect ANRs
FXL_VLOG(1) << "SendChildAttached: container_state=" << container_state
<< ", child_key=" << child_key
<< ", child_view_info=" << child_view_info;
container_state->view_container_listener()->OnChildAttached(
child_key, child_view_info, [] {});
}
void ViewRegistry::SendChildUnavailable(ViewContainerState* container_state,
uint32_t child_key) {
FXL_DCHECK(container_state);
if (!container_state->view_container_listener())
return;
// TODO: Detect ANRs
FXL_VLOG(1) << "SendChildUnavailable: container=" << container_state
<< ", child_key=" << child_key;
container_state->view_container_listener()->OnChildUnavailable(child_key,
[] {});
}
// TRANSFERRING VIEWS
std::unique_ptr<ViewContainerState::ChildView> ViewRegistry::FindOrphanedView(
zx_handle_t view_holder_token) {
zx_koid_t peer_koid = fsl::GetRelatedKoid(view_holder_token);
auto view_it = orphaned_views_.find(peer_koid);
if (view_it != orphaned_views_.end()) {
auto child_view = std::move(view_it->second.child_view);
orphaned_views_.erase(view_it);
return child_view;
}
return nullptr;
}
void ViewRegistry::AddOrphanedView(
zx::eventpair view_holder_token,
std::unique_ptr<ViewContainerState::ChildView> child) {
zx_koid_t koid = fsl::GetKoid(view_holder_token.get());
orphaned_views_[koid] = {std::move(view_holder_token), std::move(child)};
}
void ViewRegistry::RemoveOrphanedView(ViewContainerState::ChildView* child) {
for (auto entry_it = orphaned_views_.begin();
entry_it != orphaned_views_.end(); entry_it++) {
if (entry_it->second.child_view.get() == child) {
orphaned_views_.erase(entry_it);
}
}
}
// SNAPSHOT
// TODO(SCN-1263): Get Snapshots working with Views v2
void ViewRegistry::TakeSnapshot(
uint64_t view_koid, fit::function<void(::fuchsia::mem::Buffer)> callback) {}
// LOOKUP
ViewState* ViewRegistry::FindView(uint32_t view_token) {
auto it = views_by_token_.find(view_token);
return it != views_by_token_.end() ? it->second.get() : nullptr;
}
ViewTreeState* ViewRegistry::FindViewTree(uint32_t view_tree_token_value) {
auto it = view_trees_by_token_.find(view_tree_token_value);
return it != view_trees_by_token_.end() ? it->second.get() : nullptr;
}
} // namespace view_manager