Implement the client-side TAK passphrase authentication flow for
devices running TAK-locked firmware.
Key components:
- TakPassphraseStore: per-device passphrase persistence using
EncryptedSharedPreferences (Android Keystore AES-256-GCM), with
boot and hour TTL fields stored alongside the passphrase
- TakLockHandler: orchestrates the full lock/unlock lifecycle —
auto-unlock on reconnect using stored credentials, passphrase
submission, token info parsing, and backoff/failure handling
- MeshCommandSender: sendTakPassphrase() and sendTakLockNow() build
plain local packets that bypass PKC signing and session_passkey;
hour TTL is encoded as an absolute Unix epoch as required by firmware
- ServiceRepository: TakLockState sealed class (None, Locked,
NeedsProvision, Unlocked, LockNowAcknowledged, UnlockFailed,
UnlockBackoff), TakTokenInfo (boots remaining + expiry epoch), and
sessionAuthorized flag
- TakUnlockDialog: Compose dialog for passphrase entry, shown on
Locked and NeedsProvision states; onDismissRequest is a no-op to
prevent race conditions with firmware response timing; cancel
disconnects the user and navigates to the Connections tab
- Lock Now (Security settings): immediately disconnects the client
after informing firmware, purges cached config, navigates away
without showing a passphrase dialog
- ConnectionsScreen: suppress "region unset" prompt while the device
is TAK-locked, since pre-auth config is zeroed/redacted and would
lead the user to a blank LoRa settings screen
- AIDL: sendTakUnlock() and sendTakLockNow() wired through
MeshService → MeshActionHandler → TakLockHandler
- Security settings: "Lock Now (TAK)" button and token info display
showing boots remaining and expiry date
Meshtastic Android API
This module contains the stable AIDL interface and dependencies required to integrate with the Meshtastic Android app.
Integration
To communicate with the Meshtastic Android service from your own application, we recommend using JitPack.
Add the JitPack repository to your root build.gradle.kts (or settings.gradle.kts):
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven { url = uri("https://jitpack.io") }
}
}
Add the dependencies to your module's build.gradle.kts:
dependencies {
// Replace 'v2.7.13' with the specific version you need
val meshtasticVersion = "v2.7.13"
// The core AIDL interface and Intent constants
implementation("com.github.meshtastic.Meshtastic-Android:meshtastic-android-api:$meshtasticVersion")
// Data models (DataPacket, MeshUser, NodeInfo, etc.)
implementation("com.github.meshtastic.Meshtastic-Android:meshtastic-android-model:$meshtasticVersion")
// Protobuf definitions (PortNum, Telemetry, etc.)
implementation("com.github.meshtastic.Meshtastic-Android:meshtastic-android-proto:$meshtasticVersion")
}
Usage
1. Bind to the Service
Use the IMeshService interface to bind to the Meshtastic service. It is recommended to query the package manager to find the correct service component, as the package name may vary between build flavors (e.g., Play Store vs. F-Droid).
val intent = Intent("com.geeksville.mesh.Service")
val resolveInfo = packageManager.queryIntentServices(intent, 0)
if (resolveInfo.isNotEmpty()) {
val serviceInfo = resolveInfo[0].serviceInfo
intent.setClassName(serviceInfo.packageName, serviceInfo.name)
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
}
2. Interact with the API
Once bound, cast the IBinder to IMeshService:
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val meshService = IMeshService.Stub.asInterface(service)
// Example: Send a broadcast text message
val packet = DataPacket(
to = DataPacket.ID_BROADCAST,
bytes = "Hello Meshtastic!".encodeToByteArray().toByteString(),
dataType = PortNum.TEXT_MESSAGE_APP.value,
id = meshService.packetId,
wantAck = true
)
meshService.send(packet)
}
3. Register a BroadcastReceiver
To receive packets and status updates, register a BroadcastReceiver. Use MeshtasticIntent constants for the actions.
Important: On Android 13+ (API 33), you must use RECEIVER_EXPORTED since you are receiving broadcasts from a different application.
// Using constants from org.meshtastic.core.api.MeshtasticIntent
val intentFilter = IntentFilter().apply {
addAction(MeshtasticIntent.ACTION_RECEIVED_TEXT_MESSAGE_APP)
addAction(MeshtasticIntent.ACTION_NODE_CHANGE)
addAction(MeshtasticIntent.ACTION_CONNECTION_CHANGED)
addAction(MeshtasticIntent.ACTION_MESH_DISCONNECTED)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
registerReceiver(meshtasticReceiver, intentFilter, Context.RECEIVER_EXPORTED)
} else {
registerReceiver(meshtasticReceiver, intentFilter)
}
Modules
core:api: ContainsIMeshService.aidlandMeshtasticIntent.core:model: Contains Parcelable data classes likeDataPacket,MeshUser,NodeInfo.core:proto: Contains the generated Protobuf code (Wire).