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 are sent using the sendPayload() method, and received in an implementation of PayloadCallback that's passed to acceptConnection() as described in Manage Connections .

Types of payloads

Bytes

Byte payloads are the simplest type of payloads. They are suitable for sending simple data like messages or metadata up to a maximum size of Connections.MAX_BYTES_DATA_SIZE . Here's an example of sending a BYTES payload:

 Payload 
  
 bytesPayload 
  
 = 
  
 Payload 
 . 
 fromBytes 
 ( 
 new 
  
 byte 
 [] 
  
 { 
 0xa 
 , 
  
 0xb 
 , 
  
 0xc 
 , 
  
 0xd 
 }); 
 Nearby 
 . 
 getConnectionsClient 
 ( 
 context 
 ) 
 . 
 sendPayload 
 ( 
 toEndpointId 
 , 
  
 bytesPayload 
 ); 

Receive a BYTES payload by implementing the onPayloadReceived() method of the PayloadCallback you passed to acceptConnection() .

 static 
  
 class 
  
 ReceiveBytesPayloadListener 
  
 extends 
  
 PayloadCallback 
  
 { 
  
 @ 
 Override 
  
 public 
  
 void 
  
 onPayloadReceived 
 ( 
 String 
  
 endpointId 
 , 
  
 Payload 
  
 payload 
 ) 
  
 { 
  
 // 
  
 This 
  
 always 
  
 gets 
  
 the 
  
 full 
  
 data 
  
 of 
  
 the 
  
 payload 
 . 
  
 Is 
  
 null 
  
 if 
  
 it's 
  
 not 
  
 a 
  
 BYTES 
  
 payload 
 . 
  
 if 
  
 ( 
 payload 
 . 
 getType 
 () 
  
 == 
  
 Payload 
 . 
 Type 
 . 
 BYTES 
 ) 
  
 { 
  
 byte 
 [] 
  
 receivedBytes 
  
 = 
  
 payload 
 . 
 asBytes 
 (); 
  
 } 
  
 } 
  
 @ 
 Override 
  
 public 
  
 void 
  
 onPayloadTransferUpdate 
 ( 
 String 
  
 endpointId 
 , 
  
 PayloadTransferUpdate 
  
 update 
 ) 
  
 { 
  
 // 
  
 Bytes 
  
 payloads 
  
 are 
  
 sent 
  
 as 
  
 a 
  
 single 
  
 chunk 
 , 
  
 so 
  
 you'll 
  
 receive 
  
 a 
  
 SUCCESS 
  
 update 
  
 immediately 
  
 // 
  
 after 
  
 the 
  
 call 
  
 to 
  
 onPayloadReceived 
 () 
 . 
  
 } 
 } 

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. Here's a simple example of sending a FILE payload:

 File 
  
 fileToSend 
  
 = 
  
 new 
  
 File 
 ( 
 context 
 . 
 getFilesDir 
 (), 
  
" hello 
 . 
 txt 
" ); 
 try 
  
 { 
  
 Payload 
  
 filePayload 
  
 = 
  
 Payload 
 . 
 fromFile 
 ( 
 fileToSend 
 ); 
  
 Nearby 
 . 
 getConnectionsClient 
 ( 
 context 
 ) 
 . 
 sendPayload 
 ( 
 toEndpointId 
 , 
  
 filePayload 
 ); 
 } 
  
 catch 
  
 ( 
 FileNotFoundException 
  
 e 
 ) 
  
 { 
  
 Log 
 . 
 e 
 ( 
" MyApp 
" , 
  
" File 
  
 not 
  
 found 
" , 
  
 e 
 ); 
 } 

It can be more efficient to use a ParcelFileDescriptor to create the FILES payload if one is available, for example from a ContentResolver . This minimizes copying of the file's bytes:

 ParcelFileDescriptor 
  
 pfd 
  
 = 
  
 getContentResolver 
 () 
 . 
 openFileDescriptor 
 ( 
 uri 
 , 
  
" r 
" ); 
 filePayload 
  
 = 
  
 Payload 
 . 
 fromFile 
 ( 
 pfd 
 ); 

When a file is received, it is saved in the Downloads folder ( DIRECTORY_DOWNLOADS ) 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 , you can retrieve the File object like so if your app is targeting < Q devices:

 File 
  
 payloadFile 
  
 = 
  
 filePayload 
 . 
 asFile 
 () 
 . 
 asJavaFile 
 (); 
 // 
  
 Rename 
  
 the 
  
 file 
 . 
 payloadFile 
 . 
 renameTo 
 ( 
 new 
  
 File 
 ( 
 payloadFile 
 . 
 getParentFile 
 (), 
  
 filename 
 )); 

If your app is targeting Q devices, you can add android:requestLegacyExternalStorage="true" in the application element of your manifest to continue using the previous code. Otherwise, for Q+ you will need to obey the Scoped Storage rules and access the received file using the uri passed from the service.

 // 
  
 Because 
  
 of 
  
 https 
 : 
 // 
 developer 
 . 
 android 
 . 
 com 
 / 
 preview 
 / 
 privacy 
 / 
 scoped 
 - 
 storage 
 , 
  
 we 
  
 are 
  
 not 
 // 
  
 allowed 
  
 to 
  
 access 
  
 filepaths 
  
 from 
  
 another 
  
 process 
  
 directly 
 . 
  
 Instead 
 , 
  
 we 
  
 must 
  
 open 
  
 the 
 // 
  
 uri 
  
 using 
  
 our 
  
 ContentResolver 
 . 
 Uri 
  
 uri 
  
 = 
  
 filePayload 
 . 
 asFile 
 () 
 . 
 asUri 
 (); 
 try 
  
 { 
  
 // 
  
 Copy 
  
 the 
  
 file 
  
 to 
  
 a 
  
 new 
  
 location 
 . 
  
 InputStream 
  
 in 
  
 = 
  
 context 
 . 
 getContentResolver 
 () 
 . 
 openInputStream 
 ( 
 uri 
 ); 
  
 copyStream 
 ( 
 in 
 , 
  
 new 
  
 FileOutputStream 
 ( 
 new 
  
 File 
 ( 
 context 
 . 
 getCacheDir 
 (), 
  
 filename 
 ))); 
 } 
  
 catch 
  
 ( 
 IOException 
  
 e 
 ) 
  
 { 
  
 // 
  
 Log 
  
 the 
  
 error 
 . 
 } 
  
 finally 
  
 { 
  
 // 
  
 Delete 
  
 the 
  
 original 
  
 file 
 . 
  
 context 
 . 
 getContentResolver 
 () 
 . 
 delete 
 ( 
 uri 
 , 
  
 null 
 , 
  
 null 
 ); 
 } 

In the following more complex example, the ACTION_OPEN_DOCUMENT intent prompts the user to choose a file and the file is efficiently sent as a payload using ParcelFileDescriptor . The file name is also sent as a BYTES payload.

 private 
  
 static 
  
 final 
  
 int 
  
 READ_REQUEST_CODE 
  
 = 
  
 42 
 ; 
 private 
  
 static 
  
 final 
  
 String 
  
 ENDPOINT_ID_EXTRA 
  
 = 
  
" com 
 . 
 foo 
 . 
 myapp 
 . 
 EndpointId 
" ; 
 /** 
  
 * 
  
 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 
 ) 
  
 { 
  
 super 
 . 
 onActivityResult 
 ( 
 requestCode 
 , 
  
 resultCode 
 , 
  
 resultData 
 ); 
  
 if 
  
 ( 
 requestCode 
  
 == 
  
 READ_REQUEST_CODE 
 && 
 resultCode 
  
 == 
  
 Activity 
 . 
 RESULT_OK 
 && 
 resultData 
  
 != 
  
 null 
 ) 
  
 { 
  
 String 
  
 endpointId 
  
 = 
  
 resultData 
 . 
 getStringExtra 
 ( 
 ENDPOINT_ID_EXTRA 
 ); 
  
 // 
  
 The 
  
 URI 
  
 of 
  
 the 
  
 file 
  
 selected 
  
 by 
  
 the 
  
 user 
 . 
  
 Uri 
  
 uri 
  
 = 
  
 resultData 
 . 
 getData 
 (); 
  
 Payload 
  
 filePayload 
 ; 
  
 try 
  
 { 
  
 // 
  
 Open 
  
 the 
  
 ParcelFileDescriptor 
  
 for 
  
 this 
  
 URI 
  
 with 
  
 read 
  
 access 
 . 
  
 ParcelFileDescriptor 
  
 pfd 
  
 = 
  
 getContentResolver 
 () 
 . 
 openFileDescriptor 
 ( 
 uri 
 , 
  
" r 
" ); 
  
 filePayload 
  
 = 
  
 Payload 
 . 
 fromFile 
 ( 
 pfd 
 ); 
  
 } 
  
 catch 
  
 ( 
 FileNotFoundException 
  
 e 
 ) 
  
 { 
  
 Log 
 . 
 e 
 ( 
" MyApp 
" , 
  
" File 
  
 not 
  
 found 
" , 
  
 e 
 ); 
  
 return 
 ; 
  
 } 
  
 // 
  
 Construct 
  
 a 
  
 simple 
  
 message 
  
 mapping 
  
 the 
  
 ID 
  
 of 
  
 the 
  
 file 
  
 payload 
  
 to 
  
 the 
  
 desired 
  
 filename 
 . 
  
 String 
  
 filenameMessage 
  
 = 
  
 filePayload 
 . 
 getId 
 () 
  
 + 
  
" : 
"  
 + 
  
 uri 
 . 
 getLastPathSegment 
 (); 
  
 // 
  
 Send 
  
 the 
  
 filename 
  
 message 
  
 as 
  
 a 
  
 bytes 
  
 payload 
 . 
  
 Payload 
  
 filenameBytesPayload 
  
 = 
  
 Payload 
 . 
 fromBytes 
 ( 
 filenameMessage 
 . 
 getBytes 
 ( 
 StandardCharsets 
 . 
 UTF_8 
 )); 
  
 Nearby 
 . 
 getConnectionsClient 
 ( 
 context 
 ) 
 . 
 sendPayload 
 ( 
 endpointId 
 , 
  
 filenameBytesPayload 
 ); 
  
 // 
  
 Finally 
 , 
  
 send 
  
 the 
  
 file 
  
 payload 
 . 
  
 Nearby 
 . 
 getConnectionsClient 
 ( 
 context 
 ) 
 . 
 sendPayload 
 ( 
 endpointId 
 , 
  
 filePayload 
 ); 
  
 } 
 } 

Since the filename was sent as a payload, our receiver can move or rename the file so that it has an appropriate extension:

 static 
  
 class 
  
 ReceiveFilePayloadCallback 
  
 extends 
  
 PayloadCallback 
  
 { 
  
 private 
  
 final 
  
 Context 
  
 context 
 ; 
  
 private 
  
 final 
  
 SimpleArrayMap<Long 
 , 
  
 Payload> 
  
 incomingFilePayloads 
  
 = 
  
 new 
  
 SimpleArrayMap 
<> (); 
  
 private 
  
 final 
  
 SimpleArrayMap<Long 
 , 
  
 Payload> 
  
 completedFilePayloads 
  
 = 
  
 new 
  
 SimpleArrayMap 
<> (); 
  
 private 
  
 final 
  
 SimpleArrayMap<Long 
 , 
  
 String> 
  
 filePayloadFilenames 
  
 = 
  
 new 
  
 SimpleArrayMap 
<> (); 
  
 public 
  
 ReceiveFilePayloadCallback 
 ( 
 Context 
  
 context 
 ) 
  
 { 
  
 this 
 . 
 context 
  
 = 
  
 context 
 ; 
  
 } 
  
 @ 
 Override 
  
 public 
  
 void 
  
 onPayloadReceived 
 ( 
 String 
  
 endpointId 
 , 
  
 Payload 
  
 payload 
 ) 
  
 { 
  
 if 
  
 ( 
 payload 
 . 
 getType 
 () 
  
 == 
  
 Payload 
 . 
 Type 
 . 
 BYTES 
 ) 
  
 { 
  
 String 
  
 payloadFilenameMessage 
  
 = 
  
 new 
  
 String 
 ( 
 payload 
 . 
 asBytes 
 (), 
  
 StandardCharsets 
 . 
 UTF_8 
 ); 
  
 long 
  
 payloadId 
  
 = 
  
 addPayloadFilename 
 ( 
 payloadFilenameMessage 
 ); 
  
 processFilePayload 
 ( 
 payloadId 
 ); 
  
 } 
  
 else 
  
 if 
  
 ( 
 payload 
 . 
 getType 
 () 
  
 == 
  
 Payload 
 . 
 Type 
 . 
 FILE 
 ) 
  
 { 
  
 // 
  
 Add 
  
 this 
  
 to 
  
 our 
  
 tracking 
  
 map 
 , 
  
 so 
  
 that 
  
 we 
  
 can 
  
 retrieve 
  
 the 
  
 payload 
  
 later 
 . 
  
 incomingFilePayloads 
 . 
 put 
 ( 
 payload 
 . 
 getId 
 (), 
  
 payload 
 ); 
  
 } 
  
 } 
  
 /** 
  
 * 
  
 Extracts 
  
 the 
  
 payloadId 
  
 and 
  
 filename 
  
 from 
  
 the 
  
 message 
  
 and 
  
 stores 
  
 it 
  
 in 
  
 the 
  
 * 
  
 filePayloadFilenames 
  
 map 
 . 
  
 The 
  
 format 
  
 is 
  
 payloadId 
 : 
 filename 
 . 
  
 */ 
  
 private 
  
 long 
  
 addPayloadFilename 
 ( 
 String 
  
 payloadFilenameMessage 
 ) 
  
 { 
  
 String 
 [] 
  
 parts 
  
 = 
  
 payloadFilenameMessage 
 . 
 split 
 (":"); 
  
 long 
  
 payloadId 
  
 = 
  
 Long 
 . 
 parseLong 
 ( 
 parts 
 [ 
 0 
 ]); 
  
 String 
  
 filename 
  
 = 
  
 parts 
 [ 
 1 
 ]; 
  
 filePayloadFilenames 
 . 
 put 
 ( 
 payloadId 
 , 
  
 filename 
 ); 
  
 return 
  
 payloadId 
 ; 
  
 } 
  
 private 
  
 void 
  
 processFilePayload 
 ( 
 long 
  
 payloadId 
 ) 
  
 { 
  
 // 
  
 BYTES 
  
 and 
  
 FILE 
  
 could 
  
 be 
  
 received 
  
 in 
  
 any 
  
 order 
 , 
  
 so 
  
 we 
  
 call 
  
 when 
  
 either 
  
 the 
  
 BYTES 
  
 or 
  
 the 
  
 FILE 
  
 // 
  
 payload 
  
 is 
  
 completely 
  
 received 
 . 
  
 The 
  
 file 
  
 payload 
  
 is 
  
 considered 
  
 complete 
  
 only 
  
 when 
  
 both 
  
 have 
  
 // 
  
 been 
  
 received 
 . 
  
 Payload 
  
 filePayload 
  
 = 
  
 completedFilePayloads 
 . 
 get 
 ( 
 payloadId 
 ); 
  
 String 
  
 filename 
  
 = 
  
 filePayloadFilenames 
 . 
 get 
 ( 
 payloadId 
 ); 
  
 if 
  
 ( 
 filePayload 
  
 != 
  
 null 
 && 
 filename 
  
 != 
  
 null 
 ) 
  
 { 
  
 completedFilePayloads 
 . 
 remove 
 ( 
 payloadId 
 ); 
  
 filePayloadFilenames 
 . 
 remove 
 ( 
 payloadId 
 ); 
  
 // 
  
 Get 
  
 the 
  
 received 
  
 file 
  
 ( 
 which 
  
 will 
  
 be 
  
 in 
  
 the 
  
 Downloads 
  
 folder 
 ) 
  
 // 
  
 Because 
  
 of 
  
 https 
 : 
 // 
 developer 
 . 
 android 
 . 
 com 
 / 
 preview 
 / 
 privacy 
 / 
 scoped 
 - 
 storage 
 , 
  
 we 
  
 are 
  
 not 
  
 // 
  
 allowed 
  
 to 
  
 access 
  
 filepaths 
  
 from 
  
 another 
  
 process 
  
 directly 
 . 
  
 Instead 
 , 
  
 we 
  
 must 
  
 open 
  
 the 
  
 // 
  
 uri 
  
 using 
  
 our 
  
 ContentResolver 
 . 
  
 Uri 
  
 uri 
  
 = 
  
 filePayload 
 . 
 asFile 
 () 
 . 
 asUri 
 (); 
  
 try 
  
 { 
  
 // 
  
 Copy 
  
 the 
  
 file 
  
 to 
  
 a 
  
 new 
  
 location 
 . 
  
 InputStream 
  
 in 
  
 = 
  
 context 
 . 
 getContentResolver 
 () 
 . 
 openInputStream 
 ( 
 uri 
 ); 
  
 copyStream 
 ( 
 in 
 , 
  
 new 
  
 FileOutputStream 
 ( 
 new 
  
 File 
 ( 
 context 
 . 
 getCacheDir 
 (), 
  
 filename 
 ))); 
  
 } 
  
 catch 
  
 ( 
 IOException 
  
 e 
 ) 
  
 { 
  
 // 
  
 Log 
  
 the 
  
 error 
 . 
  
 } 
  
 finally 
  
 { 
  
 // 
  
 Delete 
  
 the 
  
 original 
  
 file 
 . 
  
 context 
 . 
 getContentResolver 
 () 
 . 
 delete 
 ( 
 uri 
 , 
  
 null 
 , 
  
 null 
 ); 
  
 } 
  
 } 
  
 } 
  
 // 
  
 add 
  
 removed 
  
 tag 
  
 back 
  
 to 
  
 fix 
  
 b 
 / 
 183037922 
  
 private 
  
 void 
  
 processFilePayload2 
 ( 
 long 
  
 payloadId 
 ) 
  
 { 
  
 // 
  
 BYTES 
  
 and 
  
 FILE 
  
 could 
  
 be 
  
 received 
  
 in 
  
 any 
  
 order 
 , 
  
 so 
  
 we 
  
 call 
  
 when 
  
 either 
  
 the 
  
 BYTES 
  
 or 
  
 the 
  
 FILE 
  
 // 
  
 payload 
  
 is 
  
 completely 
  
 received 
 . 
  
 The 
  
 file 
  
 payload 
  
 is 
  
 considered 
  
 complete 
  
 only 
  
 when 
  
 both 
  
 have 
  
 // 
  
 been 
  
 received 
 . 
  
 Payload 
  
 filePayload 
  
 = 
  
 completedFilePayloads 
 . 
 get 
 ( 
 payloadId 
 ); 
  
 String 
  
 filename 
  
 = 
  
 filePayloadFilenames 
 . 
 get 
 ( 
 payloadId 
 ); 
  
 if 
  
 ( 
 filePayload 
  
 != 
  
 null 
 && 
 filename 
  
 != 
  
 null 
 ) 
  
 { 
  
 completedFilePayloads 
 . 
 remove 
 ( 
 payloadId 
 ); 
  
 filePayloadFilenames 
 . 
 remove 
 ( 
 payloadId 
 ); 
  
 // 
  
 Get 
  
 the 
  
 received 
  
 file 
  
 ( 
 which 
  
 will 
  
 be 
  
 in 
  
 the 
  
 Downloads 
  
 folder 
 ) 
  
 if 
  
 ( 
 VERSION 
 . 
 SDK_INT 
  
> = 
  
 VERSION_CODES 
 . 
 Q 
 ) 
  
 { 
  
 // 
  
 Because 
  
 of 
  
 https 
 : 
 // 
 developer 
 . 
 android 
 . 
 com 
 / 
 preview 
 / 
 privacy 
 / 
 scoped 
 - 
 storage 
 , 
  
 we 
  
 are 
  
 not 
  
 // 
  
 allowed 
  
 to 
  
 access 
  
 filepaths 
  
 from 
  
 another 
  
 process 
  
 directly 
 . 
  
 Instead 
 , 
  
 we 
  
 must 
  
 open 
  
 the 
  
 // 
  
 uri 
  
 using 
  
 our 
  
 ContentResolver 
 . 
  
 Uri 
  
 uri 
  
 = 
  
 filePayload 
 . 
 asFile 
 () 
 . 
 asUri 
 (); 
  
 try 
  
 { 
  
 // 
  
 Copy 
  
 the 
  
 file 
  
 to 
  
 a 
  
 new 
  
 location 
 . 
  
 InputStream 
  
 in 
  
 = 
  
 context 
 . 
 getContentResolver 
 () 
 . 
 openInputStream 
 ( 
 uri 
 ); 
  
 copyStream 
 ( 
 in 
 , 
  
 new 
  
 FileOutputStream 
 ( 
 new 
  
 File 
 ( 
 context 
 . 
 getCacheDir 
 (), 
  
 filename 
 ))); 
  
 } 
  
 catch 
  
 ( 
 IOException 
  
 e 
 ) 
  
 { 
  
 // 
  
 Log 
  
 the 
  
 error 
 . 
  
 } 
  
 finally 
  
 { 
  
 // 
  
 Delete 
  
 the 
  
 original 
  
 file 
 . 
  
 context 
 . 
 getContentResolver 
 () 
 . 
 delete 
 ( 
 uri 
 , 
  
 null 
 , 
  
 null 
 ); 
  
 } 
  
 } 
  
 else 
  
 { 
  
 File 
  
 payloadFile 
  
 = 
  
 filePayload 
 . 
 asFile 
 () 
 . 
 asJavaFile 
 (); 
  
 // 
  
 Rename 
  
 the 
  
 file 
 . 
  
 payloadFile 
 . 
 renameTo 
 ( 
 new 
  
 File 
 ( 
 payloadFile 
 . 
 getParentFile 
 (), 
  
 filename 
 )); 
  
 } 
  
 } 
  
 } 
  
 @ 
 Override 
  
 public 
  
 void 
  
 onPayloadTransferUpdate 
 ( 
 String 
  
 endpointId 
 , 
  
 PayloadTransferUpdate 
  
 update 
 ) 
  
 { 
  
 if 
  
 ( 
 update 
 . 
 getStatus 
 () 
  
 == 
  
 PayloadTransferUpdate 
 . 
 Status 
 . 
 SUCCESS 
 ) 
  
 { 
  
 long 
  
 payloadId 
  
 = 
  
 update 
 . 
 getPayloadId 
 (); 
  
 Payload 
  
 payload 
  
 = 
  
 incomingFilePayloads 
 . 
 remove 
 ( 
 payloadId 
 ); 
  
 completedFilePayloads 
 . 
 put 
 ( 
 payloadId 
 , 
  
 payload 
 ); 
  
 if 
  
 ( 
 payload 
 . 
 getType 
 () 
  
 == 
  
 Payload 
 . 
 Type 
 . 
 FILE 
 ) 
  
 { 
  
 processFilePayload 
 ( 
 payloadId 
 ); 
  
 } 
  
 } 
  
 } 
  
 /** 
  
 Copies 
  
 a 
  
 stream 
  
 from 
  
 one 
  
 location 
  
 to 
  
 another 
 . 
  
 */ 
  
 private 
  
 static 
  
 void 
  
 copyStream 
 ( 
 InputStream 
  
 in 
 , 
  
 OutputStream 
  
 out 
 ) 
  
 throws 
  
 IOException 
  
 { 
  
 try 
  
 { 
  
 byte 
 [] 
  
 buffer 
  
 = 
  
 new 
  
 byte 
 [ 
 1024 
 ]; 
  
 int 
  
 read 
 ; 
  
 while 
  
 (( 
 read 
  
 = 
  
 in 
 . 
 read 
 ( 
 buffer 
 )) 
  
 != 
  
 - 
 1 
 ) 
  
 { 
  
 out 
 . 
 write 
 ( 
 buffer 
 , 
  
 0 
 , 
  
 read 
 ); 
  
 } 
  
 out 
 . 
 flush 
 (); 
  
 } 
  
 finally 
  
 { 
  
 in 
 . 
 close 
 (); 
  
 out 
 . 
 close 
 (); 
  
 } 
  
 } 
 } 

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 . For example:

 URL 
  
 url 
  
 = 
  
 new 
  
 URL 
 ( 
" https 
 : 
 // 
 developers 
 . 
 google 
 . 
 com 
 / 
 nearby 
 / 
 connections 
 / 
 android 
 / 
 exchange 
 - 
 data 
" ); 
 Payload 
  
 streamPayload 
  
 = 
  
 Payload 
 . 
 fromStream 
 ( 
 url 
 . 
 openStream 
 ()); 
 Nearby 
 . 
 getConnectionsClient 
 ( 
 context 
 ) 
 . 
 sendPayload 
 ( 
 toEndpointId 
 , 
  
 streamPayload 
 ); 

On the recipient, call payload.asStream().asInputStream() or payload.asStream().asParcelFileDescriptor() in a successful onPayloadTransferUpdate callback:

 static 
  
 class 
  
 ReceiveStreamPayloadCallback 
  
 extends 
  
 PayloadCallback 
  
 { 
  
 private 
  
 final 
  
 SimpleArrayMap<Long 
 , 
  
 Thread> 
  
 backgroundThreads 
  
 = 
  
 new 
  
 SimpleArrayMap 
<> (); 
  
 private 
  
 static 
  
 final 
  
 long 
  
 READ_STREAM_IN_BG_TIMEOUT 
  
 = 
  
 5000 
 ; 
  
 @Override 
  
 public 
  
 void 
  
 onPayloadTransferUpdate 
 ( 
 String 
  
 endpointId 
 , 
  
 PayloadTransferUpdate 
  
 update 
 ) 
  
 { 
  
 if 
  
 ( 
 backgroundThreads 
 . 
 containsKey 
 ( 
 update 
 . 
 getPayloadId 
 ()) 
 && 
 update 
 . 
 getStatus 
 () 
  
 != 
  
 PayloadTransferUpdate 
 . 
 Status 
 . 
 IN_PROGRESS 
 ) 
  
 { 
  
 backgroundThreads 
 . 
 get 
 ( 
 update 
 . 
 getPayloadId 
 ()). 
 interrupt 
 (); 
  
 } 
  
 } 
  
 @Override 
  
 public 
  
 void 
  
 onPayloadReceived 
 ( 
 String 
  
 endpointId 
 , 
  
 Payload 
  
 payload 
 ) 
  
 { 
  
 if 
  
 ( 
 payload 
 . 
 getType 
 () 
  
 == 
  
 Payload 
 . 
 Type 
 . 
 STREAM 
 ) 
  
 { 
  
 // 
  
 Read 
  
 the 
  
 available 
  
 bytes 
  
 in 
  
 a 
  
 while 
  
 loop 
  
 to 
  
 free 
  
 the 
  
 stream 
  
 pipe 
  
 in 
  
 time 
 . 
  
 Otherwise 
 , 
  
 the 
  
 // 
  
 bytes 
  
 will 
  
 block 
  
 the 
  
 pipe 
  
 and 
  
 slow 
  
 down 
  
 the 
  
 throughput 
 . 
  
 Thread 
  
 backgroundThread 
  
 = 
  
 new 
  
 Thread 
 () 
  
 { 
  
 @Override 
  
 public 
  
 void 
  
 run 
 () 
  
 { 
  
 InputStream 
  
 inputStream 
  
 = 
  
 payload 
 . 
 asStream 
 (). 
 asInputStream 
 (); 
  
 long 
  
 lastRead 
  
 = 
  
 SystemClock 
 . 
 elapsedRealtime 
 (); 
  
 while 
  
 ( 
 ! 
 Thread 
 . 
 interrupted 
 ()) 
  
 { 
  
 if 
  
 (( 
 SystemClock 
 . 
 elapsedRealtime 
 () 
  
 - 
  
 lastRead 
 ) 
  
> = 
  
 READ_STREAM_IN_BG_TIMEOUT 
 ) 
  
 { 
  
 Log 
 . 
 e 
 ( 
" MyApp 
" , 
  
" Read 
  
 data 
  
 from 
  
 stream 
  
 but 
  
 timed 
  
 out 
 ."); 
  
 break 
 ; 
  
 } 
  
 try 
  
 { 
  
 int 
  
 availableBytes 
  
 = 
  
 inputStream 
 . 
 available 
 (); 
  
 if 
  
 ( 
 availableBytes 
 > 
 0 
 ) 
  
 { 
  
 byte 
 [] 
  
 bytes 
  
 = 
  
 new 
  
 byte 
 [ 
 availableBytes 
 ] 
 ; 
  
 if 
  
 ( 
 inputStream 
 . 
 read 
 ( 
 bytes 
 ) 
  
 == 
  
 availableBytes 
 ) 
  
 { 
  
 lastRead 
  
 = 
  
 SystemClock 
 . 
 elapsedRealtime 
 (); 
  
 // 
  
 Do 
  
 something 
  
 with 
  
 is 
  
 here 
 ... 
  
 } 
  
 } 
  
 else 
  
 { 
  
 // 
  
 Sleep 
  
 or 
  
 just 
  
 continue 
 . 
  
 } 
  
 } 
  
 catch 
  
 ( 
 IOException 
  
 e 
 ) 
  
 { 
  
 Log 
 . 
 e 
 ( 
" MyApp 
" , 
  
" Failed 
  
 to 
  
 read 
  
 bytes 
  
 from 
  
 InputStream 
 .", 
  
 e 
 ); 
  
 break 
 ; 
  
 } 
  
 // 
  
 try 
 - 
 catch 
  
 } 
  
 // 
  
 while 
  
 } 
  
 } 
 ; 
  
 backgroundThread 
 . 
 start 
 (); 
  
 backgroundThreads 
 . 
 put 
 ( 
 payload 
 . 
 getId 
 (), 
  
 backgroundThread 
 ); 
  
 } 
  
 } 
 } 

Ordering with multiple payloads

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.

Progress updates

The onPayloadTransferUpdate() method 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:

 class 
  
 ReceiveWithProgressCallback 
  
 extends 
  
 PayloadCallback 
  
 { 
  
 private 
  
 final 
  
 SimpleArrayMap<Long 
 , 
  
 NotificationCompat 
 . 
 Builder> 
  
 incomingPayloads 
  
 = 
  
 new 
  
 SimpleArrayMap 
<> (); 
  
 private 
  
 final 
  
 SimpleArrayMap<Long 
 , 
  
 NotificationCompat 
 . 
 Builder> 
  
 outgoingPayloads 
  
 = 
  
 new 
  
 SimpleArrayMap 
<> (); 
  
 NotificationManager 
  
 notificationManager 
  
 = 
  
 ( 
 NotificationManager 
 ) 
  
 context 
 . 
 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 
 , 
  
 /* 
 isIncoming 
 =*/ 
  
 false 
 ); 
  
 notificationManager 
 . 
 notify 
 (( 
 int 
 ) 
  
 payload 
 . 
 getId 
 (), 
  
 notification 
 . 
 build 
 ()); 
  
 // 
  
 Add 
  
 it 
  
 to 
  
 the 
  
 tracking 
  
 list 
  
 so 
  
 we 
  
 can 
  
 update 
  
 it 
 . 
  
 outgoingPayloads 
 . 
 put 
 ( 
 payload 
 . 
 getId 
 (), 
  
 notification 
 ); 
  
 } 
  
 private 
  
 NotificationCompat 
 . 
 Builder 
  
 buildNotification 
 ( 
 Payload 
  
 payload 
 , 
  
 boolean 
  
 isIncoming 
 ) 
  
 { 
  
 NotificationCompat 
 . 
 Builder 
  
 notification 
  
 = 
  
 new 
  
 NotificationCompat 
 . 
 Builder 
 ( 
 context 
 ) 
  
 . 
 setContentTitle 
 ( 
 isIncoming 
  
 ? 
  
" Receiving 
 ... 
"  
 : 
  
" Sending 
 ... 
" ); 
  
 boolean 
  
 indeterminate 
  
 = 
  
 false 
 ; 
  
 if 
  
 ( 
 payload 
 . 
 getType 
 () 
  
 == 
  
 Payload 
 . 
 Type 
 . 
 STREAM 
 ) 
  
 { 
  
 // 
  
 We 
  
 can 
  
 only 
  
 show 
  
 indeterminate 
  
 progress 
  
 for 
  
 stream 
  
 payloads 
 . 
  
 indeterminate 
  
 = 
  
 true 
 ; 
  
 } 
  
 notification 
 . 
 setProgress 
 ( 
 100 
 , 
  
 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 
 */ 
 ); 
  
 notificationManager 
 . 
 notify 
 (( 
 int 
 ) 
  
 payload 
 . 
 getId 
 (), 
  
 notification 
 . 
 build 
 ()); 
  
 // 
  
 Add 
  
 it 
  
 to 
  
 the 
  
 tracking 
  
 list 
  
 so 
  
 we 
  
 can 
  
 update 
  
 it 
 . 
  
 incomingPayloads 
 . 
 put 
 ( 
 payload 
 . 
 getId 
 (), 
  
 notification 
 ); 
  
 } 
  
 @ 
 Override 
  
 public 
  
 void 
  
 onPayloadTransferUpdate 
 ( 
 String 
  
 endpointId 
 , 
  
 PayloadTransferUpdate 
  
 update 
 ) 
  
 { 
  
 long 
  
 payloadId 
  
 = 
  
 update 
 . 
 getPayloadId 
 (); 
  
 NotificationCompat 
 . 
 Builder 
  
 notification 
  
 = 
  
 null 
 ; 
  
 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 
 ); 
  
 } 
  
 } 
  
 if 
  
 ( 
 notification 
  
 == 
  
 null 
 ) 
  
 { 
  
 return 
 ; 
  
 } 
  
 switch 
  
 ( 
 update 
 . 
 getStatus 
 ()) 
  
 { 
  
 case 
  
 PayloadTransferUpdate 
 . 
 Status 
 . 
 IN_PROGRESS 
 : 
  
 long 
  
 size 
  
 = 
  
 update 
 . 
 getTotalBytes 
 (); 
  
 if 
  
 ( 
 size 
  
 == 
  
 - 
 1 
 ) 
  
 { 
  
 // 
  
 This 
  
 is 
  
 a 
  
 stream 
  
 payload 
 , 
  
 so 
  
 we 
  
 don't 
  
 need 
  
 to 
  
 update 
  
 anything 
  
 at 
  
 this 
  
 point 
 . 
  
 return 
 ; 
  
 } 
  
 int 
  
 percentTransferred 
  
 = 
  
 ( 
 int 
 ) 
  
 ( 
 100.0 
  
 * 
  
 ( 
 update 
 . 
 getBytesTransferred 
 () 
  
 / 
  
 ( 
 double 
 ) 
  
 update 
 . 
 getTotalBytes 
 ())); 
  
 notification 
 . 
 setProgress 
 ( 
 100 
 , 
  
 percentTransferred 
 , 
  
 /* 
  
 indeterminate 
 = 
  
 */ 
  
 false 
 ); 
  
 break 
 ; 
  
 case 
  
 PayloadTransferUpdate 
 . 
 Status 
 . 
 SUCCESS 
 : 
  
 // 
  
 SUCCESS 
  
 always 
  
 means 
  
 that 
  
 we 
  
 transferred 
  
 100 
 %. 
  
 notification 
  
 . 
 setProgress 
 ( 
 100 
 , 
  
 100 
 , 
  
 /* 
  
 indeterminate 
 = 
  
 */ 
  
 false 
 ) 
  
 . 
 setContentText 
 ( 
" Transfer 
  
 complete 
 ! 
" ); 
  
 break 
 ; 
  
 case 
  
 PayloadTransferUpdate 
 . 
 Status 
 . 
 FAILURE 
 : 
  
 case 
  
 PayloadTransferUpdate 
 . 
 Status 
 . 
 CANCELED 
 : 
  
 notification 
 . 
 setProgress 
 ( 
 0 
 , 
  
 0 
 , 
  
 false 
 ) 
 . 
 setContentText 
 ( 
" Transfer 
  
 failed 
" ); 
  
 break 
 ; 
  
 default 
 : 
  
 // 
  
 Unknown 
  
 status 
 . 
  
 } 
  
 notificationManager 
 . 
 notify 
 (( 
 int 
 ) 
  
 payloadId 
 , 
  
 notification 
 . 
 build 
 ()); 
  
 } 
 } 
Create a Mobile Website
View Site in Mobile | Classic
Share by: