-Add a mojo service to get video frames from the camera through an android service
-Add an example mojo app that displays this video in its view.
-Some re-organization around the camera mojo definition.

R=alhaad@google.com, qsr@chromium.org

Review URL: https://codereview.chromium.org/1375733004 .
diff --git a/examples/dart/camera_roll/lib/main.dart b/examples/dart/camera_roll/lib/main.dart
index d0a1bd7..d0ab8a7 100644
--- a/examples/dart/camera_roll/lib/main.dart
+++ b/examples/dart/camera_roll/lib/main.dart
@@ -89,7 +89,7 @@
 }
 
 void main() {
-  embedder.connectToService("mojo:camera_roll", cameraRoll);
+  embedder.connectToService("mojo:camera", cameraRoll);
   view.setFrameCallback(beginFrame);
   view.setEventCallback(handleEvent);
   getPhoto();
diff --git a/examples/dart/camera_video/lib/main.dart b/examples/dart/camera_video/lib/main.dart
new file mode 100644
index 0000000..4637a21
--- /dev/null
+++ b/examples/dart/camera_video/lib/main.dart
@@ -0,0 +1,79 @@
+// Copyright 2015 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.
+
+// This example makes use of mojo:camera which is available only when
+// running on Android. It repeatedly captures camera video frame images
+// and displays it in a mojo view.
+//
+// Example usage:
+//   pub get
+//   pub run sky_tools build
+//   pub run sky_tools run_mojo --mojo-path=../../.. --android
+
+import 'dart:sky';
+import 'dart:typed_data';
+
+import 'package:mojo_services/mojo/camera.mojom.dart';
+import 'package:sky/services.dart';
+
+Image image = null;
+final CameraServiceProxy camera = new CameraServiceProxy.unbound();
+
+Picture paint(Rect paintBounds) {
+  PictureRecorder recorder = new PictureRecorder();
+  if (image != null) {
+    Canvas canvas = new Canvas(recorder, paintBounds);
+    canvas.translate(paintBounds.width / 2.0, paintBounds.height / 2.0);
+    canvas.scale(0.3, 0.3);
+    Paint paint = new Paint()..color = const Color.fromARGB(255, 0, 255, 0);
+    canvas.drawImage(image, new Point(-image.width / 2.0, -image.height / 2.0), paint);
+  }
+  return recorder.endRecording();
+}
+
+Scene composite(Picture picture, Rect paintBounds) {
+  final double devicePixelRatio = view.devicePixelRatio;
+  Rect sceneBounds = new Rect.fromLTWH(
+      0.0, 0.0, view.width * devicePixelRatio, view.height * devicePixelRatio);
+  Float32List deviceTransform = new Float32List(16)
+    ..[0] = devicePixelRatio
+    ..[5] = devicePixelRatio
+    ..[10] = 1.0
+    ..[15] = 1.0;
+  SceneBuilder sceneBuilder = new SceneBuilder(sceneBounds)
+    ..pushTransform(deviceTransform)
+    ..addPicture(Offset.zero, picture, paintBounds)
+    ..pop();
+  return sceneBuilder.build();
+}
+
+void beginFrame(double timeStamp) {
+  Rect paintBounds = new Rect.fromLTWH(0.0, 0.0, view.width, view.height);
+  Picture picture = paint(paintBounds);
+  Scene scene = composite(picture, paintBounds);
+  view.scene = scene;
+}
+
+void drawNextPhoto() {
+  var future = camera.ptr.getLatestFrame();
+  future.then((response) {
+    if (response.content == null) {
+      drawNextPhoto();
+      return;
+    }
+    new ImageDecoder(response.content.handle.h, (frame) {
+      if (frame != null) {
+        image = frame;
+        view.scheduleFrame();
+        drawNextPhoto();
+      }
+    });
+  });
+}
+
+void main() {
+  view.setFrameCallback(beginFrame);
+  embedder.connectToService("mojo:camera", camera);
+  drawNextPhoto();
+}
\ No newline at end of file
diff --git a/examples/dart/camera_video/pubspec.lock b/examples/dart/camera_video/pubspec.lock
new file mode 100644
index 0000000..532f213
--- /dev/null
+++ b/examples/dart/camera_video/pubspec.lock
@@ -0,0 +1,219 @@
+# Generated by pub
+# See http://pub.dartlang.org/doc/glossary.html#lockfile
+packages:
+  analyzer:
+    description: analyzer
+    source: hosted
+    version: "0.26.1+7"
+  archive:
+    description: archive
+    source: hosted
+    version: "1.0.20"
+  args:
+    description: args
+    source: hosted
+    version: "0.13.2"
+  async:
+    description: async
+    source: hosted
+    version: "1.3.0"
+  barback:
+    description: barback
+    source: hosted
+    version: "0.15.2+7"
+  cassowary:
+    description: cassowary
+    source: hosted
+    version: "0.1.7"
+  charcode:
+    description: charcode
+    source: hosted
+    version: "1.1.0"
+  collection:
+    description: collection
+    source: hosted
+    version: "1.1.3"
+  concepts:
+    description: concepts
+    source: hosted
+    version: "0.2.0"
+  crypto:
+    description: crypto
+    source: hosted
+    version: "0.9.1"
+  csslib:
+    description: csslib
+    source: hosted
+    version: "0.12.1"
+  either:
+    description: either
+    source: hosted
+    version: "0.1.8"
+  glob:
+    description: glob
+    source: hosted
+    version: "1.0.5"
+  html:
+    description: html
+    source: hosted
+    version: "0.12.2"
+  http_multi_server:
+    description: http_multi_server
+    source: hosted
+    version: "1.3.2"
+  http_parser:
+    description: http_parser
+    source: hosted
+    version: "1.0.0"
+  intl:
+    description: intl
+    source: hosted
+    version: "0.12.4+2"
+  logging:
+    description: logging
+    source: hosted
+    version: "0.11.1+1"
+  matcher:
+    description: matcher
+    source: hosted
+    version: "0.12.0+1"
+  material_design_icons:
+    description: material_design_icons
+    source: hosted
+    version: "0.0.3"
+  mime:
+    description: mime
+    source: hosted
+    version: "0.9.3"
+  mojo:
+    description: mojo
+    source: hosted
+    version: "0.1.0"
+  mojo_services:
+    description: mojo_services
+    source: hosted
+    version: "0.1.0"
+  mojom:
+    description: mojom
+    source: hosted
+    version: "0.1.0"
+  mustache4dart:
+    description: mustache4dart
+    source: hosted
+    version: "1.0.10"
+  newton:
+    description: newton
+    source: hosted
+    version: "0.1.4"
+  option:
+    description: option
+    source: hosted
+    version: "1.1.0"
+  package_config:
+    description: package_config
+    source: hosted
+    version: "0.1.3"
+  path:
+    description: path
+    source: hosted
+    version: "1.3.6"
+  petitparser:
+    description: petitparser
+    source: hosted
+    version: "1.4.3"
+  plugin:
+    description: plugin
+    source: hosted
+    version: "0.1.0"
+  pool:
+    description: pool
+    source: hosted
+    version: "1.1.0"
+  pub_semver:
+    description: pub_semver
+    source: hosted
+    version: "1.2.2"
+  quiver:
+    description: quiver
+    source: hosted
+    version: "0.21.4"
+  shelf:
+    description: shelf
+    source: hosted
+    version: "0.6.3"
+  shelf_path:
+    description: shelf_path
+    source: hosted
+    version: "0.1.7"
+  shelf_route:
+    description: shelf_route
+    source: hosted
+    version: "0.13.5"
+  shelf_static:
+    description: shelf_static
+    source: hosted
+    version: "0.2.3+1"
+  shelf_web_socket:
+    description: shelf_web_socket
+    source: hosted
+    version: "0.0.1+4"
+  sky:
+    description: sky
+    source: hosted
+    version: "0.0.51"
+  sky_engine:
+    description: sky_engine
+    source: hosted
+    version: "0.0.29"
+  sky_services:
+    description: sky_services
+    source: hosted
+    version: "0.0.29"
+  sky_tools:
+    description: sky_tools
+    source: hosted
+    version: "0.0.15"
+  source_map_stack_trace:
+    description: source_map_stack_trace
+    source: hosted
+    version: "1.0.4"
+  source_maps:
+    description: source_maps
+    source: hosted
+    version: "0.10.1"
+  source_span:
+    description: source_span
+    source: hosted
+    version: "1.2.1"
+  stack_trace:
+    description: stack_trace
+    source: hosted
+    version: "1.4.2"
+  string_scanner:
+    description: string_scanner
+    source: hosted
+    version: "0.1.4"
+  test:
+    description: test
+    source: hosted
+    version: "0.12.4+9"
+  uri:
+    description: uri
+    source: hosted
+    version: "0.11.0"
+  utf:
+    description: utf
+    source: hosted
+    version: "0.9.0+2"
+  vector_math:
+    description: vector_math
+    source: hosted
+    version: "1.4.3"
+  watcher:
+    description: watcher
+    source: hosted
+    version: "0.9.7"
+  yaml:
+    description: yaml
+    source: hosted
+    version: "2.1.6"
diff --git a/examples/dart/camera_video/pubspec.yaml b/examples/dart/camera_video/pubspec.yaml
new file mode 100644
index 0000000..0e1e628
--- /dev/null
+++ b/examples/dart/camera_video/pubspec.yaml
@@ -0,0 +1,5 @@
+name: camera
+dependencies:
+  mojo_services: any
+  sky: any
+  sky_tools: any
diff --git a/mojo/services/camera_roll/public/interfaces/BUILD.gn b/mojo/services/camera/public/interfaces/BUILD.gn
similarity index 94%
rename from mojo/services/camera_roll/public/interfaces/BUILD.gn
rename to mojo/services/camera/public/interfaces/BUILD.gn
index 53657bf..c001bf5 100644
--- a/mojo/services/camera_roll/public/interfaces/BUILD.gn
+++ b/mojo/services/camera/public/interfaces/BUILD.gn
@@ -7,7 +7,7 @@
 
 mojom("interfaces") {
   sources = [
-    "camera_roll.mojom",
+    "camera.mojom",
   ]
 
   import_dirs = [ get_path_info("../../../", "abspath") ]
diff --git a/mojo/services/camera_roll/public/interfaces/camera_roll.mojom b/mojo/services/camera/public/interfaces/camera.mojom
similarity index 82%
rename from mojo/services/camera_roll/public/interfaces/camera_roll.mojom
rename to mojo/services/camera/public/interfaces/camera.mojom
index 850da78..b7cc5cb 100644
--- a/mojo/services/camera_roll/public/interfaces/camera_roll.mojom
+++ b/mojo/services/camera/public/interfaces/camera.mojom
@@ -28,3 +28,10 @@
   // if such an index is out-of-bounds.
   GetPhoto(uint32 index) => (Photo? photo);
 };
+
+// |CameraService| provides access to the device's camera video stream.
+interface CameraService {
+  // Returns the most recent frame captured by the device's camera
+  // in preview mode.
+  GetLatestFrame() => (handle<data_pipe_consumer>? content);
+};
diff --git a/mojo/services/mojo_services.gni b/mojo/services/mojo_services.gni
index 2f6b0a1..3edd025 100644
--- a/mojo/services/mojo_services.gni
+++ b/mojo/services/mojo_services.gni
@@ -12,7 +12,7 @@
   "//mojo/services/asset_bundle/public/interfaces",
   "//mojo/services/authenticating_url_loader_interceptor/public/interfaces",
   "//mojo/services/authentication/public/interfaces",
-  "//mojo/services/camera_roll/public/interfaces",
+  "//mojo/services/camera/public/interfaces",
   "//mojo/services/clipboard/public/interfaces",
   "//mojo/services/contacts/public/interfaces",
   "//mojo/services/content_handler/public/interfaces",
diff --git a/services/BUILD.gn b/services/BUILD.gn
index 788ed2a..f9a568e 100644
--- a/services/BUILD.gn
+++ b/services/BUILD.gn
@@ -28,7 +28,7 @@
   if (is_android) {
     deps += [
       "//services/android:java_handler",
-      "//services/camera_roll",
+      "//services/camera",
       "//services/contacts",
       "//services/location",
       "//services/notifications",
diff --git a/services/camera/BUILD.gn b/services/camera/BUILD.gn
new file mode 100644
index 0000000..6a07fcf
--- /dev/null
+++ b/services/camera/BUILD.gn
@@ -0,0 +1,36 @@
+# Copyright 2015 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("//mojo/public/tools/bindings/mojom.gni")
+import("//mojo/android/rules.gni")
+
+mojo_android_java_application("camera") {
+  sources = [
+    "src/org/chromium/services/camera/CameraApp.java",
+    "src/org/chromium/services/camera/CameraServiceImpl.java",
+  ]
+
+  mojo_main = "org.chromium.services.camera.CameraApp"
+
+  deps = [
+    "//base:base_java",
+    "//mojo/public/interfaces/application:application_java",
+    "//mojo/public/java:application",
+    "//mojo/services/camera/public/interfaces:interfaces_java",
+  ]
+}
+
+mojo_android_java_application("camera_roll") {
+  sources = [
+    "src/org/chromium/services/camera/CameraRollApp.java",
+  ]
+
+  mojo_main = "org.chromium.services.camera.CameraRollApp"
+
+  deps = [
+    "//mojo/public/interfaces/application:application_java",
+    "//mojo/public/java:application",
+    "//mojo/services/camera/public/interfaces:interfaces_java",
+  ]
+}
diff --git a/services/camera/src/org/chromium/services/camera/CameraApp.java b/services/camera/src/org/chromium/services/camera/CameraApp.java
new file mode 100644
index 0000000..76fabd5
--- /dev/null
+++ b/services/camera/src/org/chromium/services/camera/CameraApp.java
@@ -0,0 +1,61 @@
+// Copyright 2015 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.
+
+package org.chromium.services.camera;
+
+import android.content.Context;
+
+import org.chromium.mojo.application.ApplicationConnection;
+import org.chromium.mojo.application.ApplicationDelegate;
+import org.chromium.mojo.application.ApplicationRunner;
+import org.chromium.mojo.application.ServiceFactoryBinder;
+import org.chromium.mojo.bindings.InterfaceRequest;
+import org.chromium.mojo.system.Core;
+import org.chromium.mojo.system.MessagePipeHandle;
+import org.chromium.mojom.mojo.CameraService;
+import org.chromium.mojom.mojo.Shell;
+
+class CameraApp implements ApplicationDelegate {
+    private CameraServiceImpl mCameraServiceImpl;
+
+    public CameraApp(Context context, Core core) {
+        mCameraServiceImpl = new CameraServiceImpl(context, core);
+    }
+
+    public void initialize(Shell shell, String[] args, String url) {
+    }
+
+    @Override
+    public boolean configureIncomingConnection(final ApplicationConnection connection) {
+        connection.addService(new ServiceFactoryBinder<CameraService>() {
+            @Override
+            public void bind(InterfaceRequest<CameraService> request) {
+                if (mCameraServiceImpl.cameraInUse()) {
+                    /* another application is using the camera */
+                    /* TODO: support multiplexing the camera stream to multiple applications */
+                    request.close();
+                    return;
+                }
+                mCameraServiceImpl.openCamera();
+                CameraService.MANAGER.bind(mCameraServiceImpl, request);
+            }
+
+            @Override
+            public String getInterfaceName() {
+                return CameraService.MANAGER.getName();
+            }
+        });
+        return true;
+    }
+
+    @Override
+    public void quit() {
+        mCameraServiceImpl.cleanup();
+    }
+
+    public static void mojoMain(Context context, Core core,
+                                MessagePipeHandle applicationRequestHandle) {
+        ApplicationRunner.run(new CameraApp(context, core), core, applicationRequestHandle);
+    }
+}
diff --git a/services/camera_roll/src/org/chromium/services/camera_roll/CameraRollApp.java b/services/camera/src/org/chromium/services/camera/CameraRollApp.java
similarity index 100%
rename from services/camera_roll/src/org/chromium/services/camera_roll/CameraRollApp.java
rename to services/camera/src/org/chromium/services/camera/CameraRollApp.java
diff --git a/services/camera/src/org/chromium/services/camera/CameraServiceImpl.java b/services/camera/src/org/chromium/services/camera/CameraServiceImpl.java
new file mode 100644
index 0000000..568a97c
--- /dev/null
+++ b/services/camera/src/org/chromium/services/camera/CameraServiceImpl.java
@@ -0,0 +1,246 @@
+// Copyright 2015 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.
+
+package org.chromium.services.camera;
+
+import android.content.Context;
+import android.graphics.ImageFormat;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.media.Image;
+import android.media.ImageReader;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Log;
+import android.util.Size;
+
+import org.chromium.mojo.system.Core;
+import org.chromium.mojo.system.DataPipe;
+import org.chromium.mojo.system.MojoException;
+import org.chromium.mojo.system.Pair;
+import org.chromium.mojom.mojo.CameraService;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.concurrent.Semaphore;
+
+/**
+ * Implementation of AuthenticationService from services/camera/camera.mojom
+ */
+public class CameraServiceImpl implements CameraService {
+    private final Core mCore;
+    private final Context mContext;
+    private static final String TAG = "CameraServiceImpl";
+
+    private Semaphore mCameraOpenCloseLock = new Semaphore(1);
+    private CameraDevice mCameraDevice;
+    private HandlerThread mBackgroundThread;
+    private Handler mHandler;
+
+    private ImageReader mImageReader;
+    private DataPipe.ProducerHandle mProducerHandle;
+
+    public CameraServiceImpl(Context context, Core core) {
+        mContext = context;
+        mCore = core;
+        startBackgroundThread();
+    }
+
+    private final ImageReader.OnImageAvailableListener mOnImageAvailableListener =
+            new ImageReader.OnImageAvailableListener() {
+                @Override
+                public void onImageAvailable(ImageReader reader) {
+                    DataPipe.ProducerHandle handle;
+                    synchronized (this) {
+                        if (mProducerHandle == null) {
+                            return;
+                        }
+                        handle = mProducerHandle;
+                        mProducerHandle = null;
+                    }
+                    try (Image img = reader.acquireLatestImage()) {
+                        // TODO: Dont write the image data as a single block.
+                        ByteBuffer buffer = img.getPlanes()[0].getBuffer();
+                        handle.writeData(buffer, DataPipe.WriteFlags.none());
+                    } catch (MojoException e) {
+                        Log.e(TAG, "Failed to write to producer", e);
+                    } finally {
+                        handle.close();
+                    }
+                }
+            };
+
+    public void openCamera() {
+        CameraManager manager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
+        try {
+            for (String cameraId : manager.getCameraIdList()) {
+                CameraCharacteristics characteristics =
+                        manager.getCameraCharacteristics(cameraId);
+                StreamConfigurationMap map = characteristics
+                        .get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+                Size size = chooseVideoSize(map.getOutputSizes(ImageFormat.JPEG));
+                // We don't use a front facing camera in this sample.
+                Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
+                if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {
+                    continue;
+                }
+                mImageReader = ImageReader
+                        .newInstance(size.getWidth(), size.getHeight(),
+                                     ImageFormat.JPEG, 100/*fps*/);
+                mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mHandler);
+                mCameraOpenCloseLock.acquire();
+                try {
+                    manager.openCamera(cameraId, mCameraStateCallback, mHandler);
+                } catch (Exception e) {
+                    mCameraOpenCloseLock.release();
+                    Log.e(TAG, "Failed to openCamera", e);
+                }
+                break;
+            }
+        } catch (CameraAccessException e) {
+            Log.e(TAG, "Failed to access camera characteristics", e);
+        } catch (InterruptedException e) {
+            throw new RuntimeException("Interrupted while trying to lock camera opening.");
+        }
+    }
+
+    private void closeCamera() {
+        try {
+            mCameraOpenCloseLock.acquire();
+            try {
+                if (mImageReader != null) {
+                    mImageReader.close();
+                    mImageReader = null;
+                }
+                if (mCameraDevice != null) {
+                    mCameraDevice.close();
+                    mCameraDevice = null;
+                }
+            } finally {
+                mCameraOpenCloseLock.release();
+            }
+        } catch (InterruptedException e) {
+            throw new RuntimeException("Interrupted while trying to lock camera opening.");
+        }
+    }
+
+    private void startBackgroundThread() {
+        mBackgroundThread = new HandlerThread("CameraServiceImplThread");
+        mBackgroundThread.start();
+        mHandler = new Handler(mBackgroundThread.getLooper());
+    }
+
+    private void stopBackgroundThread() {
+        mBackgroundThread.quitSafely();
+        try {
+            mBackgroundThread.join();
+            mBackgroundThread = null;
+            mHandler = null;
+        } catch (InterruptedException e) {
+            Log.e(TAG, "Failed to stop background thread", e);
+        }
+    }
+
+    private Size chooseVideoSize(Size[] choices) {
+        for (Size size : choices) {
+            // 1080p resolution
+            if (size.getWidth() == size.getHeight() * 16 / 9 && size.getHeight() <= 1080) {
+                return size;
+            }
+        }
+        return choices[choices.length - 1];
+    }
+
+    private final CameraDevice.StateCallback mCameraStateCallback =
+            new CameraDevice.StateCallback() {
+                @Override
+                public void onOpened(CameraDevice device) {
+                    mCameraDevice = device;
+                    startPreview();
+                    mCameraOpenCloseLock.release();
+                }
+
+                @Override
+                public void onDisconnected(CameraDevice cameraDevice) {
+                    mCameraOpenCloseLock.release();
+                }
+
+                @Override
+                public void onError(CameraDevice cameraDevice, int error) {
+                    mCameraOpenCloseLock.release();
+                    Log.e(TAG, "Failed to connect to camera:" + error);
+                }
+            };
+
+    private void startPreview() {
+        try {
+            final CaptureRequest.Builder request =
+                    mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+            request.addTarget(mImageReader.getSurface());
+            mCameraDevice.createCaptureSession(
+                    Arrays.asList(mImageReader.getSurface()),
+                    new CameraCaptureSession.StateCallback() {
+                        @Override
+                        public void onConfigured(CameraCaptureSession session) {
+                            request.set(CaptureRequest.CONTROL_MODE,
+                                    CameraMetadata.CONTROL_MODE_AUTO);
+                            try {
+                                session.setRepeatingRequest(request.build(), null, mHandler);
+                            } catch (CameraAccessException e) {
+                                Log.e(TAG, "Failed to set repeating request", e);
+                            }
+                        }
+
+                        @Override
+                        public void onConfigureFailed(CameraCaptureSession session) {
+                            Log.e(TAG, "Could not configure session capture");
+                        }
+                    }, mHandler);
+        } catch (CameraAccessException e) {
+            Log.e(TAG, "Failed to start preview for camera", e);
+        }
+    }
+
+    @Override
+    public void getLatestFrame(CameraService.GetLatestFrameResponse callback) {
+        Pair<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> handles = mCore.createDataPipe(null);
+        callback.call(handles.second);
+        synchronized (this) {
+            if (mProducerHandle != null) {
+                mProducerHandle.close();
+            }
+            mProducerHandle = handles.first;
+        }
+    }
+
+    @Override
+    public void onConnectionError(MojoException e) {
+    }
+
+    @Override
+    public void close() {
+        closeCamera();
+        synchronized (this) {
+            if (mProducerHandle != null) {
+                mProducerHandle.close();
+                mProducerHandle = null;
+            }
+        }
+    }
+
+    public boolean cameraInUse() {
+        return mImageReader != null;
+    }
+
+    public void cleanup() {
+        stopBackgroundThread();
+        close();
+    }
+}
diff --git a/services/camera_roll/BUILD.gn b/services/camera_roll/BUILD.gn
deleted file mode 100644
index c6839c4..0000000
--- a/services/camera_roll/BUILD.gn
+++ /dev/null
@@ -1,19 +0,0 @@
-# Copyright 2015 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("//mojo/android/rules.gni")
-
-mojo_android_java_application("camera_roll") {
-  sources = [
-    "src/org/chromium/services/camera_roll/CameraRollApp.java",
-  ]
-
-  mojo_main = "org.chromium.services.camera_roll.CameraRollApp"
-
-  deps = [
-    "//mojo/public/interfaces/application:application_java",
-    "//mojo/public/java:application",
-    "//mojo/services/camera_roll/public/interfaces:interfaces_java",
-  ]
-}
diff --git a/shell/android/apk/AndroidManifest.xml.jinja2 b/shell/android/apk/AndroidManifest.xml.jinja2
index 45640bc..0229050 100644
--- a/shell/android/apk/AndroidManifest.xml.jinja2
+++ b/shell/android/apk/AndroidManifest.xml.jinja2
@@ -19,6 +19,7 @@
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
     <uses-permission android:name="android.permission.NFC"/>
     <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+    <uses-permission android:name="android.permission.CAMERA" />
 
     <application android:icon="@mipmap/ic_launcher"
             android:name="org.chromium.mojo.shell.MojoShellApplication"