Skip to content

Commit 35f0b1a

Browse files
authored
[camerax] Add system services to plugin (flutter#6986)
* Add base code from proof of concept * Add pigeon types * Make corrections, add Dart tests * Add test files * Add java tests * Add docs * Add stop, docs, fix analyze * Fix comment * Add comment and remove literals * Formatting * Remove merge conflict reside * Fix test * Fix bad pasting job
1 parent 2edf563 commit 35f0b1a

19 files changed

+2016
-3
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
11
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
22
package="io.flutter.plugins.camerax">
3+
<uses-feature android:name="android.hardware.camera.any" />
4+
<uses-permission android:name="android.permission.CAMERA" />
5+
<uses-permission android:name="android.permission.RECORD_AUDIO" />
6+
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
7+
android:maxSdkVersion="28" />
38
</manifest>

packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@
1111
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
1212
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
1313
import io.flutter.plugin.common.BinaryMessenger;
14+
import io.flutter.view.TextureRegistry;
1415

1516
/** Platform implementation of the camera_plugin implemented with the CameraX library. */
1617
public final class CameraAndroidCameraxPlugin implements FlutterPlugin, ActivityAware {
1718
private InstanceManager instanceManager;
1819
private FlutterPluginBinding pluginBinding;
19-
public ProcessCameraProviderHostApiImpl processCameraProviderHostApi;
20+
private ProcessCameraProviderHostApiImpl processCameraProviderHostApi;
21+
public SystemServicesHostApiImpl systemServicesHostApi;
2022

2123
/**
2224
* Initialize this within the {@code #configureFlutterEngine} of a Flutter activity or fragment.
@@ -25,7 +27,7 @@ public final class CameraAndroidCameraxPlugin implements FlutterPlugin, Activity
2527
*/
2628
public CameraAndroidCameraxPlugin() {}
2729

28-
void setUp(BinaryMessenger binaryMessenger, Context context) {
30+
void setUp(BinaryMessenger binaryMessenger, Context context, TextureRegistry textureRegistry) {
2931
// Set up instance manager.
3032
instanceManager =
3133
InstanceManager.open(
@@ -45,6 +47,8 @@ void setUp(BinaryMessenger binaryMessenger, Context context) {
4547
new ProcessCameraProviderHostApiImpl(binaryMessenger, instanceManager, context);
4648
GeneratedCameraXLibrary.ProcessCameraProviderHostApi.setup(
4749
binaryMessenger, processCameraProviderHostApi);
50+
systemServicesHostApi = new SystemServicesHostApiImpl(binaryMessenger, instanceManager);
51+
GeneratedCameraXLibrary.SystemServicesHostApi.setup(binaryMessenger, systemServicesHostApi);
4852
}
4953

5054
@Override
@@ -63,10 +67,16 @@ public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
6367

6468
@Override
6569
public void onAttachedToActivity(@NonNull ActivityPluginBinding activityPluginBinding) {
66-
setUp(pluginBinding.getBinaryMessenger(), pluginBinding.getApplicationContext());
70+
setUp(
71+
pluginBinding.getBinaryMessenger(),
72+
pluginBinding.getApplicationContext(),
73+
pluginBinding.getTextureRegistry());
6774
updateContext(pluginBinding.getApplicationContext());
6875
processCameraProviderHostApi.setLifecycleOwner(
6976
(LifecycleOwner) activityPluginBinding.getActivity());
77+
systemServicesHostApi.setActivity(activityPluginBinding.getActivity());
78+
systemServicesHostApi.setPermissionsRegistry(
79+
activityPluginBinding::addRequestPermissionsResultListener);
7080
}
7181

7282
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.plugins.camerax;
6+
7+
import android.Manifest;
8+
import android.Manifest.permission;
9+
import android.app.Activity;
10+
import android.content.pm.PackageManager;
11+
import androidx.annotation.VisibleForTesting;
12+
import androidx.core.app.ActivityCompat;
13+
import androidx.core.content.ContextCompat;
14+
15+
final class CameraPermissionsManager {
16+
interface PermissionsRegistry {
17+
@SuppressWarnings("deprecation")
18+
void addListener(
19+
io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener handler);
20+
}
21+
22+
interface ResultCallback {
23+
void onResult(String errorCode, String errorDescription);
24+
}
25+
26+
/**
27+
* Camera access permission errors handled when camera is created. See {@code MethodChannelCamera}
28+
* in {@code camera/camera_platform_interface} for details.
29+
*/
30+
private static final String CAMERA_PERMISSIONS_REQUEST_ONGOING =
31+
"CameraPermissionsRequestOngoing";
32+
33+
private static final String CAMERA_PERMISSIONS_REQUEST_ONGOING_MESSAGE =
34+
"Another request is ongoing and multiple requests cannot be handled at once.";
35+
private static final String CAMERA_ACCESS_DENIED = "CameraAccessDenied";
36+
private static final String CAMERA_ACCESS_DENIED_MESSAGE = "Camera access permission was denied.";
37+
private static final String AUDIO_ACCESS_DENIED = "AudioAccessDenied";
38+
private static final String AUDIO_ACCESS_DENIED_MESSAGE = "Audio access permission was denied.";
39+
40+
private static final int CAMERA_REQUEST_ID = 9796;
41+
@VisibleForTesting boolean ongoing = false;
42+
43+
void requestPermissions(
44+
Activity activity,
45+
PermissionsRegistry permissionsRegistry,
46+
boolean enableAudio,
47+
ResultCallback callback) {
48+
if (ongoing) {
49+
callback.onResult(
50+
CAMERA_PERMISSIONS_REQUEST_ONGOING, CAMERA_PERMISSIONS_REQUEST_ONGOING_MESSAGE);
51+
return;
52+
}
53+
if (!hasCameraPermission(activity) || (enableAudio && !hasAudioPermission(activity))) {
54+
permissionsRegistry.addListener(
55+
new CameraRequestPermissionsListener(
56+
(String errorCode, String errorDescription) -> {
57+
ongoing = false;
58+
callback.onResult(errorCode, errorDescription);
59+
}));
60+
ongoing = true;
61+
ActivityCompat.requestPermissions(
62+
activity,
63+
enableAudio
64+
? new String[] {Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO}
65+
: new String[] {Manifest.permission.CAMERA},
66+
CAMERA_REQUEST_ID);
67+
} else {
68+
// Permissions already exist. Call the callback with success.
69+
callback.onResult(null, null);
70+
}
71+
}
72+
73+
private boolean hasCameraPermission(Activity activity) {
74+
return ContextCompat.checkSelfPermission(activity, permission.CAMERA)
75+
== PackageManager.PERMISSION_GRANTED;
76+
}
77+
78+
private boolean hasAudioPermission(Activity activity) {
79+
return ContextCompat.checkSelfPermission(activity, permission.RECORD_AUDIO)
80+
== PackageManager.PERMISSION_GRANTED;
81+
}
82+
83+
@VisibleForTesting
84+
@SuppressWarnings("deprecation")
85+
static final class CameraRequestPermissionsListener
86+
implements io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener {
87+
88+
// There's no way to unregister permission listeners in the v1 embedding, so we'll be called
89+
// duplicate times in cases where the user denies and then grants a permission. Keep track of if
90+
// we've responded before and bail out of handling the callback manually if this is a repeat
91+
// call.
92+
boolean alreadyCalled = false;
93+
94+
final ResultCallback callback;
95+
96+
@VisibleForTesting
97+
CameraRequestPermissionsListener(ResultCallback callback) {
98+
this.callback = callback;
99+
}
100+
101+
@Override
102+
public boolean onRequestPermissionsResult(int id, String[] permissions, int[] grantResults) {
103+
if (alreadyCalled || id != CAMERA_REQUEST_ID) {
104+
return false;
105+
}
106+
107+
alreadyCalled = true;
108+
// grantResults could be empty if the permissions request with the user is interrupted
109+
// https://developer.android.com/reference/android/app/Activity#onRequestPermissionsResult(int,%20java.lang.String[],%20int[])
110+
if (grantResults.length == 0 || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
111+
callback.onResult(CAMERA_ACCESS_DENIED, CAMERA_ACCESS_DENIED_MESSAGE);
112+
} else if (grantResults.length > 1 && grantResults[1] != PackageManager.PERMISSION_GRANTED) {
113+
callback.onResult(AUDIO_ACCESS_DENIED, AUDIO_ACCESS_DENIED_MESSAGE);
114+
} else {
115+
callback.onResult(null, null);
116+
}
117+
return true;
118+
}
119+
}
120+
}

packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXProxy.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,23 @@
44

55
package io.flutter.plugins.camerax;
66

7+
import android.app.Activity;
78
import androidx.camera.core.CameraSelector;
89

910
public class CameraXProxy {
1011
public CameraSelector.Builder createCameraSelectorBuilder() {
1112
return new CameraSelector.Builder();
1213
}
14+
15+
public CameraPermissionsManager createCameraPermissionsManager() {
16+
return new CameraPermissionsManager();
17+
}
18+
19+
public DeviceOrientationManager createDeviceOrientationManager(
20+
Activity activity,
21+
Boolean isFrontFacing,
22+
int sensorOrientation,
23+
DeviceOrientationManager.DeviceOrientationChangeCallback callback) {
24+
return new DeviceOrientationManager(activity, isFrontFacing, sensorOrientation, callback);
25+
}
1326
}

0 commit comments

Comments
 (0)