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