diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md index 080240a64f42..50fdf586c5e7 100644 --- a/packages/camera/camera_android_camerax/CHANGELOG.md +++ b/packages/camera/camera_android_camerax/CHANGELOG.md @@ -9,3 +9,4 @@ * Bump CameraX version to 1.3.0-alpha03 and Kotlin version to 1.8.0. * Changes instance manager to allow the separate creation of identical objects. * Adds Preview and Surface classes, along with other methods needed to implement camera preview. +* Adds implementation of availableCameras() diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraSelectorHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraSelectorHostApiImpl.java index 0528d584d26e..603f7cf78def 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraSelectorHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraSelectorHostApiImpl.java @@ -55,14 +55,9 @@ public List filter(@NonNull Long identifier, @NonNull List cameraInf } List filteredCameraInfos = cameraSelector.filter(cameraInfosForFilter); - final CameraInfoFlutterApiImpl cameraInfoFlutterApiImpl = - new CameraInfoFlutterApiImpl(binaryMessenger, instanceManager); List filteredCameraInfosIds = new ArrayList(); for (CameraInfo cameraInfo : filteredCameraInfos) { - if (!instanceManager.containsInstance(cameraInfo)) { - cameraInfoFlutterApiImpl.create(cameraInfo, result -> {}); - } Long filteredCameraInfoId = instanceManager.getIdentifierForStrongReference(cameraInfo); filteredCameraInfosIds.add(filteredCameraInfoId); } diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart index f03273861793..300d6717fb46 100644 --- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart +++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart @@ -5,6 +5,11 @@ import 'dart:async'; import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:flutter/foundation.dart'; + +import 'camera_info.dart'; +import 'camera_selector.dart'; +import 'process_camera_provider.dart'; /// The Android implementation of [CameraPlatform] that uses the CameraX library. class AndroidCameraCameraX extends CameraPlatform { @@ -13,9 +18,56 @@ class AndroidCameraCameraX extends CameraPlatform { CameraPlatform.instance = AndroidCameraCameraX(); } + /// ProcessCameraProvider used to get list of cameras. Visible only for testing. + @visibleForTesting + ProcessCameraProvider? processCameraProvider; + + /// Camera selector used to determine which CameraInfos are back cameras. + @visibleForTesting + CameraSelector? backCameraSelector = CameraSelector.getDefaultBackCamera(); + + /// Camera selector used to determine which CameraInfos are back cameras. + @visibleForTesting + CameraSelector? frontCameraSelector = CameraSelector.getDefaultFrontCamera(); + /// Returns list of all available cameras and their descriptions. @override Future> availableCameras() async { - throw UnimplementedError('availableCameras() is not implemented.'); + final List cameraDescriptions = []; + + processCameraProvider ??= await ProcessCameraProvider.getInstance(); + final List cameraInfos = + await processCameraProvider!.getAvailableCameraInfos(); + + CameraLensDirection? cameraLensDirection; + int cameraCount = 0; + int? cameraSensorOrientation; + String? cameraName; + + for (final CameraInfo cameraInfo in cameraInfos) { + // Determine the lens direction by filtering the CameraInfo + // TODO(gmackall): replace this with call to CameraInfo.getLensFacing when changes containing that method are available + if ((await backCameraSelector!.filter([cameraInfo])) + .isNotEmpty) { + cameraLensDirection = CameraLensDirection.back; + } else if ((await frontCameraSelector!.filter([cameraInfo])) + .isNotEmpty) { + cameraLensDirection = CameraLensDirection.front; + } else { + //Skip this CameraInfo as its lens direction is unknown + continue; + } + + cameraSensorOrientation = await cameraInfo.getSensorRotationDegrees(); + cameraName = 'Camera $cameraCount'; + cameraCount++; + + cameraDescriptions.add(CameraDescription( + name: cameraName, + lensDirection: cameraLensDirection, + sensorOrientation: cameraSensorOrientation)); + } + + return cameraDescriptions; } } diff --git a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart new file mode 100644 index 000000000000..8b16c64c6ede --- /dev/null +++ b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart @@ -0,0 +1,88 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:camera_android_camerax/camera_android_camerax.dart'; +import 'package:camera_android_camerax/src/camera_info.dart'; +import 'package:camera_android_camerax/src/camera_selector.dart'; +import 'package:camera_android_camerax/src/process_camera_provider.dart'; +import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'android_camera_camerax_test.mocks.dart'; + +@GenerateMocks([ + ProcessCameraProvider, + CameraSelector, + CameraInfo, +]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + test('Should fetch CameraDescription instances for available cameras', + () async { + // Arrange + final List returnData = [ + { + 'name': 'Camera 0', + 'lensFacing': 'back', + 'sensorOrientation': 0 + }, + { + 'name': 'Camera 1', + 'lensFacing': 'front', + 'sensorOrientation': 90 + } + ]; + + //Create mocks to use + final MockProcessCameraProvider mockProcessCameraProvider = + MockProcessCameraProvider(); + final MockCameraSelector mockBackCameraSelector = MockCameraSelector(); + final MockCameraSelector mockFrontCameraSelector = MockCameraSelector(); + final MockCameraInfo mockFrontCameraInfo = MockCameraInfo(); + final MockCameraInfo mockBackCameraInfo = MockCameraInfo(); + AndroidCameraCameraX.registerWith(); + + //Set class level ProcessCameraProvider and camera selectors to created mocks + final AndroidCameraCameraX androidCameraCamerax = AndroidCameraCameraX(); + androidCameraCamerax.backCameraSelector = mockBackCameraSelector; + androidCameraCamerax.frontCameraSelector = mockFrontCameraSelector; + androidCameraCamerax.processCameraProvider = mockProcessCameraProvider; + + //Mock calls to native platform + when(mockProcessCameraProvider.getAvailableCameraInfos()).thenAnswer( + (_) async => [mockBackCameraInfo, mockFrontCameraInfo]); + when(mockBackCameraSelector.filter([mockFrontCameraInfo])) + .thenAnswer((_) async => []); + when(mockBackCameraSelector.filter([mockBackCameraInfo])) + .thenAnswer((_) async => [mockBackCameraInfo]); + when(mockFrontCameraSelector.filter([mockBackCameraInfo])) + .thenAnswer((_) async => []); + when(mockFrontCameraSelector.filter([mockFrontCameraInfo])) + .thenAnswer((_) async => [mockFrontCameraInfo]); + when(mockBackCameraInfo.getSensorRotationDegrees()) + .thenAnswer((_) async => 0); + when(mockFrontCameraInfo.getSensorRotationDegrees()) + .thenAnswer((_) async => 90); + + final List cameraDescriptions = + await androidCameraCamerax.availableCameras(); + + expect(cameraDescriptions.length, returnData.length); + for (int i = 0; i < returnData.length; i++) { + final Map typedData = + (returnData[i] as Map).cast(); + final CameraDescription cameraDescription = CameraDescription( + name: typedData['name']! as String, + lensDirection: (typedData['lensFacing']! as String) == 'front' + ? CameraLensDirection.front + : CameraLensDirection.back, + sensorOrientation: typedData['sensorOrientation']! as int, + ); + expect(cameraDescriptions[i], cameraDescription); + } + }); +} diff --git a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart new file mode 100644 index 000000000000..44171eebda91 --- /dev/null +++ b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart @@ -0,0 +1,79 @@ +// Mocks generated by Mockito 5.3.2 from annotations +// in camera_android_camerax/test/android_camera_camerax_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i3; + +import 'package:camera_android_camerax/src/camera_info.dart' as _i4; +import 'package:camera_android_camerax/src/camera_selector.dart' as _i5; +import 'package:camera_android_camerax/src/process_camera_provider.dart' as _i2; +import 'package:mockito/mockito.dart' as _i1; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +/// A class which mocks [ProcessCameraProvider]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockProcessCameraProvider extends _i1.Mock + implements _i2.ProcessCameraProvider { + MockProcessCameraProvider() { + _i1.throwOnMissingStub(this); + } + + @override + _i3.Future> getAvailableCameraInfos() => + (super.noSuchMethod( + Invocation.method( + #getAvailableCameraInfos, + [], + ), + returnValue: _i3.Future>.value(<_i4.CameraInfo>[]), + ) as _i3.Future>); +} + +/// A class which mocks [CameraSelector]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCameraSelector extends _i1.Mock implements _i5.CameraSelector { + MockCameraSelector() { + _i1.throwOnMissingStub(this); + } + + @override + _i3.Future> filter(List<_i4.CameraInfo>? cameraInfos) => + (super.noSuchMethod( + Invocation.method( + #filter, + [cameraInfos], + ), + returnValue: _i3.Future>.value(<_i4.CameraInfo>[]), + ) as _i3.Future>); +} + +/// A class which mocks [CameraInfo]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCameraInfo extends _i1.Mock implements _i4.CameraInfo { + MockCameraInfo() { + _i1.throwOnMissingStub(this); + } + + @override + _i3.Future getSensorRotationDegrees() => (super.noSuchMethod( + Invocation.method( + #getSensorRotationDegrees, + [], + ), + returnValue: _i3.Future.value(0), + ) as _i3.Future); +}