diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java index 164a529ffa8b..aaac1361eb3d 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java @@ -5,8 +5,8 @@ package io.flutter.plugins.camera; import android.os.Handler; -import android.os.Looper; import android.text.TextUtils; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import io.flutter.embedding.engine.systemchannels.PlatformChannel; import io.flutter.plugin.common.BinaryMessenger; @@ -17,6 +17,7 @@ import java.util.Map; class DartMessenger { + @NonNull private final Handler handler; @Nullable private MethodChannel cameraChannel; @Nullable private MethodChannel deviceChannel; @@ -41,9 +42,10 @@ enum CameraEventType { } } - DartMessenger(BinaryMessenger messenger, long cameraId) { + DartMessenger(BinaryMessenger messenger, long cameraId, @NonNull Handler handler) { cameraChannel = new MethodChannel(messenger, "flutter.io/cameraPlugin/camera" + cameraId); deviceChannel = new MethodChannel(messenger, "flutter.io/cameraPlugin/device"); + this.handler = handler; } void sendDeviceOrientationChangeEvent(PlatformChannel.DeviceOrientation orientation) { @@ -106,14 +108,14 @@ void send(CameraEventType eventType, Map args) { if (cameraChannel == null) { return; } - new Handler(Looper.getMainLooper()) - .post( - new Runnable() { - @Override - public void run() { - cameraChannel.invokeMethod(eventType.method, args); - } - }); + + handler.post( + new Runnable() { + @Override + public void run() { + cameraChannel.invokeMethod(eventType.method, args); + } + }); } void send(DeviceEventType eventType) { @@ -124,13 +126,13 @@ void send(DeviceEventType eventType, Map args) { if (deviceChannel == null) { return; } - new Handler(Looper.getMainLooper()) - .post( - new Runnable() { - @Override - public void run() { - deviceChannel.invokeMethod(eventType.method, args); - } - }); + + handler.post( + new Runnable() { + @Override + public void run() { + deviceChannel.invokeMethod(eventType.method, args); + } + }); } } diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java index 040225ed5ff3..50bca6349217 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java @@ -6,6 +6,8 @@ import android.app.Activity; import android.hardware.camera2.CameraAccessException; +import android.os.Handler; +import android.os.Looper; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import io.flutter.embedding.engine.systemchannels.PlatformChannel; @@ -353,7 +355,9 @@ private void instantiateCamera(MethodCall call, Result result) throws CameraAcce boolean enableAudio = call.argument("enableAudio"); TextureRegistry.SurfaceTextureEntry flutterSurfaceTexture = textureRegistry.createSurfaceTexture(); - DartMessenger dartMessenger = new DartMessenger(messenger, flutterSurfaceTexture.id()); + DartMessenger dartMessenger = + new DartMessenger( + messenger, flutterSurfaceTexture.id(), new Handler(Looper.getMainLooper())); camera = new Camera( activity, diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java index ed6fd56a3c34..25f5df9e9db9 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java @@ -6,7 +6,11 @@ import static junit.framework.TestCase.assertNull; import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import android.os.Handler; import androidx.annotation.NonNull; import io.flutter.embedding.engine.systemchannels.PlatformChannel; import io.flutter.plugin.common.BinaryMessenger; @@ -19,6 +23,8 @@ import java.util.List; import org.junit.Before; import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; public class DartMessengerTest { /** A {@link BinaryMessenger} implementation that does nothing but save its messages. */ @@ -43,20 +49,24 @@ List getMessages() { } } + private Handler mockHandler; private DartMessenger dartMessenger; private FakeBinaryMessenger fakeBinaryMessenger; @Before public void setUp() { + mockHandler = mock(Handler.class); fakeBinaryMessenger = new FakeBinaryMessenger(); - dartMessenger = new DartMessenger(fakeBinaryMessenger, 0); + dartMessenger = new DartMessenger(fakeBinaryMessenger, 0, mockHandler); } @Test public void sendCameraErrorEvent_includesErrorDescriptions() { - dartMessenger.sendCameraErrorEvent("error description"); + doAnswer(createPostHandlerAnswer()).when(mockHandler).post(any(Runnable.class)); + dartMessenger.sendCameraErrorEvent("error description"); List sentMessages = fakeBinaryMessenger.getMessages(); + assertEquals(1, sentMessages.size()); MethodCall call = decodeSentMessage(sentMessages.get(0)); assertEquals("error", call.method); @@ -65,6 +75,7 @@ public void sendCameraErrorEvent_includesErrorDescriptions() { @Test public void sendCameraInitializedEvent_includesPreviewSize() { + doAnswer(createPostHandlerAnswer()).when(mockHandler).post(any(Runnable.class)); dartMessenger.sendCameraInitializedEvent(0, 0, ExposureMode.auto, FocusMode.auto, true, true); List sentMessages = fakeBinaryMessenger.getMessages(); @@ -81,6 +92,7 @@ public void sendCameraInitializedEvent_includesPreviewSize() { @Test public void sendCameraClosingEvent() { + doAnswer(createPostHandlerAnswer()).when(mockHandler).post(any(Runnable.class)); dartMessenger.sendCameraClosingEvent(); List sentMessages = fakeBinaryMessenger.getMessages(); @@ -92,6 +104,7 @@ public void sendCameraClosingEvent() { @Test public void sendDeviceOrientationChangedEvent() { + doAnswer(createPostHandlerAnswer()).when(mockHandler).post(any(Runnable.class)); dartMessenger.sendDeviceOrientationChangeEvent(PlatformChannel.DeviceOrientation.PORTRAIT_UP); List sentMessages = fakeBinaryMessenger.getMessages(); @@ -101,6 +114,19 @@ public void sendDeviceOrientationChangedEvent() { assertEquals(call.argument("orientation"), "portraitUp"); } + private static Answer createPostHandlerAnswer() { + return new Answer() { + @Override + public Boolean answer(InvocationOnMock invocation) throws Throwable { + Runnable runnable = invocation.getArgument(0, Runnable.class); + if (runnable != null) { + runnable.run(); + } + return true; + } + }; + } + private MethodCall decodeSentMessage(ByteBuffer sentMessage) { sentMessage.position(0); diff --git a/script/tool/lib/src/java_test_command.dart b/script/tool/lib/src/java_test_command.dart index 45042c3c7006..4b6a561b9e6d 100644 --- a/script/tool/lib/src/java_test_command.dart +++ b/script/tool/lib/src/java_test_command.dart @@ -32,9 +32,12 @@ class JavaTestCommand extends PluginCommand { final Stream examplesWithTests = getExamples().where( (Directory d) => isFlutterPackage(d, fileSystem) && - fileSystem - .directory(p.join(d.path, 'android', 'app', 'src', 'test')) - .existsSync()); + (fileSystem + .directory(p.join(d.path, 'android', 'app', 'src', 'test')) + .existsSync() || + fileSystem + .directory(p.join(d.path, '..', 'android', 'src', 'test')) + .existsSync())); final List failingPackages = []; final List missingFlutterBuild = []; diff --git a/script/tool/test/java_test_command_test.dart b/script/tool/test/java_test_command_test.dart new file mode 100644 index 000000000000..b036d7ec0c6f --- /dev/null +++ b/script/tool/test/java_test_command_test.dart @@ -0,0 +1,86 @@ +// Copyright 2013 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 'package:args/command_runner.dart'; +import 'package:file/file.dart'; +import 'package:flutter_plugin_tools/src/java_test_command.dart'; +import 'package:path/path.dart' as p; +import 'package:test/test.dart'; + +import 'util.dart'; + +void main() { + group('$JavaTestCommand', () { + CommandRunner runner; + final RecordingProcessRunner processRunner = RecordingProcessRunner(); + + setUp(() { + initializeFakePackages(); + final JavaTestCommand command = JavaTestCommand( + mockPackagesDir, mockFileSystem, + processRunner: processRunner); + + runner = + CommandRunner('java_test_test', 'Test for $JavaTestCommand'); + runner.addCommand(command); + }); + + tearDown(() { + cleanupPackages(); + processRunner.recordedCalls.clear(); + }); + + test('Should run Java tests in Android implementation folder', () async { + final Directory plugin = createFakePlugin( + 'plugin1', + isAndroidPlugin: true, + isFlutter: true, + withSingleExample: true, + withExtraFiles: >[ + ['example/android', 'gradlew'], + ['android/src/test', 'example_test.java'], + ], + ); + + await runner.run(['java-test']); + + expect( + processRunner.recordedCalls, + orderedEquals([ + ProcessCall( + p.join(plugin.path, 'example/android/gradlew'), + ['testDebugUnitTest', '--info'], + p.join(plugin.path, 'example/android'), + ), + ]), + ); + }); + + test('Should run Java tests in example folder', () async { + final Directory plugin = createFakePlugin( + 'plugin1', + isAndroidPlugin: true, + isFlutter: true, + withSingleExample: true, + withExtraFiles: >[ + ['example/android', 'gradlew'], + ['example/android/app/src/test', 'example_test.java'], + ], + ); + + await runner.run(['java-test']); + + expect( + processRunner.recordedCalls, + orderedEquals([ + ProcessCall( + p.join(plugin.path, 'example/android/gradlew'), + ['testDebugUnitTest', '--info'], + p.join(plugin.path, 'example/android'), + ), + ]), + ); + }); + }); +}