Skip to content

Commit ad7010a

Browse files
committed
feat(android): implement connection watchdog, optimize autoconnect, and refine socket ping interval
- Enable OkHttp pingInterval (10 seconds) on the client WebSocket to automatically detect half-open sockets and lost connections faster. - Introduce a connection watchdog in WebSocketUtil to monitor connection health and coordinate with the macOS server's reconnect grace timer. - Refactor AirSyncService and WakeupHandler to improve auto-start and connection reliability after reboot. - Enable directBootAware for MediaNotificationListener and AirSyncService. - Optimize network state validation and simplify network state tracking by cleaning up dead code. - Send manual disconnect signals over BLE before terminating connection to prevent ghost sessions on Mac.
1 parent 4da51ab commit ad7010a

12 files changed

Lines changed: 814 additions & 446 deletions

File tree

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,4 @@ app/release
3535
local.properties
3636
.vscode/launch.json
3737
build/reports/problems/problems-report.html
38-
.agents/
38+
.agents/

app/src/main/AndroidManifest.xml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@
118118
<service
119119
android:name=".service.MediaNotificationListener"
120120
android:exported="false"
121+
android:directBootAware="true"
121122
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
122123
<intent-filter>
123124
<action android:name="android.service.notification.NotificationListenerService" />
@@ -178,15 +179,11 @@
178179

179180

180181

181-
<!-- Wake-up Service for receiving reconnection requests from Mac -->
182-
<service
183-
android:name=".service.WakeupService"
184-
android:exported="false" />
185-
186182
<!-- AirSync Service - maintains connection to Mac and handles call monitoring -->
187183
<service
188184
android:name=".service.AirSyncService"
189185
android:exported="false"
186+
android:directBootAware="true"
190187
android:foregroundServiceType="connectedDevice" />
191188

192189
<service

app/src/main/java/com/sameerasw/airsync/presentation/viewmodel/AirSyncViewModel.kt

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class AirSyncViewModel(
3838
) : ViewModel() {
3939

4040
companion object {
41+
private const val TAG = "AirSyncViewModel"
4142
fun create(context: Context): AirSyncViewModel {
4243
val dataStoreManager = DataStoreManager(context)
4344
val repository = AirSyncRepositoryImpl(dataStoreManager)
@@ -129,6 +130,13 @@ class AirSyncViewModel(
129130
}
130131

131132
init {
133+
// Clear manual disconnect flag on app startup so auto-reconnect works
134+
viewModelScope.launch {
135+
try {
136+
repository.setUserManuallyDisconnected(false)
137+
} catch (_: Exception) {}
138+
}
139+
132140
// Register for WebSocket connection status updates
133141
WebSocketUtil.registerConnectionStatusListener(connectionStatusListener)
134142
try {
@@ -219,6 +227,9 @@ class AirSyncViewModel(
219227
appContext?.unregisterReceiver(powerSaveReceiver)
220228
} catch (_: IllegalArgumentException) {
221229
// Receiver was not registered
230+
} catch (e: Exception) {
231+
// Context may be invalid (Activity leaked)
232+
Log.e(TAG, "Failed to unregister receiver: ${e.message}")
222233
}
223234
}
224235

@@ -749,7 +760,7 @@ class AirSyncViewModel(
749760
fun startNetworkMonitoring(context: Context) {
750761
if (isNetworkMonitoringActive) return
751762
isNetworkMonitoringActive = true
752-
previousNetworkIp = DeviceInfoUtil.getWifiIpAddress(context) ?: "Unknown"
763+
previousNetworkIp = DeviceInfoUtil.getWifiIpAddress(context) ?: DeviceInfoUtil.getLocalIpAddress() ?: "Unknown"
753764

754765
viewModelScope.launch {
755766
try {
@@ -780,7 +791,7 @@ class AirSyncViewModel(
780791
if (currentIp == "No Wi-Fi" || currentIp == "Unknown") {
781792
// No usable Wi‑Fi: ensure we stop any active connection and do not attempt reconnect
782793
try {
783-
WebSocketUtil.disconnect(context)
794+
WebSocketUtil.disconnect(context, isManual = false)
784795
} catch (_: Exception) {
785796
}
786797
// Stop service if needed
@@ -803,7 +814,7 @@ class AirSyncViewModel(
803814
// If connected/connecting to old network, disconnect first to force a clean switch
804815
if (WebSocketUtil.isConnected() || WebSocketUtil.isConnecting()) {
805816
try {
806-
WebSocketUtil.disconnect(context)
817+
WebSocketUtil.disconnect(context, isManual = false)
807818
} catch (_: Exception) {
808819
}
809820
}
@@ -862,7 +873,7 @@ class AirSyncViewModel(
862873
// No mapping for this network: disconnect if connected and, if allowed, start generic auto-reconnect
863874
if (WebSocketUtil.isConnected() || WebSocketUtil.isConnecting()) {
864875
try {
865-
WebSocketUtil.disconnect(context)
876+
WebSocketUtil.disconnect(context, isManual = false)
866877
} catch (_: Exception) {
867878
}
868879
}

0 commit comments

Comments
 (0)