Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
475f4ab
Replicated existing code and migrated references to new embedding, ad…
matthew-carroll Sep 24, 2019
8cde03b
Introduced CameraPreviewDisplay and CameraImageStream to remove Flutt…
matthew-carroll Sep 24, 2019
2081f42
Introduced a CameraEventListener to completely decouple Camera from E…
matthew-carroll Sep 24, 2019
b78bfcb
Removed all references of Result from Camera by introducing a combina…
matthew-carroll Sep 24, 2019
b45ae68
Refactored Camera such that getFlutterTexture() no longer needs to ex…
matthew-carroll Sep 24, 2019
6780abf
Moved ResolutionPreset to standalone class and made Camera package pr…
matthew-carroll Sep 24, 2019
8b93f68
Re-organized Camera code order to clearly separate preview images, fr…
matthew-carroll Sep 24, 2019
c85fcc2
Removed Activity reference from Camera.
matthew-carroll Sep 24, 2019
e04653b
Refactored CameraPermissions to eliminate Activity, ActivityCompat, C…
matthew-carroll Sep 24, 2019
60c1d20
Refactored CameraPermissions into an Interface and AndroidCameraPermi…
matthew-carroll Sep 25, 2019
13e4303
Separated the CameraPlugin class from the concept of the comms Camera…
matthew-carroll Sep 25, 2019
d5da9ce
Introduced CameraDetails data structure & wrote unit tests for Camera…
matthew-carroll Sep 25, 2019
436d9ca
Added unit tests for CameraPluginProtocol and reached 100% coverage f…
matthew-carroll Sep 25, 2019
3dcc0ab
Moved CameraPreviewDisplay and CameraImageStream implementations into…
matthew-carroll Sep 26, 2019
02ab397
Extracted per-camera channel construction out into CameraPlugin for t…
matthew-carroll Sep 26, 2019
e5408d9
Refactored so that CameraSystem and AndroidCameraSystem could be redu…
matthew-carroll Sep 26, 2019
6ea1b4f
Deleted CameraUtils, and continued to cleanup relationships.
matthew-carroll Sep 26, 2019
4669cb4
Added tests for CameraSystem - at 93% line coverage.
matthew-carroll Sep 26, 2019
410d0ac
Added tests to ensure that CameraPlugin does nothing without an Activ…
matthew-carroll Sep 26, 2019
0bd6522
Added tests for ChannelCameraEventHandler - now at CameraSystem (93%)…
matthew-carroll Sep 26, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package dev.flutter.plugins.camera;

import android.Manifest.permission;
import android.app.Activity;
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;

public class CameraPermissions {
private static final int CAMERA_REQUEST_ID = 9796;
private boolean ongoing = false;

public void requestPermissions(
ActivityPluginBinding activityPluginBinding,
boolean enableAudio,
ResultCallback callback
) {
if (ongoing) {
callback.onResult("cameraPermission", "Camera permission request ongoing");
}
Activity activity = activityPluginBinding.getActivity();
if (!hasCameraPermission(activity) || (enableAudio && !hasAudioPermission(activity))) {
activityPluginBinding.addRequestPermissionsResultListener(
new CameraRequestPermissionsListener(
(String errorCode, String errorDescription) -> {
ongoing = false;
callback.onResult(errorCode, errorDescription);
}));
ongoing = true;
ActivityCompat.requestPermissions(
activity,
enableAudio
? new String[] {permission.CAMERA, permission.RECORD_AUDIO}
: new String[] {permission.CAMERA},
CAMERA_REQUEST_ID);
} else {
// Permissions already exist. Call the callback with success.
callback.onResult(null, null);
}
}

private boolean hasCameraPermission(Activity activity) {
return ContextCompat.checkSelfPermission(activity, permission.CAMERA)
== PackageManager.PERMISSION_GRANTED;
}

private boolean hasAudioPermission(Activity activity) {
return ContextCompat.checkSelfPermission(activity, permission.RECORD_AUDIO)
== PackageManager.PERMISSION_GRANTED;
}

private static class CameraRequestPermissionsListener
implements PluginRegistry.RequestPermissionsResultListener {
final ResultCallback callback;

private CameraRequestPermissionsListener(ResultCallback callback) {
this.callback = callback;
}

@Override
public boolean onRequestPermissionsResult(int id, String[] permissions, int[] grantResults) {
if (id == CAMERA_REQUEST_ID) {
if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
callback.onResult("cameraPermission", "MediaRecorderCamera permission not granted");
} else if (grantResults.length > 1
&& grantResults[1] != PackageManager.PERMISSION_GRANTED) {
callback.onResult("cameraPermission", "MediaRecorderAudio permission not granted");
} else {
callback.onResult(null, null);
}
return true;
}
return false;
}
}

interface ResultCallback {
void onResult(String errorCode, String errorDescription);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
// 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;

public class CameraPlugin implements FlutterPlugin, ActivityAware, MethodCallHandler {

private final CameraPermissions cameraPermissions = new CameraPermissions();
private EventChannel imageStreamChannel;
private Camera camera;

private FlutterPluginBinding pluginBinding;
private ActivityPluginBinding activityBinding;

@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(this);
}

@Override
public void onDetachedFromActivityForConfigChanges() {
// Ignore
}

@Override
public void onReattachedToActivityForConfigChanges(ActivityPluginBinding activityPluginBinding) {
// Ignore
}

@Override
public void onDetachedFromActivity() {
// Teardown
this.activityBinding = 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");
camera = new Camera(
activityBinding.getActivity(),
pluginBinding.getFlutterEngine().getRenderer(),
cameraName,
resolutionPreset,
enableAudio
);

EventChannel cameraEventChannel =
new EventChannel(
pluginBinding.getFlutterEngine().getDartExecutor(),
"flutter.io/cameraPlugin/cameraEvents" + camera.getFlutterTexture().id());
camera.setupCameraEventChannel(cameraEventChannel);

camera.open(result);
}

@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we find a way to not duplicate all this code?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, but it looks like this PR probably won't merge because @bparrishMines has other plans for the plugin. I would recommend that each plugin dev do whatever is most appropriate in terms of replication as they migrate.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be useful to have a sample PR/commit that shows only the diff that's relevant to properly extending a plugin to the new embedder.

The camera refactoring plans are longer term, we will want to extend the existing plugin to support the new embedder much sooner, and I won't tie doing that to the big refactoring planned.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. I'll put up a PR that does the minimum possible work and shares code.

switch (call.method) {
case "availableCameras":
try {
result.success(CameraUtils.getAvailableCameras(activityBinding.getActivity()));
} catch (Exception e) {
handleException(e, result);
}
break;
case "initialize":
{
if (camera != null) {
camera.close();
}
cameraPermissions.requestPermissions(
activityBinding,
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;
}
}

// 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;
}
}
Loading