Merge "Camera: release extension when extension app crash" into udc-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
4d27c8d18c
@@ -34,6 +34,7 @@ import android.hardware.camera2.impl.CameraExtensionUtils;
|
||||
import android.hardware.camera2.impl.CameraMetadataNative;
|
||||
import android.hardware.camera2.params.ExtensionSessionConfiguration;
|
||||
import android.hardware.camera2.params.StreamConfigurationMap;
|
||||
import android.os.Binder;
|
||||
import android.os.ConditionVariable;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
@@ -48,7 +49,6 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -346,28 +346,29 @@ public final class CameraExtensionCharacteristics {
|
||||
}
|
||||
}
|
||||
|
||||
public long registerClient(Context ctx) {
|
||||
public boolean registerClient(Context ctx, IBinder token) {
|
||||
synchronized (mLock) {
|
||||
connectToProxyLocked(ctx);
|
||||
if (mProxy != null) {
|
||||
try {
|
||||
return mProxy.registerClient();
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to initialize extension! Extension service does "
|
||||
+ " not respond!");
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
return -1;
|
||||
if (mProxy == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
return mProxy.registerClient(token);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to initialize extension! Extension service does "
|
||||
+ " not respond!");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void unregisterClient(long clientId) {
|
||||
public void unregisterClient(IBinder token) {
|
||||
synchronized (mLock) {
|
||||
if (mProxy != null) {
|
||||
try {
|
||||
mProxy.unregisterClient(clientId);
|
||||
mProxy.unregisterClient(token);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to de-initialize extension! Extension service does"
|
||||
+ " not respond!");
|
||||
@@ -438,15 +439,15 @@ public final class CameraExtensionCharacteristics {
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public static long registerClient(Context ctx) {
|
||||
return CameraExtensionManagerGlobal.get().registerClient(ctx);
|
||||
public static boolean registerClient(Context ctx, IBinder token) {
|
||||
return CameraExtensionManagerGlobal.get().registerClient(ctx, token);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public static void unregisterClient(long clientId) {
|
||||
CameraExtensionManagerGlobal.get().unregisterClient(clientId);
|
||||
public static void unregisterClient(IBinder token) {
|
||||
CameraExtensionManagerGlobal.get().unregisterClient(token);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -564,8 +565,9 @@ public final class CameraExtensionCharacteristics {
|
||||
*/
|
||||
public @NonNull List<Integer> getSupportedExtensions() {
|
||||
ArrayList<Integer> ret = new ArrayList<>();
|
||||
long clientId = registerClient(mContext);
|
||||
if (clientId < 0) {
|
||||
final IBinder token = new Binder(TAG + "#getSupportedExtensions:" + mCameraId);
|
||||
boolean success = registerClient(mContext, token);
|
||||
if (!success) {
|
||||
return Collections.unmodifiableList(ret);
|
||||
}
|
||||
|
||||
@@ -576,7 +578,7 @@ public final class CameraExtensionCharacteristics {
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
unregisterClient(clientId);
|
||||
unregisterClient(token);
|
||||
}
|
||||
|
||||
return Collections.unmodifiableList(ret);
|
||||
@@ -599,8 +601,9 @@ public final class CameraExtensionCharacteristics {
|
||||
* supported device-specific extension
|
||||
*/
|
||||
public boolean isPostviewAvailable(@Extension int extension) {
|
||||
long clientId = registerClient(mContext);
|
||||
if (clientId < 0) {
|
||||
final IBinder token = new Binder(TAG + "#isPostviewAvailable:" + mCameraId);
|
||||
boolean success = registerClient(mContext, token);
|
||||
if (!success) {
|
||||
throw new IllegalArgumentException("Unsupported extensions");
|
||||
}
|
||||
|
||||
@@ -623,7 +626,7 @@ public final class CameraExtensionCharacteristics {
|
||||
Log.e(TAG, "Failed to query the extension for postview availability! Extension "
|
||||
+ "service does not respond!");
|
||||
} finally {
|
||||
unregisterClient(clientId);
|
||||
unregisterClient(token);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -656,9 +659,9 @@ public final class CameraExtensionCharacteristics {
|
||||
@NonNull
|
||||
public List<Size> getPostviewSupportedSizes(@Extension int extension,
|
||||
@NonNull Size captureSize, int format) {
|
||||
|
||||
long clientId = registerClient(mContext);
|
||||
if (clientId < 0) {
|
||||
final IBinder token = new Binder(TAG + "#getPostviewSupportedSizes:" + mCameraId);
|
||||
boolean success = registerClient(mContext, token);
|
||||
if (!success) {
|
||||
throw new IllegalArgumentException("Unsupported extensions");
|
||||
}
|
||||
|
||||
@@ -719,7 +722,7 @@ public final class CameraExtensionCharacteristics {
|
||||
+ "service does not respond!");
|
||||
return Collections.emptyList();
|
||||
} finally {
|
||||
unregisterClient(clientId);
|
||||
unregisterClient(token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -756,8 +759,9 @@ public final class CameraExtensionCharacteristics {
|
||||
// TODO: Revisit this code once the Extension preview processor output format
|
||||
// ambiguity is resolved in b/169799538.
|
||||
|
||||
long clientId = registerClient(mContext);
|
||||
if (clientId < 0) {
|
||||
final IBinder token = new Binder(TAG + "#getExtensionSupportedSizes:" + mCameraId);
|
||||
boolean success = registerClient(mContext, token);
|
||||
if (!success) {
|
||||
throw new IllegalArgumentException("Unsupported extensions");
|
||||
}
|
||||
|
||||
@@ -787,7 +791,7 @@ public final class CameraExtensionCharacteristics {
|
||||
+ " not respond!");
|
||||
return new ArrayList<>();
|
||||
} finally {
|
||||
unregisterClient(clientId);
|
||||
unregisterClient(token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -814,8 +818,9 @@ public final class CameraExtensionCharacteristics {
|
||||
public @NonNull
|
||||
List<Size> getExtensionSupportedSizes(@Extension int extension, int format) {
|
||||
try {
|
||||
long clientId = registerClient(mContext);
|
||||
if (clientId < 0) {
|
||||
final IBinder token = new Binder(TAG + "#getExtensionSupportedSizes:" + mCameraId);
|
||||
boolean success = registerClient(mContext, token);
|
||||
if (!success) {
|
||||
throw new IllegalArgumentException("Unsupported extensions");
|
||||
}
|
||||
|
||||
@@ -867,7 +872,7 @@ public final class CameraExtensionCharacteristics {
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
unregisterClient(clientId);
|
||||
unregisterClient(token);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to query the extension supported sizes! Extension service does"
|
||||
@@ -888,7 +893,6 @@ public final class CameraExtensionCharacteristics {
|
||||
* @param format device-specific extension output format
|
||||
* @return the range of estimated minimal and maximal capture latency in milliseconds
|
||||
* or null if no capture latency info can be provided
|
||||
*
|
||||
* @throws IllegalArgumentException in case of format different from {@link ImageFormat#JPEG} /
|
||||
* {@link ImageFormat#YUV_420_888}; or unsupported extension.
|
||||
*/
|
||||
@@ -903,8 +907,9 @@ public final class CameraExtensionCharacteristics {
|
||||
throw new IllegalArgumentException("Unsupported format: " + format);
|
||||
}
|
||||
|
||||
long clientId = registerClient(mContext);
|
||||
if (clientId < 0) {
|
||||
final IBinder token = new Binder(TAG + "#getEstimatedCaptureLatencyRangeMillis:" + mCameraId);
|
||||
boolean success = registerClient(mContext, token);
|
||||
if (!success) {
|
||||
throw new IllegalArgumentException("Unsupported extensions");
|
||||
}
|
||||
|
||||
@@ -952,7 +957,7 @@ public final class CameraExtensionCharacteristics {
|
||||
Log.e(TAG, "Failed to query the extension capture latency! Extension service does"
|
||||
+ " not respond!");
|
||||
} finally {
|
||||
unregisterClient(clientId);
|
||||
unregisterClient(token);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -968,8 +973,9 @@ public final class CameraExtensionCharacteristics {
|
||||
* @throws IllegalArgumentException in case of an unsupported extension.
|
||||
*/
|
||||
public boolean isCaptureProcessProgressAvailable(@Extension int extension) {
|
||||
long clientId = registerClient(mContext);
|
||||
if (clientId < 0) {
|
||||
final IBinder token = new Binder(TAG + "#isCaptureProcessProgressAvailable:" + mCameraId);
|
||||
boolean success = registerClient(mContext, token);
|
||||
if (!success) {
|
||||
throw new IllegalArgumentException("Unsupported extensions");
|
||||
}
|
||||
|
||||
@@ -992,7 +998,7 @@ public final class CameraExtensionCharacteristics {
|
||||
Log.e(TAG, "Failed to query the extension progress callbacks! Extension service does"
|
||||
+ " not respond!");
|
||||
} finally {
|
||||
unregisterClient(clientId);
|
||||
unregisterClient(token);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -1013,8 +1019,9 @@ public final class CameraExtensionCharacteristics {
|
||||
*/
|
||||
@NonNull
|
||||
public Set<CaptureRequest.Key> getAvailableCaptureRequestKeys(@Extension int extension) {
|
||||
long clientId = registerClient(mContext);
|
||||
if (clientId < 0) {
|
||||
final IBinder token = new Binder(TAG + "#getAvailableCaptureRequestKeys:" + mCameraId);
|
||||
boolean success = registerClient(mContext, token);
|
||||
if (!success) {
|
||||
throw new IllegalArgumentException("Unsupported extensions");
|
||||
}
|
||||
|
||||
@@ -1033,10 +1040,11 @@ public final class CameraExtensionCharacteristics {
|
||||
} else {
|
||||
Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
|
||||
initializeExtension(extension);
|
||||
extenders.second.onInit(mCameraId, mCharacteristicsMapNative.get(mCameraId));
|
||||
extenders.second.onInit(token, mCameraId,
|
||||
mCharacteristicsMapNative.get(mCameraId));
|
||||
extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId));
|
||||
captureRequestMeta = extenders.second.getAvailableCaptureRequestKeys();
|
||||
extenders.second.onDeInit();
|
||||
extenders.second.onDeInit(token);
|
||||
}
|
||||
|
||||
if (captureRequestMeta != null) {
|
||||
@@ -1067,7 +1075,7 @@ public final class CameraExtensionCharacteristics {
|
||||
} catch (RemoteException e) {
|
||||
throw new IllegalStateException("Failed to query the available capture request keys!");
|
||||
} finally {
|
||||
unregisterClient(clientId);
|
||||
unregisterClient(token);
|
||||
}
|
||||
|
||||
return Collections.unmodifiableSet(ret);
|
||||
@@ -1092,8 +1100,9 @@ public final class CameraExtensionCharacteristics {
|
||||
*/
|
||||
@NonNull
|
||||
public Set<CaptureResult.Key> getAvailableCaptureResultKeys(@Extension int extension) {
|
||||
long clientId = registerClient(mContext);
|
||||
if (clientId < 0) {
|
||||
final IBinder token = new Binder(TAG + "#getAvailableCaptureResultKeys:" + mCameraId);
|
||||
boolean success = registerClient(mContext, token);
|
||||
if (!success) {
|
||||
throw new IllegalArgumentException("Unsupported extensions");
|
||||
}
|
||||
|
||||
@@ -1111,10 +1120,11 @@ public final class CameraExtensionCharacteristics {
|
||||
} else {
|
||||
Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
|
||||
initializeExtension(extension);
|
||||
extenders.second.onInit(mCameraId, mCharacteristicsMapNative.get(mCameraId));
|
||||
extenders.second.onInit(token, mCameraId,
|
||||
mCharacteristicsMapNative.get(mCameraId));
|
||||
extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId));
|
||||
captureResultMeta = extenders.second.getAvailableCaptureResultKeys();
|
||||
extenders.second.onDeInit();
|
||||
extenders.second.onDeInit(token);
|
||||
}
|
||||
|
||||
if (captureResultMeta != null) {
|
||||
@@ -1126,7 +1136,7 @@ public final class CameraExtensionCharacteristics {
|
||||
}
|
||||
CameraCharacteristics resultChars = new CameraCharacteristics(captureResultMeta);
|
||||
Object crKey = CaptureResult.Key.class;
|
||||
Class<CaptureResult.Key<?>> crKeyTyped = (Class<CaptureResult.Key<?>>)crKey;
|
||||
Class<CaptureResult.Key<?>> crKeyTyped = (Class<CaptureResult.Key<?>>) crKey;
|
||||
|
||||
ret.addAll(resultChars.getAvailableKeyList(CaptureResult.class, crKeyTyped,
|
||||
resultKeys, /*includeSynthetic*/ true));
|
||||
@@ -1145,7 +1155,7 @@ public final class CameraExtensionCharacteristics {
|
||||
} catch (RemoteException e) {
|
||||
throw new IllegalStateException("Failed to query the available capture result keys!");
|
||||
} finally {
|
||||
unregisterClient(clientId);
|
||||
unregisterClient(token);
|
||||
}
|
||||
|
||||
return Collections.unmodifiableSet(ret);
|
||||
|
||||
@@ -20,11 +20,13 @@ import android.hardware.camera2.extension.IPreviewExtenderImpl;
|
||||
import android.hardware.camera2.extension.IImageCaptureExtenderImpl;
|
||||
import android.hardware.camera2.extension.IInitializeSessionCallback;
|
||||
|
||||
import android.os.IBinder;
|
||||
|
||||
/** @hide */
|
||||
interface ICameraExtensionsProxyService
|
||||
{
|
||||
long registerClient();
|
||||
void unregisterClient(long clientId);
|
||||
boolean registerClient(in IBinder token);
|
||||
void unregisterClient(in IBinder token);
|
||||
boolean advancedExtensionsSupported();
|
||||
void initializeSession(in IInitializeSessionCallback cb);
|
||||
void releaseSession();
|
||||
|
||||
@@ -24,11 +24,13 @@ import android.hardware.camera2.extension.LatencyRange;
|
||||
import android.hardware.camera2.extension.Size;
|
||||
import android.hardware.camera2.extension.SizeList;
|
||||
|
||||
import android.os.IBinder;
|
||||
|
||||
/** @hide */
|
||||
interface IImageCaptureExtenderImpl
|
||||
{
|
||||
void onInit(in String cameraId, in CameraMetadataNative cameraCharacteristics);
|
||||
void onDeInit();
|
||||
void onInit(in IBinder token, in String cameraId, in CameraMetadataNative cameraCharacteristics);
|
||||
void onDeInit(in IBinder token);
|
||||
@nullable CaptureStageImpl onPresetSession();
|
||||
@nullable CaptureStageImpl onEnableSession();
|
||||
@nullable CaptureStageImpl onDisableSession();
|
||||
|
||||
@@ -22,11 +22,13 @@ import android.hardware.camera2.extension.IPreviewImageProcessorImpl;
|
||||
import android.hardware.camera2.extension.IRequestUpdateProcessorImpl;
|
||||
import android.hardware.camera2.extension.SizeList;
|
||||
|
||||
import android.os.IBinder;
|
||||
|
||||
/** @hide */
|
||||
interface IPreviewExtenderImpl
|
||||
{
|
||||
void onInit(in String cameraId, in CameraMetadataNative cameraCharacteristics);
|
||||
void onDeInit();
|
||||
void onInit(in IBinder token, in String cameraId, in CameraMetadataNative cameraCharacteristics);
|
||||
void onDeInit(in IBinder token);
|
||||
@nullable CaptureStageImpl onPresetSession();
|
||||
@nullable CaptureStageImpl onEnableSession();
|
||||
@nullable CaptureStageImpl onDisableSession();
|
||||
|
||||
@@ -25,13 +25,15 @@ import android.hardware.camera2.extension.LatencyPair;
|
||||
import android.hardware.camera2.extension.LatencyRange;
|
||||
import android.hardware.camera2.extension.OutputSurface;
|
||||
|
||||
import android.os.IBinder;
|
||||
|
||||
/** @hide */
|
||||
interface ISessionProcessorImpl
|
||||
{
|
||||
CameraSessionConfig initSession(in String cameraId,
|
||||
CameraSessionConfig initSession(in IBinder token, in String cameraId,
|
||||
in Map<String, CameraMetadataNative> charsMap, in OutputSurface previewSurface,
|
||||
in OutputSurface imageCaptureSurface, in OutputSurface postviewSurface);
|
||||
void deInitSession();
|
||||
void deInitSession(in IBinder token);
|
||||
void onCaptureSessionStart(IRequestProcessorImpl requestProcessor);
|
||||
void onCaptureSessionEnd();
|
||||
int startRepeating(in ICaptureCallback callback);
|
||||
|
||||
@@ -60,6 +60,7 @@ import android.media.ImageReader;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.util.Size;
|
||||
@@ -79,7 +80,6 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes
|
||||
private final Executor mExecutor;
|
||||
private CameraDevice mCameraDevice;
|
||||
private final Map<String, CameraMetadataNative> mCharacteristicsMap;
|
||||
private final long mExtensionClientId;
|
||||
private final Handler mHandler;
|
||||
private final HandlerThread mHandlerThread;
|
||||
private final CameraExtensionSession.StateCallback mCallbacks;
|
||||
@@ -90,6 +90,7 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes
|
||||
private final HashMap<Integer, ImageReader> mReaderMap = new HashMap<>();
|
||||
private RequestProcessor mRequestProcessor = new RequestProcessor();
|
||||
private final int mSessionId;
|
||||
private final IBinder mToken;
|
||||
|
||||
private Surface mClientRepeatingRequestSurface;
|
||||
private Surface mClientCaptureSurface;
|
||||
@@ -114,8 +115,9 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes
|
||||
@NonNull Map<String, CameraCharacteristics> characteristicsMap,
|
||||
@NonNull Context ctx, @NonNull ExtensionSessionConfiguration config, int sessionId)
|
||||
throws CameraAccessException, RemoteException {
|
||||
long clientId = CameraExtensionCharacteristics.registerClient(ctx);
|
||||
if (clientId < 0) {
|
||||
final IBinder token = new Binder(TAG + " : " + sessionId);
|
||||
boolean success = CameraExtensionCharacteristics.registerClient(ctx, token);
|
||||
if (!success) {
|
||||
throw new UnsupportedOperationException("Unsupported extension!");
|
||||
}
|
||||
|
||||
@@ -202,11 +204,10 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes
|
||||
IAdvancedExtenderImpl extender = CameraExtensionCharacteristics.initializeAdvancedExtension(
|
||||
config.getExtension());
|
||||
extender.init(cameraId, characteristicsMapNative);
|
||||
|
||||
CameraAdvancedExtensionSessionImpl ret = new CameraAdvancedExtensionSessionImpl(clientId,
|
||||
extender, cameraDevice, characteristicsMapNative, repeatingRequestSurface,
|
||||
CameraAdvancedExtensionSessionImpl ret = new CameraAdvancedExtensionSessionImpl(extender,
|
||||
cameraDevice, characteristicsMapNative, repeatingRequestSurface,
|
||||
burstCaptureSurface, postviewSurface, config.getStateCallback(),
|
||||
config.getExecutor(), sessionId);
|
||||
config.getExecutor(), sessionId, token);
|
||||
|
||||
ret.mStatsAggregator.setClientName(ctx.getOpPackageName());
|
||||
ret.mStatsAggregator.setExtensionType(config.getExtension());
|
||||
@@ -216,15 +217,13 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes
|
||||
return ret;
|
||||
}
|
||||
|
||||
private CameraAdvancedExtensionSessionImpl(long extensionClientId,
|
||||
@NonNull IAdvancedExtenderImpl extender,
|
||||
private CameraAdvancedExtensionSessionImpl(@NonNull IAdvancedExtenderImpl extender,
|
||||
@NonNull CameraDeviceImpl cameraDevice,
|
||||
Map<String, CameraMetadataNative> characteristicsMap,
|
||||
@Nullable Surface repeatingRequestSurface, @Nullable Surface burstCaptureSurface,
|
||||
@Nullable Surface postviewSurface,
|
||||
@NonNull StateCallback callback, @NonNull Executor executor,
|
||||
int sessionId) {
|
||||
mExtensionClientId = extensionClientId;
|
||||
int sessionId, @NonNull IBinder token) {
|
||||
mAdvancedExtender = extender;
|
||||
mCameraDevice = cameraDevice;
|
||||
mCharacteristicsMap = characteristicsMap;
|
||||
@@ -240,6 +239,7 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes
|
||||
mSessionClosed = false;
|
||||
mInitializeHandler = new InitializeSessionHandler();
|
||||
mSessionId = sessionId;
|
||||
mToken = token;
|
||||
mInterfaceLock = cameraDevice.mInterfaceLock;
|
||||
|
||||
mStatsAggregator = new ExtensionSessionStatsAggregator(mCameraDevice.getId(),
|
||||
@@ -260,7 +260,8 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes
|
||||
OutputSurface postviewSurface = initializeParcelable(mClientPostviewSurface);
|
||||
|
||||
mSessionProcessor = mAdvancedExtender.getSessionProcessor();
|
||||
CameraSessionConfig sessionConfig = mSessionProcessor.initSession(mCameraDevice.getId(),
|
||||
CameraSessionConfig sessionConfig = mSessionProcessor.initSession(mToken,
|
||||
mCameraDevice.getId(),
|
||||
mCharacteristicsMap, previewSurface, captureSurface, postviewSurface);
|
||||
List<CameraOutputConfig> outputConfigs = sessionConfig.outputConfigs;
|
||||
ArrayList<OutputConfiguration> outputList = new ArrayList<>();
|
||||
@@ -569,7 +570,7 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes
|
||||
if (!mSessionClosed) {
|
||||
mSessionProcessor.onCaptureSessionEnd();
|
||||
}
|
||||
mSessionProcessor.deInitSession();
|
||||
mSessionProcessor.deInitSession(mToken);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to de-initialize session processor, extension service"
|
||||
+ " does not respond!") ;
|
||||
@@ -577,12 +578,10 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes
|
||||
mSessionProcessor = null;
|
||||
}
|
||||
|
||||
if (mExtensionClientId >= 0) {
|
||||
CameraExtensionCharacteristics.unregisterClient(mExtensionClientId);
|
||||
if (mInitialized || (mCaptureSession != null)) {
|
||||
notifyClose = true;
|
||||
CameraExtensionCharacteristics.releaseSession();
|
||||
}
|
||||
CameraExtensionCharacteristics.unregisterClient(mToken);
|
||||
if (mInitialized || (mCaptureSession != null)) {
|
||||
notifyClose = true;
|
||||
CameraExtensionCharacteristics.releaseSession();
|
||||
}
|
||||
mInitialized = false;
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ import android.media.ImageWriter;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.util.LongSparseArray;
|
||||
@@ -79,7 +80,6 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
|
||||
|
||||
private final Executor mExecutor;
|
||||
private final CameraDevice mCameraDevice;
|
||||
private final long mExtensionClientId;
|
||||
private final IImageCaptureExtenderImpl mImageExtender;
|
||||
private final IPreviewExtenderImpl mPreviewExtender;
|
||||
private final Handler mHandler;
|
||||
@@ -91,6 +91,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
|
||||
private final Set<CaptureRequest.Key> mSupportedRequestKeys;
|
||||
private final Set<CaptureResult.Key> mSupportedResultKeys;
|
||||
private final ExtensionSessionStatsAggregator mStatsAggregator;
|
||||
private final IBinder mToken;
|
||||
private boolean mCaptureResultsSupported;
|
||||
|
||||
private CameraCaptureSession mCaptureSession = null;
|
||||
@@ -136,8 +137,9 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
|
||||
@NonNull ExtensionSessionConfiguration config,
|
||||
int sessionId)
|
||||
throws CameraAccessException, RemoteException {
|
||||
long clientId = CameraExtensionCharacteristics.registerClient(ctx);
|
||||
if (clientId < 0) {
|
||||
final IBinder token = new Binder(TAG + " : " + sessionId);
|
||||
boolean success = CameraExtensionCharacteristics.registerClient(ctx, token);
|
||||
if (!success) {
|
||||
throw new UnsupportedOperationException("Unsupported extension!");
|
||||
}
|
||||
|
||||
@@ -225,15 +227,16 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
|
||||
}
|
||||
|
||||
extenders.first.init(cameraId, characteristicsMap.get(cameraId).getNativeMetadata());
|
||||
extenders.first.onInit(cameraId, characteristicsMap.get(cameraId).getNativeMetadata());
|
||||
extenders.first.onInit(token, cameraId,
|
||||
characteristicsMap.get(cameraId).getNativeMetadata());
|
||||
extenders.second.init(cameraId, characteristicsMap.get(cameraId).getNativeMetadata());
|
||||
extenders.second.onInit(cameraId, characteristicsMap.get(cameraId).getNativeMetadata());
|
||||
extenders.second.onInit(token, cameraId,
|
||||
characteristicsMap.get(cameraId).getNativeMetadata());
|
||||
|
||||
CameraExtensionSessionImpl session = new CameraExtensionSessionImpl(
|
||||
extenders.second,
|
||||
extenders.first,
|
||||
supportedPreviewSizes,
|
||||
clientId,
|
||||
cameraDevice,
|
||||
repeatingRequestSurface,
|
||||
burstCaptureSurface,
|
||||
@@ -241,6 +244,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
|
||||
config.getStateCallback(),
|
||||
config.getExecutor(),
|
||||
sessionId,
|
||||
token,
|
||||
extensionChars.getAvailableCaptureRequestKeys(config.getExtension()),
|
||||
extensionChars.getAvailableCaptureResultKeys(config.getExtension()));
|
||||
|
||||
@@ -255,7 +259,6 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
|
||||
public CameraExtensionSessionImpl(@NonNull IImageCaptureExtenderImpl imageExtender,
|
||||
@NonNull IPreviewExtenderImpl previewExtender,
|
||||
@NonNull List<Size> previewSizes,
|
||||
long extensionClientId,
|
||||
@NonNull android.hardware.camera2.impl.CameraDeviceImpl cameraDevice,
|
||||
@Nullable Surface repeatingRequestSurface,
|
||||
@Nullable Surface burstCaptureSurface,
|
||||
@@ -263,9 +266,9 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
|
||||
@NonNull StateCallback callback,
|
||||
@NonNull Executor executor,
|
||||
int sessionId,
|
||||
@NonNull IBinder token,
|
||||
@NonNull Set<CaptureRequest.Key> requestKeys,
|
||||
@Nullable Set<CaptureResult.Key> resultKeys) {
|
||||
mExtensionClientId = extensionClientId;
|
||||
mImageExtender = imageExtender;
|
||||
mPreviewExtender = previewExtender;
|
||||
mCameraDevice = cameraDevice;
|
||||
@@ -282,6 +285,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
|
||||
mSessionClosed = false;
|
||||
mInitializeHandler = new InitializeSessionHandler();
|
||||
mSessionId = sessionId;
|
||||
mToken = token;
|
||||
mSupportedRequestKeys = requestKeys;
|
||||
mSupportedResultKeys = resultKeys;
|
||||
mCaptureResultsSupported = !resultKeys.isEmpty();
|
||||
@@ -867,19 +871,17 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
|
||||
mPreviewExtender.onDisableSession();
|
||||
mImageExtender.onDisableSession();
|
||||
}
|
||||
mPreviewExtender.onDeInit();
|
||||
mImageExtender.onDeInit();
|
||||
mPreviewExtender.onDeInit(mToken);
|
||||
mImageExtender.onDeInit(mToken);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to release extensions! Extension service does not"
|
||||
+ " respond!");
|
||||
}
|
||||
|
||||
if (mExtensionClientId >= 0) {
|
||||
CameraExtensionCharacteristics.unregisterClient(mExtensionClientId);
|
||||
if (mInitialized || (mCaptureSession != null)) {
|
||||
notifyClose = true;
|
||||
CameraExtensionCharacteristics.releaseSession();
|
||||
}
|
||||
CameraExtensionCharacteristics.unregisterClient(mToken);
|
||||
if (mInitialized || (mCaptureSession != null)) {
|
||||
notifyClose = true;
|
||||
CameraExtensionCharacteristics.releaseSession();
|
||||
}
|
||||
mInitialized = false;
|
||||
|
||||
|
||||
@@ -75,6 +75,7 @@ import android.util.Range;
|
||||
import android.util.Size;
|
||||
import android.view.Surface;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.camera.extensions.impl.AutoImageCaptureExtenderImpl;
|
||||
import androidx.camera.extensions.impl.AutoPreviewExtenderImpl;
|
||||
import androidx.camera.extensions.impl.BeautyImageCaptureExtenderImpl;
|
||||
@@ -204,7 +205,7 @@ public class CameraExtensionsProxyService extends Service {
|
||||
* A per-process global camera extension manager instance, to track and
|
||||
* initialize/release extensions depending on client activity.
|
||||
*/
|
||||
private static final class CameraExtensionManagerGlobal {
|
||||
private static final class CameraExtensionManagerGlobal implements IBinder.DeathRecipient {
|
||||
private static final String TAG = "CameraExtensionManagerGlobal";
|
||||
private final int EXTENSION_DELAY_MS = 1000;
|
||||
|
||||
@@ -212,8 +213,9 @@ public class CameraExtensionsProxyService extends Service {
|
||||
private final HandlerThread mHandlerThread;
|
||||
private final Object mLock = new Object();
|
||||
|
||||
private long mCurrentClientCount = 0;
|
||||
private ArraySet<Long> mActiveClients = new ArraySet<>();
|
||||
private ArraySet<IBinder> mActiveClients = new ArraySet<>();
|
||||
private HashMap<IBinder, ArraySet<IBinder.DeathRecipient>> mClientDeathRecipient =
|
||||
new HashMap<>();
|
||||
private IInitializeSessionCallback mInitializeCb = null;
|
||||
|
||||
// Singleton instance
|
||||
@@ -314,8 +316,20 @@ public class CameraExtensionsProxyService extends Service {
|
||||
return GLOBAL_CAMERA_MANAGER;
|
||||
}
|
||||
|
||||
public long registerClient(Context ctx) {
|
||||
public boolean registerClient(Context ctx, IBinder token) {
|
||||
synchronized (mLock) {
|
||||
if (mActiveClients.contains(token)) {
|
||||
Log.e(TAG, "Failed to register existing client!");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
token.linkToDeath(this, 0);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to link to binder token!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (INIT_API_SUPPORTED) {
|
||||
if (mActiveClients.isEmpty()) {
|
||||
InitializerFuture status = new InitializerFuture();
|
||||
@@ -327,47 +341,80 @@ public class CameraExtensionsProxyService extends Service {
|
||||
TimeUnit.MILLISECONDS);
|
||||
} catch (TimeoutException e) {
|
||||
Log.e(TAG, "Timed out while initializing camera extensions!");
|
||||
return -1;
|
||||
return false;
|
||||
}
|
||||
if (!initSuccess) {
|
||||
Log.e(TAG, "Failed while initializing camera extensions!");
|
||||
return -1;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
long ret = mCurrentClientCount;
|
||||
mCurrentClientCount++;
|
||||
if (mCurrentClientCount < 0) {
|
||||
mCurrentClientCount = 0;
|
||||
}
|
||||
mActiveClients.add(ret);
|
||||
mActiveClients.add(token);
|
||||
mClientDeathRecipient.put(token, new ArraySet<>());
|
||||
|
||||
return ret;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void unregisterClient(long clientId) {
|
||||
public void unregisterClient(IBinder token) {
|
||||
synchronized (mLock) {
|
||||
if (mActiveClients.remove(clientId) && mActiveClients.isEmpty() &&
|
||||
INIT_API_SUPPORTED) {
|
||||
InitializerFuture status = new InitializerFuture();
|
||||
InitializerImpl.deinit(new ReleaseHandler(status),
|
||||
new HandlerExecutor(mHandler));
|
||||
boolean releaseSuccess;
|
||||
try {
|
||||
releaseSuccess = status.get(EXTENSION_DELAY_MS, TimeUnit.MILLISECONDS);
|
||||
} catch (TimeoutException e) {
|
||||
Log.e(TAG, "Timed out while releasing camera extensions!");
|
||||
return;
|
||||
}
|
||||
if (!releaseSuccess) {
|
||||
Log.e(TAG, "Failed while releasing camera extensions!");
|
||||
if (mActiveClients.remove(token)) {
|
||||
token.unlinkToDeath(this, 0);
|
||||
mClientDeathRecipient.remove(token);
|
||||
if (mActiveClients.isEmpty() && INIT_API_SUPPORTED) {
|
||||
InitializerFuture status = new InitializerFuture();
|
||||
InitializerImpl.deinit(new ReleaseHandler(status),
|
||||
new HandlerExecutor(mHandler));
|
||||
boolean releaseSuccess;
|
||||
try {
|
||||
releaseSuccess = status.get(EXTENSION_DELAY_MS, TimeUnit.MILLISECONDS);
|
||||
} catch (TimeoutException e) {
|
||||
Log.e(TAG, "Timed out while releasing camera extensions!");
|
||||
return;
|
||||
}
|
||||
if (!releaseSuccess) {
|
||||
Log.e(TAG, "Failed while releasing camera extensions!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void binderDied() {
|
||||
// Do nothing, handled below
|
||||
}
|
||||
|
||||
@Override
|
||||
public void binderDied(@NonNull IBinder who) {
|
||||
synchronized (mLock) {
|
||||
if (mClientDeathRecipient.containsKey(who)) {
|
||||
mClientDeathRecipient.get(who).stream().forEach(
|
||||
recipient -> recipient.binderDied(who));
|
||||
}
|
||||
unregisterClient(who);
|
||||
}
|
||||
}
|
||||
|
||||
public void registerDeathRecipient(IBinder token, IBinder.DeathRecipient recipient) {
|
||||
synchronized (mLock) {
|
||||
if (mClientDeathRecipient.containsKey(token)) {
|
||||
ArraySet<IBinder.DeathRecipient> recipients = mClientDeathRecipient.get(token);
|
||||
recipients.add(recipient);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void unregisterDeathRecipient(IBinder token, IBinder.DeathRecipient recipient) {
|
||||
synchronized (mLock) {
|
||||
if (mClientDeathRecipient.containsKey(token)) {
|
||||
ArraySet<IBinder.DeathRecipient> recipients = mClientDeathRecipient.get(token);
|
||||
recipients.remove(recipient);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
|
||||
@Override
|
||||
public void binderDied() {
|
||||
@@ -406,21 +453,35 @@ public class CameraExtensionsProxyService extends Service {
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
private static long registerClient(Context ctx) {
|
||||
private static boolean registerClient(Context ctx, IBinder token) {
|
||||
if (!EXTENSIONS_PRESENT) {
|
||||
return -1;
|
||||
return false;
|
||||
}
|
||||
return CameraExtensionManagerGlobal.get().registerClient(ctx);
|
||||
return CameraExtensionManagerGlobal.get().registerClient(ctx, token);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public static void unregisterClient(long clientId) {
|
||||
public static void unregisterClient(IBinder token) {
|
||||
if (!EXTENSIONS_PRESENT) {
|
||||
return;
|
||||
}
|
||||
CameraExtensionManagerGlobal.get().unregisterClient(clientId);
|
||||
CameraExtensionManagerGlobal.get().unregisterClient(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
private static void registerDeathRecipient(IBinder token, IBinder.DeathRecipient recipient) {
|
||||
CameraExtensionManagerGlobal.get().registerDeathRecipient(token, recipient);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
private static void unregisterDeathRecipient(IBinder token, IBinder.DeathRecipient recipient) {
|
||||
CameraExtensionManagerGlobal.get().unregisterDeathRecipient(token, recipient);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -649,13 +710,14 @@ public class CameraExtensionsProxyService extends Service {
|
||||
|
||||
private class CameraExtensionsProxyServiceStub extends ICameraExtensionsProxyService.Stub {
|
||||
@Override
|
||||
public long registerClient() {
|
||||
return CameraExtensionsProxyService.registerClient(CameraExtensionsProxyService.this);
|
||||
public boolean registerClient(IBinder token) {
|
||||
return CameraExtensionsProxyService.registerClient(CameraExtensionsProxyService.this,
|
||||
token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterClient(long clientId) {
|
||||
CameraExtensionsProxyService.unregisterClient(clientId);
|
||||
public void unregisterClient(IBinder token) {
|
||||
CameraExtensionsProxyService.unregisterClient(token);
|
||||
}
|
||||
|
||||
private boolean checkCameraPermission() {
|
||||
@@ -1192,16 +1254,18 @@ public class CameraExtensionsProxyService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
private class SessionProcessorImplStub extends ISessionProcessorImpl.Stub {
|
||||
private class SessionProcessorImplStub extends ISessionProcessorImpl.Stub implements
|
||||
IBinder.DeathRecipient {
|
||||
private final SessionProcessorImpl mSessionProcessor;
|
||||
private String mCameraId = null;
|
||||
private IBinder mToken;
|
||||
|
||||
public SessionProcessorImplStub(SessionProcessorImpl sessionProcessor) {
|
||||
mSessionProcessor = sessionProcessor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CameraSessionConfig initSession(String cameraId,
|
||||
public CameraSessionConfig initSession(IBinder token, String cameraId,
|
||||
Map<String, CameraMetadataNative> charsMapNative, OutputSurface previewSurface,
|
||||
OutputSurface imageCaptureSurface, OutputSurface postviewSurface) {
|
||||
OutputSurfaceImplStub outputPreviewSurfaceImpl =
|
||||
@@ -1253,12 +1317,14 @@ public class CameraExtensionsProxyService extends Service {
|
||||
ret.sessionParameter = initializeParcelableMetadata(
|
||||
sessionConfig.getSessionParameters(), cameraId);
|
||||
mCameraId = cameraId;
|
||||
|
||||
mToken = token;
|
||||
CameraExtensionsProxyService.registerDeathRecipient(mToken, this);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deInitSession() {
|
||||
public void deInitSession(IBinder token) {
|
||||
CameraExtensionsProxyService.unregisterDeathRecipient(mToken, this);
|
||||
mSessionProcessor.deInitSession();
|
||||
}
|
||||
|
||||
@@ -1330,6 +1396,11 @@ public class CameraExtensionsProxyService extends Service {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void binderDied() {
|
||||
mSessionProcessor.deInitSession();
|
||||
}
|
||||
}
|
||||
|
||||
private class OutputSurfaceConfigurationImplStub implements OutputSurfaceConfigurationImpl {
|
||||
@@ -1395,24 +1466,31 @@ public class CameraExtensionsProxyService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
private class PreviewExtenderImplStub extends IPreviewExtenderImpl.Stub {
|
||||
private class PreviewExtenderImplStub extends IPreviewExtenderImpl.Stub implements
|
||||
IBinder.DeathRecipient {
|
||||
private final PreviewExtenderImpl mPreviewExtender;
|
||||
private String mCameraId = null;
|
||||
private boolean mSessionEnabled;
|
||||
private IBinder mToken;
|
||||
|
||||
public PreviewExtenderImplStub(PreviewExtenderImpl previewExtender) {
|
||||
mPreviewExtender = previewExtender;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInit(String cameraId, CameraMetadataNative cameraCharacteristics) {
|
||||
public void onInit(IBinder token, String cameraId,
|
||||
CameraMetadataNative cameraCharacteristics) {
|
||||
mCameraId = cameraId;
|
||||
CameraCharacteristics chars = new CameraCharacteristics(cameraCharacteristics);
|
||||
mCameraManager.registerDeviceStateListener(chars);
|
||||
mPreviewExtender.onInit(cameraId, chars, CameraExtensionsProxyService.this);
|
||||
mToken = token;
|
||||
CameraExtensionsProxyService.registerDeathRecipient(mToken, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeInit() {
|
||||
public void onDeInit(IBinder token) {
|
||||
CameraExtensionsProxyService.unregisterDeathRecipient(mToken, this);
|
||||
mPreviewExtender.onDeInit();
|
||||
}
|
||||
|
||||
@@ -1423,11 +1501,13 @@ public class CameraExtensionsProxyService extends Service {
|
||||
|
||||
@Override
|
||||
public CaptureStageImpl onEnableSession() {
|
||||
mSessionEnabled = true;
|
||||
return initializeParcelable(mPreviewExtender.onEnableSession(), mCameraId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CaptureStageImpl onDisableSession() {
|
||||
mSessionEnabled = false;
|
||||
return initializeParcelable(mPreviewExtender.onDisableSession(), mCameraId);
|
||||
}
|
||||
|
||||
@@ -1516,26 +1596,41 @@ public class CameraExtensionsProxyService extends Service {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void binderDied() {
|
||||
if (mSessionEnabled) {
|
||||
mPreviewExtender.onDisableSession();
|
||||
}
|
||||
mPreviewExtender.onDeInit();
|
||||
}
|
||||
}
|
||||
|
||||
private class ImageCaptureExtenderImplStub extends IImageCaptureExtenderImpl.Stub {
|
||||
private class ImageCaptureExtenderImplStub extends IImageCaptureExtenderImpl.Stub implements
|
||||
IBinder.DeathRecipient {
|
||||
private final ImageCaptureExtenderImpl mImageExtender;
|
||||
private String mCameraId = null;
|
||||
private boolean mSessionEnabled;
|
||||
private IBinder mToken;
|
||||
|
||||
public ImageCaptureExtenderImplStub(ImageCaptureExtenderImpl imageExtender) {
|
||||
mImageExtender = imageExtender;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInit(String cameraId, CameraMetadataNative cameraCharacteristics) {
|
||||
public void onInit(IBinder token, String cameraId,
|
||||
CameraMetadataNative cameraCharacteristics) {
|
||||
CameraCharacteristics chars = new CameraCharacteristics(cameraCharacteristics);
|
||||
mCameraManager.registerDeviceStateListener(chars);
|
||||
mImageExtender.onInit(cameraId, chars, CameraExtensionsProxyService.this);
|
||||
mCameraId = cameraId;
|
||||
mToken = token;
|
||||
CameraExtensionsProxyService.registerDeathRecipient(mToken, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeInit() {
|
||||
public void onDeInit(IBinder token) {
|
||||
CameraExtensionsProxyService.unregisterDeathRecipient(mToken, this);
|
||||
mImageExtender.onDeInit();
|
||||
}
|
||||
|
||||
@@ -1564,11 +1659,13 @@ public class CameraExtensionsProxyService extends Service {
|
||||
|
||||
@Override
|
||||
public CaptureStageImpl onEnableSession() {
|
||||
mSessionEnabled = true;
|
||||
return initializeParcelable(mImageExtender.onEnableSession(), mCameraId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CaptureStageImpl onDisableSession() {
|
||||
mSessionEnabled = false;
|
||||
return initializeParcelable(mImageExtender.onDisableSession(), mCameraId);
|
||||
}
|
||||
|
||||
@@ -1737,6 +1834,14 @@ public class CameraExtensionsProxyService extends Service {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void binderDied() {
|
||||
if (mSessionEnabled) {
|
||||
mImageExtender.onDisableSession();
|
||||
}
|
||||
mImageExtender.onDeInit();
|
||||
}
|
||||
}
|
||||
|
||||
private class ProcessResultCallback implements ProcessResultImpl {
|
||||
|
||||
Reference in New Issue
Block a user