Initial camera device implementation

* Working streaming preview requests only
* Almost everything else returns empty objects that don't do anything

Bug: 9213377
Change-Id: Ie6f02a7c0952b0f5ebc41905425b15cae221f7d3
diff --git a/core/java/android/hardware/ICameraService.aidl b/core/java/android/hardware/ICameraService.aidl
index b8fbfdb..2d0c099 100644
--- a/core/java/android/hardware/ICameraService.aidl
+++ b/core/java/android/hardware/ICameraService.aidl
@@ -20,6 +20,8 @@
 import android.hardware.ICameraClient;
 import android.hardware.IProCameraUser;
 import android.hardware.IProCameraCallbacks;
+import android.hardware.photography.ICameraDeviceUser;
+import android.hardware.photography.ICameraDeviceCallbacks;
 import android.hardware.ICameraServiceListener;
 import android.hardware.CameraInfo;
 
@@ -43,6 +45,10 @@
                               String clientPackageName,
                               int clientUid);
 
+    ICameraDeviceUser connectDevice(ICameraDeviceCallbacks callbacks, int cameraId,
+                              String clientPackageName,
+                              int clientUid);
+
     int addListener(ICameraServiceListener listener);
     int removeListener(ICameraServiceListener listener);
 }
diff --git a/core/java/android/hardware/photography/CameraDevice.java b/core/java/android/hardware/photography/CameraDevice.java
index e94e3a1..5fb14dc 100644
--- a/core/java/android/hardware/photography/CameraDevice.java
+++ b/core/java/android/hardware/photography/CameraDevice.java
@@ -16,11 +16,6 @@
 
 package android.hardware.photography;
 
-import android.graphics.ImageFormat;
-import android.os.IBinder;
-import android.renderscript.Allocation;
-import android.renderscript.RenderScript;
-import android.util.Log;
 import android.view.Surface;
 
 import java.lang.AutoCloseable;
@@ -49,7 +44,7 @@
  * @see CameraManager#openCamera
  * @see android.Manifest.permission#CAMERA
  */
-public final class CameraDevice implements AutoCloseable {
+public interface CameraDevice extends AutoCloseable {
 
     /**
      * Create a request suitable for a camera preview window. Specifically, this
@@ -103,8 +98,6 @@
      */
     public static final int TEMPLATE_MANUAL = 5;
 
-    private static final String TAG = "CameraDevice";
-
     /**
      * Get the static properties for this camera. These are identical to the
      * properties returned by {@link CameraManager#getCameraProperties}.
@@ -115,10 +108,7 @@
      *
      * @see CameraManager#getCameraProperties
      */
-    public CameraProperties getProperties() throws CameraAccessException {
-        return null;
-    }
-
+    public CameraProperties getProperties() throws CameraAccessException;
     /**
      * <p>Set up a new output set of Surfaces for the camera device.</p>
      *
@@ -200,8 +190,7 @@
      * @throws IllegalStateException if the camera device is not idle, or has
      * encountered a fatal error
      */
-    public void configureOutputs(List<Surface> outputs) {
-    }
+    public void configureOutputs(List<Surface> outputs) throws CameraAccessException;
 
     /**
      * <p>Create a {@link CaptureRequest} initialized with template for a target
@@ -227,9 +216,7 @@
      * @see #TEMPLATE_MANUAL
      */
     public CaptureRequest createCaptureRequest(int templateType)
-            throws CameraAccessException {
-        return null;
-    }
+            throws CameraAccessException;
 
     /**
      * <p>Submit a request for an image to be captured by this CameraDevice.</p>
@@ -261,8 +248,7 @@
      * @see #setRepeatingBurst
      */
     public void capture(CaptureRequest request, CaptureListener listener)
-            throws CameraAccessException {
-    }
+            throws CameraAccessException;
 
     /**
      * <p>Submit a list of requests to be captured in sequence as a burst. The
@@ -293,8 +279,7 @@
      * @see #setRepeatingBurst
      */
     public void captureBurst(List<CaptureRequest> requests,
-            CaptureListener listener) throws CameraAccessException {
-    }
+            CaptureListener listener) throws CameraAccessException;
 
     /**
      * <p>Request endlessly repeating capture of images by this
@@ -336,8 +321,7 @@
      * @see #setRepeatingBurst
      */
     public void setRepeatingRequest(CaptureRequest request, CaptureListener listener)
-            throws CameraAccessException {
-    }
+            throws CameraAccessException;
 
     /**
      * <p>Request endlessly repeating capture of a sequence of images by this
@@ -381,8 +365,7 @@
      * @see #setRepeatingRequest
      */
     public void setRepeatingBurst(List<CaptureRequest> requests, CaptureListener listener)
-            throws CameraAccessException {
-    }
+            throws CameraAccessException;
 
     /**
      * <p>Cancel any ongoing repeating capture set by either
@@ -408,8 +391,7 @@
      * device has encountered a fatal error, or if there is an active repeating
      * request or burst.
      */
-    public void stopRepeating() throws CameraAccessException {
-    }
+    public void stopRepeating() throws CameraAccessException;
 
     /**
      * <p>Wait until all the submitted requests have finished processing</p>
@@ -434,8 +416,7 @@
      * device has encountered a fatal error, or if there is an active repeating
      * request or burst.
      */
-    public void waitUntilIdle() throws CameraAccessException {
-    }
+    public void waitUntilIdle() throws CameraAccessException;
 
     /**
      * Set the error listener object to call when an asynchronous error
@@ -447,17 +428,17 @@
      * notifications to. Setting this to null will stop notifications about
      * asynchronous errors.
      */
-    public void setErrorListener(ErrorListener listener) {
-    }
+    public void setErrorListener(ErrorListener listener);
 
     /**
      * Close the connection to this camera device. After this call, all calls to
      * the camera device interface will throw a {@link IllegalStateException},
      * except for calls to close().
+     * @throws Exception
      */
     @Override
-    public void close() {
-    }
+    public void close() throws Exception;
+    // TODO: We should decide on the behavior of in-flight requests should be on close.
 
     /**
      * A listener for receiving metadata about completed image captures. The
@@ -556,12 +537,4 @@
          */
         public void onCameraDeviceError(CameraDevice camera, int error);
     }
-
-    /**
-     * @hide
-     */
-    public CameraDevice(IBinder binder) {
-        Log.e(TAG, "CameraDevice constructor not implemented yet");
-    }
-
 }
diff --git a/core/java/android/hardware/photography/CameraManager.java b/core/java/android/hardware/photography/CameraManager.java
index 115f151..c1c9435 100644
--- a/core/java/android/hardware/photography/CameraManager.java
+++ b/core/java/android/hardware/photography/CameraManager.java
@@ -94,7 +94,13 @@
      */
     public String[] getDeviceIdList() throws CameraAccessException {
         synchronized (mLock) {
-            return (String[]) getOrCreateDeviceIdListLocked().toArray();
+            try {
+                return getOrCreateDeviceIdListLocked().toArray(new String[0]);
+            } catch(CameraAccessException e) {
+                // this should almost never happen, except if mediaserver crashes
+                throw new IllegalStateException(
+                        "Failed to query camera service for device ID list", e);
+            }
         }
     }
 
@@ -179,17 +185,32 @@
     public CameraDevice openCamera(String cameraId) throws CameraAccessException {
 
         try {
-            IProCameraUser cameraUser;
 
             synchronized (mLock) {
-                // TODO: Use ICameraDevice or some such instead of this...
-                cameraUser = mCameraService.connectPro(null,
+
+                ICameraDeviceUser cameraUser;
+
+                android.hardware.photography.impl.CameraDevice device =
+                        new android.hardware.photography.impl.CameraDevice(cameraId);
+
+                cameraUser = mCameraService.connectDevice(device.getCallbacks(),
                         Integer.parseInt(cameraId),
                         mContext.getPackageName(), USE_CALLING_UID);
 
+                // TODO: change ICameraService#connectDevice to return status_t
+                if (cameraUser == null) {
+                    // TEMPORARY CODE.
+                    // catch-all exception since we aren't yet getting the actual error code
+                    throw new IllegalStateException("Failed to open camera device");
+                }
+
+                // TODO: factor out listener to be non-nested, then move setter to constructor
+                device.setRemoteDevice(cameraUser);
+
+                return device;
+
             }
 
-            return new CameraDevice(cameraUser.asBinder());
         } catch (NumberFormatException e) {
             throw new IllegalArgumentException("Expected cameraId to be numeric, but it was: "
                     + cameraId);
@@ -306,7 +327,7 @@
 
         @Override
         public void onStatusChanged(int status, int cameraId) throws RemoteException {
-            synchronized(CameraManager.this) {
+            synchronized(CameraManager.this.mLock) {
 
                 Log.v(TAG,
                         String.format("Camera id %d has status changed to 0x%x", cameraId, status));
diff --git a/core/java/android/hardware/photography/CameraMetadata.aidl b/core/java/android/hardware/photography/CameraMetadata.aidl
new file mode 100644
index 0000000..b4dc9ac
--- /dev/null
+++ b/core/java/android/hardware/photography/CameraMetadata.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.photography;
+
+/** @hide */
+parcelable CameraMetadata;
diff --git a/core/java/android/hardware/photography/CameraMetadata.java b/core/java/android/hardware/photography/CameraMetadata.java
index 385a1b9..5488952 100644
--- a/core/java/android/hardware/photography/CameraMetadata.java
+++ b/core/java/android/hardware/photography/CameraMetadata.java
@@ -18,6 +18,8 @@
 
 import android.os.Parcelable;
 import android.os.Parcel;
+import android.util.Log;
+
 import java.util.HashMap;
 import java.util.Map;
 
@@ -32,27 +34,34 @@
  * @see CameraManager
  * @see CameraProperties
  **/
-public class CameraMetadata implements Parcelable {
+public class CameraMetadata implements Parcelable, AutoCloseable {
 
     public CameraMetadata() {
         mMetadataMap = new HashMap<Key<?>, Object>();
-    }
 
-    private CameraMetadata(Parcel in) {
-
+        mMetadataPtr = nativeAllocate();
+        if (mMetadataPtr == 0) {
+            throw new OutOfMemoryError("Failed to allocate native CameraMetadata");
+        }
     }
 
     public static final Parcelable.Creator<CameraMetadata> CREATOR =
             new Parcelable.Creator<CameraMetadata>() {
+        @Override
         public CameraMetadata createFromParcel(Parcel in) {
-            return new CameraMetadata(in);
+            CameraMetadata metadata = new CameraMetadata();
+            metadata.readFromParcel(in);
+            return metadata;
         }
 
+        @Override
         public CameraMetadata[] newArray(int size) {
             return new CameraMetadata[size];
         }
     };
 
+    private static final String TAG = "CameraMetadataJV";
+
     /**
      * Set a camera metadata field to a value. The field definitions can be
      * found in {@link CameraProperties}, {@link CaptureResult}, and
@@ -63,6 +72,8 @@
      * type to the key.
      */
     public <T> void set(Key<T> key, T value) {
+        Log.e(TAG, "Not fully implemented yet");
+
         mMetadataMap.put(key, value);
     }
 
@@ -76,6 +87,8 @@
      */
     @SuppressWarnings("unchecked")
     public <T> T get(Key<T> key) {
+        Log.e(TAG, "Not fully implemented yet");
+
         return (T) mMetadataMap.get(key);
     }
 
@@ -86,7 +99,15 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
+        nativeWriteToParcel(dest);
+    }
 
+    /**
+     * Expand this object from a Parcel.
+     * @param in The Parcel from which the object should be read
+     */
+    public void readFromParcel(Parcel in) {
+        nativeReadFromParcel(in);
     }
 
     public static class Key<T> {
@@ -125,5 +146,88 @@
         private final String mName;
     }
 
-    private Map<Key<?>, Object> mMetadataMap;
+    private final Map<Key<?>, Object> mMetadataMap;
+
+    /**
+     * @hide
+     */
+    private long mMetadataPtr; // native CameraMetadata*
+
+    private native long nativeAllocate();
+    private native synchronized void nativeWriteToParcel(Parcel dest);
+    private native synchronized void nativeReadFromParcel(Parcel source);
+    private native synchronized void nativeSwap(CameraMetadata other) throws NullPointerException;
+    private native synchronized void nativeClose();
+    private native synchronized boolean nativeIsEmpty();
+    private native synchronized int nativeGetEntryCount();
+    private static native void nativeClassInit();
+
+    /**
+     * <p>Perform a 0-copy swap of the internal metadata with another object.</p>
+     *
+     * <p>Useful to convert a CameraMetadata into e.g. a CaptureRequest.</p>
+     *
+     * @param other metadata to swap with
+     * @throws NullPointerException if other was null
+     * @hide
+     */
+    public void swap(CameraMetadata other) {
+        nativeSwap(other);
+    }
+
+    /**
+     * @hide
+     */
+    public int getEntryCount() {
+        return nativeGetEntryCount();
+    }
+
+    /**
+     * Does this metadata contain at least 1 entry?
+     *
+     * @hide
+     */
+    public boolean isEmpty() {
+        return nativeIsEmpty();
+    }
+
+    /**
+     * <p>Closes this object, and releases all native resources associated with it.</p>
+     *
+     * <p>Calling any other public method after this will result in an IllegalStateException
+     * being thrown.</p>
+     */
+    @Override
+    public void close() throws Exception {
+        // this sets mMetadataPtr to 0
+        nativeClose();
+        mMetadataPtr = 0; // set it to 0 again to prevent eclipse from making this field final
+    }
+
+    /**
+     * Whether or not {@link #close} has already been called (at least once) on this object.
+     * @hide
+     */
+    public boolean isClosed() {
+        synchronized (this) {
+            return mMetadataPtr == 0;
+        }
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            close();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * We use a class initializer to allow the native code to cache some field offsets
+     */
+    static {
+        System.loadLibrary("media_jni");
+        nativeClassInit();
+    }
 }
\ No newline at end of file
diff --git a/core/java/android/hardware/photography/CameraProperties.java b/core/java/android/hardware/photography/CameraProperties.java
index 1bfd712..ad42285 100644
--- a/core/java/android/hardware/photography/CameraProperties.java
+++ b/core/java/android/hardware/photography/CameraProperties.java
@@ -39,7 +39,7 @@
      * {@link #INFO_IDENTIFIER} can be used to distinguish between multiple
      * removable cameras of the same model.
      */
-    public static final Key INFO_MODEL =
+    public static final Key<String> INFO_MODEL =
             new Key<String>("android.info.model");
 
     /**
@@ -48,7 +48,7 @@
      * same model and manufacturer. For non-removable cameras, the
      * identifier is equal to the the device's id.
      */
-    public static final Key INFO_IDENTIFIER =
+    public static final Key<String> INFO_IDENTIFIER =
             new Key<String>("android.info.identifier");
 
     /**
@@ -58,7 +58,7 @@
      * to be disconnected during use. Use the {@link #INFO_IDENTIFIER} field to
      * determine if this camera is a match for a camera device seen earlier.</p>
      */
-    public static final Key INFO_REMOVABLE =
+    public static final Key<Boolean> INFO_REMOVABLE =
             new Key<Boolean>("android.info.isRemovable");
 
     /**
@@ -99,7 +99,7 @@
      * @see #INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
      * @see #INFO_SUPPORTED_HARDWARE_LEVEL_FULL
      */
-    public static final Key INFO_SUPPORTED_HARDWARE_LEVEL =
+    public static final Key<Integer> INFO_SUPPORTED_HARDWARE_LEVEL =
             new Key<Integer>("android.info.supportedHardwareLevel");
 
     /**
@@ -164,7 +164,7 @@
      * {@link android.graphics.ImageFormat#YUV_420_888} are guaranteed to be
      * supported.</p>
      */
-    public static final Key SCALER_AVAILABLE_FORMATS =
+    public static final Key<Integer[]> SCALER_AVAILABLE_FORMATS =
             new Key<Integer[]>("android.scaler.availableFormats");
 
     /**
@@ -173,7 +173,7 @@
      * target, the ImageReader must be configured to use one of these sizes
      * when using format {@link android.graphics.ImageFormat#JPEG}.</p>
      */
-    public static final Key SCALER_AVAILABLE_JPEG_SIZES =
+    public static final Key<Size[]> SCALER_AVAILABLE_JPEG_SIZES =
             new Key<Size[]>("android.scaler.availableJpegSizes");
 
     /**
@@ -193,7 +193,7 @@
      * when using format {@link android.graphics.ImageFormat#YUV_420_888}.</p>
      *
      */
-    public static final Key SCALER_AVAILABLE_PROCESSED_SIZES =
+    public static final Key<Size[]> SCALER_AVAILABLE_PROCESSED_SIZES =
             new Key<Size[]>("android.scaler.availableProcessedSizes");
 
     /**
@@ -206,7 +206,7 @@
      * target, the ImageReader must be configured to use one of these sizes
      * when using image format {@link android.graphics.ImageFormat#RAW_SENSOR}.</p>
      */
-    public static final Key SCALER_AVAILABLE_RAW_SIZES =
+    public static final Key<Size[]> SCALER_AVAILABLE_RAW_SIZES =
             new Key<Size[]>("android.scaler.availableRawSizes");
 
     /**
@@ -229,7 +229,7 @@
      * a coordinate system based on the active array dimensions, with (0,0)
      * being the top-left corner of the active array.</p>
      */
-    public static final Key SENSOR_ACTIVE_ARRAY_SIZE =
+    public static final Key<Rect> SENSOR_ACTIVE_ARRAY_SIZE =
             new Key<Rect>("android.sensor.activeArraySize");
 
     /**
@@ -239,7 +239,7 @@
      * this. If raw sensor capture is supported by this device, this is one of
      * the supported capture sizes.</p>
      */
-    public static final Key SENSOR_PIXEL_ARRAY_SIZE =
+    public static final Key<Size> SENSOR_PIXEL_ARRAY_SIZE =
             new Key<Size>("android.sensor.activeArraySize");
 
     // TODO: Many more of these.
diff --git a/core/java/android/hardware/photography/CaptureRequest.aidl b/core/java/android/hardware/photography/CaptureRequest.aidl
new file mode 100644
index 0000000..64fb6f2
--- /dev/null
+++ b/core/java/android/hardware/photography/CaptureRequest.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.photography;
+
+/** @hide */
+parcelable CaptureRequest;
diff --git a/core/java/android/hardware/photography/CaptureRequest.java b/core/java/android/hardware/photography/CaptureRequest.java
index a54c743..ac2041b 100644
--- a/core/java/android/hardware/photography/CaptureRequest.java
+++ b/core/java/android/hardware/photography/CaptureRequest.java
@@ -16,9 +16,11 @@
 
 package android.hardware.photography;
 
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.view.Surface;
 
-import java.util.List;
+import java.util.HashSet;
 
 
 /**
@@ -47,24 +49,30 @@
  * @see CameraDevice#setRepeatingRequest
  * @see CameraDevice#createRequest
  */
-public final class CaptureRequest extends CameraMetadata {
+public final class CaptureRequest extends CameraMetadata implements Parcelable {
+
+    private final Object mLock = new Object();
+    private final HashSet<Surface> mSurfaceSet = new HashSet<Surface>();
 
     /**
      * The exposure time for this capture, in nanoseconds.
      */
-    public static final Key SENSOR_EXPOSURE_TIME =
+    public static final Key<Long> SENSOR_EXPOSURE_TIME =
             new Key<Long>("android.sensor.exposureTime");
 
     /**
      * The sensor sensitivity (gain) setting for this camera.
      * This is represented as an ISO sensitivity value
      */
-    public static final Key SENSOR_SENSITIVITY =
+    public static final Key<Integer> SENSOR_SENSITIVITY =
             new Key<Integer>("android.sensor.sensitivity");
 
     // Many more settings
 
-    CaptureRequest() {
+    /**
+     * @hide
+     */
+    public CaptureRequest() {
     }
 
     /**
@@ -72,14 +80,76 @@
      *
      * <p>The Surface added must be one of the surfaces included in the last
      * call to {@link CameraDevice#configureOutputs}.</p>
+     *
+     * <p>Adding a target more than once has no effect.</p>
+     *
+     * @param outputTarget surface to use as an output target for this request
      */
     public void addTarget(Surface outputTarget) {
+        synchronized (mLock) {
+            mSurfaceSet.add(outputTarget);
+        }
     }
 
     /**
      * <p>Remove a surface from the list of targets for this request.</p>
+     *
+     * <p>Removing a target that is not currently added has no effect.</p>
+     *
+     * @param outputTarget surface to use as an output target for this request
      */
     public void removeTarget(Surface outputTarget) {
+        synchronized (mLock) {
+            mSurfaceSet.remove(outputTarget);
+        }
+    }
+
+    public static final Parcelable.Creator<CaptureRequest> CREATOR =
+            new Parcelable.Creator<CaptureRequest>() {
+        @Override
+        public CaptureRequest createFromParcel(Parcel in) {
+            CaptureRequest request = new CaptureRequest();
+            request.readFromParcel(in);
+            return request;
+        }
+
+        @Override
+        public CaptureRequest[] newArray(int size) {
+            return new CaptureRequest[size];
+        }
+    };
+
+    /**
+     * Expand this object from a Parcel.
+     * @param in The parcel from which the object should be read
+     */
+    @Override
+    public void readFromParcel(Parcel in) {
+        synchronized (mLock) {
+            super.readFromParcel(in);
+
+            mSurfaceSet.clear();
+
+            Parcelable[] parcelableArray = in.readParcelableArray(Surface.class.getClassLoader());
+
+            if (parcelableArray == null) {
+                return;
+            }
+
+            for (Parcelable p : parcelableArray) {
+                Surface s = (Surface) p;
+                mSurfaceSet.add(s);
+            }
+        }
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        synchronized (mLock) {
+            super.writeToParcel(dest, flags);
+
+            dest.writeParcelableArray(mSurfaceSet.toArray(new Surface[mSurfaceSet.size()]), flags);
+        }
     }
 
 }
\ No newline at end of file
diff --git a/core/java/android/hardware/photography/CaptureResult.java b/core/java/android/hardware/photography/CaptureResult.java
index dd36f1d..b502c4c 100644
--- a/core/java/android/hardware/photography/CaptureResult.java
+++ b/core/java/android/hardware/photography/CaptureResult.java
@@ -101,7 +101,10 @@
 
     // TODO: Many many more
 
-    CaptureResult() {
+    /**
+     * @hide
+     */
+    public CaptureResult() {
     }
 
     /**
diff --git a/core/java/android/hardware/photography/ICameraDeviceCallbacks.aidl b/core/java/android/hardware/photography/ICameraDeviceCallbacks.aidl
new file mode 100644
index 0000000..c506800
--- /dev/null
+++ b/core/java/android/hardware/photography/ICameraDeviceCallbacks.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.photography;
+
+import android.hardware.photography.CameraMetadata;
+
+/** @hide */
+interface ICameraDeviceCallbacks
+{
+    /**
+     * Keep up-to-date with frameworks/av/include/camera/photography/ICameraDeviceCallbacks.h
+     */
+
+    void notifyCallback(int msgType, int ext1, int ext2);
+    void onResultReceived(int frameId, in CameraMetadata result);
+}
diff --git a/core/java/android/hardware/photography/ICameraDeviceUser.aidl b/core/java/android/hardware/photography/ICameraDeviceUser.aidl
new file mode 100644
index 0000000..d1310fb
--- /dev/null
+++ b/core/java/android/hardware/photography/ICameraDeviceUser.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.photography;
+
+import android.view.Surface;
+import android.hardware.photography.CameraMetadata;
+import android.hardware.photography.CaptureRequest;
+
+/** @hide */
+interface ICameraDeviceUser
+{
+    /**
+     * Keep up-to-date with frameworks/av/include/camera/photography/ICameraDeviceUser.h
+     */
+    void disconnect();
+
+    // ints here are status_t
+
+    // non-negative value is the requestId. negative value is status_t
+    int submitRequest(in CaptureRequest request, boolean streaming);
+
+    int cancelRequest(int requestId);
+
+    int deleteStream(int streamId);
+
+    // non-negative value is the stream ID. negative value is status_t
+    int createStream(int width, int height, int format, in Surface surface);
+
+    int createDefaultRequest(int templateId, out CameraMetadata request);
+}
diff --git a/core/java/android/hardware/photography/impl/CameraDevice.java b/core/java/android/hardware/photography/impl/CameraDevice.java
new file mode 100644
index 0000000..b1e3f6a
--- /dev/null
+++ b/core/java/android/hardware/photography/impl/CameraDevice.java
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.photography.impl;
+
+import android.hardware.photography.CameraMetadata;
+import android.hardware.photography.CaptureResult;
+import android.hardware.photography.ICameraDeviceUser;
+import android.hardware.photography.ICameraDeviceCallbacks;
+import android.hardware.photography.CameraAccessException;
+import android.hardware.photography.CameraProperties;
+import android.hardware.photography.CaptureRequest;
+import android.hardware.photography.utils.CameraRuntimeException;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.Surface;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Stack;
+
+/**
+ * HAL2.1+ implementation of CameraDevice Use CameraManager#open to instantiate
+ */
+public class CameraDevice implements android.hardware.photography.CameraDevice {
+
+    private final String TAG;
+
+    // TODO: guard every function with if (!mRemoteDevice) check (if it was closed)
+    private ICameraDeviceUser mRemoteDevice;
+
+    private final Object mLock = new Object();
+    private final CameraDeviceCallbacks mCallbacks = new CameraDeviceCallbacks();
+
+    // XX: Make this a WeakReference<CaptureListener> ?
+    private final HashMap<Integer, CaptureListenerHolder> mCaptureListenerMap =
+            new HashMap<Integer, CaptureListenerHolder>();
+
+    private final Stack<Integer> mRepeatingRequestIdStack = new Stack<Integer>();
+
+    private final String mCameraId;
+
+    public CameraDevice(String cameraId) {
+        mCameraId = cameraId;
+        TAG = String.format("CameraDevice-%s-JV", mCameraId);
+    }
+
+    public CameraDeviceCallbacks getCallbacks() {
+        return mCallbacks;
+    }
+
+    /**
+     * @hide
+     */
+    public void setRemoteDevice(ICameraDeviceUser remoteDevice) {
+        mRemoteDevice = remoteDevice;
+    }
+
+    @Override
+    public CameraProperties getProperties() throws CameraAccessException {
+        // TODO
+        Log.v(TAG, "TODO: Implement getProperties");
+        return new CameraProperties();
+    }
+
+    @Override
+    public void configureOutputs(List<Surface> outputs) throws CameraAccessException {
+        synchronized (mLock) {
+            // TODO: delete outputs that aren't in this list that were configured previously
+            for (Surface s : outputs) {
+                try {
+                    // TODO: remove width,height,format since we are ignoring
+                    // it.
+                    mRemoteDevice.createStream(0, 0, 0, s);
+                } catch (CameraRuntimeException e) {
+                    throw e.asChecked();
+                } catch (RemoteException e) {
+                    // impossible
+                    return;
+                }
+            }
+        }
+    }
+
+    @Override
+    public CaptureRequest createCaptureRequest(int templateType) throws CameraAccessException {
+
+        synchronized (mLock) {
+
+            CameraMetadata templatedRequest = new CameraMetadata();
+
+            try {
+                mRemoteDevice.createDefaultRequest(templateType, /* out */templatedRequest);
+            } catch (CameraRuntimeException e) {
+                throw e.asChecked();
+            } catch (RemoteException e) {
+                // impossible
+                return null;
+            }
+
+            CaptureRequest request = new CaptureRequest();
+
+            // XX: could also change binder signature but that's more work than
+            // just using swap.
+            request.swap(templatedRequest);
+
+            return request;
+
+        }
+    }
+
+    @Override
+    public void capture(CaptureRequest request, CaptureListener listener)
+            throws CameraAccessException {
+        submitCaptureRequest(request, listener, /*streaming*/false);
+    }
+
+    @Override
+    public void captureBurst(List<CaptureRequest> requests, CaptureListener listener)
+            throws CameraAccessException {
+        // TODO
+        throw new UnsupportedOperationException("Burst capture implemented yet");
+
+    }
+
+    private void submitCaptureRequest(CaptureRequest request, CaptureListener listener,
+            boolean repeating) throws CameraAccessException {
+
+        synchronized (mLock) {
+
+            int requestId;
+
+            try {
+                requestId = mRemoteDevice.submitRequest(request, repeating);
+            } catch (CameraRuntimeException e) {
+                throw e.asChecked();
+            } catch (RemoteException e) {
+                // impossible
+                return;
+            }
+
+            mCaptureListenerMap.put(requestId, new CaptureListenerHolder(listener, request,
+                    repeating));
+
+            if (repeating) {
+                mRepeatingRequestIdStack.add(requestId);
+            }
+
+        }
+    }
+
+    @Override
+    public void setRepeatingRequest(CaptureRequest request, CaptureListener listener)
+            throws CameraAccessException {
+        submitCaptureRequest(request, listener, /*streaming*/true);
+    }
+
+    @Override
+    public void setRepeatingBurst(List<CaptureRequest> requests, CaptureListener listener)
+            throws CameraAccessException {
+        // TODO
+        throw new UnsupportedOperationException("Burst capture implemented yet");
+    }
+
+    @Override
+    public void stopRepeating() throws CameraAccessException {
+
+        synchronized (mLock) {
+
+            while (!mRepeatingRequestIdStack.isEmpty()) {
+                int requestId = mRepeatingRequestIdStack.pop();
+
+                try {
+                    mRemoteDevice.cancelRequest(requestId);
+                } catch (CameraRuntimeException e) {
+                    throw e.asChecked();
+                } catch (RemoteException e) {
+                    // impossible
+                    return;
+                }
+            }
+        }
+    }
+
+    @Override
+    public void waitUntilIdle() throws CameraAccessException {
+        // TODO: implement
+    }
+
+    @Override
+    public void setErrorListener(ErrorListener listener) {
+        // TODO Auto-generated method stub
+
+    }
+
+    @Override
+    public void close() throws Exception {
+
+        // TODO: every method should throw IllegalStateException after close has been called
+
+        synchronized (mLock) {
+
+            try {
+                mRemoteDevice.disconnect();
+            } catch (CameraRuntimeException e) {
+                throw e.asChecked();
+            } catch (RemoteException e) {
+                // impossible
+            }
+
+            mRemoteDevice = null;
+
+        }
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            close();
+        } catch (CameraRuntimeException e) {
+            Log.e(TAG, "Got error while trying to finalize, ignoring: " + e.getMessage());
+        }
+        finally {
+            super.finalize();
+        }
+    }
+
+    static class CaptureListenerHolder {
+
+        private final boolean mRepeating;
+        private final CaptureListener mListener;
+        private final CaptureRequest mRequest;
+
+        CaptureListenerHolder(CaptureListener listener, CaptureRequest request, boolean repeating) {
+            mRepeating = repeating;
+            mRequest = request;
+            mListener = listener;
+        }
+
+        public boolean isRepeating() {
+            return mRepeating;
+        }
+
+        public CaptureListener getListener() {
+            return mListener;
+        }
+
+        public CaptureRequest getRequest() {
+            return mRequest;
+        }
+    }
+
+    // TODO: unit tests
+    public class CameraDeviceCallbacks extends Binder implements ICameraDeviceCallbacks {
+
+        @Override
+        public IBinder asBinder() {
+            return this;
+        }
+
+        // TODO: consider rename to onMessageReceived
+        @Override
+        public void notifyCallback(int msgType, int ext1, int ext2) throws RemoteException {
+            Log.d(TAG, "Got message " + msgType + " ext1: " + ext1 + " , ext2: " + ext2);
+            // TODO implement rest
+        }
+
+        @Override
+        public void onResultReceived(int frameId, CameraMetadata result) throws RemoteException {
+            Log.d(TAG, "Received result for frameId " + frameId);
+
+            CaptureListenerHolder holder;
+
+            synchronized (mLock) {
+                // TODO: move this whole map into this class to make it more testable,
+                //        exposing the methods necessary like subscribeToRequest, unsubscribe..
+                // TODO: make class static class
+
+                holder = CameraDevice.this.mCaptureListenerMap.get(frameId);
+
+                // Clean up listener once we no longer expect to see it.
+
+                // TODO: how to handle repeating listeners?
+                // we probably want cancelRequest to return # of times it already enqueued and
+                // keep a counter.
+                if (holder != null && !holder.isRepeating()) {
+                    CameraDevice.this.mCaptureListenerMap.remove(frameId);
+                }
+            }
+
+            if (holder == null) {
+                Log.e(TAG, "Result had no listener holder associated with it, dropping result");
+                return;
+            }
+
+            CaptureResult resultAsCapture = new CaptureResult();
+            resultAsCapture.swap(result);
+
+            if (holder.getListener() != null) {
+                holder.getListener().onCaptureComplete(CameraDevice.this, holder.getRequest(),
+                        resultAsCapture);
+            }
+        }
+
+    }
+
+}
diff --git a/core/java/android/hardware/photography/impl/package.html b/core/java/android/hardware/photography/impl/package.html
new file mode 100644
index 0000000..783d0a1
--- /dev/null
+++ b/core/java/android/hardware/photography/impl/package.html
@@ -0,0 +1,3 @@
+<body>
+{@hide}
+</body>
diff --git a/core/java/android/hardware/photography/utils/CameraBinderDecorator.java b/core/java/android/hardware/photography/utils/CameraBinderDecorator.java
index 99e7c78..1a44b97f 100644
--- a/core/java/android/hardware/photography/utils/CameraBinderDecorator.java
+++ b/core/java/android/hardware/photography/utils/CameraBinderDecorator.java
@@ -67,6 +67,8 @@
                     case PERMISSION_DENIED:
                         throw new SecurityException("Lacking privileges to access camera service");
                     case ALREADY_EXISTS:
+                        // This should be handled at the call site. Typically this isn't bad,
+                        // just means we tried to do an operation that already completed.
                         return;
                     case BAD_VALUE:
                         throw new IllegalArgumentException("Bad argument passed to camera service");
diff --git a/core/java/android/hardware/photography/utils/package.html b/core/java/android/hardware/photography/utils/package.html
new file mode 100644
index 0000000..783d0a1
--- /dev/null
+++ b/core/java/android/hardware/photography/utils/package.html
@@ -0,0 +1,3 @@
+<body>
+{@hide}
+</body>
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index faaf588..0a694c7 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -131,6 +131,7 @@
 	android_media_RemoteDisplay.cpp \
 	android_media_ToneGenerator.cpp \
 	android_hardware_Camera.cpp \
+	android_hardware_photography_CameraMetadata.cpp \
 	android_hardware_SensorManager.cpp \
 	android_hardware_SerialPort.cpp \
 	android_hardware_UsbDevice.cpp \
@@ -164,6 +165,7 @@
 	$(call include-path-for, libhardware)/hardware \
 	$(call include-path-for, libhardware_legacy)/hardware_legacy \
 	$(TOP)/frameworks/av/include \
+	$(TOP)/system/media/camera/include \
 	external/skia/src/core \
 	external/skia/src/pdf \
 	external/skia/src/images \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 144cc84..1299579 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -76,6 +76,7 @@
 extern int register_android_opengl_jni_GLES30(JNIEnv* env);
 
 extern int register_android_hardware_Camera(JNIEnv *env);
+extern int register_android_hardware_photography_CameraMetadata(JNIEnv *env);
 extern int register_android_hardware_SensorManager(JNIEnv *env);
 extern int register_android_hardware_SerialPort(JNIEnv *env);
 extern int register_android_hardware_UsbDevice(JNIEnv *env);
@@ -1191,6 +1192,7 @@
     REG_JNI(register_android_os_MemoryFile),
     REG_JNI(register_com_android_internal_os_ZygoteInit),
     REG_JNI(register_android_hardware_Camera),
+    REG_JNI(register_android_hardware_photography_CameraMetadata),
     REG_JNI(register_android_hardware_SensorManager),
     REG_JNI(register_android_hardware_SerialPort),
     REG_JNI(register_android_hardware_UsbDevice),
diff --git a/core/jni/android_hardware_photography_CameraMetadata.cpp b/core/jni/android_hardware_photography_CameraMetadata.cpp
new file mode 100644
index 0000000..fa363f3
--- /dev/null
+++ b/core/jni/android_hardware_photography_CameraMetadata.cpp
@@ -0,0 +1,271 @@
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "CameraMetadata-JNI"
+#include <utils/Log.h>
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "android_os_Parcel.h"
+#include "android_runtime/AndroidRuntime.h"
+
+#include <camera/CameraMetadata.h>
+
+// fully-qualified class name
+#define CAMERA_METADATA_CLASS_NAME "android/hardware/photography/CameraMetadata"
+
+using namespace android;
+
+struct fields_t {
+    jfieldID    metadata_ptr;
+};
+
+static fields_t fields;
+
+extern "C" {
+
+static void CameraMetadata_classInit(JNIEnv *env, jobject thiz);
+
+// Less safe access to native pointer. Does NOT throw any Java exceptions if NULL.
+static CameraMetadata* CameraMetadata_getPointerNoThrow(JNIEnv *env, jobject thiz) {
+
+    if (thiz == NULL) {
+        return NULL;
+    }
+
+    return reinterpret_cast<CameraMetadata*>(env->GetLongField(thiz, fields.metadata_ptr));
+}
+
+// Safe access to native pointer from object. Throws if not possible to access.
+static CameraMetadata* CameraMetadata_getPointerThrow(JNIEnv *env, jobject thiz,
+                                                 const char* argName = "this") {
+
+    if (thiz == NULL) {
+        ALOGV("%s: Throwing java.lang.NullPointerException for null reference",
+              __FUNCTION__);
+        jniThrowNullPointerException(env, argName);
+        return NULL;
+    }
+
+    CameraMetadata* metadata = CameraMetadata_getPointerNoThrow(env, thiz);
+    if (metadata == NULL) {
+        ALOGV("%s: Throwing java.lang.IllegalStateException for closed object",
+              __FUNCTION__);
+        jniThrowException(env, "java/lang/IllegalStateException",
+                            "Metadata object was already closed");
+        return NULL;
+    }
+
+    return metadata;
+}
+
+static jlong CameraMetadata_allocate(JNIEnv *env, jobject thiz) {
+    ALOGV("%s", __FUNCTION__);
+
+    return reinterpret_cast<jlong>(new CameraMetadata());
+}
+
+static jboolean CameraMetadata_isEmpty(JNIEnv *env, jobject thiz) {
+    ALOGV("%s", __FUNCTION__);
+
+    CameraMetadata* metadata = CameraMetadata_getPointerThrow(env, thiz);
+
+    if (metadata == NULL) {
+        ALOGW("%s: Returning early due to exception being thrown",
+               __FUNCTION__);
+        return JNI_TRUE; // actually throws java exc.
+    }
+
+    jboolean empty = metadata->isEmpty();
+
+    ALOGV("%s: Empty returned %d, entry count was %d",
+          __FUNCTION__, empty, metadata->entryCount());
+
+    return empty;
+}
+
+static jint CameraMetadata_getEntryCount(JNIEnv *env, jobject thiz) {
+    ALOGV("%s", __FUNCTION__);
+
+    CameraMetadata* metadata = CameraMetadata_getPointerThrow(env, thiz);
+
+    if (metadata == NULL) return 0; // actually throws java exc.
+
+    return metadata->entryCount();
+}
+
+// idempotent. calling more than once has no effect.
+static void CameraMetadata_close(JNIEnv *env, jobject thiz) {
+    ALOGV("%s", __FUNCTION__);
+
+    CameraMetadata* metadata = CameraMetadata_getPointerNoThrow(env, thiz);
+
+    if (metadata != NULL) {
+        delete metadata;
+        env->SetLongField(thiz, fields.metadata_ptr, 0);
+    }
+
+    LOG_ALWAYS_FATAL_IF(CameraMetadata_getPointerNoThrow(env, thiz) != NULL,
+                        "Expected the native ptr to be 0 after #close");
+}
+
+static void CameraMetadata_swap(JNIEnv *env, jobject thiz, jobject other) {
+    ALOGV("%s", __FUNCTION__);
+
+    CameraMetadata* metadata = CameraMetadata_getPointerThrow(env, thiz);
+
+    // order is important: we can't call another JNI method
+    // if there is an exception pending
+    if (metadata == NULL) return;
+
+    CameraMetadata* otherMetadata = CameraMetadata_getPointerThrow(env, other, "other");
+
+    if (otherMetadata == NULL) return;
+
+    metadata->swap(*otherMetadata);
+}
+
+static void CameraMetadata_readFromParcel(JNIEnv *env, jobject thiz, jobject parcel) {
+    ALOGV("%s", __FUNCTION__);
+    CameraMetadata* metadata = CameraMetadata_getPointerThrow(env, thiz);
+    if (metadata == NULL) {
+        return;
+    }
+
+    Parcel* parcelNative = parcelForJavaObject(env, parcel);
+    if (parcelNative == NULL) {
+        jniThrowNullPointerException(env, "parcel");
+        return;
+    }
+
+    status_t err;
+    if ((err = metadata->readFromParcel(parcelNative)) != OK) {
+        jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+                             "Failed to read from parcel (error code %d)", err);
+        return;
+    }
+}
+
+static void CameraMetadata_writeToParcel(JNIEnv *env, jobject thiz, jobject parcel) {
+    ALOGV("%s", __FUNCTION__);
+    CameraMetadata* metadata = CameraMetadata_getPointerThrow(env, thiz);
+    if (metadata == NULL) {
+        return;
+    }
+
+    Parcel* parcelNative = parcelForJavaObject(env, parcel);
+    if (parcelNative == NULL) {
+        jniThrowNullPointerException(env, "parcel");
+        return;
+    }
+
+    status_t err;
+    if ((err = metadata->writeToParcel(parcelNative)) != OK) {
+        jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+                                  "Failed to write to parcel (error code %d)", err);
+        return;
+    }
+}
+
+} // extern "C"
+
+//-------------------------------------------------
+
+static JNINativeMethod gCameraMetadataMethods[] = {
+  { "nativeClassInit",
+    "()V",
+    (void *)CameraMetadata_classInit },
+  { "nativeAllocate",
+    "()J",
+    (void*)CameraMetadata_allocate },
+  { "nativeIsEmpty",
+    "()Z",
+    (void*)CameraMetadata_isEmpty },
+  { "nativeGetEntryCount",
+    "()I",
+    (void*)CameraMetadata_getEntryCount },
+  { "nativeClose",
+    "()V",
+    (void*)CameraMetadata_close },
+  { "nativeSwap",
+    "(L" CAMERA_METADATA_CLASS_NAME ";)V",
+    (void *)CameraMetadata_swap },
+  { "nativeReadFromParcel",
+    "(Landroid/os/Parcel;)V",
+    (void *)CameraMetadata_readFromParcel },
+  { "nativeWriteToParcel",
+    "(Landroid/os/Parcel;)V",
+    (void *)CameraMetadata_writeToParcel },
+};
+
+struct field {
+    const char *class_name;
+    const char *field_name;
+    const char *field_type;
+    jfieldID   *jfield;
+};
+
+static int find_fields(JNIEnv *env, field *fields, int count)
+{
+    for (int i = 0; i < count; i++) {
+        field *f = &fields[i];
+        jclass clazz = env->FindClass(f->class_name);
+        if (clazz == NULL) {
+            ALOGE("Can't find %s", f->class_name);
+            return -1;
+        }
+
+        jfieldID field = env->GetFieldID(clazz, f->field_name, f->field_type);
+        if (field == NULL) {
+            ALOGE("Can't find %s.%s", f->class_name, f->field_name);
+            return -1;
+        }
+
+        *(f->jfield) = field;
+    }
+
+    return 0;
+}
+
+// Get all the required offsets in java class and register native functions
+int register_android_hardware_photography_CameraMetadata(JNIEnv *env)
+{
+    // Register native functions
+    return AndroidRuntime::registerNativeMethods(env,
+            CAMERA_METADATA_CLASS_NAME,
+            gCameraMetadataMethods,
+            NELEM(gCameraMetadataMethods));
+}
+
+extern "C" {
+static void CameraMetadata_classInit(JNIEnv *env, jobject thiz) {
+    // XX: Why do this separately instead of doing it in the register function?
+    ALOGV("%s", __FUNCTION__);
+
+    field fields_to_find[] = {
+        { CAMERA_METADATA_CLASS_NAME, "mMetadataPtr", "J", &fields.metadata_ptr },
+    };
+
+    // Do this here instead of in register_native_methods,
+    // since otherwise it will fail to find the fields.
+    if (find_fields(env, fields_to_find, NELEM(fields_to_find)) < 0)
+        return;
+
+    jclass clazz = env->FindClass(CAMERA_METADATA_CLASS_NAME);
+}
+} // extern "C"