File Transfers & Heavy Work

Large file transfers require special handling on Android. This guide covers best practices for reliable, battery-efficient file transfers with LiteP2P.

File Transfer Rules

Follow these rules for reliable file transfers:

Rule Requirement Reason
Foreground Service Mandatory Prevents OS from killing transfer
Prefer Charging + Wi-Fi Recommended Best battery and bandwidth
Use QUIC for large files Recommended Better performance, resumability
Lightweight control channel Required Keep TCP channel for signaling only
Required

File transfers must run during a Foreground Service. Without it, Android will terminate the transfer when the app goes to background.

Basic File Transfer

// Start foreground service first
val serviceIntent = Intent(context, LiteP2PService::class.java)
ContextCompat.startForegroundService(context, serviceIntent)

// Send file
val p2p = LiteP2P.getInstance()
val transfer = p2p.sendFile(
    peerId = targetPeerId,
    file = fileToSend,
    options = FileTransferOptions.Builder()
        .setResumeEnabled(true)
        .setChunkSize(64 * 1024) // 64KB chunks
        .build()
)

// Monitor progress
transfer.onProgress { progress ->
    updateNotification(progress.bytesTransferred, progress.totalBytes)
}

transfer.onComplete { result ->
    when (result) {
        is TransferResult.Success -> {
            Log.d("LiteP2P", "File sent successfully")
        }
        is TransferResult.Error -> {
            Log.e("LiteP2P", "Transfer failed: ${result.message}")
        }
    }
    // Stop foreground service
    context.stopService(serviceIntent)
}

QUIC for Large Transfers

For files over 1MB, use QUIC transport for better performance:

val options = FileTransferOptions.Builder()
    .setTransport(TransferTransport.QUIC)  // Use QUIC instead of TCP
    .setResumeEnabled(true)                 // Enable resume on disconnect
    .setVerifyIntegrity(true)               // SHA-256 verification
    .setMaxRetries(3)                       // Auto-retry on failure
    .build()

val transfer = p2p.sendFile(peerId, largeFile, options)

Why QUIC?

  • Faster connection establishment – 0-RTT handshake
  • Better on unreliable networks – Handles packet loss gracefully
  • Stream multiplexing – Multiple files over one connection
  • Built-in encryption – TLS 1.3 by default
  • Connection migration – Survives network changes

Receiving Files

// Register file receive handler
p2p.onFileTransferRequest { request ->
    // Decide whether to accept
    val shouldAccept = checkStorageSpace(request.fileSize) &&
                      userHasApproved(request)

    if (shouldAccept) {
        // Start foreground service for receiving
        startForegroundService()

        val download = request.accept(
            destination = getDownloadPath(request.fileName)
        )

        download.onProgress { progress ->
            updateDownloadNotification(progress)
        }

        download.onComplete { result ->
            handleDownloadComplete(result)
            stopForegroundService()
        }
    } else {
        request.reject(RejectReason.USER_DECLINED)
    }
}

Waiting for Optimal Conditions

For non-urgent transfers, wait for ideal conditions:

// Check conditions before transfer
fun shouldTransferNow(): Boolean {
    val battery = context.batteryManager
    val connectivity = context.connectivityManager

    val isCharging = battery.isCharging
    val isWifi = connectivity.activeNetwork?.let { network ->
        connectivity.getNetworkCapabilities(network)
            ?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
    } ?: false

    return isCharging && isWifi
}

// Queue transfer for later
if (!shouldTransferNow()) {
    p2p.queueFileTransfer(peerId, file, TransferPriority.LOW)

    // Will be sent when conditions are met
    p2p.setTransferConditions(
        requireCharging = true,
        requireWifi = true,
        requireBatteryLevel = 20 // Minimum 20%
    )
}

Resume Support

LiteP2P supports resuming interrupted transfers:

// Enable resume
val options = FileTransferOptions.Builder()
    .setResumeEnabled(true)
    .build()

// On connection restore, check for pending transfers
p2p.onConnectionRestored { peerId ->
    val pendingTransfers = p2p.getPendingTransfers(peerId)

    pendingTransfers.forEach { transfer ->
        Log.d("LiteP2P", "Resuming transfer: ${transfer.fileName} " +
              "from ${transfer.bytesTransferred}/${transfer.totalBytes}")
        transfer.resume()
    }
}

Best Practices

Always use Foreground Service for transfers
Show progress in notification
Enable resume for files over 1MB
Use QUIC for large transfers
Queue non-urgent transfers for Wi-Fi + charging
Verify file integrity after transfer