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
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"cmake.sourceDirectory": "/Users/ahmedharmouche/Documents/dashpilot/dashpilot-android/bridge"
}
1 change: 1 addition & 0 deletions dashpilot-android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:localeConfig="@xml/locales_config"
android:usesCleartextTraffic="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ class MainActivity : ComponentActivity() {
}
},
onSkip = {
connectionVM.disconnect()
connectionVM.disconnect(context)
setOnboardingCompleted(context, true)
navController.navigate(SetupRoute) {
popUpTo(OnboardingRoute) { inclusive = true }
Expand Down Expand Up @@ -273,7 +273,7 @@ class MainActivity : ComponentActivity() {
)
},
onDisconnect = {
connectionVM.disconnect()
connectionVM.disconnect(context)
},
onNext = toDashboard,
onBattery = {
Expand Down Expand Up @@ -374,7 +374,7 @@ class MainActivity : ComponentActivity() {
onRemoveTesla = { teslaSource?.requestReset() == true },
onEnrollTesla = { navController.navigate(TeslaEnrollRoute) },
onReplayOnboarding = {
connectionVM.disconnect()
connectionVM.disconnect(context)
setOnboardingCompleted(context, false)
navController.navigate(OnboardingRoute) {
popUpTo(SetupRoute) { inclusive = false }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.softwiredtech.dashpilot.datasource.GattListener
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import java.util.UUID
import com.softwiredtech.dashpilot.R

sealed class OtaState {
object Idle : OtaState()
Expand Down Expand Up @@ -52,7 +53,7 @@ class DashKitOtaUpdate(

fun start(fw: ByteArray) {
if (fw.isEmpty()) {
_state.value = OtaState.Error("Firmware file is empty")
_state.value = OtaState.Error(manager.context.getString(R.string.ota_error_empty))
return
}
firmware = fw
Expand Down Expand Up @@ -98,7 +99,7 @@ class DashKitOtaUpdate(
private fun setupOtaService(g: BluetoothGatt) {
val service = g.getService(OTA_SERVICE_UUID)
if (service == null) {
_state.value = OtaState.Error("OTA service not found on device")
_state.value = OtaState.Error(manager.context.getString(R.string.ota_error_service_not_found))
return
}

Expand All @@ -107,7 +108,7 @@ class DashKitOtaUpdate(
statusChar = service.getCharacteristic(OTA_STATUS_UUID)

if (ctrlChar == null || dataChar == null || statusChar == null) {
_state.value = OtaState.Error("OTA characteristics not found")
_state.value = OtaState.Error(manager.context.getString(R.string.ota_error_chars_not_found))
return
}
controlChar = g.getService(VehicleControl.SERVICE_UUID)
Expand All @@ -132,7 +133,7 @@ class DashKitOtaUpdate(
// Only handle OTA status descriptor
if (descriptor.characteristic?.uuid != OTA_STATUS_UUID) return
if (status != BluetoothGatt.GATT_SUCCESS) {
_state.value = OtaState.Error("Failed to enable OTA notifications")
_state.value = OtaState.Error(manager.context.getString(R.string.ota_error_notify))
return
}
sendBeginCommand(gatt)
Expand All @@ -159,7 +160,7 @@ class DashKitOtaUpdate(
return
}
if (status != BluetoothGatt.GATT_SUCCESS) {
_state.value = OtaState.Error("Write failed (status $status)")
_state.value = OtaState.Error(manager.context.getString(R.string.ota_error_write, status))
return
}
val fw = firmware
Expand All @@ -177,7 +178,7 @@ class DashKitOtaUpdate(
Log.d(TAG, "Link dropped after the last chunk; treating as reboot")
_state.value = OtaState.Rebooting
} else if (currentState !is OtaState.Rebooting && currentState !is OtaState.Idle) {
_state.value = OtaState.Error("Disconnected unexpectedly")
_state.value = OtaState.Error(manager.context.getString(R.string.ota_error_disconnected))
}
manager.suppressPings = false
firmware = null
Expand Down Expand Up @@ -265,7 +266,7 @@ class DashKitOtaUpdate(
0xFF -> {
val errCode = if (value.size > 1) value[1].toInt() and 0xFF else 0
Log.e(TAG, "OTA error from device: 0x${errCode.toString(16)}")
_state.value = OtaState.Error("Device reported error (0x${errCode.toString(16)})")
_state.value = OtaState.Error(manager.context.getString(R.string.ota_error_device, errCode.toString(16)))
manager.removeGattListener(this)
manager.suppressPings = false
uploadFinished = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.softwiredtech.dashpilot.util.SemVer
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import java.security.MessageDigest
import com.softwiredtech.dashpilot.R

/**
* Orchestrates the end-to-end firmware update flow:
Expand Down Expand Up @@ -60,7 +61,7 @@ class FirmwareUpdateManager(
_check.value = Check.Checking
val manifest = FirmwareUpdateRepository.fetchManifest()
if (manifest == null) {
_check.value = Check.Error("Could not reach update server")
_check.value = Check.Error(manager.context.getString(R.string.fw_error_unreachable))
return
}
val current = installedVersion.value ?: versionReader.await()
Expand Down Expand Up @@ -93,14 +94,14 @@ class FirmwareUpdateManager(
if (!expected.isNullOrEmpty()) {
val actual = sha256Hex(bytes)
if (actual != expected) {
_check.value = Check.Error("Downloaded firmware failed integrity check")
_check.value = Check.Error(manager.context.getString(R.string.fw_error_integrity))
return
}
}
ota.start(bytes)
} catch (e: Exception) {
_downloadProgress.value = null
_check.value = Check.Error("Download failed: ${e.message}")
_check.value = Check.Error(manager.context.getString(R.string.fw_error_download, e.message))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import java.util.UUID
import com.softwiredtech.dashpilot.ble.VehicleControl
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import com.softwiredtech.dashpilot.R

interface GattListener {
fun onServicesReady(gatt: BluetoothGatt) {}
Expand All @@ -39,7 +40,7 @@ interface GattListener {
class BleConnectionException(message: String) : Exception(message)

@SuppressLint("MissingPermission")
class DashKitBleManager(private val context: Context) {
class DashKitBleManager(internal val context: Context) {

companion object {
private const val TAG = "DashKitBleManager"
Expand Down Expand Up @@ -186,7 +187,7 @@ class DashKitBleManager(private val context: Context) {
Log.w(TAG, "Scan timed out without finding DashKit (attempt $attemptCount/$MAX_ATTEMPTS)")
crashlytics.log("BLE: scan timed out (attempt $attemptCount/$MAX_ATTEMPTS)")
stopScan()
retryOrFail("DashKit not found")
retryOrFail(context.getString(R.string.ble_dashkit_not_found))
}

private val connectTimeoutRunnable = Runnable {
Expand All @@ -195,7 +196,7 @@ class DashKitBleManager(private val context: Context) {
Log.w(TAG, "Connect attempt timed out (attempt $attemptCount/$MAX_ATTEMPTS)")
crashlytics.log("BLE: connect attempt timed out (attempt $attemptCount/$MAX_ATTEMPTS)")
try { pending.close() } catch (_: Exception) {}
retryOrFail("Could not connect to DashKit")
retryOrFail(context.getString(R.string.ble_could_not_connect))
}

// Retry after a short pause while attempts remain, otherwise publish an
Expand Down Expand Up @@ -281,7 +282,7 @@ class DashKitBleManager(private val context: Context) {
override fun onScanFailed(errorCode: Int) {
Log.e(TAG, "BLE scan failed: $errorCode")
crashlytics.recordException(BleConnectionException("BLE scan failed (error $errorCode)"))
_connectionState.value = ConnectionStatus.Error("BLE scan failed (error $errorCode)")
_connectionState.value = ConnectionStatus.Error(context.getString(R.string.ble_scan_failed_code, errorCode))
}
}

Expand Down Expand Up @@ -327,7 +328,7 @@ class DashKitBleManager(private val context: Context) {
Log.e(TAG, "createBond() failed to start")
crashlytics.recordException(BleConnectionException("Could not start pairing (createBond failed)"))
_connectionState.value =
ConnectionStatus.Error("Could not start pairing")
ConnectionStatus.Error(context.getString(R.string.ble_could_not_start_pairing))
}
}
}
Expand Down Expand Up @@ -360,9 +361,9 @@ class DashKitBleManager(private val context: Context) {
// pairing window is open.
val message =
if (g.device.bondState == BluetoothDevice.BOND_BONDED) {
"Could not connect to DashKit"
context.getString(R.string.ble_could_not_connect)
} else {
"DashKit did not accept pairing. Open \"Pair a new device\" in DashPilot settings on an already-paired phone, then try again."
context.getString(R.string.ble_pairing_rejected)
}
retryOrFail(message)
return
Expand Down Expand Up @@ -448,7 +449,7 @@ class DashKitBleManager(private val context: Context) {
if (status != BluetoothGatt.GATT_SUCCESS) {
Log.e(TAG, "Service discovery failed: $status")
crashlytics.recordException(BleConnectionException("Service discovery failed (status=$status)"))
_connectionState.value = ConnectionStatus.Error("Service discovery failed")
_connectionState.value = ConnectionStatus.Error(context.getString(R.string.ble_service_discovery_failed))
return
}
Log.d(TAG, "Discovered ${g.services.size} services:")
Expand Down Expand Up @@ -602,13 +603,13 @@ class DashKitBleManager(private val context: Context) {
.adapter ?: run {
Log.e(TAG, "Bluetooth not available")
crashlytics.recordException(BleConnectionException("Bluetooth not available"))
_connectionState.value = ConnectionStatus.Error("Bluetooth not available")
_connectionState.value = ConnectionStatus.Error(context.getString(R.string.ble_bluetooth_unavailable))
return
}
val scanner = adapter.bluetoothLeScanner ?: run {
Log.e(TAG, "BLE scanner not available")
crashlytics.recordException(BleConnectionException("BLE scanner not available (Bluetooth off?)"))
_connectionState.value = ConnectionStatus.Error("BLE scanner not available")
_connectionState.value = ConnectionStatus.Error(context.getString(R.string.ble_scanner_unavailable))
return
}
val settings = ScanSettings.Builder()
Expand All @@ -631,11 +632,11 @@ class DashKitBleManager(private val context: Context) {
} catch (e: SecurityException) {
Log.e(TAG, "BLE scan failed — missing permission: ${e.message}")
crashlytics.recordException(BleConnectionException("BLE scan failed — missing permission: ${e.message}"))
_connectionState.value = ConnectionStatus.Error("Missing BLE permission")
_connectionState.value = ConnectionStatus.Error(context.getString(R.string.ble_missing_permission))
} catch (e: Exception) {
Log.e(TAG, "BLE scan failed: ${e.message}")
crashlytics.recordException(BleConnectionException("BLE scan failed: ${e.message}"))
_connectionState.value = ConnectionStatus.Error("Scan failed: ${e.message}")
_connectionState.value = ConnectionStatus.Error(context.getString(R.string.ble_scan_failed, e.message))
}
}

Expand Down
Loading
Loading