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

Commit e473f7d8 authored by Igor Murashkin's avatar Igor Murashkin
Browse files

Partial CameraManager implementation

Bug: 9213377
Change-Id: I29864a5d1f7971ed589d1ffaddeefbb703e34018
parent a3f62a84
Loading
Loading
Loading
Loading
+1 −2
Original line number Diff line number Diff line
@@ -10653,7 +10653,7 @@ package android.hardware.location {
package android.hardware.photography {
  public class CameraAccessException extends java.lang.Exception {
  public class CameraAccessException extends android.util.AndroidException {
    ctor public CameraAccessException(int);
    ctor public CameraAccessException(int, java.lang.String);
    ctor public CameraAccessException(int, java.lang.String, java.lang.Throwable);
@@ -10666,7 +10666,6 @@ package android.hardware.photography {
  }
  public final class CameraDevice implements java.lang.AutoCloseable {
    ctor public CameraDevice();
    method public void capture(android.hardware.photography.CaptureRequest, android.hardware.photography.CameraDevice.CaptureListener) throws android.hardware.photography.CameraAccessException;
    method public void captureBurst(java.util.List<android.hardware.photography.CaptureRequest>, android.hardware.photography.CameraDevice.CaptureListener) throws android.hardware.photography.CameraAccessException;
    method public void close();
+3 −3
Original line number Diff line number Diff line
@@ -549,9 +549,9 @@ class ContextImpl extends Context {
                return new AppOpsManager(ctx, service);
            }});

        registerService(CAMERA_SERVICE, new StaticServiceFetcher() {
            public Object createStaticService() {
                return new CameraManager();
        registerService(CAMERA_SERVICE, new ServiceFetcher() {
            public Object createService(ContextImpl ctx) {
                return new CameraManager(ctx);
            }
        });

+27 −3
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package android.hardware.photography;

import android.util.AndroidException;

/**
 * <p><code>CameraAccessException</code> is thrown if a camera device could not
 * be queried or opened by the {@link CameraManager}, or if the connection to an
@@ -24,7 +26,7 @@ package android.hardware.photography;
 * @see CameraManager
 * @see CameraDevice
 */
public class CameraAccessException extends Exception {
public class CameraAccessException extends AndroidException {
    /**
     * The camera device is in use already
     */
@@ -51,7 +53,10 @@ public class CameraAccessException extends Exception {
     */
    public static final int CAMERA_DISCONNECTED = 4;

    private int mReason;
    // Make the eclipse warning about serializable exceptions go away
    private static final long serialVersionUID = 5630338637471475675L; // randomly generated

    private final int mReason;

    /**
     * The reason for the failure to access the camera.
@@ -66,6 +71,7 @@ public class CameraAccessException extends Exception {
    }

    public CameraAccessException(int problem) {
        super(getDefaultMessage(problem));
        mReason = problem;
    }

@@ -80,7 +86,25 @@ public class CameraAccessException extends Exception {
    }

    public CameraAccessException(int problem, Throwable cause) {
        super(cause);
        super(getDefaultMessage(problem), cause);
        mReason = problem;
    }

    private static String getDefaultMessage(int problem) {
        switch (problem) {
            case CAMERA_IN_USE:
                return "The camera device is in use already";
            case MAX_CAMERAS_IN_USE:
                return "The system-wide limit for number of open cameras has been reached, " +
                       "and more camera devices cannot be opened until previous instances " +
                       "are closed.";
            case CAMERA_DISABLED:
                return "The camera is disabled due to a device policy, and cannot be opened.";
            case CAMERA_DISCONNECTED:
                return "The camera device is removable and has been disconnected from the Android" +
                       " device, or the camera service has shut down the connection due to a " +
                       "higher-priority access request for the camera device.";
        }
        return null;
    }
}
+12 −0
Original line number Diff line number Diff line
@@ -17,8 +17,10 @@
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;
@@ -101,6 +103,8 @@ public final class CameraDevice implements AutoCloseable {
     */
    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}.
@@ -451,6 +455,7 @@ public final class CameraDevice implements AutoCloseable {
     * the camera device interface will throw a {@link IllegalStateException},
     * except for calls to close().
     */
    @Override
    public void close() {
    }

@@ -552,4 +557,11 @@ public final class CameraDevice implements AutoCloseable {
        public void onCameraDeviceError(CameraDevice camera, int error);
    }

    /**
     * @hide
     */
    public CameraDevice(IBinder binder) {
        Log.e(TAG, "CameraDevice constructor not implemented yet");
    }

}
+234 −7
Original line number Diff line number Diff line
@@ -16,6 +16,22 @@

package android.hardware.photography;

import android.content.Context;
import android.hardware.ICameraService;
import android.hardware.ICameraServiceListener;
import android.hardware.IProCameraUser;
import android.hardware.photography.utils.CameraBinderDecorator;
import android.hardware.photography.utils.CameraRuntimeException;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;

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

/**
 * <p>An interface for iterating, listing, and connecting to
 * {@link CameraDevice CameraDevices}.</p>
@@ -31,10 +47,41 @@ package android.hardware.photography;
 */
public final class CameraManager {

    /**
     * This should match the ICameraService definition
     */
    private static final String CAMERA_SERVICE_BINDER_NAME = "media.camera";
    private static final int USE_CALLING_UID = -1;

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

    /**
     * @hide
     */
    public CameraManager() {
    public CameraManager(Context context) {
        mContext = context;

        IBinder cameraServiceBinder = ServiceManager.getService(CAMERA_SERVICE_BINDER_NAME);
        ICameraService cameraServiceRaw = ICameraService.Stub.asInterface(cameraServiceBinder);

        /**
         * Wrap the camera service in a decorator which automatically translates return codes
         * into exceptions, and RemoteExceptions into other exceptions.
         */
        mCameraService = CameraBinderDecorator.newInstance(cameraServiceRaw);

        try {
            mCameraService.addListener(new CameraServiceListener());
        } catch(CameraRuntimeException e) {
            throw new IllegalStateException("Failed to register a camera service listener",
                    e.asChecked());
        } catch (RemoteException e) {
            // impossible
        }
    }

    /**
@@ -45,25 +92,37 @@ public final class CameraManager {
     *
     * @return The list of currently connected camera devices.
     */
    public String[] getDeviceIdList() {
        return null;
    public String[] getDeviceIdList() throws CameraAccessException {
        synchronized (mLock) {
            return (String[]) getOrCreateDeviceIdListLocked().toArray();
        }
    }

    /**
     * Register a listener to be notified about camera device availability.
     *
     * @param listener the new listener to send camera availablity notices to.
     * Registering a listener more than once has no effect.
     *
     * @param listener the new listener to send camera availability notices to.
     */
    public void registerCameraListener(CameraListener listener) {
        synchronized (mLock) {
            mListenerSet.add(listener);
        }
    }

    /**
     * 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.
     *
     * @param listener the listener to remove from the notification list
     */
    public void unregisterCameraListener(CameraListener listener) {
        synchronized (mLock) {
            mListenerSet.remove(listener);
        }
    }

    /**
@@ -84,7 +143,18 @@ public final class CameraManager {
     */
    public CameraProperties getCameraProperties(String cameraId)
            throws CameraAccessException {
        throw new IllegalArgumentException();

        synchronized (mLock) {
            if (!getOrCreateDeviceIdListLocked().contains(cameraId)) {
                throw new IllegalArgumentException(String.format("Camera id %s does not match any" +
                        " currently connected camera device", cameraId));
            }
        }

        // TODO: implement and call a service function to get the capabilities on C++ side

        // TODO: get properties from service
        return new CameraProperties();
    }

    /**
@@ -107,7 +177,33 @@ public final class CameraManager {
     * @see android.app.admin.DevicePolicyManager#setCameraDisabled
     */
    public CameraDevice openCamera(String cameraId) throws CameraAccessException {
        throw new IllegalArgumentException();

        try {
            IProCameraUser cameraUser;

            synchronized (mLock) {
                // TODO: Use ICameraDevice or some such instead of this...
                cameraUser = mCameraService.connectPro(null,
                        Integer.parseInt(cameraId),
                        mContext.getPackageName(), USE_CALLING_UID);

            }

            return new CameraDevice(cameraUser.asBinder());
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("Expected cameraId to be numeric, but it was: "
                    + cameraId);
        } catch (CameraRuntimeException e) {
            if (e.getReason() == CameraAccessException.CAMERA_DISCONNECTED) {
                throw new IllegalArgumentException("Invalid camera ID specified -- " +
                        "perhaps the camera was physically disconnected", e);
            } else {
                throw e.asChecked();
            }
        } catch (RemoteException e) {
            // impossible
            return null;
        }
    }

    /**
@@ -135,4 +231,135 @@ public final class CameraManager {
         */
        public void onCameraUnavailable(String cameraId);
    }

    private ArrayList<String> getOrCreateDeviceIdListLocked() throws CameraAccessException {
        if (mDeviceIdList == null) {
            int numCameras = 0;

            try {
                numCameras = mCameraService.getNumberOfCameras();
            } catch(CameraRuntimeException e) {
                throw e.asChecked();
            } catch (RemoteException e) {
                // impossible
                return null;
            }

            mDeviceIdList = new ArrayList<String>();
            for (int i = 0; i < numCameras; ++i) {
                // Non-removable cameras use integers starting at 0 for their
                // identifiers
                mDeviceIdList.add(String.valueOf(i));
            }

        }
        return mDeviceIdList;
    }

    // TODO: this class needs unit tests
    // TODO: extract class into top level
    private class CameraServiceListener extends Binder implements ICameraServiceListener  {

        // Keep up-to-date with ICameraServiceListener.h

        // Device physically unplugged
        public static final int STATUS_NOT_PRESENT = 0;
        // Device physically has been plugged in
        // and the camera can be used exclusively
        public static final int STATUS_PRESENT = 1;
        // Device physically has been plugged in
        // but it will not be connect-able until enumeration is complete
        public static final int STATUS_ENUMERATING = 2;
        // Camera is in use by another app and cannot be used exclusively
        public static final int STATUS_NOT_AVAILABLE = 0x80000000;

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

        private static final String TAG = "CameraServiceListener";

        @Override
        public IBinder asBinder() {
            return this;
        }

        private boolean isAvailable(int status) {
            switch (status) {
                case STATUS_PRESENT:
                    return true;
                default:
                    return false;
            }
        }

        private boolean validStatus(int status) {
            switch (status) {
                case STATUS_NOT_PRESENT:
                case STATUS_PRESENT:
                case STATUS_ENUMERATING:
                case STATUS_NOT_AVAILABLE:
                    return true;
                default:
                    return false;
            }
        }

        @Override
        public void onStatusChanged(int status, int cameraId) throws RemoteException {
            synchronized(CameraManager.this) {

                Log.v(TAG,
                        String.format("Camera id %d has status changed to 0x%x", cameraId, status));

                String id = String.valueOf(cameraId);

                if (!validStatus(status)) {
                    Log.e(TAG, String.format("Ignoring invalid device %d status 0x%x", cameraId,
                            status));
                    return;
                }

                Integer oldStatus = mDeviceStatus.put(id, status);

                if (oldStatus == status) {
                    Log.v(TAG, String.format(
                            "Device status changed to 0x%x, which is what it already was",
                            status));
                    return;
                }

                // TODO: consider abstracting out this state minimization + transition
                // into a separate
                // more easily testable class
                // i.e. (new State()).addState(STATE_AVAILABLE)
                //                   .addState(STATE_NOT_AVAILABLE)
                //                   .addTransition(STATUS_PRESENT, STATE_AVAILABLE),
                //                   .addTransition(STATUS_NOT_PRESENT, STATE_NOT_AVAILABLE)
                //                   .addTransition(STATUS_ENUMERATING, STATE_NOT_AVAILABLE);
                //                   .addTransition(STATUS_NOT_AVAILABLE, STATE_NOT_AVAILABLE);

                // Translate all the statuses to either 'available' or 'not available'
                //  available -> available         => no new update
                //  not available -> not available => no new update
                if (oldStatus != null && isAvailable(status) == isAvailable(oldStatus)) {

                    Log.v(TAG,
                            String.format(
                                    "Device status was previously available (%d), " +
                                            " and is now again available (%d)" +
                                            "so no new client visible update will be sent",
                                    isAvailable(status), isAvailable(status)));
                    return;
                }

                for (CameraListener listener : mListenerSet) {
                    if (isAvailable(status)) {
                        listener.onCameraAvailable(id);
                    } else {
                        listener.onCameraUnavailable(id);
                    }
                } // for
            } // synchronized
        } // onStatusChanged
    } // CameraServiceListener
} // CameraManager
Loading