diff --git a/asg_client/app/src/main/java/com/mentra/asg_client/io/media/core/MediaCaptureService.java b/asg_client/app/src/main/java/com/mentra/asg_client/io/media/core/MediaCaptureService.java index e929303df..78e764c49 100644 --- a/asg_client/app/src/main/java/com/mentra/asg_client/io/media/core/MediaCaptureService.java +++ b/asg_client/app/src/main/java/com/mentra/asg_client/io/media/core/MediaCaptureService.java @@ -459,7 +459,7 @@ private void flashPrivacyLedForPhoto() { } Log.d(TAG, "📸 Flashing privacy LED synchronized with shutter sound"); - hardwareManager.flashRecordingLed(2200); // 300ms flash duration + hardwareManager.flashRecordingLed(1000); // 1000ms flash duration } /** @@ -1129,9 +1129,12 @@ public void takePhotoLocally(String size, boolean enableLed) { // TESTING: Add fake delay for camera init PhotoCaptureTestFramework.addFakeDelay("CAMERA_INIT"); - playShutterSound(); + // RGB LED always flashes for photos (user visibility indicator) + triggerPhotoFlashLed(); + + // enableLed (from silent param) controls sound and privacy LED only if (enableLed) { - triggerPhotoFlashLed(); // Trigger white RGB LED flash synchronized with shutter sound + playShutterSound(); flashPrivacyLedForPhoto(); // Flash privacy LED synchronized with shutter sound } @@ -1266,9 +1269,12 @@ public void takePhotoAndUpload(String photoFilePath, String requestId, String we PhotoCaptureTestFramework.addFakeDelay("CAMERA_CAPTURE"); try { - playShutterSound(); + // RGB LED always flashes for photos (user visibility indicator) + triggerPhotoFlashLed(); + + // enableLed (from silent param) controls sound and privacy LED only if (enableLed) { - triggerPhotoFlashLed(); // Trigger white RGB LED flash synchronized with shutter sound + playShutterSound(); flashPrivacyLedForPhoto(); // Flash privacy LED synchronized with shutter sound } @@ -2078,9 +2084,12 @@ public void takePhotoForBleTransfer(String photoFilePath, String requestId, Stri // TESTING: Add fake delay for camera capture PhotoCaptureTestFramework.addFakeDelay("CAMERA_CAPTURE"); - playShutterSound(); + // RGB LED always flashes for photos (user visibility indicator) + triggerPhotoFlashLed(); + + // enableLed (from silent param) controls sound and privacy LED only if (enableLed) { - triggerPhotoFlashLed(); // Trigger white RGB LED flash synchronized with shutter sound + playShutterSound(); flashPrivacyLedForPhoto(); // Flash privacy LED synchronized with shutter sound } 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 0af4bb495..ce2a735d9 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 @@ -74,7 +74,9 @@ private boolean handleTakePhoto(JSONObject data) { boolean save = data.optBoolean("save", false); String size = data.optString("size", "medium"); String compress = data.optString("compress", "none"); // Default to none (no compression) - boolean enableLed = data.optBoolean("enable_led", true); // Default true for phone commands + // silent: true = no sound/LED, false (default) = normal behavior with sound/LED + boolean silent = data.optBoolean("silent", false); + boolean enableLed = !silent; // Convert to internal enableLed (inverted logic) // Generate file path using base class functionality String fileName = generateUniqueFilename("IMG_", ".jpg"); diff --git a/asg_client/app/src/main/java/com/mentra/asg_client/service/core/handlers/RtmpCommandHandler.java b/asg_client/app/src/main/java/com/mentra/asg_client/service/core/handlers/RtmpCommandHandler.java index e0688ad18..84fdf6d5b 100644 --- a/asg_client/app/src/main/java/com/mentra/asg_client/service/core/handlers/RtmpCommandHandler.java +++ b/asg_client/app/src/main/java/com/mentra/asg_client/service/core/handlers/RtmpCommandHandler.java @@ -108,7 +108,9 @@ private boolean handleStartRtmpStream(JSONObject data) { } String streamId = data.optString("streamId", ""); - boolean enableLed = data.optBoolean("enable_led", true); // Default true for livestreams + // silent: true = no sound/LED, false (default) = normal behavior with sound/LED + boolean silent = data.optBoolean("silent", false); + boolean enableLed = !silent; // Convert to internal enableLed (inverted logic) RtmpStreamingService.startStreaming(context, rtmpUrl, streamId, enableLed); // Set StateManager for battery monitoring diff --git a/asg_client/app/src/main/java/com/mentra/asg_client/service/core/handlers/VideoCommandHandler.java b/asg_client/app/src/main/java/com/mentra/asg_client/service/core/handlers/VideoCommandHandler.java index a5482ce56..42d9a72f0 100644 --- a/asg_client/app/src/main/java/com/mentra/asg_client/service/core/handlers/VideoCommandHandler.java +++ b/asg_client/app/src/main/java/com/mentra/asg_client/service/core/handlers/VideoCommandHandler.java @@ -139,7 +139,9 @@ private boolean handleStartVideoRecording(JSONObject data) { // Start recording with settings boolean save = data.optBoolean("save", false); - boolean enableLed = data.optBoolean("enable_led", true); // Default true for phone commands + // silent: true = no sound/LED, false (default) = normal behavior with sound/LED + boolean silent = data.optBoolean("silent", false); + boolean enableLed = !silent; // Convert to internal enableLed (inverted logic) String requestId = data.optString("requestId", "video_" + System.currentTimeMillis()); if (videoSettings != null) { diff --git a/mobile/modules/core/android/src/main/java/com/mentra/core/CoreManager.kt b/mobile/modules/core/android/src/main/java/com/mentra/core/CoreManager.kt index ff02e0821..e2b84a974 100644 --- a/mobile/modules/core/android/src/main/java/com/mentra/core/CoreManager.kt +++ b/mobile/modules/core/android/src/main/java/com/mentra/core/CoreManager.kt @@ -1027,9 +1027,9 @@ class CoreManager { sgc?.saveBufferVideo(requestId, durationSeconds) } - fun startVideoRecording(requestId: String, save: Boolean) { - Bridge.log("MAN: onStartVideoRecording: requestId=$requestId, save=$save") - sgc?.startVideoRecording(requestId, save) + fun startVideoRecording(requestId: String, save: Boolean, silent: Boolean) { + Bridge.log("MAN: onStartVideoRecording: requestId=$requestId, save=$save, silent=$silent") + sgc?.startVideoRecording(requestId, save, silent) } fun stopVideoRecording(requestId: String) { @@ -1060,10 +1060,11 @@ class CoreManager { size: String, webhookUrl: String, authToken: String, - compress: String + compress: String, + silent: Boolean ) { - Bridge.log("MAN: onPhotoRequest: $requestId, $appId, $size, compress=$compress") - sgc?.requestPhoto(requestId, appId, size, webhookUrl, authToken, compress) + Bridge.log("MAN: onPhotoRequest: $requestId, $appId, $size, compress=$compress, silent=$silent") + sgc?.requestPhoto(requestId, appId, size, webhookUrl, authToken, compress, silent) } fun rgbLedControl( diff --git a/mobile/modules/core/android/src/main/java/com/mentra/core/CoreModule.kt b/mobile/modules/core/android/src/main/java/com/mentra/core/CoreModule.kt index 5b7ba220c..a5ffc3bb8 100644 --- a/mobile/modules/core/android/src/main/java/com/mentra/core/CoreModule.kt +++ b/mobile/modules/core/android/src/main/java/com/mentra/core/CoreModule.kt @@ -80,8 +80,9 @@ class CoreModule : Module() { size: String, webhookUrl: String, authToken: String, - compress: String -> - coreManager?.photoRequest(requestId, appId, size, webhookUrl, authToken, compress) + compress: String, + silent: Boolean -> + coreManager?.photoRequest(requestId, appId, size, webhookUrl, authToken, compress, silent) } // MARK: - Video Recording Commands @@ -94,8 +95,8 @@ class CoreModule : Module() { coreManager?.saveBufferVideo(requestId, durationSeconds) } - AsyncFunction("startVideoRecording") { requestId: String, save: Boolean -> - coreManager?.startVideoRecording(requestId, save) + AsyncFunction("startVideoRecording") { requestId: String, save: Boolean, silent: Boolean -> + coreManager?.startVideoRecording(requestId, save, silent) } AsyncFunction("stopVideoRecording") { requestId: String -> diff --git a/mobile/modules/core/android/src/main/java/com/mentra/core/sgcs/G1.java b/mobile/modules/core/android/src/main/java/com/mentra/core/sgcs/G1.java index adfa07aa0..9e80fec7d 100644 --- a/mobile/modules/core/android/src/main/java/com/mentra/core/sgcs/G1.java +++ b/mobile/modules/core/android/src/main/java/com/mentra/core/sgcs/G1.java @@ -1536,7 +1536,7 @@ public void sendJson(Map jsonOriginal, boolean wakeUp) { } @Override - public void requestPhoto(String requestId, String appId, String size, String webhookUrl, String authToken, String compress) { + public void requestPhoto(String requestId, String appId, String size, String webhookUrl, String authToken, String compress, boolean silent) { } @@ -1571,7 +1571,7 @@ public void saveBufferVideo(String requestId, int durationSeconds) { } @Override - public void startVideoRecording(String requestId, boolean save) { + public void startVideoRecording(String requestId, boolean save, boolean silent) { } diff --git a/mobile/modules/core/android/src/main/java/com/mentra/core/sgcs/Mach1.java b/mobile/modules/core/android/src/main/java/com/mentra/core/sgcs/Mach1.java index 645782842..f935fca68 100644 --- a/mobile/modules/core/android/src/main/java/com/mentra/core/sgcs/Mach1.java +++ b/mobile/modules/core/android/src/main/java/com/mentra/core/sgcs/Mach1.java @@ -127,7 +127,7 @@ public List sortMicRanking(List list) { } @Override - public void requestPhoto(@NonNull String requestId, @NonNull String appId, @NonNull String size, @Nullable String webhookUrl, @Nullable String authToken, @Nullable String compress) { + public void requestPhoto(@NonNull String requestId, @NonNull String appId, @NonNull String size, @Nullable String webhookUrl, @Nullable String authToken, @Nullable String compress, boolean silent) { } @@ -162,7 +162,7 @@ public void saveBufferVideo(@NonNull String requestId, int durationSeconds) { } @Override - public void startVideoRecording(@NonNull String requestId, boolean save) { + public void startVideoRecording(@NonNull String requestId, boolean save, boolean silent) { } diff --git a/mobile/modules/core/android/src/main/java/com/mentra/core/sgcs/MentraLive.java b/mobile/modules/core/android/src/main/java/com/mentra/core/sgcs/MentraLive.java index b94764d22..37cb4091e 100644 --- a/mobile/modules/core/android/src/main/java/com/mentra/core/sgcs/MentraLive.java +++ b/mobile/modules/core/android/src/main/java/com/mentra/core/sgcs/MentraLive.java @@ -3327,8 +3327,8 @@ public boolean isMicrophoneEnabled() { return isMicrophoneEnabled; } - public void requestPhoto(String requestId, String appId, String size, String webhookUrl, String authToken, String compress) { - Bridge.log("LIVE: Requesting photo: " + requestId + " for app: " + appId + " with size: " + size + ", webhookUrl: " + webhookUrl + ", authToken: " + (authToken.isEmpty() ? "none" : "***") + ", compress=" + compress); + public void requestPhoto(String requestId, String appId, String size, String webhookUrl, String authToken, String compress, boolean silent) { + Bridge.log("LIVE: Requesting photo: " + requestId + " for app: " + appId + " with size: " + size + ", webhookUrl: " + webhookUrl + ", authToken: " + (authToken.isEmpty() ? "none" : "***") + ", compress=" + compress + ", silent=" + silent); try { JSONObject json = new JSONObject(); @@ -3349,6 +3349,8 @@ public void requestPhoto(String requestId, String appId, String size, String web } else { json.put("compress", "none"); } + // silent mode: disables shutter sound and privacy LED + json.put("silent", silent); // Always generate BLE ID for potential fallback String bleImgId = "I" + String.format("%09d", System.currentTimeMillis() % 1000000000); @@ -5248,21 +5250,22 @@ public void sendButtonMaxRecordingTime() { } @Override - public void startVideoRecording(String requestId, boolean save) { - startVideoRecording(requestId, save, 0, 0, 0); // Use defaults + public void startVideoRecording(String requestId, boolean save, boolean silent) { + startVideoRecording(requestId, save, silent, 0, 0, 0); // Use defaults } /** * Start video recording with optional resolution settings * @param requestId Request ID for tracking * @param save Whether to save the video + * @param silent Whether to disable sound and privacy LED * @param width Video width (0 for default) * @param height Video height (0 for default) * @param fps Video frame rate (0 for default) */ - public void startVideoRecording(String requestId, boolean save, int width, int height, int fps) { + public void startVideoRecording(String requestId, boolean save, boolean silent, int width, int height, int fps) { Bridge.log("LIVE: Starting video recording: requestId=" + requestId + ", save=" + save + - ", resolution=" + width + "x" + height + "@" + fps + "fps"); + ", silent=" + silent + ", resolution=" + width + "x" + height + "@" + fps + "fps"); if (!isConnected) { Log.w(TAG, "Cannot start video recording - not connected"); @@ -5274,6 +5277,7 @@ public void startVideoRecording(String requestId, boolean save, int width, int h json.put("type", "start_video_recording"); json.put("requestId", requestId); json.put("save", save); + json.put("silent", silent); // Add video settings if provided if (width > 0 && height > 0) { diff --git a/mobile/modules/core/android/src/main/java/com/mentra/core/sgcs/SGCManager.kt b/mobile/modules/core/android/src/main/java/com/mentra/core/sgcs/SGCManager.kt index ff3e64e3b..1c415fbc1 100644 --- a/mobile/modules/core/android/src/main/java/com/mentra/core/sgcs/SGCManager.kt +++ b/mobile/modules/core/android/src/main/java/com/mentra/core/sgcs/SGCManager.kt @@ -53,7 +53,8 @@ abstract class SGCManager { size: String, webhookUrl: String?, authToken: String?, - compress: String? + compress: String?, + silent: Boolean ) abstract fun startRtmpStream(message: MutableMap) abstract fun stopRtmpStream() @@ -61,7 +62,7 @@ abstract class SGCManager { abstract fun startBufferRecording() abstract fun stopBufferRecording() abstract fun saveBufferVideo(requestId: String, durationSeconds: Int) - abstract fun startVideoRecording(requestId: String, save: Boolean) + abstract fun startVideoRecording(requestId: String, save: Boolean, silent: Boolean) abstract fun stopVideoRecording(requestId: String) // Button Settings diff --git a/mobile/modules/core/android/src/main/java/com/mentra/core/sgcs/Simulated.kt b/mobile/modules/core/android/src/main/java/com/mentra/core/sgcs/Simulated.kt index d27cd9db8..5c4245fee 100644 --- a/mobile/modules/core/android/src/main/java/com/mentra/core/sgcs/Simulated.kt +++ b/mobile/modules/core/android/src/main/java/com/mentra/core/sgcs/Simulated.kt @@ -31,9 +31,10 @@ class Simulated : SGCManager() { size: String, webhookUrl: String?, authToken: String?, - compress: String? + compress: String?, + silent: Boolean ) { - Bridge.log("requestPhoto") + Bridge.log("requestPhoto silent=$silent") } override fun startRtmpStream(message: MutableMap) { @@ -60,8 +61,8 @@ class Simulated : SGCManager() { Bridge.log("saveBufferVideo") } - override fun startVideoRecording(requestId: String, save: Boolean) { - Bridge.log("startVideoRecording") + override fun startVideoRecording(requestId: String, save: Boolean, silent: Boolean) { + Bridge.log("startVideoRecording silent=$silent") } override fun stopVideoRecording(requestId: String) { diff --git a/mobile/modules/core/ios/CoreModule.swift b/mobile/modules/core/ios/CoreModule.swift index 0d0b93cb7..9e354c378 100644 --- a/mobile/modules/core/ios/CoreModule.swift +++ b/mobile/modules/core/ios/CoreModule.swift @@ -110,11 +110,11 @@ public class CoreModule: Module { AsyncFunction("photoRequest") { ( requestId: String, appId: String, size: String, webhookUrl: String?, - authToken: String?, compress: String? + authToken: String?, compress: String?, silent: Bool ) in await MainActor.run { CoreManager.shared.photoRequest( - requestId, appId, size, webhookUrl, authToken, compress + requestId, appId, size, webhookUrl, authToken, compress, silent ) } } @@ -139,9 +139,9 @@ public class CoreModule: Module { } } - AsyncFunction("startVideoRecording") { (requestId: String, save: Bool) in + AsyncFunction("startVideoRecording") { (requestId: String, save: Bool, silent: Bool) in await MainActor.run { - CoreManager.shared.startVideoRecording(requestId, save) + CoreManager.shared.startVideoRecording(requestId, save, silent) } } diff --git a/mobile/modules/core/ios/Source/CoreManager.swift b/mobile/modules/core/ios/Source/CoreManager.swift index 3ae055786..a7c575d18 100644 --- a/mobile/modules/core/ios/Source/CoreManager.swift +++ b/mobile/modules/core/ios/Source/CoreManager.swift @@ -1019,9 +1019,9 @@ struct ViewState { sgc?.saveBufferVideo(requestId: requestId, durationSeconds: durationSeconds) } - func startVideoRecording(_ requestId: String, _ save: Bool) { - Bridge.log("MAN: onStartVideoRecording: requestId=\(requestId), save=\(save)") - sgc?.startVideoRecording(requestId: requestId, save: save) + func startVideoRecording(_ requestId: String, _ save: Bool, _ silent: Bool) { + Bridge.log("MAN: onStartVideoRecording: requestId=\(requestId), save=\(save), silent=\(silent)") + sgc?.startVideoRecording(requestId: requestId, save: save, silent: silent) } func stopVideoRecording(_ requestId: String) { @@ -1070,14 +1070,15 @@ struct ViewState { _ size: String, _ webhookUrl: String?, _ authToken: String?, - _ compress: String? + _ compress: String?, + _ silent: Bool ) { Bridge.log( - "MAN: onPhotoRequest: \(requestId), \(appId), \(webhookUrl), size=\(size), compress=\(compress ?? "none")" + "MAN: onPhotoRequest: \(requestId), \(appId), \(webhookUrl), size=\(size), compress=\(compress ?? "none"), silent=\(silent)" ) sgc?.requestPhoto( requestId, appId: appId, size: size, webhookUrl: webhookUrl, authToken: authToken, - compress: compress + compress: compress, silent: silent ) } diff --git a/mobile/modules/core/ios/Source/sgcs/G1.swift b/mobile/modules/core/ios/Source/sgcs/G1.swift index 275024f57..d1e1778ae 100644 --- a/mobile/modules/core/ios/Source/sgcs/G1.swift +++ b/mobile/modules/core/ios/Source/sgcs/G1.swift @@ -315,7 +315,7 @@ class G1: NSObject, SGCManager { func requestPhoto( _: String, appId _: String, size _: String?, webhookUrl _: String?, authToken _: String?, - compress _: String? + compress _: String?, silent _: Bool ) {} func startRtmpStream(_: [String: Any]) {} @@ -330,7 +330,7 @@ class G1: NSObject, SGCManager { func saveBufferVideo(requestId _: String, durationSeconds _: Int) {} - func startVideoRecording(requestId _: String, save _: Bool) {} + func startVideoRecording(requestId _: String, save _: Bool, silent _: Bool) {} func stopVideoRecording(requestId _: String) {} diff --git a/mobile/modules/core/ios/Source/sgcs/Mach1.swift b/mobile/modules/core/ios/Source/sgcs/Mach1.swift index c5aa84f5a..2899ce3a8 100644 --- a/mobile/modules/core/ios/Source/sgcs/Mach1.swift +++ b/mobile/modules/core/ios/Source/sgcs/Mach1.swift @@ -14,7 +14,7 @@ import UltraliteSDK @MainActor class Mach1: UltraliteBaseViewController, SGCManager { - func requestPhoto(_: String, appId _: String, size _: String?, webhookUrl _: String?, authToken _: String?, compress _: String?) {} + func requestPhoto(_: String, appId _: String, size _: String?, webhookUrl _: String?, authToken _: String?, compress _: String?, silent _: Bool) {} func sendGalleryMode() {} @@ -104,7 +104,7 @@ class Mach1: UltraliteBaseViewController, SGCManager { func saveBufferVideo(requestId _: String, durationSeconds _: Int) {} - func startVideoRecording(requestId _: String, save _: Bool) {} + func startVideoRecording(requestId _: String, save _: Bool, silent _: Bool) {} func stopVideoRecording(requestId _: String) {} diff --git a/mobile/modules/core/ios/Source/sgcs/MentraLive.swift b/mobile/modules/core/ios/Source/sgcs/MentraLive.swift index 78784240c..fcfcb4fb8 100644 --- a/mobile/modules/core/ios/Source/sgcs/MentraLive.swift +++ b/mobile/modules/core/ios/Source/sgcs/MentraLive.swift @@ -1238,9 +1238,9 @@ class MentraLive: NSObject, SGCManager { func requestPhoto( _ requestId: String, appId: String, size: String?, webhookUrl: String?, authToken: String?, - compress: String? + compress: String?, silent: Bool ) { - Bridge.log("Requesting photo: \(requestId) for app: \(appId)") + Bridge.log("Requesting photo: \(requestId) for app: \(appId), silent: \(silent)") var json: [String: Any] = [ "type": "take_photo", @@ -1284,6 +1284,9 @@ class MentraLive: NSObject, SGCManager { // Add compress parameter json["compress"] = compress ?? "none" + // silent mode: disables shutter sound and privacy LED + json["silent"] = silent + Bridge.log("Using auto transfer mode with BLE fallback ID: \(bleImgId)") sendJson(json, wakeUp: true) @@ -3818,8 +3821,8 @@ extension MentraLive { sendJson(json, wakeUp: true) } - func startVideoRecording(requestId: String, save: Bool) { - startVideoRecording(requestId: requestId, save: save, width: 0, height: 0, fps: 0) + func startVideoRecording(requestId: String, save: Bool, silent: Bool) { + startVideoRecording(requestId: requestId, save: save, silent: silent, width: 0, height: 0, fps: 0) } // MARK: - SGCManager Protocol Compliance @@ -3841,9 +3844,9 @@ extension MentraLive { sendJson(json, wakeUp: true) } - func startVideoRecording(requestId: String, save: Bool, width: Int, height: Int, fps: Int) { + func startVideoRecording(requestId: String, save: Bool, silent: Bool, width: Int, height: Int, fps: Int) { Bridge.log( - "Starting video recording on glasses: requestId=\(requestId), save=\(save), resolution=\(width)x\(height)@\(fps)fps" + "Starting video recording on glasses: requestId=\(requestId), save=\(save), silent=\(silent), resolution=\(width)x\(height)@\(fps)fps" ) guard connectionState == ConnTypes.CONNECTED else { @@ -3855,6 +3858,7 @@ extension MentraLive { "type": "start_video_recording", "request_id": requestId, "save": save, + "silent": silent, ] // Add video settings if provided diff --git a/mobile/modules/core/ios/Source/sgcs/SGCManager.swift b/mobile/modules/core/ios/Source/sgcs/SGCManager.swift index e456b55cf..6bb78ad5f 100644 --- a/mobile/modules/core/ios/Source/sgcs/SGCManager.swift +++ b/mobile/modules/core/ios/Source/sgcs/SGCManager.swift @@ -55,7 +55,7 @@ protocol SGCManager { func requestPhoto( _ requestId: String, appId: String, size: String?, webhookUrl: String?, authToken: String?, - compress: String? + compress: String?, silent: Bool ) func startRtmpStream(_ message: [String: Any]) func stopRtmpStream() @@ -63,7 +63,7 @@ protocol SGCManager { func startBufferRecording() func stopBufferRecording() func saveBufferVideo(requestId: String, durationSeconds: Int) - func startVideoRecording(requestId: String, save: Bool) + func startVideoRecording(requestId: String, save: Bool, silent: Bool) func stopVideoRecording(requestId: String) // MARK: - Button Settings diff --git a/mobile/modules/core/ios/Source/sgcs/Simulated.swift b/mobile/modules/core/ios/Source/sgcs/Simulated.swift index 6f7de8519..35ef85d2f 100644 --- a/mobile/modules/core/ios/Source/sgcs/Simulated.swift +++ b/mobile/modules/core/ios/Source/sgcs/Simulated.swift @@ -66,7 +66,7 @@ class Simulated: SGCManager { // MARK: - Camera & Media - func requestPhoto(_: String, appId _: String, size _: String?, webhookUrl _: String?, authToken _: String?, compress _: String?) { + func requestPhoto(_: String, appId _: String, size _: String?, webhookUrl _: String?, authToken _: String?, compress _: String?, silent _: Bool) { Bridge.log("requestPhoto") } @@ -94,7 +94,7 @@ class Simulated: SGCManager { Bridge.log("saveBufferVideo") } - func startVideoRecording(requestId _: String, save _: Bool) { + func startVideoRecording(requestId _: String, save _: Bool, silent _: Bool) { Bridge.log("startVideoRecording") } diff --git a/mobile/modules/core/src/CoreModule.ts b/mobile/modules/core/src/CoreModule.ts index 2f99aff53..b8fee64cc 100644 --- a/mobile/modules/core/src/CoreModule.ts +++ b/mobile/modules/core/src/CoreModule.ts @@ -33,13 +33,14 @@ declare class CoreModule extends NativeModule { webhookUrl: string | null, authToken: string | null, compress: string, + silent: boolean, ): Promise // Video Recording Commands startBufferRecording(): Promise stopBufferRecording(): Promise saveBufferVideo(requestId: string, durationSeconds: number): Promise - startVideoRecording(requestId: string, save: boolean): Promise + startVideoRecording(requestId: string, save: boolean, silent: boolean): Promise stopVideoRecording(requestId: string): Promise // RTMP Stream Commands diff --git a/mobile/plugins/android.ts b/mobile/plugins/android.ts index db8316835..959441345 100644 --- a/mobile/plugins/android.ts +++ b/mobile/plugins/android.ts @@ -39,6 +39,7 @@ function withAppBuildGradleModifications(config: any) { * Release-Store credentials. * Looks for keystore in shared location (~/.mentra/credentials/) first, * then falls back to repo-local credentials/ folder. + * If no keystore is found, falls back to debug keystore for local development. */ def releaseStorePassword = project.hasProperty("MENTRAOS_UPLOAD_STORE_PASSWORD") ? project.property("MENTRAOS_UPLOAD_STORE_PASSWORD") : "" def releaseKeyPassword = project.hasProperty("MENTRAOS_UPLOAD_KEY_PASSWORD") ? project.property("MENTRAOS_UPLOAD_KEY_PASSWORD") : "" @@ -46,8 +47,33 @@ def releaseKeyAlias = project.hasProperty("MENTRAOS_UPLOAD_KEY_ALIAS") ? project // Find keystore: check shared location first, then local def sharedKeystore = new File(System.getProperty("user.home"), ".mentra/credentials/upload-keystore.jks") -def localKeystore = file('../credentials/upload-keystore.jks') -def releaseKeystoreFile = sharedKeystore.exists() ? sharedKeystore : localKeystore +def localKeystore = file('../../credentials/upload-keystore.jks') +def releaseKeystoreFile = sharedKeystore.exists() ? sharedKeystore : (localKeystore.exists() ? localKeystore : null) + +// Check if we have valid release signing credentials +def hasReleaseSigningConfig = releaseKeystoreFile != null && releaseStorePassword + +// Print signing configuration being used +println "" +println "==============================================" +println "[MentraOS] Signing Configuration" +println "==============================================" +if (hasReleaseSigningConfig) { + println " Using RELEASE keystore: \${releaseKeystoreFile.absolutePath}" + println " Key alias: \${releaseKeyAlias}" +} else { + println " Using DEBUG keystore (no release credentials found)" + println " Checked locations:" + println " - \${sharedKeystore.absolutePath} (\${sharedKeystore.exists() ? 'exists' : 'not found'})" + println " - \${localKeystore.absolutePath} (\${localKeystore.exists() ? 'exists' : 'not found'})" + if (releaseKeystoreFile != null && !releaseStorePassword) { + println " NOTE: Keystore found but MENTRAOS_UPLOAD_STORE_PASSWORD not set" + } + println "" + println " Release builds will be signed with debug key (NOT for production!)" +} +println "==============================================" +println "" // Conditionally apply Sentry gradle script for source map uploads if (project.hasProperty("sentryUploadEnabled") && project.property("sentryUploadEnabled").toBoolean()) { @@ -123,28 +149,36 @@ configurations.all { buildGradle = buildGradle.replace(/(dependencies\s*{)/, `${configurationsBlock}\n$1`) } - // 6. Add release signing config (from android-signing-config.js) + // 6. Add release signing config with fallback to debug keystore (from android-signing-config.js) if (!buildGradle.includes("storeFile releaseKeystoreFile")) { const releaseSigningConfig = ` release { - storeFile releaseKeystoreFile - storePassword = releaseStorePassword - keyAlias = releaseKeyAlias - keyPassword = releaseKeyPassword + if (hasReleaseSigningConfig) { + storeFile releaseKeystoreFile + storePassword = releaseStorePassword + keyAlias = releaseKeyAlias + keyPassword = releaseKeyPassword + } else { + // Fall back to debug keystore for local development + storeFile file('debug.keystore') + storePassword 'android' + keyAlias 'androiddebugkey' + keyPassword 'android' + } }` buildGradle = buildGradle.replace(/(signingConfigs\s*{\s*debug\s*{[^}]*})/, `$1${releaseSigningConfig}`) } - // 7. Update release build type to use release signing conditionally (from android-signing-config.js) + // 7. Update release build type to always use release signing config (which has fallback built-in) if ( buildGradle.includes("signingConfig signingConfigs.debug") && buildGradle.includes("release {") && - !buildGradle.includes("releaseStorePassword ? signingConfigs.release") + !buildGradle.includes("signingConfig signingConfigs.release") ) { buildGradle = buildGradle.replace( /release\s*{[^{}]*signingConfig signingConfigs\.debug/, - "release {\n signingConfig releaseStorePassword ? signingConfigs.release : signingConfigs.debug", + "release {\n // signingConfigs.release has built-in fallback to debug keystore\n signingConfig signingConfigs.release", ) } diff --git a/mobile/src/services/SocketComms.ts b/mobile/src/services/SocketComms.ts index b0db81287..7dd17cfe5 100644 --- a/mobile/src/services/SocketComms.ts +++ b/mobile/src/services/SocketComms.ts @@ -438,15 +438,16 @@ class SocketComms { const size = msg.size ?? "medium" const authToken = msg.authToken ?? "" const compress = msg.compress ?? "none" + const silent = msg.silent ?? false console.log( - `Received photo_request, requestId: ${requestId}, appId: ${appId}, webhookUrl: ${webhookUrl}, size: ${size} authToken: ${authToken} compress: ${compress}`, + `Received photo_request, requestId: ${requestId}, appId: ${appId}, webhookUrl: ${webhookUrl}, size: ${size} authToken: ${authToken} compress: ${compress} silent: ${silent}`, ) if (!requestId || !appId) { console.log("Invalid photo request: missing requestId or appId") return } - // Parameter order: requestId, appId, size, webhookUrl, authToken, compress - CoreModule.photoRequest(requestId, appId, size, webhookUrl, authToken, compress) + // Parameter order: requestId, appId, size, webhookUrl, authToken, compress, silent + CoreModule.photoRequest(requestId, appId, size, webhookUrl, authToken, compress, silent) } private handle_start_rtmp_stream(msg: any) { @@ -488,7 +489,8 @@ class SocketComms { console.log(`SOCKET: Received START_VIDEO_RECORDING: ${JSON.stringify(msg)}`) const videoRequestId = msg.requestId || `video_${Date.now()}` const save = msg.save !== false - CoreModule.startVideoRecording(videoRequestId, save) + const silent = msg.silent ?? false + CoreModule.startVideoRecording(videoRequestId, save, silent) } private handle_stop_video_recording(msg: any) {