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/Android.mk b/Android.mk
index 603f5c3..db6a704 100644
--- a/Android.mk
+++ b/Android.mk
@@ -125,6 +125,8 @@
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 \
diff --git a/api/current.txt b/api/current.txt
index dc695d5..8d11dd0 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -10665,18 +10665,18 @@
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 @@
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 @@
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;
}
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"
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkIntegrationTestRunner.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkIntegrationTestRunner.java
index 88c5b0c..7751fcc 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkIntegrationTestRunner.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkIntegrationTestRunner.java
@@ -19,8 +19,10 @@
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 @@
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 @@
@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"));
+ }
+ }
}
}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java
index e9fbca7..ecdc287 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java
@@ -62,6 +62,7 @@
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.
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
index ba2859b..bc3adc9 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
@@ -16,122 +16,72 @@
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";
+ 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;
-
- 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 @@
@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 @@
@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 @@
}
/**
+ * <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 removed before added",
+ mUtils.getCameraService().removeListener(listener) ==
+ CameraBinderTestUtils.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 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",
- mCameraService.removeListener(listener) == NO_ERROR);
- assertTrue("Listener was wrongly removed again",
- mCameraService.removeListener(listener) == BAD_VALUE);
+ 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
index 0000000..f0fc817
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTestUtils.java
@@ -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
index 0000000..0cbb989
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
@@ -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
index 0000000..3400434
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java
@@ -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
+ }
+ }
+}