Thread Play Services APIs

Many new Matter devices will use Thread—an IP-based wireless mesh networking technology, designed for smart home devices.

Thread has the following key benefits:

  • IPv6 based: Thread devices can join the same network as your other devices, and talk directly to each other and the cloud
  • Low-powered mesh: Built for IoT, Thread supports battery operated devices, with a mesh that delivers range and reliability.
  • Speed: Low overhead, local connectivity, and mesh makes Thread devices extremely responsive.

Thread is developed in the Thread Group, which Google founded with other member companies in 2014.

Thread devices join users' existing home networks through a Thread Border Router. Just like a Wi-Fi router can bridge Wi-Fi and ethernet devices into a single network, a Thread Border Router allows Thread devices to become part of users' networks.

Google devices like the Nest Wifi, Nest Hub Max and Nest Hub (2nd gen) have Thread radios built-into them and will act as Thread Border Routers when Matter launches in 2022.

An Interoperable Mesh

Thread devices and Thread Border Routers are built by a variety of device makers. We want to enable users to have an open, interoperable and strong Thread mesh in the home, regardless of the manufacturer.

As part of our new Mobile SDK, we will have Thread APIs in Google Play services that Android developers can use to join their devices to users' Thread networks.

Our Matter APIs in Google Play services will use these APIs as part of the setup process

The Thread APIs can be directly accessed for Android developers who want to customize their Thread setup, or have other non-Matter use cases that utilize Thread.

Thread and Matter

APIs

The following APIs are a preview of the core APIs that will be available in Google Play services. As these are early preview APIs, they are subject to change before general availability.

Google Play services API Entry Point

Interaction with Google Play services APIs happen via Clients which group related APIs together.

/** Entry point for Thread Network API. */
public final class ThreadNetwork {
  private ThreadNetwork() {}

  /** Creates a new instance of {@link ThreadNetworkClient} */
  public static ThreadNetworkClient getClient(Context context);

  /** Creates a new instance of {@link ThreadNetworkClient} */
  public static ThreadNetworkClient getClient(Activity activity);
}

ThreadNetworkCredentials

For API users, Thread Network Credential consists of an opaque Active Operational Dataset blob.

Data interface for managing Thread Network Credentials

/**
 * Data interface for managing Thread Network Credentials.
 *
 * An example usage of creating Thread Network Credentials with random Active Operational
 * Dataset:
 *
 * {@code
 * ThreadNetworkCredentials threadCredentials =
 *     ThreadNetworkCredentials.newRandomizedBuilder().build();
 * }
 *
 * or random Dataset with given Network Name:
 *
 * {@code
 * ThreadNetworkCredentials threadCredentials =
 *   ThreadNetworkCredentials.newRandomizedBuilder()
 *     .setNetworkName("MyThreadNet")
 *     .build();
 * }
 *
 * If the Thread Active Operational Dataset is already known, you can simply use:
 * {@code
 * ThreadNetworkCredentials threadCredentials =
 *     ThreadNetworkCredentials.fromActiveOperationalDataset(activeOperationalDataset);
 * }
 */
@SafeParcelable.Class(creator = "ThreadNetworkCredentialsCreator")
public final class ThreadNetworkCredentials extends AbstractSafeParcelable {
  /** The length of Extended PAN ID that can be set by {@link Builder#setExtendedPanId}. */
  public static final int LENGTH_EXTENDED_PANID = 8;

  /** The minimum length of Network Name that can be set by {@link Builder#setNetworkName}. */
  public static final int LENGTH_MIN_NETWORK_NAME = 1;

  /** The maximum length of NetworkName that can be set by {@link Builder#setNetworkName}. */
  public static final int LENGTH_MAX_NETWORK_NAME = 16;

  /** The length of Network Key that can be set by {@link Builder#setNetworkKey}. */
  public static final int LENGTH_NETWORK_KEY = 16;

  /** The length of Mesh-Local Prefix that can be set by {@link Builder#setMeshLocalPrefix}. */
  public static final int LENGTH_MESH_LOCAL_PREFIX = 8;

  /** The first byte of Mesh-Local Prefix that can be set by {@link Builder#setMeshLocalPrefix}. */
  public static final byte MESH_LOCAL_PREFIX_FIRST_BYTE = (byte) 0xfd;

  /** The length of PSKc that can be set by {@link Builder#setPskc}. */
  public static final int LENGTH_PSKC = 16;

  /**
   * The minimum length of Security Policy Flags that can be set by {@link
   * SecurityPolicy#SecurityPolicy}.
   */
  public static final int LENGTH_MIN_SECURITY_POLICY_FLAGS = 1;

  /**
   * The maximum length of Security Policy Flags that can be set by {@link
   * SecurityPolicy#SecurityPolicy}.
   */
  public static final int LENGTH_MAX_SECURITY_POLICY_FLAGS = 2;

  /**
   * The maximum length of Operational Dataset that can be set by {@link
   * #fromActiveOperationalDataset}.
   */
  public static final int LENGTH_MAX_OPERATIONAL_DATASET = 254;

  /** The default Thread 1.2 Security Policy. */
  public static final SecurityPolicy DEFAULT_SECURITY_POLICY =
      new SecurityPolicy(672, new byte[] {(byte) 0xff, (byte) 0xf8});

  /** The 2.4 GHz channel page. */
  public static final int CHANNEL_PAGE_2P4GHZ = 0;

  /** The minimum 2.4GHz channel. */
  public static final int CHANNEL_MIN_2P4GHZ = 11;

  /** The maximum 2.4GHz channel. */
  public static final int CHANNEL_MAX_2P4GHZ = 26;

  /** The default channel mask which enables all 2.4GHz channels. */
  public static final ChannelMaskEntry DEFAULT_CHANNEL_MASK =
      new ChannelMaskEntry(CHANNEL_PAGE_2P4GHZ, new byte[] {0x00, 0x1f, (byte) 0xff, (byte) 0xe0});

  /**
   * Parses out the data from {@link ThreadNetworkClient#getPreferredCredentials} or {@link
   * ThreadNetworkClient#getCredentialsByExtendedPanId} {@link android.content.IntentSender} result.
   * This method should only be called for success case to interpret the result data.
   *
   * @throws IllegalArgumentException if no valid Thread credentials included in {@code data}.
   */
  public static ThreadNetworkCredentials fromIntentSenderResultData(Intent data);

  /**
   * Creates a new {@link ThreadNetworkCredentials} instance with given Thread Active Operational
   * Dataset which is encoded as Thread TLV list.
   *
   * @throws IllegalArgumentException if length of {@code activeOperationalDataset} is larger than
   *     {@link #LENGTH_MAX_OPERATIONAL_DATASET}, {@code activeOperationalDataset} is malformed or
   *     there are missing Thread TLVs.
   */
  public static ThreadNetworkCredentials fromActiveOperationalDataset(
      @Size(max = LENGTH_MAX_OPERATIONAL_DATASET) byte[] activeOperationalDataset);

  /**
   * Creates a new {@link ThreadNetworkCredentials.Builder} instance for creating {@link
   * ThreadNetworkCredentials} instances with randomly generated credentials.
   *
   * 

If there are requirements on specific parameters, set it explicitly with their setters in * {@link ThreadNetworkCredentials.Builder}. For example, it's likely that a readable Thread * Network Name should be set with {@link Builder#setNetworkName}. */ public static ThreadNetworkCredentials.Builder newRandomizedBuilder(); /** Returns the Thread active operational dataset as encoded Thread TLV list. */ @Size(max = LENGTH_MAX_OPERATIONAL_DATASET) public byte[] getActiveOperationalDataset(); /** Returns the Network Key in the Thread active operational dataset. */ @Size(LENGTH_NETWORK_KEY) public byte[] getNetworkKey(); /** Returns the Channel in the Thread active operational dataset. */ public int getChannel(); /** Returns the Channel Page in the Thread active operational dataset. */ public int getChannelPage(); /** Returns the PAN ID in the Thread active operational dataset. */ public int getPanId(); /** Returns the Extended PAN ID in the Thread active operational dataset. */ @Size(LENGTH_EXTENDED_PANID) public byte[] getExtendedPanId(); /** Returns the network name if it is included in the Thread active operational dataset. */ public String getNetworkName(); /** Returns the PSKc in the Thread active operational dataset. */ @Size(LENGTH_PSKC) public byte[] getPskc(); /** Returns the Channel Masks in the Thread active operational dataset. */ public Set getChannelMasks(); /** Returns the Mesh-Local Prefix in the Thread active operational dataset. */ @Size(LENGTH_MESH_LOCAL_PREFIX) public byte[] getMeshLocalPrefix(); /** Returns the Security Policy in the Thread active operational dataset. */ public SecurityPolicy getSecurityPolicy(); /** Returns the Active Timestamp in the Thread active operational dataset. */ public Timestamp getActiveTimestamp(); /** * Returns the Unix epoch in Milliseconds the {@link ThreadNetworkCredentials} instance is created * at. Note that this is the time when the Thread network credentials is first added to the Thread * Network service via {@link ThreadNetworkClient#addCredentials}. Zero will be returned if this * instance is not returned by {@link ThreadNetworkClient} methods (e.g. created with {@link * ThreadNetworkCredentials#fromActiveOperationalDataset}). */ public long getCreatedAtMillis(); /** * Returns the Unix epoch in Milliseconds the {@link ThreadNetworkCredentials} instance is updated * at. Note that this is the time when the Thread network credentials was last added/updated to * the Thread Network service via {@link ThreadNetworkClient#addCredentials}. Zero will be * returned if this instance is not returned by {@link ThreadNetworkClient} methods (e.g. created * with {@link ThreadNetworkCredentials#fromActiveOperationalDataset}). */ public long getUpdatedAtMillis(); /** The Channel Mask Entry of Thread Operational Dataset. */ public static final class ChannelMaskEntry { /** * Creates a new {@link ChannelMaskEntry} object. * * @throws IllegalArgumentException if {@code page} exceeds range [0, 255]. */ public ChannelMaskEntry(@IntRange(from = 0, to = 255) int page, byte[] mask); /** Returns the Channel Page. */ public int getPage(); /** Returns the Channel Mask. */ public byte[] getMask(); } /** The timestamp of Thread Operational Dataset. */ public static final class Timestamp { /** * Creates a new {@link Timestamp} object with given parameters. * * @param seconds the value encodes a Unix Time value. Must be in the range of * 0x0-0xffffffffffffL. * @param ticks the value encodes the fractional Unix Time value in 32.768 kHz resolution. Must * be in the range of 0x0-0x7fff. * @param isAuthoritativeSource the flag indicates the time was obtained from an authoritative * source: either NTP (Network Time Protocol), GPS (Global Positioning System), cell * network, or other method. * @throws IllegalArgumentException if the {@code seconds} is not in range of * 0x0-0xffffffffffffL or {@code ticks} is not in range of 0x0-0x7fff. */ public Timestamp( @IntRange(from = 0, to = 0xffffffffffffL) long seconds, @IntRange(from = 0, to = 0x7fff) int ticks, boolean isAuthoritativeSource); /** Returns the seconds portion of the timestamp. */ public long getSeconds(); /** Returns the ticks portion of the timestamp. */ public int getTicks(); /** Indicates whether the timestamp comes from an authoritative source. */ public boolean isAuthoritativeSource(); } /** The class represents Thread Security Policy. */ public static final class SecurityPolicy { /** * Creates a new {@link SecurityPolicy} object. * * @param rotationTimeHours the value for Thread key rotation in hours. Must be in range of * 0x1-0xffff. * @param flags security policy flags with length of either 1 byte for Thread 1.1 or 2 bytes for * Thread 1.2 or higher. * @throws IllegalArgumentException if {@code rotationTimeHours} is not in range of 0x1-0xffff * or length of {@code flags} is smaller than {@link #LENGTH_MIN_SECURITY_POLICY_FLAGS}. */ public SecurityPolicy( @IntRange(from = 1, to = 0xffff) int rotationTimeHours, @Size(min = LENGTH_MIN_SECURITY_POLICY_FLAGS) byte[] flags); /** Returns the Security Policy Rotation Time in hours. */ public int getRotationTimeHours(); /** Returns 1 byte flags for Thread 1.1 or 2 bytes flags for Thread 1.2. */ @Size(min = LENGTH_MIN_SECURITY_POLICY_FLAGS) public byte[] getFlags(); } /** Builder for constructing {@link ThreadNetworkCredentials} instances. */ public static class Builder { /** * Sets Thread Channel and Channel Page. The default channel page is {@link * #CHANNEL_PAGE_2P4GHZ} and the channel is randomly selected in range [{@link * #CHANNEL_MIN_2P4GHZ}, {@link #CHANNEL_MAX_2P4GHZ}]. Other channel pages and associated * channel are undefined and may lead to undefined behavior if it's applied to Thread devices. * * @throws IllegalArgumentException if invalid channel is specified for the {@code channelPage}. */ public Builder setChannel( @IntRange(from = 0, to = 255) int channelPage, @IntRange(from = 1, to = 65535) int channel); /** * Sets the Thread Channel Masks. The default channel masks consist of {@link * #DEFAULT_CHANNEL_MASK} which enables all 2.4GHz channels. */ public Builder setChannelMasks(Set channelMasks); /** * Sets the Thread Active Timestamp. The default Active Timestamp is {@code Timestamp(seconds=1, * ticks=0, isAuthoritative=false)}. */ public Builder setActiveTimestamp(Timestamp activeTimestamp); /** * Sets the Thread Security Policy. The default Security Policy is {@link * #DEFAULT_SECURITY_POLICY}. */ public Builder setSecurityPolicy(SecurityPolicy securityPolicy); /** * Sets the Thread Network Mesh-Local Prefix. The default Mesh-Local Prefix is randomly * generated with a secure random generator. * *

It's discouraged to call this method to override the default value in production. * * @throws IllegalArgumentException if length of {@code meshLocalPrefix} isn't {@link * #LENGTH_MESH_LOCAL_PREFIX} or {@code meshLocalPrefix} doesn't start with 0xfd. */ public Builder setMeshLocalPrefix(@Size(LENGTH_MESH_LOCAL_PREFIX) byte[] meshLocalPrefix); /** * Sets the Thread Network Key. The default Network Key is randomly generated with a secure * random generator. * *

It's discouraged to call this method to override the default value in production. * * @throws IllegalArgumentException if length of {@code networkKey} is not {@link * #LENGTH_NETWORK_KEY}. */ public Builder setNetworkKey(@Size(LENGTH_NETWORK_KEY) byte[] networkKey); /** * Sets the Thread Extended PAN ID. The default Extended PAN ID is randomly generated. * *

It's discouraged to call this method to override the default value in production. * * @throws IllegalArgumentException if length of {@code extendedPanId} is not {@link * #LENGTH_EXTENDED_PANID}. */ public Builder setExtendedPanId(@Size(LENGTH_EXTENDED_PANID) byte[] extendedPanId); /** * Sets the Thread Network Name. The default Network Name is randomly generated. * * @throws IllegalArgumentException if length of {@code networkName} isn't in range of [{@link * #LENGTH_MIN_NETWORK_NAME}, {@link #LENGTH_MAX_NETWORK_NAME}]. */ public Builder setNetworkName( @Size(min = LENGTH_MIN_NETWORK_NAME, max = LENGTH_MAX_NETWORK_NAME) String networkName); /** * Sets the Thread PAN ID with valid value in range of 0x0-0xffff. The default PAN ID is * randomly generated. * *

It's discouraged to call this method to override the default value in production. * * @throws IllegalArgumentException if {@code panId} is not in range of 0x0-0xffff. */ public Builder setPanId(@IntRange(from = 0, to = 0xffff) int panId); /** * Sets the Thread PSKc. The default PSKc has length of 16 bytes and is randomly generated with * a secure random generator. * * @param pskc the pskc value derived from Extended PAN ID, Network Name and Commissioning * Credentials. * @throws IllegalArgumentException if length of {@code pskc} is not {@link #LENGTH_PSKC}. */ public Builder setPskc(byte[] pskc); /** * Constructs a {@link ThreadNetworkCredentials} as configured by this builder. * * @throws IllegalStateException if {@code networkKey} is missing. */ public ThreadNetworkCredentials build(); } }

ThreadBorderAgent

A Thread Border Agent object uniquely identifies a Border Agent / Router device. It consists of a 64-bit discriminator value for now but may include more descriptive information in the future.

Data interface for Thread Border Agent

/** Data interface for Thread Border Agent. */
public class ThreadBorderAgent {
  /**
   * Creates a new {@link ThreadBorderAgent.Builder} for constructing a {@link ThreadBorderAgent}.
   *
   * @param discriminator the discriminator which uniquely identifies the Border Agent/Router 
   *     device. It should be the IEEE 802.15.4 Extended Address of the device.
   */
  public static ThreadBorderAgent.Builder newBuilder(long discriminator);

  /** Returns the discriminator that uniquely identifies a Thread Border Agent device. */
  public long getDiscriminator();

  /** Builder for constructing {@link ThreadBorderAgent} instances. */
  public static final class Builder {
    /** Constructs a {@link ThreadBorderAgent} as configured by this builder. */
    public ThreadBorderAgent build();
  }
}

ThreadNetworkClient

A client for the Thread Network API

/** A client for the Thread Network API. */
public interface ThreadNetworkClient {
  /**
   * Returns a {@link Task} which asynchronously adds {@code networkCredential} to secure storage.
   * Existing Thread Network Credentials which is associated with the same {@code borderAgent} will
   * be overwritten.
   *
   * @param borderAgent the Thread Border Agent with which {@code networkCredential} is associated
   */
  Task addCredentials(
      ThreadBorderAgent borderAgent, ThreadNetworkCredentials networkCredential);

  /**
   * Returns a {@link Task} which asynchronously removes the Thread Network Credentials of {@code
   * borderAgent} from secure storage. Nothing will be done and the task will complete successfully
   * if there isn't an existing Thread Network Credentials.
   */
  Task removeCredentials(ThreadBorderAgent borderAgent);

  /**
   * Returns a {@link Task} which asynchronously gets a {@link IntentSender} for starting a consent
   * dialog to ask for sharing credentials of the preferred Thread network.
   *
   * 

The activity result will contain a {@link ThreadNetworkCredentials} if the user approved to * share the credentials. */ Task getPreferredCredentials(); /** * Returns true if the {@code credentials} represent the same Thread network as the preferred * network. This usually indicates the Thread device holds {@code credentials} are already in the * preferred Thread network. * *

Typical usage of this API is to check if a Thread Border Router is already in the preferred * network before calling {@link #getPreferredCredentials} which can pop up a consent dialog. */ Task isTheSameAsPreferredCredentials(ThreadNetworkCredentials credentials); /** * Returns a {@link Task} which asynchronously gets the Thread Network Credentials associated with * given {@code borderAgent}. This API will fail if the Thread Network Credentials was not added * by the current caller/app. */ Task getCredentialsByBorderAgent(ThreadBorderAgent borderAgent); /** * Returns a {@link Task} which asynchronously gets a {@link IntentSender} for starting a consent * dialog to ask for sharing credentials of Thread network with given {@code extendedPanId}. * *

The activity result will contain a {@link ThreadNetworkCredentials} if the user approved to * share the credentials. */ Task getCredentialsByExtendedPanId(byte[] extendedPanId); /** * Returns a {@link Task} which asynchronously gets all the Thread Network Credentials which were * added by the current caller/app. */ Task> getAllCredentials(); } /** * A wrapper of nullable {@link ThreadNetworkCredential} result returned by APIs of {@link * ThreadNetworkClient}. */ public final class ThreadNetworkCredentialResult { public ThreadNetworkCredentialResult(@Nullable ThreadNetworkCredential networkCredential); @Nullable public ThreadNetworkCredential getCredential(); }

Thread API snippets

An example of calling ThreadNetworkClient#getPreferredCredentials to get the preferred Thread network credentials can be:

A client for the Thread Network API

ThreadNetwork.getClient(this)
    .getPreferredCredentials()
    .addOnSuccessListener(
        intentSender ->
            registerForActivityResult(
                    new StartIntentSenderForResult(),
                    result -> {
                      if (result.getResultCode() == RESULT_OK) {
                        ThreadNetworkCredentials credentials =
                            ThreadNetworkCredentials.fromIntentSenderResultData(result.getData());
                        resultGet.setText(convertCredentials(credentials));
                      } else {
                        resultGet.setText("User denied to share!");
                      }
                    })
                .launch(new IntentSenderRequest.Builder(intentSender).build()))
    .addOnFailureListener(e -> resultGet.setText(e.getMessage()));

A consent dialog will be popped up in above code and the Thread credentials will be set to resultGet TextView if the user approves the consent dialog.

A Border Router Setup App can also create random Thread network credentials for a new Thread Border Router when the preferred Thread network credentials are missing:

ThreadNetworkCredentials threadCredentials =
    ThreadNetworkCredentials.newRandomizedBuilder()
        .setNetworkName("MyThreadNet")
        .build();

And then add the Thread network credentials to Google Play services with the ThreadNetworkClient#addCredentials:

ThreadNetwork.getClient(this)
    .addCredentials(borderAgent, threadCredentials)
    .addOnSuccessListener(status -> resultAdd.setText("success"))
    .addOnFailureListener(e -> resultAdd.setText(e.getMessage()));

Availability

The APIs will be released in the coming months to Google Play services. Please check back here on their availability in addition to sample apps that show their usage.