blob: 98d017f5918e76c5efa076d615ab8c644bfb1e22 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
import 'dart:developer' show Timeline;
import 'dart:typed_data';
import 'package:fidl/fidl.dart';
import 'package:fidl_fuchsia_mem/fidl.dart' as fuchsia_mem;
import 'package:fidl_fuchsia_modular/fidl.dart';
import 'package:fidl_fuchsia_ui_gfx/fidl.dart' show ImportToken;
import 'package:fidl_fuchsia_ui_policy/fidl.dart';
import 'package:fidl_fuchsia_ui_views/fidl_async.dart';
import 'package:fuchsia/fuchsia.dart' show exit;
import 'package:lib.app.dart/logging.dart';
import 'package:lib.story_shell/common.dart';
import 'package:lib.widgets/utils_deprecated.dart';
import 'package:zircon/zircon.dart';
import 'models/surface/surface_graph.dart';
import 'models/surface/surface_properties.dart';
/// An implementation of the [StoryShell] interface.
class StoryShellImpl implements StoryShell, StoryVisualStateWatcher, Lifecycle {
final StoryShellBinding _storyShellBinding = new StoryShellBinding();
final LifecycleBinding _lifecycleBinding = new LifecycleBinding();
final StoryShellContextProxy _storyShellContext =
new StoryShellContextProxy();
final LinkProxy _linkProxy = new LinkProxy();
final PointerEventsListener _pointerEventsListener =
new PointerEventsListener();
final KeyListener keyListener;
final StoryVisualStateWatcherBinding _visualStateWatcherBinding =
new StoryVisualStateWatcherBinding();
final SurfaceGraph surfaceGraph;
final String _storyShellLinkName = 'story_shell_state';
StoryVisualState _visualState;
String _lastFocusedSurfaceId;
StoryShellImpl({this.surfaceGraph, this.keyListener});
/// StoryShell
@override
void initialize(InterfaceHandle<StoryShellContext> contextHandle) async {
_storyShellContext.ctrl.bind(contextHandle);
_storyShellContext
..watchVisualState(_visualStateWatcherBinding.wrap(this))
..getLink(_linkProxy.ctrl.request());
await reloadStoryState().then(onLinkContentsFetched);
surfaceGraph.addListener(() {
String surfaceId = surfaceGraph.focused?.node?.value;
if (surfaceId != null && surfaceId != _lastFocusedSurfaceId) {
_storyShellBinding.events.onSurfaceFocused(surfaceId);
_lastFocusedSurfaceId = surfaceId;
}
});
}
/// Bind an [InterfaceRequest] for a [StoryShell] interface to this object.
void bindStoryShell(InterfaceRequest<StoryShell> request) {
_storyShellBinding.bind(this, request);
}
/// Bind an [InterfaceRequest] for a [Lifecycle] interface to this object.
void bindLifecycle(InterfaceRequest<Lifecycle> request) {
_lifecycleBinding.bind(this, request);
}
/// Introduce a new Surface and corresponding [ImportToken] to the current
/// Story.
///
/// The Surface may have a relationship with its parent surface.
@override
void addSurface(
ViewConnection viewConnection,
SurfaceInfo surfaceInfo,
) {
trace(
'connecting surface ${viewConnection.surfaceId} with parent ${surfaceInfo.parentId}');
log.fine(
'Connecting surface ${viewConnection.surfaceId} with parent ${surfaceInfo.parentId}');
/// ignore: cascade_invocations
log.fine('Were passed manifest: $surfaceInfo.moduleManifest');
surfaceGraph
..addSurface(
viewConnection.surfaceId,
new SurfaceProperties(source: surfaceInfo.moduleSource),
surfaceInfo.parentId,
surfaceInfo.surfaceRelation ?? const SurfaceRelation(),
surfaceInfo.moduleManifest != null
? surfaceInfo.moduleManifest.compositionPattern
: '',
surfaceInfo.moduleManifest != null
? surfaceInfo.moduleManifest.placeholderColor
: '',
)
..connectViewFromViewHolderToken(
viewConnection.surfaceId,
ViewHolderToken(
value:
EventPair(viewConnection.owner.passChannel().passHandle())));
}
/// Focus the surface with this id
@override
void focusSurface(String surfaceId) {
Timeline.instantSync('focusing view',
arguments: {'surfaceId': '$surfaceId'});
surfaceGraph.focusSurface(surfaceId);
persistStoryState();
}
/// Defocus the surface with this id
@override
void defocusSurface(String surfaceId, void callback()) {
Timeline.instantSync('defocusing view',
arguments: {'surfaceId': '$surfaceId'});
surfaceGraph.dismissSurface(surfaceId);
// TODO(alangardner, djmurphy): Make Mondrian not crash if the process
// associated with surfaceId is closed after callback returns.
callback();
persistStoryState();
}
/// Add a container node to the graph, with associated layout as a property,
/// and optionally specify a parent and a relationship to the parent
@override
void addContainer(
String containerName,
String parentId,
SurfaceRelation relation,
List<ContainerLayout> layouts,
List<ContainerRelationEntry> relationships,
List<ContainerView> views) {
// Add a root node for the container
Timeline.instantSync('adding container', arguments: {
'containerName': '$containerName',
'parentId': '$parentId'
});
surfaceGraph.addContainer(
containerName,
new SurfaceProperties(),
parentId,
relation,
layouts,
);
Map<String, ContainerRelationEntry> nodeMap =
<String, ContainerRelationEntry>{};
Map<String, List<String>> parentChildrenMap = <String, List<String>>{};
Map<String, ViewHolderToken> viewMap = <String, ViewHolderToken>{};
for (ContainerView view in views) {
viewMap[view.nodeName] = ViewHolderToken(
value: EventPair(view.owner.passChannel().passHandle()));
}
for (ContainerRelationEntry relatedNode in relationships) {
nodeMap[relatedNode.nodeName] = relatedNode;
parentChildrenMap
.putIfAbsent(relatedNode.parentNodeName, () => <String>[])
.add(relatedNode.nodeName);
}
List<String> nodeQueue =
views.map((ContainerView v) => v.nodeName).toList();
List<String> addedParents = <String>[containerName];
int i = 0;
while (nodeQueue.isNotEmpty) {
String nodeId = nodeQueue.elementAt(i);
String parentId = nodeMap[nodeId].parentNodeName;
if (addedParents.contains(parentId)) {
for (nodeId in parentChildrenMap[parentId]) {
SurfaceProperties prop = new SurfaceProperties()
..containerMembership = <String>[containerName]
..containerLabel = nodeId;
surfaceGraph.addSurface(
nodeId, prop, parentId, nodeMap[nodeId].relationship, null, '');
addedParents.add(nodeId);
surfaceGraph.connectViewFromViewHolderToken(nodeId, viewMap[nodeId]);
nodeQueue.remove(nodeId);
surfaceGraph.focusSurface(nodeId);
}
i = 0;
} else {
i++;
if (i > nodeQueue.length) {
log.warning('''Error iterating through container children.
All nodes iterated without finding all parents specified in
Container Relations''');
return;
}
}
}
}
@override
void removeSurface(String surfaceId) {
surfaceGraph.removeSurface(surfaceId);
}
@override
void reconnectView(ViewConnection viewConnection) {
// TODO (jphsiao): implement
}
@override
void updateSurface(
ViewConnection viewConnection,
SurfaceInfo surfaceInfo,
) {
// TODO (jphsiao): implement
}
/// Terminate the StoryShell.
@override
void terminate() => exit(0);
@override
void onVisualStateChange(StoryVisualState visualState) {
if (_visualState == visualState) {
return;
}
_visualState = visualState;
_pointerEventsListener.stop();
if (visualState == StoryVisualState.maximized) {
PresentationProxy presentationProxy = new PresentationProxy();
_storyShellContext.getPresentation(presentationProxy.ctrl.request());
// TODO(nzheng): switch story shell to use async fidl
_pointerEventsListener.listen(presentationProxy);
keyListener?.listen(presentationProxy);
presentationProxy.ctrl.close();
} else {
keyListener.stop();
}
}
Future<fuchsia_mem.Buffer> reloadStoryState() {
Completer<fuchsia_mem.Buffer> completer = Completer<fuchsia_mem.Buffer>();
_linkProxy.get([_storyShellLinkName], completer.complete);
return completer.future;
}
void onLinkContentsFetched(fuchsia_mem.Buffer buffer) {
var dataVmo = new SizedVmo(buffer.vmo.handle, buffer.size);
var data = dataVmo.read(buffer.size);
dataVmo.close();
dynamic decoded = jsonDecode(utf8.decode(data.bytesAsUint8List()));
if (decoded is Map<String, dynamic>) {
surfaceGraph.reload(decoded.cast<String, dynamic>());
}
}
void persistStoryState() async {
String encoded = json.encode(surfaceGraph);
var jsonList = Uint8List.fromList(utf8.encode(encoded));
var data = fuchsia_mem.Buffer(
vmo: new SizedVmo.fromUint8List(jsonList),
size: jsonList.length,
);
_linkProxy.set([_storyShellLinkName], data);
}
}