OSDN Git Service

Initial camera device implementation
authorIgor Murashkin <iam@google.com>
Tue, 25 Jun 2013 20:27:06 +0000 (20:27 +0000)
committerIgor Murashkin <iam@google.com>
Wed, 26 Jun 2013 20:19:45 +0000 (13:19 -0700)
* Working streaming preview requests only
* Almost everything else returns empty objects that don't do anything

Bug: 9213377
Change-Id: Ie6f02a7c0952b0f5ebc41905425b15cae221f7d3

26 files changed:
Android.mk
api/current.txt
core/java/android/hardware/ICameraService.aidl
core/java/android/hardware/photography/CameraDevice.java
core/java/android/hardware/photography/CameraManager.java
core/java/android/hardware/photography/CameraMetadata.aidl [new file with mode: 0644]
core/java/android/hardware/photography/CameraMetadata.java
core/java/android/hardware/photography/CameraProperties.java
core/java/android/hardware/photography/CaptureRequest.aidl [new file with mode: 0644]
core/java/android/hardware/photography/CaptureRequest.java
core/java/android/hardware/photography/CaptureResult.java
core/java/android/hardware/photography/ICameraDeviceCallbacks.aidl [new file with mode: 0644]
core/java/android/hardware/photography/ICameraDeviceUser.aidl [new file with mode: 0644]
core/java/android/hardware/photography/impl/CameraDevice.java [new file with mode: 0644]
core/java/android/hardware/photography/impl/package.html [new file with mode: 0644]
core/java/android/hardware/photography/utils/CameraBinderDecorator.java
core/java/android/hardware/photography/utils/package.html [new file with mode: 0644]
core/jni/Android.mk
core/jni/AndroidRuntime.cpp
core/jni/android_hardware_photography_CameraMetadata.cpp [new file with mode: 0644]
media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkIntegrationTestRunner.java
media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java
media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTestUtils.java [new file with mode: 0644]
media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java [new file with mode: 0644]
media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java [new file with mode: 0644]

index 603f5c3..db6a704 100644 (file)
@@ -125,6 +125,8 @@ LOCAL_SRC_FILES += \
        core/java/android/hardware/ICameraClient.aidl \
        core/java/android/hardware/IProCameraUser.aidl \
        core/java/android/hardware/IProCameraCallbacks.aidl \
+       core/java/android/hardware/photography/ICameraDeviceUser.aidl \
+       core/java/android/hardware/photography/ICameraDeviceCallbacks.aidl \
        core/java/android/hardware/ISerialManager.aidl \
        core/java/android/hardware/display/IDisplayManager.aidl \
        core/java/android/hardware/display/IDisplayManagerCallback.aidl \
index dc695d5..8d11dd0 100644 (file)
@@ -10665,18 +10665,18 @@ package android.hardware.photography {
     field public static final int MAX_CAMERAS_IN_USE = 2; // 0x2
   }
 
-  public final class CameraDevice implements java.lang.AutoCloseable {
-    method public void capture(android.hardware.photography.CaptureRequest, android.hardware.photography.CameraDevice.CaptureListener) throws android.hardware.photography.CameraAccessException;
-    method public void captureBurst(java.util.List<android.hardware.photography.CaptureRequest>, android.hardware.photography.CameraDevice.CaptureListener) throws android.hardware.photography.CameraAccessException;
-    method public void close();
-    method public void configureOutputs(java.util.List<android.view.Surface>);
-    method public android.hardware.photography.CaptureRequest createCaptureRequest(int) throws android.hardware.photography.CameraAccessException;
-    method public android.hardware.photography.CameraProperties getProperties() throws android.hardware.photography.CameraAccessException;
-    method public void setErrorListener(android.hardware.photography.CameraDevice.ErrorListener);
-    method public void setRepeatingBurst(java.util.List<android.hardware.photography.CaptureRequest>, android.hardware.photography.CameraDevice.CaptureListener) throws android.hardware.photography.CameraAccessException;
-    method public void setRepeatingRequest(android.hardware.photography.CaptureRequest, android.hardware.photography.CameraDevice.CaptureListener) throws android.hardware.photography.CameraAccessException;
-    method public void stopRepeating() throws android.hardware.photography.CameraAccessException;
-    method public void waitUntilIdle() throws android.hardware.photography.CameraAccessException;
+  public abstract interface CameraDevice implements java.lang.AutoCloseable {
+    method public abstract void capture(android.hardware.photography.CaptureRequest, android.hardware.photography.CameraDevice.CaptureListener) throws android.hardware.photography.CameraAccessException;
+    method public abstract void captureBurst(java.util.List<android.hardware.photography.CaptureRequest>, android.hardware.photography.CameraDevice.CaptureListener) throws android.hardware.photography.CameraAccessException;
+    method public abstract void close() throws java.lang.Exception;
+    method public abstract void configureOutputs(java.util.List<android.view.Surface>) throws android.hardware.photography.CameraAccessException;
+    method public abstract android.hardware.photography.CaptureRequest createCaptureRequest(int) throws android.hardware.photography.CameraAccessException;
+    method public abstract android.hardware.photography.CameraProperties getProperties() throws android.hardware.photography.CameraAccessException;
+    method public abstract void setErrorListener(android.hardware.photography.CameraDevice.ErrorListener);
+    method public abstract void setRepeatingBurst(java.util.List<android.hardware.photography.CaptureRequest>, android.hardware.photography.CameraDevice.CaptureListener) throws android.hardware.photography.CameraAccessException;
+    method public abstract void setRepeatingRequest(android.hardware.photography.CaptureRequest, android.hardware.photography.CameraDevice.CaptureListener) throws android.hardware.photography.CameraAccessException;
+    method public abstract void stopRepeating() throws android.hardware.photography.CameraAccessException;
+    method public abstract void waitUntilIdle() throws android.hardware.photography.CameraAccessException;
     field public static final int TEMPLATE_MANUAL = 5; // 0x5
     field public static final int TEMPLATE_PREVIEW = 1; // 0x1
     field public static final int TEMPLATE_RECORD = 2; // 0x2
@@ -10709,10 +10709,12 @@ package android.hardware.photography {
     method public abstract void onCameraUnavailable(java.lang.String);
   }
 
-  public class CameraMetadata implements android.os.Parcelable {
+  public class CameraMetadata implements java.lang.AutoCloseable android.os.Parcelable {
     ctor public CameraMetadata();
+    method public void close() throws java.lang.Exception;
     method public int describeContents();
     method public T get(android.hardware.photography.CameraMetadata.Key<T>);
+    method public void readFromParcel(android.os.Parcel);
     method public void set(android.hardware.photography.CameraMetadata.Key<T>, T);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator CREATOR;
@@ -10741,9 +10743,10 @@ package android.hardware.photography {
     field public static final android.hardware.photography.CameraMetadata.Key SENSOR_PIXEL_ARRAY_SIZE;
   }
 
-  public final class CaptureRequest extends android.hardware.photography.CameraMetadata {
+  public final class CaptureRequest extends android.hardware.photography.CameraMetadata implements android.os.Parcelable {
     method public void addTarget(android.view.Surface);
     method public void removeTarget(android.view.Surface);
+    field public static final android.os.Parcelable.Creator CREATOR;
     field public static final android.hardware.photography.CameraMetadata.Key SENSOR_EXPOSURE_TIME;
     field public static final android.hardware.photography.CameraMetadata.Key SENSOR_SENSITIVITY;
   }
index b8fbfdb..2d0c099 100644 (file)
@@ -20,6 +20,8 @@ import android.hardware.ICamera;
 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 @@ interface ICameraService
                               String clientPackageName,
                               int clientUid);
 
+    ICameraDeviceUser connectDevice(ICameraDeviceCallbacks callbacks, int cameraId,
+                              String clientPackageName,
+                              int clientUid);
+
     int addListener(ICameraServiceListener listener);
     int removeListener(ICameraServiceListener listener);
 }
index e94e3a1..5fb14dc 100644 (file)
 
 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 @@ import java.util.List;
  * @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 final class CameraDevice implements AutoCloseable {
      */
     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 @@ public final class CameraDevice implements AutoCloseable {
      *
      * @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 @@ public final class CameraDevice implements AutoCloseable {
      * @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 @@ public final class CameraDevice implements AutoCloseable {
      * @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 @@ public final class CameraDevice implements AutoCloseable {
      * @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 @@ public final class CameraDevice implements AutoCloseable {
      * @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 @@ public final class CameraDevice implements AutoCloseable {
      * @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 @@ public final class CameraDevice implements AutoCloseable {
      * @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 @@ public final class CameraDevice implements AutoCloseable {
      * 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 @@ public final class CameraDevice implements AutoCloseable {
      * 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 @@ public final class CameraDevice implements AutoCloseable {
      * 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 final class CameraDevice implements AutoCloseable {
          */
         public void onCameraDeviceError(CameraDevice camera, int error);
     }
-
-    /**
-     * @hide
-     */
-    public CameraDevice(IBinder binder) {
-        Log.e(TAG, "CameraDevice constructor not implemented yet");
-    }
-
 }
index 115f151..c1c9435 100644 (file)
@@ -94,7 +94,13 @@ public final class CameraManager {
      */
     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 final class CameraManager {
     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 @@ public final class CameraManager {
 
         @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 (file)
index 0000000..b4dc9ac
--- /dev/null
@@ -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;
index 385a1b9..5488952 100644 (file)
@@ -18,6 +18,8 @@ package android.hardware.photography;
 
 import android.os.Parcelable;
 import android.os.Parcel;
+import android.util.Log;
+
 import java.util.HashMap;
 import java.util.Map;
 
@@ -32,27 +34,34 @@ import java.util.Map;
  * @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 @@ public class CameraMetadata implements Parcelable {
      * 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 @@ public class CameraMetadata implements Parcelable {
      */
     @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 @@ public class CameraMetadata implements Parcelable {
 
     @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 @@ public class CameraMetadata implements Parcelable {
         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
index 1bfd712..ad42285 100644 (file)
@@ -39,7 +39,7 @@ public final class CameraProperties extends CameraMetadata {
      * {@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 @@ public final class CameraProperties extends CameraMetadata {
      * 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 @@ public final class CameraProperties extends CameraMetadata {
      * 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 @@ public final class CameraProperties extends CameraMetadata {
      * @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 @@ public final class CameraProperties extends CameraMetadata {
      * {@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 @@ public final class CameraProperties extends CameraMetadata {
      * 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 @@ public final class CameraProperties extends CameraMetadata {
      * 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 @@ public final class CameraProperties extends CameraMetadata {
      * 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 @@ public final class CameraProperties extends CameraMetadata {
      * 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 @@ public final class CameraProperties extends CameraMetadata {
      * 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 (file)
index 0000000..64fb6f2
--- /dev/null
@@ -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;
index a54c743..ac2041b 100644 (file)
 
 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 @@ import java.util.List;
  * @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 @@ public final class CaptureRequest extends CameraMetadata {
      *
      * <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
index dd36f1d..b502c4c 100644 (file)
@@ -101,7 +101,10 @@ public final class CaptureResult extends CameraMetadata {
 
     // 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 (file)
index 0000000..c506800
--- /dev/null
@@ -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 (file)
index 0000000..d1310fb
--- /dev/null
@@ -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 (file)
index 0000000..b1e3f6a
--- /dev/null
@@ -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 (file)
index 0000000..783d0a1
--- /dev/null
@@ -0,0 +1,3 @@
+<body>
+{@hide}
+</body>
index 99e7c78..1a44b97 100644 (file)
@@ -67,6 +67,8 @@ public class CameraBinderDecorator {
                     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 (file)
index 0000000..783d0a1
--- /dev/null
@@ -0,0 +1,3 @@
+<body>
+{@hide}
+</body>
index faaf588..0a694c7 100644 (file)
@@ -131,6 +131,7 @@ LOCAL_SRC_FILES:= \
        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 @@ LOCAL_C_INCLUDES += \
        $(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 \
index 144cc84..1299579 100644 (file)
@@ -76,6 +76,7 @@ extern int register_android_opengl_jni_GLES20(JNIEnv* env);
 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 @@ static const RegJNIRec gRegJNI[] = {
     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 (file)
index 0000000..fa363f3
--- /dev/null
@@ -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"
index 88c5b0c..7751fcc 100644 (file)
@@ -19,8 +19,10 @@ package com.android.mediaframeworktest;
 import android.os.Bundle;
 import android.test.InstrumentationTestRunner;
 import android.test.InstrumentationTestSuite;
+import android.util.Log;
 
 import com.android.mediaframeworktest.integration.CameraBinderTest;
+import com.android.mediaframeworktest.integration.CameraDeviceBinderTest;
 
 import junit.framework.TestSuite;
 
@@ -34,11 +36,15 @@ import junit.framework.TestSuite;
 
 public class MediaFrameworkIntegrationTestRunner extends InstrumentationTestRunner {
 
+    private static final String TAG = "MediaFrameworkIntegrationTestRunner";
+
+    public static int mCameraId = 0;
 
     @Override
     public TestSuite getAllTests() {
         TestSuite suite = new InstrumentationTestSuite(this);
         suite.addTestSuite(CameraBinderTest.class);
+        suite.addTestSuite(CameraDeviceBinderTest.class);
         return suite;
     }
 
@@ -50,5 +56,17 @@ public class MediaFrameworkIntegrationTestRunner extends InstrumentationTestRunn
     @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
+
+        String cameraId = (String) icicle.get("camera_id");
+        if (cameraId != null) {
+            try {
+                Log.v(TAG,
+                        String.format("Reading camera_id from icicle: '%s'", cameraId));
+                mCameraId = Integer.parseInt(cameraId);
+            }
+            catch (NumberFormatException e) {
+                Log.e(TAG, String.format("Failed to convert camera_id to integer"));
+            }
+        }
     }
 }
index e9fbca7..ecdc287 100644 (file)
@@ -62,6 +62,7 @@ public class MediaFrameworkUnitTestRunner extends InstrumentationTestRunner {
         suite.addTestSuite(CameraUtilsRuntimeExceptionTest.class);
         suite.addTestSuite(CameraUtilsUncheckedThrowTest.class);
         suite.addTestSuite(CameraUtilsBinderDecoratorTest.class);
+        suite.addTestSuite(CameraMetadataTest.class);
     }
 
     // Running all unit tests checking the state machine may be time-consuming.
index ba2859b..bc3adc9 100644 (file)
 
 package com.android.mediaframeworktest.integration;
 
-import android.content.Context;
-import android.content.pm.FeatureInfo;
-import android.content.pm.PackageManager;
 import android.hardware.CameraInfo;
 import android.hardware.ICamera;
 import android.hardware.ICameraClient;
-import android.hardware.ICameraService;
 import android.hardware.ICameraServiceListener;
 import android.hardware.IProCameraCallbacks;
 import android.hardware.IProCameraUser;
+import android.hardware.photography.CameraMetadata;
+import android.hardware.photography.ICameraDeviceCallbacks;
+import android.hardware.photography.ICameraDeviceUser;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
 
 /**
+ * <p>
  * Junit / Instrumentation test case for the camera2 api
+ * </p>
+ * <p>
+ * To run only tests in this class:
+ * </p>
  *
- * To run only tests in
- * this class:
- *
+ * <pre>
  * adb shell am instrument \
- *      -e class com.android.mediaframeworktest.integration.CameraBinderTest \
- *      -w com.android.mediaframeworktest/.MediaFrameworkIntegrationTestRunner
+ *   -e class com.android.mediaframeworktest.integration.CameraBinderTest \
+ *   -w com.android.mediaframeworktest/.MediaFrameworkIntegrationTestRunner
+ * </pre>
  */
 public class CameraBinderTest extends AndroidTestCase {
-    private static String TAG = "CameraBinderTest";
-
-    private static final String CAMERA_SERVICE_BINDER_NAME = "media.camera";
-    private static final int NO_ERROR = 0;
-    private static final int ALREADY_EXISTS = -17;
-    private static final int BAD_VALUE = -22;
-
-    private static final int USE_CALLING_UID = -1;
+    static String TAG = "CameraBinderTest";
 
-    private ICameraService mCameraService;
-    private int mGuessedNumCameras = 0;
+    protected CameraBinderTestUtils mUtils;
 
     public CameraBinderTest() {
     }
 
-    private final static boolean isFeatureAvailable(Context context, String feature) {
-        final PackageManager packageManager = context.getPackageManager();
-        final FeatureInfo[] featuresList = packageManager.getSystemAvailableFeatures();
-        for (FeatureInfo f : featuresList) {
-            if (f.name != null && f.name.equals(feature)) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    // Guess the lower bound for how many cameras there are
-    private void guessNumCameras() {
-
-        /**
-         * Why do we need this? This way we have no dependency on getNumCameras
-         * actually working. On most systems there are only 0, 1, or 2 cameras,
-         * and this covers that 'usual case'. On other systems there might be 3+
-         * cameras, but this will at least check the first 2.
-         */
-        mGuessedNumCameras = 0;
-
-        // Front facing camera
-        if (isFeatureAvailable(getContext(), PackageManager.FEATURE_CAMERA_FRONT)) {
-            mGuessedNumCameras++;
-        }
-
-        // Back facing camera
-        if (isFeatureAvailable(getContext(), PackageManager.FEATURE_CAMERA)) {
-            mGuessedNumCameras++;
-        }
-
-        // Any facing camera
-        if (mGuessedNumCameras == 0
-                && isFeatureAvailable(getContext(), PackageManager.FEATURE_CAMERA_ANY)) {
-            mGuessedNumCameras++;
-        }
-
-        Log.v(TAG, "Guessing there are at least " + mGuessedNumCameras + " cameras");
-    }
-
+    @Override
     protected void setUp() throws Exception {
         super.setUp();
 
-        guessNumCameras();
-
-        IBinder cameraServiceBinder = ServiceManager.getService(CAMERA_SERVICE_BINDER_NAME);
-        assertNotNull("Camera service IBinder should not be null", cameraServiceBinder);
-
-        mCameraService = ICameraService.Stub.asInterface(cameraServiceBinder);
-        assertNotNull("Camera service should not be null", mCameraService);
+        mUtils = new CameraBinderTestUtils(getContext());
     }
 
     @SmallTest
     public void testNumberOfCameras() throws Exception {
-        int numCameras = mCameraService.getNumberOfCameras();
-        assertTrue("At least this many cameras: " + mGuessedNumCameras,
-                numCameras >= mGuessedNumCameras);
+
+        int numCameras = mUtils.getCameraService().getNumberOfCameras();
+        assertTrue("At least this many cameras: " + mUtils.getGuessedNumCameras(),
+                numCameras >= mUtils.getGuessedNumCameras());
         Log.v(TAG, "Number of cameras " + numCameras);
     }
 
     @SmallTest
     public void testCameraInfo() throws Exception {
-        for (int cameraId = 0; cameraId < mGuessedNumCameras; ++cameraId) {
+        for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) {
 
             CameraInfo info = new CameraInfo();
             info.info.facing = -1;
             info.info.orientation = -1;
 
-            assertTrue("Camera service returned info for camera " + cameraId,
-                    mCameraService.getCameraInfo(cameraId, info) == NO_ERROR);
+            assertTrue(
+                    "Camera service returned info for camera " + cameraId,
+                    mUtils.getCameraService().getCameraInfo(cameraId, info) ==
+                    CameraBinderTestUtils.NO_ERROR);
             assertTrue("Facing was not set for camera " + cameraId, info.info.facing != -1);
             assertTrue("Orientation was not set for camera " + cameraId,
                     info.info.orientation != -1);
@@ -153,14 +103,15 @@ public class CameraBinderTest extends AndroidTestCase {
 
     @SmallTest
     public void testConnect() throws Exception {
-        for (int cameraId = 0; cameraId < mGuessedNumCameras; ++cameraId) {
+        for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) {
 
             ICameraClient dummyCallbacks = new DummyCameraClient();
 
             String clientPackageName = getContext().getPackageName();
 
-            ICamera cameraUser = mCameraService.connect(dummyCallbacks, cameraId, clientPackageName,
-                    USE_CALLING_UID);
+            ICamera cameraUser = mUtils.getCameraService().connect(dummyCallbacks, cameraId,
+                    clientPackageName,
+                    CameraBinderTestUtils.USE_CALLING_UID);
             assertNotNull(String.format("Camera %s was null", cameraId), cameraUser);
 
             Log.v(TAG, String.format("Camera %s connected", cameraId));
@@ -174,14 +125,45 @@ public class CameraBinderTest extends AndroidTestCase {
 
     @SmallTest
     public void testConnectPro() throws Exception {
-        for (int cameraId = 0; cameraId < mGuessedNumCameras; ++cameraId) {
+        for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) {
 
             IProCameraCallbacks dummyCallbacks = new DummyProCameraCallbacks();
 
             String clientPackageName = getContext().getPackageName();
 
-            IProCameraUser cameraUser = mCameraService.connectPro(dummyCallbacks, cameraId,
-                    clientPackageName, USE_CALLING_UID);
+            IProCameraUser cameraUser = mUtils.getCameraService().connectPro(dummyCallbacks,
+                    cameraId,
+                    clientPackageName, CameraBinderTestUtils.USE_CALLING_UID);
+            assertNotNull(String.format("Camera %s was null", cameraId), cameraUser);
+
+            Log.v(TAG, String.format("Camera %s connected", cameraId));
+
+            cameraUser.disconnect();
+        }
+    }
+
+    static class DummyCameraDeviceCallbacks extends DummyBase implements ICameraDeviceCallbacks {
+
+        @Override
+        public void notifyCallback(int msgType, int ext1, int ext2) throws RemoteException {
+        }
+
+        @Override
+        public void onResultReceived(int frameId, CameraMetadata result) throws RemoteException {
+        }
+    }
+
+    @SmallTest
+    public void testConnectDevice() throws Exception {
+        for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) {
+
+            ICameraDeviceCallbacks dummyCallbacks = new DummyCameraDeviceCallbacks();
+
+            String clientPackageName = getContext().getPackageName();
+
+            ICameraDeviceUser cameraUser = mUtils.getCameraService().connectDevice(dummyCallbacks,
+                    cameraId,
+                    clientPackageName, CameraBinderTestUtils.USE_CALLING_UID);
             assertNotNull(String.format("Camera %s was null", cameraId), cameraUser);
 
             Log.v(TAG, String.format("Camera %s connected", cameraId));
@@ -199,27 +181,39 @@ public class CameraBinderTest extends AndroidTestCase {
     }
 
     /**
+     * <pre>
      * adb shell am instrument \
-     *     -e class 'com.android.mediaframeworktest.integration.CameraBinderTest#testAddRemoveListeners' \
-     *     -w com.android.mediaframeworktest/.MediaFrameworkIntegrationTestRunner
+     *   -e class 'com.android.mediaframeworktest.integration.CameraBinderTest#testAddRemoveListeners' \
+     *   -w com.android.mediaframeworktest/.MediaFrameworkIntegrationTestRunner
+     * </pre>
      */
     @SmallTest
     public void testAddRemoveListeners() throws Exception {
-        for (int cameraId = 0; cameraId < mGuessedNumCameras; ++cameraId) {
+        for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) {
 
             ICameraServiceListener listener = new DummyCameraServiceListener();
 
-            assertTrue("Listener was removed before added",
-                    mCameraService.removeListener(listener) == BAD_VALUE);
-
-            assertTrue("Listener was not added", mCameraService.addListener(listener) == NO_ERROR);
-            assertTrue("Listener was wrongly added again",
-                    mCameraService.addListener(listener) == ALREADY_EXISTS);
-
-            assertTrue("Listener was not removed",
-                    mCameraService.removeListener(listener) == NO_ERROR);
-            assertTrue("Listener was wrongly removed again",
-                    mCameraService.removeListener(listener) == BAD_VALUE);
+            assertTrue(
+                    "Listener was removed before added",
+                    mUtils.getCameraService().removeListener(listener) ==
+                    CameraBinderTestUtils.BAD_VALUE);
+
+            assertTrue("Listener was not added",
+                    mUtils.getCameraService().addListener(listener) ==
+                    CameraBinderTestUtils.NO_ERROR);
+            assertTrue(
+                    "Listener was wrongly added again",
+                    mUtils.getCameraService().addListener(listener) ==
+                    CameraBinderTestUtils.ALREADY_EXISTS);
+
+            assertTrue(
+                    "Listener was not removed",
+                    mUtils.getCameraService().removeListener(listener) ==
+                    CameraBinderTestUtils.NO_ERROR);
+            assertTrue(
+                    "Listener was wrongly removed again",
+                    mUtils.getCameraService().removeListener(listener) ==
+                    CameraBinderTestUtils.BAD_VALUE);
         }
     }
 }
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTestUtils.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTestUtils.java
new file mode 100644 (file)
index 0000000..f0fc817
--- /dev/null
@@ -0,0 +1,92 @@
+
+package com.android.mediaframeworktest.integration;
+
+import static org.junit.Assert.assertNotNull;
+
+import android.content.Context;
+import android.content.pm.FeatureInfo;
+import android.content.pm.PackageManager;
+import android.hardware.ICameraService;
+import android.os.IBinder;
+import android.os.ServiceManager;
+import android.util.Log;
+
+public class CameraBinderTestUtils {
+    private final ICameraService mCameraService;
+    private int mGuessedNumCameras;
+
+    static final String CAMERA_SERVICE_BINDER_NAME = "media.camera";
+
+    protected static final int USE_CALLING_UID = -1;
+    protected static final int BAD_VALUE = -22;
+    protected static final int ALREADY_EXISTS = -17;
+    protected static final int NO_ERROR = 0;
+    private final Context mContext;
+
+    public CameraBinderTestUtils(Context context) {
+
+        mContext = context;
+
+        guessNumCameras();
+
+        IBinder cameraServiceBinder = ServiceManager
+                .getService(CameraBinderTestUtils.CAMERA_SERVICE_BINDER_NAME);
+        assertNotNull("Camera service IBinder should not be null", cameraServiceBinder);
+
+        this.mCameraService = ICameraService.Stub.asInterface(cameraServiceBinder);
+        assertNotNull("Camera service should not be null", getCameraService());
+    }
+
+    private void guessNumCameras() {
+
+        /**
+         * Why do we need this? This way we have no dependency on getNumCameras
+         * actually working. On most systems there are only 0, 1, or 2 cameras,
+         * and this covers that 'usual case'. On other systems there might be 3+
+         * cameras, but this will at least check the first 2.
+         */
+        this.mGuessedNumCameras = 0;
+
+        // Front facing camera
+        if (CameraBinderTestUtils.isFeatureAvailable(mContext,
+                PackageManager.FEATURE_CAMERA_FRONT)) {
+            this.mGuessedNumCameras = getGuessedNumCameras() + 1;
+        }
+
+        // Back facing camera
+        if (CameraBinderTestUtils.isFeatureAvailable(mContext,
+                PackageManager.FEATURE_CAMERA)) {
+            this.mGuessedNumCameras = getGuessedNumCameras() + 1;
+        }
+
+        // Any facing camera
+        if (getGuessedNumCameras() == 0
+                && CameraBinderTestUtils.isFeatureAvailable(mContext,
+                        PackageManager.FEATURE_CAMERA_ANY)) {
+            this.mGuessedNumCameras = getGuessedNumCameras() + 1;
+        }
+
+        Log.v(CameraBinderTest.TAG, "Guessing there are at least " + getGuessedNumCameras()
+                + " cameras");
+    }
+
+    final static public boolean isFeatureAvailable(Context context, String feature) {
+        final PackageManager packageManager = context.getPackageManager();
+        final FeatureInfo[] featuresList = packageManager.getSystemAvailableFeatures();
+        for (FeatureInfo f : featuresList) {
+            if (f.name != null && f.name.equals(feature)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    ICameraService getCameraService() {
+        return mCameraService;
+    }
+
+    int getGuessedNumCameras() {
+        return mGuessedNumCameras;
+    }
+}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
new file mode 100644 (file)
index 0000000..0cbb989
--- /dev/null
@@ -0,0 +1,297 @@
+/*
+ * 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 com.android.mediaframeworktest.integration;
+
+import android.graphics.SurfaceTexture;
+import android.hardware.photography.CameraMetadata;
+import android.hardware.photography.CaptureRequest;
+import android.hardware.photography.ICameraDeviceCallbacks;
+import android.hardware.photography.ICameraDeviceUser;
+import android.os.RemoteException;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+import android.view.Surface;
+
+import static android.hardware.photography.CameraDevice.TEMPLATE_PREVIEW;
+
+import com.android.mediaframeworktest.MediaFrameworkIntegrationTestRunner;
+import com.android.mediaframeworktest.integration.CameraBinderTest.DummyBase;
+
+public class CameraDeviceBinderTest extends AndroidTestCase {
+    private static String TAG = "CameraDeviceBinderTest";
+
+    private int mCameraId;
+    private ICameraDeviceUser mCameraUser;
+    private CameraBinderTestUtils mUtils;
+
+    public CameraDeviceBinderTest() {
+    }
+
+    static class DummyCameraDeviceCallbacks extends DummyBase implements ICameraDeviceCallbacks {
+
+        @Override
+        public void notifyCallback(int msgType, int ext1, int ext2) throws RemoteException {
+        }
+
+        @Override
+        public void onResultReceived(int frameId, CameraMetadata result) throws RemoteException {
+        }
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mUtils = new CameraBinderTestUtils(getContext());
+
+        // This cannot be set in the constructor, since the onCreate isn't
+        // called yet
+        mCameraId = MediaFrameworkIntegrationTestRunner.mCameraId;
+
+        ICameraDeviceCallbacks dummyCallbacks = new DummyCameraDeviceCallbacks();
+
+        String clientPackageName = getContext().getPackageName();
+
+        mCameraUser = mUtils.getCameraService().connectDevice(dummyCallbacks, mCameraId,
+                clientPackageName, CameraBinderTestUtils.USE_CALLING_UID);
+        assertNotNull(String.format("Camera %s was null", mCameraId), mCameraUser);
+
+        Log.v(TAG, String.format("Camera %s connected", mCameraId));
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mCameraUser.disconnect();
+        mCameraUser = null;
+    }
+
+    @SmallTest
+    public void testCreateDefaultRequest() throws Exception {
+        CameraMetadata metadata = new CameraMetadata();
+        assertTrue(metadata.isEmpty());
+
+        int status = mCameraUser.createDefaultRequest(TEMPLATE_PREVIEW, /* out */metadata);
+        assertEquals(CameraBinderTestUtils.NO_ERROR, status);
+        assertFalse(metadata.isEmpty());
+
+        metadata.close();
+    }
+
+    @SmallTest
+    public void testCreateStream() throws Exception {
+        SurfaceTexture surfaceTexture = new SurfaceTexture(/* ignored */0);
+        surfaceTexture.setDefaultBufferSize(640, 480);
+        Surface surface = new Surface(surfaceTexture);
+
+        int streamId = mCameraUser.createStream(/* ignored */10, /* ignored */20, /* ignored */30,
+                surface);
+
+        assertEquals(0, streamId);
+
+        assertEquals(CameraBinderTestUtils.ALREADY_EXISTS,
+                mCameraUser.createStream(/* ignored */0, /* ignored */0, /* ignored */0, surface));
+
+        assertEquals(CameraBinderTestUtils.NO_ERROR, mCameraUser.deleteStream(streamId));
+    }
+
+    @SmallTest
+    public void testDeleteInvalidStream() throws Exception {
+        assertEquals(CameraBinderTestUtils.BAD_VALUE, mCameraUser.deleteStream(-1));
+        assertEquals(CameraBinderTestUtils.BAD_VALUE, mCameraUser.deleteStream(0));
+        assertEquals(CameraBinderTestUtils.BAD_VALUE, mCameraUser.deleteStream(1));
+        assertEquals(CameraBinderTestUtils.BAD_VALUE, mCameraUser.deleteStream(0xC0FFEE));
+    }
+
+    @SmallTest
+    public void testCreateStreamTwo() throws Exception {
+
+        // Create first stream
+
+        SurfaceTexture surfaceTexture = new SurfaceTexture(/* ignored */0);
+        surfaceTexture.setDefaultBufferSize(640, 480);
+        Surface surface = new Surface(surfaceTexture);
+
+        int streamId = mCameraUser.createStream(/* ignored */0, /* ignored */0, /* ignored */0,
+                surface);
+
+        assertEquals(0, streamId);
+
+        assertEquals(CameraBinderTestUtils.ALREADY_EXISTS,
+                mCameraUser.createStream(/* ignored */0, /* ignored */0, /* ignored */0, surface));
+
+        // Create second stream.
+
+        SurfaceTexture surfaceTexture2 = new SurfaceTexture(/* ignored */0);
+        surfaceTexture2.setDefaultBufferSize(640, 480);
+        Surface surface2 = new Surface(surfaceTexture2);
+
+        int streamId2 = mCameraUser.createStream(/* ignored */0, /* ignored */0, /* ignored */0,
+                surface2);
+
+        assertEquals(1, streamId2);
+
+        // Clean up streams
+
+        assertEquals(CameraBinderTestUtils.NO_ERROR, mCameraUser.deleteStream(streamId));
+        assertEquals(CameraBinderTestUtils.NO_ERROR, mCameraUser.deleteStream(streamId2));
+    }
+
+    @SmallTest
+    public void testSubmitBadRequest() throws Exception {
+
+        CameraMetadata metadata = new CameraMetadata();
+        assertTrue(metadata.isEmpty());
+
+        CaptureRequest request = new CaptureRequest();
+        assertTrue(request.isEmpty());
+
+        int status = mCameraUser.createDefaultRequest(TEMPLATE_PREVIEW, /* out */metadata);
+        assertEquals(CameraBinderTestUtils.NO_ERROR, status);
+        assertFalse(metadata.isEmpty());
+
+        request.swap(metadata);
+        assertFalse(request.isEmpty());
+        assertTrue(metadata.isEmpty());
+
+        status = mCameraUser.submitRequest(request, /* streaming */false);
+        assertEquals("Expected submitRequest to return BAD_VALUE " +
+                "since we had 0 surface targets set.", CameraBinderTestUtils.BAD_VALUE, status);
+
+        SurfaceTexture surfaceTexture = new SurfaceTexture(/* ignored */0);
+        surfaceTexture.setDefaultBufferSize(640, 480);
+        Surface surface = new Surface(surfaceTexture);
+        request.addTarget(surface);
+
+        status = mCameraUser.submitRequest(request, /* streaming */false);
+        assertEquals("Expected submitRequest to return BAD_VALUE since " +
+                "the target surface wasn't registered with createStream.",
+                CameraBinderTestUtils.BAD_VALUE, status);
+
+        request.close();
+        metadata.close();
+        surface.release();
+    }
+
+    @SmallTest
+    public void testSubmitGoodRequest() throws Exception {
+
+        CameraMetadata metadata = new CameraMetadata();
+        assertTrue(metadata.isEmpty());
+
+        CaptureRequest request = new CaptureRequest();
+        assertTrue(request.isEmpty());
+
+        // Create default request from template.
+
+        int status = mCameraUser.createDefaultRequest(TEMPLATE_PREVIEW, /* out */metadata);
+        assertEquals(CameraBinderTestUtils.NO_ERROR, status);
+        assertFalse(metadata.isEmpty());
+
+        request.swap(metadata);
+        assertFalse(request.isEmpty());
+        assertTrue(metadata.isEmpty());
+
+        SurfaceTexture surfaceTexture = new SurfaceTexture(/* ignored */0);
+        surfaceTexture.setDefaultBufferSize(640, 480);
+        Surface surface = new Surface(surfaceTexture);
+
+        // Create stream first. Pre-requisite to submitting a request using that
+        // stream.
+
+        int streamId = mCameraUser.createStream(/* ignored */10, /* ignored */20, /* ignored */30,
+                surface);
+        assertEquals(0, streamId);
+
+        request.addTarget(surface);
+
+        // Submit valid request twice.
+
+        int requestId1;
+        requestId1 = mCameraUser.submitRequest(request, /* streaming */false);
+        assertTrue("Request IDs should be non-negative", requestId1 >= 0);
+
+        int requestId2 = mCameraUser.submitRequest(request, /* streaming */false);
+        assertTrue("Request IDs should be non-negative", requestId2 >= 0);
+        assertNotSame("Request IDs should be unique for multiple requests", requestId1, requestId2);
+
+        surface.release();
+        request.close();
+        metadata.close();
+    }
+
+    @SmallTest
+    public void testSubmitStreamingRequest() throws Exception {
+
+        CameraMetadata metadata = new CameraMetadata();
+        assertTrue(metadata.isEmpty());
+
+        CaptureRequest request = new CaptureRequest();
+        assertTrue(request.isEmpty());
+
+        // Create default request from template.
+
+        int status = mCameraUser.createDefaultRequest(TEMPLATE_PREVIEW, /* out */metadata);
+        assertEquals(CameraBinderTestUtils.NO_ERROR, status);
+        assertFalse(metadata.isEmpty());
+
+        request.swap(metadata);
+        assertFalse(request.isEmpty());
+        assertTrue(metadata.isEmpty());
+
+        SurfaceTexture surfaceTexture = new SurfaceTexture(/* ignored */0);
+        surfaceTexture.setDefaultBufferSize(640, 480);
+        Surface surface = new Surface(surfaceTexture);
+
+        // Create stream first. Pre-requisite to submitting a request using that
+        // stream.
+
+        int streamId = mCameraUser.createStream(/* ignored */10, /* ignored */20, /* ignored */30,
+                surface);
+        assertEquals(0, streamId);
+
+        request.addTarget(surface);
+
+        // Submit valid request once (non-streaming), and another time
+        // (streaming)
+
+        int requestId1;
+        requestId1 = mCameraUser.submitRequest(request, /* streaming */true);
+        assertTrue("Request IDs should be non-negative", requestId1 >= 0);
+
+        int requestIdStreaming = mCameraUser.submitRequest(request, /* streaming */false);
+        assertTrue("Request IDs should be non-negative", requestIdStreaming >= 0);
+        assertNotSame("Request IDs should be unique for multiple requests", requestId1,
+                requestIdStreaming);
+
+        status = mCameraUser.cancelRequest(-1);
+        assertEquals("Invalid request IDs should not be cancellable",
+                CameraBinderTestUtils.BAD_VALUE, status);
+
+        status = mCameraUser.cancelRequest(requestId1);
+        assertEquals("Non-streaming request IDs should not be cancellable",
+                CameraBinderTestUtils.BAD_VALUE, status);
+
+        status = mCameraUser.cancelRequest(requestIdStreaming);
+        assertEquals("Streaming request IDs should be cancellable", CameraBinderTestUtils.NO_ERROR,
+                status);
+
+        surface.release();
+        request.close();
+        metadata.close();
+    }
+}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java
new file mode 100644 (file)
index 0000000..3400434
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * 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 com.android.mediaframeworktest.unit;
+
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.hardware.photography.CameraMetadata;
+
+/**
+ * <pre>
+ * adb shell am instrument \
+ *      -e class 'com.android.mediaframeworktest.unit.CameraMetadataTest' \
+ *      -w com.android.mediaframeworktest/.MediaFrameworkUnitTestRunner
+ * </pre>
+ */
+public class CameraMetadataTest extends junit.framework.TestCase {
+
+    CameraMetadata mMetadata;
+    Parcel mParcel;
+
+    @Override
+    public void setUp() {
+        mMetadata = new CameraMetadata();
+        mParcel = Parcel.obtain();
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        mMetadata.close();
+        mMetadata = null;
+
+        mParcel.recycle();
+        mParcel = null;
+    }
+
+    @SmallTest
+    public void testNew() {
+        assertEquals(0, mMetadata.getEntryCount());
+        assertTrue(mMetadata.isEmpty());
+    }
+
+    @SmallTest
+    public void testClose() throws Exception {
+        mMetadata.isEmpty(); // no throw
+
+        assertFalse(mMetadata.isClosed());
+
+        mMetadata.close();
+
+        assertTrue(mMetadata.isClosed());
+
+        // OK: second close should not throw
+        mMetadata.close();
+
+        assertTrue(mMetadata.isClosed());
+
+        // All other calls after close should throw IllegalStateException
+
+        try {
+            mMetadata.isEmpty();
+            fail("Unreachable -- isEmpty after close should throw IllegalStateException");
+        } catch (IllegalStateException e) {
+            // good: we expect calling this method after close to fail
+        }
+
+        try {
+            mMetadata.getEntryCount();
+            fail("Unreachable -- getEntryCount after close should throw IllegalStateException");
+        } catch (IllegalStateException e) {
+            // good: we expect calling this method after close to fail
+        }
+
+
+        try {
+            mMetadata.swap(mMetadata);
+            fail("Unreachable -- swap after close should throw IllegalStateException");
+        } catch (IllegalStateException e) {
+         // good: we expect calling this method after close to fail
+        }
+
+        try {
+            mMetadata.readFromParcel(mParcel);
+            fail("Unreachable -- readFromParcel after close should throw IllegalStateException");
+        } catch (IllegalStateException e) {
+         // good: we expect calling this method after close to fail
+        }
+
+        try {
+            mMetadata.writeToParcel(mParcel, /*flags*/0);
+            fail("Unreachable -- writeToParcel after close should throw IllegalStateException");
+        } catch (IllegalStateException e) {
+         // good: we expect calling this method after close to fail
+        }
+    }
+}