Exchange Data

Once connections are established between devices, you can exchange data by sending and receiving Payload objects. A Payload can represent a simple byte array, such as a short text message; a file, such as a photo or video; or a stream, such as the audio stream from the device's microphone.

Payloads of the same type are guaranteed to arrive in the order they were sent, but there is no guarantee of preserving the ordering amongst payloads of different types. For example, if a sender sends a FILE payload followed by a BYTE payload, the receiver could get the BYTE payload first, followed by the FILE payload.

Send and Receive

To send payloads to a connected endpoint, call sendPayload().

To receive payloads, implement the onPayloadReceived() method of the PayloadCallback that was passed to acceptConnection().

Progress Updates

The PayloadCallback also has an onPayloadTransferUpdate() method which provides updates about the progress of both incoming and outgoing payloads. In both cases, this in an opportunity to display the progress of the transfer to the user, such as with a progress bar. For incoming payloads, updates also indicate when new data has been received.

The following sample code demonstrates one way to display the progress of incoming and outgoing payloads via notifications:

private final SimpleArrayMap<long, NotificationCompat.Builder> incomingPayloads = new SimpleArrayMap<>();
private final SimpleArrayMap<long, NotificationCompat.Builder> outgoingPayloads = new SimpleArrayMap<>();
...
mNotificationManager =  (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
...

private void sendPayload(String endpointId, Payload payload) {
  if (payload.getType() == Payload.Type.BYTES) {
    // No need to track progress for bytes.
    return;
  }

  // Build and start showing the notification.
  NotificationCompat.Builder notification = buildNotification(payload, false /*isIncoming*/);
  mNotificationManager.notify((int) payload.getId(), notification.build());

  // Add it to the tracking list so we can update it.
  outgoingPayloads.add(payload.getId(), notification);
}

private NotificationCompat.Builder buildNotification(Payload payload, boolean isIncoming) {
  NotificationCompat.Builder notification = new NotificationCompat.Builder(this);
      .setContentTitle(isIncoming ? "Receiving..." : "Sending...");
  int size = payload.getSize();
  boolean indeterminate = false;
  if (size == -1) {
    // This is a stream payload, so we don't know the size ahead of time.
    size = 100;
    indeterminate = true;
  }
  notification.setProgress(size, 0, indeterminate);
  return notification;
}

@Override
public void onPayloadReceived(String endpointId, Payload payload) {
  if (payload.getType() == Payload.Type.BYTES) {
    // No need to track progress for bytes.
    return;
  }

  // Build and start showing the notification.
  NotificationCompat.Builder notification = buildNotification(payload, true /*isIncoming*/);
  mNotificationManager.notify((int) payload.getId(), notification.build());

  // Add it to the tracking list so we can update it.
  incomingPayloads.add(payload.getId(), notification);
}

@Override
public void onPayloadTransferUpdate(long payloadId, PayloadTransferUpdate update) {
  NotificationCompat.Builder notification;
  if (incomingPayloads.containsKey(payloadId)) {
    notification = incomingPayloads.get(payloadId);
    if (update.getStatus() != PayloadTransferUpdate.Status.IN_PROGRESS) {
      // This is the last update, so we no longer need to keep track of this notification.
      incomingPayloads.remove(payloadId);
    }
  } else if (outgoingPayloads.containsKey(payloadId)) {
    notification = outgoingPayloads.get(payloadId);
    if (update.getStatus() != PayloadTransferUpdate.Status.IN_PROGRESS) {
      // This is the last update, so we no longer need to keep track of this notification.
      outgoingPayloads.remove(payloadId);
    }
  }

  switch(update.getStatus()) {
    case PayloadTransferUpdate.Status.IN_PROGRESS:
      int size = update.getTotalBytes();
      if (size == -1) {
        // This is a stream payload, so we don't need to update anything at this point.
        return;
      }
      notification.setProgress(size, update.getBytesTransferred(), false /* indeterminate */);
      break;
    case PayloadTransferUpdate.Status.SUCCESS:
      // SUCCESS always means that we transferred 100%.
      notification
          .setProgress(100, 100, false /* indeterminate */)
          .setContentText("Transfer complete!");
      break;
    case PayloadTransferUpdate.Status.ERROR:
      notification
          .setProgress(0, 0, false)
          .setContentText("Transfer failed");
      break;
  }

  mNotificationManager.notify((int) payloadId, notification.build());
}

Types of Payloads

Bytes

Byte payloads are the simplest type of payloads. They are suitable for sending simple data like messages or metadata. Create a BYTE Payload by calling Payload.fromBytes(byte[] bytes), where bytes is a byte array with a maximum size of Connections.MAX_BYTES_DATA_SIZE. On the recipient, call payload.asBytes() to get the byte array that makes up the payload.

Unlike FILE and STREAM payloads, BYTES payloads are sent as a single chunk, so there is no need to wait for the SUCCESS update (although it will still be delivered, immediately after the call to onPayloadReceived()). Instead, you can safely call payload.asBytes() to get the full data of the payload as soon as onPayloadReceived() is called.

File

File payloads are created from a file stored on the local device, such as a photo or video file. Create a FILE Payload by calling Payload.fromFile(), passing in either a java.io.File or a ParcelFileDescriptor. On the recipient, call payload.asFile().asJavaFile() or payload.asFile().asParcelFileDescriptor().

When a file is received, it is saved in the Downloads folder on the recipient's device with a generic name and no extension. Once the transfer has completed, indicated by a call to onPayloadTransferUpdate() with PayloadTransferUpdate.Status.SUCCESS, your app should move or rename the file with an appropriate extension. The sender can provide the filename to the recipient by sending it out-of-band as a bytes payload.

The following sample code shows how to let the user choose an image on the device, send the filename, and then send the image as a file payload:

private static final String ENDPOINT_ID_EXTRA = "endpointId";
private static final int READ_REQUEST_CODE = 42;

/**
 * Fires an intent to spin up the file chooser UI and select an image for
 * sending to endpointId.
 */
private void showImageChooser(String endpointId) {
  Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
  intent.addCategory(Intent.CATEGORY_OPENABLE);
  intent.setType("image/*");
  intent.putExtra(ENDPOINT_ID_EXTRA, endpointId);
  startActivityForResult(intent, READ_REQUEST_CODE);
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
  if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
    if (resultData != null) {
      String endpointId = intent.getStringExtra(ENDPOINT_ID_EXTRA);

      // The URI of the file selected by the user.
      Uri uri = resultData.getData();

      // Open the ParcelFileDescriptor for this URI with read access.
      ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(uri, "r");
      Payload filePayload = Payload.fromFile(pfd);

      // Construct a simple message mapping the ID of the file payload to the desired filename.
      String payloadFilenameMessage = filePayload.getId() + ":" + uri.getLastPathSegment();

      // Send this message as a bytes payload.
      Nearby.getConnections(context).sendPayload(
          endpointId, Payload.fromBytes(payloadFilenameMessage.getBytes("UTF-8")));

      // Finally, send the file payload.
      Nearby.getConnections(context).sendPayload(endpointId, filePayload);
    }
  }
}

This snippet shows how to receive and rename the file:

private final SimpleArrayMap<long, Payload> incomingPayloads = new SimpleArrayMap<>();
private final SimpleArrayMap<long, String> filePayloadFilenames = new SimpleArrayMap<>();

@Override
public void onPayloadReceived(String endpointId, Payload payload) {
  if (payload.getType() == Payload.Type.BYTES) {
    String payloadFilenameMessage = new String(payload.asBytes(), "UTF-8");
    addPayloadFilename(payloadFilenameMessage);
  } else if (payload.getType() == Payload.Type.FILE) {
    // Add this to our tracking map, so that we can retrieve the payload later.
    incomingPayloads.add(payload.getId(), payload);
  }
}

/**
 * Extracts the payloadId and filename from the message and stores it in the
 * filePayloadFilenames map. The format is payloadId:filename.
 */
private void addPayloadFilename(String payloadFilenameMessage) {
  int colonIndex = payloadFilenameMessage.indexOf(`:`);
  String payloadId = payloadFilenameMessage.substring(0, colonIndex);
  String filename = payloadFilenameMessage.substring(colonIndex + 1);
  filePayloadFilenames.add(payloadId, filename);
}

@Override
public void onPayloadTransferUpdate(long payloadId, PayloadTransferUpdate update) {
  if (update.getStatus() == PayloadTransferUpdate.Status.SUCCESS) {
    Payload payload = incomingPayloads.remove(payloadId);
    if (payload.getType() == Payload.Type.FILE) {
      // Retrieve the filename that was received in a bytes payload.
      String newFilename = filePayloadFilenames.remove(payloadId);

      File payloadFile = payload.asFile().asJavaFile();

      // Rename the file.
      payloadFile.renameTo(new File(payloadFile.getParentFile(), newFilename));
    }
  }
}

Stream

Stream payloads are suitable when you want to send large amounts of data that is generated on the fly, such as an audio stream. Create a STREAM Payload by calling Payload.fromStream(), passing in either an InputStream or a ParcelFileDescriptor. On the recipient, call payload.asStream().asInputStream() or payload.asStream().asParcelFileDescriptor().