// Need a valid handler, or current thread needs to have a looper, if
// listener is valid
- if (handler == null && listener != null) {
- Looper looper = Looper.myLooper();
- if (looper == null) {
- throw new IllegalArgumentException(
- "No handler given, and current thread has no looper!");
- }
- handler = new Handler(looper);
+ if (listener != null) {
+ handler = checkHandler(handler);
}
synchronized (mLock) {
// impossible
return;
}
- }
+ }
}
@Override
public void setDeviceListener(StateListener listener, Handler handler) {
synchronized (mLock) {
+ if (listener != null) {
+ handler = checkHandler(handler);
+ }
+
mDeviceListener = listener;
mDeviceHandler = handler;
}
}
- // TODO: unit tests
public class CameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub {
+ //
+ // Constants below need to be kept up-to-date with
+ // frameworks/av/include/camera/camera2/ICameraDeviceCallbacks.h
+ //
+
+ //
+ // Error codes for onCameraError
+ //
+
+ /**
+ * Camera has been disconnected
+ */
+ static final int ERROR_CAMERA_DISCONNECTED = 0;
+
+ /**
+ * Camera has encountered a device-level error
+ * Matches CameraDevice.StateListener#ERROR_CAMERA_DEVICE
+ */
+ static final int ERROR_CAMERA_DEVICE = 1;
+
+ /**
+ * Camera has encountered a service-level error
+ * Matches CameraDevice.StateListener#ERROR_CAMERA_SERVICE
+ */
+ static final int ERROR_CAMERA_SERVICE = 2;
+
@Override
public IBinder asBinder() {
return this;
}
- // TODO: consider rename to onMessageReceived
@Override
- public void notifyCallback(int msgType, int ext1, int ext2) throws RemoteException {
+ public void onCameraError(final int errorCode) {
+ synchronized (mLock) {
+ if (CameraDevice.this.mDeviceListener == null) return;
+ final StateListener listener = CameraDevice.this.mDeviceListener;
+ Runnable r = null;
+ switch (errorCode) {
+ case ERROR_CAMERA_DISCONNECTED:
+ r = new Runnable() {
+ public void run() {
+ listener.onDisconnected(CameraDevice.this);
+ }
+ };
+ break;
+ case ERROR_CAMERA_DEVICE:
+ case ERROR_CAMERA_SERVICE:
+ r = new Runnable() {
+ public void run() {
+ listener.onError(CameraDevice.this, errorCode);
+ }
+ };
+ break;
+ default:
+ Log.e(TAG, "Unknown error from camera device: " + errorCode);
+ }
+ if (r != null) {
+ CameraDevice.this.mDeviceHandler.post(r);
+ }
+ }
+ }
+
+ @Override
+ public void onCameraIdle() {
+ if (DEBUG) {
+ Log.d(TAG, "Camera now idle");
+ }
+ synchronized (mLock) {
+ if (CameraDevice.this.mDeviceListener == null) return;
+ final StateListener listener = CameraDevice.this.mDeviceListener;
+ Runnable r = new Runnable() {
+ public void run() {
+ listener.onIdle(CameraDevice.this);
+ }
+ };
+ CameraDevice.this.mDeviceHandler.post(r);
+ }
+ }
+
+ @Override
+ public void onCaptureStarted(int requestId, final long timestamp) {
if (DEBUG) {
- Log.d(TAG, "Got message " + msgType + " ext1: " + ext1 + " , ext2: " + ext2);
+ Log.d(TAG, "Capture started for id " + requestId);
+ }
+ final CaptureListenerHolder holder;
+
+ // Get the listener for this frame ID, if there is one
+ synchronized (mLock) {
+ holder = CameraDevice.this.mCaptureListenerMap.get(requestId);
}
- // TODO implement rest
+
+ if (holder == null) {
+ return;
+ }
+
+ // Dispatch capture start notice
+ holder.getHandler().post(
+ new Runnable() {
+ public void run() {
+ holder.getListener().onCaptureStarted(
+ CameraDevice.this,
+ holder.getRequest(),
+ timestamp);
+ }
+ });
}
@Override
}
+ /**
+ * Default handler management. If handler is null, get the current thread's
+ * Looper to create a Handler with. If no looper exists, throw exception.
+ */
+ private Handler checkHandler(Handler handler) {
+ if (handler == null) {
+ Looper looper = Looper.myLooper();
+ if (looper == null) {
+ throw new IllegalArgumentException(
+ "No handler given, and current thread has no looper!");
+ }
+ handler = new Handler(looper);
+ }
+ return handler;
+ }
+
private void checkIfCameraClosed() {
if (mRemoteDevice == null) {
throw new IllegalStateException("CameraDevice was already closed");
import android.os.Handler;
import android.os.HandlerThread;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
import com.android.mediaframeworktest.MediaFrameworkIntegrationTestRunner;
import org.mockito.ArgumentMatcher;
+import org.mockito.ArgumentCaptor;
import static org.mockito.Mockito.*;
public class CameraDeviceBinderTest extends AndroidTestCase {
private static int NUM_CALLBACKS_CHECKED = 10;
// Wait for capture result timeout value: 1500ms
private final static int WAIT_FOR_COMPLETE_TIMEOUT_MS = 1500;
+ // Wait for flush timeout value: 1000ms
+ private final static int WAIT_FOR_FLUSH_TIMEOUT_MS = 1000;
+ // Wait for idle timeout value: 2000ms
+ private final static int WAIT_FOR_IDLE_TIMEOUT_MS = 2000;
+ // Wait while camera device starts working on requests
+ private final static int WAIT_FOR_WORK_MS = 300;
// Default size is VGA, which is mandatory camera supported image size by CDD.
private static final int DEFAULT_IMAGE_WIDTH = 640;
private static final int DEFAULT_IMAGE_HEIGHT = 480;
public class DummyCameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub {
@Override
- public void notifyCallback(int msgType, int ext1, int ext2) throws RemoteException {
+ public void onCameraError(int errorCode) {
}
@Override
- public void onResultReceived(int frameId, CameraMetadataNative result) throws RemoteException {
+ public void onCameraIdle() {
+ }
+
+ @Override
+ public void onCaptureStarted(int requestId, long timestamp) {
+ }
+
+ @Override
+ public void onResultReceived(int frameId, CameraMetadataNative result) {
}
}
public boolean matches(Object obj) {
return !((CameraMetadataNative) obj).isEmpty();
}
- }
+ }
private void createDefaultSurface() {
mImageReader =
}
@SmallTest
+ public void testCaptureStartedCallbacks() throws Exception {
+ CaptureRequest request = createDefaultBuilder(/* needStream */true).build();
+
+ ArgumentCaptor<Long> timestamps = ArgumentCaptor.forClass(Long.class);
+
+ // Test both single request and streaming request.
+ int requestId1 = submitCameraRequest(request, /* streaming */false);
+ verify(mMockCb, timeout(WAIT_FOR_COMPLETE_TIMEOUT_MS).times(1)).onCaptureStarted(
+ eq(requestId1),
+ anyLong());
+
+ int streamingId = submitCameraRequest(request, /* streaming */true);
+ verify(mMockCb, timeout(WAIT_FOR_COMPLETE_TIMEOUT_MS).atLeast(NUM_CALLBACKS_CHECKED))
+ .onCaptureStarted(
+ eq(streamingId),
+ timestamps.capture());
+
+ long timestamp = 0; // All timestamps should be larger than 0.
+ for (Long nextTimestamp : timestamps.getAllValues()) {
+ Log.v(TAG, "next t: " + nextTimestamp + " current t: " + timestamp);
+ assertTrue("Captures are out of order", timestamp < nextTimestamp);
+ timestamp = nextTimestamp;
+ }
+ }
+
+ @SmallTest
+ public void testIdleCallback() throws Exception {
+ int status;
+ CaptureRequest request = createDefaultBuilder(/* needStream */true).build();
+
+ // Try streaming
+ int streamingId = submitCameraRequest(request, /* streaming */true);
+
+ // Wait a bit to fill up the queue
+ SystemClock.sleep(WAIT_FOR_WORK_MS);
+
+ // Cancel and make sure we eventually quiesce
+ status = mCameraUser.cancelRequest(streamingId);
+
+ verify(mMockCb, timeout(WAIT_FOR_IDLE_TIMEOUT_MS).times(1)).onCameraIdle();
+
+ // Submit a few capture requests
+ int requestId1 = submitCameraRequest(request, /* streaming */false);
+ int requestId2 = submitCameraRequest(request, /* streaming */false);
+ int requestId3 = submitCameraRequest(request, /* streaming */false);
+ int requestId4 = submitCameraRequest(request, /* streaming */false);
+ int requestId5 = submitCameraRequest(request, /* streaming */false);
+
+ // And wait for more idle
+ verify(mMockCb, timeout(WAIT_FOR_IDLE_TIMEOUT_MS).times(2)).onCameraIdle();
+
+ }
+
+ @SmallTest
public void testFlush() throws Exception {
int status;
int requestId4 = submitCameraRequest(request, /* streaming */false);
int requestId5 = submitCameraRequest(request, /* streaming */false);
- // Then flush
+ // Then flush and wait for idle
+ status = mCameraUser.flush();
+ assertEquals(CameraBinderTestUtils.NO_ERROR, status);
+
+ verify(mMockCb, timeout(WAIT_FOR_FLUSH_TIMEOUT_MS).times(1)).onCameraIdle();
+
+ // Now a streaming request
+ int streamingId = submitCameraRequest(request, /* streaming */true);
+
+ // Wait a bit to fill up the queue
+ SystemClock.sleep(WAIT_FOR_WORK_MS);
+
+ // Then flush and wait for the idle callback
status = mCameraUser.flush();
assertEquals(CameraBinderTestUtils.NO_ERROR, status);
+ verify(mMockCb, timeout(WAIT_FOR_FLUSH_TIMEOUT_MS).times(2)).onCameraIdle();
+
// TODO: When errors are hooked up, count that errors + successful
// requests equal to 5.
}