blob: cfeb564e9dd518c325b63ca5e1d9ce591f8a02a8 [file] [log] [blame]
// Copyright 2018 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:typed_data';
import 'package:fidl/fidl.dart';
import 'package:fidl_fuchsia_mem/fidl_async.dart' as mem;
import 'package:fidl_fuchsia_modular/fidl_async.dart' as modular;
import 'package:fidl_fuchsia_ui_views/fidl_async.dart' as views;
import 'package:fidl_fuchsia_ui_viewsv1token/fidl_async.dart' as deprecated;
import 'package:fuchsia_modular/lifecycle.dart';
import 'package:meta/meta.dart';
import 'package:zircon/zircon.dart' as zx;
import '../../entity/entity.dart';
import '../../entity/internal/_entity_impl.dart';
import '../embedded_module.dart';
import '../intent.dart';
import '../intent_handler.dart';
import '../module.dart';
import '../module_state_exception.dart';
import '../ongoing_activity.dart';
import '_intent_handler_impl.dart';
import '_module_context.dart';
import '_ongoing_activity_impl.dart';
/// A concrete implementation of the [Module] interface. This class
/// is not intended to be used directly by authors but instead should
/// be used by the [Module] factory constructor.
class ModuleImpl implements Module {
/// Holds a reference to the already registered intent handler
IntentHandler _intentHandler;
/// The intent handler host which will proxy intents to the registered
/// intent handler
// ignore: unused_field
IntentHandlerImpl _intentHandlerImpl;
/// Returns the [fidl.ModuleContext] for the running module.
/// This variable should not be used directly. Use the
/// [getContext()] method instead
modular.ModuleContext _moduleContext;
/// The default constructor for this instance.
///
/// the [moduleContext] is an optional parameter that
/// can be supplied to override the default module context.
/// This is mainly useful in testing scenarios.
ModuleImpl({
@required IntentHandlerImpl intentHandlerImpl,
Lifecycle lifecycle,
modular.ModuleContext moduleContext,
}) : _moduleContext = moduleContext,
assert(intentHandlerImpl != null) {
(lifecycle ??= Lifecycle()).addTerminateListener(_terminate);
_intentHandlerImpl = intentHandlerImpl
..onHandleIntent = _proxyIntentToIntentHandler;
}
@override
Future<modular.ModuleController> addModuleToStory({
@required String name,
@required modular.Intent intent,
modular.SurfaceRelation surfaceRelation = const modular.SurfaceRelation(
arrangement: modular.SurfaceArrangement.copresent,
dependency: modular.SurfaceDependency.dependent,
emphasis: 0.5,
),
}) async {
if (name == null || name.isEmpty) {
throw ArgumentError.value(
name, 'name', 'addModuleToStory should be called with a valid name');
}
if (intent == null) {
throw ArgumentError.notNull('intent');
}
final moduleControllerProxy = modular.ModuleControllerProxy();
modular.StartModuleStatus status = await _getContext().addModuleToStory(
name, intent, moduleControllerProxy.ctrl.request(), surfaceRelation);
_validateStartModuleStatus(status, name, intent);
return moduleControllerProxy;
}
@override
Future<Entity> createEntity({
@required String type,
@required Uint8List initialData,
}) async {
ArgumentError.checkNotNull(type, 'type');
ArgumentError.checkNotNull(initialData, 'initialData');
if (type.isEmpty) {
throw ArgumentError.value(type, 'type cannot be an empty string');
}
final context = _getContext();
// need to create the proxy and write data immediately so other modules
// can extract values
final proxy = modular.EntityProxy();
final vmo = zx.SizedVmo.fromUint8List(initialData);
final buffer = mem.Buffer(vmo: vmo, size: initialData.length);
final ref = await context.createEntity(type, buffer, proxy.ctrl.request());
// use the ref value to determine if creation was successful
if (ref == null || ref.isEmpty) {
throw Exception('Module.createEntity creation failed because'
' the framework was unable to create the entity.');
}
return EntityImpl(type: type, proxyFactory: () => proxy);
}
@override
Future<EmbeddedModule> embedModule({
@required String name,
@required modular.Intent intent,
}) async {
if (name == null || name.isEmpty) {
throw ArgumentError.value(
name, 'name', 'embedModuleNew should be called with a valid name');
}
if (intent == null) {
throw ArgumentError.notNull('intent');
}
final moduleController = modular.ModuleControllerProxy();
final viewOwner = InterfacePair<deprecated.ViewOwner>();
final status = await _getContext().embedModule(
name, intent, moduleController.ctrl.request(), viewOwner.passRequest());
_validateStartModuleStatus(status, name, intent);
return EmbeddedModule(
moduleController: moduleController,
viewHolderToken: views.ViewHolderToken(
value: zx.EventPair(
viewOwner.passHandle().passChannel().passHandle())));
}
@override
void registerIntentHandler(IntentHandler intentHandler) {
if (_intentHandler != null) {
throw ModuleStateException(
'Intent handler registration failed because a handler is already '
'registered.');
}
_intentHandler = intentHandler;
}
@override
void removeSelfFromStory() {
_getContext().removeSelfFromStory();
}
@override
void requestFocus() {
_getContext().requestFocus();
}
@override
OngoingActivity startOngoingActivity(modular.OngoingActivityType type) {
final proxy = modular.OngoingActivityProxy();
_getContext().startOngoingActivity(type, proxy.ctrl.request());
return OngoingActivityImpl(proxy);
}
modular.ModuleContext _getContext() => _moduleContext ??= getModuleContext();
void _proxyIntentToIntentHandler(Intent intent) {
if (_intentHandler == null) {
throw ModuleStateException(
'Module received an intent but no intent handler was registered to '
'receive it. If you do not intend to handle intents but you still '
'need to use the module functionality register a NoopIntentHandler '
'to explicitly declare that you will not handle the intent.');
}
_intentHandler.handleIntent(intent);
}
// any necessary cleanup should be done in this method.
Future<void> _terminate() async {
_intentHandler = null;
}
void _validateStartModuleStatus(
modular.StartModuleStatus status, String name, modular.Intent intent) {
switch (status) {
case modular.StartModuleStatus.success:
break;
case modular.StartModuleStatus.noModulesFound:
throw ModuleResolutionException(
'no modules found for intent [$intent]');
break;
default:
throw ModuleStateException(
'unknown start module status [$status] for intent [$intent]');
}
}
}
/// When Module resolution fails.
class ModuleResolutionException implements Exception {
/// Information about the failure.
final String message;
/// Create a new [ModuleResolutionException].
ModuleResolutionException(this.message);
}