Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit fe3b542

Browse files
authored
[camera] Add CameraSelector class to CameraX plugin (#6348)
1 parent 45d6e0d commit fe3b542

17 files changed

+861
-17
lines changed

packages/camera/camera_android_camerax/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22

33
* Creates camera_android_camerax plugin for development.
44
* Adds CameraInfo class and removes unnecessary code from plugin.
5+
* Adds CameraSelector class.

packages/camera/camera_android_camerax/android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ buildscript {
88
}
99

1010
dependencies {
11-
classpath 'com.android.tools.build:gradle:7.2.2'
11+
classpath 'com.android.tools.build:gradle:7.3.0'
1212
}
1313
}
1414

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ void setUp(BinaryMessenger binaryMessenger, Context context) {
3737
binaryMessenger, new CameraInfoHostApiImpl(instanceManager));
3838
GeneratedCameraXLibrary.JavaObjectHostApi.setup(
3939
binaryMessenger, new JavaObjectHostApiImpl(instanceManager));
40+
GeneratedCameraXLibrary.CameraSelectorHostApi.setup(
41+
binaryMessenger, new CameraSelectorHostApiImpl(binaryMessenger, instanceManager));
4042
}
4143

4244
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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 androidx.camera.core.CameraSelector;
8+
import io.flutter.plugin.common.BinaryMessenger;
9+
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraSelectorFlutterApi;
10+
11+
public class CameraSelectorFlutterApiImpl extends CameraSelectorFlutterApi {
12+
private final InstanceManager instanceManager;
13+
14+
public CameraSelectorFlutterApiImpl(
15+
BinaryMessenger binaryMessenger, InstanceManager instanceManager) {
16+
super(binaryMessenger);
17+
this.instanceManager = instanceManager;
18+
}
19+
20+
void create(CameraSelector cameraSelector, Long lensFacing, Reply<Void> reply) {
21+
create(instanceManager.addHostCreatedInstance(cameraSelector), lensFacing, reply);
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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 androidx.annotation.NonNull;
8+
import androidx.annotation.VisibleForTesting;
9+
import androidx.camera.core.CameraInfo;
10+
import androidx.camera.core.CameraSelector;
11+
import io.flutter.plugin.common.BinaryMessenger;
12+
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraSelectorHostApi;
13+
import java.util.ArrayList;
14+
import java.util.List;
15+
16+
public class CameraSelectorHostApiImpl implements CameraSelectorHostApi {
17+
private final BinaryMessenger binaryMessenger;
18+
private final InstanceManager instanceManager;
19+
20+
@VisibleForTesting public CameraXProxy cameraXProxy = new CameraXProxy();
21+
22+
public CameraSelectorHostApiImpl(
23+
BinaryMessenger binaryMessenger, InstanceManager instanceManager) {
24+
this.binaryMessenger = binaryMessenger;
25+
this.instanceManager = instanceManager;
26+
}
27+
28+
@Override
29+
public void create(@NonNull Long identifier, Long lensFacing) {
30+
CameraSelector.Builder cameraSelectorBuilder = cameraXProxy.createCameraSelectorBuilder();
31+
CameraSelector cameraSelector;
32+
33+
if (lensFacing != null) {
34+
cameraSelector = cameraSelectorBuilder.requireLensFacing(Math.toIntExact(lensFacing)).build();
35+
} else {
36+
cameraSelector = cameraSelectorBuilder.build();
37+
}
38+
39+
instanceManager.addDartCreatedInstance(cameraSelector, identifier);
40+
}
41+
42+
@Override
43+
public List<Long> filter(@NonNull Long identifier, @NonNull List<Long> cameraInfoIds) {
44+
CameraSelector cameraSelector = (CameraSelector) instanceManager.getInstance(identifier);
45+
List<CameraInfo> cameraInfosForFilter = new ArrayList<CameraInfo>();
46+
47+
for (Number cameraInfoAsNumber : cameraInfoIds) {
48+
Long cameraInfoId = cameraInfoAsNumber.longValue();
49+
50+
CameraInfo cameraInfo = (CameraInfo) instanceManager.getInstance(cameraInfoId);
51+
cameraInfosForFilter.add(cameraInfo);
52+
}
53+
54+
List<CameraInfo> filteredCameraInfos = cameraSelector.filter(cameraInfosForFilter);
55+
final CameraInfoFlutterApiImpl cameraInfoFlutterApiImpl =
56+
new CameraInfoFlutterApiImpl(binaryMessenger, instanceManager);
57+
List<Long> filteredCameraInfosIds = new ArrayList<Long>();
58+
59+
for (CameraInfo cameraInfo : filteredCameraInfos) {
60+
cameraInfoFlutterApiImpl.create(cameraInfo, result -> {});
61+
Long filteredCameraInfoId = instanceManager.getIdentifierForStrongReference(cameraInfo);
62+
filteredCameraInfosIds.add(filteredCameraInfoId);
63+
}
64+
65+
return filteredCameraInfosIds;
66+
}
67+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
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 androidx.camera.core.CameraSelector;
8+
9+
public class CameraXProxy {
10+
public CameraSelector.Builder createCameraSelectorBuilder() {
11+
return new CameraSelector.Builder();
12+
}
13+
}

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

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@
88

99
import android.util.Log;
1010
import androidx.annotation.NonNull;
11+
import androidx.annotation.Nullable;
1112
import io.flutter.plugin.common.BasicMessageChannel;
1213
import io.flutter.plugin.common.BinaryMessenger;
1314
import io.flutter.plugin.common.MessageCodec;
1415
import io.flutter.plugin.common.StandardMessageCodec;
1516
import java.util.ArrayList;
1617
import java.util.Arrays;
1718
import java.util.HashMap;
19+
import java.util.List;
1820
import java.util.Map;
1921

2022
/** Generated class from Pigeon. */
@@ -187,6 +189,128 @@ public void create(@NonNull Long identifierArg, Reply<Void> callback) {
187189
}
188190
}
189191

192+
private static class CameraSelectorHostApiCodec extends StandardMessageCodec {
193+
public static final CameraSelectorHostApiCodec INSTANCE = new CameraSelectorHostApiCodec();
194+
195+
private CameraSelectorHostApiCodec() {}
196+
}
197+
198+
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
199+
public interface CameraSelectorHostApi {
200+
void create(@NonNull Long identifier, @Nullable Long lensFacing);
201+
202+
@NonNull
203+
List<Long> filter(@NonNull Long identifier, @NonNull List<Long> cameraInfoIds);
204+
205+
/** The codec used by CameraSelectorHostApi. */
206+
static MessageCodec<Object> getCodec() {
207+
return CameraSelectorHostApiCodec.INSTANCE;
208+
}
209+
210+
/**
211+
* Sets up an instance of `CameraSelectorHostApi` to handle messages through the
212+
* `binaryMessenger`.
213+
*/
214+
static void setup(BinaryMessenger binaryMessenger, CameraSelectorHostApi api) {
215+
{
216+
BasicMessageChannel<Object> channel =
217+
new BasicMessageChannel<>(
218+
binaryMessenger, "dev.flutter.pigeon.CameraSelectorHostApi.create", getCodec());
219+
if (api != null) {
220+
channel.setMessageHandler(
221+
(message, reply) -> {
222+
Map<String, Object> wrapped = new HashMap<>();
223+
try {
224+
ArrayList<Object> args = (ArrayList<Object>) message;
225+
Number identifierArg = (Number) args.get(0);
226+
if (identifierArg == null) {
227+
throw new NullPointerException("identifierArg unexpectedly null.");
228+
}
229+
Number lensFacingArg = (Number) args.get(1);
230+
api.create(
231+
(identifierArg == null) ? null : identifierArg.longValue(),
232+
(lensFacingArg == null) ? null : lensFacingArg.longValue());
233+
wrapped.put("result", null);
234+
} catch (Error | RuntimeException exception) {
235+
wrapped.put("error", wrapError(exception));
236+
}
237+
reply.reply(wrapped);
238+
});
239+
} else {
240+
channel.setMessageHandler(null);
241+
}
242+
}
243+
{
244+
BasicMessageChannel<Object> channel =
245+
new BasicMessageChannel<>(
246+
binaryMessenger, "dev.flutter.pigeon.CameraSelectorHostApi.filter", getCodec());
247+
if (api != null) {
248+
channel.setMessageHandler(
249+
(message, reply) -> {
250+
Map<String, Object> wrapped = new HashMap<>();
251+
try {
252+
ArrayList<Object> args = (ArrayList<Object>) message;
253+
Number identifierArg = (Number) args.get(0);
254+
if (identifierArg == null) {
255+
throw new NullPointerException("identifierArg unexpectedly null.");
256+
}
257+
List<Long> cameraInfoIdsArg = (List<Long>) args.get(1);
258+
if (cameraInfoIdsArg == null) {
259+
throw new NullPointerException("cameraInfoIdsArg unexpectedly null.");
260+
}
261+
List<Long> output =
262+
api.filter(
263+
(identifierArg == null) ? null : identifierArg.longValue(),
264+
cameraInfoIdsArg);
265+
wrapped.put("result", output);
266+
} catch (Error | RuntimeException exception) {
267+
wrapped.put("error", wrapError(exception));
268+
}
269+
reply.reply(wrapped);
270+
});
271+
} else {
272+
channel.setMessageHandler(null);
273+
}
274+
}
275+
}
276+
}
277+
278+
private static class CameraSelectorFlutterApiCodec extends StandardMessageCodec {
279+
public static final CameraSelectorFlutterApiCodec INSTANCE =
280+
new CameraSelectorFlutterApiCodec();
281+
282+
private CameraSelectorFlutterApiCodec() {}
283+
}
284+
285+
/** Generated class from Pigeon that represents Flutter messages that can be called from Java. */
286+
public static class CameraSelectorFlutterApi {
287+
private final BinaryMessenger binaryMessenger;
288+
289+
public CameraSelectorFlutterApi(BinaryMessenger argBinaryMessenger) {
290+
this.binaryMessenger = argBinaryMessenger;
291+
}
292+
293+
public interface Reply<T> {
294+
void reply(T reply);
295+
}
296+
297+
static MessageCodec<Object> getCodec() {
298+
return CameraSelectorFlutterApiCodec.INSTANCE;
299+
}
300+
301+
public void create(
302+
@NonNull Long identifierArg, @Nullable Long lensFacingArg, Reply<Void> callback) {
303+
BasicMessageChannel<Object> channel =
304+
new BasicMessageChannel<>(
305+
binaryMessenger, "dev.flutter.pigeon.CameraSelectorFlutterApi.create", getCodec());
306+
channel.send(
307+
new ArrayList<Object>(Arrays.asList(identifierArg, lensFacingArg)),
308+
channelReply -> {
309+
callback.reply(null);
310+
});
311+
}
312+
}
313+
190314
private static Map<String, Object> wrapError(Throwable exception) {
191315
Map<String, Object> errorMap = new HashMap<>();
192316
errorMap.put("message", exception.toString());

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,16 @@ public void addDartCreatedInstance(Object instance, long identifier) {
122122
/**
123123
* Adds a new instance that was instantiated from the host platform.
124124
*
125+
* <p>If an instance has already been added, the identifier of the instance will be returned.
126+
*
125127
* @param instance the instance to be stored.
126128
* @return the unique identifier stored with instance.
127129
*/
128130
public long addHostCreatedInstance(Object instance) {
129131
assertManagerIsNotClosed();
132+
if (containsInstance(instance)) {
133+
return getIdentifierForStrongReference(instance);
134+
}
130135
final long identifier = nextIdentifier++;
131136
addInstance(instance, identifier);
132137
return identifier;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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 static org.junit.Assert.assertEquals;
8+
import static org.mockito.ArgumentMatchers.any;
9+
import static org.mockito.ArgumentMatchers.eq;
10+
import static org.mockito.Mockito.mock;
11+
import static org.mockito.Mockito.spy;
12+
import static org.mockito.Mockito.verify;
13+
import static org.mockito.Mockito.when;
14+
15+
import androidx.camera.core.CameraInfo;
16+
import androidx.camera.core.CameraSelector;
17+
import io.flutter.plugin.common.BinaryMessenger;
18+
import java.util.Arrays;
19+
import java.util.List;
20+
import java.util.Objects;
21+
import org.junit.After;
22+
import org.junit.Before;
23+
import org.junit.Rule;
24+
import org.junit.Test;
25+
import org.mockito.Mock;
26+
import org.mockito.junit.MockitoJUnit;
27+
import org.mockito.junit.MockitoRule;
28+
29+
public class CameraSelectorTest {
30+
@Rule public MockitoRule mockitoRule = MockitoJUnit.rule();
31+
32+
@Mock public CameraSelector mockCameraSelector;
33+
@Mock public BinaryMessenger mockBinaryMessenger;
34+
35+
InstanceManager testInstanceManager;
36+
37+
@Before
38+
public void setUp() {
39+
testInstanceManager = InstanceManager.open(identifier -> {});
40+
}
41+
42+
@After
43+
public void tearDown() {
44+
testInstanceManager.close();
45+
}
46+
47+
@Test
48+
public void createTest() {
49+
final CameraSelectorHostApiImpl cameraSelectorHostApi =
50+
new CameraSelectorHostApiImpl(mockBinaryMessenger, testInstanceManager);
51+
final CameraXProxy mockCameraXProxy = mock(CameraXProxy.class);
52+
final CameraSelector.Builder mockCameraSelectorBuilder = mock(CameraSelector.Builder.class);
53+
54+
cameraSelectorHostApi.cameraXProxy = mockCameraXProxy;
55+
when(mockCameraXProxy.createCameraSelectorBuilder()).thenReturn(mockCameraSelectorBuilder);
56+
57+
when(mockCameraSelectorBuilder.requireLensFacing(1)).thenReturn(mockCameraSelectorBuilder);
58+
when(mockCameraSelectorBuilder.build()).thenReturn(mockCameraSelector);
59+
60+
cameraSelectorHostApi.create(0L, 1L);
61+
62+
verify(mockCameraSelectorBuilder).requireLensFacing(CameraSelector.LENS_FACING_BACK);
63+
assertEquals(testInstanceManager.getInstance(0L), mockCameraSelector);
64+
}
65+
66+
@Test
67+
public void filterTest() {
68+
final CameraSelectorHostApiImpl cameraSelectorHostApi =
69+
new CameraSelectorHostApiImpl(mockBinaryMessenger, testInstanceManager);
70+
final CameraInfo cameraInfo = mock(CameraInfo.class);
71+
final List<CameraInfo> cameraInfosForFilter = Arrays.asList(cameraInfo);
72+
final List<Long> cameraInfosIds = Arrays.asList(1L);
73+
74+
testInstanceManager.addDartCreatedInstance(mockCameraSelector, 0);
75+
testInstanceManager.addDartCreatedInstance(cameraInfo, 1);
76+
77+
when(mockCameraSelector.filter(cameraInfosForFilter)).thenReturn(cameraInfosForFilter);
78+
79+
assertEquals(
80+
cameraSelectorHostApi.filter(0L, cameraInfosIds),
81+
Arrays.asList(testInstanceManager.getIdentifierForStrongReference(cameraInfo)));
82+
verify(mockCameraSelector).filter(cameraInfosForFilter);
83+
}
84+
85+
@Test
86+
public void flutterApiCreateTest() {
87+
final CameraSelectorFlutterApiImpl spyFlutterApi =
88+
spy(new CameraSelectorFlutterApiImpl(mockBinaryMessenger, testInstanceManager));
89+
90+
spyFlutterApi.create(mockCameraSelector, 0L, reply -> {});
91+
92+
final long identifier =
93+
Objects.requireNonNull(
94+
testInstanceManager.getIdentifierForStrongReference(mockCameraSelector));
95+
verify(spyFlutterApi).create(eq(identifier), eq(0L), any());
96+
}
97+
}

0 commit comments

Comments
 (0)