Criar conexões P2P com o Wi-Fi Direct

O Wi-Fi Direct (também conhecido como ponto a ponto ou P2P) permite que seu app encontre dispositivos próximos rapidamente e interaja com eles, com um alcance superior aos recursos do Bluetooth.

As APIs do Wi-Fi Direct (P2P) permitem que os apps se conectem a dispositivos próximos sem precisar se conectar a uma rede ou ponto de acesso. Caso seu app tenha sido projetado para fazer parte de uma rede segura e de curto alcance, o Wi-Fi Direct é uma opção mais adequada que a rede Wi-Fi ad-hoc tradicional pelos seguintes motivos:

  • O Wi-Fi Direct é compatível com criptografia WPA2. Algumas redes ad-hoc são compatíveis apenas com criptografia WEP.
  • Os dispositivos podem transmitir os serviços que fornecem, o que ajuda outros dispositivos a descobrirem apps semelhantes adequados com mais facilidade.
  • Ao determinar qual dispositivo precisa ser o proprietário do grupo da rede, o Wi-Fi Direct examina os recursos de gerenciamento de energia, IU e serviço de cada dispositivo e usa essas informações para escolher aquele que pode lidar com as responsabilidades do servidor de forma mais eficiente.
  • O Android não é compatível com o modo Wi-Fi ad-hoc.

Esta lição mostra como encontrar dispositivos próximos e conectar-se a eles usando o Wi-Fi P2P.

Configurar permissões de apps

Para usar o Wi-Fi Direct, adicione as permissões ACCESS_FINE_LOCATION, CHANGE_WIFI_STATE, ACCESS_WIFI_STATE e INTERNET ao manifesto. Caso o app seja destinado ao Android 13 (nível 33 da API) ou versões mais recentes, adicione também a permissão NEARBY_WIFI_DEVICES ao manifesto. O Wi-Fi Direct não precisa de uma conexão de Internet, mas usa soquetes Java padrão, que precisam da permissão INTERNET. Portanto, você precisa das seguintes permissões para usar o Wi-Fi Direct:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"     package="com.example.android.nsdchat"     ...     <!-- 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:required="true"         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" />     <uses-permission         android:required="true"         android:name="android.permission.ACCESS_WIFI_STATE"/>     <uses-permission         android:required="true"         android:name="android.permission.CHANGE_WIFI_STATE"/>     <uses-permission         android:required="true"         android:name="android.permission.INTERNET"/>     ...

Além das permissões anteriores, as seguintes APIs também exigem que o "Modo de localização" esteja ativado:

Configurar um broadcast receiver e um administrador de ponto a ponto

Para usar o Wi-Fi Direct, é preciso detectar intents de transmissão que informam ao app quando determinados eventos ocorreram. No seu app, instancie um IntentFilter e configure-o para detectar o seguinte:

WIFI_P2P_STATE_CHANGED_ACTION
Indica se o Wi-Fi Direct está ativado.
WIFI_P2P_PEERS_CHANGED_ACTION
Indica que a lista de apps semelhantes disponíveis mudou.
WIFI_P2P_CONNECTION_CHANGED_ACTION
Indica que o estado da conectividade do Wi-Fi Direct mudou. A partir do Android 10, essa opção não é fixa. Caso seu app dependia do recebimento dessas transmissões no momento de registro porque elas eram fixas, use o método get apropriado na inicialização para conseguir as informações.
WIFI_P2P_THIS_DEVICE_CHANGED_ACTION
Indica que os detalhes de configuração deste dispositivo mudaram. A partir do Android 10, essa opção não é fixa. Caso seu app dependia do recebimento dessas transmissões no momento de registro porque elas eram fixas, use o método get apropriado na inicialização para conseguir as informações.

Kotlin

private val intentFilter = IntentFilter() ... override fun onCreate(savedInstanceState: Bundle?) {     super.onCreate(savedInstanceState)     setContentView(R.layout.main)      // Indicates a change in the Wi-Fi Direct status.     intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)      // Indicates a change in the list of available peers.     intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)      // Indicates the state of Wi-Fi Direct connectivity has changed.     intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)      // Indicates this device's details have changed.     intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION)     ... }

Java

private final IntentFilter intentFilter = new IntentFilter(); ... @Override public void onCreate(Bundle savedInstanceState) {     super.onCreate(savedInstanceState);     setContentView(R.layout.main);      // Indicates a change in the Wi-Fi Direct status.     intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);      // Indicates a change in the list of available peers.     intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);      // Indicates the state of Wi-Fi Direct connectivity has changed.     intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);      // Indicates this device's details have changed.     intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);     ... }

No final do método onCreate(), receba uma instância do WifiP2pManager e chame o método initialize(). Esse método retorna um objeto WifiP2pManager.Channel, que você usará depois para conectar seu app ao framework do Wi-Fi Direct.

Kotlin

private lateinit var channel: WifiP2pManager.Channel private lateinit var manager: WifiP2pManager  override fun onCreate(savedInstanceState: Bundle?) {     ...     manager = getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager     channel = manager.initialize(this, mainLooper, null) }

Java

Channel channel; WifiP2pManager manager;  @Override public void onCreate(Bundle savedInstanceState) {     ...     manager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);     channel = manager.initialize(this, getMainLooper(), null); }

Agora, crie uma nova classe BroadcastReceiver, que você usará para detectar as mudanças feitas no estado do Wi-Fi do sistema. No método onReceive(), adicione uma condição para processar cada mudança de estado listada acima.

Kotlin

override fun onReceive(context: Context, intent: Intent) {     when(intent.action) {         WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION -> {             // Determine if Wi-Fi Direct mode is enabled or not, alert             // the Activity.             val state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1)             activity.isWifiP2pEnabled = state == WifiP2pManager.WIFI_P2P_STATE_ENABLED         }         WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> {              // The peer list has changed! We should probably do something about             // that.          }         WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> {              // Connection state changed! We should probably do something about             // that.          }         WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION -> {             (activity.supportFragmentManager.findFragmentById(R.id.frag_list) as DeviceListFragment)                     .apply {                         updateThisDevice(                                 intent.getParcelableExtra(                                         WifiP2pManager.EXTRA_WIFI_P2P_DEVICE) as WifiP2pDevice                         )                     }         }     } }

Java

@Override public void onReceive(Context context, Intent intent) {     String action = intent.getAction();     if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {         // Determine if Wi-Fi Direct mode is enabled or not, alert         // the Activity.         int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1);         if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) {             activity.setIsWifiP2pEnabled(true);         } else {             activity.setIsWifiP2pEnabled(false);         }     } else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {          // The peer list has changed! We should probably do something about         // that.      } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {          // Connection state changed! We should probably do something about         // that.      } else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) {         DeviceListFragment fragment = (DeviceListFragment) activity.getFragmentManager()                 .findFragmentById(R.id.frag_list);         fragment.updateThisDevice((WifiP2pDevice) intent.getParcelableExtra(                 WifiP2pManager.EXTRA_WIFI_P2P_DEVICE));      } }

Por fim, adicione um código para registrar o filtro de intent e o broadcast receiver quando sua atividade principal estiver ativa e cancele o registro quando a atividade estiver pausada. O melhor lugar para fazer isso são nos métodos onResume() e onPause().

Kotlin

/** register the BroadcastReceiver with the intent values to be matched  */ public override fun onResume() {     super.onResume()     receiver = WiFiDirectBroadcastReceiver(manager, channel, this)     registerReceiver(receiver, intentFilter) }  public override fun onPause() {     super.onPause()     unregisterReceiver(receiver) }

Java

/** register the BroadcastReceiver with the intent values to be matched */ @Override public void onResume() {     super.onResume();     receiver = new WiFiDirectBroadcastReceiver(manager, channel, this);     registerReceiver(receiver, intentFilter); }  @Override public void onPause() {     super.onPause();     unregisterReceiver(receiver); }

Iniciar descoberta de pontos

Para começar a procurar dispositivos próximos com Wi-Fi P2P, chame discoverPeers(). Esse método tem os seguintes argumentos:

Kotlin

manager.discoverPeers(channel, object : WifiP2pManager.ActionListener {      override fun onSuccess() {         // Code for when the discovery initiation is successful goes here.         // No services have actually been discovered yet, so this method         // can often be left blank. Code for peer discovery goes in the         // onReceive method, detailed below.     }      override fun onFailure(reasonCode: Int) {         // Code for when the discovery initiation fails goes here.         // Alert the user that something went wrong.     } })

Java

manager.discoverPeers(channel, new WifiP2pManager.ActionListener() {      @Override     public void onSuccess() {         // Code for when the discovery initiation is successful goes here.         // No services have actually been discovered yet, so this method         // can often be left blank. Code for peer discovery goes in the         // onReceive method, detailed below.     }      @Override     public void onFailure(int reasonCode) {         // Code for when the discovery initiation fails goes here.         // Alert the user that something went wrong.     } });

Lembre-se de que isso apenas inicia a descoberta de apps semelhantes. O método discoverPeers() inicia o processo de descoberta e retorna imediatamente. O sistema notifica se o processo de descoberta de apps semelhantes foi iniciado com êxito, chamando métodos no listener de ação fornecido. Além disso, a descoberta permanece ativa até que uma conexão seja iniciada ou que um grupo P2P seja formado.

Buscar a lista de apps semelhantes

Agora, escreva o código que busca e processa a lista de pontos. Primeiro, implemente a interface WifiP2pManager.PeerListListener, que fornece informações sobre os pares que o Wi-Fi Direct detectou. Essas informações também permitem que seu app determine quando os apps semelhantes entram ou saem da rede. O snippet de código a seguir ilustra essas operações relacionadas a apps semelhantes.

Kotlin

private val peers = mutableListOf<WifiP2pDevice>() ...  private val peerListListener = WifiP2pManager.PeerListListener { peerList ->     val refreshedPeers = peerList.deviceList     if (refreshedPeers != peers) {         peers.clear()         peers.addAll(refreshedPeers)          // If an AdapterView is backed by this data, notify it         // of the change. For instance, if you have a ListView of         // available peers, trigger an update.         (listAdapter as WiFiPeerListAdapter).notifyDataSetChanged()          // Perform any other updates needed based on the new list of         // peers connected to the Wi-Fi P2P network.     }      if (peers.isEmpty()) {         Log.d(TAG, "No devices found")         return@PeerListListener     } }

Java

private List<WifiP2pDevice> peers = new ArrayList<WifiP2pDevice>(); ...  private PeerListListener peerListListener = new PeerListListener() {     @Override     public void onPeersAvailable(WifiP2pDeviceList peerList) {          List<WifiP2pDevice> refreshedPeers = peerList.getDeviceList();         if (!refreshedPeers.equals(peers)) {             peers.clear();             peers.addAll(refreshedPeers);              // If an AdapterView is backed by this data, notify it             // of the change. For instance, if you have a ListView of             // available peers, trigger an update.             ((WiFiPeerListAdapter) getListAdapter()).notifyDataSetChanged();              // Perform any other updates needed based on the new list of             // peers connected to the Wi-Fi P2P network.         }          if (peers.size() == 0) {             Log.d(WiFiDirectActivity.TAG, "No devices found");             return;         }     } }

Agora, modifique o método onReceive() do seu broadcast receiver para chamar requestPeers() quando uma intent com a ação WIFI_P2P_PEERS_CHANGED_ACTION for recebida. Você precisa transmitir esse listener ao receiver de alguma forma. Uma maneira é enviá-lo como argumento ao construtor do broadcast receiver.

Kotlin

fun onReceive(context: Context, intent: Intent) {     when (intent.action) {         ...         WifiP2pManager.WIFI_P2P_PEERS_CHANGED_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()             mManager?.requestPeers(channel, peerListListener)             Log.d(TAG, "P2P peers changed")           }         ...     } }

Java

public void onReceive(Context context, Intent intent) {     ...     else 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 (mManager != null) {             mManager.requestPeers(channel, peerListListener);         }         Log.d(WiFiDirectActivity.TAG, "P2P peers changed");     }... }

Agora, um intent com o intent de ação WIFI_P2P_PEERS_CHANGED_ACTION aciona uma solicitação de uma lista atualizada de apps semelhantes.

Conectar-se a um app semelhante

Para se conectar a um app semelhante, crie um novo objeto WifiP2pConfig e copie nele os dados do WifiP2pDevice que representa o dispositivo ao qual você quer se conectar. Depois, chame o método connect().

Kotlin

override fun connect() {     // Picking the first device found on the network.     val device = peers[0]      val config = WifiP2pConfig().apply {         deviceAddress = device.deviceAddress         wps.setup = WpsInfo.PBC     }      manager.connect(channel, config, object : WifiP2pManager.ActionListener {          override fun onSuccess() {             // WiFiDirectBroadcastReceiver notifies us. Ignore for now.         }          override fun onFailure(reason: Int) {             Toast.makeText(                     this@WiFiDirectActivity,                     "Connect failed. Retry.",                     Toast.LENGTH_SHORT             ).show()         }     }) }

Java

@Override public void connect() {     // Picking the first device found on the network.     WifiP2pDevice device = peers.get(0);      WifiP2pConfig config = new WifiP2pConfig();     config.deviceAddress = device.deviceAddress;     config.wps.setup = WpsInfo.PBC;      manager.connect(channel, config, new ActionListener() {          @Override         public void onSuccess() {             // WiFiDirectBroadcastReceiver notifies us. Ignore for now.         }          @Override         public void onFailure(int reason) {             Toast.makeText(WiFiDirectActivity.this, "Connect failed. Retry.",                     Toast.LENGTH_SHORT).show();         }     }); }

Se cada um dos dispositivos do seu grupo for compatível com Wi-Fi Direct, você não precisará pedir explicitamente a senha do grupo ao se conectar. No entanto, para permitir que um dispositivo incompatível com Wi-Fi Direct participe de um grupo, chame requestGroupInfo(), conforme mostrado no snippet de código a seguir.

Kotlin

manager.requestGroupInfo(channel) { group ->     val groupPassword = group.passphrase }

Java

manager.requestGroupInfo(channel, new GroupInfoListener() {   @Override   public void onGroupInfoAvailable(WifiP2pGroup group) {       String groupPassword = group.getPassphrase();   } });

Observe que o WifiP2pManager.ActionListener implementado no método connect() notificará você apenas quando a iniciação for bem-sucedida ou não. Para detectar alterações no estado da conexão, implemente a interface WifiP2pManager.ConnectionInfoListener. Seu callback onConnectionInfoAvailable() notifica quando o estado da conexão muda. Nos casos em que vários dispositivos serão conectados a um único dispositivo (como um jogo com três ou mais jogadores ou um app de bate-papo), um dispositivo é designado "proprietário do grupo". É possível designar um dispositivo específico como o proprietário do grupo da rede seguindo as etapas descritas na seção Criar um grupo.

Kotlin

private val connectionListener = WifiP2pManager.ConnectionInfoListener { info ->      // String from WifiP2pInfo struct     val groupOwnerAddress: String = info.groupOwnerAddress.hostAddress      // After the group negotiation, we can determine the group owner     // (server).     if (info.groupFormed && info.isGroupOwner) {         // Do whatever tasks are specific to the group owner.         // One common case is creating a group owner thread and accepting         // incoming connections.     } else if (info.groupFormed) {         // The other device acts as the peer (client). In this case,         // you'll want to create a peer thread that connects         // to the group owner.     } }

Java

@Override public void onConnectionInfoAvailable(final WifiP2pInfo info) {      // String from WifiP2pInfo struct     String groupOwnerAddress = info.groupOwnerAddress.getHostAddress();      // After the group negotiation, we can determine the group owner     // (server).     if (info.groupFormed && info.isGroupOwner) {         // Do whatever tasks are specific to the group owner.         // One common case is creating a group owner thread and accepting         // incoming connections.     } else if (info.groupFormed) {         // The other device acts as the peer (client). In this case,         // you'll want to create a peer thread that connects         // to the group owner.     } }

Agora, volte para o método onReceive() do broadcast receiver e modifique a seção que detecta uma intent WIFI_P2P_CONNECTION_CHANGED_ACTION. Quando essa intent for recebida, chame requestConnectionInfo(). Como essa é uma chamada assíncrona, os resultados são recebidos pelo listener de informações da conexão que você fornece como parâmetro.

Kotlin

when (intent.action) {     ...     WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> {          // Connection state changed! We should probably do something about         // that.          mManager?.let { manager ->              val networkInfo: NetworkInfo? = intent                     .getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO) as NetworkInfo              if (networkInfo?.isConnected == true) {                  // We are connected with the other device, request connection                 // info to find group owner IP                  manager.requestConnectionInfo(channel, connectionListener)             }         }     }     ... }

Java

    ...     } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {          if (manager == null) {             return;         }          NetworkInfo networkInfo = (NetworkInfo) intent                 .getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO);          if (networkInfo.isConnected()) {              // We are connected with the other device, request connection             // info to find group owner IP              manager.requestConnectionInfo(channel, connectionListener);         }         ...

Criar um grupo

Para que o dispositivo que está executando seu app seja o proprietário do grupo de uma rede com dispositivos legados, ou seja, não compatíveis com Wi-Fi Direct, siga a mesma sequência de etapas da seção Conectar-se a um app semelhante, a menos que você crie um novo WifiP2pManager.ActionListener usando createGroup() em vez de connect(). O processamento de callback é o mesmo no WifiP2pManager.ActionListener, conforme mostrado no snippet de código a seguir.

Kotlin

manager.createGroup(channel, object : WifiP2pManager.ActionListener {     override fun onSuccess() {         // Device is ready to accept incoming connections from peers.     }      override fun onFailure(reason: Int) {         Toast.makeText(                 this@WiFiDirectActivity,                 "P2P group creation failed. Retry.",                 Toast.LENGTH_SHORT         ).show()     } })

Java

manager.createGroup(channel, new WifiP2pManager.ActionListener() {     @Override     public void onSuccess() {         // Device is ready to accept incoming connections from peers.     }      @Override     public void onFailure(int reason) {         Toast.makeText(WiFiDirectActivity.this, "P2P group creation failed. Retry.",                 Toast.LENGTH_SHORT).show();     } });

Observação: se todos os dispositivos de uma rede forem compatíveis com o Wi-Fi Direct, você poderá usar o método connect() em cada um deles, porque esse método cria o grupo e seleciona um proprietário de grupo automaticamente.

Depois de criar um grupo, você pode chamar requestGroupInfo() para recuperar detalhes sobre os apps semelhantes da rede, incluindo nomes de dispositivos e status de conexões.