[camera] Implement initialize and add documentation (#1838)

diff --git a/packages/camera/lib/new/src/camera_controller.dart b/packages/camera/lib/new/src/camera_controller.dart
index 4ae6b47..4296f39 100644
--- a/packages/camera/lib/new/src/camera_controller.dart
+++ b/packages/camera/lib/new/src/camera_controller.dart
@@ -12,14 +12,21 @@
 ///
 /// Use [CameraController.availableCameras] to get a list of available cameras.
 ///
-/// This class is used as a simple interface that works for Android and iOS.
+/// This class is used as a simple interface to control a camera on Android or
+/// iOS.
 ///
-/// When using iOS, simultaneously calling [start] on two [CameraController]s
-/// will throw a [PlatformException].
+/// Only one instance of [CameraController] can be active at a time. If you call
+/// [initialize] on a [CameraController] while another is active, the old
+/// controller will be disposed before initializing the new controller.
 ///
-/// When using Android, simultaneously calling [start] on two
-/// [CameraController]s may throw a [PlatformException] depending on the
-/// hardware resources of the device.
+/// Example using [CameraController]:
+///
+/// ```dart
+/// final List<CameraDescription> cameras = async CameraController.availableCameras();
+/// final CameraController controller = CameraController(description: cameras[0]);
+/// controller.initialize();
+/// controller.start();
+/// ```
 class CameraController {
   /// Default constructor.
   ///
@@ -28,7 +35,6 @@
   ///
   /// This will choose the best [CameraConfigurator] for the current device.
   factory CameraController({@required CameraDescription description}) {
-    assert(description != null);
     return CameraController._(
       description: description,
       configurator: _createDefaultConfigurator(description),
@@ -59,6 +65,14 @@
     );
   }
 
+  static const String _isNotInitializedMessage = 'Initialize was not called.';
+  static const String _isDisposedMessage = 'This controller has been disposed.';
+
+  // Keep only one active instance of CameraController.
+  static CameraController _instance;
+
+  bool _isDisposed = false;
+
   /// Details for the camera this controller accesses.
   final CameraDescription description;
 
@@ -68,6 +82,8 @@
   /// Api used by the [configurator].
   final CameraApi api;
 
+  bool get isDisposed => _isDisposed;
+
   /// Retrieves a list of available cameras for the current device.
   ///
   /// This will choose the best [CameraAPI] for the current device.
@@ -75,14 +91,54 @@
     throw UnimplementedError('$defaultTargetPlatform not supported');
   }
 
-  /// Begins the flow of data between the inputs and outputs connected the camera instance.
-  Future<void> start() => configurator.start();
+  /// Initializes the camera on the device.
+  ///
+  /// You must call [dispose] when you are done using the camera, otherwise it
+  /// will remain locked and be unavailable to other applications.
+  ///
+  /// Only one instance of [CameraController] can be active at a time. If you
+  /// call [initialize] on a [CameraController] while another is active, the old
+  /// controller will be disposed before initializing the new controller.
+  Future<void> initialize() {
+    if (_instance == this) {
+      return Future<void>.value();
+    }
 
-  /// Stops the flow of data between the inputs and outputs connected the camera instance.
-  Future<void> stop() => configurator.stop();
+    final Completer<void> completer = Completer<void>();
+
+    if (_instance != null) {
+      _instance
+          .dispose()
+          .then((_) => configurator.initialize())
+          .then((_) => completer.complete());
+    }
+    _instance = this;
+
+    return completer.future;
+  }
+
+  /// Begins the flow of data between the inputs and outputs connected to the camera instance.
+  Future<void> start() {
+    assert(!_isDisposed, _isDisposedMessage);
+    assert(_instance != this, _isNotInitializedMessage);
+
+    return configurator.start();
+  }
+
+  /// Stops the flow of data between the inputs and outputs connected to the camera instance.
+  Future<void> stop() {
+    assert(!_isDisposed, _isDisposedMessage);
+    assert(_instance != this, _isNotInitializedMessage);
+
+    return configurator.stop();
+  }
 
   /// Deallocate all resources and disables further use of the controller.
-  Future<void> dispose() => configurator.dispose();
+  Future<void> dispose() {
+    _instance = null;
+    _isDisposed = true;
+    return configurator.dispose();
+  }
 
   static CameraConfigurator _createDefaultConfigurator(
     CameraDescription description,
@@ -101,10 +157,15 @@
   }
 
   static CameraApi _getCameraApi(CameraDescription description) {
+    return CameraApi.iOS;
+
+    // TODO(bparrishMines): Uncomment this when platform specific code is added.
+    /*
     throw ArgumentError.value(
       description.runtimeType,
       'description.runtimeType',
       'Failed to get $CameraApi from',
     );
+    */
   }
 }
diff --git a/packages/camera/lib/new/src/common/camera_interface.dart b/packages/camera/lib/new/src/common/camera_interface.dart
index 8b5ca9d..99ead09 100644
--- a/packages/camera/lib/new/src/common/camera_interface.dart
+++ b/packages/camera/lib/new/src/common/camera_interface.dart
@@ -38,12 +38,15 @@
   /// You must call [addPreviewTexture] first or this will only return null.
   int get previewTextureId;
 
-  /// Begins the flow of data between the inputs and outputs connected the camera instance.
+  /// Initializes the camera on the device.
+  Future<void> initialize();
+
+  /// Begins the flow of data between the inputs and outputs connected to the camera instance.
   ///
   /// This will start updating the texture with id: [previewTextureId].
   Future<void> start();
 
-  /// Stops the flow of data between the inputs and outputs connected the camera instance.
+  /// Stops the flow of data between the inputs and outputs connected to the camera instance.
   Future<void> stop();
 
   /// Dispose all resources and disables further use of this configurator.
diff --git a/packages/camera/test/camera_test.dart b/packages/camera/test/camera_test.dart
index ae6c38f..39b08ec 100644
--- a/packages/camera/test/camera_test.dart
+++ b/packages/camera/test/camera_test.dart
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'package:camera/new/camera.dart';
 import 'package:flutter/services.dart';
 import 'package:flutter_test/flutter_test.dart';
 import 'package:camera/new/src/camera_testing.dart';
@@ -33,6 +34,41 @@
       CameraTesting.nextHandle = 0;
     });
 
+    group('$CameraController', () {
+      test('Initializing a second controller closes the first', () {
+        final MockCameraDescription description = MockCameraDescription();
+        final MockCameraConfigurator configurator = MockCameraConfigurator();
+
+        final CameraController controller1 =
+            CameraController.customConfigurator(
+          description: description,
+          configurator: configurator,
+        );
+
+        controller1.initialize();
+
+        final CameraController controller2 =
+            CameraController.customConfigurator(
+          description: description,
+          configurator: configurator,
+        );
+
+        controller2.initialize();
+
+        expect(
+          () => controller1.start(),
+          throwsA(isInstanceOf<AssertionError>()),
+        );
+
+        expect(
+          () => controller1.stop(),
+          throwsA(isInstanceOf<AssertionError>()),
+        );
+
+        expect(controller1.isDisposed, isTrue);
+      });
+    });
+
     group('$NativeTexture', () {
       test('allocate', () async {
         final NativeTexture texture = await NativeTexture.allocate();
@@ -48,3 +84,31 @@
     });
   });
 }
+
+class MockCameraDescription extends CameraDescription {
+  @override
+  LensDirection get direction => LensDirection.unknown;
+
+  @override
+  String get name => 'none';
+}
+
+class MockCameraConfigurator extends CameraConfigurator {
+  @override
+  Future<int> addPreviewTexture() => Future<int>.value(7);
+
+  @override
+  Future<void> dispose() => Future<void>.value();
+
+  @override
+  Future<void> initialize() => Future<void>.value();
+
+  @override
+  int get previewTextureId => 7;
+
+  @override
+  Future<void> start() => Future<void>.value();
+
+  @override
+  Future<void> stop() => Future<void>.value();
+}