Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
dc6ed88
Add hardware-agnostic RgbLedConstants with LED indexes and default br…
nic-olo Jun 23, 2026
653ef68
Add IBesOtaController interface for BES OTA subsystem operations
nic-olo Jun 23, 2026
60c5c03
Add IBesOtaRegistry interface for late-binding BES OTA controller lookup
nic-olo Jun 23, 2026
bbce70e
Add TransportModule: vendor Hilt bindings for transport, network, and…
nic-olo Jun 23, 2026
82a37b6
Implement IBesOtaController on BesOtaManager; convert static version …
nic-olo Jun 23, 2026
527615b
Implement IBesOtaRegistry on BesOtaRegistry
nic-olo Jun 23, 2026
35c9740
Add default transport-level hook methods to IBluetoothManager
nic-olo Jun 23, 2026
6c68bfe
Override transport hooks in K900BluetoothManager, delegating to BesWi…
nic-olo Jun 23, 2026
b3faaef
Bind IBesOtaRegistry in OtaModule and wire OtaHelper to the core inte…
nic-olo Jun 23, 2026
7dceb69
Expose IBesOtaRegistry instead of BesOtaRegistry from Hilt entry point
nic-olo Jun 23, 2026
3faac59
Use RgbLedConstants instead of K900RgbLedController constants in RgbL…
nic-olo Jun 23, 2026
dc9de22
Use RgbLedConstants instead of K900RgbLedController constants in Medi…
nic-olo Jun 23, 2026
72261a4
Route set_ble_mtu through ICompanionTransport.onMtuNegotiated instead…
nic-olo Jun 23, 2026
5e8c586
Route transport reset through ICompanionTransport.onTransportReset in…
nic-olo Jun 23, 2026
57c4aae
Forward transfer_complete to ICompanionTransport.onFileTransferConfir…
nic-olo Jun 23, 2026
8fb8591
Forward file transfer ACK to ICompanionTransport.onFileTransferAck, r…
nic-olo Jun 23, 2026
1912000
Use IBesOtaController instead of BesOtaManager in BesOtaAuthEventSubs…
nic-olo Jun 23, 2026
610a191
Use IBesOtaController instead of BesOtaManager in K900CommandHandler
nic-olo Jun 23, 2026
6db9ad8
Remove hardcoded K900ProtocolStrategy from CommandProtocolDetector
nic-olo Jun 23, 2026
40cf7c1
Accept set of vendor protocol strategies in CommandProcessor constructor
nic-olo Jun 23, 2026
4db0ee5
Accept injected ICompanionTransport, INetworkManager, and protocol st…
nic-olo Jun 23, 2026
5212364
Inject ICompanionTransport, INetworkManager, IBesOtaRegistry and prot…
nic-olo Jun 23, 2026
39346e3
Return IBesOtaController from getBesOtaManager and accept IBesOtaRegi…
nic-olo Jun 23, 2026
f80264c
Migrate OtaHelper from BesOtaManager statics to IBesOtaController int…
nic-olo Jun 23, 2026
120ce2d
Use IBesOtaController in DebugBesOtaReceiver instead of concrete BesO…
nic-olo Jun 23, 2026
3daace4
Test RgbLedConstants values match K900RgbLedController parity
nic-olo Jun 23, 2026
93706ad
Test RgbLedCommandHandler applies RgbLedConstants defaults correctly
nic-olo Jun 23, 2026
1a11a9b
Test IBluetoothManager default transport hooks are safe no-ops for no…
nic-olo Jun 23, 2026
f2bea0f
Test BleConfigCommandHandler forwards MTU via ICompanionTransport int…
nic-olo Jun 23, 2026
7ff210f
Test PhoneReadyCommandHandler resets transport via interface
nic-olo Jun 23, 2026
50afa44
Test TransferCompleteCommandHandler uses ICompanionTransport without …
nic-olo Jun 23, 2026
5b6bf87
Test FileTransferAckEventSubscriber routes ACK via ICompanionTransport
nic-olo Jun 23, 2026
3bd90eb
Update BesOtaAuthEventSubscriberTest to mock IBesOtaController
nic-olo Jun 23, 2026
4816513
Test CommandProtocolDetector base behavior and runtime strategy regis…
nic-olo Jun 23, 2026
b26f7a5
Test TransportModule provider instances and unscoped lifecycle
nic-olo Jun 23, 2026
8069c4c
Test BesOtaRegistry via IBesOtaRegistry interface
nic-olo Jun 23, 2026
ab67e89
Test BesOtaManager version methods through IBesOtaController
nic-olo Jun 23, 2026
373ce46
Test OtaModule interface binding and OtaHelper wiring
nic-olo Jun 23, 2026
f5d9f22
Test OtaHelper null-safe IBesOtaRegistry access
nic-olo Jun 23, 2026
ef78d9d
Integration test: peripheral bus routes BES OTA auth events to IBesOt…
nic-olo Jun 23, 2026
1328561
Integration test: CommandProcessor routes commands via injected vendo…
nic-olo Jun 23, 2026
db858a9
Remove unused EntryPointAccessors import and dead no-op cache stubs f…
cursoragent Jun 23, 2026
803fafe
Update OtaHelperBesGuardTest after removing dead no-op methods
cursoragent Jun 23, 2026
84901dc
Defer ICompanionTransport and INetworkManager construction via Provid…
cursoragent Jun 23, 2026
b740dff
Update AGENTS.md: Hilt is now used in the service layer
cursoragent Jun 23, 2026
b9e093f
Update OTA README to reflect two-arg OtaHelper constructor
cursoragent Jun 23, 2026
c667deb
Merge branch 'dev' into cursor/pre-a6-vendor-decoupling-90cb
nic-olo Jun 23, 2026
21fa656
Merge branch 'dev' into cursor/pre-a6-vendor-decoupling-90cb
nic-olo Jul 2, 2026
a6d1a67
Implement new sendMessage overloads in IBluetoothManagerDefaultsTest
cursoragent Jul 2, 2026
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
2 changes: 1 addition & 1 deletion asg_client/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ asg_client/

### Architecture

- **Dependency Injection**: Manual factories under `io/*/core/*Factory.java` and `service/utils/DeviceProfile`. No Dagger/Hilt today (`/di` only has `AppModule.java`).
- **Dependency Injection**: Hilt is used for the service layer (`AsgClientService` and the `di/hilt/` modules). Manual factories remain under `io/*/core/*Factory.java` and `service/utils/DeviceProfile` for device-detection paths.
- **Error Reporting**: Use Sentry via reporting package
- **Logging**: Use Android Logcat with appropriate tags
- **Services**: Follow Android foreground service best practices
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.mentra.asg_client.di.hilt;

import com.mentra.asg_client.io.bes.BesOtaRegistry;
import com.mentra.asg_client.io.file.core.FileManager;
import com.mentra.asg_client.io.ota.helpers.OtaHelper;
import com.mentra.asg_client.io.ota.interfaces.IBesOtaRegistry;
import dagger.hilt.EntryPoint;
import dagger.hilt.InstallIn;
import dagger.hilt.components.SingletonComponent;
Expand All @@ -14,5 +14,5 @@ public interface AsgClientEntryPoint {

OtaHelper otaHelper();

BesOtaRegistry besOtaRegistry();
IBesOtaRegistry besOtaRegistry();
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import android.content.Context;
import com.mentra.asg_client.io.bes.BesOtaRegistry;
import com.mentra.asg_client.io.ota.helpers.OtaHelper;
import com.mentra.asg_client.io.ota.interfaces.IBesOtaRegistry;
import dagger.Module;
import dagger.Provides;
import dagger.hilt.InstallIn;
Expand All @@ -14,10 +15,17 @@
@InstallIn(SingletonComponent.class)
public class OtaModule {

/** Binds the core registry interface to the vendor singleton implementation. */
@Provides
@Singleton
static IBesOtaRegistry provideBesOtaRegistry(BesOtaRegistry impl) {
return impl;
}

@Provides
@Singleton
static OtaHelper provideOtaHelper(
@ApplicationContext Context context, BesOtaRegistry besOtaRegistry) {
@ApplicationContext Context context, IBesOtaRegistry besOtaRegistry) {
return new OtaHelper(context, besOtaRegistry);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.mentra.asg_client.di.hilt;

import android.content.Context;
import com.mentra.asg_client.io.bluetooth.core.BluetoothManagerFactory;
import com.mentra.asg_client.io.bluetooth.interfaces.ICompanionTransport;
import com.mentra.asg_client.io.bluetooth.managers.mentralive.internal.K900ProtocolStrategy;
import com.mentra.asg_client.io.network.core.NetworkManagerFactory;
import com.mentra.asg_client.io.network.interfaces.INetworkManager;
import com.mentra.asg_client.service.core.processors.CommandProtocolDetector;
import dagger.Module;
import dagger.Provides;
import dagger.hilt.InstallIn;
import dagger.hilt.android.qualifiers.ApplicationContext;
import dagger.hilt.components.SingletonComponent;
import dagger.multibindings.IntoSet;

/**
* Vendor wiring for the companion transport layer. Binds device-specific implementations (selected
* via the existing factories) to the core transport interfaces, and contributes vendor protocol
* detection strategies to the core {@link CommandProtocolDetector}.
*
* <p>The transport and network providers are intentionally UNSCOPED: {@code
* AsgClientServiceManager.cleanup()} shuts the transport down when the service is destroyed, so a
* singleton binding would re-inject a dead instance on service restart. Unscoped providers match
* the previous fresh-instance-per-service-creation factory behavior.
*/
@Module
@InstallIn(SingletonComponent.class)
public class TransportModule {

/**
* Mentra Live MCU wire-format detection. Registered into the core protocol detector at
* runtime so core never imports vendor classes.
*/
@Provides
@IntoSet
static CommandProtocolDetector.ProtocolDetectionStrategy provideK900ProtocolStrategy() {
return new K900ProtocolStrategy();
}

/** Device-appropriate companion transport (K900 UART bridge or standard BLE). */
@Provides
static ICompanionTransport provideCompanionTransport(@ApplicationContext Context context) {
return BluetoothManagerFactory.getBluetoothManager(context);
}

/** Device-appropriate network manager (K900, system-privileged, or fallback). */
@Provides
static INetworkManager provideNetworkManager(@ApplicationContext Context context) {
return NetworkManagerFactory.getNetworkManager(context);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.mentra.asg_client.io.bes.util.BesOtaUtil;
import com.mentra.asg_client.io.bluetooth.managers.mentralive.internal.SerialPortBridge;
import com.mentra.asg_client.io.bluetooth.utils.ByteUtil;
import com.mentra.asg_client.io.ota.interfaces.IBesOtaController;
import com.mentra.asg_client.service.core.handlers.K900CommandHandler;
import com.mentra.asg_client.utils.WakeLockManager;
import java.io.File;
Expand All @@ -19,7 +20,7 @@
* Manages BES2700 firmware OTA updates Handles file loading, packet transmission, state tracking,
* and protocol state machine
*/
public class BesOtaManager implements BesOtaUartListener, BesOtaCommandListener {
public class BesOtaManager implements IBesOtaController, BesOtaUartListener, BesOtaCommandListener {
private static final String TAG = "BesOtaManager";

// Static flag to track if BES OTA is in progress
Expand Down Expand Up @@ -64,12 +65,21 @@ public BesOtaManager(
this.k900CommandHandler = k900CommandHandler;
}

/**
* @return true if a BES OTA update is currently in progress
*/
@Override
public boolean isBesOtaInProgress() {
return isBesOtaInProgress;
}

/**
* Get current firmware version from BES device
*
* @return byte array with [major, minor, patch, build] or null if not available
*/
public static byte[] getCurrentFirmwareVersion() {
@Override
public byte[] getCurrentFirmwareVersion() {
return sCurrentFirmwareVersion;
}

Expand All @@ -80,7 +90,8 @@ public static byte[] getCurrentFirmwareVersion() {
* @param versionCode Server version code
* @return byte array [major, minor, patch, build]
*/
public static byte[] parseServerVersionCode(long versionCode) {
@Override
public byte[] parseServerVersionCode(long versionCode) {
int major = (int) (versionCode / 1000000);
int minor = (int) ((versionCode / 1000) % 1000);
int patch = (int) (versionCode % 1000);
Expand All @@ -96,7 +107,8 @@ public static byte[] parseServerVersionCode(long versionCode) {
* @param v2 Second version
* @return true if v1 is newer than v2, false otherwise
*/
public static boolean isNewerVersion(byte[] v1, byte[] v2) {
@Override
public boolean isNewerVersion(byte[] v1, byte[] v2) {
if (v1 == null || v2 == null || v1.length < 4 || v2.length < 4) {
return false;
}
Expand Down Expand Up @@ -233,6 +245,7 @@ public boolean init(String filePath) {
* @param filePath Path to firmware .bin file
* @return true if started successfully
*/
@Override
public boolean startFirmwareUpdate(String filePath) {
Log.i(TAG, "startFirmwareUpdate: " + filePath);

Expand Down Expand Up @@ -333,6 +346,7 @@ private void cleanup() {
* Called when BES chip grants OTA authorization This is the trigger to actually start the OTA
* protocol
*/
@Override
public void onAuthorizationGranted() {
if (!isWaitingForAuthorization) {
Log.w(TAG, "Received authorization but not waiting for it");
Expand Down Expand Up @@ -372,6 +386,7 @@ public void onAuthorizationGranted() {
}

/** Called when BES chip denies OTA authorization */
@Override
public void onAuthorizationDenied() {
Log.e(TAG, "BES OTA authorization DENIED by BES chip");
if (authTimeoutHandler != null && authTimeoutRunnable != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,33 @@
package com.mentra.asg_client.io.bes;

import androidx.annotation.Nullable;
import com.mentra.asg_client.io.ota.interfaces.IBesOtaController;
import com.mentra.asg_client.io.ota.interfaces.IBesOtaRegistry;
import javax.inject.Inject;
import javax.inject.Singleton;

/** Holds the live {@link BesOtaManager} after K900 UART transport is ready. */
/** Holds the live {@link IBesOtaController} after K900 UART transport is ready. */
@Singleton
public class BesOtaRegistry {
public class BesOtaRegistry implements IBesOtaRegistry {

private volatile BesOtaManager instance;
private volatile IBesOtaController instance;

@Inject
public BesOtaRegistry() {}

public void setInstance(BesOtaManager manager) {
this.instance = manager;
@Override
public void setInstance(IBesOtaController controller) {
this.instance = controller;
}

@Override
public void clear() {
this.instance = null;
}

public BesOtaManager getInstance() {
@Override
@Nullable
public IBesOtaController getInstance() {
return instance;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,37 @@ interface SendMessageGate {
void removeBluetoothListener(TransportListener listener);

void shutdown();

/**
* Called when the BLE MTU has been negotiated with the phone. Implementations should adjust
* transport packet sizes accordingly. Default is a no-op for transports without configurable
* packet sizes.
*
* @param mtu the negotiated MTU value in bytes
*/
default void onMtuNegotiated(int mtu) {}

/**
* Called when the transport connection is reset (e.g. the phone reconnects). Implementations
* should restore default transport configuration. Default is a no-op.
*/
default void onTransportReset() {}

/**
* Called when the phone confirms a file transfer result. Default is a no-op for transports
* without file-transfer sessions.
*
* @param fileName the name of the file that was transferred
* @param success whether the transfer succeeded
*/
default void onFileTransferConfirmation(String fileName, boolean success) {}

/**
* Called when the MCU sends a file-transfer ACK frame. Default is a no-op for transports
* without file-transfer sessions.
*
* @param state ACK state code from the MCU
* @param index packet index being acknowledged
*/
default void onFileTransferAck(int state, int index) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1194,6 +1194,37 @@ private void checkFilePacketAck(int packetIndex) {
}
}

// ========================================
// ICompanionTransport transport-level hooks
// ========================================

@Override
public void onMtuNegotiated(int mtu) {
BesWireFormat.setFilePackSizeFromMtu(mtu);
Log.i(
TAG,
"📦 MTU negotiated ("
+ mtu
+ ") - file pack size now "
+ BesWireFormat.getFilePackSize());
}

@Override
public void onTransportReset() {
BesWireFormat.resetFilePackSize();
Log.i(TAG, "📦 Transport reset - file pack size restored to default");
}

@Override
public void onFileTransferConfirmation(String fileName, boolean success) {
handlePhoneConfirmation(fileName, success);
}

@Override
public void onFileTransferAck(int state, int index) {
handleFileTransferAck(state, index);
}

/**
* Handle file transfer acknowledgment Made public so K900CommandHandler can call it when ACK is
* received as JSON
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.mentra.asg_client.io.hardware.interfaces;

/**
* Hardware-agnostic RGB LED index and brightness constants.
*
* <p>These values define the wire contract between the phone and the glasses for RGB LED commands
* (LED index 0-4 plus a default brightness). Vendor-specific controllers (e.g. {@code
* K900RgbLedController}) must keep their own constants in sync with these values.
*/
public final class RgbLedConstants {
private RgbLedConstants() {}

public static final int LED_RED = 0;
public static final int LED_GREEN = 1;
public static final int LED_BLUE = 2;
public static final int LED_ORANGE = 3;
public static final int LED_WHITE = 4;

/** Default LED brightness (0-255 scale). */
public static final int DEFAULT_BRIGHTNESS = 100;
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
import com.mentra.asg_client.settings.AsgSettings;
import com.mentra.asg_client.camera.policy.PhotoSizeTier;
import com.mentra.asg_client.camera.lifecycle.PhotoExifMetadataWriter;
import com.mentra.asg_client.hardware.K900RgbLedController;
import com.mentra.asg_client.io.file.core.FileManager;
import com.mentra.asg_client.io.hardware.core.HardwareManagerFactory;
import com.mentra.asg_client.io.hardware.interfaces.IHardwareManager;
import com.mentra.asg_client.io.hardware.interfaces.RgbLedConstants;
import com.mentra.asg_client.io.media.interfaces.ServiceCallbackInterface;
import com.mentra.asg_client.io.media.managers.MediaUploadQueueManager;
import com.mentra.asg_client.io.media.upload.MediaUploadService;
Expand Down Expand Up @@ -403,7 +403,7 @@ private void flashPrivacyLedForPhoto() {
* brightness)
*/
private void triggerPhotoFlashLed() {
triggerPhotoFlashLed(K900RgbLedController.DEFAULT_RGB_LED_BRIGHTNESS);
triggerPhotoFlashLed(RgbLedConstants.DEFAULT_BRIGHTNESS);
}

/**
Expand All @@ -427,7 +427,7 @@ private void triggerPhotoFlashLed(int brightness) {

/** Trigger solid white LED for video recording duration (default brightness) */
private void triggerVideoRecordingLed() {
triggerVideoRecordingLed(K900RgbLedController.DEFAULT_RGB_LED_BRIGHTNESS);
triggerVideoRecordingLed(RgbLedConstants.DEFAULT_BRIGHTNESS);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,8 +252,9 @@ if (success) {
### **OTA Helper Operations**

```java
// Create OTA helper
OtaHelper otaHelper = new OtaHelper(context);
// OtaHelper is Hilt-injected; obtain the instance via @Inject or AsgClientEntryPoint.
// For testing or non-Hilt contexts use the two-arg constructor:
// OtaHelper otaHelper = new OtaHelper(context, new BesOtaRegistry());

// Start version check
boolean checkStarted = otaHelper.startVersionCheck(context);
Expand Down
Loading
Loading