Skip to main content
Version: 4.4.x

MTE Relay Client for Android

Maven Central

Introduction

This library provides the Eclypses MteRelay Mobile Client for Android. It enables secure, encrypted HTTP(S) communication between your Android app and your backend via an MteRelay server. You must have licensed access to an MteRelay server instance. More Info

Purpose of MteRelay:

  • Securely relay HTTP requests to your server
  • Protect sensitive headers and data with MTE encryption
  • Stream large files efficiently
  • Support for both Volley and OkHttp networking libraries
Comprehensive Documentation

This guide provides a quick-start for experienced developers. For detailed examples (both Kotlin and Java), troubleshooting, and in-depth explanations, see the complete documentation on GitHub.

Prerequisites

  • Android API 26 (Android 8.0) or later
  • Kotlin 1.9+ or Java 11+
  • Access to a licensed MteRelay server instance

Installation

Add the following dependencies to your module's build.gradle.kts or build.gradle:

dependencies {
// MTE Relay Client
implementation("com.eclypses:eclypses-aws-mte-relay-client-android-release:4.2.6")

// Required logging dependencies
implementation("org.slf4j:slf4j-api:2.0.9")
implementation("com.github.tony19:logback-android:3.0.0")

// Networking libraries (choose based on your needs)
implementation("com.android.volley:volley:1.2.1") // For Volley support
implementation("com.squareup.okhttp3:okhttp:4.12.0") // For OkHttp support
}
Required Dependencies

The SLF4J and Logback dependencies are required. Without them, the library will crash with NoClassDefFoundError: LoggerFactory. These are not optional.

Permissions

Add the following permissions to your AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

For local development servers, you may also need:

<application
android:usesCleartextTraffic="true"
... >

Setup

  1. Set up and configure your MteRelay server API to receive and decode requests from your application.
  2. In your Android app, import the MteRelay classes:
import com.eclypses.mte.relay.Relay
import com.eclypses.mte.relay.callback.RelayResponseListener
  1. Create a singleton Relay instance with a pairing callback:
class RelayManager(private val context: Context) {
private var relay: Relay? = null

fun initializeRelay(
onPaired: () -> Unit,
onError: (String) -> Unit
) {
relay = Relay.getInstance(context, object : RelayResponseListener {
override fun onCompletion(success: Boolean, message: String) {
if (success) {
onPaired()
} else {
onError(message)
}
}
})
}

fun getRelay(): Relay? = relay
}

Usage

Simple Volley GET Request

val relay = Relay.getInstance(context, pairingCallback)
val url = "https://your-relay-server.com/api/patients"

val request = JsonArrayRequest(
Request.Method.GET,
url,
null,
{ response -> /* Not called - use RelayVolleyRequestListener */ },
{ error -> /* Not called - use RelayVolleyRequestListener */ }
)

// Headers to encrypt (optional)
val headersToEncrypt = arrayOf("content-type")

relay.addToMteRequestQueue(request, headersToEncrypt, object : RelayVolleyRequestListener {
override fun onError(statusCode: Int?, message: String, responseHeaders: Map<String, List<String>>?) {
Log.e("Relay", "Error: $message")
}

override fun onResponse(response: ByteArray, responseHeaders: Map<String, List<String>>) {
val responseStr = String(response)
Log.d("Relay", "Response: $responseStr")
}

override fun onResponse(response: JSONObject, responseHeaders: Map<String, List<String>>) {
Log.d("Relay", "JSON Response: $response")
}

override fun onResponse(response: JSONArray, responseHeaders: Map<String, List<String>>) {
Log.d("Relay", "JSON Array Response: $response")
}
})

Simple Volley POST Request

val url = "https://your-relay-server.com/api/login"

val jsonBody = JSONObject().apply {
put("email", "user@example.com")
put("password", "secure_password")
}

val request = object : JsonObjectRequest(
Method.POST,
url,
jsonBody,
{ response -> /* Not called */ },
{ error -> /* Not called */ }
) {
override fun getHeaders(): Map<String, String> {
return mapOf("Content-Type" to "application/json; charset=utf-8")
}
}

val headersToEncrypt = arrayOf("content-type")

relay.addToMteRequestQueue(request, headersToEncrypt, object : RelayVolleyRequestListener {
override fun onError(statusCode: Int?, message: String, responseHeaders: Map<String, List<String>>?) {
Log.e("Relay", "Login failed: $message")
}

override fun onResponse(response: JSONObject, responseHeaders: Map<String, List<String>>) {
Log.d("Relay", "Login successful: $response")
}

override fun onResponse(response: ByteArray, responseHeaders: Map<String, List<String>>) {
val responseStr = String(response)
Log.d("Relay", "Login response: $responseStr")
}

override fun onResponse(response: JSONArray, responseHeaders: Map<String, List<String>>) {
Log.d("Relay", "Login response: $response")
}
})

OkHttp GET Request

val url = "https://your-relay-server.com/api/patients"

val request = Request.Builder()
.url(url)
.addHeader("content-type", "application/json")
.get()
.build()

val headersToEncrypt = arrayOf("content-type")

relay.send(request, headersToEncrypt, object : RelayOkHttpRequestListener {
override fun onError(response: okhttp3.Response) {
Log.e("Relay", "Error: ${response.code}")
}

override fun onResponse(response: okhttp3.Response) {
val responseStr = response.body?.string()
Log.d("Relay", "Response: $responseStr")
}
})

OkHttp POST Request

val url = "https://your-relay-server.com/api/credit-card"

val jsonBody = JSONObject().apply {
put("fullName", "John Doe")
put("creditCardNumber", "4111111111111111")
put("expiration", "12/25")
put("cvc", "123")
}

val requestBody = jsonBody.toString()
.toRequestBody("application/json; charset=utf-8".toMediaType())

val request = Request.Builder()
.url(url)
.addHeader("content-type", "application/json")
.addHeader("test-header", "test-value")
.post(requestBody)
.build()

val headersToEncrypt = arrayOf("content-type", "test-header")

relay.send(request, headersToEncrypt, object : RelayOkHttpRequestListener {
override fun onError(response: okhttp3.Response) {
Log.e("Relay", "Transaction failed: ${response.code}")
}

override fun onResponse(response: okhttp3.Response) {
val responseStr = response.body?.string()
Log.d("Relay", "Transaction successful: $responseStr")
}
})

Streamed File Upload

Create request properties and upload file:

val file = File(context.filesDir, "upload.txt")
val serverUrl = "https://your-relay-server.com"
val route = "/api/files/upload"

// Prepare headers (including Content-Type and content-length)
val headers = mapOf(
"Content-Type" to "application/octet-stream",
"content-length" to file.length().toString()
)

val headersToEncrypt = arrayOf("Content-Type", "content-length")

// Create request properties with stream callback
val reqProperties = RelayFileRequestProperties(
serverUrl,
headers,
headersToEncrypt,
object : RelayStreamCallback {
override fun getRequestBodyStream(outputStream: PipedOutputStream) {
// Write file bytes to output stream
file.inputStream().use { input ->
input.copyTo(outputStream)
}
outputStream.close()
}
}
)

// Upload the file
relay.uploadFile(
reqProperties,
route,
object : RelayStreamResponseListener {
override fun relayStreamResponse(
statusCode: Int,
success: Boolean,
responseStr: String?,
errorMessage: String?,
responseHeaders: Map<String, List<String>>?
) {
if (success) {
Log.d("Upload", "Success: $responseStr")
} else {
Log.e("Upload", "Failed: $errorMessage")
}
}
},
object : RelayStreamCompletionCallback {
override fun onProgressUpdate(bytesCompleted: Int, totalBytes: Int) {
val percentage = (bytesCompleted.toDouble() / totalBytes) * 100
Log.d("Upload", "Progress: ${percentage.toInt()}%")
}
}
)
Multipart Uploads

For multipart/form-data uploads, see the complete example on GitHub which includes the FileUploadHelper utility.

Streamed File Download

Important: Ensure the download directory exists:

val downloadFile = File(context.filesDir, "downloads/file.txt")
downloadFile.parentFile?.mkdirs()

Create request properties and download:

val serverUrl = "https://your-relay-server.com"
val route = "/api/files/download/file.txt"

val headers = mapOf(
"content-type" to "application/octet-stream"
)
val headersToEncrypt = arrayOf("content-type")

// Create request properties for download
val reqProperties = RelayFileRequestProperties(
serverUrl,
route,
downloadFile.absolutePath,
headers,
headersToEncrypt
)

// Download the file
relay.downloadFile(
reqProperties,
object : RelayStreamResponseListener {
override fun relayStreamResponse(
statusCode: Int,
success: Boolean,
responseStr: String?,
errorMessage: String?,
responseHeaders: Map<String, List<String>>?
) {
if (success) {
Log.d("Download", "Saved to: ${downloadFile.absolutePath}")
} else {
Log.e("Download", "Failed: $errorMessage")
}
}
}
)

Re-pair with Server

Re-pairing uses the RelayResponseListener set during Relay initialization:

val relayUrl = "https://your-relay-server.com"
relay.rePairWithRelayServer(relayUrl)

// Response will be delivered to the RelayResponseListener.onCompletion() callback
// that was provided when creating the Relay instance

Adjust Relay Settings

val serverUrl = "https://your-relay-server.com"
val streamChunkSize = 1048576 // 1MB chunks (0 = no change)
val pairPoolSize = 3 // Number of pairs (0 = no change)
val persistPairs = false // Persist pairs to storage

val message = relay.adjustRelaySettings(
serverUrl,
streamChunkSize,
pairPoolSize,
persistPairs
)

Log.d("Relay", "Settings adjusted: $message")
// If settings changed, relay will automatically re-pair with server
// Response delivered to RelayResponseListener.onCompletion() callback

Logging

val serverUrl = "https://your-relay-server.com"

// Enable file logging
Relay.enableFileLogging(serverUrl, true)

// Read log contents
val logs = Relay.readLogFile(serverUrl)
Log.d("Relay", "Logs: $logs")

// Clear log file
Relay.clearLogFile(serverUrl)

API Reference

See the source code and inline documentation for full API details. Key classes:

  • Relay: Main entry point for secure requests and file streaming
  • RelayResponseListener: Interface for receiving responses and errors
  • ProgressCallback: Interface for tracking upload/download progress
  • RelaySettings: Configuration for server URL, chunk size, pair pool, etc.

Support

Email: info@eclypses.com
Web: www.eclypses.com

Additional Resources

For more detailed examples and advanced implementation patterns, visit the library documentation:


All trademarks of Eclypses Inc. may not be used without Eclypses Inc.'s prior written consent.