blob: 74a0fe142da435d307316694fc8008f2f9916695 [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/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;
}
// Returns true if the properties are valid and are sufficient for
// operating the view tree.
bool IsComplete(const ::fuchsia::ui::viewsv1::ViewProperties& value) {
return Validate(value) && value.view_layout;
}
void ApplyOverrides(::fuchsia::ui::viewsv1::ViewProperties* value,
const ::fuchsia::ui::viewsv1::ViewProperties* overrides) {
if (!overrides)
return;
if (overrides->view_layout)
*value->view_layout = *overrides->view_layout;
}
std::string SanitizeLabel(fidl::StringPtr label) {
return label.get().substr(0, ::fuchsia::ui::viewsv1::kLabelMaxLength);
}
bool Equals(const ::fuchsia::ui::viewsv1::ViewPropertiesPtr& a,
const ::fuchsia::ui::viewsv1::ViewPropertiesPtr& b) {
if (!a || !b)
return !a && !b;
return *a == *b;
}
} // 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 and bind the interfaces to it.
ViewLinker::ImportLink view_owner_link =
view_linker_.CreateImport(std::move(view_token), this);
auto view_state = std::make_unique<ViewState>(
this, view_id, std::move(view_request), std::move(view_listener),
&session_, SanitizeLabel(label));
// Export a node which represents the view's attachment point.
view_state->top_node().Export(std::move(parent_export_token));
view_state->top_node().SetTag(view_state->view_token());
view_state->top_node().SetLabel(view_state->FormattedLabel());
// TODO(MZ-371): Avoid Z-fighting by introducing a smidgen of elevation
// between each view and its embedded sub-views. This is not a long-term fix.
view_state->top_node().SetTranslation(0.f, 0.f, 0.1f);
SchedulePresentSession();
// Begin tracking the view, and bind it to the owner link. Binding may cause
// the ViewStub to be attached, so we make sure to begin tracking the view in
// the map beforehand.
ViewState* view_state_ptr = view_state.get();
views_by_token_.emplace(view_id, std::move(view_state));
view_state_ptr->BindOwner(std::move(view_owner_link));
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->top_node().Detach();
SchedulePresentSession();
// 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), 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));
// Recursively unregister all children since they will become unowned
// at this point taking care to unlink each one before its unregistration.
for (auto& child : container_state->UnlinkAllChildren())
UnregisterViewStub(std::move(child));
}
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.
ViewLinker::ExportLink view_link =
view_linker_.CreateExport(std::move(view_holder_token), this);
container_state->LinkChild(child_key, std::unique_ptr<ViewStub>(new ViewStub(
this, std::move(view_link),
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;
}
// Unlink the child from its container.
TransferOrUnregisterViewStub(container_state->UnlinkChild(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;
}
// Immediately discard requests on unavailable views.
ViewStub* child_stub = child_it->second.get();
if (child_stub->is_unavailable())
return;
// Store the updated properties specified by the container if changed.
if (Equals(child_properties, child_stub->properties()))
return;
// Apply the change.
child_stub->SetProperties(std::move(child_properties), &session_);
if (child_stub->state()) {
InvalidateView(child_stub->state(),
ViewState::INVALIDATION_PROPERTIES_CHANGED);
}
}
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;
}
// Immediately discard requests on unavailable views.
ViewStub* child_stub = child_it->second.get();
if (child_stub->is_unavailable() || child_stub->is_pending()) {
FXL_VLOG(1) << "RequestSnapshot called for view that is currently "
<< (child_stub->is_unavailable() ? "unavailable" : "pending");
// 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_stub->state()->top_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;
}
// Immediately discard requests on unavailable views.
ViewStub* child_stub = child_it->second.get();
if (child_stub->is_unavailable() || child_stub->is_pending()) {
FXL_VLOG(1) << "SendSizeChangeHintHACK called for view that is currently "
<< (child_stub->is_unavailable() ? "unavailable" : "pending");
return;
}
FXL_DCHECK(child_stub->state());
child_stub->state()->top_node().SendSizeChangeHint(width_change_factor,
height_change_factor);
SchedulePresentSession();
}
void ViewRegistry::OnViewResolved(ViewStub* view_stub, ViewState* view_state) {
FXL_DCHECK(view_stub);
if (view_state)
AttachResolvedViewAndNotify(view_stub, view_state);
else
ReleaseUnavailableViewAndNotify(view_stub);
}
void ViewRegistry::TransferView(ViewState* view_state,
zx::eventpair transferred_view_token) {
FXL_DCHECK(transferred_view_token);
if (view_state) {
InvalidateView(view_state, ViewState::INVALIDATION_PARENT_CHANGED);
// This will cause the view_state to be rebound, and released from the
// view_stub.
ViewLinker::ImportLink view_owner_link =
view_linker_.CreateImport(std::move(transferred_view_token), this);
view_state->BindOwner(std::move(view_owner_link));
}
}
void ViewRegistry::AttachResolvedViewAndNotify(ViewStub* view_stub,
ViewState* view_state) {
FXL_DCHECK(view_stub);
FXL_DCHECK(IsViewStateRegisteredDebug(view_state));
FXL_VLOG(2) << "AttachViewStubAndNotify: view=" << view_state;
// Precondition: The view_state will not have a view_stub attached.
FXL_CHECK(!view_state->view_stub())
<< "Attempted to attach ViewState " << view_state
<< " that already had a ViewStub";
// Attach the view's content.
if (view_stub->container()) {
view_stub->ImportHostNode(&session_);
view_stub->host_node()->AddChild(view_state->top_node());
SchedulePresentSession();
SendChildAttached(view_stub->container(), view_stub->key(),
::fuchsia::ui::viewsv1::ViewInfo());
}
// Attach the view.
view_stub->AttachView(view_state);
InvalidateView(view_state, ViewState::INVALIDATION_PARENT_CHANGED);
}
void ViewRegistry::ReleaseUnavailableViewAndNotify(ViewStub* view_stub) {
FXL_DCHECK(view_stub);
FXL_VLOG(2) << "ReleaseUnavailableViewAndNotify: key=" << view_stub->key();
ViewState* view_state = view_stub->ReleaseView();
FXL_DCHECK(!view_state);
if (view_stub->container())
SendChildUnavailable(view_stub->container(), view_stub->key());
}
void ViewRegistry::TransferOrUnregisterViewStub(
std::unique_ptr<ViewStub> view_stub, zx::eventpair transferred_view_token) {
FXL_DCHECK(view_stub);
if (transferred_view_token) {
ReleaseViewStubChildHost(view_stub.get());
if (view_stub->state()) {
ViewState* view_state = view_stub->ReleaseView();
TransferView(view_state, std::move(transferred_view_token));
return;
}
if (view_stub->is_pending()) {
FXL_DCHECK(!view_stub->state());
// Handle transfer of pending view.
view_stub->TransferViewWhenResolved(std::move(view_stub),
std::move(transferred_view_token));
return;
}
}
UnregisterViewStub(std::move(view_stub));
}
// INVALIDATION
void ViewRegistry::InvalidateView(ViewState* view_state, uint32_t flags) {
FXL_DCHECK(IsViewStateRegisteredDebug(view_state));
FXL_VLOG(2) << "InvalidateView: view=" << view_state << ", flags=" << flags;
view_state->set_invalidation_flags(view_state->invalidation_flags() | flags);
if (view_state->view_stub() && view_state->view_stub()->tree()) {
InvalidateViewTree(view_state->view_stub()->tree(),
ViewTreeState::INVALIDATION_VIEWS_INVALIDATED);
}
}
void ViewRegistry::InvalidateViewTree(ViewTreeState* tree_state,
uint32_t flags) {
FXL_DCHECK(IsViewTreeStateRegisteredDebug(tree_state));
FXL_VLOG(2) << "InvalidateViewTree: tree=" << tree_state
<< ", flags=" << flags;
tree_state->set_invalidation_flags(tree_state->invalidation_flags() | flags);
ScheduleTraversal();
}
void ViewRegistry::ScheduleTraversal() {
if (!traversal_scheduled_) {
traversal_scheduled_ = true;
async::PostTask(async_get_default_dispatcher(),
[weak = weak_factory_.GetWeakPtr()] {
if (weak)
weak->Traverse();
});
}
}
void ViewRegistry::Traverse() {
FXL_DCHECK(traversal_scheduled_);
traversal_scheduled_ = false;
for (const auto& pair : view_trees_by_token_)
TraverseViewTree(pair.second.get());
}
void ViewRegistry::TraverseViewTree(ViewTreeState* tree_state) {
FXL_DCHECK(IsViewTreeStateRegisteredDebug(tree_state));
FXL_VLOG(2) << "TraverseViewTree: tree=" << tree_state
<< ", invalidation_flags=" << tree_state->invalidation_flags();
uint32_t flags = tree_state->invalidation_flags();
if (flags & ViewTreeState::INVALIDATION_VIEWS_INVALIDATED) {
ViewStub* root_stub = tree_state->GetRoot();
if (root_stub && root_stub->state())
TraverseView(root_stub->state(), false);
}
tree_state->set_invalidation_flags(0u);
}
void ViewRegistry::TraverseView(ViewState* view_state,
bool parent_properties_changed) {
FXL_DCHECK(IsViewStateRegisteredDebug(view_state));
FXL_VLOG(2) << "TraverseView: view=" << view_state
<< ", parent_properties_changed=" << parent_properties_changed
<< ", invalidation_flags=" << view_state->invalidation_flags();
uint32_t flags = view_state->invalidation_flags();
// Update view properties.
bool view_properties_changed = false;
if (parent_properties_changed ||
(flags & (ViewState::INVALIDATION_PROPERTIES_CHANGED |
ViewState::INVALIDATION_PARENT_CHANGED))) {
::fuchsia::ui::viewsv1::ViewPropertiesPtr properties =
ResolveViewProperties(view_state);
if (properties) {
if (!view_state->issued_properties() ||
!Equals(properties, view_state->issued_properties())) {
view_state->IssueProperties(std::move(properties));
view_properties_changed = true;
}
}
flags &= ~(ViewState::INVALIDATION_PROPERTIES_CHANGED |
ViewState::INVALIDATION_PARENT_CHANGED);
}
// If we don't have view properties yet then we cannot pursue traversals
// any further.
if (!view_state->issued_properties()) {
FXL_VLOG(2) << "View has no valid properties: view=" << view_state;
view_state->set_invalidation_flags(flags);
return;
}
// Deliver property change event if needed.
bool send_properties = view_properties_changed ||
(flags & ViewState::INVALIDATION_RESEND_PROPERTIES);
if (send_properties) {
if (!(flags & ViewState::INVALIDATION_IN_PROGRESS)) {
::fuchsia::ui::viewsv1::ViewProperties cloned_properties;
view_state->issued_properties()->Clone(&cloned_properties);
SendPropertiesChanged(view_state, std::move(cloned_properties));
flags = ViewState::INVALIDATION_IN_PROGRESS;
} else {
FXL_VLOG(2) << "View invalidation stalled awaiting response: view="
<< view_state;
if (send_properties)
flags |= ViewState::INVALIDATION_RESEND_PROPERTIES;
flags |= ViewState::INVALIDATION_STALLED;
}
}
view_state->set_invalidation_flags(flags);
// TODO(jeffbrown): Optimize propagation.
// This should defer traversal of the rest of the subtree until the view
// flushes its container or a timeout expires. We will need to be careful
// to ensure that we completely process one traversal before starting the
// next one and we'll have to retain some state. The same behavior should
// be applied when the parent's own properties change (assuming that it is
// likely to want to resize its children, unless it says otherwise somehow).
// Traverse all children.
for (const auto& pair : view_state->children()) {
ViewState* child_state = pair.second->state();
if (child_state)
TraverseView(pair.second->state(), view_properties_changed);
}
}
::fuchsia::ui::viewsv1::ViewPropertiesPtr ViewRegistry::ResolveViewProperties(
ViewState* view_state) {
FXL_DCHECK(IsViewStateRegisteredDebug(view_state));
ViewStub* view_stub = view_state->view_stub();
if (!view_stub || !view_stub->properties())
return nullptr;
if (view_stub->parent()) {
if (!view_stub->parent()->issued_properties())
return nullptr;
auto properties = ::fuchsia::ui::viewsv1::ViewProperties::New();
view_stub->parent()->issued_properties()->Clone(properties.get());
ApplyOverrides(properties.get(), view_stub->properties().get());
return properties;
} else if (view_stub->is_root_of_tree()) {
if (!view_stub->properties() || !IsComplete(*view_stub->properties())) {
FXL_VLOG(2) << "View tree properties are incomplete: root=" << view_state
<< ", properties=" << view_stub->properties();
return nullptr;
}
auto cloned_properties = ::fuchsia::ui::viewsv1::ViewProperties::New();
view_stub->properties()->Clone(cloned_properties.get());
return cloned_properties;
} else {
return nullptr;
}
}
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::SendPropertiesChanged(
ViewState* view_state, ::fuchsia::ui::viewsv1::ViewProperties properties) {
FXL_DCHECK(view_state);
FXL_DCHECK(view_state->view_listener());
FXL_VLOG(1) << "SendPropertiesChanged: view_state=" << view_state
<< ", properties=" << properties;
// It's safe to capture the view state because the ViewListener is closed
// before the view state is destroyed so we will only receive the callback
// if the view state is still alive.
view_state->view_listener()->OnPropertiesChanged(
std::move(properties), [this, view_state] {
uint32_t old_flags = view_state->invalidation_flags();
FXL_DCHECK(old_flags & ViewState::INVALIDATION_IN_PROGRESS);
view_state->set_invalidation_flags(
old_flags & ~(ViewState::INVALIDATION_IN_PROGRESS |
ViewState::INVALIDATION_STALLED));
if (old_flags & ViewState::INVALIDATION_STALLED) {
FXL_VLOG(2) << "View recovered from stalled invalidation: view_state="
<< view_state;
InvalidateView(view_state, 0u);
}
});
}
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,
[] {});
}
// SNAPSHOT
void ViewRegistry::TakeSnapshot(
uint64_t view_koid, fit::function<void(::fuchsia::mem::Buffer)> callback) {
auto view_state = static_cast<ViewState*>(view_linker_.GetImport(view_koid));
if (view_koid > 0 && !view_state) {
// TODO(SCN-978): Did not find the view for the view koid, return error.
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));
if (view_state) {
// Snapshot the child.
view_state->top_node().Snapshot(std::move(snapshot_callback));
} else {
// Snapshot the entire composition.
session_.Enqueue(
scenic::NewTakeSnapshotCmdHACK(0, std::move(snapshot_callback)));
}
SchedulePresentSession();
}
// 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