diff --git a/packages/camera/lib/new/camera.dart b/packages/camera/lib/new/camera.dart index ab135079d2dd..08b085f8e2c8 100644 --- a/packages/camera/lib/new/camera.dart +++ b/packages/camera/lib/new/camera.dart @@ -6,3 +6,5 @@ export 'src/camera_controller.dart'; export 'src/camera_testing.dart'; export 'src/common/camera_interface.dart'; export 'src/common/native_texture.dart'; +export 'src/support_android/camera.dart'; +export 'src/support_android/camera_info.dart'; diff --git a/packages/camera/lib/new/src/support_android/camera.dart b/packages/camera/lib/new/src/support_android/camera.dart new file mode 100644 index 000000000000..d78753d24355 --- /dev/null +++ b/packages/camera/lib/new/src/support_android/camera.dart @@ -0,0 +1,120 @@ +// 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. + +import 'dart:async'; + +import '../common/camera_channel.dart'; +import '../common/camera_mixins.dart'; +import '../common/native_texture.dart'; +import 'camera_info.dart'; + +/// The Camera class used to set image capture settings, start/stop preview, snap pictures, and retrieve frames for encoding for video. +/// +/// This class is a client for the Camera service, which manages the actual +/// camera hardware. +/// +/// This exposes the deprecated Android +/// [Camera](https://developer.android.com/reference/android/hardware/Camera) +/// API. This should only be used with Android sdk versions less than 21. +class Camera with NativeMethodCallHandler { + Camera._(); + + bool _isClosed = false; + + /// Retrieves the number of physical cameras available on this device. + static Future getNumberOfCameras() { + return CameraChannel.channel.invokeMethod( + 'Camera#getNumberOfCameras', + ); + } + + /// Creates a new [Camera] object to access a particular hardware camera. + /// + /// If the same camera is opened by other applications, this will throw a + /// [PlatformException]. + /// + /// You must call [release] when you are done using the camera, otherwise it + /// will remain locked and be unavailable to other applications. + /// + /// Your application should only have one [Camera] object active at a time for + /// a particular hardware camera. + static Camera open(int cameraId) { + final Camera camera = Camera._(); + + CameraChannel.channel.invokeMethod( + 'Camera#open', + {'cameraId': cameraId, 'cameraHandle': camera.handle}, + ); + + return camera; + } + + /// Retrieves information about a particular camera. + /// + /// If [getNumberOfCameras] returns N, the valid id is 0 to N-1. + static Future getCameraInfo(int cameraId) async { + final Map infoMap = + await CameraChannel.channel.invokeMapMethod( + 'Camera#getCameraInfo', + {'cameraId': cameraId}, + ); + + return CameraInfo.fromMap(infoMap); + } + + /// Sets the [NativeTexture] to be used for live preview. + /// + /// This method must be called before [startPreview]. + /// + /// The one exception is that if the preview native texture is not set (or + /// set to null) before [startPreview] is called, then this method may be + /// called once with a non-null parameter to set the preview texture. + /// (This allows camera setup and surface creation to happen in parallel, + /// saving time.) The preview native texture may not otherwise change while + /// preview is running. + set previewTexture(NativeTexture texture) { + assert(!_isClosed); + + CameraChannel.channel.invokeMethod( + 'Camera#previewTexture', + {'handle': handle, 'nativeTexture': texture?.asMap()}, + ); + } + + /// Starts capturing and drawing preview frames to the screen. + /// + /// Preview will not actually start until a surface is supplied with + /// [previewTexture]. + Future startPreview() { + assert(!_isClosed); + + return CameraChannel.channel.invokeMethod( + 'Camera#startPreview', + {'handle': handle}, + ); + } + + /// Stops capturing and drawing preview frames to the [previewTexture], and resets the camera for a future call to [startPreview]. + Future stopPreview() { + assert(!_isClosed); + + return CameraChannel.channel.invokeMethod( + 'Camera#stopPreview', + {'handle': handle}, + ); + } + + /// Disconnects and releases the Camera object resources. + /// + /// You must call this as soon as you're done with the Camera object. + Future release() { + if (_isClosed) return Future.value(); + + _isClosed = true; + return CameraChannel.channel.invokeMethod( + 'Camera#release', + {'handle': handle}, + ); + } +} diff --git a/packages/camera/lib/new/src/support_android/camera_info.dart b/packages/camera/lib/new/src/support_android/camera_info.dart new file mode 100644 index 000000000000..033fecfea6d9 --- /dev/null +++ b/packages/camera/lib/new/src/support_android/camera_info.dart @@ -0,0 +1,68 @@ +// 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. + +import 'package:flutter/foundation.dart'; + +import '../common/camera_interface.dart'; + +/// The direction that the camera faces. +enum Facing { back, front } + +/// Information about a camera. +/// +/// Retrieved from [Camera.getCameraInfo]. +class CameraInfo implements CameraDescription { + const CameraInfo({ + @required this.id, + @required this.facing, + @required this.orientation, + }) : assert(id != null), + assert(facing != null), + assert(orientation != null); + + factory CameraInfo.fromMap(Map map) { + return CameraInfo( + id: map['id'], + orientation: map['orientation'], + facing: Facing.values.firstWhere( + (Facing facing) => facing.toString() == map['facing'], + ), + ); + } + + /// Identifier for a particular camera. + final int id; + + /// The direction that the camera faces. + final Facing facing; + + /// The orientation of the camera image. + /// + /// The value is the angle that the camera image needs to be rotated clockwise + /// so it shows correctly on the display in its natural orientation. + /// It should be 0, 90, 180, or 270. + /// + /// For example, suppose a device has a naturally tall screen. The back-facing + /// camera sensor is mounted in landscape. You are looking at the screen. If + /// the top side of the camera sensor is aligned with the right edge of the + /// screen in natural orientation, the value should be 90. If the top side of + /// a front-facing camera sensor is aligned with the right of the screen, the + /// value should be 270. + final int orientation; + + @override + String get name => id.toString(); + + @override + LensDirection get direction { + switch (facing) { + case Facing.front: + return LensDirection.front; + case Facing.back: + return LensDirection.back; + } + + return null; + } +} diff --git a/packages/camera/test/support_android/support_android_test.dart b/packages/camera/test/support_android/support_android_test.dart new file mode 100644 index 000000000000..114f56ab3348 --- /dev/null +++ b/packages/camera/test/support_android/support_android_test.dart @@ -0,0 +1,141 @@ +// 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. + +import 'package:camera/new/src/support_android/camera_info.dart'; +import 'package:camera/new/src/support_android/camera.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:camera/new/src/camera_testing.dart'; + +void main() { + group('Support Android Camera', () { + group('$Camera', () { + final List log = []; + setUpAll(() { + CameraTesting.channel + .setMockMethodCallHandler((MethodCall methodCall) async { + log.add(methodCall); + switch (methodCall.method) { + case 'Camera#getNumberOfCameras': + return 3; + case 'Camera#open': + return null; + case 'Camera#getCameraInfo': + return { + 'id': 3, + 'orientation': 90, + 'facing': Facing.front.toString(), + }; + case 'Camera#startPreview': + return null; + case 'Camera#stopPreview': + return null; + case 'Camera#release': + return null; + } + + throw ArgumentError.value( + methodCall.method, + 'methodCall.method', + 'No method found for', + ); + }); + }); + + setUp(() { + log.clear(); + CameraTesting.nextHandle = 0; + }); + + test('getNumberOfCameras', () async { + final int result = await Camera.getNumberOfCameras(); + + expect(result, 3); + expect(log, [ + isMethodCall( + '$Camera#getNumberOfCameras', + arguments: null, + ) + ]); + }); + + test('open', () { + Camera.open(14); + + expect(log, [ + isMethodCall( + '$Camera#open', + arguments: { + 'cameraId': 14, + 'cameraHandle': 0, + }, + ) + ]); + }); + + test('getCameraInfo', () async { + final CameraInfo info = await Camera.getCameraInfo(14); + + expect(info.id, 3); + expect(info.orientation, 90); + expect(info.facing, Facing.front); + + expect(log, [ + isMethodCall( + '$Camera#getCameraInfo', + arguments: {'cameraId': 14}, + ) + ]); + }); + + test('startPreview', () { + final Camera camera = Camera.open(0); + + log.clear(); + camera.startPreview(); + + expect(log, [ + isMethodCall( + '$Camera#startPreview', + arguments: { + 'handle': 0, + }, + ) + ]); + }); + + test('stopPreview', () { + final Camera camera = Camera.open(0); + + log.clear(); + camera.stopPreview(); + + expect(log, [ + isMethodCall( + '$Camera#stopPreview', + arguments: { + 'handle': 0, + }, + ) + ]); + }); + + test('release', () { + final Camera camera = Camera.open(0); + + log.clear(); + camera.release(); + + expect(log, [ + isMethodCall( + '$Camera#release', + arguments: { + 'handle': 0, + }, + ) + ]); + }); + }); + }); +}