[ermine] Close view if underlying component exits
This change responds to the session's ViewController closing its
bound handle to dismiss the associated story and its view.
Test: Added a unittest for close on ViewController. All tests pass.
Bug: 47792
Change-Id: Ic0d4ee1369b063850284c13dcb49042cc470d215
Reviewed-on: https://fuchsia-review.googlesource.com/c/experiences/+/427246
Reviewed-by: Darren Chan <chandarren@google.com>
Reviewed-by: Charles Whitten <cwhitten@google.com>
Testability-Review: Sanjay Chouksey <sanjayc@google.com>
Commit-Queue: Sanjay Chouksey <sanjayc@google.com>
diff --git a/session_shells/ermine/session/src/element_repository/event_handler.rs b/session_shells/ermine/session/src/element_repository/event_handler.rs
index 46de872..7c1b021 100644
--- a/session_shells/ermine/session/src/element_repository/event_handler.rs
+++ b/session_shells/ermine/session/src/element_repository/event_handler.rs
@@ -13,7 +13,6 @@
},
fidl_fuchsia_ui_app::ViewProviderMarker,
fuchsia_async as fasync, fuchsia_scenic as scenic,
- fuchsia_syslog::fx_log_info,
fuchsia_zircon as zx,
fuchsia_zircon::AsHandleRef,
futures::{select, stream::StreamExt, FutureExt, TryStreamExt},
@@ -125,7 +124,14 @@
// that there was a crash. The current contract is that if the
// view controller binding closes without a dismiss then the
// presenter should treat this as a crash and respond accordingly.
- fx_log_info!("An element closed unexpectedly: {:?}", element);
+ if let Some(proxy) = view_controller_proxy {
+ // We want to allow the presenter the ability to dismiss
+ // the view so we tell it to dismiss and then wait for
+ // the view controller stream to close.
+ let _ = proxy.dismiss();
+ //TODO(47925) introdue a timeout here
+ wait_for_view_controller_close(proxy).await;
+ }
},
_ = wait_for_optional_view_controller_close(view_controller_proxy.clone()).fuse() => {
// signals that the presenter would like to close the element.
diff --git a/session_shells/ermine/shell/lib/src/models/app_model.dart b/session_shells/ermine/shell/lib/src/models/app_model.dart
index cac9ae5..a447a40 100644
--- a/session_shells/ermine/shell/lib/src/models/app_model.dart
+++ b/session_shells/ermine/shell/lib/src/models/app_model.dart
@@ -132,7 +132,10 @@
@visibleForTesting
void advertise() {
// Expose the presenter service to the environment.
- _presenterService = PresenterService(clustersModel.presentStory);
+ _presenterService = PresenterService(
+ onPresent: clustersModel.presentStory,
+ onDismiss: clustersModel.dismissStory,
+ );
_startupContext.outgoing
.addPublicService(_presenterService.bind, PresenterService.serviceName);
}
diff --git a/session_shells/ermine/shell/lib/src/models/cluster_model.dart b/session_shells/ermine/shell/lib/src/models/cluster_model.dart
index d21bf34..424366d 100644
--- a/session_shells/ermine/shell/lib/src/models/cluster_model.dart
+++ b/session_shells/ermine/shell/lib/src/models/cluster_model.dart
@@ -7,6 +7,7 @@
import 'package:flutter/rendering.dart';
import 'package:fidl_fuchsia_ui_views/fidl_async.dart';
+import 'package:fuchsia_scenic_flutter/child_view_connection.dart';
import 'package:tiler/tiler.dart' show TilerModel, TileModel;
import '../utils/presenter.dart';
@@ -79,7 +80,7 @@
@override
void presentStory(
- ViewHolderToken token,
+ ChildViewConnection connection,
ViewRef viewRef,
ViewControllerImpl viewController,
String id,
@@ -97,7 +98,16 @@
_addErmineStory(story);
}
- story.presentView(token, viewRef, viewController);
+ story.presentView(connection, viewRef, viewController);
+ }
+
+ @override
+ void dismissStory(ViewControllerImpl viewController) {
+ // Find the story with the supplied viewController.
+ final story =
+ stories.where((story) => story.viewController == viewController).first;
+ assert(story != null);
+ story.delete();
}
/// Creates and adds a [Story] to the current cluster given it's [StoryInfo],
diff --git a/session_shells/ermine/shell/lib/src/models/ermine_shell.dart b/session_shells/ermine/shell/lib/src/models/ermine_shell.dart
index aad7c6f..63ee39d 100644
--- a/session_shells/ermine/shell/lib/src/models/ermine_shell.dart
+++ b/session_shells/ermine/shell/lib/src/models/ermine_shell.dart
@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'package:fidl_fuchsia_ui_views/fidl_async.dart';
+import 'package:fuchsia_scenic_flutter/child_view_connection.dart';
import '../utils/presenter.dart';
import '../utils/suggestion.dart';
@@ -29,8 +30,8 @@
/// it has been launched. The story may have been proposed by ermine or
/// an external source.
void presentStory(
- /// The view holder token used to connect the view to the process
- ViewHolderToken token,
+ /// The [ChildViewConnection] used to connect the view to the process.
+ ChildViewConnection connection,
/// The [ViewRef] of the view used as a handle for focusing.
ViewRef viewRef,
@@ -43,4 +44,7 @@
/// story was launched from ermine.
String id,
);
+
+ /// Called when a story is dismissed by the session instead of the user.
+ void dismissStory(ViewControllerImpl viewController);
}
diff --git a/session_shells/ermine/shell/lib/src/models/ermine_story.dart b/session_shells/ermine/shell/lib/src/models/ermine_story.dart
index 3a4a634..3830c78 100644
--- a/session_shells/ermine/shell/lib/src/models/ermine_story.dart
+++ b/session_shells/ermine/shell/lib/src/models/ermine_story.dart
@@ -25,7 +25,6 @@
// An optional view controller which allows the story to communicate with the
// process.
- @visibleForTesting
ViewControllerImpl viewController;
ViewRef viewRef;
@@ -134,12 +133,8 @@
}
void presentView(
- ViewHolderToken viewHolderToken, ViewRef viewRef, ViewControllerImpl vc) {
- childViewConnectionNotifier.value = ChildViewConnection(
- viewHolderToken,
- onAvailable: (_) {},
- onUnavailable: (_) {},
- );
+ ChildViewConnection connection, ViewRef viewRef, ViewControllerImpl vc) {
+ childViewConnectionNotifier.value = connection;
this.viewRef = viewRef;
viewController = vc;
viewController?.didPresent();
diff --git a/session_shells/ermine/shell/lib/src/utils/presenter.dart b/session_shells/ermine/shell/lib/src/utils/presenter.dart
index c948582..f388ad0 100644
--- a/session_shells/ermine/shell/lib/src/utils/presenter.dart
+++ b/session_shells/ermine/shell/lib/src/utils/presenter.dart
@@ -7,7 +7,7 @@
import 'package:fidl_fuchsia_session/fidl_async.dart' as fidl;
import 'package:fidl_fuchsia_ui_views/fidl_async.dart';
import 'package:fidl/fidl.dart';
-import 'package:fuchsia_logger/logger.dart';
+import 'package:fuchsia_scenic_flutter/child_view_connection.dart';
import 'suggestion.dart';
@@ -17,16 +17,24 @@
/// requesting source did not provide a channel. The methods will still be safe
/// to call even if it is not bound.
typedef PresentViewCallback = void Function(
- ViewHolderToken, ViewRef, ViewControllerImpl, String);
+ ChildViewConnection, ViewRef, ViewControllerImpl, String);
+
+/// A callback which is invoked when the element is dismissed by the session.
+///
+/// This callback is the result of the session getting notified of a component
+/// exiting by itself or a crash. It is NOT due to the user closing it from
+/// the shell.
+typedef DismissViewCallback = void Function(ViewControllerImpl);
/// A service which implements the fuchsia.session.GraphicalPresenter protocol.
class PresenterService extends fidl.GraphicalPresenter {
static const String serviceName = fidl.GraphicalPresenter.$serviceName;
final List<fidl.GraphicalPresenterBinding> _bindings = [];
- final PresentViewCallback _onPresent;
+ final PresentViewCallback onPresent;
+ final DismissViewCallback onDismiss;
- PresenterService(this._onPresent);
+ PresenterService({this.onPresent, this.onDismiss});
/// Binds the request to this model.
void bind(InterfaceRequest<fidl.GraphicalPresenter> request) {
@@ -42,7 +50,8 @@
@override
Future<void> presentView(fidl.ViewSpec viewSpec,
InterfaceRequest<fidl.ViewController> viewControllerRequest) async {
- final viewController = ViewControllerImpl()..bind(viewControllerRequest);
+ final viewController = ViewControllerImpl(onDismiss)
+ ..bind(viewControllerRequest);
// Check to see if we have an id that we included in the annotation.
final idAnnotation = viewSpec.annotations?.customAnnotations
@@ -50,8 +59,13 @@
final viewHolderToken = viewSpec.viewHolderToken;
if (viewHolderToken != null) {
- _onPresent(
+ final connection = ChildViewConnection(
viewHolderToken,
+ onAvailable: (_) {},
+ onUnavailable: (_) {},
+ );
+ onPresent(
+ connection,
viewSpec.viewRef,
viewController,
idAnnotation?.value?.text ?? '',
@@ -66,14 +80,13 @@
final _binding = fidl.ViewControllerBinding();
final StreamController<void> _onPresentedStreamController =
StreamController.broadcast();
+ DismissViewCallback onDismiss;
+
+ ViewControllerImpl(this.onDismiss);
void bind(InterfaceRequest<fidl.ViewController> interfaceRequest) {
if (interfaceRequest != null && interfaceRequest.channel != null) {
_binding.bind(this, interfaceRequest);
- _binding.whenClosed.then((_) {
- //TODO(47793) Need to watch our binding for unexpected closures and notify the user
- log.info('binding closed unexpectedly');
- });
}
}
@@ -91,9 +104,7 @@
@override
Future<void> dismiss() async {
- // TODO(47792): A call to dismiss indicates that the view should go away we need to
- // allow the user of this class to asynchronously dismiss the view before closing
- // the channel.
+ onDismiss?.call(this);
close();
}
diff --git a/session_shells/ermine/shell/meta/ermine.cmx b/session_shells/ermine/shell/meta/ermine.cmx
index de9f7c8..c1224c6 100644
--- a/session_shells/ermine/shell/meta/ermine.cmx
+++ b/session_shells/ermine/shell/meta/ermine.cmx
@@ -26,11 +26,12 @@
"fuchsia.settings.Intl",
"fuchsia.sys.Environment",
"fuchsia.sys.Launcher",
- "fuchsia.ui.focus.FocusChainListenerRegistry",
"fuchsia.ui.brightness.Control",
+ "fuchsia.ui.focus.FocusChainListenerRegistry",
"fuchsia.ui.input.ImeService",
- "fuchsia.ui.policy.Presenter",
+ "fuchsia.ui.input.InputDeviceRegistry",
"fuchsia.ui.policy.Presentation",
+ "fuchsia.ui.policy.Presenter",
"fuchsia.ui.scenic.Scenic",
"fuchsia.ui.shortcut.Registry"
]
diff --git a/session_shells/ermine/shell/test/app_model_test.dart b/session_shells/ermine/shell/test/app_model_test.dart
index 3a1bddb..36342e3 100644
--- a/session_shells/ermine/shell/test/app_model_test.dart
+++ b/session_shells/ermine/shell/test/app_model_test.dart
@@ -216,6 +216,7 @@
test('Should update peekNotifier when fullscreen', () async {
final story = MockErmineStory();
when(clustersModel.fullscreenStory).thenReturn(story);
+ when(clustersModel.hasStories).thenReturn(true);
expect(appModel.isFullscreen, true);
diff --git a/session_shells/ermine/shell/test/clusters_model_test.dart b/session_shells/ermine/shell/test/clusters_model_test.dart
index eee312b..d1261f6 100644
--- a/session_shells/ermine/shell/test/clusters_model_test.dart
+++ b/session_shells/ermine/shell/test/clusters_model_test.dart
@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'package:fidl_fuchsia_ui_views/fidl_async.dart';
+import 'package:fuchsia_scenic_flutter/child_view_connection.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
import 'package:zircon/zircon.dart';
@@ -32,9 +33,9 @@
when(eventPair.isValid).thenReturn(false);
clustersModel.presentStory(
- ViewHolderToken(value: eventPair),
+ MockChildViewConnection(),
ViewRef(reference: eventPair),
- ViewControllerImpl(),
+ ViewControllerImpl((_) {}),
'id',
);
@@ -45,6 +46,25 @@
expect(clustersModel.getStory(id), isNotNull);
});
+ test('Dismissed stories should be deleted', () {
+ final eventPair = MockEventPair();
+ when(eventPair.isValid).thenReturn(false);
+
+ final viewController = ViewControllerImpl((_) {});
+ clustersModel.presentStory(
+ MockChildViewConnection(),
+ ViewRef(reference: eventPair),
+ viewController,
+ 'id',
+ );
+
+ final story = clustersModel.focusedStory;
+ expect(story.viewController, viewController);
+
+ clustersModel.dismissStory(viewController);
+ expect(clustersModel.hasStories, false);
+ });
+
test('Story maximize/minimize should toggle fullscreen', () {
// Create a story.
final suggestion = Suggestion(id: 'id', url: 'url', title: 'title');
@@ -112,4 +132,6 @@
});
}
+class MockChildViewConnection extends Mock implements ChildViewConnection {}
+
class MockEventPair extends Mock implements EventPair {}