Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e841d4e1 authored by Eino-Ville Talvala's avatar Eino-Ville Talvala
Browse files

Camera2: Implement idle callbacks

- Rework camera callback binder interface
- Connect up idle, disconnect callbacks
- A few unit tests for shutter firing and idle use

Bug: 10549462
Change-Id: I8455a8a0561e366b7edeef6b101682be2ec44d79
parent 778c2de0
Loading
Loading
Loading
Loading
+4 −2
Original line number Diff line number Diff line
@@ -25,6 +25,8 @@ interface ICameraDeviceCallbacks
     * Keep up-to-date with frameworks/av/include/camera/camera2/ICameraDeviceCallbacks.h
     */

    oneway void notifyCallback(int msgType, int ext1, int ext2);
    oneway void onResultReceived(int frameId, in CameraMetadataNative result);
    oneway void onCameraError(int errorCode);
    oneway void onCameraIdle();
    oneway void onCaptureStarted(int requestId, long timestamp);
    oneway void onResultReceived(int requestId, in CameraMetadataNative result);
}
+120 −13
Original line number Diff line number Diff line
@@ -183,13 +183,8 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {

        // 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) {
@@ -277,6 +272,10 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
    @Override
    public void setDeviceListener(StateListener listener, Handler handler) {
        synchronized (mLock) {
            if (listener != null) {
                handler = checkHandler(handler);
            }

            mDeviceListener = listener;
            mDeviceHandler = handler;
        }
@@ -365,21 +364,113 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {

    }

    // 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);
            }

            if (holder == null) {
                return;
            }

            // Dispatch capture start notice
            holder.getHandler().post(
                new Runnable() {
                    public void run() {
                        holder.getListener().onCaptureStarted(
                            CameraDevice.this,
                            holder.getRequest(),
                            timestamp);
                    }
            // TODO implement rest
                });
        }

        @Override
@@ -429,6 +520,22 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {

    }

    /**
     * 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");
+0 −1
Original line number Diff line number Diff line
@@ -652,7 +652,6 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable {
     * We use a class initializer to allow the native code to cache some field offsets
     */
    static {
        System.loadLibrary("media_jni");
        nativeClassInit();

        Log.v(TAG, "Shall register metadata marshalers");
+11 −2
Original line number Diff line number Diff line
@@ -152,11 +152,20 @@ public class CameraBinderTest extends AndroidTestCase {
    static 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)
                throws RemoteException {
        }
    }

+88 −4
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import android.media.ImageReader;
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;
@@ -40,6 +41,7 @@ import static android.hardware.camera2.CameraDevice.TEMPLATE_PREVIEW;
import com.android.mediaframeworktest.MediaFrameworkIntegrationTestRunner;

import org.mockito.ArgumentMatcher;
import org.mockito.ArgumentCaptor;
import static org.mockito.Mockito.*;

public class CameraDeviceBinderTest extends AndroidTestCase {
@@ -48,6 +50,12 @@ 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;
@@ -77,11 +85,19 @@ public class CameraDeviceBinderTest extends AndroidTestCase {
    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) {
        }
    }

@@ -345,6 +361,60 @@ public class CameraDeviceBinderTest extends AndroidTestCase {
                        argThat(matcher));
    }

    @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;
@@ -367,10 +437,24 @@ public class CameraDeviceBinderTest extends AndroidTestCase {
        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.
    }