This repository was archived by the owner on Feb 22, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 9.7k
[camera] Migrate to new embedding #2114
Closed
matthew-carroll
wants to merge
20
commits into
flutter:master
from
matthew-carroll:migrate_camera_plugin_to_new_embedding
Closed
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 8cde03b
Introduced CameraPreviewDisplay and CameraImageStream to remove Flutt…
matthew-carroll 2081f42
Introduced a CameraEventListener to completely decouple Camera from E…
matthew-carroll b78bfcb
Removed all references of Result from Camera by introducing a combina…
matthew-carroll b45ae68
Refactored Camera such that getFlutterTexture() no longer needs to ex…
matthew-carroll 6780abf
Moved ResolutionPreset to standalone class and made Camera package pr…
matthew-carroll 8b93f68
Re-organized Camera code order to clearly separate preview images, fr…
matthew-carroll c85fcc2
Removed Activity reference from Camera.
matthew-carroll e04653b
Refactored CameraPermissions to eliminate Activity, ActivityCompat, C…
matthew-carroll 60c1d20
Refactored CameraPermissions into an Interface and AndroidCameraPermi…
matthew-carroll 13e4303
Separated the CameraPlugin class from the concept of the comms Camera…
matthew-carroll d5da9ce
Introduced CameraDetails data structure & wrote unit tests for Camera…
matthew-carroll 436d9ca
Added unit tests for CameraPluginProtocol and reached 100% coverage f…
matthew-carroll 3dcc0ab
Moved CameraPreviewDisplay and CameraImageStream implementations into…
matthew-carroll 02ab397
Extracted per-camera channel construction out into CameraPlugin for t…
matthew-carroll e5408d9
Refactored so that CameraSystem and AndroidCameraSystem could be redu…
matthew-carroll 6ea1b4f
Deleted CameraUtils, and continued to cleanup relationships.
matthew-carroll 4669cb4
Added tests for CameraSystem - at 93% line coverage.
matthew-carroll 410d0ac
Added tests to ensure that CameraPlugin does nothing without an Activ…
matthew-carroll 0bd6522
Added tests for ChannelCameraEventHandler - now at CameraSystem (93%)…
matthew-carroll File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
557 changes: 557 additions & 0 deletions
557
packages/camera/android/src/main/java/dev/flutter/plugins/camera/Camera.java
Large diffs are not rendered by default.
Oops, something went wrong.
84 changes: 84 additions & 0 deletions
84
packages/camera/android/src/main/java/dev/flutter/plugins/camera/CameraPermissions.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | ||
| } | ||
| } |
207 changes: 207 additions & 0 deletions
207
packages/camera/android/src/main/java/dev/flutter/plugins/camera/CameraPlugin.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) { | ||
| 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; | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.