The Thread Network SDK provides functionality that's similar to a digital keychain, allowing your Android apps to share Thread network credentials with Google Play services. This allows your apps to set up any Thread device from any smart home ecosystem, without exposing credentials and user data directly.
With just a few API calls, you can:
- Request preferred Thread network credentials from Google Play services.
- Set up new border routers and add your Thread network credentials to Google Play services.
- If you already have in-field border routers, you can check if your border routers are in the preferred network and migrate them, if necessary.
There are several user and developer journeys to consider. We'll cover most of them in this guide, along with other key features and recommended usage.
Key terminology and API concepts
Before you get started, it's helpful to understand the following terms:
Thread Network Credentials: Binary blob of Thread TLVs that encodes Thread Network Name, Network Key and other properties that are required by a Thread device to join a given Thread network.
Preferred Thread Network Credentials: The auto-selected Thread network credentials that can be shared with apps of different vendors using the
getPreferredCredentials
API.Border Agent ID: A 16-byte globally unique ID for a Thread Border Router device. This ID is created and managed by border router vendors.
Thread Border Router Setup app: This is your Android app that sets up new Thread Border Router devices and adds the Thread network credentials to Google Play services. Your app is the authoritative owner of the added credentials and has access to them.
Many of the Thread Network APIs return a Task that completes asynchronously. You can use addOnSuccessListener and addOnFailureListener to register callbacks for receiving the result. To learn more, refer to the Task documentation.
Credentials ownership and maintenance
The app that adds the Thread network credentials becomes the owner of the
credentials, and has full permissions to access to the credentials. If you try
to access credentials added by other apps, you'll receive a PERMISSION_DENIED
error.
As the app owner, it's recommended that you keep credentials stored in Google Play services up-to-date when the Thread Border Router network is updated. This means adding credentials when required, updating credentials when the border router's Thread network credentials change, and removing credentials when the Thread Border Router is removed or factory reset.
Border Agent discovery
Credentials must be saved with a Border Agent ID. You'll need to make sure that your Thread Border Router Setup app is able to determine the Border Agent IDs of your Thread border routers.
Thread Border Routers must use mDNS to advertise Thread network information,
including the Network Name, Extended Pan ID, and Border Agent ID. The
corresponding txt
values for these attributes are nn
, xp
, and id
,
respectively.
For networks with Google border routers, Google Play services automatically gets Google Thread network credentials for use.
Integrate the SDK into your Android app
To get started, complete the following steps:
Follow the instructions provided at Set up Google Play services.
Add the Google Play services dependency to your
build.gradle
file:implementation 'com.google.android.gms:play-services-threadnetwork:16.2.1'
Optional: Define a
BorderAgent
data class to store border router information. We'll use this data throughout this guide:data class BorderAgentInfo( // Network Name max 16 len val networkName: String = "", val extPanId: ByteArray = ByteArray(16), val borderAgentId: ByteArray = ByteArray(16), ... )
Next, we'll go over the recommended steps to add and manage preferred credentials.
New border router setups
Before you create a new network for new border routers, it's important that you try using the preferred network credentials first. This ensures that Thread devices are connected to a single Thread network when possible.
A call to getPreferredCredentials
launches
an Activity, prompting users to allow the network request. If network
credentials have been stored in the Thread SDK digital keychain, the credentials
are returned to your app.
Request credentials
To prompt the user for preferred credentials:
Declare an
ActivityLauncher
:private lateinit var preferredCredentialsLauncher: ActivityResultLauncher<IntentSenderRequest>
Handle the Activity result, returned as
ThreadNetworkCredentials
:preferredCredentialsLauncher = registerForActivityResult( StartIntentSenderForResult() ) { result: ActivityResult -> if (result.resultCode == RESULT_OK) { val threadNetworkCredentials = ThreadNetworkCredentials.fromIntentSenderResultData(result.data!!) Log.d("debug", threadNetworkCredentials.networkName) } else { Log.d("debug", "User denied request.") } }
If you're setting up a new Thread Border Router, it's advised that you call
preferredCredentials
and launch the Activity. This call will ensure that your new Thread Border Router will use the same credentials already stored as preferred in the phone, promoting the convergence of different TBRs to the same network.private fun getPreferredThreadNetworkCredentials() { ThreadNetwork.getClient(this) .preferredCredentials .addOnSuccessListener { intentSenderResult -> intentSenderResult.intentSender?.let { preferredCredentialsLauncher.launch(IntentSenderRequest.Builder(it).build()) } ?: Log.d("debug", "No preferred credentials found.") } .addOnFailureListener { e: Exception -> Log.d(TAG, "ERROR: [${e}]") } }
If your use case relates to setting up non-TBR devices, such as a new Matter-over-Thread end device, it's advised that you use the
allActiveCredentials
api to fetch credentials. This call will scan for TBRs found in the local network and thus won't return credentials that are not available by an existing TBR locally.// Creates the IntentSender result launcher for the getAllActiveCredentials API private val getAllActiveCredentialsLauncher = registerForActivityResult( StartIntentSenderForResult() ) { result: ActivityResult -> if (result.resultCode == RESULT_OK) { val activeCredentials: List<ThreadNetworkCredentials> = ThreadNetworkCredentials.parseListFromIntentSenderResultData( result.data!! ) // Use the activeCredentials list } else { // The user denied to share! } } // Invokes the getAllActiveCredentials API and starts the dialog activity with the returned // IntentSender threadNetworkClient .getAllActiveCredentials() .addOnSuccessListener { intentSenderResult: IntentSenderResult -> val intentSender = intentSenderResult.intentSender if (intentSender != null) { getAllActiveCredentialsLauncher.launch( IntentSenderRequest.Builder(intentSender).build() ) } else { // No active network credentials found! } } // Handles the failure .addOnFailureListener { e: Exception -> // Handle the exception }
Create a new Thread network
If there are neither preferred Thread network credentials nor active thread
credentials available in a user's
Thread network, then you can use the addCredentials
API to add credentials to
Google Play Services. To do this, you'll need to create a ThreadBorderAgent
,
and also supply a ThreadNetworkCredentials
object.
To create a random network, call the newRandomizeBuilder
:
val threadCredentials = ThreadNetworkCredentials.newRandomizedBuilder().build()
To specify the Thread network Name:
val threadCredentials = ThreadNetworkCredentials.newRandomizedBuilder()
.setNetworkName("ThreadNetworkSDK")
.build()
Add credentials
To make your Thread network credentials available for other Thread vendors, we need to add them to Google Play services. Before we can add our new credentials, we also need to know to which border router device this Thread network belongs to.
In this example, we'll create a ThreadBorderAgent
from a Border Agent ID, and
pass the new Thread network credentials that you just created:
private fun addCredentials(borderAgentInfo: BorderAgentInfo, credentialsToBeAdded: ThreadNetworkCredentials) {
val threadBorderAgent = ThreadBorderAgent.newBuilder(borderAgentInfo.borderAgentId).build()
Log.d("debug", "border router id:" + threadBorderAgent.id)
ThreadNetwork.getClient(this)
.addCredentials(threadBorderAgent, credentialsToBeAdded)
.addOnSuccessListener {
Log.d("debug", "Credentials added.")
}
.addOnFailureListener { e: Exception -> Log.d(TAG, "ERROR: [${e}]") }
}
Detect and migrate in-field border routers
If you currently have in-field border routers, you can use
isPreferredCredentials
to determine if your border routers belong
to the preferred network. This API does not prompt the
user for permission, and checks border router credentials against what's stored
in Google Play services.
isPreferredCredentails
returns 0
for not matched, and 1
for
matched, as an Int
datatype. You can use IsPreferredCredentialsResult
to check your results.
public @interface IsPreferredCredentialsResult {
int PREFERRED_CREDENTIALS_NOT_FOUND = -1;
int PREFERRED_CREDENTIALS_NOT_MATCHED = 0;
int PREFERRED_CREDENTIALS_MATCHED = 1;
}
To use isPreferredCredentials
, you'll need to create a
ThreadNetworkCredentials
object first. There are several ways to instantiate ThreadNetworkCredentials
. In the next steps, we'll go over these options.
Thread network credentials by Operational Dataset
There are cases that your Thread Border Router is already set up with a
Thread network, and you want to add this Thread network to Google Play services
to share it with other vendors. You can create a ThreadNetworkCredential
instance from a raw Thread Active Operational Dataset TLV list:
Convert the Operational Dataset to a
ByteArray
. For example:val activeDataset = "0e080000000000010000000300000f35060004001fffe0020833333333...".dsToByteArray()
fun String.dsToByteArray(): ByteArray { return chunked(2).map { it.toInt(16).toByte() }.toByteArray() }
Use
fromActiveOperationalDataset
to create theThreadNetworkCredentials
. When successful, you'll be able to get the Thread network Name, Channel, and other network information. For a complete list of properties, refer to ThreadNetworkCredentials.val threadNetworkCredentials = ThreadNetworkCredentials.fromActiveOperationalDataset(activeDataset) Log.d( "threadNetworkCredentials", threadNetworkCredentials.channel.toString() + " - " + threadNetworkCredentials.networkName)
Call the
isPreferredCredentials
API and pass theThreadNetworkCredentials
.ThreadNetwork.getClient(this) .isPreferredCredentials(threadNetworkCredentials) .addOnSuccessListener { result -> when (result) { IsPreferredCredentialsResult.PREFERRED_CREDENTIALS_NOT_MATCHED -> Log.d("isPreferredCredentials", "Credentials not matched.") IsPreferredCredentialsResult.PREFERRED_CREDENTIALS_MATCHED -> Log.d("isPreferredCredentials", "Credentials matched.") } } .addOnFailureListener { e: Exception -> Log.d("isPreferredCredentials", "ERROR: [${e}]") }
Thread network credentials by Border Agent
A Border Agent ID uniquely identifies a border router device. To use
the getCredentialsByBorderAgent
API, first you'll need to create a
ThreadBorderAgent
object and pass the Border Agent ID.
Once you've created the ThreadBorderAgent
object, call
getCredentialsByBorderAgent
. If the credentials have been saved, check to see
if they're preferred.
private fun isPreferredThreadNetworkByBorderAgent(borderAgentInfo: BorderAgentInfo) {
val threadBorderAgent = ThreadBorderAgent.newBuilder(borderAgentInfo.borderAgentId).build()
Log.d("debug", "border router id:" + threadBorderAgent.id)
var isPreferred = IsPreferredCredentialsResult.PREFERRED_CREDENTIALS_NOT_FOUND
var borderAgentCredentials: ThreadNetworkCredentials?
val taskByBorderAgent = ThreadNetwork.getClient(this)
taskByBorderAgent
.getCredentialsByBorderAgent(threadBorderAgent)
.addOnSuccessListener { result: ThreadNetworkCredentialsResult ->
borderAgentCredentials = result.credentials
result.credentials?.let {
taskByBorderAgent.isPreferredCredentials(it).addOnSuccessListener { result ->
isPreferred = result
}
}
}
.addOnFailureListener { e: Exception -> Log.d(TAG, "ERROR: [${e}]") }
}
Thread network credentials by Extended Pan ID
Similar to getPreferredCredentials
, you can also prompt the user for
credentials from a border router's Extended Pan ID. The
getCredentialsByExtendedPanId
returns an IntentSender
, and the Activity
result contains a ThreadNetworkCredentials
object when the user approves.
private fun getCredentialsByExtPanId(borderAgentInfo: BorderAgentInfo) {
ThreadNetwork.getClient(this)
.getCredentialsByExtendedPanId(borderAgentInfo.extPanId)
.addOnSuccessListener { intentSenderResult ->
intentSenderResult.intentSender?.let {
preferredCredentialsLauncher.launch(IntentSenderRequest.Builder(it).build())
}
?: Log.d("debug", "No credentials found.")
}
.addOnFailureListener { e: Exception -> Log.d(TAG, "ERROR: [${e}]") }
}
Remove Credentials
When your Border Router device is removed from your home or factory reset, you need to remove its Thread network from Google Play services.
private fun removeCredentials(borderAgentInfo: BorderAgentInfo) {
val threadBorderAgent = ThreadBorderAgent.newBuilder(borderAgentInfo.borderAgentId).build()
Log.d("debug", "border router id:" + threadBorderAgent.id)
ThreadNetwork.getClient(this)
.removeCredentials(threadBorderAgent)
.addOnSuccessListener { Log.d("debug", "Credentials removed.") }
.addOnFailureListener { e: Exception -> Log.d(TAG, "ERROR: [${e}]") }
}
Resources
To learn more about the Thread Network SDK, refer to the API Reference.