Coverage Summary for Class: MessageNetworkHandler (debug.com.greybox.projectmesh.messaging.network)

Class Method, % Branch, % Line, % Instruction, %
MessageNetworkHandler$Companion 0% (0/2) 0% (0/38) 0% (0/63) 0% (0/337)
MessageNetworkHandler$Companion$handleIncomingMessage$1 0% (0/1) 0% (0/2) 0% (0/4) 0% (0/31)
MessageNetworkHandler$Companion$handleIncomingMessage$conversation$1 0% (0/1) 0% (0/2) 0% (0/4) 0% (0/30)
MessageNetworkHandler$Companion$handleIncomingMessage$user$1 0% (0/1) 0% (0/2) 0% (0/1) 0% (0/31)
MessageNetworkHandler$sendChatMessage$1 0% (0/1) 0% (0/5) 0% (0/25) 0% (0/161)
MessageNetworkHandler$special$$inlined$instance$1 0% (0/1)
MessageNetworkHandler$special$$inlined$instance$default$1 0% (0/1)
Total 0% (0/10) 0% (0/49) 0% (0/97) 0% (0/590)


 // Path: app/src/main/java/com/greybox/projectmesh/messaging/network/MessageNetworkHandler.kt
 package com.greybox.projectmesh.messaging.network
 
 import android.app.NotificationManager
 import android.app.PendingIntent
 import android.content.Context
 import android.content.Intent
 import android.util.Log
 import androidx.core.app.NotificationCompat
 import com.google.gson.Gson
 import com.greybox.projectmesh.GlobalApp
 import com.greybox.projectmesh.MainActivity
 import com.greybox.projectmesh.R
 import com.greybox.projectmesh.messaging.data.entities.Conversation
 import com.greybox.projectmesh.messaging.data.entities.Message
 import com.greybox.projectmesh.messaging.utils.ConversationUtils
 import com.greybox.projectmesh.messaging.repository.ConversationRepository
 import com.greybox.projectmesh.server.AppServer
 import com.greybox.projectmesh.testing.TestDeviceService
 import com.greybox.projectmesh.util.NotificationHelper
 import java.net.InetAddress
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
 import okhttp3.HttpUrl
 import okhttp3.OkHttpClient
 import okhttp3.Request
 import org.kodein.di.DI
 import org.kodein.di.DIAware
 import org.kodein.di.instance
 import com.greybox.projectmesh.user.UserRepository
 import kotlinx.coroutines.runBlocking
 import android.content.SharedPreferences
 import android.os.Parcel
 import android.os.Parcelable
 import java.net.URI
 
 /**
  * Handles sending and receiving chat messages over the network.
  *
  * @property httpClient The OkHttpClient used to make HTTP requests.
  * @property localVirtualAddr The local device's virtual network IP address.
  * @property di The Kodein DI container instance for retrieving dependencies.
  */
 class MessageNetworkHandler(
     private val httpClient: OkHttpClient,
     private val localVirtualAddr: InetAddress,
     override val di: DI
 ) : DIAware {
     private val scope = CoroutineScope(Dispatchers.IO)
     private val conversationRepository: ConversationRepository by di.instance()
     private val settingsPrefs: SharedPreferences by di.instance(tag = "settings")
 
     /**
      * Sends a chat message to a remote device over HTTP.
      *
      * @param address The target device's IP address.
      * @param time The timestamp of the message in milliseconds.
      * @param message The message text to send.
      * @param file Optional URI of a file to send along with the message.
      */
     fun sendChatMessage(address: InetAddress, time: Long, message: String, file: URI?/* test this*/) {
         scope.launch {
             try {
                 val httpUrl = HttpUrl.Builder()
                     .scheme("http")
                     .host(address.hostAddress)
                     .port(AppServer.DEFAULT_PORT)
                     .addPathSegment("chat")
                     .addQueryParameter("chatMessage", message)
                     .addQueryParameter("time", time.toString())
                     .addQueryParameter("senderIp", localVirtualAddr.hostAddress)
                     .apply {
                         if (file != null) {
                             addQueryParameter("incomingfile", file.toString())
                         }
                     }
                     .build()
 
                 val request = Request.Builder()
                     .url(httpUrl)
                     .get()
                     .build()
 
                 Log.d("MessageNetworkHandler", "Request URL: ${request.url}")
                 //Turn this into JSON
                 /*
                 val gs = Gson()
                 val msg = Message(
                     id = 0,
                     dateReceived = time,
                     content = message,
                     sender = localVirtualAddr.hostName,
                     chat = address.hostAddress,
                     file = file
                 )
                 val jsmsg = gs.toJson(msg)
                 //send
                 */
 
                 // Use try-with-resources pattern to ensure resources are closed
                 try {
                     httpClient.newCall(request).execute().use { response ->
                         if (response.isSuccessful) {
                             Log.d("MessageNetworkHandler", "Message sent successfully")
                         } else {
                             Log.e("MessageNetworkHandler", "Failed to send message: ${response.code}")
                         }
                     }
                 } catch (e: Exception) {
                     Log.e("MessageNetworkHandler", "Failed to send message, connection error: ${e.message}", e)
                 }
 
             } catch (e: Exception) {
                 Log.e("MessageNetworkHandler", "Failed to send message to ${address.hostAddress}")
             }
         }
     }
 
     companion object {
         /**
          * Processes an incoming message and routes it to the correct conversation.
          *
          * @param chatMessage The message content received.
          * @param time The timestamp when the message was received.
          * @param senderIp The IP address of the sender.
          * @param incomingfile Optional file URI attached to the message.
          * @return The created [Message] object representing the incoming message.
          */
         fun handleIncomingMessage(
             chatMessage: String?,
             time: Long,
             senderIp: InetAddress,
             incomingfile: URI?
         ): Message {
             Log.d(
                 "MessageNetworkHandler",
                 "Handling incoming message: $chatMessage, from: ${senderIp.hostAddress}, has file: ${incomingfile != null}"
             )
 
             val ipStr = senderIp.hostAddress
             val user = runBlocking { GlobalApp.GlobalUserRepo.userRepository.getUserByIp(ipStr) }
             val sender = user?.name ?: "Unknown"
 
             //determine the correct chat name
             val userUuid = when {
                 TestDeviceService.isOnlineTestDevice(senderIp) ->
                     "test-device-uuid"
 
                 ipStr == TestDeviceService.TEST_DEVICE_IP_OFFLINE || user?.name == TestDeviceService.TEST_DEVICE_NAME_OFFLINE ->
                     "offline-test-device-uuid"
 
                 else -> user?.uuid ?: "unknown-${senderIp.hostAddress}"
             }
 
             val localUuid = GlobalApp.GlobalUserRepo.prefs.getString("UUID", null) ?: "local-user"
             val chatName = ConversationUtils.createConversationId(localUuid, userUuid)
 
             Log.d(
                 "MessageNetworkHandler",
                 "Creating message with chat name: $chatName, sender: $sender"
             )
 
             //Create the message
             val message = Message(
                 id = 0,
                 dateReceived = time,
                 content = chatMessage ?: "Error! No message found.",
                 sender = sender,
                 chat = chatName,
                 file = incomingfile//will be null for reg text messages
             )
 
             //update convo with new message
             if (user != null) {
                 try {
                     //get/create convo
                     val conversation = runBlocking {
                         GlobalApp.GlobalUserRepo.conversationRepository.getOrCreateConversation(
                             localUuid = localUuid,
                             remoteUser = user
                         )
                     }
 
                     //update convo with the new message
                     runBlocking {
                         GlobalApp.GlobalUserRepo.conversationRepository.updateWithMessage(
                             conversationId = conversation.id,
                             message = message
                         )
                     }
 
                     Log.d("MessageNetworkHandler", "Updated conversation with new message")
                 } catch (e: Exception) {
                     Log.e("MessageNetworkHandler", "Failed to update conversation", e)
                 }
             }
             return message
         }
 
         /**
          * Shows a notification for an incoming message and routes to the chat screen.
          *
          * @param conversation The conversation to which the message belongs.
          * @param message The message to display in the notification.
          * @param senderIp The IP address of the sender.
          */
         private fun showMessageNotification(
             conversation: Conversation,
             message: Message,
             senderIp: InetAddress
         ) {
             try {
                 // get context
                 val context = try {
                     GlobalApp.GlobalUserRepo.userRepository.javaClass.classLoader?.loadClass("com.greybox.projectmesh.GlobalApp")
                         ?.getDeclaredField("INSTANCE")?.get(null) as? Context
                         ?: throw Exception("Cannot get application context")
                 } catch (e: Exception) {
                     Log.e("MessageNetworkHandler", "Failed to get application context", e)
                     return
                 }
 
                 // Determine title and content based on whether there's a file
                 val hasFile = message.file != null
                 val title = if (hasFile) "New message with file" else "New message"
                 val content = "From ${conversation.userName}: ${message.content}"
 
                 // Create intent that routes to the chat screen
                 val intent = Intent(context, MainActivity::class.java).apply {
                     action = "OPEN_CHAT_SCREEN"
                     putExtra("ip", senderIp.hostAddress)
                     flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
                 }
 
                 val pendingIntent = PendingIntent.getActivity(
                     context,
                     message.hashCode(),
                     intent,
                     PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
                 )
 
                 val channelId = "file_receive_channel"
 
                 val notification = NotificationCompat.Builder(context, channelId)
                         .setSmallIcon(R.drawable.ic_launcher_foreground)
                         .setContentTitle(title)
                         .setContentText(content)
                         .setPriority(NotificationCompat.PRIORITY_HIGH)
                         .setDefaults(NotificationCompat.DEFAULT_VIBRATE or NotificationCompat.DEFAULT_SOUND)
                         .setContentIntent(pendingIntent)
                         .setAutoCancel(true)
                         .build()
 
                 val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
                 notificationManager.notify(message.hashCode(), notification)
 
                 Log.d("MessageNetworkHandler", "Showed notification for message with file")
             } catch (e: Exception) {
                 Log.e("MessageNetworkHandler", "Failed to show notification", e)
             }
         }
     }
 
 }