flutter/dev/tools/android_driver_extensions/lib/extension.dart
Matan Lurey 89b336109f
Add a virtual-display (VD) platform view test, and refactor tests a bit. (#161349)
Towards https://github.com/flutter/flutter/issues/161261.

Still need to add a HC (Hybrid Composition) variant, but figured I'd do
this incrementally to make it easier to review.
2025-01-10 03:23:40 +00:00

115 lines
3.9 KiB
Dart

// 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<String, Object?>` (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<Result> 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<Object>(command.method, command.arguments);
if (result == null) {
return const _MethodChannelResult(<String, Object?>{});
}
if (result is! Map<Object?, Object?>) {
throw ArgumentError.value(
result,
'result',
'Expected a Map<String, Object?>, 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<Result?> _builtInCall(String method) async {
switch (method) {
case 'rotate_landscape':
await flt.SystemChrome.setPreferredOrientations(const <flt.DeviceOrientation>[
flt.DeviceOrientation.landscapeLeft,
]);
return Result.empty;
case 'rotate_default':
await flt.SystemChrome.setPreferredOrientations(const <flt.DeviceOrientation>[]);
return Result.empty;
default:
return null;
}
}
@override
String get commandKind => 'native_driver';
@override
NativeCommand deserialize(
Map<String, String> 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<String, Object?>? decoded;
if (arguments == null) {
decoded = null;
} else {
final Object? intermediate = json.decode(arguments);
if (intermediate is! Map<String, Object?>) {
throw ArgumentError.value(arguments, 'arguments', 'Expected a Map<String, Object?>');
}
decoded = intermediate;
}
return NativeCommand(method, arguments: decoded);
}
}
final class _MethodChannelResult implements Result {
const _MethodChannelResult(this._json);
final Map<String, Object?> _json;
@override
Map<String, Object?> toJson() => _json;
}