Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ CameraNeoService.takePictureWithCallback(
context,
filePath, // if null/empty, a default path is generated
new CameraNeoService.PhotoCaptureCallback() {
@Override public void onPhotoCaptured(String filePath) {}
@Override public void onPhotoError(String error) {}
@Override public void onPhotoCaptured(String filePath, JSONObject captureMetadata) {}
@Override public void onPhotoError(CameraOperationError error) {}
}
);

Expand Down Expand Up @@ -197,8 +197,8 @@ Notes:

```java
public interface PhotoCaptureCallback {
void onPhotoCaptured(String filePath);
void onPhotoError(String errorMessage);
void onPhotoCaptured(String filePath, @Nullable JSONObject captureMetadata);
void onPhotoError(CameraOperationError error);
}

public interface VideoRecordingCallback {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import com.mentra.asg_client.camera.lifecycle.PhotoSession;
import org.json.JSONObject;
import com.mentra.asg_client.camera.lifecycle.VideoRecordingSession;
import com.mentra.asg_client.camera.model.CameraOperationError;
import com.mentra.asg_client.camera.model.PhotoCaptureSettings;
import com.mentra.asg_client.camera.model.QueuedPhotoRequest;
import com.mentra.asg_client.camera.model.QueuedPhotoRequestQueue;
Expand Down Expand Up @@ -168,7 +169,11 @@ default void onPhotoCaptured(String filePath) {

void onPhotoCaptured(String filePath, @Nullable JSONObject captureMetadata);

void onPhotoError(String errorMessage);
void onPhotoError(CameraOperationError error);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep legacy photo error callbacks source-compatible

For callers that still implement the previously documented string callback, this new typed overload is abstract, so an existing anonymous class passed to takePictureWithCallback/enqueuePhotoRequest with only onPhotoCaptured(...) and onPhotoError(String) now fails to compile despite the added default string overload. If the string callback is meant to remain backward-compatible, make the typed overload default/delegate to the string path or otherwise keep the old abstract contract so existing camera integrations are not forced to change.

Useful? React with 👍 / 👎.


default void onPhotoError(String errorMessage) {
onPhotoError(CameraOperationError.captureFailed(errorMessage));
}
}

/** Callback for {@code camera_warm_up} lifecycle (no capture). */
Expand All @@ -186,7 +191,11 @@ public interface CameraWarmUpCallback {
void onCameraStopped();

/** Warm-up failed (open/configure failure). */
void onCameraError(String errorMessage);
void onCameraError(CameraOperationError error);

default void onCameraError(String errorMessage) {
onCameraError(CameraOperationError.warmUpFailed(errorMessage));
}

/**
* The phone-side request this warm-up belongs to, or {@code null} if none. Used to keep at
Expand Down Expand Up @@ -760,7 +769,7 @@ public static void warmUpCamera(
// at the entry gate could race a capture that starts before setupWarmUp runs, letting the
// phone see `warming` immediately followed by `error` (camera_busy).
if (rejectBusy != null) {
rejectBusy.onCameraError("camera_busy");
rejectBusy.onCameraError(CameraOperationError.cameraBusy());
}
}

Expand Down Expand Up @@ -953,14 +962,14 @@ private void fireWarmReady() {
}

/** Fail every in-flight warm-up (open/configure failure). */
private void fireWarmError(String errorMessage) {
private void fireWarmError(CameraOperationError error) {
final List<CameraWarmUpCallback> failed;
synchronized (SERVICE_LOCK) {
failed = new ArrayList<>(warmCallbacks);
warmCallbacks.clear();
}
for (CameraWarmUpCallback callback : failed) {
callback.onCameraError(errorMessage);
callback.onCameraError(error);
}
}

Expand Down Expand Up @@ -1261,15 +1270,22 @@ public void onDisconnected(@NonNull CameraDevice camera) {

@Override
public void onError(@NonNull CameraDevice camera, int error) {
Log.e(TAG, "Camera device error: " + error);
CameraOperationError cameraError =
CameraOperationError.fromCameraDeviceError(error);
Log.e(
TAG,
"Camera open failed: "
+ cameraError.code()
+ " (Android code "
+ error
+ ")");
cameraCoordinator.releaseOpenCloseLock();
camera.close();
cameraCoordinator.clearDevice();
if (forVideo) {
notifyVideoError(
videoSession.currentVideoId(), "Camera device error: " + error);
notifyVideoError(videoSession.currentVideoId(), cameraError.message());
} else {
photoSession.notifyHostPhotoError("Camera device error: " + error);
photoSession.notifyHostPhotoError(cameraError);
}
stopSelf();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import com.mentra.asg_client.camera.CameraSettings;
import com.mentra.asg_client.camera.diagnostics.CameraDiagnosticsLog;
import com.mentra.asg_client.camera.model.ActivePhotoCapture;
import com.mentra.asg_client.camera.model.CameraOperationError;
import com.mentra.asg_client.camera.model.PhotoCaptureSettings;
import com.mentra.asg_client.camera.model.QueuedPhotoRequest;
import com.mentra.asg_client.camera.model.QueuedPhotoRequestQueue;
Expand Down Expand Up @@ -570,7 +571,7 @@ public void setupWarmUp(
@Nullable PhotoCaptureSettings captureSettings,
long durationMs,
Runnable onReady,
java.util.function.Consumer<String> onError) {
java.util.function.Consumer<CameraOperationError> onError) {
synchronized (hooks.serviceLock()) {
// Serialize warm-ups: the camera is a single shared resource. If a capture is in flight,
// or another warm-up is still opening, reject with a retryable busy error rather than
Expand All @@ -586,7 +587,7 @@ public void setupWarmUp(
+ shotState
+ ")");
if (onError != null) {
onError.accept("camera_busy");
onError.accept(CameraOperationError.cameraBusy());
}
return;
}
Expand Down Expand Up @@ -657,20 +658,27 @@ private void finishWarmUpReady() {
}
}

private void failWarmUp(String errorMessage) {
private void failWarmUp(CameraOperationError error) {
WarmUpRequest req = warmUpRequest;
warmUpRequest = null;
clearActiveCapture();
if (req != null && req.onError != null) {
hooks.executor().execute(() -> req.onError.accept(errorMessage));
hooks.executor().execute(() -> req.onError.accept(error));
}
}

private void failWarmUpOrPhoto(String errorMessage) {
failWarmUpOrPhoto(
warmUpRequest != null
? CameraOperationError.warmUpFailed(errorMessage)
: CameraOperationError.captureFailed(errorMessage));
}

private void failWarmUpOrPhoto(CameraOperationError error) {
if (warmUpRequest != null) {
failWarmUp(errorMessage);
failWarmUp(error);
} else {
notifyPhotoError(errorMessage);
notifyPhotoError(error);
}
}

Expand Down Expand Up @@ -1007,11 +1015,15 @@ private void recordStillCaptureMetadata(long captureGeneration, JSONObject captu
}

private void notifyPhotoError(String errorMessage) {
notifyPhotoError(CameraOperationError.captureFailed(errorMessage));
}

private void notifyPhotoError(CameraOperationError error) {
resetCaptureMetadataState();
CameraNeoService.PhotoCaptureCallback callback =
activeCapture != null ? activeCapture.callback : null;
if (callback != null) {
hooks.executor().execute(() -> callback.onPhotoError(errorMessage));
hooks.executor().execute(() -> callback.onPhotoError(error));
}
}

Expand Down Expand Up @@ -1175,7 +1187,9 @@ public void startPreviewWithAeMonitoring() {
} catch (CameraAccessException e) {
Log.e(TAG, "Error starting preview with AE monitoring", e);
if (warmUpRequest != null) {
failWarmUp("Error starting preview: " + e.getMessage());
failWarmUp(
CameraOperationError.warmUpFailed(
"Error starting preview: " + e.getMessage()));
} else {
notifyPhotoError("Error starting preview: " + e.getMessage());
}
Expand Down Expand Up @@ -1743,11 +1757,19 @@ public String photoRequestSizeTier() {

/** Called from {@link CameraNeoService} when setup/open/session errors occur before capture. */
public void notifyHostPhotoError(String errorMessage) {
notifyHostPhotoError(
warmUpRequest != null
? CameraOperationError.warmUpFailed(errorMessage)
: CameraOperationError.captureFailed(errorMessage));
}

/** Called when setup/open/session code has classified the failure. */
public void notifyHostPhotoError(CameraOperationError error) {
if (warmUpRequest != null) {
failWarmUp(errorMessage);
failWarmUp(error);
return;
}
notifyPhotoError(errorMessage);
notifyPhotoError(error);
}

public int previewJpegQuality() {
Expand All @@ -1761,15 +1783,15 @@ private static final class WarmUpRequest {
@Nullable final PhotoCaptureSettings captureSettings;
final long durationMs;
@Nullable final Runnable onReady;
@Nullable final java.util.function.Consumer<String> onError;
@Nullable final java.util.function.Consumer<CameraOperationError> onError;

WarmUpRequest(
@Nullable String size,
@Nullable Long exposureTimeNs,
@Nullable PhotoCaptureSettings captureSettings,
long durationMs,
@Nullable Runnable onReady,
@Nullable java.util.function.Consumer<String> onError) {
@Nullable java.util.function.Consumer<CameraOperationError> onError) {
this.size = size;
this.exposureTimeNs = exposureTimeNs;
this.captureSettings = captureSettings;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.mentra.asg_client.camera.model;

import android.hardware.camera2.CameraDevice;
import androidx.annotation.NonNull;

/** Structured camera failure carried from Camera2 callbacks to SDK responses. */
public final class CameraOperationError {
public static final String CAMERA_CAPTURE_FAILED = "CAMERA_CAPTURE_FAILED";
public static final String CAMERA_WARM_UP_FAILED = "camera_warm_up_failed";

private final String code;
private final String message;

public CameraOperationError(@NonNull String code, @NonNull String message) {
this.code = code;
this.message = message;
}

@NonNull
public String code() {
return code;
}

@NonNull
public String message() {
return message;
}

public static CameraOperationError captureFailed(@NonNull String message) {
return new CameraOperationError(CAMERA_CAPTURE_FAILED, message);
}

public static CameraOperationError warmUpFailed(@NonNull String message) {
return new CameraOperationError(CAMERA_WARM_UP_FAILED, message);
}

public static CameraOperationError cameraBusy() {
return new CameraOperationError(
"camera_busy", "Camera is busy with another operation. Wait and try again.");
}

public static CameraOperationError fromCameraDeviceError(int error) {
switch (error) {
case CameraDevice.StateCallback.ERROR_CAMERA_IN_USE:
return new CameraOperationError(
"CAMERA_IN_USE", "Camera is already in use. Wait and try again.");
case CameraDevice.StateCallback.ERROR_MAX_CAMERAS_IN_USE:
return new CameraOperationError(
"MAX_CAMERAS_IN_USE",
"Another camera operation is active. Wait and try again.");
case CameraDevice.StateCallback.ERROR_CAMERA_DISABLED:
return new CameraOperationError(
"CAMERA_DISABLED", "Camera access is disabled by the system.");
case CameraDevice.StateCallback.ERROR_CAMERA_DEVICE:
return new CameraOperationError(
"CAMERA_DEVICE_ERROR",
"Camera hardware was not ready. Wait a few seconds and try again.");
case CameraDevice.StateCallback.ERROR_CAMERA_SERVICE:
return new CameraOperationError(
"CAMERA_SERVICE_ERROR",
"Camera service failed. Restart the glasses if the problem continues.");
default:
return new CameraOperationError(
"CAMERA_DEVICE_ERROR",
"Camera failed with an unknown device error (Android code "
+ error
+ "). Try again.");
}
}
}
Loading
Loading