Merge "camera2: Implement most of CameraCaptureSession" into lmp-preview-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
c6b4d9b8d1
@@ -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();
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
3
core/java/android/hardware/camera2/dispatch/package.html
Normal file
3
core/java/android/hardware/camera2/dispatch/package.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<body>
|
||||
{@hide}
|
||||
</body>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
168
core/java/android/hardware/camera2/impl/ListenerProxies.java
Normal file
168
core/java/android/hardware/camera2/impl/ListenerProxies.java
Normal 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();
|
||||
}
|
||||
}
|
||||
201
core/java/android/hardware/camera2/utils/TaskDrainer.java
Normal file
201
core/java/android/hardware/camera2/utils/TaskDrainer.java
Normal 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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
104
core/java/android/hardware/camera2/utils/TaskSingleDrainer.java
Normal file
104
core/java/android/hardware/camera2/utils/TaskSingleDrainer.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user