All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.android.hardware.camera2.impl.CameraDeviceImpl Maven / Gradle / Ivy

/*
 * 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.camera2.impl;

import static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable;

import android.annotation.NonNull;
import android.hardware.ICameraService;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CaptureFailure;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.ICameraDeviceCallbacks;
import android.hardware.camera2.ICameraDeviceUser;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.InputConfiguration;
import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.params.SessionConfiguration;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.hardware.camera2.utils.SubmitInfo;
import android.hardware.camera2.utils.SurfaceUtils;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.util.Log;
import android.util.Range;
import android.util.Size;
import android.util.SparseArray;
import android.view.Surface;

import com.android.internal.util.Preconditions;

import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.Executor;


/**
 * HAL2.1+ implementation of CameraDevice. Use CameraManager#open to instantiate
 */
public class CameraDeviceImpl extends CameraDevice
        implements IBinder.DeathRecipient {
    private final String TAG;
    private final boolean DEBUG = false;

    private static final int REQUEST_ID_NONE = -1;

    // TODO: guard every function with if (!mRemoteDevice) check (if it was closed)
    private ICameraDeviceUserWrapper mRemoteDevice;

    // Lock to synchronize cross-thread access to device public interface
    final Object mInterfaceLock = new Object(); // access from this class and Session only!
    private final CameraDeviceCallbacks mCallbacks = new CameraDeviceCallbacks();

    private final StateCallback mDeviceCallback;
    private volatile StateCallbackKK mSessionStateCallback;
    private final Executor mDeviceExecutor;

    private final AtomicBoolean mClosing = new AtomicBoolean();
    private boolean mInError = false;
    private boolean mIdle = true;

    /** map request IDs to callback/request data */
    private final SparseArray mCaptureCallbackMap =
            new SparseArray();

    private int mRepeatingRequestId = REQUEST_ID_NONE;
    // Latest repeating request list's types
    private int[] mRepeatingRequestTypes;
    // Map stream IDs to input/output configurations
    private SimpleEntry mConfiguredInput =
            new SimpleEntry<>(REQUEST_ID_NONE, null);
    private final SparseArray mConfiguredOutputs =
            new SparseArray<>();

    private final String mCameraId;
    private final CameraCharacteristics mCharacteristics;
    private final int mTotalPartialCount;

    private static final long NANO_PER_SECOND = 1000000000; //ns

    /**
     * A list tracking request and its expected last regular/reprocess/zslStill frame
     * number. Updated when calling ICameraDeviceUser methods.
     */
    private final List mRequestLastFrameNumbersList =
            new ArrayList<>();

    /**
     * An object tracking received frame numbers.
     * Updated when receiving callbacks from ICameraDeviceCallbacks.
     */
    private final FrameNumberTracker mFrameNumberTracker = new FrameNumberTracker();

    private CameraCaptureSessionCore mCurrentSession;
    private int mNextSessionId = 0;

    private final int mAppTargetSdkVersion;

    // Runnables for all state transitions, except error, which needs the
    // error code argument

    private final Runnable mCallOnOpened = new Runnable() {
        @Override
        public void run() {
            StateCallbackKK sessionCallback = null;
            synchronized(mInterfaceLock) {
                if (mRemoteDevice == null) return; // Camera already closed

                sessionCallback = mSessionStateCallback;
            }
            if (sessionCallback != null) {
                sessionCallback.onOpened(CameraDeviceImpl.this);
            }
            mDeviceCallback.onOpened(CameraDeviceImpl.this);
        }
    };

    private final Runnable mCallOnUnconfigured = new Runnable() {
        @Override
        public void run() {
            StateCallbackKK sessionCallback = null;
            synchronized(mInterfaceLock) {
                if (mRemoteDevice == null) return; // Camera already closed

                sessionCallback = mSessionStateCallback;
            }
            if (sessionCallback != null) {
                sessionCallback.onUnconfigured(CameraDeviceImpl.this);
            }
        }
    };

    private final Runnable mCallOnActive = new Runnable() {
        @Override
        public void run() {
            StateCallbackKK sessionCallback = null;
            synchronized(mInterfaceLock) {
                if (mRemoteDevice == null) return; // Camera already closed

                sessionCallback = mSessionStateCallback;
            }
            if (sessionCallback != null) {
                sessionCallback.onActive(CameraDeviceImpl.this);
            }
        }
    };

    private final Runnable mCallOnBusy = new Runnable() {
        @Override
        public void run() {
            StateCallbackKK sessionCallback = null;
            synchronized(mInterfaceLock) {
                if (mRemoteDevice == null) return; // Camera already closed

                sessionCallback = mSessionStateCallback;
            }
            if (sessionCallback != null) {
                sessionCallback.onBusy(CameraDeviceImpl.this);
            }
        }
    };

    private final Runnable mCallOnClosed = new Runnable() {
        private boolean mClosedOnce = false;

        @Override
        public void run() {
            if (mClosedOnce) {
                throw new AssertionError("Don't post #onClosed more than once");
            }
            StateCallbackKK sessionCallback = null;
            synchronized(mInterfaceLock) {
                sessionCallback = mSessionStateCallback;
            }
            if (sessionCallback != null) {
                sessionCallback.onClosed(CameraDeviceImpl.this);
            }
            mDeviceCallback.onClosed(CameraDeviceImpl.this);
            mClosedOnce = true;
        }
    };

    private final Runnable mCallOnIdle = new Runnable() {
        @Override
        public void run() {
            StateCallbackKK sessionCallback = null;
            synchronized(mInterfaceLock) {
                if (mRemoteDevice == null) return; // Camera already closed

                sessionCallback = mSessionStateCallback;
            }
            if (sessionCallback != null) {
                sessionCallback.onIdle(CameraDeviceImpl.this);
            }
        }
    };

    private final Runnable mCallOnDisconnected = new Runnable() {
        @Override
        public void run() {
            StateCallbackKK sessionCallback = null;
            synchronized(mInterfaceLock) {
                if (mRemoteDevice == null) return; // Camera already closed

                sessionCallback = mSessionStateCallback;
            }
            if (sessionCallback != null) {
                sessionCallback.onDisconnected(CameraDeviceImpl.this);
            }
            mDeviceCallback.onDisconnected(CameraDeviceImpl.this);
        }
    };

    public CameraDeviceImpl(String cameraId, StateCallback callback, Executor executor,
                        CameraCharacteristics characteristics, int appTargetSdkVersion) {
        if (cameraId == null || callback == null || executor == null || characteristics == null) {
            throw new IllegalArgumentException("Null argument given");
        }
        mCameraId = cameraId;
        mDeviceCallback = callback;
        mDeviceExecutor = executor;
        mCharacteristics = characteristics;
        mAppTargetSdkVersion = appTargetSdkVersion;

        final int MAX_TAG_LEN = 23;
        String tag = String.format("CameraDevice-JV-%s", mCameraId);
        if (tag.length() > MAX_TAG_LEN) {
            tag = tag.substring(0, MAX_TAG_LEN);
        }
        TAG = tag;

        Integer partialCount =
                mCharacteristics.get(CameraCharacteristics.REQUEST_PARTIAL_RESULT_COUNT);
        if (partialCount == null) {
            // 1 means partial result is not supported.
            mTotalPartialCount = 1;
        } else {
            mTotalPartialCount = partialCount;
        }
    }

    public CameraDeviceCallbacks getCallbacks() {
        return mCallbacks;
    }

    /**
     * Set remote device, which triggers initial onOpened/onUnconfigured callbacks
     *
     * 

This function may post onDisconnected and throw CAMERA_DISCONNECTED if remoteDevice dies * during setup.

* */ public void setRemoteDevice(ICameraDeviceUser remoteDevice) throws CameraAccessException { synchronized(mInterfaceLock) { // TODO: Move from decorator to direct binder-mediated exceptions // If setRemoteFailure already called, do nothing if (mInError) return; mRemoteDevice = new ICameraDeviceUserWrapper(remoteDevice); IBinder remoteDeviceBinder = remoteDevice.asBinder(); // For legacy camera device, remoteDevice is in the same process, and // asBinder returns NULL. if (remoteDeviceBinder != null) { try { remoteDeviceBinder.linkToDeath(this, /*flag*/ 0); } catch (RemoteException e) { CameraDeviceImpl.this.mDeviceExecutor.execute(mCallOnDisconnected); throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED, "The camera device has encountered a serious error"); } } mDeviceExecutor.execute(mCallOnOpened); mDeviceExecutor.execute(mCallOnUnconfigured); } } /** * Call to indicate failed connection to a remote camera device. * *

This places the camera device in the error state and informs the callback. * Use in place of setRemoteDevice() when startup fails.

*/ public void setRemoteFailure(final ServiceSpecificException failure) { int failureCode = StateCallback.ERROR_CAMERA_DEVICE; boolean failureIsError = true; switch (failure.errorCode) { case ICameraService.ERROR_CAMERA_IN_USE: failureCode = StateCallback.ERROR_CAMERA_IN_USE; break; case ICameraService.ERROR_MAX_CAMERAS_IN_USE: failureCode = StateCallback.ERROR_MAX_CAMERAS_IN_USE; break; case ICameraService.ERROR_DISABLED: failureCode = StateCallback.ERROR_CAMERA_DISABLED; break; case ICameraService.ERROR_DISCONNECTED: failureIsError = false; break; case ICameraService.ERROR_INVALID_OPERATION: failureCode = StateCallback.ERROR_CAMERA_DEVICE; break; default: Log.e(TAG, "Unexpected failure in opening camera device: " + failure.errorCode + failure.getMessage()); break; } final int code = failureCode; final boolean isError = failureIsError; synchronized(mInterfaceLock) { mInError = true; mDeviceExecutor.execute(new Runnable() { @Override public void run() { if (isError) { mDeviceCallback.onError(CameraDeviceImpl.this, code); } else { mDeviceCallback.onDisconnected(CameraDeviceImpl.this); } } }); } } @Override public String getId() { return mCameraId; } public void configureOutputs(List outputs) throws CameraAccessException { // Leave this here for backwards compatibility with older code using this directly ArrayList outputConfigs = new ArrayList<>(outputs.size()); for (Surface s : outputs) { outputConfigs.add(new OutputConfiguration(s)); } configureStreamsChecked(/*inputConfig*/null, outputConfigs, /*operatingMode*/ICameraDeviceUser.NORMAL_MODE, /*sessionParams*/ null); } /** * Attempt to configure the input and outputs; the device goes to idle and then configures the * new input and outputs if possible. * *

The configuration may gracefully fail, if input configuration is not supported, * if there are too many outputs, if the formats are not supported, or if the sizes for that * format is not supported. In this case this function will return {@code false} and the * unconfigured callback will be fired.

* *

If the configuration succeeds (with 1 or more outputs with or without an input), * then the idle callback is fired. Unconfiguring the device always fires the idle callback.

* * @param inputConfig input configuration or {@code null} for no input * @param outputs a list of one or more surfaces, or {@code null} to unconfigure * @param operatingMode If the stream configuration is for a normal session, * a constrained high speed session, or something else. * @param sessionParams Session parameters. * @return whether or not the configuration was successful * * @throws CameraAccessException if there were any unexpected problems during configuration */ public boolean configureStreamsChecked(InputConfiguration inputConfig, List outputs, int operatingMode, CaptureRequest sessionParams) throws CameraAccessException { // Treat a null input the same an empty list if (outputs == null) { outputs = new ArrayList(); } if (outputs.size() == 0 && inputConfig != null) { throw new IllegalArgumentException("cannot configure an input stream without " + "any output streams"); } checkInputConfiguration(inputConfig); boolean success = false; synchronized(mInterfaceLock) { checkIfCameraClosedOrInError(); // Streams to create HashSet addSet = new HashSet(outputs); // Streams to delete List deleteList = new ArrayList(); // Determine which streams need to be created, which to be deleted for (int i = 0; i < mConfiguredOutputs.size(); ++i) { int streamId = mConfiguredOutputs.keyAt(i); OutputConfiguration outConfig = mConfiguredOutputs.valueAt(i); if (!outputs.contains(outConfig) || outConfig.isDeferredConfiguration()) { // Always delete the deferred output configuration when the session // is created, as the deferred output configuration doesn't have unique surface // related identifies. deleteList.add(streamId); } else { addSet.remove(outConfig); // Don't create a stream previously created } } mDeviceExecutor.execute(mCallOnBusy); stopRepeating(); try { waitUntilIdle(); mRemoteDevice.beginConfigure(); // reconfigure the input stream if the input configuration is different. InputConfiguration currentInputConfig = mConfiguredInput.getValue(); if (inputConfig != currentInputConfig && (inputConfig == null || !inputConfig.equals(currentInputConfig))) { if (currentInputConfig != null) { mRemoteDevice.deleteStream(mConfiguredInput.getKey()); mConfiguredInput = new SimpleEntry( REQUEST_ID_NONE, null); } if (inputConfig != null) { int streamId = mRemoteDevice.createInputStream(inputConfig.getWidth(), inputConfig.getHeight(), inputConfig.getFormat()); mConfiguredInput = new SimpleEntry( streamId, inputConfig); } } // Delete all streams first (to free up HW resources) for (Integer streamId : deleteList) { mRemoteDevice.deleteStream(streamId); mConfiguredOutputs.delete(streamId); } // Add all new streams for (OutputConfiguration outConfig : outputs) { if (addSet.contains(outConfig)) { int streamId = mRemoteDevice.createStream(outConfig); mConfiguredOutputs.put(streamId, outConfig); } } if (sessionParams != null) { mRemoteDevice.endConfigure(operatingMode, sessionParams.getNativeCopy()); } else { mRemoteDevice.endConfigure(operatingMode, null); } success = true; } catch (IllegalArgumentException e) { // OK. camera service can reject stream config if it's not supported by HAL // This is only the result of a programmer misusing the camera2 api. Log.w(TAG, "Stream configuration failed due to: " + e.getMessage()); return false; } catch (CameraAccessException e) { if (e.getReason() == CameraAccessException.CAMERA_IN_USE) { throw new IllegalStateException("The camera is currently busy." + " You must wait until the previous operation completes.", e); } throw e; } finally { if (success && outputs.size() > 0) { mDeviceExecutor.execute(mCallOnIdle); } else { // Always return to the 'unconfigured' state if we didn't hit a fatal error mDeviceExecutor.execute(mCallOnUnconfigured); } } } return success; } @Override public void createCaptureSession(List outputs, CameraCaptureSession.StateCallback callback, Handler handler) throws CameraAccessException { List outConfigurations = new ArrayList<>(outputs.size()); for (Surface surface : outputs) { outConfigurations.add(new OutputConfiguration(surface)); } createCaptureSessionInternal(null, outConfigurations, callback, checkAndWrapHandler(handler), /*operatingMode*/ICameraDeviceUser.NORMAL_MODE, /*sessionParams*/ null); } @Override public void createCaptureSessionByOutputConfigurations( List outputConfigurations, CameraCaptureSession.StateCallback callback, Handler handler) throws CameraAccessException { if (DEBUG) { Log.d(TAG, "createCaptureSessionByOutputConfigurations"); } // OutputConfiguration objects are immutable, but need to have our own array List currentOutputs = new ArrayList<>(outputConfigurations); createCaptureSessionInternal(null, currentOutputs, callback, checkAndWrapHandler(handler), /*operatingMode*/ICameraDeviceUser.NORMAL_MODE, /*sessionParams*/null); } @Override public void createReprocessableCaptureSession(InputConfiguration inputConfig, List outputs, CameraCaptureSession.StateCallback callback, Handler handler) throws CameraAccessException { if (DEBUG) { Log.d(TAG, "createReprocessableCaptureSession"); } if (inputConfig == null) { throw new IllegalArgumentException("inputConfig cannot be null when creating a " + "reprocessable capture session"); } List outConfigurations = new ArrayList<>(outputs.size()); for (Surface surface : outputs) { outConfigurations.add(new OutputConfiguration(surface)); } createCaptureSessionInternal(inputConfig, outConfigurations, callback, checkAndWrapHandler(handler), /*operatingMode*/ICameraDeviceUser.NORMAL_MODE, /*sessionParams*/ null); } @Override public void createReprocessableCaptureSessionByConfigurations(InputConfiguration inputConfig, List outputs, android.hardware.camera2.CameraCaptureSession.StateCallback callback, Handler handler) throws CameraAccessException { if (DEBUG) { Log.d(TAG, "createReprocessableCaptureSessionWithConfigurations"); } if (inputConfig == null) { throw new IllegalArgumentException("inputConfig cannot be null when creating a " + "reprocessable capture session"); } if (outputs == null) { throw new IllegalArgumentException("Output configurations cannot be null when " + "creating a reprocessable capture session"); } // OutputConfiguration objects aren't immutable, make a copy before using. List currentOutputs = new ArrayList(); for (OutputConfiguration output : outputs) { currentOutputs.add(new OutputConfiguration(output)); } createCaptureSessionInternal(inputConfig, currentOutputs, callback, checkAndWrapHandler(handler), /*operatingMode*/ICameraDeviceUser.NORMAL_MODE, /*sessionParams*/ null); } @Override public void createConstrainedHighSpeedCaptureSession(List outputs, android.hardware.camera2.CameraCaptureSession.StateCallback callback, Handler handler) throws CameraAccessException { if (outputs == null || outputs.size() == 0 || outputs.size() > 2) { throw new IllegalArgumentException( "Output surface list must not be null and the size must be no more than 2"); } List outConfigurations = new ArrayList<>(outputs.size()); for (Surface surface : outputs) { outConfigurations.add(new OutputConfiguration(surface)); } createCaptureSessionInternal(null, outConfigurations, callback, checkAndWrapHandler(handler), /*operatingMode*/ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE, /*sessionParams*/ null); } @Override public void createCustomCaptureSession(InputConfiguration inputConfig, List outputs, int operatingMode, android.hardware.camera2.CameraCaptureSession.StateCallback callback, Handler handler) throws CameraAccessException { List currentOutputs = new ArrayList(); for (OutputConfiguration output : outputs) { currentOutputs.add(new OutputConfiguration(output)); } createCaptureSessionInternal(inputConfig, currentOutputs, callback, checkAndWrapHandler(handler), operatingMode, /*sessionParams*/ null); } @Override public void createCaptureSession(SessionConfiguration config) throws CameraAccessException { if (config == null) { throw new IllegalArgumentException("Invalid session configuration"); } List outputConfigs = config.getOutputConfigurations(); if (outputConfigs == null) { throw new IllegalArgumentException("Invalid output configurations"); } if (config.getExecutor() == null) { throw new IllegalArgumentException("Invalid executor"); } createCaptureSessionInternal(config.getInputConfiguration(), outputConfigs, config.getStateCallback(), config.getExecutor(), config.getSessionType(), config.getSessionParameters()); } private void createCaptureSessionInternal(InputConfiguration inputConfig, List outputConfigurations, CameraCaptureSession.StateCallback callback, Executor executor, int operatingMode, CaptureRequest sessionParams) throws CameraAccessException { synchronized(mInterfaceLock) { if (DEBUG) { Log.d(TAG, "createCaptureSessionInternal"); } checkIfCameraClosedOrInError(); boolean isConstrainedHighSpeed = (operatingMode == ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE); if (isConstrainedHighSpeed && inputConfig != null) { throw new IllegalArgumentException("Constrained high speed session doesn't support" + " input configuration yet."); } // Notify current session that it's going away, before starting camera operations // After this call completes, the session is not allowed to call into CameraDeviceImpl if (mCurrentSession != null) { mCurrentSession.replaceSessionClose(); } // TODO: dont block for this boolean configureSuccess = true; CameraAccessException pendingException = null; Surface input = null; try { // configure streams and then block until IDLE configureSuccess = configureStreamsChecked(inputConfig, outputConfigurations, operatingMode, sessionParams); if (configureSuccess == true && inputConfig != null) { input = mRemoteDevice.getInputSurface(); } } catch (CameraAccessException e) { configureSuccess = false; pendingException = e; input = null; if (DEBUG) { Log.v(TAG, "createCaptureSession - failed with exception ", e); } } // Fire onConfigured if configureOutputs succeeded, fire onConfigureFailed otherwise. CameraCaptureSessionCore newSession = null; if (isConstrainedHighSpeed) { ArrayList surfaces = new ArrayList<>(outputConfigurations.size()); for (OutputConfiguration outConfig : outputConfigurations) { surfaces.add(outConfig.getSurface()); } StreamConfigurationMap config = getCharacteristics().get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); SurfaceUtils.checkConstrainedHighSpeedSurfaces(surfaces, /*fpsRange*/null, config); newSession = new CameraConstrainedHighSpeedCaptureSessionImpl(mNextSessionId++, callback, executor, this, mDeviceExecutor, configureSuccess, mCharacteristics); } else { newSession = new CameraCaptureSessionImpl(mNextSessionId++, input, callback, executor, this, mDeviceExecutor, configureSuccess); } // TODO: wait until current session closes, then create the new session mCurrentSession = newSession; if (pendingException != null) { throw pendingException; } mSessionStateCallback = mCurrentSession.getDeviceStateCallback(); } } @Override public boolean isSessionConfigurationSupported( @NonNull SessionConfiguration sessionConfig) throws CameraAccessException, UnsupportedOperationException, IllegalArgumentException { synchronized(mInterfaceLock) { checkIfCameraClosedOrInError(); return mRemoteDevice.isSessionConfigurationSupported(sessionConfig); } } /** * For use by backwards-compatibility code only. */ public void setSessionListener(StateCallbackKK sessionCallback) { synchronized(mInterfaceLock) { mSessionStateCallback = sessionCallback; } } private void overrideEnableZsl(CameraMetadataNative request, boolean newValue) { Boolean enableZsl = request.get(CaptureRequest.CONTROL_ENABLE_ZSL); if (enableZsl == null) { // If enableZsl is not available, don't override. return; } request.set(CaptureRequest.CONTROL_ENABLE_ZSL, newValue); } @Override public CaptureRequest.Builder createCaptureRequest(int templateType, Set physicalCameraIdSet) throws CameraAccessException { synchronized(mInterfaceLock) { checkIfCameraClosedOrInError(); for (String physicalId : physicalCameraIdSet) { if (physicalId == getId()) { throw new IllegalStateException("Physical id matches the logical id!"); } } CameraMetadataNative templatedRequest = null; templatedRequest = mRemoteDevice.createDefaultRequest(templateType); // If app target SDK is older than O, or it's not a still capture template, enableZsl // must be false in the default request. if (mAppTargetSdkVersion < Build.VERSION_CODES.O || templateType != TEMPLATE_STILL_CAPTURE) { overrideEnableZsl(templatedRequest, false); } CaptureRequest.Builder builder = new CaptureRequest.Builder( templatedRequest, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE, getId(), physicalCameraIdSet); return builder; } } @Override public CaptureRequest.Builder createCaptureRequest(int templateType) throws CameraAccessException { synchronized(mInterfaceLock) { checkIfCameraClosedOrInError(); CameraMetadataNative templatedRequest = null; templatedRequest = mRemoteDevice.createDefaultRequest(templateType); // If app target SDK is older than O, or it's not a still capture template, enableZsl // must be false in the default request. if (mAppTargetSdkVersion < Build.VERSION_CODES.O || templateType != TEMPLATE_STILL_CAPTURE) { overrideEnableZsl(templatedRequest, false); } CaptureRequest.Builder builder = new CaptureRequest.Builder( templatedRequest, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE, getId(), /*physicalCameraIdSet*/ null); return builder; } } @Override public CaptureRequest.Builder createReprocessCaptureRequest(TotalCaptureResult inputResult) throws CameraAccessException { synchronized(mInterfaceLock) { checkIfCameraClosedOrInError(); CameraMetadataNative resultMetadata = new CameraMetadataNative(inputResult.getNativeCopy()); return new CaptureRequest.Builder(resultMetadata, /*reprocess*/true, inputResult.getSessionId(), getId(), /*physicalCameraIdSet*/ null); } } public void prepare(Surface surface) throws CameraAccessException { if (surface == null) throw new IllegalArgumentException("Surface is null"); synchronized(mInterfaceLock) { int streamId = -1; for (int i = 0; i < mConfiguredOutputs.size(); i++) { final List surfaces = mConfiguredOutputs.valueAt(i).getSurfaces(); if (surfaces.contains(surface)) { streamId = mConfiguredOutputs.keyAt(i); break; } } if (streamId == -1) { throw new IllegalArgumentException("Surface is not part of this session"); } mRemoteDevice.prepare(streamId); } } public void prepare(int maxCount, Surface surface) throws CameraAccessException { if (surface == null) throw new IllegalArgumentException("Surface is null"); if (maxCount <= 0) throw new IllegalArgumentException("Invalid maxCount given: " + maxCount); synchronized(mInterfaceLock) { int streamId = -1; for (int i = 0; i < mConfiguredOutputs.size(); i++) { if (surface == mConfiguredOutputs.valueAt(i).getSurface()) { streamId = mConfiguredOutputs.keyAt(i); break; } } if (streamId == -1) { throw new IllegalArgumentException("Surface is not part of this session"); } mRemoteDevice.prepare2(maxCount, streamId); } } public void updateOutputConfiguration(OutputConfiguration config) throws CameraAccessException { synchronized(mInterfaceLock) { int streamId = -1; for (int i = 0; i < mConfiguredOutputs.size(); i++) { if (config.getSurface() == mConfiguredOutputs.valueAt(i).getSurface()) { streamId = mConfiguredOutputs.keyAt(i); break; } } if (streamId == -1) { throw new IllegalArgumentException("Invalid output configuration"); } mRemoteDevice.updateOutputConfiguration(streamId, config); mConfiguredOutputs.put(streamId, config); } } public void tearDown(Surface surface) throws CameraAccessException { if (surface == null) throw new IllegalArgumentException("Surface is null"); synchronized(mInterfaceLock) { int streamId = -1; for (int i = 0; i < mConfiguredOutputs.size(); i++) { if (surface == mConfiguredOutputs.valueAt(i).getSurface()) { streamId = mConfiguredOutputs.keyAt(i); break; } } if (streamId == -1) { throw new IllegalArgumentException("Surface is not part of this session"); } mRemoteDevice.tearDown(streamId); } } public void finalizeOutputConfigs(List outputConfigs) throws CameraAccessException { if (outputConfigs == null || outputConfigs.size() == 0) { throw new IllegalArgumentException("deferred config is null or empty"); } synchronized(mInterfaceLock) { for (OutputConfiguration config : outputConfigs) { int streamId = -1; for (int i = 0; i < mConfiguredOutputs.size(); i++) { // Have to use equal here, as createCaptureSessionByOutputConfigurations() and // createReprocessableCaptureSessionByConfigurations() do a copy of the configs. if (config.equals(mConfiguredOutputs.valueAt(i))) { streamId = mConfiguredOutputs.keyAt(i); break; } } if (streamId == -1) { throw new IllegalArgumentException("Deferred config is not part of this " + "session"); } if (config.getSurfaces().size() == 0) { throw new IllegalArgumentException("The final config for stream " + streamId + " must have at least 1 surface"); } mRemoteDevice.finalizeOutputConfigurations(streamId, config); mConfiguredOutputs.put(streamId, config); } } } public int capture(CaptureRequest request, CaptureCallback callback, Executor executor) throws CameraAccessException { if (DEBUG) { Log.d(TAG, "calling capture"); } List requestList = new ArrayList(); requestList.add(request); return submitCaptureRequest(requestList, callback, executor, /*streaming*/false); } public int captureBurst(List requests, CaptureCallback callback, Executor executor) throws CameraAccessException { if (requests == null || requests.isEmpty()) { throw new IllegalArgumentException("At least one request must be given"); } return submitCaptureRequest(requests, callback, executor, /*streaming*/false); } /** * This method checks lastFrameNumber returned from ICameraDeviceUser methods for * starting and stopping repeating request and flushing. * *

If lastFrameNumber is NO_FRAMES_CAPTURED, it means that the request was never * sent to HAL. Then onCaptureSequenceAborted is immediately triggered. * If lastFrameNumber is non-negative, then the requestId and lastFrameNumber as the last * regular frame number will be added to the list mRequestLastFrameNumbersList.

* * @param requestId the request ID of the current repeating request. * @param lastFrameNumber last frame number returned from binder. * @param repeatingRequestTypes the repeating requests' types. */ private void checkEarlyTriggerSequenceComplete( final int requestId, final long lastFrameNumber, final int[] repeatingRequestTypes) { // lastFrameNumber being equal to NO_FRAMES_CAPTURED means that the request // was never sent to HAL. Should trigger onCaptureSequenceAborted immediately. if (lastFrameNumber == CaptureCallback.NO_FRAMES_CAPTURED) { final CaptureCallbackHolder holder; int index = mCaptureCallbackMap.indexOfKey(requestId); holder = (index >= 0) ? mCaptureCallbackMap.valueAt(index) : null; if (holder != null) { mCaptureCallbackMap.removeAt(index); if (DEBUG) { Log.v(TAG, String.format( "remove holder for requestId %d, " + "because lastFrame is %d.", requestId, lastFrameNumber)); } } if (holder != null) { if (DEBUG) { Log.v(TAG, "immediately trigger onCaptureSequenceAborted because" + " request did not reach HAL"); } Runnable resultDispatch = new Runnable() { @Override public void run() { if (!CameraDeviceImpl.this.isClosed()) { if (DEBUG) { Log.d(TAG, String.format( "early trigger sequence complete for request %d", requestId)); } holder.getCallback().onCaptureSequenceAborted( CameraDeviceImpl.this, requestId); } } }; final long ident = Binder.clearCallingIdentity(); try { holder.getExecutor().execute(resultDispatch); } finally { Binder.restoreCallingIdentity(ident); } } else { Log.w(TAG, String.format( "did not register callback to request %d", requestId)); } } else { // This function is only called for regular/ZslStill request so lastFrameNumber is the // last regular/ZslStill frame number. mRequestLastFrameNumbersList.add(new RequestLastFrameNumbersHolder(requestId, lastFrameNumber, repeatingRequestTypes)); // It is possible that the last frame has already arrived, so we need to check // for sequence completion right away checkAndFireSequenceComplete(); } } private int[] getRequestTypes(final CaptureRequest[] requestArray) { int[] requestTypes = new int[requestArray.length]; for (int i = 0; i < requestArray.length; i++) { requestTypes[i] = requestArray[i].getRequestType(); } return requestTypes; } private int submitCaptureRequest(List requestList, CaptureCallback callback, Executor executor, boolean repeating) throws CameraAccessException { // Need a valid executor, or current thread needs to have a looper, if // callback is valid executor = checkExecutor(executor, callback); // Make sure that there all requests have at least 1 surface; all surfaces are non-null; // the surface isn't a physical stream surface for reprocessing request for (CaptureRequest request : requestList) { if (request.getTargets().isEmpty()) { throw new IllegalArgumentException( "Each request must have at least one Surface target"); } for (Surface surface : request.getTargets()) { if (surface == null) { throw new IllegalArgumentException("Null Surface targets are not allowed"); } for (int i = 0; i < mConfiguredOutputs.size(); i++) { OutputConfiguration configuration = mConfiguredOutputs.valueAt(i); if (configuration.isForPhysicalCamera() && configuration.getSurfaces().contains(surface)) { if (request.isReprocess()) { throw new IllegalArgumentException( "Reprocess request on physical stream is not allowed"); } } } } } synchronized(mInterfaceLock) { checkIfCameraClosedOrInError(); if (repeating) { stopRepeating(); } SubmitInfo requestInfo; CaptureRequest[] requestArray = requestList.toArray(new CaptureRequest[requestList.size()]); // Convert Surface to streamIdx and surfaceIdx for (CaptureRequest request : requestArray) { request.convertSurfaceToStreamId(mConfiguredOutputs); } requestInfo = mRemoteDevice.submitRequestList(requestArray, repeating); if (DEBUG) { Log.v(TAG, "last frame number " + requestInfo.getLastFrameNumber()); } for (CaptureRequest request : requestArray) { request.recoverStreamIdToSurface(); } if (callback != null) { mCaptureCallbackMap.put(requestInfo.getRequestId(), new CaptureCallbackHolder( callback, requestList, executor, repeating, mNextSessionId - 1)); } else { if (DEBUG) { Log.d(TAG, "Listen for request " + requestInfo.getRequestId() + " is null"); } } if (repeating) { if (mRepeatingRequestId != REQUEST_ID_NONE) { checkEarlyTriggerSequenceComplete(mRepeatingRequestId, requestInfo.getLastFrameNumber(), mRepeatingRequestTypes); } mRepeatingRequestId = requestInfo.getRequestId(); mRepeatingRequestTypes = getRequestTypes(requestArray); } else { mRequestLastFrameNumbersList.add( new RequestLastFrameNumbersHolder(requestList, requestInfo)); } if (mIdle) { mDeviceExecutor.execute(mCallOnActive); } mIdle = false; return requestInfo.getRequestId(); } } public int setRepeatingRequest(CaptureRequest request, CaptureCallback callback, Executor executor) throws CameraAccessException { List requestList = new ArrayList(); requestList.add(request); return submitCaptureRequest(requestList, callback, executor, /*streaming*/true); } public int setRepeatingBurst(List requests, CaptureCallback callback, Executor executor) throws CameraAccessException { if (requests == null || requests.isEmpty()) { throw new IllegalArgumentException("At least one request must be given"); } return submitCaptureRequest(requests, callback, executor, /*streaming*/true); } public void stopRepeating() throws CameraAccessException { synchronized(mInterfaceLock) { checkIfCameraClosedOrInError(); if (mRepeatingRequestId != REQUEST_ID_NONE) { int requestId = mRepeatingRequestId; mRepeatingRequestId = REQUEST_ID_NONE; int[] requestTypes = mRepeatingRequestTypes; mRepeatingRequestTypes = null; long lastFrameNumber; try { lastFrameNumber = mRemoteDevice.cancelRequest(requestId); } catch (IllegalArgumentException e) { if (DEBUG) { Log.v(TAG, "Repeating request was already stopped for request " + requestId); } // Repeating request was already stopped. Nothing more to do. return; } checkEarlyTriggerSequenceComplete(requestId, lastFrameNumber, requestTypes); } } } private void waitUntilIdle() throws CameraAccessException { synchronized(mInterfaceLock) { checkIfCameraClosedOrInError(); if (mRepeatingRequestId != REQUEST_ID_NONE) { throw new IllegalStateException("Active repeating request ongoing"); } mRemoteDevice.waitUntilIdle(); } } public void flush() throws CameraAccessException { synchronized(mInterfaceLock) { checkIfCameraClosedOrInError(); mDeviceExecutor.execute(mCallOnBusy); // If already idle, just do a busy->idle transition immediately, don't actually // flush. if (mIdle) { mDeviceExecutor.execute(mCallOnIdle); return; } long lastFrameNumber = mRemoteDevice.flush(); if (mRepeatingRequestId != REQUEST_ID_NONE) { checkEarlyTriggerSequenceComplete(mRepeatingRequestId, lastFrameNumber, mRepeatingRequestTypes); mRepeatingRequestId = REQUEST_ID_NONE; mRepeatingRequestTypes = null; } } } @Override public void close() { synchronized (mInterfaceLock) { if (mClosing.getAndSet(true)) { return; } if (mRemoteDevice != null) { mRemoteDevice.disconnect(); mRemoteDevice.unlinkToDeath(this, /*flags*/0); } // Only want to fire the onClosed callback once; // either a normal close where the remote device is valid // or a close after a startup error (no remote device but in error state) if (mRemoteDevice != null || mInError) { mDeviceExecutor.execute(mCallOnClosed); } mRemoteDevice = null; } } @Override protected void finalize() throws Throwable { try { close(); } finally { super.finalize(); } } private void checkInputConfiguration(InputConfiguration inputConfig) { if (inputConfig != null) { StreamConfigurationMap configMap = mCharacteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); int[] inputFormats = configMap.getInputFormats(); boolean validFormat = false; for (int format : inputFormats) { if (format == inputConfig.getFormat()) { validFormat = true; } } if (validFormat == false) { throw new IllegalArgumentException("input format " + inputConfig.getFormat() + " is not valid"); } boolean validSize = false; Size[] inputSizes = configMap.getInputSizes(inputConfig.getFormat()); for (Size s : inputSizes) { if (inputConfig.getWidth() == s.getWidth() && inputConfig.getHeight() == s.getHeight()) { validSize = true; } } if (validSize == false) { throw new IllegalArgumentException("input size " + inputConfig.getWidth() + "x" + inputConfig.getHeight() + " is not valid"); } } } /** *

A callback for tracking the progress of a {@link CaptureRequest} * submitted to the camera device.

* * An interface instead of an abstract class because this is internal and * we want to make sure we always implement all its callbacks until we reach * the public layer. */ public interface CaptureCallback { /** * This constant is used to indicate that no images were captured for * the request. * * @hide */ public static final int NO_FRAMES_CAPTURED = -1; /** * This method is called when the camera device has started capturing * the output image for the request, at the beginning of image exposure. * * @see android.media.MediaActionSound */ public void onCaptureStarted(CameraDevice camera, CaptureRequest request, long timestamp, long frameNumber); /** * This method is called when some results from an image capture are * available. * * @hide */ public void onCapturePartial(CameraDevice camera, CaptureRequest request, CaptureResult result); /** * This method is called when an image capture makes partial forward progress; some * (but not all) results from an image capture are available. * */ public void onCaptureProgressed(CameraDevice camera, CaptureRequest request, CaptureResult partialResult); /** * This method is called when an image capture has fully completed and all the * result metadata is available. */ public void onCaptureCompleted(CameraDevice camera, CaptureRequest request, TotalCaptureResult result); /** * This method is called instead of {@link #onCaptureCompleted} when the * camera device failed to produce a {@link CaptureResult} for the * request. */ public void onCaptureFailed(CameraDevice camera, CaptureRequest request, CaptureFailure failure); /** * This method is called independently of the others in CaptureCallback, * when a capture sequence finishes and all {@link CaptureResult} * or {@link CaptureFailure} for it have been returned via this callback. */ public void onCaptureSequenceCompleted(CameraDevice camera, int sequenceId, long frameNumber); /** * This method is called independently of the others in CaptureCallback, * when a capture sequence aborts before any {@link CaptureResult} * or {@link CaptureFailure} for it have been returned via this callback. */ public void onCaptureSequenceAborted(CameraDevice camera, int sequenceId); /** * This method is called independently of the others in CaptureCallback, if an output buffer * is dropped for a particular capture request. * * Loss of metadata is communicated via onCaptureFailed, independently of any buffer loss. */ public void onCaptureBufferLost(CameraDevice camera, CaptureRequest request, Surface target, long frameNumber); } /** * A callback for notifications about the state of a camera device, adding in the callbacks that * were part of the earlier KK API design, but now only used internally. */ public static abstract class StateCallbackKK extends StateCallback { /** * The method called when a camera device has no outputs configured. * */ public void onUnconfigured(CameraDevice camera) { // Default empty implementation } /** * The method called when a camera device begins processing * {@link CaptureRequest capture requests}. * */ public void onActive(CameraDevice camera) { // Default empty implementation } /** * The method called when a camera device is busy. * */ public void onBusy(CameraDevice camera) { // Default empty implementation } /** * The method called when a camera device has finished processing all * submitted capture requests and has reached an idle state. * */ public void onIdle(CameraDevice camera) { // Default empty implementation } /** * This method is called when camera device's non-repeating request queue is empty, * and is ready to start capturing next image. */ public void onRequestQueueEmpty() { // Default empty implementation } /** * The method called when the camera device has finished preparing * an output Surface */ public void onSurfacePrepared(Surface surface) { // Default empty implementation } } static class CaptureCallbackHolder { private final boolean mRepeating; private final CaptureCallback mCallback; private final List mRequestList; private final Executor mExecutor; private final int mSessionId; /** *

Determine if the callback holder is for a constrained high speed request list that * expects batched capture results. Capture results will be batched if the request list * is interleaved with preview and video requests. Capture results won't be batched if the * request list only contains preview requests, or if the request doesn't belong to a * constrained high speed list. */ private final boolean mHasBatchedOutputs; CaptureCallbackHolder(CaptureCallback callback, List requestList, Executor executor, boolean repeating, int sessionId) { if (callback == null || executor == null) { throw new UnsupportedOperationException( "Must have a valid handler and a valid callback"); } mRepeating = repeating; mExecutor = executor; mRequestList = new ArrayList(requestList); mCallback = callback; mSessionId = sessionId; // Check whether this callback holder is for batched outputs. // The logic here should match createHighSpeedRequestList. boolean hasBatchedOutputs = true; for (int i = 0; i < requestList.size(); i++) { CaptureRequest request = requestList.get(i); if (!request.isPartOfCRequestList()) { hasBatchedOutputs = false; break; } if (i == 0) { Collection targets = request.getTargets(); if (targets.size() != 2) { hasBatchedOutputs = false; break; } } } mHasBatchedOutputs = hasBatchedOutputs; } public boolean isRepeating() { return mRepeating; } public CaptureCallback getCallback() { return mCallback; } public CaptureRequest getRequest(int subsequenceId) { if (subsequenceId >= mRequestList.size()) { throw new IllegalArgumentException( String.format( "Requested subsequenceId %d is larger than request list size %d.", subsequenceId, mRequestList.size())); } else { if (subsequenceId < 0) { throw new IllegalArgumentException(String.format( "Requested subsequenceId %d is negative", subsequenceId)); } else { return mRequestList.get(subsequenceId); } } } public CaptureRequest getRequest() { return getRequest(0); } public Executor getExecutor() { return mExecutor; } public int getSessionId() { return mSessionId; } public int getRequestCount() { return mRequestList.size(); } public boolean hasBatchedOutputs() { return mHasBatchedOutputs; } } /** * This class holds a capture ID and its expected last regular, zslStill, and reprocess * frame number. */ static class RequestLastFrameNumbersHolder { // request ID private final int mRequestId; // The last regular frame number for this request ID. It's // CaptureCallback.NO_FRAMES_CAPTURED if the request ID has no regular request. private final long mLastRegularFrameNumber; // The last reprocess frame number for this request ID. It's // CaptureCallback.NO_FRAMES_CAPTURED if the request ID has no reprocess request. private final long mLastReprocessFrameNumber; // The last ZSL still capture frame number for this request ID. It's // CaptureCallback.NO_FRAMES_CAPTURED if the request ID has no zsl request. private final long mLastZslStillFrameNumber; /** * Create a request-last-frame-numbers holder with a list of requests, request ID, and * the last frame number returned by camera service. */ public RequestLastFrameNumbersHolder(List requestList, SubmitInfo requestInfo) { long lastRegularFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED; long lastReprocessFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED; long lastZslStillFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED; long frameNumber = requestInfo.getLastFrameNumber(); if (requestInfo.getLastFrameNumber() < requestList.size() - 1) { throw new IllegalArgumentException( "lastFrameNumber: " + requestInfo.getLastFrameNumber() + " should be at least " + (requestList.size() - 1) + " for the number of " + " requests in the list: " + requestList.size()); } // find the last regular, zslStill, and reprocess frame number for (int i = requestList.size() - 1; i >= 0; i--) { CaptureRequest request = requestList.get(i); int requestType = request.getRequestType(); if (requestType == CaptureRequest.REQUEST_TYPE_REPROCESS && lastReprocessFrameNumber == CaptureCallback.NO_FRAMES_CAPTURED) { lastReprocessFrameNumber = frameNumber; } else if (requestType == CaptureRequest.REQUEST_TYPE_ZSL_STILL && lastZslStillFrameNumber == CaptureCallback.NO_FRAMES_CAPTURED) { lastZslStillFrameNumber = frameNumber; } else if (requestType == CaptureRequest.REQUEST_TYPE_REGULAR && lastRegularFrameNumber == CaptureCallback.NO_FRAMES_CAPTURED) { lastRegularFrameNumber = frameNumber; } if (lastReprocessFrameNumber != CaptureCallback.NO_FRAMES_CAPTURED && lastZslStillFrameNumber != CaptureCallback.NO_FRAMES_CAPTURED && lastRegularFrameNumber != CaptureCallback.NO_FRAMES_CAPTURED) { break; } frameNumber--; } mLastRegularFrameNumber = lastRegularFrameNumber; mLastReprocessFrameNumber = lastReprocessFrameNumber; mLastZslStillFrameNumber = lastZslStillFrameNumber; mRequestId = requestInfo.getRequestId(); } /** * Create a request-last-frame-numbers holder with a request ID and last regular/ZslStill * frame number. */ RequestLastFrameNumbersHolder(int requestId, long lastFrameNumber, int[] repeatingRequestTypes) { long lastRegularFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED; long lastZslStillFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED; if (repeatingRequestTypes == null) { throw new IllegalArgumentException( "repeatingRequest list must not be null"); } if (lastFrameNumber < repeatingRequestTypes.length - 1) { throw new IllegalArgumentException( "lastFrameNumber: " + lastFrameNumber + " should be at least " + (repeatingRequestTypes.length - 1) + " for the number of requests in the list: " + repeatingRequestTypes.length); } long frameNumber = lastFrameNumber; for (int i = repeatingRequestTypes.length - 1; i >= 0; i--) { if (repeatingRequestTypes[i] == CaptureRequest.REQUEST_TYPE_ZSL_STILL && lastZslStillFrameNumber == CaptureCallback.NO_FRAMES_CAPTURED) { lastZslStillFrameNumber = frameNumber; } else if (repeatingRequestTypes[i] == CaptureRequest.REQUEST_TYPE_REGULAR && lastRegularFrameNumber == CaptureCallback.NO_FRAMES_CAPTURED) { lastRegularFrameNumber = frameNumber; } if (lastZslStillFrameNumber != CaptureCallback.NO_FRAMES_CAPTURED && lastRegularFrameNumber != CaptureCallback.NO_FRAMES_CAPTURED) { break; } frameNumber--; } mLastRegularFrameNumber = lastRegularFrameNumber; mLastZslStillFrameNumber = lastZslStillFrameNumber; mLastReprocessFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED; mRequestId = requestId; } /** * Return the last regular frame number. Return CaptureCallback.NO_FRAMES_CAPTURED if * it contains no regular request. */ public long getLastRegularFrameNumber() { return mLastRegularFrameNumber; } /** * Return the last reprocess frame number. Return CaptureCallback.NO_FRAMES_CAPTURED if * it contains no reprocess request. */ public long getLastReprocessFrameNumber() { return mLastReprocessFrameNumber; } /** * Return the last ZslStill frame number. Return CaptureCallback.NO_FRAMES_CAPTURED if * it contains no Zsl request. */ public long getLastZslStillFrameNumber() { return mLastZslStillFrameNumber; } /** * Return the last frame number overall. */ public long getLastFrameNumber() { return Math.max(mLastZslStillFrameNumber, Math.max(mLastRegularFrameNumber, mLastReprocessFrameNumber)); } /** * Return the request ID. */ public int getRequestId() { return mRequestId; } } /** * This class tracks the last frame number for submitted requests. */ public class FrameNumberTracker { /** the completed frame number for each type of capture results */ private long[] mCompletedFrameNumber = new long[CaptureRequest.REQUEST_TYPE_COUNT]; /** the skipped frame numbers that don't belong to each type of capture results */ private final LinkedList[] mSkippedOtherFrameNumbers = new LinkedList[CaptureRequest.REQUEST_TYPE_COUNT]; /** the skipped frame numbers that belong to each type of capture results */ private final LinkedList[] mSkippedFrameNumbers = new LinkedList[CaptureRequest.REQUEST_TYPE_COUNT]; /** frame number -> request type */ private final TreeMap mFutureErrorMap = new TreeMap(); /** Map frame numbers to list of partial results */ private final HashMap> mPartialResults = new HashMap<>(); public FrameNumberTracker() { for (int i = 0; i < CaptureRequest.REQUEST_TYPE_COUNT; i++) { mCompletedFrameNumber[i] = CaptureCallback.NO_FRAMES_CAPTURED; mSkippedOtherFrameNumbers[i] = new LinkedList(); mSkippedFrameNumbers[i] = new LinkedList(); } } private void update() { Iterator iter = mFutureErrorMap.entrySet().iterator(); while (iter.hasNext()) { TreeMap.Entry pair = (TreeMap.Entry)iter.next(); Long errorFrameNumber = (Long)pair.getKey(); int requestType = (int) pair.getValue(); Boolean removeError = false; if (errorFrameNumber == mCompletedFrameNumber[requestType] + 1) { mCompletedFrameNumber[requestType] = errorFrameNumber; removeError = true; } else { if (!mSkippedFrameNumbers[requestType].isEmpty()) { if (errorFrameNumber == mSkippedFrameNumbers[requestType].element()) { mCompletedFrameNumber[requestType] = errorFrameNumber; mSkippedFrameNumbers[requestType].remove(); removeError = true; } } else { for (int i = 1; i < CaptureRequest.REQUEST_TYPE_COUNT; i++) { int otherType = (requestType + i) % CaptureRequest.REQUEST_TYPE_COUNT; if (!mSkippedOtherFrameNumbers[otherType].isEmpty() && errorFrameNumber == mSkippedOtherFrameNumbers[otherType].element()) { mCompletedFrameNumber[requestType] = errorFrameNumber; mSkippedOtherFrameNumbers[otherType].remove(); removeError = true; break; } } } } if (removeError) { iter.remove(); } } } /** * This function is called every time when a result or an error is received. * @param frameNumber the frame number corresponding to the result or error * @param isError true if it is an error, false if it is not an error * @param requestType the type of capture request: Reprocess, ZslStill, or Regular. */ public void updateTracker(long frameNumber, boolean isError, int requestType) { if (isError) { mFutureErrorMap.put(frameNumber, requestType); } else { try { updateCompletedFrameNumber(frameNumber, requestType); } catch (IllegalArgumentException e) { Log.e(TAG, e.getMessage()); } } update(); } /** * This function is called every time a result has been completed. * *

It keeps a track of all the partial results already created for a particular * frame number.

* * @param frameNumber the frame number corresponding to the result * @param result the total or partial result * @param partial {@true} if the result is partial, {@code false} if total * @param requestType the type of capture request: Reprocess, ZslStill, or Regular. */ public void updateTracker(long frameNumber, CaptureResult result, boolean partial, int requestType) { if (!partial) { // Update the total result's frame status as being successful updateTracker(frameNumber, /*isError*/false, requestType); // Don't keep a list of total results, we don't need to track them return; } if (result == null) { // Do not record blank results; this also means there will be no total result // so it doesn't matter that the partials were not recorded return; } // Partial results must be aggregated in-order for that frame number List partials = mPartialResults.get(frameNumber); if (partials == null) { partials = new ArrayList<>(); mPartialResults.put(frameNumber, partials); } partials.add(result); } /** * Attempt to pop off all of the partial results seen so far for the {@code frameNumber}. * *

Once popped-off, the partial results are forgotten (unless {@code updateTracker} * is called again with new partials for that frame number).

* * @param frameNumber the frame number corresponding to the result * @return a list of partial results for that frame with at least 1 element, * or {@code null} if there were no partials recorded for that frame */ public List popPartialResults(long frameNumber) { return mPartialResults.remove(frameNumber); } public long getCompletedFrameNumber() { return mCompletedFrameNumber[CaptureRequest.REQUEST_TYPE_REGULAR]; } public long getCompletedReprocessFrameNumber() { return mCompletedFrameNumber[CaptureRequest.REQUEST_TYPE_REPROCESS]; } public long getCompletedZslStillFrameNumber() { return mCompletedFrameNumber[CaptureRequest.REQUEST_TYPE_ZSL_STILL]; } /** * Update the completed frame number for results of 3 categories * (Regular/Reprocess/ZslStill). * * It validates that all previous frames of the same category have arrived. * * If there is a gap since previous frame number of the same category, assume the frames in * the gap are other categories and store them in the skipped frame number queue to check * against when frames of those categories arrive. */ private void updateCompletedFrameNumber(long frameNumber, int requestType) throws IllegalArgumentException { if (frameNumber <= mCompletedFrameNumber[requestType]) { throw new IllegalArgumentException("frame number " + frameNumber + " is a repeat"); } // Assume there are only 3 different types of capture requests. int otherType1 = (requestType + 1) % CaptureRequest.REQUEST_TYPE_COUNT; int otherType2 = (requestType + 2) % CaptureRequest.REQUEST_TYPE_COUNT; long maxOtherFrameNumberSeen = Math.max(mCompletedFrameNumber[otherType1], mCompletedFrameNumber[otherType2]); if (frameNumber < maxOtherFrameNumberSeen) { // if frame number is smaller than completed frame numbers of other categories, // it must be: // - the head of mSkippedFrameNumbers for this category, or // - in one of other mSkippedOtherFrameNumbers if (!mSkippedFrameNumbers[requestType].isEmpty()) { // frame number must be head of current type of mSkippedFrameNumbers if // mSkippedFrameNumbers isn't empty. if (frameNumber < mSkippedFrameNumbers[requestType].element()) { throw new IllegalArgumentException("frame number " + frameNumber + " is a repeat"); } else if (frameNumber > mSkippedFrameNumbers[requestType].element()) { throw new IllegalArgumentException("frame number " + frameNumber + " comes out of order. Expecting " + mSkippedFrameNumbers[requestType].element()); } // frame number matches the head of the skipped frame number queue. mSkippedFrameNumbers[requestType].remove(); } else { // frame number must be in one of the other mSkippedOtherFrameNumbers. int index1 = mSkippedOtherFrameNumbers[otherType1].indexOf(frameNumber); int index2 = mSkippedOtherFrameNumbers[otherType2].indexOf(frameNumber); boolean inSkippedOther1 = index1 != -1; boolean inSkippedOther2 = index2 != -1; if (!(inSkippedOther1 ^ inSkippedOther2)) { throw new IllegalArgumentException("frame number " + frameNumber + " is a repeat or invalid"); } // We know the category of frame numbers in skippedOtherFrameNumbers leading up // to the current frame number. Move them into the correct skippedFrameNumbers. LinkedList srcList, dstList; int index; if (inSkippedOther1) { srcList = mSkippedOtherFrameNumbers[otherType1]; dstList = mSkippedFrameNumbers[otherType2]; index = index1; } else { srcList = mSkippedOtherFrameNumbers[otherType2]; dstList = mSkippedFrameNumbers[otherType1]; index = index2; } for (int i = 0; i < index; i++) { dstList.add(srcList.removeFirst()); } // Remove current frame number from skippedOtherFrameNumbers srcList.remove(); } } else { // there is a gap of unseen frame numbers which should belong to the other // 2 categories. Put all the skipped frame numbers in the queue. for (long i = Math.max(maxOtherFrameNumberSeen, mCompletedFrameNumber[requestType]) + 1; i < frameNumber; i++) { mSkippedOtherFrameNumbers[requestType].add(i); } } mCompletedFrameNumber[requestType] = frameNumber; } } private void checkAndFireSequenceComplete() { long completedFrameNumber = mFrameNumberTracker.getCompletedFrameNumber(); long completedReprocessFrameNumber = mFrameNumberTracker.getCompletedReprocessFrameNumber(); long completedZslStillFrameNumber = mFrameNumberTracker.getCompletedZslStillFrameNumber(); boolean isReprocess = false; Iterator iter = mRequestLastFrameNumbersList.iterator(); while (iter.hasNext()) { final RequestLastFrameNumbersHolder requestLastFrameNumbers = iter.next(); boolean sequenceCompleted = false; final int requestId = requestLastFrameNumbers.getRequestId(); final CaptureCallbackHolder holder; synchronized(mInterfaceLock) { if (mRemoteDevice == null) { Log.w(TAG, "Camera closed while checking sequences"); return; } int index = mCaptureCallbackMap.indexOfKey(requestId); holder = (index >= 0) ? mCaptureCallbackMap.valueAt(index) : null; if (holder != null) { long lastRegularFrameNumber = requestLastFrameNumbers.getLastRegularFrameNumber(); long lastReprocessFrameNumber = requestLastFrameNumbers.getLastReprocessFrameNumber(); long lastZslStillFrameNumber = requestLastFrameNumbers.getLastZslStillFrameNumber(); // check if it's okay to remove request from mCaptureCallbackMap if (lastRegularFrameNumber <= completedFrameNumber && lastReprocessFrameNumber <= completedReprocessFrameNumber && lastZslStillFrameNumber <= completedZslStillFrameNumber) { sequenceCompleted = true; mCaptureCallbackMap.removeAt(index); if (DEBUG) { Log.v(TAG, String.format( "Remove holder for requestId %d, because lastRegularFrame %d " + "is <= %d, lastReprocessFrame %d is <= %d, " + "lastZslStillFrame %d is <= %d", requestId, lastRegularFrameNumber, completedFrameNumber, lastReprocessFrameNumber, completedReprocessFrameNumber, lastZslStillFrameNumber, completedZslStillFrameNumber)); } } } } // If no callback is registered for this requestId or sequence completed, remove it // from the frame number->request pair because it's not needed anymore. if (holder == null || sequenceCompleted) { iter.remove(); } // Call onCaptureSequenceCompleted if (sequenceCompleted) { Runnable resultDispatch = new Runnable() { @Override public void run() { if (!CameraDeviceImpl.this.isClosed()){ if (DEBUG) { Log.d(TAG, String.format( "fire sequence complete for request %d", requestId)); } holder.getCallback().onCaptureSequenceCompleted( CameraDeviceImpl.this, requestId, requestLastFrameNumbers.getLastFrameNumber()); } } }; final long ident = Binder.clearCallingIdentity(); try { holder.getExecutor().execute(resultDispatch); } finally { Binder.restoreCallingIdentity(ident); } } } } public class CameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub { @Override public IBinder asBinder() { return this; } @Override public void onDeviceError(final int errorCode, CaptureResultExtras resultExtras) { if (DEBUG) { Log.d(TAG, String.format( "Device error received, code %d, frame number %d, request ID %d, subseq ID %d", errorCode, resultExtras.getFrameNumber(), resultExtras.getRequestId(), resultExtras.getSubsequenceId())); } synchronized(mInterfaceLock) { if (mRemoteDevice == null) { return; // Camera already closed } switch (errorCode) { case ERROR_CAMERA_DISCONNECTED: final long ident = Binder.clearCallingIdentity(); try { CameraDeviceImpl.this.mDeviceExecutor.execute(mCallOnDisconnected); } finally { Binder.restoreCallingIdentity(ident); } break; case ERROR_CAMERA_REQUEST: case ERROR_CAMERA_RESULT: case ERROR_CAMERA_BUFFER: onCaptureErrorLocked(errorCode, resultExtras); break; case ERROR_CAMERA_DEVICE: scheduleNotifyError(StateCallback.ERROR_CAMERA_DEVICE); break; case ERROR_CAMERA_DISABLED: scheduleNotifyError(StateCallback.ERROR_CAMERA_DISABLED); break; default: Log.e(TAG, "Unknown error from camera device: " + errorCode); scheduleNotifyError(StateCallback.ERROR_CAMERA_SERVICE); } } } private void scheduleNotifyError(int code) { mInError = true; final long ident = Binder.clearCallingIdentity(); try { CameraDeviceImpl.this.mDeviceExecutor.execute(obtainRunnable( CameraDeviceCallbacks::notifyError, this, code).recycleOnUse()); } finally { Binder.restoreCallingIdentity(ident); } } private void notifyError(int code) { if (!CameraDeviceImpl.this.isClosed()) { mDeviceCallback.onError(CameraDeviceImpl.this, code); } } @Override public void onRepeatingRequestError(long lastFrameNumber, int repeatingRequestId) { if (DEBUG) { Log.d(TAG, "Repeating request error received. Last frame number is " + lastFrameNumber); } synchronized(mInterfaceLock) { // Camera is already closed or no repeating request is present. if (mRemoteDevice == null || mRepeatingRequestId == REQUEST_ID_NONE) { return; // Camera already closed } checkEarlyTriggerSequenceComplete(mRepeatingRequestId, lastFrameNumber, mRepeatingRequestTypes); // Check if there is already a new repeating request if (mRepeatingRequestId == repeatingRequestId) { mRepeatingRequestId = REQUEST_ID_NONE; mRepeatingRequestTypes = null; } } } @Override public void onDeviceIdle() { if (DEBUG) { Log.d(TAG, "Camera now idle"); } synchronized(mInterfaceLock) { if (mRemoteDevice == null) return; // Camera already closed if (!CameraDeviceImpl.this.mIdle) { final long ident = Binder.clearCallingIdentity(); try { CameraDeviceImpl.this.mDeviceExecutor.execute(mCallOnIdle); } finally { Binder.restoreCallingIdentity(ident); } } CameraDeviceImpl.this.mIdle = true; } } @Override public void onCaptureStarted(final CaptureResultExtras resultExtras, final long timestamp) { int requestId = resultExtras.getRequestId(); final long frameNumber = resultExtras.getFrameNumber(); if (DEBUG) { Log.d(TAG, "Capture started for id " + requestId + " frame number " + frameNumber); } final CaptureCallbackHolder holder; synchronized(mInterfaceLock) { if (mRemoteDevice == null) return; // Camera already closed // Get the callback for this frame ID, if there is one holder = CameraDeviceImpl.this.mCaptureCallbackMap.get(requestId); if (holder == null) { return; } if (isClosed()) return; // Dispatch capture start notice final long ident = Binder.clearCallingIdentity(); try { holder.getExecutor().execute( new Runnable() { @Override public void run() { if (!CameraDeviceImpl.this.isClosed()) { final int subsequenceId = resultExtras.getSubsequenceId(); final CaptureRequest request = holder.getRequest(subsequenceId); if (holder.hasBatchedOutputs()) { // Send derived onCaptureStarted for requests within the // batch final Range fpsRange = request.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE); for (int i = 0; i < holder.getRequestCount(); i++) { holder.getCallback().onCaptureStarted( CameraDeviceImpl.this, holder.getRequest(i), timestamp - (subsequenceId - i) * NANO_PER_SECOND/fpsRange.getUpper(), frameNumber - (subsequenceId - i)); } } else { holder.getCallback().onCaptureStarted( CameraDeviceImpl.this, holder.getRequest(resultExtras.getSubsequenceId()), timestamp, frameNumber); } } } }); } finally { Binder.restoreCallingIdentity(ident); } } } @Override public void onResultReceived(CameraMetadataNative result, CaptureResultExtras resultExtras, PhysicalCaptureResultInfo physicalResults[]) throws RemoteException { int requestId = resultExtras.getRequestId(); long frameNumber = resultExtras.getFrameNumber(); if (DEBUG) { Log.v(TAG, "Received result frame " + frameNumber + " for id " + requestId); } synchronized(mInterfaceLock) { if (mRemoteDevice == null) return; // Camera already closed // TODO: Handle CameraCharacteristics access from CaptureResult correctly. result.set(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE, getCharacteristics().get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE)); final CaptureCallbackHolder holder = CameraDeviceImpl.this.mCaptureCallbackMap.get(requestId); final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId()); boolean isPartialResult = (resultExtras.getPartialResultCount() < mTotalPartialCount); int requestType = request.getRequestType(); // Check if we have a callback for this if (holder == null) { if (DEBUG) { Log.d(TAG, "holder is null, early return at frame " + frameNumber); } mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult, requestType); return; } if (isClosed()) { if (DEBUG) { Log.d(TAG, "camera is closed, early return at frame " + frameNumber); } mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult, requestType); return; } Runnable resultDispatch = null; CaptureResult finalResult; // Make a copy of the native metadata before it gets moved to a CaptureResult // object. final CameraMetadataNative resultCopy; if (holder.hasBatchedOutputs()) { resultCopy = new CameraMetadataNative(result); } else { resultCopy = null; } // Either send a partial result or the final capture completed result if (isPartialResult) { final CaptureResult resultAsCapture = new CaptureResult(result, request, resultExtras); // Partial result resultDispatch = new Runnable() { @Override public void run() { if (!CameraDeviceImpl.this.isClosed()) { if (holder.hasBatchedOutputs()) { // Send derived onCaptureProgressed for requests within // the batch. for (int i = 0; i < holder.getRequestCount(); i++) { CameraMetadataNative resultLocal = new CameraMetadataNative(resultCopy); CaptureResult resultInBatch = new CaptureResult( resultLocal, holder.getRequest(i), resultExtras); holder.getCallback().onCaptureProgressed( CameraDeviceImpl.this, holder.getRequest(i), resultInBatch); } } else { holder.getCallback().onCaptureProgressed( CameraDeviceImpl.this, request, resultAsCapture); } } } }; finalResult = resultAsCapture; } else { List partialResults = mFrameNumberTracker.popPartialResults(frameNumber); final long sensorTimestamp = result.get(CaptureResult.SENSOR_TIMESTAMP); final Range fpsRange = request.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE); final int subsequenceId = resultExtras.getSubsequenceId(); final TotalCaptureResult resultAsCapture = new TotalCaptureResult(result, request, resultExtras, partialResults, holder.getSessionId(), physicalResults); // Final capture result resultDispatch = new Runnable() { @Override public void run() { if (!CameraDeviceImpl.this.isClosed()){ if (holder.hasBatchedOutputs()) { // Send derived onCaptureCompleted for requests within // the batch. for (int i = 0; i < holder.getRequestCount(); i++) { resultCopy.set(CaptureResult.SENSOR_TIMESTAMP, sensorTimestamp - (subsequenceId - i) * NANO_PER_SECOND/fpsRange.getUpper()); CameraMetadataNative resultLocal = new CameraMetadataNative(resultCopy); // No logical multi-camera support for batched output mode. TotalCaptureResult resultInBatch = new TotalCaptureResult( resultLocal, holder.getRequest(i), resultExtras, partialResults, holder.getSessionId(), new PhysicalCaptureResultInfo[0]); holder.getCallback().onCaptureCompleted( CameraDeviceImpl.this, holder.getRequest(i), resultInBatch); } } else { holder.getCallback().onCaptureCompleted( CameraDeviceImpl.this, request, resultAsCapture); } } } }; finalResult = resultAsCapture; } final long ident = Binder.clearCallingIdentity(); try { holder.getExecutor().execute(resultDispatch); } finally { Binder.restoreCallingIdentity(ident); } // Collect the partials for a total result; or mark the frame as totally completed mFrameNumberTracker.updateTracker(frameNumber, finalResult, isPartialResult, requestType); // Fire onCaptureSequenceCompleted if (!isPartialResult) { checkAndFireSequenceComplete(); } } } @Override public void onPrepared(int streamId) { final OutputConfiguration output; final StateCallbackKK sessionCallback; if (DEBUG) { Log.v(TAG, "Stream " + streamId + " is prepared"); } synchronized(mInterfaceLock) { output = mConfiguredOutputs.get(streamId); sessionCallback = mSessionStateCallback; } if (sessionCallback == null) return; if (output == null) { Log.w(TAG, "onPrepared invoked for unknown output Surface"); return; } final List surfaces = output.getSurfaces(); for (Surface surface : surfaces) { sessionCallback.onSurfacePrepared(surface); } } @Override public void onRequestQueueEmpty() { final StateCallbackKK sessionCallback; if (DEBUG) { Log.v(TAG, "Request queue becomes empty"); } synchronized(mInterfaceLock) { sessionCallback = mSessionStateCallback; } if (sessionCallback == null) return; sessionCallback.onRequestQueueEmpty(); } /** * Called by onDeviceError for handling single-capture failures. */ private void onCaptureErrorLocked(int errorCode, CaptureResultExtras resultExtras) { final int requestId = resultExtras.getRequestId(); final int subsequenceId = resultExtras.getSubsequenceId(); final long frameNumber = resultExtras.getFrameNumber(); final String errorPhysicalCameraId = resultExtras.getErrorPhysicalCameraId(); final CaptureCallbackHolder holder = CameraDeviceImpl.this.mCaptureCallbackMap.get(requestId); final CaptureRequest request = holder.getRequest(subsequenceId); Runnable failureDispatch = null; if (errorCode == ERROR_CAMERA_BUFFER) { // Because 1 stream id could map to multiple surfaces, we need to specify both // streamId and surfaceId. List surfaces = mConfiguredOutputs.get(resultExtras.getErrorStreamId()).getSurfaces(); for (Surface surface : surfaces) { if (!request.containsTarget(surface)) { continue; } if (DEBUG) { Log.v(TAG, String.format("Lost output buffer reported for frame %d, target %s", frameNumber, surface)); } failureDispatch = new Runnable() { @Override public void run() { if (!CameraDeviceImpl.this.isClosed()){ holder.getCallback().onCaptureBufferLost( CameraDeviceImpl.this, request, surface, frameNumber); } } }; // Dispatch the failure callback final long ident = Binder.clearCallingIdentity(); try { holder.getExecutor().execute(failureDispatch); } finally { Binder.restoreCallingIdentity(ident); } } } else { boolean mayHaveBuffers = (errorCode == ERROR_CAMERA_RESULT); // This is only approximate - exact handling needs the camera service and HAL to // disambiguate between request failures to due abort and due to real errors. For // now, assume that if the session believes we're mid-abort, then the error is due // to abort. int reason = (mCurrentSession != null && mCurrentSession.isAborting()) ? CaptureFailure.REASON_FLUSHED : CaptureFailure.REASON_ERROR; final CaptureFailure failure = new CaptureFailure( request, reason, /*dropped*/ mayHaveBuffers, requestId, frameNumber, errorPhysicalCameraId); failureDispatch = new Runnable() { @Override public void run() { if (!CameraDeviceImpl.this.isClosed()){ holder.getCallback().onCaptureFailed( CameraDeviceImpl.this, request, failure); } } }; // Fire onCaptureSequenceCompleted if appropriate if (DEBUG) { Log.v(TAG, String.format("got error frame %d", frameNumber)); } mFrameNumberTracker.updateTracker(frameNumber, /*error*/true, request.getRequestType()); checkAndFireSequenceComplete(); // Dispatch the failure callback final long ident = Binder.clearCallingIdentity(); try { holder.getExecutor().execute(failureDispatch); } finally { Binder.restoreCallingIdentity(ident); } } } } // public class CameraDeviceCallbacks /** * A camera specific adapter {@link Executor} that posts all executed tasks onto the given * {@link Handler}. * * @hide */ private static class CameraHandlerExecutor implements Executor { private final Handler mHandler; public CameraHandlerExecutor(@NonNull Handler handler) { mHandler = Preconditions.checkNotNull(handler); } @Override public void execute(Runnable command) { // Return value of 'post()' will be ignored in order to keep the // same camera behavior. For further details see b/74605221 . mHandler.post(command); } } /** * Default executor management. * *

* If executor is null, get the current thread's * Looper to create a Executor with. If no looper exists, throw * {@code IllegalArgumentException}. *

*/ static Executor checkExecutor(Executor executor) { return (executor == null) ? checkAndWrapHandler(null) : executor; } /** * Default executor management. * *

If the callback isn't null, check the executor, otherwise pass it through.

*/ public static Executor checkExecutor(Executor executor, T callback) { return (callback != null) ? checkExecutor(executor) : executor; } /** * Wrap Handler in Executor. * *

* If handler is null, get the current thread's * Looper to create a Executor with. If no looper exists, throw * {@code IllegalArgumentException}. *

*/ public static Executor checkAndWrapHandler(Handler handler) { return new CameraHandlerExecutor(checkHandler(handler)); } /** * Default handler management. * *

* If handler is null, get the current thread's * Looper to create a Handler with. If no looper exists, throw {@code IllegalArgumentException}. *

*/ static 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; } /** * Default handler management, conditional on there being a callback. * *

If the callback isn't null, check the handler, otherwise pass it through.

*/ static Handler checkHandler(Handler handler, T callback) { if (callback != null) { return checkHandler(handler); } return handler; } private void checkIfCameraClosedOrInError() throws CameraAccessException { if (mRemoteDevice == null) { throw new IllegalStateException("CameraDevice was already closed"); } if (mInError) { throw new CameraAccessException(CameraAccessException.CAMERA_ERROR, "The camera device has encountered a serious error"); } } /** Whether the camera device has started to close (may not yet have finished) */ private boolean isClosed() { return mClosing.get(); } private CameraCharacteristics getCharacteristics() { return mCharacteristics; } /** * Listener for binder death. * *

Handle binder death for ICameraDeviceUser. Trigger onError.

*/ @Override public void binderDied() { Log.w(TAG, "CameraDevice " + mCameraId + " died unexpectedly"); if (mRemoteDevice == null) { return; // Camera already closed } mInError = true; Runnable r = new Runnable() { @Override public void run() { if (!isClosed()) { mDeviceCallback.onError(CameraDeviceImpl.this, StateCallback.ERROR_CAMERA_SERVICE); } } }; final long ident = Binder.clearCallingIdentity(); try { CameraDeviceImpl.this.mDeviceExecutor.execute(r); } finally { Binder.restoreCallingIdentity(ident); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy