diff --git a/packages/camera/android/src/main/java/dev/flutter/plugins/camera/CameraPlugin.java b/packages/camera/android/src/main/java/dev/flutter/plugins/camera/CameraPlugin.java
new file mode 100644
index 000000000000..fe64c77e73f8
--- /dev/null
+++ b/packages/camera/android/src/main/java/dev/flutter/plugins/camera/CameraPlugin.java
@@ -0,0 +1,103 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package dev.flutter.plugins.camera;
+
+import android.hardware.camera2.CameraAccessException;
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+
+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.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 io.flutter.plugin.common.PluginRegistry;
+import io.flutter.plugins.camera.Camera;
+import io.flutter.plugins.camera.CameraChannelHandler;
+import io.flutter.plugins.camera.CameraPermissions;
+import io.flutter.plugins.camera.CameraUtils;
+
+public class CameraPlugin implements FlutterPlugin, ActivityAware {
+
+ private FlutterPluginBinding pluginBinding;
+ private ActivityPluginBinding activityBinding;
+
+ private final CameraPermissions cameraPermissions = new CameraPermissions();
+ private EventChannel imageStreamChannel;
+ private Camera camera;
+
+ @Override
+ public void onAttachedToEngine(FlutterPluginBinding flutterPluginBinding) {
+ this.pluginBinding = flutterPluginBinding;
+ this.imageStreamChannel = new EventChannel(
+ this.pluginBinding.getFlutterEngine().getDartExecutor(),
+ "plugins.flutter.io/camera/imageStream"
+ );
+ }
+
+ @Override
+ public void onDetachedFromEngine(FlutterPluginBinding flutterPluginBinding) {
+ this.pluginBinding = null;
+ }
+
+ @Override
+ public void onAttachedToActivity(ActivityPluginBinding activityPluginBinding) {
+ // Setup
+ 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;
+ }
+
+ this.activityBinding = activityPluginBinding;
+
+ final MethodChannel channel =
+ new MethodChannel(pluginBinding.getFlutterEngine().getDartExecutor(), "plugins.flutter.io/camera");
+
+ channel.setMethodCallHandler(new CameraChannelHandler(
+ activityPluginBinding.getActivity(),
+ pluginBinding.getFlutterEngine().getRenderer(),
+ pluginBinding.getFlutterEngine().getDartExecutor(),
+ new EventChannel(pluginBinding.getFlutterEngine().getDartExecutor(), "plugins.flutter.io/camera/imageStream"),
+ new NewEmbeddingPermissions(activityPluginBinding)
+ ));
+ }
+
+ @Override
+ public void onDetachedFromActivityForConfigChanges() {
+ // Ignore
+ }
+
+ @Override
+ public void onReattachedToActivityForConfigChanges(ActivityPluginBinding activityPluginBinding) {
+ // Ignore
+ }
+
+ @Override
+ public void onDetachedFromActivity() {
+ // Teardown
+ this.activityBinding = null;
+ }
+
+ private static class NewEmbeddingPermissions implements CameraPermissions.Permissions {
+ private ActivityPluginBinding activityPluginBinding;
+
+ private NewEmbeddingPermissions(@NonNull ActivityPluginBinding activityPluginBinding) {
+ this.activityPluginBinding = activityPluginBinding;
+ }
+
+ @Override
+ public PluginRegistry.Registrar addRequestPermissionsResultListener(PluginRegistry.RequestPermissionsResultListener listener) {
+ activityPluginBinding.addRequestPermissionsResultListener(listener);
+
+ return null; // <- this is a breaking change.
+ }
+ }
+}
diff --git a/packages/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java
index 80da644a146a..2a86c30a134f 100644
--- a/packages/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java
+++ b/packages/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java
@@ -29,6 +29,7 @@
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.view.FlutterView;
+import io.flutter.view.TextureRegistry;
import io.flutter.view.TextureRegistry.SurfaceTextureEntry;
import java.io.File;
import java.io.FileOutputStream;
@@ -74,7 +75,7 @@ public enum ResolutionPreset {
public Camera(
final Activity activity,
- final FlutterView flutterView,
+ final TextureRegistry textureRegistry,
final String cameraName,
final String resolutionPreset,
final boolean enableAudio)
@@ -85,7 +86,7 @@ public Camera(
this.cameraName = cameraName;
this.enableAudio = enableAudio;
- this.flutterTexture = flutterView.createSurfaceTexture();
+ this.flutterTexture = textureRegistry.createSurfaceTexture();
this.cameraManager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
orientationEventListener =
new OrientationEventListener(activity.getApplicationContext()) {
@@ -233,7 +234,7 @@ private void writeToFile(ByteBuffer buffer, File file) throws IOException {
}
}
- SurfaceTextureEntry getFlutterTexture() {
+ public SurfaceTextureEntry getFlutterTexture() {
return flutterTexture;
}
diff --git a/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraChannelHandler.java b/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraChannelHandler.java
new file mode 100644
index 000000000000..739d66057c0c
--- /dev/null
+++ b/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraChannelHandler.java
@@ -0,0 +1,163 @@
+package io.flutter.plugins.camera;
+
+import android.app.Activity;
+import android.hardware.camera2.CameraAccessException;
+
+import androidx.annotation.NonNull;
+
+import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
+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.view.TextureRegistry;
+
+public class CameraChannelHandler implements MethodChannel.MethodCallHandler {
+
+ private final CameraPermissions cameraPermissions = new CameraPermissions();
+ private final Activity activity;
+ private final TextureRegistry textureRegistry;
+ private final BinaryMessenger messenger;
+ private final EventChannel imageStreamChannel;
+ private final CameraPermissions.Permissions permissions;
+ private Camera camera;
+
+ public CameraChannelHandler(
+ @NonNull Activity activity,
+ @NonNull TextureRegistry textureRegistry,
+ @NonNull BinaryMessenger binaryMessenger,
+ @NonNull EventChannel imageStreamChannel,
+ @NonNull CameraPermissions.Permissions permissions
+ ) {
+ this.activity = activity;
+ this.textureRegistry = textureRegistry;
+ this.messenger = binaryMessenger;
+ this.imageStreamChannel = imageStreamChannel;
+ this.permissions = permissions;
+ }
+
+ @Override
+ public void onMethodCall(@NonNull MethodCall call, @NonNull final MethodChannel.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,
+ permissions,
+ 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;
+ }
+ }
+
+ private void instantiateCamera(MethodCall call, MethodChannel.Result result) throws CameraAccessException {
+ String cameraName = call.argument("cameraName");
+ String resolutionPreset = call.argument("resolutionPreset");
+ boolean enableAudio = call.argument("enableAudio");
+ camera = new Camera(activity, textureRegistry, cameraName, resolutionPreset, enableAudio);
+
+ EventChannel cameraEventChannel =
+ new EventChannel(
+ messenger,
+ "flutter.io/cameraPlugin/cameraEvents" + camera.getFlutterTexture().id());
+ camera.setupCameraEventChannel(cameraEventChannel);
+
+ 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, MethodChannel.Result result) {
+ if (exception instanceof CameraAccessException) {
+ result.error("CameraAccess", exception.getMessage(), null);
+ }
+
+ throw (RuntimeException) exception;
+ }
+}
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..ee07afaf83d5 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
@@ -6,6 +6,8 @@
import android.content.pm.PackageManager;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
+
+import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugin.common.PluginRegistry.Registrar;
@@ -14,13 +16,12 @@ public class CameraPermissions {
private boolean ongoing = false;
public void requestPermissions(
- Registrar registrar, boolean enableAudio, ResultCallback callback) {
+ Activity activity, Permissions permissions, 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(
+ permissions.addRequestPermissionsResultListener(
new CameraRequestPermissionsListener(
(String errorCode, String errorDescription) -> {
ongoing = false;
@@ -74,7 +75,13 @@ public boolean onRequestPermissionsResult(int id, String[] permissions, int[] gr
}
}
- interface ResultCallback {
+ public interface Permissions {
+ Registrar addRequestPermissionsResultListener(
+ PluginRegistry.RequestPermissionsResultListener listener
+ );
+ }
+
+ public 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 b3a1da8b1b09..98e8d164343b 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,31 +4,16 @@
package io.flutter.plugins.camera;
-import android.hardware.camera2.CameraAccessException;
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 io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugin.common.PluginRegistry.Registrar;
-import io.flutter.view.FlutterView;
-
-public class CameraPlugin implements MethodCallHandler {
- private final CameraPermissions cameraPermissions = new CameraPermissions();
- private final FlutterView view;
- private final Registrar registrar;
- private final EventChannel imageStreamChannel;
- private Camera camera;
-
- private CameraPlugin(Registrar registrar) {
- this.registrar = registrar;
- this.view = registrar.view();
- this.imageStreamChannel =
- new EventChannel(registrar.messenger(), "plugins.flutter.io/camera/imageStream");
- }
+public class CameraPlugin {
public static void registerWith(Registrar registrar) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
@@ -38,133 +23,30 @@ public static void registerWith(Registrar registrar) {
return;
}
+ CameraChannelHandler channelHandler = new CameraChannelHandler(
+ registrar.activity(),
+ registrar.view(),
+ registrar.messenger(),
+ new EventChannel(registrar.messenger(), "plugins.flutter.io/camera/imageStream"),
+ new OldEmbeddingPermissions(registrar)
+ );
+
final MethodChannel channel =
new MethodChannel(registrar.messenger(), "plugins.flutter.io/camera");
- channel.setMethodCallHandler(new CameraPlugin(registrar));
+ channel.setMethodCallHandler(channelHandler);
}
- private void instantiateCamera(MethodCall call, Result result) throws CameraAccessException {
- String cameraName = call.argument("cameraName");
- String resolutionPreset = call.argument("resolutionPreset");
- boolean enableAudio = call.argument("enableAudio");
- camera = new Camera(registrar.activity(), view, cameraName, resolutionPreset, enableAudio);
+ private static class OldEmbeddingPermissions implements CameraPermissions.Permissions {
+ private Registrar registrar;
- EventChannel cameraEventChannel =
- new EventChannel(
- registrar.messenger(),
- "flutter.io/cameraPlugin/cameraEvents" + camera.getFlutterTexture().id());
- camera.setupCameraEventChannel(cameraEventChannel);
-
- camera.open(result);
- }
-
- @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);
- }
- });
-
- 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;
+ private OldEmbeddingPermissions(@NonNull Registrar registrar) {
+ this.registrar = registrar;
}
- }
- // 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);
+ @Override
+ public Registrar addRequestPermissionsResultListener(PluginRegistry.RequestPermissionsResultListener listener) {
+ return registrar.addRequestPermissionsResultListener(listener);
}
-
- throw (RuntimeException) exception;
}
}
diff --git a/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java b/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java
index a7bb3b7d4914..ec0399e04446 100644
--- a/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java
+++ b/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java
@@ -24,7 +24,7 @@ public final class CameraUtils {
private CameraUtils() {}
- static Size computeBestPreviewSize(String cameraName, ResolutionPreset preset) {
+ public static Size computeBestPreviewSize(String cameraName, ResolutionPreset preset) {
if (preset.ordinal() > ResolutionPreset.high.ordinal()) {
preset = ResolutionPreset.high;
}
diff --git a/packages/camera/example/android/app/build.gradle b/packages/camera/example/android/app/build.gradle
index 39003759e4a3..f4eb5498a77d 100644
--- a/packages/camera/example/android/app/build.gradle
+++ b/packages/camera/example/android/app/build.gradle
@@ -57,6 +57,10 @@ flutter {
}
dependencies {
+ // TODO: remove these dependencies when engine POM is working correctly.
+ implementation 'androidx.lifecycle:lifecycle-runtime:2.1.0'
+ implementation 'androidx.lifecycle:lifecycle-common-java8:2.1.0'
+
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
diff --git a/packages/camera/example/android/app/src/main/AndroidManifest.xml b/packages/camera/example/android/app/src/main/AndroidManifest.xml
index 15f6087e4ebe..51b898f1b4d6 100644
--- a/packages/camera/example/android/app/src/main/AndroidManifest.xml
+++ b/packages/camera/example/android/app/src/main/AndroidManifest.xml
@@ -21,6 +21,17 @@
+
+
+
+
+
+
diff --git a/packages/camera/example/android/app/src/main/java/dev/flutter/plugins/cameraexample/MainActivity.java b/packages/camera/example/android/app/src/main/java/dev/flutter/plugins/cameraexample/MainActivity.java
new file mode 100644
index 000000000000..cdc794a3d495
--- /dev/null
+++ b/packages/camera/example/android/app/src/main/java/dev/flutter/plugins/cameraexample/MainActivity.java
@@ -0,0 +1,14 @@
+package dev.flutter.plugins.cameraexample;
+
+import android.os.Bundle;
+
+import dev.flutter.plugins.camera.CameraPlugin;
+import io.flutter.embedding.android.FlutterActivity;
+import io.flutter.embedding.engine.FlutterEngine;
+
+public class MainActivity extends FlutterActivity {
+ @Override
+ public void configureFlutterEngine(FlutterEngine flutterEngine) {
+ flutterEngine.getPlugins().add(new CameraPlugin());
+ }
+}
diff --git a/packages/camera/example/android/gradle.properties b/packages/camera/example/android/gradle.properties
index 8bd86f680510..4d3226abc21b 100644
--- a/packages/camera/example/android/gradle.properties
+++ b/packages/camera/example/android/gradle.properties
@@ -1 +1,3 @@
org.gradle.jvmargs=-Xmx1536M
+android.useAndroidX=true
+android.enableJetifier=true
\ No newline at end of file