Merge "camera2: Implement most of CameraCaptureSession" into lmp-preview-dev

This commit is contained in:
Igor Murashkin
2014-06-04 01:00:06 +00:00
committed by Android (Google) Code Review
16 changed files with 1612 additions and 33 deletions

View File

@@ -54,7 +54,7 @@ import java.util.List;
public abstract class CameraCaptureSession implements AutoCloseable {
/**
* Get the camera device that this session is created for
* Get the camera device that this session is created for.
*/
public abstract CameraDevice getDevice();
@@ -90,8 +90,9 @@ public abstract class CameraCaptureSession implements AutoCloseable {
*
* @throws CameraAccessException if the camera device is no longer connected or has
* encountered a fatal error
* @throws IllegalStateException if this session is no longer active, either because a new
* session has been created or the camera device has been closed.
* @throws IllegalStateException if this session is no longer active, either because the session
* was explicitly closed, a new session has been created
* or the camera device has been closed.
* @throws IllegalArgumentException if the request targets Surfaces that are not configured as
* outputs for this session. Or if the handler is null, the
* listener is not null, and the calling thread has no looper.
@@ -99,6 +100,7 @@ public abstract class CameraCaptureSession implements AutoCloseable {
* @see #captureBurst
* @see #setRepeatingRequest
* @see #setRepeatingBurst
* @see #abortCaptures
*/
public abstract int capture(CaptureRequest request, CaptureListener listener, Handler handler)
throws CameraAccessException;
@@ -132,8 +134,9 @@ public abstract class CameraCaptureSession implements AutoCloseable {
*
* @throws CameraAccessException if the camera device is no longer connected or has
* encountered a fatal error
* @throws IllegalStateException if this session is no longer active, either because a new
* session has been created or the camera device has been closed.
* @throws IllegalStateException if this session is no longer active, either because the session
* was explicitly closed, a new session has been created
* or the camera device has been closed.
* @throws IllegalArgumentException If the requests target Surfaces not currently configured as
* outputs. Or if the handler is null, the listener is not
* null, and the calling thread has no looper.
@@ -141,6 +144,7 @@ public abstract class CameraCaptureSession implements AutoCloseable {
* @see #capture
* @see #setRepeatingRequest
* @see #setRepeatingBurst
* @see #abortCaptures
*/
public abstract int captureBurst(List<CaptureRequest> requests, CaptureListener listener,
Handler handler) throws CameraAccessException;
@@ -188,11 +192,13 @@ public abstract class CameraCaptureSession implements AutoCloseable {
*
* @throws CameraAccessException if the camera device is no longer connected or has
* encountered a fatal error
* @throws IllegalStateException if this session is no longer active, either because a new
* session has been created or the camera device has been closed.
* @throws IllegalStateException if this session is no longer active, either because the session
* was explicitly closed, a new session has been created
* or the camera device has been closed.
* @throws IllegalArgumentException If the requests reference Surfaces that are not currently
* configured as outputs. Or if the handler is null, the
* listener is not null, and the calling thread has no looper.
* Or if no requests were passed in.
*
* @see #capture
* @see #captureBurst
@@ -246,11 +252,13 @@ public abstract class CameraCaptureSession implements AutoCloseable {
*
* @throws CameraAccessException if the camera device is no longer connected or has
* encountered a fatal error
* @throws IllegalStateException if this session is no longer active, either because a new
* session has been created or the camera device has been closed.
* @throws IllegalStateException if this session is no longer active, either because the session
* was explicitly closed, a new session has been created
* or the camera device has been closed.
* @throws IllegalArgumentException If the requests reference Surfaces not currently configured
* as outputs. Or if the handler is null, the listener is not
* null, and the calling thread has no looper.
* null, and the calling thread has no looper. Or if no
* requests were passed in.
*
* @see #capture
* @see #captureBurst
@@ -274,8 +282,9 @@ public abstract class CameraCaptureSession implements AutoCloseable {
*
* @throws CameraAccessException if the camera device is no longer connected or has
* encountered a fatal error
* @throws IllegalStateException if this session is no longer active, either because a new
* session has been created or the camera device has been closed.
* @throws IllegalStateException if this session is no longer active, either because the session
* was explicitly closed, a new session has been created
* or the camera device has been closed.
*
* @see #setRepeatingRequest
* @see #setRepeatingBurst
@@ -308,8 +317,9 @@ public abstract class CameraCaptureSession implements AutoCloseable {
*
* @throws CameraAccessException if the camera device is no longer connected or has
* encountered a fatal error
* @throws IllegalStateException if this session is no longer active, either because a new
* session has been created or the camera device has been closed.
* @throws IllegalStateException if this session is no longer active, either because the session
* was explicitly closed, a new session has been created
* or the camera device has been closed.
*
* @see #setRepeatingRequest
* @see #setRepeatingBurst
@@ -320,8 +330,8 @@ public abstract class CameraCaptureSession implements AutoCloseable {
/**
* Close this capture session asynchronously.
*
* <p>Closing a session frees up the target output Surfaces of the session for reuse with either a
* new session, or to other APIs that can draw to Surfaces.</p>
* <p>Closing a session frees up the target output Surfaces of the session for reuse with either
* a new session, or to other APIs that can draw to Surfaces.</p>
*
* <p>Note that creating a new capture session with {@link CameraDevice#createCaptureSession}
* will close any existing capture session automatically, and call the older session listener's
@@ -334,6 +344,8 @@ public abstract class CameraCaptureSession implements AutoCloseable {
* However, any in-progress capture requests submitted to the session will be completed as
* normal; once all captures have completed and the session has been torn down,
* {@link StateListener#onClosed} will be called.</p>
*
* <p>Closing a session is idempotent; closing more than once has no effect.</p>
*/
@Override
public abstract void close();

View File

@@ -244,6 +244,7 @@ public interface CameraDevice extends AutoCloseable {
* @see StreamConfigurationMap#getOutputSizes(Class)
* @deprecated Use {@link #createCaptureSession} instead
*/
@Deprecated
public void configureOutputs(List<Surface> outputs) throws CameraAccessException;
/**
@@ -432,6 +433,7 @@ public interface CameraDevice extends AutoCloseable {
* @see #setRepeatingBurst
* @deprecated Use {@link CameraCaptureSession} instead
*/
@Deprecated
public int capture(CaptureRequest request, CaptureListener listener, Handler handler)
throws CameraAccessException;
@@ -470,13 +472,15 @@ public interface CameraDevice extends AutoCloseable {
* or the camera device has been closed.
* @throws IllegalArgumentException If the requests target Surfaces not
* currently configured as outputs. Or if the handler is null, the listener
* is not null, and the calling thread has no looper.
* is not null, and the calling thread has no looper. Or if no requests were
* passed in.
*
* @see #capture
* @see #setRepeatingRequest
* @see #setRepeatingBurst
* @deprecated Use {@link CameraCaptureSession} instead
*/
@Deprecated
public int captureBurst(List<CaptureRequest> requests, CaptureListener listener,
Handler handler) throws CameraAccessException;
@@ -536,6 +540,7 @@ public interface CameraDevice extends AutoCloseable {
* @see #flush
* @deprecated Use {@link CameraCaptureSession} instead
*/
@Deprecated
public int setRepeatingRequest(CaptureRequest request, CaptureListener listener,
Handler handler) throws CameraAccessException;
@@ -586,7 +591,8 @@ public interface CameraDevice extends AutoCloseable {
* or the camera device has been closed.
* @throws IllegalArgumentException If the requests reference Surfaces not
* currently configured as outputs. Or if the handler is null, the listener
* is not null, and the calling thread has no looper.
* is not null, and the calling thread has no looper. Or if no requests were
* passed in.
*
* @see #capture
* @see #captureBurst
@@ -595,6 +601,7 @@ public interface CameraDevice extends AutoCloseable {
* @see #flush
* @deprecated Use {@link CameraCaptureSession} instead
*/
@Deprecated
public int setRepeatingBurst(List<CaptureRequest> requests, CaptureListener listener,
Handler handler) throws CameraAccessException;
@@ -620,6 +627,7 @@ public interface CameraDevice extends AutoCloseable {
* @see StateListener#onIdle
* @deprecated Use {@link CameraCaptureSession} instead
*/
@Deprecated
public void stopRepeating() throws CameraAccessException;
/**
@@ -657,6 +665,7 @@ public interface CameraDevice extends AutoCloseable {
* @see #configureOutputs
* @deprecated Use {@link CameraCaptureSession} instead
*/
@Deprecated
public void flush() throws CameraAccessException;
/**
@@ -691,6 +700,7 @@ public interface CameraDevice extends AutoCloseable {
* @see #setRepeatingBurst
* @deprecated Use {@link CameraCaptureSession} instead
*/
@Deprecated
public static abstract class CaptureListener {
/**
@@ -1042,6 +1052,7 @@ public interface CameraDevice extends AutoCloseable {
* @param camera the camera device has that become unconfigured
* @deprecated Use {@link CameraCaptureSession.StateListener} instead.
*/
@Deprecated
public void onUnconfigured(CameraDevice camera) {
// Default empty implementation
}
@@ -1072,6 +1083,7 @@ public interface CameraDevice extends AutoCloseable {
* @see CameraDevice#setRepeatingRequest
* @deprecated Use {@link CameraCaptureSession.StateListener} instead.
*/
@Deprecated
public void onActive(CameraDevice camera) {
// Default empty implementation
}
@@ -1106,6 +1118,7 @@ public interface CameraDevice extends AutoCloseable {
* @see CameraDevice#flush
* @deprecated Use {@link CameraCaptureSession.StateListener} instead.
*/
@Deprecated
public void onBusy(CameraDevice camera) {
// Default empty implementation
}
@@ -1154,6 +1167,7 @@ public interface CameraDevice extends AutoCloseable {
* @see CameraDevice#flush
* @deprecated Use {@link CameraCaptureSession.StateListener} instead.
*/
@Deprecated
public void onIdle(CameraDevice camera) {
// Default empty implementation
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright (C) 2014 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.dispatch;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import static com.android.internal.util.Preconditions.*;
/**
* Broadcast a single dispatch into multiple other dispatchables.
*
* <p>Every time {@link #dispatch} is invoked, all the broadcast targets will
* see the same dispatch as well. The first target's return value is returned.</p>
*
* <p>This enables a single listener to be converted into a multi-listener.</p>
*/
public class BroadcastDispatcher<T> implements Dispatchable<T> {
private final List<Dispatchable<T>> mDispatchTargets;
/**
* Create a broadcast dispatcher from the supplied dispatch targets.
*
* @param dispatchTargets one or more targets to dispatch to
*/
@SafeVarargs
public BroadcastDispatcher(Dispatchable<T>... dispatchTargets) {
mDispatchTargets = Arrays.asList(
checkNotNull(dispatchTargets, "dispatchTargets must not be null"));
}
@Override
public Object dispatch(Method method, Object[] args) throws Throwable {
Object result = null;
boolean gotResult = false;
for (Dispatchable<T> dispatchTarget : mDispatchTargets) {
Object localResult = dispatchTarget.dispatch(method, args);
if (!gotResult) {
gotResult = true;
result = localResult;
}
}
return result;
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright (C) 2014 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.dispatch;
import java.lang.reflect.Method;
/**
* Dynamically dispatch a method and its argument to some object.
*
* <p>This can be used to intercept method calls and do work around them, redirect work,
* or block calls entirely.</p>
*/
public interface Dispatchable<T> {
/**
* Dispatch the method and arguments to this object.
* @param method a method defined in class {@code T}
* @param args arguments corresponding to said {@code method}
* @return the object returned when invoking {@code method}
* @throws Throwable any exception that might have been raised while invoking the method
*/
public Object dispatch(Method method, Object[] args) throws Throwable;
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright (C) 2014 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.dispatch;
import java.lang.reflect.Method;
import static com.android.internal.util.Preconditions.*;
/**
* Duck typing dispatcher; converts dispatch methods calls from one class to another by
* looking up equivalently methods at runtime by name.
*
* <p>For example, if two types have identical method names and arguments, but
* are not subclasses/subinterfaces of each other, this dispatcher will allow calls to be
* made from one type to the other.</p>
*
* @param <TFrom> source dispatch type, whose methods with {@link #dispatch} will be called
* @param <T> destination dispatch type, methods will be converted to the class of {@code T}
*/
public class DuckTypingDispatcher<TFrom, T> implements Dispatchable<TFrom> {
private final MethodNameInvoker<T> mDuck;
/**
* Create a new duck typing dispatcher.
*
* @param target destination dispatch type, methods will be redirected to this dispatcher
* @param targetClass destination dispatch class, methods will be converted to this class's
*/
public DuckTypingDispatcher(Dispatchable<T> target, Class<T> targetClass) {
checkNotNull(targetClass, "targetClass must not be null");
checkNotNull(target, "target must not be null");
mDuck = new MethodNameInvoker<T>(target, targetClass);
}
@Override
public Object dispatch(Method method, Object[] args) {
return mDuck.invoke(method.getName(), args);
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright (C) 2014 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.dispatch;
import android.hardware.camera2.utils.UncheckedThrow;
import android.os.Handler;
import android.util.Log;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import static com.android.internal.util.Preconditions.*;
/**
* Forward all interface calls into a handler by posting it as a {@code Runnable}.
*
* <p>All calls will return immediately; functions with return values will return a default
* value of {@code null}, {@code 0}, or {@code false} where that value is legal.</p>
*
* <p>Any exceptions thrown on the handler while trying to invoke a method
* will be re-thrown. Throwing checked exceptions on a handler which doesn't expect any
* checked exceptions to be thrown will result in "undefined" behavior
* (although in practice it is usually thrown as normal).</p>
*/
public class HandlerDispatcher<T> implements Dispatchable<T> {
private static final String TAG = "HandlerDispatcher";
private final Dispatchable<T> mDispatchTarget;
private final Handler mHandler;
/**
* Create a dispatcher that forwards it's dispatch calls by posting
* them onto the {@code handler} as a {@code Runnable}.
*
* @param dispatchTarget the destination whose method calls will be redirected into the handler
* @param handler all calls into {@code dispatchTarget} will be posted onto this handler
* @param <T> the type of the element you want to wrap.
* @return a dispatcher that will forward it's dispatch calls to a handler
*/
public HandlerDispatcher(Dispatchable<T> dispatchTarget, Handler handler) {
mDispatchTarget = checkNotNull(dispatchTarget, "dispatchTarget must not be null");
mHandler = checkNotNull(handler, "handler must not be null");
}
@Override
public Object dispatch(final Method method, final Object[] args) throws Throwable {
mHandler.post(new Runnable() {
@Override
public void run() {
try {
mDispatchTarget.dispatch(method, args);
} catch (InvocationTargetException e) {
Throwable t = e.getTargetException();
// Potential UB. Hopefully 't' is a runtime exception.
UncheckedThrow.throwAnyException(t);
} catch (IllegalAccessException e) {
// Impossible
Log.wtf(TAG, "IllegalAccessException while invoking " + method, e);
} catch (IllegalArgumentException e) {
// Impossible
Log.wtf(TAG, "IllegalArgumentException while invoking " + method, e);
} catch (Throwable e) {
UncheckedThrow.throwAnyException(e);
}
}
});
// TODO handle primitive return values that would avoid NPE if unboxed
return null;
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright (C) 2014 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.dispatch;
import android.hardware.camera2.utils.UncheckedThrow;
import android.util.Log;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import static com.android.internal.util.Preconditions.*;
public class InvokeDispatcher<T> implements Dispatchable<T> {
private static final String TAG = "InvocationSink";
private final T mTarget;
public InvokeDispatcher(T target) {
mTarget = checkNotNull(target, "target must not be null");
}
@Override
public Object dispatch(Method method, Object[] args) {
try {
return method.invoke(mTarget, args);
} catch (InvocationTargetException e) {
Throwable t = e.getTargetException();
// Potential UB. Hopefully 't' is a runtime exception.
UncheckedThrow.throwAnyException(t);
} catch (IllegalAccessException e) {
// Impossible
Log.wtf(TAG, "IllegalAccessException while invoking " + method, e);
} catch (IllegalArgumentException e) {
// Impossible
Log.wtf(TAG, "IllegalArgumentException while invoking " + method, e);
}
// unreachable
return null;
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright (C) 2014 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.dispatch;
import android.hardware.camera2.utils.UncheckedThrow;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;
import static com.android.internal.util.Preconditions.*;
/**
* Invoke a method on a dispatchable by its name (without knowing the {@code Method} ahead of time).
*
* @param <T> destination dispatch type, methods will be looked up in the class of {@code T}
*/
public class MethodNameInvoker<T> {
private final Dispatchable<T> mTarget;
private final Class<T> mTargetClass;
private final ConcurrentHashMap<String, Method> mMethods =
new ConcurrentHashMap<>();
/**
* Create a new method name invoker.
*
* @param target destination dispatch type, invokes will be redirected to this dispatcher
* @param targetClass destination dispatch class, the invoked methods will be from this class
*/
public MethodNameInvoker(Dispatchable<T> target, Class<T> targetClass) {
mTargetClass = targetClass;
mTarget = target;
}
/**
* Invoke a method by its name.
*
* <p>If more than one method exists in {@code targetClass}, the first method will be used.</p>
*
* @param methodName
* The name of the method, which will be matched 1:1 to the destination method
* @param params
* Variadic parameter list.
* @return
* The same kind of value that would normally be returned by calling {@code methodName}
* statically.
*
* @throws IllegalArgumentException if {@code methodName} does not exist on the target class
* @throws Throwable will rethrow anything that the target method would normally throw
*/
@SuppressWarnings("unchecked")
public <K> K invoke(String methodName, Object... params) {
checkNotNull(methodName, "methodName must not be null");
Method targetMethod = mMethods.get(methodName);
if (targetMethod == null) {
for (Method method : mTargetClass.getMethods()) {
// TODO future: match by # of params and types of params if possible
if (method.getName().equals(methodName)) {
targetMethod = method;
mMethods.put(methodName, targetMethod);
break;
}
}
if (targetMethod == null) {
throw new IllegalArgumentException(
"Method " + methodName + " does not exist on class " + mTargetClass);
}
}
try {
return (K) mTarget.dispatch(targetMethod, params);
} catch (Throwable e) {
UncheckedThrow.throwAnyException(e);
// unreachable
return null;
}
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright (C) 2014 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.dispatch;
import java.lang.reflect.Method;
/**
* Do nothing when dispatching; follows the null object pattern.
*/
public class NullDispatcher<T> implements Dispatchable<T> {
/**
* Create a dispatcher that does nothing when dispatched to.
*/
public NullDispatcher() {
}
/**
* Do nothing; all parameters are ignored.
*/
@Override
public Object dispatch(Method method, Object[] args) {
return null;
}
}

View File

@@ -0,0 +1,3 @@
<body>
{@hide}
</body>

View File

@@ -0,0 +1,574 @@
/*
* Copyright (C) 2014 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 android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.dispatch.BroadcastDispatcher;
import android.hardware.camera2.dispatch.Dispatchable;
import android.hardware.camera2.dispatch.DuckTypingDispatcher;
import android.hardware.camera2.dispatch.HandlerDispatcher;
import android.hardware.camera2.dispatch.InvokeDispatcher;
import android.hardware.camera2.dispatch.NullDispatcher;
import android.hardware.camera2.utils.TaskDrainer;
import android.hardware.camera2.utils.TaskSingleDrainer;
import android.os.Handler;
import android.util.Log;
import android.view.Surface;
import java.util.Arrays;
import java.util.List;
import static android.hardware.camera2.impl.CameraDevice.checkHandler;
import static com.android.internal.util.Preconditions.*;
public class CameraCaptureSessionImpl extends CameraCaptureSession {
private static final String TAG = "CameraCaptureSession";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
/** User-specified set of surfaces used as the configuration outputs */
private final List<Surface> mOutputs;
/**
* User-specified state listener, used for outgoing events; calls to this object will be
* automatically {@link Handler#post(Runnable) posted} to {@code mStateHandler}.
*/
private final CameraCaptureSession.StateListener mStateListener;
/** User-specified state handler used for outgoing state listener events */
private final Handler mStateHandler;
/** Internal camera device; used to translate calls into existing deprecated API */
private final android.hardware.camera2.impl.CameraDevice mDeviceImpl;
/** Internal handler; used for all incoming events to preserve total order */
private final Handler mDeviceHandler;
/** Drain Sequence IDs which have been queued but not yet finished with aborted/completed */
private final TaskDrainer<Integer> mSequenceDrainer;
/** Drain state transitions from ACTIVE -> IDLE */
private final TaskSingleDrainer mIdleDrainer;
/** Drain state transitions from BUSY -> IDLE */
private final TaskSingleDrainer mAbortDrainer;
/** Drain the UNCONFIGURED state transition */
private final TaskSingleDrainer mUnconfigureDrainer;
/** This session is closed; all further calls will throw ISE */
private boolean mClosed = false;
/** Do not unconfigure if this is set; another session will overwrite configuration */
private boolean mSkipUnconfigure = false;
/** Is the session in the process of aborting? Pay attention to BUSY->IDLE transitions. */
private boolean mAborting;
/**
* Create a new CameraCaptureSession.
*
* <p>The camera device must already be in the {@code IDLE} state when this is invoked.
* There must be no pending actions
* (e.g. no pending captures, no repeating requests, no flush).</p>
*/
CameraCaptureSessionImpl(List<Surface> outputs,
CameraCaptureSession.StateListener listener, Handler stateHandler,
android.hardware.camera2.impl.CameraDevice deviceImpl,
Handler deviceStateHandler, boolean configureSuccess) {
if (outputs == null || outputs.isEmpty()) {
throw new IllegalArgumentException("outputs must be a non-null, non-empty list");
} else if (listener == null) {
throw new IllegalArgumentException("listener must not be null");
}
// TODO: extra verification of outputs
mOutputs = outputs;
mStateHandler = checkHandler(stateHandler);
mStateListener = createUserStateListenerProxy(mStateHandler, listener);
mDeviceHandler = checkNotNull(deviceStateHandler, "deviceStateHandler must not be null");
mDeviceImpl = checkNotNull(deviceImpl, "deviceImpl must not be null");
/*
* Use the same handler as the device's StateListener for all the internal coming events
*
* This ensures total ordering between CameraDevice.StateListener and
* CameraDevice.CaptureListener events.
*/
mSequenceDrainer = new TaskDrainer<>(mDeviceHandler, new SequenceDrainListener(),
/*name*/"seq");
mIdleDrainer = new TaskSingleDrainer(mDeviceHandler, new IdleDrainListener(),
/*name*/"idle");
mAbortDrainer = new TaskSingleDrainer(mDeviceHandler, new AbortDrainListener(),
/*name*/"abort");
mUnconfigureDrainer = new TaskSingleDrainer(mDeviceHandler, new UnconfigureDrainListener(),
/*name*/"unconf");
// CameraDevice should call configureOutputs and have it finish before constructing us
if (configureSuccess) {
mStateListener.onConfigured(this);
} else {
mStateListener.onConfigureFailed(this);
mClosed = true; // do not fire any other callbacks, do not allow any other work
}
}
@Override
public CameraDevice getDevice() {
return mDeviceImpl;
}
@Override
public synchronized int capture(CaptureRequest request, CaptureListener listener,
Handler handler) throws CameraAccessException {
checkNotClosed();
checkLegalToCapture();
handler = checkHandler(handler);
if (VERBOSE) {
Log.v(TAG, "capture - request " + request + ", listener " + listener + " handler" +
"" + handler);
}
return addPendingSequence(mDeviceImpl.capture(request,
createCaptureListenerProxy(handler, listener), mDeviceHandler));
}
@Override
public synchronized int captureBurst(List<CaptureRequest> requests, CaptureListener listener,
Handler handler) throws CameraAccessException {
checkNotClosed();
checkLegalToCapture();
handler = checkHandler(handler);
if (VERBOSE) {
CaptureRequest[] requestArray = requests.toArray(new CaptureRequest[0]);
Log.v(TAG, "captureBurst - requests " + Arrays.toString(requestArray) + ", listener " +
listener + " handler" + "" + handler);
}
return addPendingSequence(mDeviceImpl.captureBurst(requests,
createCaptureListenerProxy(handler, listener), mDeviceHandler));
}
@Override
public synchronized int setRepeatingRequest(CaptureRequest request, CaptureListener listener,
Handler handler) throws CameraAccessException {
checkNotClosed();
checkLegalToCapture();
handler = checkHandler(handler);
return addPendingSequence(mDeviceImpl.setRepeatingRequest(request,
createCaptureListenerProxy(handler, listener), mDeviceHandler));
}
@Override
public synchronized int setRepeatingBurst(List<CaptureRequest> requests,
CaptureListener listener, Handler handler) throws CameraAccessException {
checkNotClosed();
checkLegalToCapture();
handler = checkHandler(handler);
if (VERBOSE) {
CaptureRequest[] requestArray = requests.toArray(new CaptureRequest[0]);
Log.v(TAG, "setRepeatingBurst - requests " + Arrays.toString(requestArray) +
", listener " + listener + " handler" + "" + handler);
}
return addPendingSequence(mDeviceImpl.setRepeatingBurst(requests,
createCaptureListenerProxy(handler, listener), mDeviceHandler));
}
@Override
public synchronized void stopRepeating() throws CameraAccessException {
checkNotClosed();
if (VERBOSE) {
Log.v(TAG, "stopRepeating");
}
mDeviceImpl.stopRepeating();
}
@Override
public synchronized void abortCaptures() throws CameraAccessException {
checkNotClosed();
if (VERBOSE) {
Log.v(TAG, "abortCaptures");
}
if (mAborting) {
Log.w(TAG, "abortCaptures - Session is already aborting; doing nothing");
return;
}
mAborting = true;
mAbortDrainer.taskStarted();
mDeviceImpl.flush();
// The next BUSY -> IDLE set of transitions will mark the end of the abort.
}
/**
* Replace this session with another session.
*
* <p>This is an optimization to avoid unconfiguring and then immediately having to
* reconfigure again.</p>
*
* <p>The semantics are identical to {@link #close}, except that unconfiguring will be skipped.
* <p>
*
* @see CameraCaptureSession#close
*/
synchronized void replaceSessionClose(CameraCaptureSession other) {
/*
* In order for creating new sessions to be fast, the new session should be created
* before the old session is closed.
*
* Otherwise the old session will always unconfigure if there is no new session to
* replace it.
*
* Unconfiguring could add hundreds of milliseconds of delay. We could race and attempt
* to skip unconfigure if a new session is created before the captures are all drained,
* but this would introduce nondeterministic behavior.
*/
// #close was already called explicitly, keep going the slow route
if (mClosed) {
return;
}
mSkipUnconfigure = true;
close();
}
@Override
public synchronized void close() {
if (mClosed) {
return;
}
mClosed = true;
/*
* Flush out any repeating request. Since camera is closed, no new requests
* can be queued, and eventually the entire request queue will be drained.
*
* Once this is done, wait for camera to idle, then unconfigure the camera.
* Once that's done, fire #onClosed.
*/
try {
mDeviceImpl.stopRepeating();
} catch (CameraAccessException e) {
// OK: close does not throw checked exceptions.
Log.e(TAG, "Exception while stopping repeating: ", e);
// TODO: call onError instead of onClosed if this happens
}
// If no sequences are pending, fire #onClosed immediately
mSequenceDrainer.beginDrain();
}
/**
* Post calls into a CameraCaptureSession.StateListener to the user-specified {@code handler}.
*/
private StateListener createUserStateListenerProxy(Handler handler, StateListener listener) {
InvokeDispatcher<StateListener> userListenerSink = new InvokeDispatcher<>(listener);
HandlerDispatcher<StateListener> handlerPassthrough =
new HandlerDispatcher<>(userListenerSink, handler);
return new ListenerProxies.SessionStateListenerProxy(handlerPassthrough);
}
/**
* Forward callbacks from
* CameraDevice.CaptureListener to the CameraCaptureSession.CaptureListener.
*
* <p>In particular, all calls are automatically split to go both to our own
* internal listener, and to the user-specified listener (by transparently posting
* to the user-specified handler).</p>
*
* <p>When a capture sequence finishes, update the pending checked sequences set.</p>
*/
@SuppressWarnings("deprecation")
private CameraDevice.CaptureListener createCaptureListenerProxy(
Handler handler, CaptureListener listener) {
CameraDevice.CaptureListener localListener = new CameraDevice.CaptureListener() {
@Override
public void onCaptureSequenceCompleted(CameraDevice camera,
int sequenceId, long frameNumber) {
finishPendingSequence(sequenceId);
}
@Override
public void onCaptureSequenceAborted(CameraDevice camera,
int sequenceId) {
finishPendingSequence(sequenceId);
}
};
/*
* Split the calls from the device listener into local listener and the following chain:
* - duck type from device listener to session listener
* - then forward the call to a handler
* - then finally invoke the destination method on the session listener object
*/
Dispatchable<CaptureListener> userListenerSink;
if (listener == null) { // OK: API allows the user to not specify a listener
userListenerSink = new NullDispatcher<>();
} else {
userListenerSink = new InvokeDispatcher<>(listener);
}
InvokeDispatcher<CameraDevice.CaptureListener> localSink =
new InvokeDispatcher<>(localListener);
HandlerDispatcher<CaptureListener> handlerPassthrough =
new HandlerDispatcher<>(userListenerSink, handler);
DuckTypingDispatcher<CameraDevice.CaptureListener, CaptureListener> duckToSessionCaptureListener
= new DuckTypingDispatcher<>(handlerPassthrough, CaptureListener.class);
BroadcastDispatcher<CameraDevice.CaptureListener> broadcaster =
new BroadcastDispatcher<CameraDevice.CaptureListener>(
duckToSessionCaptureListener,
localSink);
return new ListenerProxies.DeviceCaptureListenerProxy(broadcaster);
}
/**
*
* Create an internal state listener, to be invoked on the mDeviceHandler
*
* <p>It has a few behaviors:
* <ul>
* <li>Convert device state changes into session state changes.
* <li>Keep track of async tasks that the session began (idle, abort).
* </ul>
* </p>
* */
CameraDevice.StateListener getDeviceStateListener() {
final CameraCaptureSession session = this;
return new CameraDevice.StateListener() {
private boolean mBusy = false;
private boolean mActive = false;
@Override
public void onOpened(CameraDevice camera) {
throw new AssertionError("Camera must already be open before creating a session");
}
@Override
public void onDisconnected(CameraDevice camera) {
close();
}
@Override
public void onError(CameraDevice camera, int error) {
// TODO: Handle errors somehow.
Log.wtf(TAG, "Got device error " + error);
}
@Override
public void onActive(CameraDevice camera) {
mIdleDrainer.taskStarted();
mActive = true;
mStateListener.onActive(session);
}
@Override
public void onIdle(CameraDevice camera) {
boolean isAborting;
synchronized (session) {
isAborting = mAborting;
}
/*
* Check which states we transitioned through:
*
* (ACTIVE -> IDLE)
* (BUSY -> IDLE)
*
* Note that this is also legal:
* (ACTIVE -> BUSY -> IDLE)
*
* and mark those tasks as finished
*/
if (mBusy && isAborting) {
mAbortDrainer.taskFinished();
synchronized (session) {
mAborting = false;
}
}
if (mActive) {
mIdleDrainer.taskFinished();
}
mBusy = false;
mActive = false;
mStateListener.onReady(session);
}
@Override
public void onBusy(CameraDevice camera) {
mBusy = true;
// TODO: Queue captures during abort instead of failing them
// since the app won't be able to distinguish the two actives
Log.w(TAG, "Device is now busy; do not submit new captures (TODO: allow this)");
mStateListener.onActive(session);
}
@Override
public void onUnconfigured(CameraDevice camera) {
mUnconfigureDrainer.taskFinished();
}
};
}
@Override
protected void finalize() throws Throwable {
try {
close();
} finally {
super.finalize();
}
}
private void checkLegalToCapture() {
if (mAborting) {
throw new IllegalStateException(
"Session is aborting captures; new captures are not permitted");
}
}
private void checkNotClosed() {
if (mClosed) {
throw new IllegalStateException(
"Session has been closed; further changes are illegal.");
}
}
/**
* Notify the session that a pending capture sequence has just been queued.
*
* <p>During a shutdown/close, the session waits until all pending sessions are finished
* before taking any further steps to shut down itself.</p>
*
* @see #finishPendingSequence
*/
private int addPendingSequence(int sequenceId) {
mSequenceDrainer.taskStarted(sequenceId);
return sequenceId;
}
/**
* Notify the session that a pending capture sequence is now finished.
*
* <p>During a shutdown/close, once all pending sequences finish, it is safe to
* close the camera further by unconfiguring and then firing {@code onClosed}.</p>
*/
private void finishPendingSequence(int sequenceId) {
mSequenceDrainer.taskFinished(sequenceId);
}
private class SequenceDrainListener implements TaskDrainer.DrainListener {
@Override
public void onDrained() {
/*
* No repeating request is set; and the capture queue has fully drained.
*
* If no captures were queued to begin with, and an abort was queued,
* it's still possible to get another BUSY before the last IDLE.
*
* If the camera is already "IDLE" and no aborts are pending,
* then the drain immediately finishes.
*/
mAbortDrainer.beginDrain();
}
}
private class AbortDrainListener implements TaskDrainer.DrainListener {
@Override
public void onDrained() {
synchronized (CameraCaptureSessionImpl.this) {
/*
* Any queued aborts have now completed.
*
* It's now safe to wait to receive the final "IDLE" event, as the camera device
* will no longer again transition to "ACTIVE" by itself.
*
* If the camera is already "IDLE", then the drain immediately finishes.
*/
mIdleDrainer.beginDrain();
}
}
}
private class IdleDrainListener implements TaskDrainer.DrainListener {
@Override
public void onDrained() {
synchronized (CameraCaptureSessionImpl.this) {
/*
* The device is now IDLE, and has settled. It will not transition to
* ACTIVE or BUSY again by itself.
*
* It's now safe to unconfigure the outputs and after it's done invoke #onClosed.
*
* This operation is idempotent; a session will not be closed twice.
*/
// Fast path: A new capture session has replaced this one; don't unconfigure.
if (mSkipUnconfigure) {
mStateListener.onClosed(CameraCaptureSessionImpl.this);
return;
}
// Slow path: #close was called explicitly on this session; unconfigure first
try {
mUnconfigureDrainer.taskStarted();
mDeviceImpl.configureOutputs(null); // begin transition to unconfigured state
} catch (CameraAccessException e) {
// OK: do not throw checked exceptions.
Log.e(TAG, "Exception while configuring outputs: ", e);
// TODO: call onError instead of onClosed if this happens
}
mUnconfigureDrainer.beginDrain();
}
}
}
private class UnconfigureDrainListener implements TaskDrainer.DrainListener {
@Override
public void onDrained() {
synchronized (CameraCaptureSessionImpl.this) {
// The device has finished unconfiguring. It's now fully closed.
mStateListener.onClosed(CameraCaptureSessionImpl.this);
}
}
}
}

View File

@@ -62,6 +62,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
private final CameraDeviceCallbacks mCallbacks = new CameraDeviceCallbacks();
private final StateListener mDeviceListener;
private volatile StateListener mSessionStateListener;
private final Handler mDeviceHandler;
private boolean mIdle = true;
@@ -90,6 +91,8 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
*/
private final FrameNumberTracker mFrameNumberTracker = new FrameNumberTracker();
private CameraCaptureSessionImpl mCurrentSession;
// Runnables for all state transitions, except error, which needs the
// error code argument
@@ -98,6 +101,10 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
public void run() {
if (!CameraDevice.this.isClosed()) {
mDeviceListener.onOpened(CameraDevice.this);
StateListener sessionListener = mSessionStateListener;
if (sessionListener != null) {
sessionListener.onOpened(CameraDevice.this);
}
}
}
};
@@ -107,6 +114,10 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
public void run() {
if (!CameraDevice.this.isClosed()) {
mDeviceListener.onUnconfigured(CameraDevice.this);
StateListener sessionListener = mSessionStateListener;
if (sessionListener != null) {
sessionListener.onUnconfigured(CameraDevice.this);
}
}
}
};
@@ -116,6 +127,10 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
public void run() {
if (!CameraDevice.this.isClosed()) {
mDeviceListener.onActive(CameraDevice.this);
StateListener sessionListener = mSessionStateListener;
if (sessionListener != null) {
sessionListener.onActive(CameraDevice.this);
}
}
}
};
@@ -125,6 +140,10 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
public void run() {
if (!CameraDevice.this.isClosed()) {
mDeviceListener.onBusy(CameraDevice.this);
StateListener sessionListener = mSessionStateListener;
if (sessionListener != null) {
sessionListener.onBusy(CameraDevice.this);
}
}
}
};
@@ -133,6 +152,10 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
@Override
public void run() {
mDeviceListener.onClosed(CameraDevice.this);
StateListener sessionListener = mSessionStateListener;
if (sessionListener != null) {
sessionListener.onClosed(CameraDevice.this);
}
}
};
@@ -141,6 +164,10 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
public void run() {
if (!CameraDevice.this.isClosed()) {
mDeviceListener.onIdle(CameraDevice.this);
StateListener sessionListener = mSessionStateListener;
if (sessionListener != null) {
sessionListener.onIdle(CameraDevice.this);
}
}
}
};
@@ -150,6 +177,10 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
public void run() {
if (!CameraDevice.this.isClosed()) {
mDeviceListener.onDisconnected(CameraDevice.this);
StateListener sessionListener = mSessionStateListener;
if (sessionListener != null) {
sessionListener.onDisconnected(CameraDevice.this);
}
}
}
};
@@ -170,7 +201,6 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
tag = tag.substring(0, MAX_TAG_LEN);
}
TAG = tag;
DEBUG = Log.isLoggable(TAG, Log.DEBUG);
}
@@ -263,7 +293,43 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
public void createCaptureSession(List<Surface> outputs,
CameraCaptureSession.StateListener listener, Handler handler)
throws CameraAccessException {
// TODO
synchronized (mLock) {
if (DEBUG) {
Log.d(TAG, "createCaptureSession");
}
checkIfCameraClosed();
// TODO: we must be in UNCONFIGURED mode to begin with, or using another session
// TODO: dont block for this
boolean configureSuccess = true;
CameraAccessException pendingException = null;
try {
configureOutputs(outputs); // and then block until IDLE
} catch (CameraAccessException e) {
configureSuccess = false;
pendingException = e;
}
// Fire onConfigured if configureOutputs succeeded, fire onConfigureFailed otherwise.
CameraCaptureSessionImpl newSession =
new CameraCaptureSessionImpl(outputs, listener, handler, this, mDeviceHandler,
configureSuccess);
if (mCurrentSession != null) {
mCurrentSession.replaceSessionClose(newSession);
}
// TODO: wait until current session closes, then create the new session
mCurrentSession = newSession;
if (pendingException != null) {
throw pendingException;
}
mSessionStateListener = mCurrentSession.getDeviceStateListener();
}
}
@Override
@@ -275,7 +341,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
CameraMetadataNative templatedRequest = new CameraMetadataNative();
try {
mRemoteDevice.createDefaultRequest(templateType, /* out */templatedRequest);
mRemoteDevice.createDefaultRequest(templateType, /*out*/templatedRequest);
} catch (CameraRuntimeException e) {
throw e.asChecked();
} catch (RemoteException e) {
@@ -304,10 +370,8 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
@Override
public int captureBurst(List<CaptureRequest> requests, CaptureListener listener,
Handler handler) throws CameraAccessException {
// TODO: remove this. Throw IAE if the request is null or empty. Need to update API doc.
if (requests.isEmpty()) {
Log.w(TAG, "Capture burst request list is empty, do nothing!");
return -1;
if (requests == null || requests.isEmpty()) {
throw new IllegalArgumentException("At least one request must be given");
}
return submitCaptureRequest(requests, listener, handler, /*streaming*/false);
}
@@ -454,10 +518,8 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
@Override
public int setRepeatingBurst(List<CaptureRequest> requests, CaptureListener listener,
Handler handler) throws CameraAccessException {
// TODO: remove this. Throw IAE if the request is null or empty. Need to update API doc.
if (requests.isEmpty()) {
Log.w(TAG, "Set Repeating burst request list is empty, do nothing!");
return -1;
if (requests == null || requests.isEmpty()) {
throw new IllegalArgumentException("At least one request must be given");
}
return submitCaptureRequest(requests, listener, handler, /*streaming*/true);
}
@@ -951,10 +1013,14 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
}
/**
* Default handler management. If handler is null, get the current thread's
* Looper to create a Handler with. If no looper exists, throw exception.
* Default handler management.
*
* <p>
* If handler is null, get the current thread's
* Looper to create a Handler with. If no looper exists, throw {@code IllegalArgumentException}.
* </p>
*/
private Handler checkHandler(Handler handler) {
static Handler checkHandler(Handler handler) {
if (handler == null) {
Looper looper = Looper.myLooper();
if (looper == null) {

View File

@@ -0,0 +1,168 @@
package android.hardware.camera2.impl;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CaptureFailure;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.dispatch.Dispatchable;
import android.hardware.camera2.dispatch.MethodNameInvoker;
import static com.android.internal.util.Preconditions.*;
/**
* Proxy out invocations to the camera2 API listeners into a {@link Dispatchable}.
*
* <p>Since abstract classes do not support Java's dynamic {@code Proxy}, we have to
* to use our own proxy mechanism.</p>
*/
public class ListenerProxies {
// TODO: replace with codegen
public static class DeviceStateListenerProxy extends CameraDevice.StateListener {
private final MethodNameInvoker<CameraDevice.StateListener> mProxy;
public DeviceStateListenerProxy(
Dispatchable<CameraDevice.StateListener> dispatchTarget) {
dispatchTarget = checkNotNull(dispatchTarget, "dispatchTarget must not be null");
mProxy = new MethodNameInvoker<>(dispatchTarget, CameraDevice.StateListener.class);
}
@Override
public void onOpened(CameraDevice camera) {
mProxy.invoke("onOpened", camera);
}
@Override
public void onDisconnected(CameraDevice camera) {
mProxy.invoke("onDisconnected", camera);
}
@Override
public void onError(CameraDevice camera, int error) {
mProxy.invoke("onError", camera, error);
}
@Override
public void onUnconfigured(CameraDevice camera) {
mProxy.invoke("onUnconfigured", camera);
}
@Override
public void onActive(CameraDevice camera) {
mProxy.invoke("onActive", camera);
}
@Override
public void onBusy(CameraDevice camera) {
mProxy.invoke("onBusy", camera);
}
@Override
public void onClosed(CameraDevice camera) {
mProxy.invoke("onClosed", camera);
}
@Override
public void onIdle(CameraDevice camera) {
mProxy.invoke("onIdle", camera);
}
}
@SuppressWarnings("deprecation")
public static class DeviceCaptureListenerProxy extends CameraDevice.CaptureListener {
private final MethodNameInvoker<CameraDevice.CaptureListener> mProxy;
public DeviceCaptureListenerProxy(
Dispatchable<CameraDevice.CaptureListener> dispatchTarget) {
dispatchTarget = checkNotNull(dispatchTarget, "dispatchTarget must not be null");
mProxy = new MethodNameInvoker<>(dispatchTarget, CameraDevice.CaptureListener.class);
}
@Override
public void onCaptureStarted(CameraDevice camera,
CaptureRequest request, long timestamp) {
mProxy.invoke("onCaptureStarted", camera, request, timestamp);
}
@Override
public void onCapturePartial(CameraDevice camera,
CaptureRequest request, CaptureResult result) {
mProxy.invoke("onCapturePartial", camera, request, result);
}
@Override
public void onCaptureProgressed(CameraDevice camera,
CaptureRequest request, CaptureResult partialResult) {
mProxy.invoke("onCaptureProgressed", camera, request, partialResult);
}
@Override
public void onCaptureCompleted(CameraDevice camera,
CaptureRequest request, TotalCaptureResult result) {
mProxy.invoke("onCaptureCompleted", camera, request, result);
}
@Override
public void onCaptureFailed(CameraDevice camera,
CaptureRequest request, CaptureFailure failure) {
mProxy.invoke("onCaptureFailed", camera, request, failure);
}
@Override
public void onCaptureSequenceCompleted(CameraDevice camera,
int sequenceId, long frameNumber) {
mProxy.invoke("onCaptureSequenceCompleted", camera, sequenceId, frameNumber);
}
@Override
public void onCaptureSequenceAborted(CameraDevice camera,
int sequenceId) {
mProxy.invoke("onCaptureSequenceAborted", camera, sequenceId);
}
}
public static class SessionStateListenerProxy
extends CameraCaptureSession.StateListener {
private final MethodNameInvoker<CameraCaptureSession.StateListener> mProxy;
public SessionStateListenerProxy(
Dispatchable<CameraCaptureSession.StateListener> dispatchTarget) {
dispatchTarget = checkNotNull(dispatchTarget, "dispatchTarget must not be null");
mProxy = new MethodNameInvoker<>(dispatchTarget,
CameraCaptureSession.StateListener.class);
}
@Override
public void onConfigured(CameraCaptureSession session) {
mProxy.invoke("onConfigured", session);
}
@Override
public void onConfigureFailed(CameraCaptureSession session) {
mProxy.invoke("onConfigureFailed", session);
}
@Override
public void onReady(CameraCaptureSession session) {
mProxy.invoke("onReady", session);
}
@Override
public void onActive(CameraCaptureSession session) {
mProxy.invoke("onActive", session);
}
@Override
public void onClosed(CameraCaptureSession session) {
mProxy.invoke("onClosed", session);
}
}
private ListenerProxies() {
throw new AssertionError();
}
}

View File

@@ -0,0 +1,201 @@
/*
* Copyright (C) 2014 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.utils;
import android.os.Handler;
import android.util.Log;
import java.util.HashSet;
import java.util.Set;
import static com.android.internal.util.Preconditions.*;
/**
* Keep track of multiple concurrent tasks starting and finishing by their key;
* allow draining existing tasks and figuring out when all tasks have finished
* (and new ones won't begin).
*
* <p>The initial state is to allow all tasks to be started and finished. A task may only be started
* once, after which it must be finished before starting again. Likewise, finishing a task
* that hasn't been started is also not allowed.</p>
*
* <p>When draining begins, no more new tasks can be started. This guarantees that at some
* point when all the tasks are finished there will be no more collective new tasks,
* at which point the {@link DrainListener#onDrained} callback will be invoked.</p>
*
*
* @param <T>
* a type for the key that will represent tracked tasks;
* must implement {@code Object#equals}
*/
public class TaskDrainer<T> {
/**
* Fired asynchronously after draining has begun with {@link TaskDrainer#beginDrain}
* <em>and</em> all tasks that were started have finished.
*/
public interface DrainListener {
/** All tasks have fully finished draining; there will be no more pending tasks. */
public void onDrained();
}
private static final String TAG = "TaskDrainer";
private static final boolean VERBOSE = false;
private final Handler mHandler;
private final DrainListener mListener;
private final String mName;
/** Set of tasks which have been started but not yet finished with #taskFinished */
private final Set<T> mTaskSet = new HashSet<T>();
private final Object mLock = new Object();
private boolean mDraining = false;
private boolean mDrainFinished = false;
/**
* Create a new task drainer; {@code onDrained} callbacks will be posted to the listener
* via the {@code handler}.
*
* @param handler a non-{@code null} handler to use to post runnables to
* @param listener a non-{@code null} listener where {@code onDrained} will be called
*/
public TaskDrainer(Handler handler, DrainListener listener) {
mHandler = checkNotNull(handler, "handler must not be null");
mListener = checkNotNull(listener, "listener must not be null");
mName = null;
}
/**
* Create a new task drainer; {@code onDrained} callbacks will be posted to the listener
* via the {@code handler}.
*
* @param handler a non-{@code null} handler to use to post runnables to
* @param listener a non-{@code null} listener where {@code onDrained} will be called
* @param name an optional name used for debug logging
*/
public TaskDrainer(Handler handler, DrainListener listener, String name) {
// XX: Probably don't need a handler at all here
mHandler = checkNotNull(handler, "handler must not be null");
mListener = checkNotNull(listener, "listener must not be null");
mName = name;
}
/**
* Mark an asynchronous task as having started.
*
* <p>A task cannot be started more than once without first having finished. Once
* draining begins with {@link #beginDrain}, no new tasks can be started.</p>
*
* @param task a key to identify a task
*
* @see #taskFinished
* @see #beginDrain
*
* @throws IllegalStateException
* If attempting to start a task which is already started (and not finished),
* or if attempting to start a task after draining has begun.
*/
public void taskStarted(T task) {
synchronized (mLock) {
if (VERBOSE) {
Log.v(TAG + "[" + mName + "]", "taskStarted " + task);
}
if (mDraining) {
throw new IllegalStateException("Can't start more tasks after draining has begun");
}
if (!mTaskSet.add(task)) {
throw new IllegalStateException("Task " + task + " was already started");
}
}
}
/**
* Mark an asynchronous task as having finished.
*
* <p>A task cannot be finished if it hasn't started. Once finished, a task
* cannot be finished again (unless it's started again).</p>
*
* @param task a key to identify a task
*
* @see #taskStarted
* @see #beginDrain
*
* @throws IllegalStateException
* If attempting to start a task which is already finished (and not re-started),
*/
public void taskFinished(T task) {
synchronized (mLock) {
if (VERBOSE) {
Log.v(TAG + "[" + mName + "]", "taskFinished " + task);
}
if (!mTaskSet.remove(task)) {
throw new IllegalStateException("Task " + task + " was already finished");
}
// If this is the last finished task and draining has already begun, fire #onDrained
checkIfDrainFinished();
}
}
/**
* Do not allow any more tasks to be started; once all existing started tasks are finished,
* fire the {@link DrainListener#onDrained} callback asynchronously.
*
* <p>This operation is idempotent; calling it more than once has no effect.</p>
*/
public void beginDrain() {
synchronized (mLock) {
if (!mDraining) {
if (VERBOSE) {
Log.v(TAG + "[" + mName + "]", "beginDrain started");
}
mDraining = true;
// If all tasks that had started had already finished by now, fire #onDrained
checkIfDrainFinished();
} else {
if (VERBOSE) {
Log.v(TAG + "[" + mName + "]", "beginDrain ignored");
}
}
}
}
private void checkIfDrainFinished() {
if (mTaskSet.isEmpty() && mDraining && !mDrainFinished) {
mDrainFinished = true;
postDrained();
}
}
private void postDrained() {
mHandler.post(new Runnable() {
@Override
public void run() {
if (VERBOSE) {
Log.v(TAG + "[" + mName + "]", "onDrained");
}
mListener.onDrained();
}
});
}
}

View File

@@ -0,0 +1,104 @@
/*
* Copyright (C) 2014 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.utils;
import android.hardware.camera2.utils.TaskDrainer.DrainListener;
import android.os.Handler;
/**
* Keep track of a single concurrent task starting and finishing;
* allow draining the existing task and figuring out when the task has finished
* (and won't restart).
*
* <p>The initial state is to allow all tasks to be started and finished. A task may only be started
* once, after which it must be finished before starting again. Likewise, finishing a task
* that hasn't been started is also not allowed.</p>
*
* <p>When draining begins, the task cannot be started again. This guarantees that at some
* point the task will be finished forever, at which point the {@link DrainListener#onDrained}
* callback will be invoked.</p>
*/
public class TaskSingleDrainer {
private final TaskDrainer<Object> mTaskDrainer;
private final Object mSingleTask = new Object();
/**
* Create a new task drainer; {@code onDrained} callbacks will be posted to the listener
* via the {@code handler}.
*
* @param handler a non-{@code null} handler to use to post runnables to
* @param listener a non-{@code null} listener where {@code onDrained} will be called
*/
public TaskSingleDrainer(Handler handler, DrainListener listener) {
mTaskDrainer = new TaskDrainer<>(handler, listener);
}
/**
* Create a new task drainer; {@code onDrained} callbacks will be posted to the listener
* via the {@code handler}.
*
* @param handler a non-{@code null} handler to use to post runnables to
* @param listener a non-{@code null} listener where {@code onDrained} will be called
* @param name an optional name used for debug logging
*/
public TaskSingleDrainer(Handler handler, DrainListener listener, String name) {
mTaskDrainer = new TaskDrainer<>(handler, listener, name);
}
/**
* Mark this asynchronous task as having started.
*
* <p>The task cannot be started more than once without first having finished. Once
* draining begins with {@link #beginDrain}, no new tasks can be started.</p>
*
* @see #taskFinished
* @see #beginDrain
*
* @throws IllegalStateException
* If attempting to start a task which is already started (and not finished),
* or if attempting to start a task after draining has begun.
*/
public void taskStarted() {
mTaskDrainer.taskStarted(mSingleTask);
}
/**
* Do not allow any more task re-starts; once the existing task is finished,
* fire the {@link DrainListener#onDrained} callback asynchronously.
*
* <p>This operation is idempotent; calling it more than once has no effect.</p>
*/
public void beginDrain() {
mTaskDrainer.beginDrain();
}
/**
* Mark this asynchronous task as having finished.
*
* <p>The task cannot be finished if it hasn't started. Once finished, a task
* cannot be finished again (unless it's started again).</p>
*
* @see #taskStarted
* @see #beginDrain
*
* @throws IllegalStateException
* If attempting to start a task which is already finished (and not re-started),
*/
public void taskFinished() {
mTaskDrainer.taskFinished(mSingleTask);
}
}

View File

@@ -33,8 +33,20 @@ public class UncheckedThrow {
UncheckedThrow.<RuntimeException>throwAnyImpl(e);
}
/**
* Throw any kind of throwable without needing it to be checked
* @param e any instance of a Throwable
*/
public static void throwAnyException(Throwable e) {
/**
* Abuse type erasure by making the compiler think we are throwing RuntimeException,
* which is unchecked, but then inserting any exception in there.
*/
UncheckedThrow.<RuntimeException>throwAnyImpl(e);
}
@SuppressWarnings("unchecked")
private static<T extends Exception> void throwAnyImpl(Exception e) throws T {
private static<T extends Throwable> void throwAnyImpl(Throwable e) throws T {
throw (T) e;
}
}