diff --git a/packages/camera/CHANGELOG.md b/packages/camera/CHANGELOG.md index 2fe0e44fa6c2..14d2289d99b1 100644 --- a/packages/camera/CHANGELOG.md +++ b/packages/camera/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.5.6 + +* Add support for the v2 Android embedding. This shouldn't affect existing + functionality. + ## 0.5.5+1 * Fix event type check diff --git a/packages/camera/android/build.gradle b/packages/camera/android/build.gradle index ab2fc8fd89f0..ebd355385c1c 100644 --- a/packages/camera/android/build.gradle +++ b/packages/camera/android/build.gradle @@ -60,3 +60,28 @@ android { dependencies { testImplementation 'junit:junit:4.12' } + +// TODO(mklim): Remove this hack once androidx.lifecycle is included on stable. https://github.com/flutter/flutter/issues/42348 +afterEvaluate { + def containsEmbeddingDependencies = false + for (def configuration : configurations.all) { + for (def dependency : configuration.dependencies) { + if (dependency.group == 'io.flutter' && + dependency.name.startsWith('flutter_embedding') && + dependency.isTransitive()) + { + containsEmbeddingDependencies = true + break + } + } + } + if (!containsEmbeddingDependencies) { + android { + dependencies { + def lifecycle_version = "2.1.0" + api "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" + api "androidx.lifecycle:lifecycle-runtime:$lifecycle_version" + } + } + } +} diff --git a/packages/camera/android/gradle.properties b/packages/camera/android/gradle.properties index 8bd86f680510..94adc3a3f97a 100644 --- a/packages/camera/android/gradle.properties +++ b/packages/camera/android/gradle.properties @@ -1 +1,3 @@ org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPermissions.java b/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPermissions.java index e45fb1e5a594..3c86ce0d9816 100644 --- a/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPermissions.java +++ b/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPermissions.java @@ -7,20 +7,30 @@ import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import io.flutter.plugin.common.PluginRegistry; -import io.flutter.plugin.common.PluginRegistry.Registrar; +import io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener; + +final class CameraPermissions { + interface PermissionsRegistry { + void addListener(RequestPermissionsResultListener handler); + } + + interface ResultCallback { + void onResult(String errorCode, String errorDescription); + } -public class CameraPermissions { private static final int CAMERA_REQUEST_ID = 9796; private boolean ongoing = false; - public void requestPermissions( - Registrar registrar, boolean enableAudio, ResultCallback callback) { + void requestPermissions( + Activity activity, + PermissionsRegistry permissionsRegistry, + boolean enableAudio, + ResultCallback callback) { if (ongoing) { callback.onResult("cameraPermission", "Camera permission request ongoing"); } - Activity activity = registrar.activity(); if (!hasCameraPermission(activity) || (enableAudio && !hasAudioPermission(activity))) { - registrar.addRequestPermissionsResultListener( + permissionsRegistry.addListener( new CameraRequestPermissionsListener( (String errorCode, String errorDescription) -> { ongoing = false; @@ -51,6 +61,7 @@ private boolean hasAudioPermission(Activity activity) { private static class CameraRequestPermissionsListener implements PluginRegistry.RequestPermissionsResultListener { + final ResultCallback callback; private CameraRequestPermissionsListener(ResultCallback callback) { @@ -73,8 +84,4 @@ public boolean onRequestPermissionsResult(int id, String[] permissions, int[] gr return false; } } - - interface ResultCallback { - void onResult(String errorCode, String errorDescription); - } } diff --git a/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java b/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java index b504f039e326..9bd34e17aa02 100644 --- a/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java +++ b/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java @@ -4,172 +4,108 @@ package io.flutter.plugins.camera; -import android.hardware.camera2.CameraAccessException; +import android.app.Activity; import android.os.Build; import androidx.annotation.NonNull; -import io.flutter.plugin.common.EventChannel; -import io.flutter.plugin.common.MethodCall; -import io.flutter.plugin.common.MethodChannel; -import io.flutter.plugin.common.MethodChannel.MethodCallHandler; -import io.flutter.plugin.common.MethodChannel.Result; +import androidx.annotation.Nullable; +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.embedding.engine.plugins.activity.ActivityAware; +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; +import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.PluginRegistry.Registrar; -import io.flutter.view.FlutterView; +import io.flutter.plugins.camera.CameraPermissions.PermissionsRegistry; import io.flutter.view.TextureRegistry; -public class CameraPlugin implements MethodCallHandler { +/** + * Platform implementation of the camera_plugin. + * + *

Instantiate this in an add to app scenario to gracefully handle activity and context changes. + * See {@code io.flutter.plugins.camera.MainActivity} for an example. + * + *

Call {@link #registerWith(Registrar)} to register an implementation of this that uses the + * stable {@code io.flutter.plugin.common} package. + */ +public final class CameraPlugin implements FlutterPlugin, ActivityAware { - private final CameraPermissions cameraPermissions = new CameraPermissions(); - private final FlutterView view; - private final Registrar registrar; - private final EventChannel imageStreamChannel; - private Camera camera; + private static final String TAG = "CameraPlugin"; + private @Nullable FlutterPluginBinding flutterPluginBinding; + private @Nullable MethodCallHandlerImpl methodCallHandler; - private CameraPlugin(Registrar registrar) { - this.registrar = registrar; - this.view = registrar.view(); - this.imageStreamChannel = - new EventChannel(registrar.messenger(), "plugins.flutter.io/camera/imageStream"); - } + /** + * Initialize this within the {@code #configureFlutterEngine} of a Flutter activity or fragment. + * + *

See {@code io.flutter.plugins.camera.MainActivity} for an example. + */ + public CameraPlugin() {} + /** + * Registers a plugin implementation that uses the stable {@code io.flutter.plugin.common} + * package. + * + *

Calling this automatically initializes the plugin. However plugins initialized this way + * won't react to changes in activity or context, unlike {@link CameraPlugin}. + */ public static void registerWith(Registrar registrar) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - // When a background flutter view tries to register the plugin, the registrar has no activity. - // We stop the registration process as this plugin is foreground only. Also, if the sdk is - // less than 21 (min sdk for Camera2) we don't register the plugin. - return; - } + CameraPlugin plugin = new CameraPlugin(); + plugin.maybeStartListening( + registrar.activity(), + registrar.messenger(), + registrar::addRequestPermissionsResultListener, + registrar.view()); + } - final MethodChannel channel = - new MethodChannel(registrar.messenger(), "plugins.flutter.io/camera"); + @Override + public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { + this.flutterPluginBinding = binding; + } - channel.setMethodCallHandler(new CameraPlugin(registrar)); + @Override + public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { + this.flutterPluginBinding = null; } - private void instantiateCamera(MethodCall call, Result result) throws CameraAccessException { - String cameraName = call.argument("cameraName"); - String resolutionPreset = call.argument("resolutionPreset"); - boolean enableAudio = call.argument("enableAudio"); - TextureRegistry.SurfaceTextureEntry flutterSurfaceTexture = view.createSurfaceTexture(); - DartMessenger dartMessenger = - new DartMessenger(registrar.messenger(), flutterSurfaceTexture.id()); - camera = - new Camera( - registrar.activity(), - flutterSurfaceTexture, - dartMessenger, - cameraName, - resolutionPreset, - enableAudio); + @Override + public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { + maybeStartListening( + binding.getActivity(), + flutterPluginBinding.getFlutterEngine().getDartExecutor(), + binding::addRequestPermissionsResultListener, + flutterPluginBinding.getFlutterEngine().getRenderer()); + } - camera.open(result); + @Override + public void onDetachedFromActivity() { + if (methodCallHandler == null) { + // Could be on too low of an SDK to have started listening originally. + return; + } + + methodCallHandler.stopListening(); + methodCallHandler = null; } @Override - public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) { - switch (call.method) { - case "availableCameras": - try { - result.success(CameraUtils.getAvailableCameras(registrar.activity())); - } catch (Exception e) { - handleException(e, result); - } - break; - case "initialize": - { - if (camera != null) { - camera.close(); - } - cameraPermissions.requestPermissions( - registrar, - call.argument("enableAudio"), - (String errCode, String errDesc) -> { - if (errCode == null) { - try { - instantiateCamera(call, result); - } catch (Exception e) { - handleException(e, result); - } - } else { - result.error(errCode, errDesc, null); - } - }); + public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { + onAttachedToActivity(binding); + } - break; - } - case "takePicture": - { - camera.takePicture(call.argument("path"), result); - break; - } - case "prepareForVideoRecording": - { - // This optimization is not required for Android. - result.success(null); - break; - } - case "startVideoRecording": - { - camera.startVideoRecording(call.argument("filePath"), result); - break; - } - case "stopVideoRecording": - { - camera.stopVideoRecording(result); - break; - } - case "pauseVideoRecording": - { - camera.pauseVideoRecording(result); - break; - } - case "resumeVideoRecording": - { - camera.resumeVideoRecording(result); - break; - } - case "startImageStream": - { - try { - camera.startPreviewWithImageStream(imageStreamChannel); - result.success(null); - } catch (Exception e) { - handleException(e, result); - } - break; - } - case "stopImageStream": - { - try { - camera.startPreview(); - result.success(null); - } catch (Exception e) { - handleException(e, result); - } - break; - } - case "dispose": - { - if (camera != null) { - camera.dispose(); - } - result.success(null); - break; - } - default: - result.notImplemented(); - break; - } + @Override + public void onDetachedFromActivityForConfigChanges() { + onDetachedFromActivity(); } - // We move catching CameraAccessException out of onMethodCall because it causes a crash - // on plugin registration for sdks incompatible with Camera2 (< 21). We want this plugin to - // to be able to compile with <21 sdks for apps that want the camera and support earlier version. - @SuppressWarnings("ConstantConditions") - private void handleException(Exception exception, Result result) { - if (exception instanceof CameraAccessException) { - result.error("CameraAccess", exception.getMessage(), null); + private void maybeStartListening( + Activity activity, + BinaryMessenger messenger, + PermissionsRegistry permissionsRegistry, + TextureRegistry textureRegistry) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + // If the sdk is less than 21 (min sdk for Camera2) we don't register the plugin. + return; } - throw (RuntimeException) exception; + methodCallHandler = + new MethodCallHandlerImpl( + activity, messenger, new CameraPermissions(), permissionsRegistry, textureRegistry); } } diff --git a/packages/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java b/packages/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java new file mode 100644 index 000000000000..cb58d19a9a02 --- /dev/null +++ b/packages/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java @@ -0,0 +1,174 @@ +package io.flutter.plugins.camera; + +import android.app.Activity; +import android.hardware.camera2.CameraAccessException; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.EventChannel; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.MethodChannel.Result; +import io.flutter.plugins.camera.CameraPermissions.PermissionsRegistry; +import io.flutter.view.TextureRegistry; + +final class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler { + private final Activity activity; + private final BinaryMessenger messenger; + private final CameraPermissions cameraPermissions; + private final PermissionsRegistry permissionsRegistry; + private final TextureRegistry textureRegistry; + private final MethodChannel methodChannel; + private final EventChannel imageStreamChannel; + private @Nullable Camera camera; + + MethodCallHandlerImpl( + Activity activity, + BinaryMessenger messenger, + CameraPermissions cameraPermissions, + PermissionsRegistry permissionsAdder, + TextureRegistry textureRegistry) { + this.activity = activity; + this.messenger = messenger; + this.cameraPermissions = cameraPermissions; + this.permissionsRegistry = permissionsAdder; + this.textureRegistry = textureRegistry; + + methodChannel = new MethodChannel(messenger, "plugins.flutter.io/camera"); + imageStreamChannel = new EventChannel(messenger, "plugins.flutter.io/camera/imageStream"); + methodChannel.setMethodCallHandler(this); + } + + @Override + public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) { + switch (call.method) { + case "availableCameras": + try { + result.success(CameraUtils.getAvailableCameras(activity)); + } catch (Exception e) { + handleException(e, result); + } + break; + case "initialize": + { + if (camera != null) { + camera.close(); + } + cameraPermissions.requestPermissions( + activity, + permissionsRegistry, + call.argument("enableAudio"), + (String errCode, String errDesc) -> { + if (errCode == null) { + try { + instantiateCamera(call, result); + } catch (Exception e) { + handleException(e, result); + } + } else { + result.error(errCode, errDesc, null); + } + }); + + break; + } + case "takePicture": + { + camera.takePicture(call.argument("path"), result); + break; + } + case "prepareForVideoRecording": + { + // This optimization is not required for Android. + result.success(null); + break; + } + case "startVideoRecording": + { + camera.startVideoRecording(call.argument("filePath"), result); + break; + } + case "stopVideoRecording": + { + camera.stopVideoRecording(result); + break; + } + case "pauseVideoRecording": + { + camera.pauseVideoRecording(result); + break; + } + case "resumeVideoRecording": + { + camera.resumeVideoRecording(result); + break; + } + case "startImageStream": + { + try { + camera.startPreviewWithImageStream(imageStreamChannel); + result.success(null); + } catch (Exception e) { + handleException(e, result); + } + break; + } + case "stopImageStream": + { + try { + camera.startPreview(); + result.success(null); + } catch (Exception e) { + handleException(e, result); + } + break; + } + case "dispose": + { + if (camera != null) { + camera.dispose(); + } + result.success(null); + break; + } + default: + result.notImplemented(); + break; + } + } + + void stopListening() { + methodChannel.setMethodCallHandler(null); + } + + private void instantiateCamera(MethodCall call, Result result) throws CameraAccessException { + String cameraName = call.argument("cameraName"); + String resolutionPreset = call.argument("resolutionPreset"); + boolean enableAudio = call.argument("enableAudio"); + TextureRegistry.SurfaceTextureEntry flutterSurfaceTexture = + textureRegistry.createSurfaceTexture(); + DartMessenger dartMessenger = new DartMessenger(messenger, flutterSurfaceTexture.id()); + camera = + new Camera( + activity, + flutterSurfaceTexture, + dartMessenger, + cameraName, + resolutionPreset, + enableAudio); + + camera.open(result); + } + + // We move catching CameraAccessException out of onMethodCall because it causes a crash + // on plugin registration for sdks incompatible with Camera2 (< 21). We want this plugin to + // to be able to compile with <21 sdks for apps that want the camera and support earlier version. + @SuppressWarnings("ConstantConditions") + private void handleException(Exception exception, Result result) { + if (exception instanceof CameraAccessException) { + result.error("CameraAccess", exception.getMessage(), null); + } + + throw (RuntimeException) exception; + } +} diff --git a/packages/camera/example/android/app/build.gradle b/packages/camera/example/android/app/build.gradle index 39003759e4a3..e47b6db5e21e 100644 --- a/packages/camera/example/android/app/build.gradle +++ b/packages/camera/example/android/app/build.gradle @@ -58,6 +58,7 @@ flutter { dependencies { testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test:runner:1.1.1' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' + androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation 'androidx.test:rules:1.2.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' } diff --git a/packages/camera/example/android/app/src/androidTestDebug/java/io/flutter/plugins/cameraexample/EmbeddingV1ActivityTest.java b/packages/camera/example/android/app/src/androidTestDebug/java/io/flutter/plugins/cameraexample/EmbeddingV1ActivityTest.java new file mode 100644 index 000000000000..95b5f4373b62 --- /dev/null +++ b/packages/camera/example/android/app/src/androidTestDebug/java/io/flutter/plugins/cameraexample/EmbeddingV1ActivityTest.java @@ -0,0 +1,13 @@ +package io.flutter.plugins.cameraexample; + +import androidx.test.rule.ActivityTestRule; +import dev.flutter.plugins.e2e.FlutterRunner; +import org.junit.Rule; +import org.junit.runner.RunWith; + +@RunWith(FlutterRunner.class) +public class EmbeddingV1ActivityTest { + @Rule + public ActivityTestRule rule = + new ActivityTestRule<>(EmbeddingV1Activity.class); +} diff --git a/packages/camera/example/android/app/src/androidTestDebug/java/io/flutter/plugins/cameraexample/MainActivityTest.java b/packages/camera/example/android/app/src/androidTestDebug/java/io/flutter/plugins/cameraexample/MainActivityTest.java new file mode 100644 index 000000000000..5d1b95578dc0 --- /dev/null +++ b/packages/camera/example/android/app/src/androidTestDebug/java/io/flutter/plugins/cameraexample/MainActivityTest.java @@ -0,0 +1,11 @@ +package io.flutter.plugins.cameraexample; + +import androidx.test.rule.ActivityTestRule; +import dev.flutter.plugins.e2e.FlutterRunner; +import org.junit.Rule; +import org.junit.runner.RunWith; + +@RunWith(FlutterRunner.class) +public class MainActivityTest { + @Rule public ActivityTestRule rule = new ActivityTestRule<>(MainActivity.class); +} diff --git a/packages/camera/example/android/app/src/main/AndroidManifest.xml b/packages/camera/example/android/app/src/main/AndroidManifest.xml index 15f6087e4ebe..aad8d98bfa27 100644 --- a/packages/camera/example/android/app/src/main/AndroidManifest.xml +++ b/packages/camera/example/android/app/src/main/AndroidManifest.xml @@ -1,30 +1,39 @@ + package="io.flutter.plugins.cameraexample"> - + + + + + + + + + + + - + - - - - - - - - - + diff --git a/packages/camera/example/android/app/src/main/java/io/flutter/plugins/cameraexample/EmbeddingV1Activity.java b/packages/camera/example/android/app/src/main/java/io/flutter/plugins/cameraexample/EmbeddingV1Activity.java new file mode 100644 index 000000000000..9e86560d3ff4 --- /dev/null +++ b/packages/camera/example/android/app/src/main/java/io/flutter/plugins/cameraexample/EmbeddingV1Activity.java @@ -0,0 +1,13 @@ +package io.flutter.plugins.cameraexample; + +import android.os.Bundle; +import io.flutter.app.FlutterActivity; +import io.flutter.plugins.GeneratedPluginRegistrant; + +public class EmbeddingV1Activity extends FlutterActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + GeneratedPluginRegistrant.registerWith(this); + } +} diff --git a/packages/camera/example/android/app/src/main/java/io/flutter/plugins/cameraexample/MainActivity.java b/packages/camera/example/android/app/src/main/java/io/flutter/plugins/cameraexample/MainActivity.java index 8692b845f947..bbe9e45be2db 100644 --- a/packages/camera/example/android/app/src/main/java/io/flutter/plugins/cameraexample/MainActivity.java +++ b/packages/camera/example/android/app/src/main/java/io/flutter/plugins/cameraexample/MainActivity.java @@ -1,13 +1,22 @@ package io.flutter.plugins.cameraexample; -import android.os.Bundle; -import io.flutter.app.FlutterActivity; -import io.flutter.plugins.GeneratedPluginRegistrant; +import androidx.annotation.NonNull; +import io.flutter.embedding.android.FlutterActivity; +import io.flutter.embedding.engine.FlutterEngine; +import io.flutter.embedding.engine.plugins.shim.ShimPluginRegistry; +import io.flutter.plugins.camera.CameraPlugin; +import io.flutter.plugins.pathprovider.PathProviderPlugin; +import io.flutter.plugins.videoplayer.VideoPlayerPlugin; public class MainActivity extends FlutterActivity { @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - GeneratedPluginRegistrant.registerWith(this); + public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { + flutterEngine.getPlugins().add(new CameraPlugin()); + + ShimPluginRegistry shimPluginRegistry = new ShimPluginRegistry(flutterEngine); + PathProviderPlugin.registerWith( + shimPluginRegistry.registrarFor("io.flutter.plugins.pathprovider.PathProviderPlugin")); + VideoPlayerPlugin.registerWith( + shimPluginRegistry.registrarFor("io.flutter.plugins.videoplayer.VideoPlayerPlugin")); } } diff --git a/packages/camera/example/android/gradle.properties b/packages/camera/example/android/gradle.properties index 8bd86f680510..a6738207fd15 100644 --- a/packages/camera/example/android/gradle.properties +++ b/packages/camera/example/android/gradle.properties @@ -1 +1,4 @@ org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true +android.enableR8=true diff --git a/packages/camera/example/pubspec.yaml b/packages/camera/example/pubspec.yaml index 59f3821abe21..0f76a09fed3b 100644 --- a/packages/camera/example/pubspec.yaml +++ b/packages/camera/example/pubspec.yaml @@ -9,6 +9,7 @@ dependencies: flutter: sdk: flutter video_player: ^0.10.0 + e2e: "^0.2.0" dev_dependencies: flutter_test: @@ -18,3 +19,7 @@ dev_dependencies: flutter: uses-material-design: true + +environment: + sdk: ">=2.0.0-dev.28.0 <3.0.0" + flutter: ">=1.9.1+hotfix.4 <2.0.0" diff --git a/packages/camera/example/test_driver/camera.dart b/packages/camera/example/test_driver/camera_e2e.dart similarity index 94% rename from packages/camera/example/test_driver/camera.dart rename to packages/camera/example/test_driver/camera_e2e.dart index d68b8c5ba1fc..2e6a344c7d04 100644 --- a/packages/camera/example/test_driver/camera.dart +++ b/packages/camera/example/test_driver/camera_e2e.dart @@ -3,16 +3,16 @@ import 'dart:io'; import 'dart:ui'; import 'package:flutter/painting.dart'; -import 'package:flutter_driver/driver_extension.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:camera/camera.dart'; import 'package:path_provider/path_provider.dart'; import 'package:video_player/video_player.dart'; +import 'package:e2e/e2e.dart'; void main() { - final Completer completer = Completer(); Directory testDir; - enableFlutterDriverExtension(handler: (_) => completer.future); + + E2EWidgetsFlutterBinding.ensureInitialized(); setUpAll(() async { final Directory extDir = await getTemporaryDirectory(); @@ -21,7 +21,6 @@ void main() { tearDownAll(() async { await testDir.delete(recursive: true); - completer.complete(null); }); final Map presetExpectedSizes = @@ -70,7 +69,8 @@ void main() { expectedSize, Size(image.height.toDouble(), image.width.toDouble())); } - test('Capture specific image resolutions', () async { + testWidgets('Capture specific image resolutions', + (WidgetTester tester) async { final List cameras = await availableCameras(); if (cameras.isEmpty) { return; @@ -90,7 +90,7 @@ void main() { await controller.dispose(); } } - }); + }, skip: !Platform.isAndroid); // This tests that the capture is no bigger than the preset, since we have // automatic code to fall back to smaller sizes when we need to. Returns @@ -121,7 +121,8 @@ void main() { expectedSize, Size(video.height, video.width)); } - test('Capture specific video resolutions', () async { + testWidgets('Capture specific video resolutions', + (WidgetTester tester) async { final List cameras = await availableCameras(); if (cameras.isEmpty) { return; @@ -142,9 +143,9 @@ void main() { await controller.dispose(); } } - }); + }, skip: !Platform.isAndroid); - test('Pause and resume video recording', () async { + testWidgets('Pause and resume video recording', (WidgetTester tester) async { final List cameras = await availableCameras(); if (cameras.isEmpty) { return; @@ -198,5 +199,5 @@ void main() { await videoController.dispose(); expect(duration, lessThan(recordingTime - timePaused)); - }); + }, skip: !Platform.isAndroid); } diff --git a/packages/camera/example/test_driver/camera_e2e_test.dart b/packages/camera/example/test_driver/camera_e2e_test.dart new file mode 100644 index 000000000000..e3e089a81fc9 --- /dev/null +++ b/packages/camera/example/test_driver/camera_e2e_test.dart @@ -0,0 +1,55 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter_driver/flutter_driver.dart'; + +const String _examplePackage = 'io.flutter.plugins.cameraexample'; + +Future main() async { + if (!(Platform.isLinux || Platform.isMacOS)) { + print('This test must be run on a POSIX host. Skipping...'); + exit(0); + } + final bool adbExists = + Process.runSync('which', ['adb']).exitCode == 0; + if (!adbExists) { + print('This test needs ADB to exist on the \$PATH. Skipping...'); + exit(0); + } + print('Granting camera permissions...'); + Process.runSync('adb', [ + 'shell', + 'pm', + 'grant', + _examplePackage, + 'android.permission.CAMERA' + ]); + Process.runSync('adb', [ + 'shell', + 'pm', + 'grant', + _examplePackage, + 'android.permission.RECORD_AUDIO' + ]); + print('Starting test.'); + final FlutterDriver driver = await FlutterDriver.connect(); + final String result = + await driver.requestData(null, timeout: const Duration(minutes: 1)); + driver.close(); + print('Test finished. Revoking camera permissions...'); + Process.runSync('adb', [ + 'shell', + 'pm', + 'revoke', + _examplePackage, + 'android.permission.CAMERA' + ]); + Process.runSync('adb', [ + 'shell', + 'pm', + 'revoke', + _examplePackage, + 'android.permission.RECORD_AUDIO' + ]); + exit(result == 'pass' ? 0 : 1); +} diff --git a/packages/camera/example/test_driver/camera_test.dart b/packages/camera/example/test_driver/camera_test.dart deleted file mode 100644 index 38fe6c447e05..000000000000 --- a/packages/camera/example/test_driver/camera_test.dart +++ /dev/null @@ -1,7 +0,0 @@ -import 'package:flutter_driver/flutter_driver.dart'; - -Future main() async { - final FlutterDriver driver = await FlutterDriver.connect(); - await driver.requestData(null, timeout: const Duration(minutes: 1)); - driver.close(); -} diff --git a/packages/camera/pubspec.yaml b/packages/camera/pubspec.yaml index 0906f175fb8d..82facbc94e5d 100644 --- a/packages/camera/pubspec.yaml +++ b/packages/camera/pubspec.yaml @@ -2,7 +2,7 @@ name: camera description: A Flutter plugin for getting information about and controlling the camera on Android and iOS. Supports previewing the camera feed, capturing images, capturing video, and streaming image buffers to dart. -version: 0.5.5+1 +version: 0.5.6 authors: - Flutter Team @@ -32,4 +32,4 @@ flutter: environment: sdk: ">=2.0.0-dev.28.0 <3.0.0" - flutter: ">=1.2.0 <2.0.0" + flutter: ">=1.6.7 <2.0.0"