Monorepo: Gitea CI, docs, auth/sync, backend APIs, gitignore
- Add Gitea Actions workflow; point README to gitea.d-bis.org/Sankofa_Phoenix/SMOA - Expand .gitignore for Spring H2 data, secrets, Kotlin .kotlin/, tooling - Track docs/api/generated ReDoc bundle; refresh api docs README - Android: network/auth/sync, UI shell, tests; backend credentials/integrity APIs - Docs, scripts (generate-api-docs), modules and core updates Made-with: Cursor
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
id("com.google.dagger.hilt.android")
|
||||
id("kotlin-kapt")
|
||||
id("dagger.hilt.android.plugin")
|
||||
}
|
||||
|
||||
android {
|
||||
@@ -29,6 +29,14 @@ android {
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.5.4"
|
||||
}
|
||||
|
||||
hilt {
|
||||
enableAggregatingTask = true
|
||||
}
|
||||
}
|
||||
|
||||
kapt {
|
||||
correctErrorTypes = true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -49,9 +57,8 @@ dependencies {
|
||||
implementation(Dependencies.retrofit)
|
||||
implementation(Dependencies.okHttp)
|
||||
|
||||
// WebRTC - TODO: Configure WebRTC dependency
|
||||
// Optional: implementation(Dependencies.webrtc) when using org.webrtc:google-webrtc AAR
|
||||
// WebRTC library needs to be built from source or obtained separately
|
||||
// Uncomment when WebRTC is available:
|
||||
// implementation(Dependencies.webrtc)
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -12,7 +12,7 @@ import com.smoa.modules.communications.domain.MediaCodecPolicy
|
||||
import com.smoa.modules.communications.domain.MediaRoutingPolicy
|
||||
import com.smoa.modules.communications.domain.NetworkPathSelector
|
||||
import com.smoa.modules.communications.domain.SmartRoutingService
|
||||
import com.smoa.modules.communications.domain.StubConnectionQualityMonitor
|
||||
import com.smoa.modules.communications.domain.NetworkEstimatesConnectionQualityMonitor
|
||||
import com.smoa.modules.communications.domain.VoiceTransport
|
||||
import com.smoa.modules.communications.domain.WebRTCManager
|
||||
import dagger.Module
|
||||
@@ -28,8 +28,8 @@ object CommunicationsModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideConnectionQualityMonitor(
|
||||
stub: StubConnectionQualityMonitor
|
||||
): ConnectionQualityMonitor = stub
|
||||
networkEstimates: NetworkEstimatesConnectionQualityMonitor
|
||||
): ConnectionQualityMonitor = networkEstimates
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
|
||||
+25
-2
@@ -13,8 +13,31 @@ class ChannelManager @Inject constructor() {
|
||||
private val channels = mutableMapOf<String, Channel>()
|
||||
|
||||
init {
|
||||
// Initialize default channels (can be loaded from policy/config)
|
||||
// Example channels would be added here
|
||||
// Demo channels until policy/backend supplies live definitions
|
||||
registerChannel(
|
||||
Channel(
|
||||
id = "unit1-ops",
|
||||
name = "Unit 1 Ops",
|
||||
description = "Demo — replace with backend-fed channel list",
|
||||
unitRestricted = true,
|
||||
allowedUnits = setOf("Unit1"),
|
||||
allowedRoles = setOf(RBACFramework.Role.OPERATOR, RBACFramework.Role.ADMIN)
|
||||
)
|
||||
)
|
||||
registerChannel(
|
||||
Channel(
|
||||
id = "command-net",
|
||||
name = "Command Net",
|
||||
description = "Demo shared net",
|
||||
unitRestricted = false,
|
||||
allowedUnits = emptySet(),
|
||||
allowedRoles = setOf(
|
||||
RBACFramework.Role.OPERATOR,
|
||||
RBACFramework.Role.VIEWER,
|
||||
RBACFramework.Role.ADMIN
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
package com.smoa.modules.communications.domain
|
||||
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Network
|
||||
import android.net.NetworkCapabilities
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Derives coarse [ConnectionQuality] from Android [NetworkCapabilities] link bandwidth
|
||||
* (downstream kbps). RTT and packet loss stay unknown until WebRTC `getStats()` is integrated;
|
||||
* call [applyWebRtcStats] from a future WebRTC-backed monitor to merge metrics.
|
||||
*/
|
||||
@Singleton
|
||||
class NetworkEstimatesConnectionQualityMonitor @Inject constructor(
|
||||
@ApplicationContext context: Context
|
||||
) : ConnectionQualityMonitor {
|
||||
|
||||
private val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
private val _currentQuality = MutableStateFlow(snapshot(cm))
|
||||
|
||||
init {
|
||||
cm.registerDefaultNetworkCallback(object : ConnectivityManager.NetworkCallback() {
|
||||
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
|
||||
_currentQuality.value = fromCapabilities(networkCapabilities)
|
||||
}
|
||||
|
||||
override fun onLost(network: Network) {
|
||||
_currentQuality.value = ConnectionQuality(
|
||||
estimatedBandwidthKbps = 0,
|
||||
rttMs = -1,
|
||||
packetLossFraction = -1f,
|
||||
tier = ConnectionTier.MEDIUM
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override val currentQuality: StateFlow<ConnectionQuality> = _currentQuality.asStateFlow()
|
||||
|
||||
override fun qualityUpdates(): Flow<ConnectionQuality> = currentQuality
|
||||
|
||||
/**
|
||||
* Optional hook for WebRTC stats layer: overrides bandwidth/tier when RTT/loss known.
|
||||
*/
|
||||
fun applyWebRtcStats(estimatedBandwidthKbps: Int, rttMs: Int, packetLoss: Float) {
|
||||
val tier = connectionTierFromBandwidth(estimatedBandwidthKbps, rttMs, packetLoss)
|
||||
_currentQuality.value = ConnectionQuality(
|
||||
estimatedBandwidthKbps = estimatedBandwidthKbps,
|
||||
rttMs = rttMs,
|
||||
packetLossFraction = packetLoss,
|
||||
tier = tier
|
||||
)
|
||||
}
|
||||
|
||||
private fun snapshot(cm: ConnectivityManager): ConnectionQuality {
|
||||
val n = cm.activeNetwork ?: return ConnectionQuality(0, -1, -1f, ConnectionTier.MEDIUM)
|
||||
val caps = cm.getNetworkCapabilities(n) ?: return ConnectionQuality(0, -1, -1f, ConnectionTier.MEDIUM)
|
||||
return fromCapabilities(caps)
|
||||
}
|
||||
|
||||
private fun fromCapabilities(caps: NetworkCapabilities): ConnectionQuality {
|
||||
val kbps = caps.linkDownstreamBandwidthKbps
|
||||
val effective = kbps.coerceAtLeast(0)
|
||||
val tier = connectionTierFromBandwidth(effective, -1, -1f)
|
||||
return ConnectionQuality(
|
||||
estimatedBandwidthKbps = effective,
|
||||
rttMs = -1,
|
||||
packetLossFraction = -1f,
|
||||
tier = tier
|
||||
)
|
||||
}
|
||||
}
|
||||
+7
-49
@@ -10,7 +10,10 @@ import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* WebRTC Manager for voice and video communication.
|
||||
* Provides WebRTC peer connection management for Communications and Meetings modules.
|
||||
*
|
||||
* **Stub:** Uses in-memory [WebRTCPeerConnection] placeholders. For production, add the
|
||||
* `org.webrtc:google-webrtc` (or vendor) AAR, then replace stubs with `PeerConnectionFactory`,
|
||||
* `PeerConnection`, and `getStats()` feeding [NetworkEstimatesConnectionQualityMonitor.applyWebRtcStats].
|
||||
*/
|
||||
@Singleton
|
||||
class WebRTCManager @Inject constructor(
|
||||
@@ -36,14 +39,7 @@ class WebRTCManager @Inject constructor(
|
||||
// Create peer connection configuration
|
||||
val rtcConfig = createRTCConfiguration()
|
||||
|
||||
// TODO: Initialize actual WebRTC PeerConnection when library is fully integrated
|
||||
// This would:
|
||||
// 1. Initialize PeerConnectionFactory with options
|
||||
// 2. Create PeerConnection with rtcConfig
|
||||
// 3. Set up audio/video tracks based on isAudioOnly
|
||||
// 4. Configure ICE candidates
|
||||
// 5. Set up signaling channel
|
||||
|
||||
// STUB: create domain placeholder; swap for real PeerConnection when WebRTC AAR is linked.
|
||||
val peerConnection = WebRTCPeerConnection(
|
||||
channelId = channelId,
|
||||
isAudioOnly = isAudioOnly,
|
||||
@@ -106,12 +102,6 @@ class WebRTCManager @Inject constructor(
|
||||
stopVideoTransmission(peerConnection)
|
||||
}
|
||||
|
||||
// TODO: Close actual WebRTC PeerConnection when library is fully integrated
|
||||
// This would:
|
||||
// 1. Close peer connection
|
||||
// 2. Release all tracks
|
||||
// 3. Dispose of resources
|
||||
|
||||
peerConnections.remove(peerConnection.channelId)
|
||||
_connectionState.value = WebRTCConnectionState.Disconnected
|
||||
Result.Success(Unit)
|
||||
@@ -125,15 +115,7 @@ class WebRTCManager @Inject constructor(
|
||||
*/
|
||||
suspend fun startAudioTransmission(peerConnection: WebRTCPeerConnection): Result<Unit> {
|
||||
return try {
|
||||
// TODO: Start audio capture when WebRTC library is fully integrated
|
||||
// This would:
|
||||
// 1. Create AudioSource with constraints
|
||||
// 2. Create AudioTrack from source
|
||||
// 3. Add track to peer connection's sender
|
||||
// 4. Enable track
|
||||
// 5. Start audio capture
|
||||
|
||||
peerConnection.isAudioActive = true
|
||||
peerConnection.isAudioActive = true // STUB: wire AudioTrack when WebRTC is integrated
|
||||
Result.Success(Unit)
|
||||
} catch (e: Exception) {
|
||||
Result.Error(e)
|
||||
@@ -145,13 +127,6 @@ class WebRTCManager @Inject constructor(
|
||||
*/
|
||||
suspend fun stopAudioTransmission(peerConnection: WebRTCPeerConnection): Result<Unit> {
|
||||
return try {
|
||||
// TODO: Stop audio capture when WebRTC library is fully integrated
|
||||
// This would:
|
||||
// 1. Disable audio track
|
||||
// 2. Remove track from peer connection sender
|
||||
// 3. Stop track
|
||||
// 4. Release audio source
|
||||
|
||||
peerConnection.isAudioActive = false
|
||||
Result.Success(Unit)
|
||||
} catch (e: Exception) {
|
||||
@@ -168,15 +143,7 @@ class WebRTCManager @Inject constructor(
|
||||
return Result.Error(IllegalStateException("Video not supported for audio-only connection"))
|
||||
}
|
||||
|
||||
// TODO: Start video capture when WebRTC library is fully integrated
|
||||
// This would:
|
||||
// 1. Create VideoSource with camera constraints
|
||||
// 2. Create VideoTrack from source
|
||||
// 3. Add track to peer connection's sender
|
||||
// 4. Enable track
|
||||
// 5. Start camera capture
|
||||
|
||||
peerConnection.isVideoActive = true
|
||||
peerConnection.isVideoActive = true // STUB: wire VideoTrack / CameraX when WebRTC is integrated
|
||||
Result.Success(Unit)
|
||||
} catch (e: Exception) {
|
||||
Result.Error(e)
|
||||
@@ -188,13 +155,6 @@ class WebRTCManager @Inject constructor(
|
||||
*/
|
||||
suspend fun stopVideoTransmission(peerConnection: WebRTCPeerConnection): Result<Unit> {
|
||||
return try {
|
||||
// TODO: Stop video capture when WebRTC library is fully integrated
|
||||
// This would:
|
||||
// 1. Disable video track
|
||||
// 2. Remove track from peer connection sender
|
||||
// 3. Stop track
|
||||
// 4. Release video source and camera
|
||||
|
||||
peerConnection.isVideoActive = false
|
||||
Result.Success(Unit)
|
||||
} catch (e: Exception) {
|
||||
@@ -212,8 +172,6 @@ data class WebRTCPeerConnection(
|
||||
val config: RTCConfiguration,
|
||||
var isAudioActive: Boolean = false,
|
||||
var isVideoActive: Boolean = false
|
||||
// TODO: Add actual WebRTC PeerConnection instance when library is integrated
|
||||
// private val peerConnection: PeerConnection
|
||||
)
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user