Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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(String errorMessage) {}
}
);

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

```java
public interface PhotoCaptureCallback {
void onPhotoCaptured(String filePath);
void onPhotoCaptured(String filePath, @Nullable JSONObject captureMetadata);
void onPhotoError(String errorMessage);
default 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 @@ -169,6 +170,10 @@ default void onPhotoCaptured(String filePath) {
void onPhotoCaptured(String filePath, @Nullable JSONObject captureMetadata);

void onPhotoError(String errorMessage);

default void onPhotoError(CameraOperationError error) {
onPhotoError(error.message());
}
}

/** Callback for {@code camera_warm_up} lifecycle (no capture). */
Expand All @@ -188,6 +193,10 @@ public interface CameraWarmUpCallback {
/** Warm-up failed (open/configure failure). */
void onCameraError(String errorMessage);

default void onCameraError(CameraOperationError error) {
onCameraError(error.message());
}

/**
* The phone-side request this warm-up belongs to, or {@code null} if none. Used to keep at
* most one pending {@code stopped} callback per request so a re-armed warm-up doesn't send
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