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

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

Camera2: Listener rework and other API updates

 - Add Handlers to each callback-accepting function
 - Expand CameraDevice ErrorListener to CameraDeviceListener
   - Add idle callback
   - Split out disconnect error to its own callback
 - Add CameraDevice#getId
 - Rename CameraManager's listener to AvailabilityListener
 - Rename CameraManager register/unregister*Listener to
   add/remove*Listener
 - Rename getDeviceIdList to getCameraIdList

Bug: 10549567
Bug: 10549462
Change-Id: Idd2ae8ad8eb126f35a15d765306ada7c1cf74eea
parent ec7a6ea8
Loading
Loading
Loading
Loading
+25 −19
Original line number Diff line number Diff line
@@ -10800,16 +10800,17 @@ package android.hardware.camera2 {
  }
  public abstract interface CameraDevice implements java.lang.AutoCloseable {
    method public abstract void capture(android.hardware.camera2.CaptureRequest, android.hardware.camera2.CameraDevice.CaptureListener) throws android.hardware.camera2.CameraAccessException;
    method public abstract void captureBurst(java.util.List<android.hardware.camera2.CaptureRequest>, android.hardware.camera2.CameraDevice.CaptureListener) throws android.hardware.camera2.CameraAccessException;
    method public abstract void capture(android.hardware.camera2.CaptureRequest, android.hardware.camera2.CameraDevice.CaptureListener, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
    method public abstract void captureBurst(java.util.List<android.hardware.camera2.CaptureRequest>, android.hardware.camera2.CameraDevice.CaptureListener, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
    method public abstract void close() throws java.lang.Exception;
    method public abstract void configureOutputs(java.util.List<android.view.Surface>) throws android.hardware.camera2.CameraAccessException;
    method public abstract android.hardware.camera2.CaptureRequest createCaptureRequest(int) throws android.hardware.camera2.CameraAccessException;
    method public abstract void flush() throws android.hardware.camera2.CameraAccessException;
    method public abstract java.lang.String getId();
    method public abstract android.hardware.camera2.CameraProperties getProperties() throws android.hardware.camera2.CameraAccessException;
    method public abstract void setErrorListener(android.hardware.camera2.CameraDevice.ErrorListener);
    method public abstract void setRepeatingBurst(java.util.List<android.hardware.camera2.CaptureRequest>, android.hardware.camera2.CameraDevice.CaptureListener) throws android.hardware.camera2.CameraAccessException;
    method public abstract void setRepeatingRequest(android.hardware.camera2.CaptureRequest, android.hardware.camera2.CameraDevice.CaptureListener) throws android.hardware.camera2.CameraAccessException;
    method public abstract void setDeviceListener(android.hardware.camera2.CameraDevice.CameraDeviceListener, android.os.Handler);
    method public abstract void setRepeatingBurst(java.util.List<android.hardware.camera2.CaptureRequest>, android.hardware.camera2.CameraDevice.CaptureListener, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
    method public abstract void setRepeatingRequest(android.hardware.camera2.CaptureRequest, android.hardware.camera2.CameraDevice.CaptureListener, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
    method public abstract void stopRepeating() throws android.hardware.camera2.CameraAccessException;
    method public abstract void waitUntilIdle() throws android.hardware.camera2.CameraAccessException;
    field public static final int TEMPLATE_MANUAL = 5; // 0x5
@@ -10819,29 +10820,34 @@ package android.hardware.camera2 {
    field public static final int TEMPLATE_VIDEO_SNAPSHOT = 4; // 0x4
  }
  public static abstract interface CameraDevice.CaptureListener {
    method public abstract void onCaptureComplete(android.hardware.camera2.CameraDevice, android.hardware.camera2.CaptureRequest, android.hardware.camera2.CaptureResult);
    method public abstract void onCaptureFailed(android.hardware.camera2.CameraDevice, android.hardware.camera2.CaptureRequest);
  public static abstract class CameraDevice.CameraDeviceListener {
    ctor public CameraDevice.CameraDeviceListener();
    method public void onCameraDisconnected(android.hardware.camera2.CameraDevice);
    method public void onCameraError(android.hardware.camera2.CameraDevice, int);
    method public void onCameraIdle(android.hardware.camera2.CameraDevice);
    field public static final int ERROR_CAMERA_DEVICE = 1; // 0x1
    field public static final int ERROR_CAMERA_SERVICE = 2; // 0x2
  }
  public static abstract interface CameraDevice.ErrorListener {
    method public abstract void onCameraDeviceError(android.hardware.camera2.CameraDevice, int);
    field public static final int DEVICE_DISCONNECTED = 1; // 0x1
    field public static final int DEVICE_ERROR = 2; // 0x2
    field public static final int SERVICE_ERROR = 3; // 0x3
  public static abstract class CameraDevice.CaptureListener {
    ctor public CameraDevice.CaptureListener();
    method public void onCaptureCompleted(android.hardware.camera2.CameraDevice, android.hardware.camera2.CaptureRequest, android.hardware.camera2.CaptureResult);
    method public void onCaptureFailed(android.hardware.camera2.CameraDevice, android.hardware.camera2.CaptureRequest);
    method public void onCaptureStarted(android.hardware.camera2.CameraDevice, android.hardware.camera2.CaptureRequest, long);
  }
  public final class CameraManager {
    method public void addAvailabilityListener(android.hardware.camera2.CameraManager.AvailabilityListener, android.os.Handler);
    method public java.lang.String[] getCameraIdList() throws android.hardware.camera2.CameraAccessException;
    method public android.hardware.camera2.CameraProperties getCameraProperties(java.lang.String) throws android.hardware.camera2.CameraAccessException;
    method public java.lang.String[] getDeviceIdList() throws android.hardware.camera2.CameraAccessException;
    method public android.hardware.camera2.CameraDevice openCamera(java.lang.String) throws android.hardware.camera2.CameraAccessException;
    method public void registerCameraListener(android.hardware.camera2.CameraManager.CameraListener);
    method public void unregisterCameraListener(android.hardware.camera2.CameraManager.CameraListener);
    method public void removeAvailabilityListener(android.hardware.camera2.CameraManager.AvailabilityListener);
  }
  public static abstract interface CameraManager.CameraListener {
    method public abstract void onCameraAvailable(java.lang.String);
    method public abstract void onCameraUnavailable(java.lang.String);
  public static abstract class CameraManager.AvailabilityListener {
    ctor public CameraManager.AvailabilityListener();
    method public void onCameraAvailable(java.lang.String);
    method public void onCameraUnavailable(java.lang.String);
  }
  public class CameraMetadata implements java.lang.AutoCloseable android.os.Parcelable {
+289 −90

File changed.

Preview size limit exceeded, changes collapsed.

+76 −31
Original line number Diff line number Diff line
@@ -24,13 +24,14 @@ import android.hardware.camera2.utils.CameraBinderDecorator;
import android.hardware.camera2.utils.CameraRuntimeException;
import android.hardware.camera2.utils.BinderHolder;
import android.os.IBinder;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
import android.util.ArrayMap;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;

/**
 * <p>An interface for iterating, listing, and connecting to
@@ -55,7 +56,10 @@ public final class CameraManager {

    private final ICameraService mCameraService;
    private ArrayList<String> mDeviceIdList;
    private final HashSet<CameraListener> mListenerSet = new HashSet<CameraListener>();

    private ArrayMap<AvailabilityListener, Handler> mListenerMap =
            new ArrayMap<AvailabilityListener, Handler>();

    private final Context mContext;
    private final Object mLock = new Object();

@@ -85,14 +89,16 @@ public final class CameraManager {
    }

    /**
     * <p>Return the list of currently connected camera devices by
     * identifier. Non-removable cameras use integers starting at 0 for their
     * Return the list of currently connected camera devices by
     * identifier.
     *
     * <p>Non-removable cameras use integers starting at 0 for their
     * identifiers, while removable cameras have a unique identifier for each
     * individual device, even if they are the same model.</p>
     *
     * @return The list of currently connected camera devices.
     */
    public String[] getDeviceIdList() throws CameraAccessException {
    public String[] getCameraIdList() throws CameraAccessException {
        synchronized (mLock) {
            try {
                return getOrCreateDeviceIdListLocked().toArray(new String[0]);
@@ -107,13 +113,25 @@ public final class CameraManager {
    /**
     * Register a listener to be notified about camera device availability.
     *
     * Registering a listener more than once has no effect.
     * <p>Registering the same listener again will replace the handler with the
     * new one provided.</p>
     *
     * @param listener The new listener to send camera availability notices to
     * @param handler The handler on which the listener should be invoked, or
     * {@code null} to use the current thread's {@link android.os.Looper looper}.
     */
    public void registerCameraListener(CameraListener listener) {
    public void addAvailabilityListener(AvailabilityListener listener, 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);
        }

        synchronized (mLock) {
            mListenerSet.add(listener);
            mListenerMap.put(listener, handler);
        }
    }

@@ -121,13 +139,13 @@ public final class CameraManager {
     * Remove a previously-added listener; the listener will no longer receive
     * connection and disconnection callbacks.
     *
     * Removing a listener that isn't registered has no effect.
     * <p>Removing a listener that isn't registered has no effect.</p>
     *
     * @param listener The listener to remove from the notification list
     */
    public void unregisterCameraListener(CameraListener listener) {
    public void removeAvailabilityListener(AvailabilityListener listener) {
        synchronized (mLock) {
            mListenerSet.remove(listener);
            mListenerMap.remove(listener);
        }
    }

@@ -144,7 +162,7 @@ public final class CameraManager {
     * @throws SecurityException if the application does not have permission to
     * access the camera
     *
     * @see #getDeviceIdList
     * @see #getCameraIdList
     * @see android.app.admin.DevicePolicyManager#setCameraDisabled
     */
    public CameraProperties getCameraProperties(String cameraId)
@@ -165,9 +183,9 @@ public final class CameraManager {

    /**
     * Open a connection to a camera with the given ID. Use
     * {@link #getDeviceIdList} to get the list of available camera
     * {@link #getCameraIdList} to get the list of available camera
     * devices. Note that even if an id is listed, open may fail if the device
     * is disconnected between the calls to {@link #getDeviceIdList} and
     * is disconnected between the calls to {@link #getCameraIdList} and
     * {@link #openCamera}.
     *
     * @param cameraId The unique identifier of the camera device to open
@@ -179,7 +197,7 @@ public final class CameraManager {
     * @throws SecurityException if the application does not have permission to
     * access the camera
     *
     * @see #getDeviceIdList
     * @see #getCameraIdList
     * @see android.app.admin.DevicePolicyManager#setCameraDisabled
     */
    public CameraDevice openCamera(String cameraId) throws CameraAccessException {
@@ -218,29 +236,43 @@ public final class CameraManager {
    }

    /**
     * Interface for listening to cameras becoming available or unavailable.
     * Cameras become available when they are no longer in use, or when a new
     * Interface for listening to camera devices becoming available or
     * unavailable.
     *
     * <p>Cameras become available when they are no longer in use, or when a new
     * removable camera is connected. They become unavailable when some
     * application or service starts using a camera, or when a removable camera
     * is disconnected.
     * is disconnected.</p>
     *
     * @see addAvailabilityListener
     */
    public interface CameraListener {
    public static abstract class AvailabilityListener {

        /**
         * A new camera has become available to use.
         *
         * <p>The default implementation of this method does nothing.</p>
         *
         * @param cameraId The unique identifier of the new camera.
         */
        public void onCameraAvailable(String cameraId);
        public void onCameraAvailable(String cameraId) {
            // default empty implementation
        }

        /**
         * A previously-available camera has become unavailable for use. If an
         * application had an active CameraDevice instance for the
         * now-disconnected camera, that application will receive a {@link
         * CameraDevice.ErrorListener#DEVICE_DISCONNECTED disconnection error}.
         * A previously-available camera has become unavailable for use.
         *
         * <p>If an application had an active CameraDevice instance for the
         * now-disconnected camera, that application will receive a
         * {@link CameraDevice.CameraDeviceListener#onCameraDisconnected disconnection error}.</p>
         *
         * <p>The default implementation of this method does nothing.</p>
         *
         * @param cameraId The unique identifier of the disconnected camera.
         */
        public void onCameraUnavailable(String cameraId);
        public void onCameraUnavailable(String cameraId) {
            // default empty implementation
        }
    }

    private ArrayList<String> getOrCreateDeviceIdListLocked() throws CameraAccessException {
@@ -285,7 +317,7 @@ public final class CameraManager {
        public static final int STATUS_NOT_AVAILABLE = 0x80000000;

        // Camera ID -> Status map
        private final HashMap<String, Integer> mDeviceStatus = new HashMap<String, Integer>();
        private final ArrayMap<String, Integer> mDeviceStatus = new ArrayMap<String, Integer>();

        private static final String TAG = "CameraServiceListener";

@@ -322,7 +354,7 @@ public final class CameraManager {
                Log.v(TAG,
                        String.format("Camera id %d has status changed to 0x%x", cameraId, status));

                String id = String.valueOf(cameraId);
                final String id = String.valueOf(cameraId);

                if (!validStatus(status)) {
                    Log.e(TAG, String.format("Ignoring invalid device %d status 0x%x", cameraId,
@@ -363,12 +395,25 @@ public final class CameraManager {
                    return;
                }

                for (CameraListener listener : mListenerSet) {
                final int listenerCount = mListenerMap.size();
                for (int i = 0; i < listenerCount; i++) {
                    Handler handler = mListenerMap.valueAt(i);
                    final AvailabilityListener listener = mListenerMap.keyAt(i);
                    if (isAvailable(status)) {
                        handler.post(
                            new Runnable() {
                                public void run() {
                                    listener.onCameraAvailable(id);
                                }
                            });
                    } else {
                        handler.post(
                            new Runnable() {
                                public void run() {
                                    listener.onCameraUnavailable(id);
                                }
                            });
                    }
                } // for
            } // synchronized
        } // onStatusChanged
+2 −2
Original line number Diff line number Diff line
@@ -96,7 +96,7 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
     * <p>This tag is not used for anything by the camera device, but can be
     * used by an application to easily identify a CaptureRequest when it is
     * returned by
     * {@link CameraDevice.CaptureListener#onCaptureComplete CaptureListener.onCaptureComplete}
     * {@link CameraDevice.CaptureListener#onCaptureCompleted CaptureListener.onCaptureCompleted}
     *
     * @param tag an arbitrary Object to store with this request
     * @see #getTag
@@ -113,7 +113,7 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
     * <p>This tag is not used for anything by the camera device, but can be
     * used by an application to easily identify a CaptureRequest when it is
     * returned by
     * {@link CameraDevice.CaptureListener#onCaptureComplete CaptureListener.onCaptureComplete}
     * {@link CameraDevice.CaptureListener#onCaptureCompleted CaptureListener.onCaptureCompleted}
     * </p>
     *
     * @return the last tag Object set on this request, or {@code null} if
+67 −28
Original line number Diff line number Diff line
@@ -29,6 +29,8 @@ import android.hardware.camera2.utils.CameraBinderDecorator;
import android.hardware.camera2.utils.CameraRuntimeException;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.util.SparseArray;
import android.view.Surface;
@@ -53,10 +55,11 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
    private final Object mLock = new Object();
    private final CameraDeviceCallbacks mCallbacks = new CameraDeviceCallbacks();

    // XX: Make this a WeakReference<CaptureListener> ?
    // TODO: Convert to SparseIntArray
    private final HashMap<Integer, CaptureListenerHolder> mCaptureListenerMap =
            new HashMap<Integer, CaptureListenerHolder>();
    private CameraDeviceListener mDeviceListener;
    private Handler mDeviceHandler;

    private final SparseArray<CaptureListenerHolder> mCaptureListenerMap =
            new SparseArray<CaptureListenerHolder>();

    private final Stack<Integer> mRepeatingRequestIdStack = new Stack<Integer>();
    // Map stream IDs to Surfaces
@@ -79,6 +82,11 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
        mRemoteDevice = CameraBinderDecorator.newInstance(remoteDevice);
    }

    @Override
    public String getId() {
        return mCameraId;
    }

    @Override
    public CameraProperties getProperties() throws CameraAccessException {

@@ -173,14 +181,14 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
    }

    @Override
    public void capture(CaptureRequest request, CaptureListener listener)
    public void capture(CaptureRequest request, CaptureListener listener, Handler handler)
            throws CameraAccessException {
        submitCaptureRequest(request, listener, /*streaming*/false);
        submitCaptureRequest(request, listener, handler, /*streaming*/false);
    }

    @Override
    public void captureBurst(List<CaptureRequest> requests, CaptureListener listener)
            throws CameraAccessException {
    public void captureBurst(List<CaptureRequest> requests, CaptureListener listener,
            Handler handler) throws CameraAccessException {
        if (requests.isEmpty()) {
            Log.w(TAG, "Capture burst request list is empty, do nothing!");
            return;
@@ -191,7 +199,18 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
    }

    private void submitCaptureRequest(CaptureRequest request, CaptureListener listener,
            boolean repeating) throws CameraAccessException {
            Handler handler, boolean repeating) throws CameraAccessException {

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

        synchronized (mLock) {

@@ -205,9 +224,10 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
                // impossible
                return;
            }

            if (listener != null) {
                mCaptureListenerMap.put(requestId, new CaptureListenerHolder(listener, request,
                    repeating));
                        handler, repeating));
            }

            if (repeating) {
                mRepeatingRequestIdStack.add(requestId);
@@ -217,14 +237,14 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
    }

    @Override
    public void setRepeatingRequest(CaptureRequest request, CaptureListener listener)
            throws CameraAccessException {
        submitCaptureRequest(request, listener, /*streaming*/true);
    public void setRepeatingRequest(CaptureRequest request, CaptureListener listener,
            Handler handler) throws CameraAccessException {
        submitCaptureRequest(request, listener, handler, /*streaming*/true);
    }

    @Override
    public void setRepeatingBurst(List<CaptureRequest> requests, CaptureListener listener)
            throws CameraAccessException {
    public void setRepeatingBurst(List<CaptureRequest> requests, CaptureListener listener,
            Handler handler) throws CameraAccessException {
        if (requests.isEmpty()) {
            Log.w(TAG, "Set Repeating burst request list is empty, do nothing!");
            return;
@@ -274,9 +294,11 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
    }

    @Override
    public void setErrorListener(ErrorListener listener) {
        // TODO Auto-generated method stub

    public void setDeviceListener(CameraDeviceListener listener, Handler handler) {
        synchronized (mLock) {
            mDeviceListener = listener;
            mDeviceHandler = handler;
        }
    }

    @Override
@@ -332,9 +354,16 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
        private final boolean mRepeating;
        private final CaptureListener mListener;
        private final CaptureRequest mRequest;
        private final Handler mHandler;

        CaptureListenerHolder(CaptureListener listener, CaptureRequest request, boolean repeating) {
        CaptureListenerHolder(CaptureListener listener, CaptureRequest request, Handler handler,
                boolean repeating) {
            if (listener == null || handler == null) {
                throw new UnsupportedOperationException(
                    "Must have a valid handler and a valid listener");
            }
            mRepeating = repeating;
            mHandler = handler;
            mRequest = request;
            mListener = listener;
        }
@@ -350,6 +379,11 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
        public CaptureRequest getRequest() {
            return mRequest;
        }

        public Handler getHandler() {
            return mHandler;
        }

    }

    // TODO: unit tests
@@ -374,7 +408,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
            if (DEBUG) {
                Log.d(TAG, "Received result for id " + requestId);
            }
            CaptureListenerHolder holder;
            final CaptureListenerHolder holder;

            synchronized (mLock) {
                // TODO: move this whole map into this class to make it more testable,
@@ -393,18 +427,23 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
                }
            }

            // Check if we have a listener for this
            if (holder == null) {
                Log.e(TAG, "Result had no listener holder associated with it, dropping result");
                return;
            }

            CaptureResult resultAsCapture = new CaptureResult();
            final CaptureResult resultAsCapture = new CaptureResult();
            resultAsCapture.swap(result);

            if (holder.getListener() != null) {
                holder.getListener().onCaptureComplete(CameraDevice.this, holder.getRequest(),
            holder.getHandler().post(
                new Runnable() {
                    public void run() {
                        holder.getListener().onCaptureCompleted(
                            CameraDevice.this,
                            holder.getRequest(),
                            resultAsCapture);
                    }
                });
        }

    }