From 15eaf44bf717f59527ed08265eadf5f8ff84614b Mon Sep 17 00:00:00 2001 From: PhilippeFerreiraDeSousa Date: Tue, 28 Jul 2026 14:52:08 -0700 Subject: [PATCH] Gate archival photo capture on the absence of a BLE transfer id MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A take_photo with save=true and no webhookUrl currently always routes to the local-save-only capture path, even when it carries a bleImgId — which means a phone-delivery request that also wants a gallery copy would save on the glasses and never start the BLE transfer, leaving the phone to time out. A request with a bleImgId has a delivery leg, so only treat save-without-webhook as an archival capture when there is also no bleImgId. No behavior change for any shape phones send today (the phone app always supplies a webhookUrl, and archival captures from BT-SDK callers carry no bleImgId). --- .../core/handlers/PhotoCommandHandler.java | 10 +-- ...PhotoCommandHandlerTransferMethodTest.java | 79 +++++++++++++++++++ 2 files changed, 84 insertions(+), 5 deletions(-) diff --git a/asg_client/app/src/main/java/com/mentra/asg_client/service/core/handlers/PhotoCommandHandler.java b/asg_client/app/src/main/java/com/mentra/asg_client/service/core/handlers/PhotoCommandHandler.java index 4e5e66dae8..2c97890861 100644 --- a/asg_client/app/src/main/java/com/mentra/asg_client/service/core/handlers/PhotoCommandHandler.java +++ b/asg_client/app/src/main/java/com/mentra/asg_client/service/core/handlers/PhotoCommandHandler.java @@ -351,15 +351,15 @@ private boolean handleTakePhoto(JSONObject data) { return false; } - // ARCHIVAL CAPTURE: a save-only request (no upload target) has no delivery leg — + // ARCHIVAL CAPTURE: a save-only request (no delivery target) has no delivery leg — // no webhook upload, no BLE transfer — so there is nothing for the single-flight // photo-job gate to protect. Route it down the button-photo path: camera-queue // serialized, no CAMERA_BUSY, so SDK callers can burst-save to the gallery and // pull the files later over WiFi sync (each stamped with its requestId). - // transferMethod is deliberately not consulted: it is always "auto" in practice - // and the phone app always supplies a webhookUrl, so this shape is only ever - // produced by direct BT-SDK callers that want exactly this behavior. - if (save && webhookUrl.isEmpty()) { + // A request carrying a bleImgId DOES have a delivery leg (BLE to the phone) and + // must go down the normal pipeline even with save=true and no webhookUrl: that + // shape is phone-delivery with a kept gallery copy, not an archival capture. + if (save && webhookUrl.isEmpty() && bleImgId.isEmpty()) { Log.i( TAG, "PHOTO PIPELINE [ASG 3/3] Local-save capture (no upload target)" diff --git a/asg_client/app/src/test/java/com/mentra/asg_client/service/core/handlers/PhotoCommandHandlerTransferMethodTest.java b/asg_client/app/src/test/java/com/mentra/asg_client/service/core/handlers/PhotoCommandHandlerTransferMethodTest.java index 9633bc2920..05c6774352 100644 --- a/asg_client/app/src/test/java/com/mentra/asg_client/service/core/handlers/PhotoCommandHandlerTransferMethodTest.java +++ b/asg_client/app/src/test/java/com/mentra/asg_client/service/core/handlers/PhotoCommandHandlerTransferMethodTest.java @@ -7,6 +7,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -150,4 +151,82 @@ public void takePhoto_invalidTransferMethod_isRejected() throws Exception { verify(captureService) .sendPhotoErrorResponse(eq("req-1"), eq("INVALID_TRANSFER_METHOD"), anyString()); } + + @Test + public void takePhoto_saveWithBleImgIdAndNoWebhook_ridesBleTransfer() throws Exception { + when(captureService.takePhotoForBleTransfer( + anyString(), + anyString(), + anyString(), + anyBoolean(), + anyString(), + anyString(), + anyBoolean(), + anyBoolean(), + nullable(Long.class), + nullable(Integer.class), + any(PhotoCaptureSettings.class))) + .thenReturn(true); + JSONObject data = + takePhotoData() + .put("webhookUrl", "") + .put("save", true) + .put("transferMethod", "ble"); + + assertThat(handler.handleCommand("take_photo", data)).isTrue(); + verify(captureService) + .takePhotoForBleTransfer( + anyString(), + eq("req-1"), + eq("ble-1"), + eq(true), + eq("medium"), + eq("photo"), + eq(true), + eq(true), + nullable(Long.class), + nullable(Integer.class), + any(PhotoCaptureSettings.class)); + verify(captureService, never()) + .takePhotoForLocalSave( + anyString(), + anyString(), + anyString(), + anyString(), + anyBoolean(), + anyBoolean(), + nullable(Long.class), + nullable(Integer.class), + any(PhotoCaptureSettings.class)); + } + + @Test + public void takePhoto_saveWithoutBleImgIdOrWebhook_staysLocalSaveCapture() throws Exception { + when(captureService.takePhotoForLocalSave( + anyString(), + anyString(), + anyString(), + anyString(), + anyBoolean(), + anyBoolean(), + nullable(Long.class), + nullable(Integer.class), + any(PhotoCaptureSettings.class))) + .thenReturn(true); + JSONObject data = + takePhotoData().put("webhookUrl", "").put("bleImgId", "").put("save", true); + + assertThat(handler.handleCommand("take_photo", data)).isTrue(); + verify(captureService) + .takePhotoForLocalSave( + anyString(), + eq("req-1"), + eq("medium"), + eq("photo"), + eq(true), + eq(true), + nullable(Long.class), + nullable(Integer.class), + any(PhotoCaptureSettings.class)); + } }