Android Native Bridge
Part of: MPAC SmartPOS Cloud Platform - Product RequirementsVersion: 2.0 Last Updated: 2026-01-28
Overview
The Android Native Bridge provides a bidirectional communication interface between the native Android application layer and the embedded WebView running terminal applications. It exposes device-specific capabilities (device info, hardware peripherals, external app launching) to JavaScript code while maintaining security and type safety through a well-defined JavascriptInterface.
Table of Contents
- Bridge Architecture
- Bridge Interface Implementation
- WebView Integration
- Available Methods
- Security Considerations
Bridge Architecture
Communication Flow:
WebView JavaScript ←→ MPACBridge (JavascriptInterface) ←→ Android Native ServicesKey Components:
- MPACBridge: Kotlin class annotated with @JavascriptInterface
- WebView Configuration: Enables JavaScript and bridge injection
- Activity Result Launchers: Handle asynchronous operations (QR scan, external apps)
- Callback Mechanism: JavaScript functions invoked from native code
Bridge Interface Implementation
Core Bridge Class
@JavascriptInterface
class MPACBridge(private val context: Context) {
@JavascriptInterface
fun getDeviceInfo(): String {
return JSONObject().apply {
put("device_id", getDeviceId())
put("merchant_id", getMerchantId())
put("store_id", getStoreId())
put("battery_level", getBatteryLevel())
}.toString()
}
@JavascriptInterface
fun launchCreditCardApp(requestJson: String, callback: String) {
val intent = Intent().apply {
setClassName(
"com.demo.smarttab.creditcard",
"com.demo.smarttab.creditcard.MainActivity"
)
putExtra("request", requestJson)
}
creditCardLauncher.launch(intent)
// Result will be sent via callback
}
@JavascriptInterface
fun printReceipt(receiptData: String): Boolean {
return printerService.print(receiptData)
}
@JavascriptInterface
fun scanQR(callback: String): Boolean {
qrScannerLauncher.launch(Intent(context, QRScanActivity::class.java))
// Result sent via callback
return true
}
}WebView Configuration
class MainActivity : AppCompatActivity() {
private lateinit var webView: WebView
private lateinit var bridge: MPACBridge
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
bridge = MPACBridge(this)
webView = findViewById(R.id.webview)
webView.settings.apply {
javaScriptEnabled = true
domStorageEnabled = true
allowFileAccess = false
allowContentAccess = false
}
// Inject bridge into WebView
webView.addJavascriptInterface(bridge, "MPACBridge")
webView.loadUrl("https://terminal.mpac-cloud.com")
}
}WebView Integration
Accessing Bridge from JavaScript
// In WebView JavaScript code
// Get device information
const deviceInfo = JSON.parse(window.MPACBridge.getDeviceInfo());
console.log('Device ID:', deviceInfo.device_id);
console.log('Merchant ID:', deviceInfo.merchant_id);
console.log('Store ID:', deviceInfo.store_id);
console.log('Battery Level:', deviceInfo.battery_level);Launching External Payment App
// Launch credit card payment application
window.MPACBridge.launchCreditCardApp(
JSON.stringify({
amount: 10000,
currency: 'JPY',
transaction_id: 'TXN-12345'
}),
'handleCreditCardResult'
);
// Callback function invoked by native code
function handleCreditCardResult(resultJson) {
const result = JSON.parse(resultJson);
if (result.status === 'success') {
console.log('Payment approved:', result.approval_code);
updatePaymentStatus(result.approval_code);
} else if (result.status === 'declined') {
console.error('Payment declined:', result.reason);
showErrorMessage(result.reason);
} else if (result.status === 'error') {
console.error('Payment error:', result.error_message);
handlePaymentError(result);
}
}QR Code Scanning
// Initiate QR code scan
const scanResult = window.MPACBridge.scanQR('handleQRScanResult');
function handleQRScanResult(qrData) {
console.log('Scanned QR:', qrData);
// Parse and validate QR code
try {
const paymentData = JSON.parse(qrData);
processPayment(paymentData);
} catch (error) {
console.error('Invalid QR code:', error);
}
}Receipt Printing
// Print receipt
const receiptData = JSON.stringify({
store_name: 'My Store',
transaction_id: 'TXN-12345',
items: [
{ name: 'Coffee', price: 500, quantity: 2 },
{ name: 'Sandwich', price: 800, quantity: 1 }
],
total: 1800,
tax: 144,
timestamp: new Date().toISOString()
});
const success = window.MPACBridge.printReceipt(receiptData);
if (success) {
console.log('Receipt printed successfully');
} else {
console.error('Printer error');
showPrinterErrorDialog();
}Available Methods
getDeviceInfo(): String
Retrieves device information and current operational context.
Returns: JSON string containing:
device_id: Unique device identifiermerchant_id: Associated merchant IDstore_id: Associated store IDbattery_level: Current battery percentage (0-100)
Example:
const info = JSON.parse(window.MPACBridge.getDeviceInfo());launchCreditCardApp(requestJson: String, callback: String): void
Launches external credit card payment application via Intent.
Parameters:
requestJson: JSON string containing payment request datacallback: JavaScript function name to invoke with result
Result Format:
{
"status": "success" | "declined" | "error",
"approval_code": "123456",
"transaction_id": "TXN-001",
"reason": "Optional decline/error reason"
}printReceipt(receiptData: String): Boolean
Prints receipt using device's built-in thermal printer.
Parameters:
receiptData: JSON string containing receipt data
Returns: true if print job queued successfully, false on error
scanQR(callback: String): Boolean
Opens camera for QR code scanning.
Parameters:
callback: JavaScript function name to invoke with scanned data
Returns: true if scanner launched, false on error
Callback Payload: Raw QR code string content
Security Considerations
Input Validation
All data passed from JavaScript to native code must be validated:
@JavascriptInterface
fun launchCreditCardApp(requestJson: String, callback: String) {
// Validate JSON structure
val request = try {
JSONObject(requestJson)
} catch (e: JSONException) {
invokeCallback(callback, """{"status":"error","message":"Invalid JSON"}""")
return
}
// Validate required fields
if (!request.has("amount") || !request.has("transaction_id")) {
invokeCallback(callback, """{"status":"error","message":"Missing required fields"}""")
return
}
// Proceed with validated data
launchPaymentIntent(request, callback)
}Callback Sanitization
Callback function names must be sanitized to prevent injection attacks:
private fun invokeCallback(callback: String, data: String) {
// Validate callback name (alphanumeric + underscore only)
if (!callback.matches(Regex("^[a-zA-Z0-9_]+$"))) {
Log.e(TAG, "Invalid callback name: $callback")
return
}
val js = "$callback('${data.replace("'", "\\'")}')"
runOnUiThread {
webView.evaluateJavascript(js, null)
}
}HTTPS Enforcement
Only load WebView content from secure HTTPS endpoints:
webView.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
if (request.url.scheme != "https") {
Log.w(TAG, "Blocked non-HTTPS URL: ${request.url}")
return true // Block HTTP requests
}
return false
}
}Permission Management
Bridge methods requiring sensitive permissions should check runtime permissions:
@JavascriptInterface
fun scanQR(callback: String): Boolean {
if (!hasCameraPermission()) {
requestCameraPermission(callback)
return false
}
launchQRScanner(callback)
return true
}See Also
Related Integration:
- Frontend SDK - JavaScript SDK for Payment Gateway integration
- External System Integrations - Payment provider integrations
Domain Context:
- Device Management - Device provisioning and lifecycle
- Payment Processing - Payment workflows
Technical Implementation:
- Security Architecture - Security patterns and best practices
- API Architecture - API design guidelines
Code Reference:
- Android Terminal Apps:
/mpac-terminal-apps/
Navigation: ← Previous: Frontend SDK | ↑ Back to Integration Specifications | Next: External System Integrations →