MTE Relay Client for Android
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
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
Gradle (Recommended)
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
}
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
- Set up and configure your MteRelay server API to receive and decode requests from your application.
- In your Android app, import the MteRelay classes:
import com.eclypses.mte.relay.Relay
import com.eclypses.mte.relay.callback.RelayResponseListener
- Create a singleton
Relayinstance 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()}%")
}
}
)
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 streamingRelayResponseListener: Interface for receiving responses and errorsProgressCallback: Interface for tracking upload/download progressRelaySettings: 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:
- GitHub Repository – Complete examples in Kotlin and Java
- Maven Central – Latest releases
All trademarks of Eclypses Inc. may not be used without Eclypses Inc.'s prior written consent.