Panoramica di Wi-Fi Direct (peer-to-peer o P2P)

Wi-Fi Direct (P2P) consente ai dispositivi con l'hardware appropriato di connettersi direttamente tra loro tramite Wi-Fi senza un punto di accesso intermedio. Utilizzando queste API, puoi scoprire e connetterti ad altri dispositivi quando ciascun dispositivo supporta il Wi-Fi P2P, quindi comunicare tramite una connessione veloce su distanze molto più lunghe di una connessione Bluetooth. Ciò è utile per le applicazioni che condividono dati tra gli utenti, come un gioco multigiocatore o un'applicazione di condivisione di foto.

Le API Wi-Fi P2P sono costituite dalle seguenti parti principali:

  • Metodi che consentono di scoprire, richiedere e connettersi ai peer, definiti nella classe WifiP2pManager.
  • Listener che ti consentono di ricevere una notifica in caso di esito positivo o negativo delle chiamate al metodo WifiP2pManager. Quando chiami i metodi WifiP2pManager, ogni metodo può ricevere un listener specifico passato come parametro.
  • Intent che ti avvisano di eventi specifici rilevati dal framework Wi-Fi P2P, ad esempio una connessione interrotta o un peer appena scoperto.

Spesso utilizzerai insieme questi tre componenti principali delle API. Ad esempio, puoi fornire un WifiP2pManager.ActionListener a una chiamata a discoverPeers() in modo che ActionListener.onSuccess() e ActionListener.onFailure() possano inviarti notifiche. Un intent WIFI_P2P_PEERS_CHANGED_ACTION viene trasmesso anche se il metodo discoverPeers() rileva che l'elenco dei peer è cambiato.

Panoramica dell'API

La classe WifiP2pManager fornisce metodi per interagire con l'hardware Wi-Fi del dispositivo per eseguire operazioni come rilevare e connettersi a peer. Sono disponibili le seguenti azioni:

Tabella 1. Metodi P2P Wi-Fi

Metodo Descrizione
initialize() Registra l'applicazione con il framework Wi-Fi. Chiama questo metodo prima di chiamare qualsiasi altro metodo Wi-Fi P2P.
connect() Avvia una connessione peer-to-peer con un dispositivo con la configurazione specificata.
cancelConnect() Annulla qualsiasi negoziazione di gruppo peer-to-peer in corso.
requestConnectInfo() Richiede le informazioni di connessione di un dispositivo.
createGroup() Crea un gruppo peer-to-peer con il dispositivo attuale come proprietario del gruppo.
removeGroup() Rimuove il gruppo peer-to-peer corrente.
requestGroupInfo() Richiede informazioni sul gruppo peer-to-peer.
discoverPeers() Avvia il rilevamento dei peer.
requestPeers() Richiede l'elenco attuale dei peer rilevati.

I metodi WifiP2pManager ti consentono di passare un listener, in modo che il framework Wi-Fi P2P possa notificare alla tua attività lo stato di una chiamata. Le interfacce listener disponibili e le chiamate al metodo WifiP2pManager corrispondenti che utilizzano i listener sono descritte nella tabella 2.

Tabella 2. Wi-Fi P2P Listeners

Interfaccia dell'ascoltatore Azioni associate
WifiP2pManager.ActionListener connect(), cancelConnect(), createGroup(), removeGroup() e discoverPeers()
WifiP2pManager.ChannelListener initialize()
WifiP2pManager.ConnectionInfoListener requestConnectInfo()
WifiP2pManager.GroupInfoListener requestGroupInfo()
WifiP2pManager.PeerListListener requestPeers()

Le API Wi-Fi P2P definiscono intent trasmessi quando si verificano determinati eventi Wi-Fi P2P, ad esempio quando viene rilevato un nuovo peer o quando lo stato del Wi-Fi di un dispositivo cambia. Puoi registrarti per ricevere questi intent nella tua applicazione creando un ricevitore di trasmissione che gestisce questi intent:

Tabella 3. Intent P2P Wi-Fi

Intenzione Descrizione
WIFI_P2P_CONNECTION_CHANGED_ACTION Trasmissione quando cambia lo stato della connessione Wi-Fi del dispositivo.
WIFI_P2P_PEERS_CHANGED_ACTION Trasmetti quando chiami il numero discoverPeers(). In genere, chiami requestPeers() per ottenere un elenco aggiornato di peer se gestisci questo intent nella tua applicazione.
WIFI_P2P_STATE_CHANGED_ACTION Trasmette quando il Wi-Fi P2P è attivato o disattivato sul dispositivo.
WIFI_P2P_THIS_DEVICE_CHANGED_ACTION Trasmetti quando i dettagli di un dispositivo sono stati modificati, ad esempio il nome del dispositivo.

Crea un ricevitore di trasmissione per gli intent Wi-Fi P2P

Un broadcast receiver consente di ricevere intent trasmessi dal sistema Android, in modo che l'applicazione possa rispondere agli eventi di tuo interesse. I passaggi di base per creare un broadcast receiver per gestire gli intent Wi-Fi P2P sono i seguenti:

  1. Crea una classe che estenda la classe BroadcastReceiver. Per il costruttore della classe, utilizzerai i parametri per WifiP2pManager, WifiP2pManager.Channel e l'attività in cui verrà registrato questo ricevitore di trasmissione. In questo modo, il ricevitore di trasmissione può inviare aggiornamenti all'attività e accedere all'hardware Wi-Fi e a un canale di comunicazione, se necessario.

  2. Nel ricevitore di trasmissione, controlla gli intent che ti interessano nel metodo onReceive(). Esegui le azioni necessarie in base all'intent ricevuto. Ad esempio, se il ricevitore di trasmissione riceve un intent WIFI_P2P_PEERS_CHANGED_ACTION, puoi chiamare il metodo requestPeers() per ottenere un elenco dei peer attualmente rilevati.

Il seguente codice mostra come creare un tipico ricevitore di trasmissione. Il ricevitore di trasmissione accetta un oggetto WifiP2pManager e un'attività come argomenti e utilizza queste due classi per eseguire in modo appropriato le azioni necessarie quando il ricevitore di trasmissione riceve un intent:

Kotlin

/**
* A BroadcastReceiver that notifies of important Wi-Fi p2p events.
*/
class WiFiDirectBroadcastReceiver(
       private val manager: WifiP2pManager,
       private val channel: WifiP2pManager.Channel,
       private val activity: MyWifiActivity
) : BroadcastReceiver() {

   override fun onReceive(context: Context, intent: Intent) {
       val action: String = intent.action
       when (action) {
           WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION -> {
               // Check to see if Wi-Fi is enabled and notify appropriate activity
           }
           WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> {
               // Call WifiP2pManager.requestPeers() to get a list of current peers
           }
           WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> {
               // Respond to new connection or disconnections
           }
           WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION -> {
               // Respond to this device's wifi state changing
           }
       }
   }
}

Java

/**
* A BroadcastReceiver that notifies of important Wi-Fi p2p events.
*/
public class WiFiDirectBroadcastReceiver extends BroadcastReceiver {

   private WifiP2pManager manager;
   private Channel channel;
   private MyWiFiActivity activity;

   public WiFiDirectBroadcastReceiver(WifiP2pManager manager, Channel channel,
           MyWifiActivity activity) {
       super();
       this.manager = manager;
       this.channel = channel;
       this.activity = activity;
   }

   @Override
   public void onReceive(Context context, Intent intent) {
       String action = intent.getAction();

       if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
           // Check to see if Wi-Fi is enabled and notify appropriate activity
       } else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {
           // Call WifiP2pManager.requestPeers() to get a list of current peers
       } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {
           // Respond to new connection or disconnections
       } else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) {
           // Respond to this device's wifi state changing
       }
   }
}

Sui dispositivi con Android 10 e versioni successive, i seguenti intent di trasmissione non sono permanenti:

WIFI_P2P_CONNECTION_CHANGED_ACTION
Le applicazioni possono utilizzare requestConnectionInfo(), requestNetworkInfo(), o requestGroupInfo() per recuperare le informazioni di connessione correnti.
WIFI_P2P_THIS_DEVICE_CHANGED_ACTION
Le applicazioni possono utilizzare requestDeviceInfo() per recuperare le informazioni di connessione correnti.

Creare un'applicazione Wi-Fi P2P

La creazione di un'applicazione Wi-Fi P2P prevede la creazione e la registrazione di un ricevitore di trasmissione per l'applicazione, l'individuazione dei peer, la connessione a un peer e il trasferimento dei dati a un peer. Le sezioni seguenti descrivono come procedere.

Configurazione iniziale

Prima di utilizzare le API Wi-Fi P2P, devi assicurarti che la tua applicazione possa accedere all'hardware e che il dispositivo supporti il protocollo Wi-Fi P2P. Se il Wi-Fi P2P è supportato, puoi ottenere un'istanza di WifiP2pManager, creare e registrare il ricevitore di trasmissione e iniziare a utilizzare le API Wi-Fi P2P.

  1. Richiedi l'autorizzazione per utilizzare l'hardware Wi-Fi sul dispositivo e dichiara che la tua applicazione ha la versione minima corretta dell'SDK nel manifest Android:

    <uses-sdk android:minSdkVersion="14" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <!-- If your app targets Android 13 (API level 33)
         or higher, you must declare the NEARBY_WIFI_DEVICES permission. -->
    <uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES"
                     <!-- If your app derives location information from
                          Wi-Fi APIs, don't include the "usesPermissionFlags"
                          attribute. -->
                     android:usesPermissionFlags="neverForLocation" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
                     <!-- If any feature in your app relies on precise location
                          information, don't include the "maxSdkVersion"
                          attribute. -->
                     android:maxSdkVersion="32" />
    

    Oltre alle autorizzazioni precedenti, le seguenti API richiedono anche l'attivazione della modalità di localizzazione:

  2. Controlla se il Wi-Fi P2P è attivo e supportato. Un buon punto per controllare questo aspetto è il ricevitore di trasmissione quando riceve l'intent WIFI_P2P_STATE_CHANGED_ACTION. Notifica alla tua attività lo stato del Wi-Fi P2P e reagisci di conseguenza:

    Kotlin

    override fun onReceive(context: Context, intent: Intent) {
    ...
    val action: String = intent.action
    when (action) {
       WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION -> {
           val state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1)
           when (state) {
               WifiP2pManager.WIFI_P2P_STATE_ENABLED -> {
                   // Wifi P2P is enabled
               }
               else -> {
                   // Wi-Fi P2P is not enabled
               }
           }
       }
    }
    ...
    }

    Java

    @Override
    public void onReceive(Context context, Intent intent) {
    ...
    String action = intent.getAction();
    if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
       int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1);
       if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) {
           // Wifi P2P is enabled
       } else {
           // Wi-Fi P2P is not enabled
       }
    }
    ...
    }
  3. Nel metodo onCreate() della tua attività, ottieni un'istanza di WifiP2pManager e registra la tua applicazione con il framework Wi-Fi P2P chiamando initialize(). Questo metodo restituisce un WifiP2pManager.Channel, che viene utilizzato per connettere l'applicazione al framework Wi-Fi P2P. Devi anche creare un'istanza del ricevitore di trasmissione con gli oggetti WifiP2pManager e WifiP2pManager.Channel insieme a un riferimento alla tua attività. In questo modo, il ricevitore di trasmissione può notificare alla tua attività eventi interessanti e aggiornarla di conseguenza. Ti consente anche di manipolare lo stato del Wi-Fi del dispositivo, se necessario:

    Kotlin

    val manager: WifiP2pManager? by lazy(LazyThreadSafetyMode.NONE) {
       getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager?
    }
    
    var channel: WifiP2pManager.Channel? = null
    var receiver: BroadcastReceiver? = null
    
    override fun onCreate(savedInstanceState: Bundle?) {
       ...
    
       channel = manager?.initialize(this, mainLooper, null)
       channel?.also { channel ->
           receiver = WiFiDirectBroadcastReceiver(manager, channel, this)
       }
    }

    Java

    WifiP2pManager manager;
    Channel channel;
    BroadcastReceiver receiver;
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState){
       ...
       manager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
       channel = manager.initialize(this, getMainLooper(), null);
       receiver = new WiFiDirectBroadcastReceiver(manager, channel, this);
       ...
    }
  4. Crea un filtro per intent e aggiungi gli stessi intent che il ricevitore di trasmissione controlla:

    Kotlin

    val intentFilter = IntentFilter().apply {
       addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)
       addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)
       addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)
       addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION)
    }

    Java

    IntentFilter intentFilter;
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState){
       ...
       intentFilter = new IntentFilter();
       intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
       intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
       intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
       intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
       ...
    }
  5. Registra il ricevitore di trasmissione nel metodo onResume() della tua attività e annullane la registrazione nel metodo onPause() della tua attività:

    Kotlin

    /* register the broadcast receiver with the intent values to be matched */
    override fun onResume() {
       super.onResume()
       receiver?.also { receiver ->
           registerReceiver(receiver, intentFilter)
       }
    }
    
    /* unregister the broadcast receiver */
    override fun onPause() {
       super.onPause()
       receiver?.also { receiver ->
           unregisterReceiver(receiver)
       }
    }

    Java

    /* register the broadcast receiver with the intent values to be matched */
    @Override
    protected void onResume() {
       super.onResume();
       registerReceiver(receiver, intentFilter);
    }
    /* unregister the broadcast receiver */
    @Override
    protected void onPause() {
       super.onPause();
       unregisterReceiver(receiver);
    }
  6. Dopo aver ottenuto un WifiP2pManager.Channel e configurato un ricevitore di trasmissione, la tua applicazione può effettuare chiamate di metodi Wi-Fi P2P e ricevere intent Wi-Fi P2P.

  7. Implementa la tua applicazione utilizzando le funzionalità Wi-Fi P2P chiamando i metodi in WifiP2pManager.

Le sezioni successive descrivono come eseguire azioni comuni come la scoperta e la connessione ai peer.

Scopri i peer

Chiama discoverPeers() per rilevare i peer disponibili nel raggio d'azione e disponibili per la connessione. La chiamata a questa funzione è asincrona e l'esito positivo o negativo viene comunicato alla tua applicazione con onSuccess() e onFailure() se hai creato un WifiP2pManager.ActionListener. Il metodo onSuccess() ti avvisa solo che la procedura di rilevamento è riuscita e non fornisce informazioni sui peer effettivi che ha rilevato, se presenti. Il seguente esempio di codice mostra come configurarlo.

Kotlin

manager?.discoverPeers(channel, object : WifiP2pManager.ActionListener {

   override fun onSuccess() {
       ...
   }

   override fun onFailure(reasonCode: Int) {
       ...
   }
})

Java

manager.discoverPeers(channel, new WifiP2pManager.ActionListener() {
   @Override
   public void onSuccess() {
       ...
   }

   @Override
   public void onFailure(int reasonCode) {
       ...
   }
});

Se la procedura di rilevamento ha esito positivo e rileva i peer, il sistema trasmette l'intent WIFI_P2P_PEERS_CHANGED_ACTION, che puoi ascoltare in un ricevitore di trasmissione per ottenere un elenco di peer. Quando la tua applicazione riceve l'intent WIFI_P2P_PEERS_CHANGED_ACTION, puoi richiedere un elenco dei peer rilevati con requestPeers(). Il seguente codice mostra come configurare questa impostazione.

Kotlin

override fun onReceive(context: Context, intent: Intent) {
   val action: String = intent.action
   when (action) {
       ...
       WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> {
           manager?.requestPeers(channel) { peers: WifiP2pDeviceList? ->
               // Handle peers list
           }
       }
       ...
   }
}

Java

PeerListListener myPeerListListener;
...
if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {

   // request available peers from the wifi p2p manager. This is an
   // asynchronous call and the calling activity is notified with a
   // callback on PeerListListener.onPeersAvailable()
   if (manager != null) {
       manager.requestPeers(channel, myPeerListListener);
   }
}

Il metodo requestPeers() è anche asincrono e può inviare una notifica della tua attività quando è disponibile un elenco di peer con onPeersAvailable(), definito nell'interfaccia WifiP2pManager.PeerListListener. Il metodo onPeersAvailable() fornisce un WifiP2pDeviceList, che puoi scorrere per trovare il peer a cui connetterti.

Connettersi con i peer

Una volta ottenuto un elenco di possibili peer e selezionato un dispositivo a cui connettersi, chiama il metodo connect() per connetterti al dispositivo. Questa chiamata al metodo richiede un oggetto WifiP2pConfig che contenga informazioni sul dispositivo a cui connettersi. WifiP2pManager.ActionListener può inviarti una notifica in caso di connessione riuscita o non riuscita. Il seguente codice mostra come creare una connessione a un dispositivo.

Kotlin

val device: WifiP2pDevice = ...
val config = WifiP2pConfig()
config.deviceAddress = device.deviceAddress
channel?.also { channel ->
   manager?.connect(channel, config, object : WifiP2pManager.ActionListener {

       override fun onSuccess() {
           //success logic
       }

       override fun onFailure(reason: Int) {
           //failure logic
       }
   }
)}

Java

//obtain a peer from the WifiP2pDeviceList
WifiP2pDevice device;
WifiP2pConfig config = new WifiP2pConfig();
config.deviceAddress = device.deviceAddress;
manager.connect(channel, config, new ActionListener() {

   @Override
   public void onSuccess() {
       //success logic
   }

   @Override
   public void onFailure(int reason) {
       //failure logic
   }
});

Trasferisci dati

Una volta stabilita la connessione, puoi trasferire i dati tra i dispositivi con i socket. I passaggi di base per il trasferimento dei dati sono i seguenti:

  1. Crea un ServerSocket. Questo socket attende una connessione da un client su una porta specificata e si blocca finché non si verifica, quindi esegui questa operazione in un thread in background.
  2. Crea un client Socket. Il client utilizza l'indirizzo IP e la porta del socket del server per connettersi al dispositivo server.
  3. Invia i dati dal client al server. Quando il socket client si connette correttamente al socket server, puoi inviare dati dal client al server con flussi di byte.
  4. Il socket del server è in attesa di una connessione client (con il metodo accept()). Questa chiamata si blocca finché non si connette un client, quindi chiamala in un altro thread. Quando si verifica una connessione, il dispositivo server può ricevere i dati dal client.

L'esempio seguente, modificato dalla demo Wi-Fi P2P, mostra come creare questa comunicazione socket client-server e trasferire immagini JPEG da un client a un server con un servizio. Per un esempio di funzionamento completo, compila ed esegui la demo.

Kotlin

class FileServerAsyncTask(
       private val context: Context,
       private var statusText: TextView
) : AsyncTask<Void, Void, String?>() {

   override fun doInBackground(vararg params: Void): String? {
       /**
        * Create a server socket.
        */
       val serverSocket = ServerSocket(8888)
       return serverSocket.use {
           /**
            * Wait for client connections. This call blocks until a
            * connection is accepted from a client.
            */
           val client = serverSocket.accept()
           /**
            * If this code is reached, a client has connected and transferred data
            * Save the input stream from the client as a JPEG file
            */
           val f = File(Environment.getExternalStorageDirectory().absolutePath +
                   "/${context.packageName}/wifip2pshared-${System.currentTimeMillis()}.jpg")
           val dirs = File(f.parent)

           dirs.takeIf { it.doesNotExist() }?.apply {
               mkdirs()
           }
           f.createNewFile()
           val inputstream = client.getInputStream()
           copyFile(inputstream, FileOutputStream(f))
           serverSocket.close()
           f.absolutePath
       }
   }

   private fun File.doesNotExist(): Boolean = !exists()

   /**
    * Start activity that can handle the JPEG image
    */
   override fun onPostExecute(result: String?) {
       result?.run {
           statusText.text = "File copied - $result"
           val intent = Intent(android.content.Intent.ACTION_VIEW).apply {
               setDataAndType(Uri.parse("file://$result"), "image/*")
           }
           context.startActivity(intent)
       }
   }
}

Java

public static class FileServerAsyncTask extends AsyncTask {

   private Context context;
   private TextView statusText;

   public FileServerAsyncTask(Context context, View statusText) {
       this.context = context;
       this.statusText = (TextView) statusText;
   }

   @Override
   protected String doInBackground(Void... params) {
       try {

           /**
            * Create a server socket and wait for client connections. This
            * call blocks until a connection is accepted from a client
            */
           ServerSocket serverSocket = new ServerSocket(8888);
           Socket client = serverSocket.accept();

           /**
            * If this code is reached, a client has connected and transferred data
            * Save the input stream from the client as a JPEG file
            */
           final File f = new File(Environment.getExternalStorageDirectory() + "/"
                   + context.getPackageName() + "/wifip2pshared-" + System.currentTimeMillis()
                   + ".jpg");

           File dirs = new File(f.getParent());
           if (!dirs.exists())
               dirs.mkdirs();
           f.createNewFile();
           InputStream inputstream = client.getInputStream();
           copyFile(inputstream, new FileOutputStream(f));
           serverSocket.close();
           return f.getAbsolutePath();
       } catch (IOException e) {
           Log.e(WiFiDirectActivity.TAG, e.getMessage());
           return null;
       }
   }

   /**
    * Start activity that can handle the JPEG image
    */
   @Override
   protected void onPostExecute(String result) {
       if (result != null) {
           statusText.setText("File copied - " + result);
           Intent intent = new Intent();
           intent.setAction(android.content.Intent.ACTION_VIEW);
           intent.setDataAndType(Uri.parse("file://" + result), "image/*");
           context.startActivity(intent);
       }
   }
}

Sul client, connettiti al socket del server con un socket client e trasferisci i dati. Questo esempio trasferisce un file JPEG sul file system del dispositivo client.

Kotlin

val context = applicationContext
val host: String
val port: Int
val len: Int
val socket = Socket()
val buf = ByteArray(1024)
...
try {
   /**
    * Create a client socket with the host,
    * port, and timeout information.
    */
   socket.bind(null)
   socket.connect((InetSocketAddress(host, port)), 500)

   /**
    * Create a byte stream from a JPEG file and pipe it to the output stream
    * of the socket. This data is retrieved by the server device.
    */
   val outputStream = socket.getOutputStream()
   val cr = context.contentResolver
   val inputStream: InputStream = cr.openInputStream(Uri.parse("path/to/picture.jpg"))
   while (inputStream.read(buf).also { len = it } != -1) {
       outputStream.write(buf, 0, len)
   }
   outputStream.close()
   inputStream.close()
} catch (e: FileNotFoundException) {
   //catch logic
} catch (e: IOException) {
   //catch logic
} finally {
   /**
    * Clean up any open sockets when done
    * transferring or if an exception occurred.
    */
   socket.takeIf { it.isConnected }?.apply {
       close()
   }
}

Java

Context context = this.getApplicationContext();
String host;
int port;
int len;
Socket socket = new Socket();
byte buf[]  = new byte[1024];
...
try {
   /**
    * Create a client socket with the host,
    * port, and timeout information.
    */
   socket.bind(null);
   socket.connect((new InetSocketAddress(host, port)), 500);

   /**
    * Create a byte stream from a JPEG file and pipe it to the output stream
    * of the socket. This data is retrieved by the server device.
    */
   OutputStream outputStream = socket.getOutputStream();
   ContentResolver cr = context.getContentResolver();
   InputStream inputStream = null;
   inputStream = cr.openInputStream(Uri.parse("path/to/picture.jpg"));
   while ((len = inputStream.read(buf)) != -1) {
       outputStream.write(buf, 0, len);
   }
   outputStream.close();
   inputStream.close();
} catch (FileNotFoundException e) {
   //catch logic
} catch (IOException e) {
   //catch logic
}

/**
* Clean up any open sockets when done
* transferring or if an exception occurred.
*/
finally {
   if (socket != null) {
       if (socket.isConnected()) {
           try {
               socket.close();
           } catch (IOException e) {
               //catch logic
           }
       }
   }
}