// Copyright 2014 The Flutter 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:convert'; import 'package:flutter/services.dart' as flt; import 'package:flutter_driver/driver_extension.dart'; import 'package:flutter_driver/flutter_driver.dart'; import 'package:flutter_test/flutter_test.dart'; import 'src/common.dart'; /// An extension that forwards [NativeCommand]s to a registered plugin. const CommandExtension nativeDriverCommands = NativeDriverCommandExtension( flt.MethodChannel('native_driver'), ); /// An extension that forwards [NativeCommand]s to a registered plugin. /// /// This extension is used to communicate with a platform plugin, and relays /// [NativeCommand]s to the platform plugin, and resolves the result as a /// [NativeResult], both thin wrappers around a `Map` (JSON /// compatible object). /// /// A singleton default instance of this class is [nativeDriverCommands]. final class NativeDriverCommandExtension implements CommandExtension { /// Creates a new [NativeDriverCommandExtension] with the given [channel]. /// /// Can be used in exceptional cases where a custom [MethodChannel] is needed; /// otherwise, use the singleton [nativeDriverCommands]. const NativeDriverCommandExtension(this._channel); final flt.MethodChannel _channel; @override Future call( Command command, WidgetController prober, CreateFinderFactory finderFactory, CommandHandlerFactory handlerFactory, ) async { if (command is! NativeCommand) { throw ArgumentError.value(command, 'command', 'Expected a NativeCommand'); } if (await _builtInCall(command.method) case final Result result) { return result; } final Object? result = await _channel.invokeMethod(command.method, command.arguments); if (result == null) { return const _MethodChannelResult({}); } if (result is! Map) { throw ArgumentError.value( result, 'result', 'Expected a Map, got ${result.runtimeType}', ); } return _MethodChannelResult(result.cast()); } // While these could have been implemented in native code, they are already // available as engine-bundled platform plugins, so using them directly // reduces the amount of code to be written and maintained. Future _builtInCall(String method) async { switch (method) { case 'rotate_landscape': await flt.SystemChrome.setPreferredOrientations(const [ flt.DeviceOrientation.landscapeLeft, ]); return Result.empty; case 'rotate_default': await flt.SystemChrome.setPreferredOrientations(const []); return Result.empty; default: return null; } } @override String get commandKind => 'native_driver'; @override NativeCommand deserialize( Map params, DeserializeFinderFactory finderFactory, DeserializeCommandFactory commandFactory, ) { final String? method = params['method']; if (method == null) { throw ArgumentError.value(params, 'params', 'Missing method'); } final String? arguments = params['arguments']; final Map? decoded; if (arguments == null) { decoded = null; } else { final Object? intermediate = json.decode(arguments); if (intermediate is! Map) { throw ArgumentError.value(arguments, 'arguments', 'Expected a Map'); } decoded = intermediate; } return NativeCommand(method, arguments: decoded); } } final class _MethodChannelResult implements Result { const _MethodChannelResult(this._json); final Map _json; @override Map toJson() => _json; }