Skip to content

Commit 435c46f

Browse files
[camera] availableCameras() implementation for CameraX re-write (flutter-team-archive#6945)
* Adding availableCameras to camerax camera implementation * Marking loop variables as nullable in availableCameras * adding testing for android_camerax availableCameras() * marking variable final * removing commented import * cleaning up tests for availableCamera camerax re-write * chaning import * respondingt to review comments * fixing tests * fixing presubmit issues * adding changelog message * formatting changes for presubmit tests * another formatting change for presubmit checks --------- Co-authored-by: Gray Mackall <mackall@google.com>
1 parent 7031110 commit 435c46f

5 files changed

Lines changed: 221 additions & 6 deletions

File tree

‎packages/camera/camera_android_camerax/CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@
99
* Bump CameraX version to 1.3.0-alpha03 and Kotlin version to 1.8.0.
1010
* Changes instance manager to allow the separate creation of identical objects.
1111
* Adds Preview and Surface classes, along with other methods needed to implement camera preview.
12+
* Adds implementation of availableCameras()

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

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,9 @@ public List<Long> filter(@NonNull Long identifier, @NonNull List<Long> cameraInf
5555
}
5656

5757
List<CameraInfo> filteredCameraInfos = cameraSelector.filter(cameraInfosForFilter);
58-
final CameraInfoFlutterApiImpl cameraInfoFlutterApiImpl =
59-
new CameraInfoFlutterApiImpl(binaryMessenger, instanceManager);
6058
List<Long> filteredCameraInfosIds = new ArrayList<Long>();
6159

6260
for (CameraInfo cameraInfo : filteredCameraInfos) {
63-
if (!instanceManager.containsInstance(cameraInfo)) {
64-
cameraInfoFlutterApiImpl.create(cameraInfo, result -> {});
65-
}
6661
Long filteredCameraInfoId = instanceManager.getIdentifierForStrongReference(cameraInfo);
6762
filteredCameraInfosIds.add(filteredCameraInfoId);
6863
}

‎packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart‎

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55
import 'dart:async';
66

77
import 'package:camera_platform_interface/camera_platform_interface.dart';
8+
import 'package:flutter/foundation.dart';
9+
10+
import 'camera_info.dart';
11+
import 'camera_selector.dart';
12+
import 'process_camera_provider.dart';
813

914
/// The Android implementation of [CameraPlatform] that uses the CameraX library.
1015
class AndroidCameraCameraX extends CameraPlatform {
@@ -13,9 +18,56 @@ class AndroidCameraCameraX extends CameraPlatform {
1318
CameraPlatform.instance = AndroidCameraCameraX();
1419
}
1520

21+
/// ProcessCameraProvider used to get list of cameras. Visible only for testing.
22+
@visibleForTesting
23+
ProcessCameraProvider? processCameraProvider;
24+
25+
/// Camera selector used to determine which CameraInfos are back cameras.
26+
@visibleForTesting
27+
CameraSelector? backCameraSelector = CameraSelector.getDefaultBackCamera();
28+
29+
/// Camera selector used to determine which CameraInfos are back cameras.
30+
@visibleForTesting
31+
CameraSelector? frontCameraSelector = CameraSelector.getDefaultFrontCamera();
32+
1633
/// Returns list of all available cameras and their descriptions.
1734
@override
1835
Future<List<CameraDescription>> availableCameras() async {
19-
throw UnimplementedError('availableCameras() is not implemented.');
36+
final List<CameraDescription> cameraDescriptions = <CameraDescription>[];
37+
38+
processCameraProvider ??= await ProcessCameraProvider.getInstance();
39+
final List<CameraInfo> cameraInfos =
40+
await processCameraProvider!.getAvailableCameraInfos();
41+
42+
CameraLensDirection? cameraLensDirection;
43+
int cameraCount = 0;
44+
int? cameraSensorOrientation;
45+
String? cameraName;
46+
47+
for (final CameraInfo cameraInfo in cameraInfos) {
48+
// Determine the lens direction by filtering the CameraInfo
49+
// TODO(gmackall): replace this with call to CameraInfo.getLensFacing when changes containing that method are available
50+
if ((await backCameraSelector!.filter(<CameraInfo>[cameraInfo]))
51+
.isNotEmpty) {
52+
cameraLensDirection = CameraLensDirection.back;
53+
} else if ((await frontCameraSelector!.filter(<CameraInfo>[cameraInfo]))
54+
.isNotEmpty) {
55+
cameraLensDirection = CameraLensDirection.front;
56+
} else {
57+
//Skip this CameraInfo as its lens direction is unknown
58+
continue;
59+
}
60+
61+
cameraSensorOrientation = await cameraInfo.getSensorRotationDegrees();
62+
cameraName = 'Camera $cameraCount';
63+
cameraCount++;
64+
65+
cameraDescriptions.add(CameraDescription(
66+
name: cameraName,
67+
lensDirection: cameraLensDirection,
68+
sensorOrientation: cameraSensorOrientation));
69+
}
70+
71+
return cameraDescriptions;
2072
}
2173
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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+
import 'package:camera_android_camerax/camera_android_camerax.dart';
6+
import 'package:camera_android_camerax/src/camera_info.dart';
7+
import 'package:camera_android_camerax/src/camera_selector.dart';
8+
import 'package:camera_android_camerax/src/process_camera_provider.dart';
9+
import 'package:camera_platform_interface/camera_platform_interface.dart';
10+
import 'package:flutter_test/flutter_test.dart';
11+
import 'package:mockito/annotations.dart';
12+
import 'package:mockito/mockito.dart';
13+
14+
import 'android_camera_camerax_test.mocks.dart';
15+
16+
@GenerateMocks(<Type>[
17+
ProcessCameraProvider,
18+
CameraSelector,
19+
CameraInfo,
20+
])
21+
void main() {
22+
TestWidgetsFlutterBinding.ensureInitialized();
23+
24+
test('Should fetch CameraDescription instances for available cameras',
25+
() async {
26+
// Arrange
27+
final List<dynamic> returnData = <dynamic>[
28+
<String, dynamic>{
29+
'name': 'Camera 0',
30+
'lensFacing': 'back',
31+
'sensorOrientation': 0
32+
},
33+
<String, dynamic>{
34+
'name': 'Camera 1',
35+
'lensFacing': 'front',
36+
'sensorOrientation': 90
37+
}
38+
];
39+
40+
//Create mocks to use
41+
final MockProcessCameraProvider mockProcessCameraProvider =
42+
MockProcessCameraProvider();
43+
final MockCameraSelector mockBackCameraSelector = MockCameraSelector();
44+
final MockCameraSelector mockFrontCameraSelector = MockCameraSelector();
45+
final MockCameraInfo mockFrontCameraInfo = MockCameraInfo();
46+
final MockCameraInfo mockBackCameraInfo = MockCameraInfo();
47+
AndroidCameraCameraX.registerWith();
48+
49+
//Set class level ProcessCameraProvider and camera selectors to created mocks
50+
final AndroidCameraCameraX androidCameraCamerax = AndroidCameraCameraX();
51+
androidCameraCamerax.backCameraSelector = mockBackCameraSelector;
52+
androidCameraCamerax.frontCameraSelector = mockFrontCameraSelector;
53+
androidCameraCamerax.processCameraProvider = mockProcessCameraProvider;
54+
55+
//Mock calls to native platform
56+
when(mockProcessCameraProvider.getAvailableCameraInfos()).thenAnswer(
57+
(_) async => <MockCameraInfo>[mockBackCameraInfo, mockFrontCameraInfo]);
58+
when(mockBackCameraSelector.filter(<MockCameraInfo>[mockFrontCameraInfo]))
59+
.thenAnswer((_) async => <MockCameraInfo>[]);
60+
when(mockBackCameraSelector.filter(<MockCameraInfo>[mockBackCameraInfo]))
61+
.thenAnswer((_) async => <MockCameraInfo>[mockBackCameraInfo]);
62+
when(mockFrontCameraSelector.filter(<MockCameraInfo>[mockBackCameraInfo]))
63+
.thenAnswer((_) async => <MockCameraInfo>[]);
64+
when(mockFrontCameraSelector.filter(<MockCameraInfo>[mockFrontCameraInfo]))
65+
.thenAnswer((_) async => <MockCameraInfo>[mockFrontCameraInfo]);
66+
when(mockBackCameraInfo.getSensorRotationDegrees())
67+
.thenAnswer((_) async => 0);
68+
when(mockFrontCameraInfo.getSensorRotationDegrees())
69+
.thenAnswer((_) async => 90);
70+
71+
final List<CameraDescription> cameraDescriptions =
72+
await androidCameraCamerax.availableCameras();
73+
74+
expect(cameraDescriptions.length, returnData.length);
75+
for (int i = 0; i < returnData.length; i++) {
76+
final Map<String, Object?> typedData =
77+
(returnData[i] as Map<dynamic, dynamic>).cast<String, Object?>();
78+
final CameraDescription cameraDescription = CameraDescription(
79+
name: typedData['name']! as String,
80+
lensDirection: (typedData['lensFacing']! as String) == 'front'
81+
? CameraLensDirection.front
82+
: CameraLensDirection.back,
83+
sensorOrientation: typedData['sensorOrientation']! as int,
84+
);
85+
expect(cameraDescriptions[i], cameraDescription);
86+
}
87+
});
88+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Mocks generated by Mockito 5.3.2 from annotations
2+
// in camera_android_camerax/test/android_camera_camerax_test.dart.
3+
// Do not manually edit this file.
4+
5+
// ignore_for_file: no_leading_underscores_for_library_prefixes
6+
import 'dart:async' as _i3;
7+
8+
import 'package:camera_android_camerax/src/camera_info.dart' as _i4;
9+
import 'package:camera_android_camerax/src/camera_selector.dart' as _i5;
10+
import 'package:camera_android_camerax/src/process_camera_provider.dart' as _i2;
11+
import 'package:mockito/mockito.dart' as _i1;
12+
13+
// ignore_for_file: type=lint
14+
// ignore_for_file: avoid_redundant_argument_values
15+
// ignore_for_file: avoid_setters_without_getters
16+
// ignore_for_file: comment_references
17+
// ignore_for_file: implementation_imports
18+
// ignore_for_file: invalid_use_of_visible_for_testing_member
19+
// ignore_for_file: prefer_const_constructors
20+
// ignore_for_file: unnecessary_parenthesis
21+
// ignore_for_file: camel_case_types
22+
// ignore_for_file: subtype_of_sealed_class
23+
24+
/// A class which mocks [ProcessCameraProvider].
25+
///
26+
/// See the documentation for Mockito's code generation for more information.
27+
class MockProcessCameraProvider extends _i1.Mock
28+
implements _i2.ProcessCameraProvider {
29+
MockProcessCameraProvider() {
30+
_i1.throwOnMissingStub(this);
31+
}
32+
33+
@override
34+
_i3.Future<List<_i4.CameraInfo>> getAvailableCameraInfos() =>
35+
(super.noSuchMethod(
36+
Invocation.method(
37+
#getAvailableCameraInfos,
38+
[],
39+
),
40+
returnValue: _i3.Future<List<_i4.CameraInfo>>.value(<_i4.CameraInfo>[]),
41+
) as _i3.Future<List<_i4.CameraInfo>>);
42+
}
43+
44+
/// A class which mocks [CameraSelector].
45+
///
46+
/// See the documentation for Mockito's code generation for more information.
47+
class MockCameraSelector extends _i1.Mock implements _i5.CameraSelector {
48+
MockCameraSelector() {
49+
_i1.throwOnMissingStub(this);
50+
}
51+
52+
@override
53+
_i3.Future<List<_i4.CameraInfo>> filter(List<_i4.CameraInfo>? cameraInfos) =>
54+
(super.noSuchMethod(
55+
Invocation.method(
56+
#filter,
57+
[cameraInfos],
58+
),
59+
returnValue: _i3.Future<List<_i4.CameraInfo>>.value(<_i4.CameraInfo>[]),
60+
) as _i3.Future<List<_i4.CameraInfo>>);
61+
}
62+
63+
/// A class which mocks [CameraInfo].
64+
///
65+
/// See the documentation for Mockito's code generation for more information.
66+
class MockCameraInfo extends _i1.Mock implements _i4.CameraInfo {
67+
MockCameraInfo() {
68+
_i1.throwOnMissingStub(this);
69+
}
70+
71+
@override
72+
_i3.Future<int> getSensorRotationDegrees() => (super.noSuchMethod(
73+
Invocation.method(
74+
#getSensorRotationDegrees,
75+
[],
76+
),
77+
returnValue: _i3.Future<int>.value(0),
78+
) as _i3.Future<int>);
79+
}

0 commit comments

Comments
 (0)