1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.snippet.wifi.direct; 18 19 import android.Manifest; 20 import android.app.Instrumentation; 21 import android.content.BroadcastReceiver; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.IntentFilter; 25 import android.content.pm.PackageManager; 26 import android.net.NetworkInfo; 27 import android.net.wifi.p2p.WifiP2pConfig; 28 import android.net.wifi.p2p.WifiP2pDevice; 29 import android.net.wifi.p2p.WifiP2pDeviceList; 30 import android.net.wifi.p2p.WifiP2pGroup; 31 import android.net.wifi.p2p.WifiP2pGroupList; 32 import android.net.wifi.p2p.WifiP2pInfo; 33 import android.net.wifi.p2p.WifiP2pManager; 34 import android.net.wifi.p2p.nsd.WifiP2pDnsSdServiceInfo; 35 import android.net.wifi.p2p.nsd.WifiP2pDnsSdServiceRequest; 36 import android.net.wifi.p2p.nsd.WifiP2pServiceInfo; 37 import android.net.wifi.p2p.nsd.WifiP2pServiceRequest; 38 import android.net.wifi.p2p.nsd.WifiP2pUpnpServiceInfo; 39 import android.net.wifi.p2p.nsd.WifiP2pUpnpServiceRequest; 40 import android.os.Bundle; 41 import android.widget.Button; 42 43 import androidx.annotation.NonNull; 44 import androidx.test.core.app.ApplicationProvider; 45 import androidx.test.platform.app.InstrumentationRegistry; 46 import androidx.test.uiautomator.By; 47 import androidx.test.uiautomator.UiDevice; 48 import androidx.test.uiautomator.UiObject2; 49 import androidx.test.uiautomator.Until; 50 51 import com.google.android.mobly.snippet.Snippet; 52 import com.google.android.mobly.snippet.event.EventCache; 53 import com.google.android.mobly.snippet.event.SnippetEvent; 54 import com.google.android.mobly.snippet.rpc.AsyncRpc; 55 import com.google.android.mobly.snippet.rpc.Rpc; 56 import com.google.android.mobly.snippet.rpc.RpcDefault; 57 import com.google.android.mobly.snippet.rpc.RpcOptional; 58 import com.google.android.mobly.snippet.util.Log; 59 60 import org.json.JSONArray; 61 import org.json.JSONObject; 62 63 import java.util.ArrayList; 64 import java.util.HashMap; 65 import java.util.Iterator; 66 import java.util.List; 67 import java.util.Map; 68 import java.util.Objects; 69 import java.util.UUID; 70 import java.util.concurrent.LinkedBlockingDeque; 71 import java.util.concurrent.TimeUnit; 72 import java.util.concurrent.TimeoutException; 73 import java.util.regex.Pattern; 74 75 76 /** Snippet class for WifiP2pManager. */ 77 public class WifiP2pManagerSnippet implements Snippet { 78 private static final String TAG = "WifiP2pManagerSnippet"; 79 private static final int TIMEOUT_SHORT_MS = 10000; 80 private static final int UI_ACTION_SHORT_TIMEOUT_MS = 5000; 81 private static final int UI_ACTION_LONG_TIMEOUT_MS = 30000; 82 private static final String EVENT_KEY_CALLBACK_NAME = "callbackName"; 83 private static final String EVENT_KEY_REASON = "reason"; 84 private static final String EVENT_KEY_P2P_DEVICE = "p2pDevice"; 85 private static final String EVENT_KEY_P2P_INFO = "p2pInfo"; 86 private static final String EVENT_KEY_P2P_GROUP = "p2pGroup"; 87 private static final String EVENT_KEY_PEER_LIST = "peerList"; 88 private static final String EVENT_KEY_SERVICE_LIST = "serviceList"; 89 private static final String EVENT_KEY_INSTANCE_NAME = "instanceName"; 90 private static final String EVENT_KEY_REGISTRATION_TYPE = "registrationType"; 91 private static final String EVENT_KEY_SOURCE_DEVICE = "sourceDevice"; 92 private static final String EVENT_KEY_FULL_DOMAIN_NAME = "fullDomainName"; 93 private static final String EVENT_KEY_TXT_RECORD_MAP = "txtRecordMap"; 94 private static final String EVENT_KEY_TIMESTAMP_MS = "timestampMs"; 95 private static final String ACTION_LISTENER_ON_SUCCESS = "onSuccess"; 96 public static final String ACTION_LISTENER_ON_FAILURE = "onFailure"; 97 98 private final Context mContext; 99 private final IntentFilter mIntentFilter; 100 private final WifiP2pManager mP2pManager; 101 102 private Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation(); 103 private UiDevice mUiDevice = UiDevice.getInstance(mInstrumentation); 104 105 private final HashMap<Integer, WifiP2pManager.Channel> mChannels = new HashMap<>(); 106 private WifiP2pStateChangedReceiver mStateChangedReceiver = null; 107 108 private int mServiceRequestCnt = 0; 109 private int mChannelCnt = -1; 110 111 private final Map<Integer, WifiP2pServiceRequest> mServiceRequests; 112 113 114 private static class WifiP2pManagerException extends Exception { WifiP2pManagerException(String message)115 WifiP2pManagerException(String message) { 116 super(message); 117 } 118 } 119 WifiP2pManagerSnippet()120 public WifiP2pManagerSnippet() { 121 Log.d("Elevating permission require to enable support for privileged operation in " 122 + "Android Q+"); 123 mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(); 124 125 mContext = ApplicationProvider.getApplicationContext(); 126 127 checkPermissions(mContext, Manifest.permission.ACCESS_WIFI_STATE, 128 Manifest.permission.CHANGE_WIFI_STATE, Manifest.permission.ACCESS_FINE_LOCATION, 129 Manifest.permission.NEARBY_WIFI_DEVICES); 130 131 mP2pManager = mContext.getSystemService(WifiP2pManager.class); 132 133 mIntentFilter = new IntentFilter(); 134 mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION); 135 mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION); 136 mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); 137 mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION); 138 139 mServiceRequests = new HashMap<Integer, WifiP2pServiceRequest>(); 140 } 141 142 /** 143 * Initialize the application with the Wi-Fi P2P framework and registers necessary receivers. 144 * 145 * @param callbackId The callback ID assigned by Mobly 146 * @return The ID of the initialized channel. Use this ID to specify which channel to 147 * operate on in future operations. 148 * @throws WifiP2pManagerException If the Wi-Fi P2P has already been initialized. 149 */ 150 @AsyncRpc(description = "Register the application with the Wi-Fi framework.") wifiP2pInitialize(String callbackId)151 public int wifiP2pInitialize(String callbackId) throws WifiP2pManagerException { 152 if (mStateChangedReceiver != null) { 153 throw new WifiP2pManagerException("WifiP2pManager has already been initialized. " 154 + "Please call `p2pClose()` close the current connection."); 155 } 156 if (mChannelCnt != -1) { 157 throw new WifiP2pManagerException("Please call `p2pClose()` to close the current " 158 + "connection before initializing a new one."); 159 } 160 checkP2pManager(); 161 // Initialize the first channel. This channel will be used by default if an Wi-Fi P2P RPC 162 // method is called without a channel ID. 163 mStateChangedReceiver = new WifiP2pStateChangedReceiver(callbackId); 164 mContext.registerReceiver(mStateChangedReceiver, mIntentFilter, 165 Context.RECEIVER_NOT_EXPORTED); 166 WifiP2pManager.Channel channel = 167 mP2pManager.initialize(mContext, mContext.getMainLooper(), null); 168 mChannelCnt += 1; 169 mChannels.put(mChannelCnt, channel); 170 return mChannelCnt; 171 } 172 173 /** 174 * Initialize an extra Wi-Fi P2P channel. This is for multi-channel tests. 175 * 176 * @return The id of the new channel. 177 */ 178 @Rpc(description = "Initialize an extra Wi-Fi P2P channel. This is needed when you need to " 179 + "test with multiple channels.") wifiP2pInitExtraChannel()180 public int wifiP2pInitExtraChannel() { 181 if (mChannelCnt == -1) { 182 throw new IllegalStateException("Main channel has not been initialized. Please call " 183 + "`wifiP2pInitialize` first."); 184 } 185 WifiP2pManager.Channel channel = 186 mP2pManager.initialize(mContext, mContext.getMainLooper(), null); 187 mChannelCnt += 1; 188 mChannels.put(mChannelCnt, channel); 189 return mChannelCnt; 190 } 191 192 /** 193 * Request the device information in the form of WifiP2pDevice. 194 * 195 * @param callbackId The callback ID assigned by Mobly. 196 * @param channelId The ID of the channel for Wi-Fi P2P to operate on. 197 * @throws WifiP2pManagerException If got invalid channel ID. 198 */ 199 @AsyncRpc(description = "Request the device information in the form of WifiP2pDevice.") wifiP2pRequestDeviceInfo(String callbackId, @RpcDefault(value = "0") Integer channelId)200 public void wifiP2pRequestDeviceInfo(String callbackId, 201 @RpcDefault(value = "0") Integer channelId) 202 throws WifiP2pManagerException { 203 WifiP2pManager.Channel channel = getChannel(channelId); 204 mP2pManager.requestDeviceInfo(channel, new DeviceInfoListener(callbackId)); 205 } 206 207 /** 208 * Request the connection information in the form of WifiP2pDevice. 209 * 210 * @param callbackId The callback ID assigned by Mobly. 211 * @param channelId The ID of the channel for Wi-Fi P2P to operate on. 212 */ 213 @AsyncRpc(description = "Request the connection information in the form of WifiP2pDevice.") wifiP2pRequestConnectionInfo(String callbackId, @RpcDefault(value = "0") Integer channelId)214 public void wifiP2pRequestConnectionInfo(String callbackId, 215 @RpcDefault(value = "0") Integer channelId) 216 throws WifiP2pManagerException { 217 WifiP2pManager.Channel channel = getChannel(channelId); 218 mP2pManager.requestConnectionInfo(channel, 219 new WifiP2pConnectionInfoListener(callbackId)); 220 } 221 222 /** 223 * Initiate peer discovery. A discovery process involves scanning for available Wi-Fi peers for 224 * the purpose of establishing a connection. 225 * 226 * @param channelId The ID of the channel for Wi-Fi P2P to operate on. 227 * @throws Throwable If the P2P operation failed or timed out, or got invalid channel ID. 228 */ 229 @Rpc(description = "Initiate peer discovery. A discovery process involves scanning for " 230 + "available Wi-Fi peers for the purpose of establishing a connection.") wifiP2pDiscoverPeers(@pcDefaultvalue = "0") Integer channelId)231 public void wifiP2pDiscoverPeers(@RpcDefault(value = "0") Integer channelId) throws Throwable { 232 WifiP2pManager.Channel channel = getChannel(channelId); 233 String callbackId = UUID.randomUUID().toString(); 234 mP2pManager.discoverPeers(channel, new ActionListener(callbackId)); 235 verifyActionListenerSucceed(callbackId); 236 } 237 238 /** 239 * Request peers that are discovered for wifi p2p. 240 * 241 * @param callbackId The callback ID assigned by Mobly. 242 * @param channelId The ID of the channel for Wi-Fi P2P to operate on. 243 * @throws Throwable If the P2P operation failed or timed out, or got invalid channel ID. 244 */ 245 @AsyncRpc(description = "Request peers that are discovered for wifi p2p.") wifiP2pRequestPeers(String callbackId, @RpcDefault(value = "0") Integer channelId)246 public void wifiP2pRequestPeers(String callbackId, @RpcDefault(value = "0") Integer channelId) 247 throws Throwable { 248 WifiP2pManager.Channel channel = getChannel(channelId); 249 mP2pManager.requestPeers(channel, new PeerListListener(callbackId)); 250 } 251 252 /** 253 * Cancel any ongoing p2p group negotiation. 254 * 255 * @param channelId The ID of the channel for Wi-Fi P2P to operate on. 256 * @return The event posted by the callback methods of {@link ActionListener}. 257 * @throws Throwable If the P2P operation failed or timed out, or got invalid channel ID. 258 */ 259 @Rpc(description = "Cancel any ongoing p2p group negotiation.") wifiP2pCancelConnect(@pcDefaultvalue = "0") Integer channelId)260 public Bundle wifiP2pCancelConnect(@RpcDefault(value = "0") Integer channelId) 261 throws Throwable { 262 WifiP2pManager.Channel channel = getChannel(channelId); 263 String callbackId = UUID.randomUUID().toString(); 264 mP2pManager.cancelConnect(channel, new ActionListener((callbackId))); 265 return waitActionListenerResult(callbackId); 266 } 267 268 /** 269 * Stop current ongoing peer discovery. 270 * 271 * @param channelId The ID of the channel for Wi-Fi P2P to operate on. 272 * @return The event posted by the callback methods of {@link ActionListener}. 273 * @throws Throwable If the P2P operation failed or timed out, or got invalid channel ID. 274 */ 275 @Rpc(description = "Stop current ongoing peer discovery.") wifiP2pStopPeerDiscovery(@pcDefaultvalue = "0") Integer channelId)276 public Bundle wifiP2pStopPeerDiscovery(@RpcDefault(value = "0") Integer channelId) 277 throws Throwable { 278 WifiP2pManager.Channel channel = getChannel(channelId); 279 String callbackId = UUID.randomUUID().toString(); 280 mP2pManager.stopPeerDiscovery(channel, new ActionListener(callbackId)); 281 return waitActionListenerResult(callbackId); 282 } 283 284 /** 285 * Create a p2p group with the current device as the group owner. 286 * 287 * @param wifiP2pConfig The configuration for the p2p group. 288 * @param channelId The ID of the channel for Wi-Fi P2P to operate on. 289 * @throws Throwable If the P2P operation failed or timed out, or got invalid channel ID. 290 */ 291 @Rpc(description = "Create a p2p group with the current device as the group owner.") wifiP2pCreateGroup( @pcOptional JSONObject wifiP2pConfig, @RpcDefault(value = "0") Integer channelId )292 public void wifiP2pCreateGroup( 293 @RpcOptional JSONObject wifiP2pConfig, 294 @RpcDefault(value = "0") Integer channelId 295 ) throws Throwable { 296 WifiP2pManager.Channel channel = getChannel(channelId); 297 String callbackId = UUID.randomUUID().toString(); 298 ActionListener actionListener = new ActionListener(callbackId); 299 WifiP2pConfig config = null; 300 if (wifiP2pConfig != null) { 301 config = JsonDeserializer.jsonToWifiP2pConfig(wifiP2pConfig); 302 } 303 Log.d("Creating wifi p2p group with config: " + String.valueOf(config)); 304 mP2pManager.createGroup(channel, config, actionListener); 305 verifyActionListenerSucceed(callbackId); 306 } 307 308 /** 309 * Start a p2p connection to a device with the specified configuration. 310 * 311 * @param wifiP2pConfig The configuration for the p2p connection. 312 * @param channelId The ID of the channel for Wi-Fi P2P to operate on. 313 * @throws Throwable If the P2P operation failed or timed out, or got invalid channel ID. 314 */ 315 @Rpc(description = "Start a p2p connection to a device with the specified configuration.") wifiP2pConnect( JSONObject wifiP2pConfig, @RpcDefault(value = "0") Integer channelId)316 public void wifiP2pConnect( 317 JSONObject wifiP2pConfig, 318 @RpcDefault(value = "0") Integer channelId) throws Throwable { 319 WifiP2pManager.Channel channel = getChannel(channelId); 320 String callbackId = UUID.randomUUID().toString(); 321 WifiP2pConfig config = JsonDeserializer.jsonToWifiP2pConfig(wifiP2pConfig); 322 Log.d("Connecting p2p group with config: " + String.valueOf(config)); 323 mP2pManager.connect(channel, config, new ActionListener(callbackId)); 324 verifyActionListenerSucceed(callbackId); 325 } 326 327 /** 328 * Accept p2p connection invitation through clicking on UI. 329 * 330 * @param deviceName The name of the device to connect. 331 * @throws WifiP2pManagerException If failed to accept invitation through UI. 332 */ 333 @Rpc(description = "Accept p2p connection invitation through clicking on UI.") wifiP2pAcceptInvitation(String deviceName)334 public void wifiP2pAcceptInvitation(String deviceName) throws WifiP2pManagerException { 335 if (!mUiDevice.wait(Until.hasObject(By.text("Invitation to connect")), 336 UI_ACTION_LONG_TIMEOUT_MS)) { 337 throw new WifiP2pManagerException( 338 "Expected connect invitation did not occur within timeout."); 339 } 340 if (!mUiDevice.wait(Until.hasObject(By.text(deviceName)), UI_ACTION_SHORT_TIMEOUT_MS)) { 341 throw new WifiP2pManagerException( 342 "The connect invitation is not triggered by expected peer device."); 343 } 344 Pattern pattern = Pattern.compile("(ACCEPT|OK|Accept)"); 345 if (!mUiDevice.wait(Until.hasObject(By.text(pattern).clazz(Button.class)), 346 UI_ACTION_SHORT_TIMEOUT_MS)) { 347 throw new WifiP2pManagerException("Accept button did not occur within timeout."); 348 } 349 UiObject2 acceptButton = mUiDevice.findObject(By.text(pattern).clazz(Button.class)); 350 if (acceptButton == null) { 351 throw new WifiP2pManagerException( 352 "There's no accept button for the connect invitation."); 353 } 354 acceptButton.click(); 355 } 356 357 /** 358 * Get p2p connect PIN code after calling {@link #wifiP2pConnect(JSONObject,Integer)} with 359 * WPS PIN. 360 * 361 * @param deviceName The name of the device to connect. 362 * @return The generated PIN as a String. 363 * @throws Throwable If failed to get PIN code. 364 */ 365 @Rpc(description = "Get p2p connect PIN code after calling wifiP2pConnect with WPS PIN.") wifiP2pGetPinCode(String deviceName)366 public String wifiP2pGetPinCode(String deviceName) throws Throwable { 367 // Wait for the 'Invitation sent' dialog to appear 368 if (!mUiDevice.wait(Until.hasObject(By.text("Invitation sent")), 369 UI_ACTION_LONG_TIMEOUT_MS)) { 370 throw new WifiP2pManagerException( 371 "Invitation sent dialog did not appear within timeout."); 372 } 373 if (!mUiDevice.wait(Until.hasObject(By.text(deviceName)), UI_ACTION_SHORT_TIMEOUT_MS)) { 374 throw new WifiP2pManagerException( 375 "The connect invitation is not triggered by expected peer device."); 376 } 377 // Find the UI lement with text='PIN:' 378 UiObject2 pinLabel = mUiDevice.findObject(By.text("PIN:")); 379 if (pinLabel == null) { 380 throw new WifiP2pManagerException("PIN label not found."); 381 } 382 // Get the sibling UI element that contains the PIN code. Use regex pattern "\d+" as PIN 383 // code must be composed entirely of numbers. 384 Pattern pattern = Pattern.compile("\\d+"); 385 UiObject2 pinValue = pinLabel.getParent().findObject(By.text(pattern)); 386 if (pinValue == null) { 387 throw new WifiP2pManagerException("Failed to find Pin code UI element."); 388 } 389 String pinCode = pinValue.getText(); 390 Log.d("Retrieved PIN code: " + pinCode); 391 // Click 'OK' to close the PIN code alert 392 UiObject2 okButton = mUiDevice.findObject(By.text("OK").clazz(Button.class)); 393 if (okButton == null) { 394 throw new WifiP2pManagerException( 395 "OK button not found in the p2p connection invitation pop-up window."); 396 } 397 okButton.click(); 398 Log.d("Closed the p2p connect invitation pop-up window."); 399 return pinCode; 400 } 401 402 /** 403 * Get p2p connect PIN code after calling {@link #wifiP2pConnect(JSONObject,Integer)} with 404 * WPS PIN. 405 * 406 * @param deviceName The name of the device to connect. 407 * @return The generated PIN as a String. 408 * @throws Throwable If failed to get PIN code. 409 */ 410 @Rpc(description = "Get p2p connect PIN code after calling wifiP2pConnect with WPS PIN.") wifiP2pGetKeypadPinCode(String deviceName)411 public String wifiP2pGetKeypadPinCode(String deviceName) throws Throwable { 412 // Wait for the 'Invitation sent' dialog to appear 413 if (!mUiDevice.wait(Until.hasObject(By.text("Invitation to connect")), 414 UI_ACTION_LONG_TIMEOUT_MS)) { 415 throw new WifiP2pManagerException( 416 "Invitation sent dialog did not appear within timeout."); 417 } 418 if (!mUiDevice.wait(Until.hasObject(By.text(deviceName)), UI_ACTION_SHORT_TIMEOUT_MS)) { 419 throw new WifiP2pManagerException( 420 "The connect invitation is not triggered by expected peer device."); 421 } 422 // Find the UI lement with text='PIN:' 423 UiObject2 pinLabel = mUiDevice.findObject(By.text("PIN:")); 424 if (pinLabel == null) { 425 throw new WifiP2pManagerException("PIN label not found."); 426 } 427 Log.d("pinLabel = " + pinLabel); 428 // Get the sibling UI element that contains the PIN code. Use regex pattern "\d+" as PIN 429 // code must be composed entirely of numbers. 430 Pattern pattern = Pattern.compile("\\d+"); 431 UiObject2 pinValue = pinLabel.getParent().findObject(By.text(pattern)); 432 if (pinValue == null) { 433 throw new WifiP2pManagerException("Failed to find Pin code UI element."); 434 } 435 String pinCode = pinValue.getText(); 436 Log.d("Retrieved PIN code: " + pinCode); 437 // Click 'OK' to close the PIN code alert 438 UiObject2 okButton = mUiDevice.findObject(By.text("Accept").clazz(Button.class)); 439 if (okButton == null) { 440 throw new WifiP2pManagerException( 441 "OK button not found in the p2p connection invitation pop-up window."); 442 } 443 okButton.click(); 444 Log.d("Closed the p2p connect invitation pop-up window."); 445 return pinCode; 446 } 447 448 /** 449 * Enters the given PIN code to accept a P2P connection invitation. 450 * 451 * @param pinCode The PIN to enter. 452 * @param deviceName The name of the device that initiated the connection. 453 * @throws WifiP2pManagerException If the PIN entry field is not found within timeout. 454 */ 455 @Rpc(description = "Enter the PIN code to accept a P2P connection invitation.") wifiP2pEnterPin(String pinCode, String deviceName)456 public void wifiP2pEnterPin(String pinCode, String deviceName) throws WifiP2pManagerException { 457 // Wait for the 'Invitation to connect' dialog to appear 458 if (!mUiDevice.wait(Until.hasObject(By.textContains("Invitation to connect")), 459 UI_ACTION_LONG_TIMEOUT_MS)) { 460 throw new WifiP2pManagerException( 461 "Invitation to connect dialog did not appear within timeout."); 462 } 463 if (!mUiDevice.wait(Until.hasObject(By.text(deviceName)), UI_ACTION_SHORT_TIMEOUT_MS)) { 464 throw new WifiP2pManagerException( 465 "The connect invitation is not triggered by expected peer device."); 466 } 467 // Find the PIN entry field 468 UiObject2 pinEntryField = mUiDevice.findObject(By.focused(true)); 469 if (pinEntryField == null) { 470 throw new WifiP2pManagerException("PIN entry field not found."); 471 } 472 // Enter the PIN code 473 pinEntryField.setText(pinCode); 474 Log.d("Entered PIN code: " + pinCode); 475 // Accept the invitation 476 Pattern acceptPattern = Pattern.compile("(ACCEPT|OK|Accept)", Pattern.CASE_INSENSITIVE); 477 UiObject2 acceptButton = mUiDevice.findObject(By.clazz(Button.class).text(acceptPattern)); 478 if (acceptButton == null) { 479 throw new WifiP2pManagerException( 480 "Failed to find accept button for p2p connect invitation."); 481 } 482 acceptButton.click(); 483 Log.d("Accepted the connection."); 484 } 485 486 /** 487 * Remove the current p2p group. 488 * 489 * @param channelId The ID of the channel for Wi-Fi P2P to operate on. 490 * @return The event posted by the callback methods of {@link ActionListener}. 491 * @throws Throwable If the P2P operation failed or timed out, or got invalid channel ID. 492 */ 493 @Rpc(description = "Remove the current p2p group.") wifiP2pRemoveGroup(@pcDefaultvalue = "0") Integer channelId)494 public Bundle wifiP2pRemoveGroup(@RpcDefault(value = "0") Integer channelId) throws Throwable { 495 WifiP2pManager.Channel channel = getChannel(channelId); 496 String callbackId = UUID.randomUUID().toString(); 497 mP2pManager.removeGroup(channel, new ActionListener(callbackId)); 498 return waitActionListenerResult(callbackId); 499 } 500 501 /** 502 * Request the number of persistent p2p group. 503 * 504 * @param callbackId The callback ID assigned by Mobly. 505 * @param channelId The ID of the channel for Wi-Fi P2P to operate on. 506 * @throws Throwable If this failed to request persistent group info, or got invalid channel ID. 507 */ 508 @AsyncRpc(description = "Request the number of persistent p2p group") wifiP2pRequestPersistentGroupInfo( String callbackId, @RpcDefault(value = "0") Integer channelId)509 public void wifiP2pRequestPersistentGroupInfo( 510 String callbackId, 511 @RpcDefault(value = "0") Integer channelId) throws Throwable { 512 WifiP2pManager.Channel channel = getChannel(channelId); 513 mP2pManager.requestPersistentGroupInfo(channel, 514 new PersistentGroupInfoListener(callbackId)); 515 } 516 517 /** 518 * Delete the persistent p2p group with the given network ID. 519 * 520 * @param networkId The network ID of the persistent p2p group to delete. 521 * @param channelId The ID of the channel for Wi-Fi P2P to operate on. 522 * @return The event posted by the callback methods of {@link ActionListener}. 523 * @throws Throwable If this failed to delete persistent group, or got invalid channel ID. 524 */ 525 @Rpc(description = "Delete the persistent p2p group with the given network ID.") wifiP2pDeletePersistentGroup(int networkId, @RpcDefault(value = "0") Integer channelId)526 public Bundle wifiP2pDeletePersistentGroup(int networkId, 527 @RpcDefault(value = "0") Integer channelId) throws Throwable { 528 WifiP2pManager.Channel channel = getChannel(channelId); 529 String callbackId = UUID.randomUUID().toString(); 530 mP2pManager.deletePersistentGroup(channel, networkId, new ActionListener(callbackId)); 531 return waitActionListenerResult(callbackId); 532 } 533 534 /** 535 * Register Upnp service as a local Wi-Fi p2p service for service discovery. 536 * @param uuid The UUID to be passed to 537 * {@link WifiP2pUpnpServiceInfo#newInstance(String, String, List)}. 538 * @param device The device to be passed to 539 * {@link WifiP2pUpnpServiceInfo#newInstance(String, String, List)}. 540 * @param services The services to be passed to 541 * {@link WifiP2pUpnpServiceInfo#newInstance(String, String, List)}. 542 * @param channelId The ID of the channel for Wi-Fi P2P to operate on. 543 * @throws Throwable If failed to add local service or got invalid channel ID. 544 */ 545 @Rpc(description = "Register Upnp service as a local Wi-Fi p2p service for service discovery.") wifiP2pAddUpnpLocalService( String uuid, String device, JSONArray services, @RpcDefault(value = "0" ) Integer channelId)546 public void wifiP2pAddUpnpLocalService( 547 String uuid, 548 String device, 549 JSONArray services, 550 @RpcDefault(value = "0" 551 ) Integer channelId) throws Throwable { 552 WifiP2pManager.Channel channel = getChannel(channelId); 553 List<String> serviceList = new ArrayList<String>(); 554 for (int i = 0; i < services.length(); i++) { 555 serviceList.add(services.getString(i)); 556 Log.d("wifiP2pAddUpnpLocalService, services: " + services.getString(i)); 557 } 558 WifiP2pServiceInfo serviceInfo = 559 WifiP2pUpnpServiceInfo.newInstance(uuid, device, serviceList); 560 561 String callbackId = UUID.randomUUID().toString(); 562 mP2pManager.addLocalService(channel, serviceInfo, new ActionListener(callbackId)); 563 verifyActionListenerSucceed(callbackId); 564 } 565 566 /** 567 * Register Bonjour service as a local Wi-Fi p2p service for service discovery. 568 * 569 * @param instanceName The instance name to be passed to 570 * {@link WifiP2pDnsSdServiceInfo#newInstance(String, String, Map)}. 571 * @param serviceType The serviceType to be passed to 572 * {@link WifiP2pDnsSdServiceInfo#newInstance(String, String, Map)}. 573 * @param txtMap The TXT record to be passed to 574 * {@link WifiP2pDnsSdServiceInfo#newInstance(String, String, Map)}. 575 * @param channelId The ID of the channel for Wi-Fi P2P to operate on. 576 * @throws Throwable If failed to add local service or got invalid channel ID. 577 */ 578 @Rpc(description = "Register Bonjour service as a local Wi-Fi p2p service for service" 579 + " discovery.") wifiP2pAddBonjourLocalService(String instanceName, String serviceType, @RpcOptional JSONObject txtMap, @RpcDefault(value = "0") Integer channelId )580 public void wifiP2pAddBonjourLocalService(String instanceName, 581 String serviceType, 582 @RpcOptional JSONObject txtMap, 583 @RpcDefault(value = "0") Integer channelId 584 ) throws Throwable { 585 WifiP2pManager.Channel channel = getChannel(channelId); 586 Map<String, String> map = null; 587 if (txtMap != null) { 588 map = new HashMap<String, String>(); 589 Iterator<String> keyIterator = txtMap.keys(); 590 while (keyIterator.hasNext()) { 591 String key = keyIterator.next(); 592 map.put(key, txtMap.getString(key)); 593 } 594 } 595 WifiP2pDnsSdServiceInfo serviceInfo = 596 WifiP2pDnsSdServiceInfo.newInstance(instanceName, serviceType, map); 597 598 String callbackId = UUID.randomUUID().toString(); 599 mP2pManager.addLocalService(channel, serviceInfo, new ActionListener(callbackId)); 600 verifyActionListenerSucceed(callbackId); 601 } 602 603 /** 604 * Clear all registered local services of service discovery. 605 * 606 * @param channelId The ID of the channel for Wi-Fi P2P to operate on. 607 * @throws Throwable If failed to clear local services or got invalid channel ID. 608 */ 609 @Rpc(description = "Clear all registered local services of service discovery.") wifiP2pClearLocalServices(@pcDefaultvalue = "0") Integer channelId)610 public void wifiP2pClearLocalServices(@RpcDefault(value = "0") Integer channelId) 611 throws Throwable { 612 WifiP2pManager.Channel channel = getChannel(channelId); 613 String callbackId = UUID.randomUUID().toString(); 614 mP2pManager.clearLocalServices(channel, new ActionListener(callbackId)); 615 waitActionListenerResult(callbackId); 616 } 617 618 /** 619 * Add a service discovery request. 620 * 621 * @param protocolType The protocol type of the service discovery request. 622 * @param channelId The ID of the channel for Wi-Fi P2P to operate on. 623 * @return The ID of the service request, which is used when calling 624 * @throws Throwable If add service request action timed out or got invalid channel ID. 625 */ 626 @Rpc(description = "Add a service discovery request.") wifiP2pAddServiceRequest( int protocolType, @RpcDefault(value = "0") Integer channelId )627 public Integer wifiP2pAddServiceRequest( 628 int protocolType, @RpcDefault(value = "0") Integer channelId 629 ) throws Throwable { 630 WifiP2pManager.Channel channel = getChannel(channelId); 631 632 WifiP2pServiceRequest request = WifiP2pServiceRequest.newInstance(protocolType); 633 mServiceRequestCnt += 1; 634 mServiceRequests.put(mServiceRequestCnt, request); 635 636 String callbackId = UUID.randomUUID().toString(); 637 mP2pManager.addServiceRequest(channel, request, new ActionListener(callbackId)); 638 verifyActionListenerSucceed(callbackId); 639 return mServiceRequestCnt; 640 } 641 642 /** 643 * Add a service Upnp discovery request. 644 * 645 * @param serviceType The service type to be passed to {@link WifiP2pUpnpServiceRequest}. 646 * @param channelId The ID of the channel for Wi-Fi P2P to operate on. 647 * @return The ID of the service request, which is used when calling 648 * {@link #wifiP2pRemoveServiceRequest(int, Integer)}. 649 * @throws Throwable If add service request action timed out or got invalid channel ID. 650 */ 651 @Rpc(description = "Add a service Upnp discovery request.") wifiP2pAddUpnpServiceRequest( @pcOptional String serviceType, @RpcDefault(value = "0") Integer channelId )652 public Integer wifiP2pAddUpnpServiceRequest( 653 @RpcOptional String serviceType, 654 @RpcDefault(value = "0") Integer channelId 655 ) throws Throwable { 656 WifiP2pManager.Channel channel = getChannel(channelId); 657 WifiP2pUpnpServiceRequest request; 658 if (serviceType == null) { 659 request = WifiP2pUpnpServiceRequest.newInstance(); 660 } else { 661 request = WifiP2pUpnpServiceRequest.newInstance(serviceType); 662 } 663 mServiceRequestCnt += 1; 664 mServiceRequests.put(mServiceRequestCnt, request); 665 666 String callbackId = UUID.randomUUID().toString(); 667 mP2pManager.addServiceRequest(channel, request, new ActionListener(callbackId)); 668 verifyActionListenerSucceed(callbackId); 669 return mServiceRequestCnt; 670 } 671 672 /** 673 * Add a service Bonjour discovery request. 674 * 675 * @param instanceName The instance name to be passed to 676 * {@link WifiP2pDnsSdServiceRequest#newInstance(String, String)}. 677 * @param serviceType The service type to be passed to 678 * {@link WifiP2pDnsSdServiceRequest#newInstance(String, String)}. 679 * @param channelId The ID of the channel for Wi-Fi P2P to operate on. 680 * @return The ID of the service request, which is used when calling 681 * {@link #wifiP2pRemoveServiceRequest(int, Integer)}. 682 * @throws Throwable If add service request action timed out or got invalid channel ID. 683 */ 684 @Rpc(description = "Add a service Bonjour discovery request.") wifiP2pAddBonjourServiceRequest( @pcOptional String instanceName, @RpcOptional String serviceType, @RpcDefault(value = "0") Integer channelId )685 public Integer wifiP2pAddBonjourServiceRequest( 686 @RpcOptional String instanceName, 687 @RpcOptional String serviceType, 688 @RpcDefault(value = "0") Integer channelId 689 ) throws Throwable { 690 WifiP2pManager.Channel channel = getChannel(channelId); 691 WifiP2pDnsSdServiceRequest request; 692 if (instanceName != null) { 693 request = WifiP2pDnsSdServiceRequest.newInstance(instanceName, serviceType); 694 } else if (serviceType == null) { 695 request = WifiP2pDnsSdServiceRequest.newInstance(); 696 } else { 697 request = WifiP2pDnsSdServiceRequest.newInstance(serviceType); 698 } 699 mServiceRequestCnt += 1; 700 mServiceRequests.put(mServiceRequestCnt, request); 701 702 String callbackId = UUID.randomUUID().toString(); 703 mP2pManager.addServiceRequest(channel, request, new ActionListener(callbackId)); 704 verifyActionListenerSucceed(callbackId); 705 return mServiceRequestCnt; 706 } 707 708 /** 709 * Remove a service discovery request. 710 * 711 * @param index The index of the service request to remove. 712 * @param channelId The ID of the channel for Wi-Fi P2P to operate on. 713 * @throws Throwable If remove service request action timed out or got invalid channel ID. 714 */ 715 @Rpc(description = "Remove a service discovery request.") wifiP2pRemoveServiceRequest(int index, @RpcDefault(value = "0") Integer channelId)716 public void wifiP2pRemoveServiceRequest(int index, @RpcDefault(value = "0") Integer channelId) 717 throws Throwable { 718 WifiP2pManager.Channel channel = getChannel(channelId); 719 String callbackId = UUID.randomUUID().toString(); 720 WifiP2pServiceRequest serviceRequest = mServiceRequests.remove(index); 721 if (serviceRequest == null) { 722 throw new WifiP2pManagerException("Service request not found. Please use the request ID" 723 + " returned by `wifiP2pAddServiceRequest`."); 724 } 725 mP2pManager.removeServiceRequest(channel, serviceRequest, 726 new ActionListener(callbackId)); 727 verifyActionListenerSucceed(callbackId); 728 } 729 730 /** 731 * Clear all registered service discovery requests. 732 * 733 * @param channelId The ID of the channel for Wi-Fi P2P to operate on. 734 * @throws Throwable If clear service requests action timed out or got invalid channel ID. 735 */ 736 @Rpc(description = "Clear all registered service discovery requests.") wifiP2pClearServiceRequests(@pcDefaultvalue = "0") Integer channelId)737 public void wifiP2pClearServiceRequests(@RpcDefault(value = "0") Integer channelId) 738 throws Throwable { 739 WifiP2pManager.Channel channel = getChannel(channelId); 740 String callbackId = UUID.randomUUID().toString(); 741 mP2pManager.clearServiceRequests(channel, new ActionListener(callbackId)); 742 waitActionListenerResult(callbackId); 743 } 744 745 /** 746 * Set a callback to be invoked on receiving Upnp service discovery response. 747 * 748 * @param callbackId The callback ID assigned by Mobly. 749 * @param channelId The ID of the channel for Wi-Fi P2P to operate on. 750 * @throws WifiP2pManagerException If the channel is not created. 751 */ 752 @AsyncRpc(description = "Set a callback to be invoked on receiving Upnp service discovery " 753 + " response.") wifiP2pSetUpnpResponseListener(String callbackId, @RpcDefault(value = "0") Integer channelId)754 public void wifiP2pSetUpnpResponseListener(String callbackId, 755 @RpcDefault(value = "0") Integer channelId) 756 throws WifiP2pManagerException { 757 WifiP2pManager.Channel channel = getChannel(channelId); 758 mP2pManager.setUpnpServiceResponseListener(channel, 759 new UpnpServiceResponseListener(callbackId)); 760 } 761 762 /** 763 * Unset the Upnp service response callback set by `wifiP2pSetUpnpResponseListener`. 764 * 765 * @param channelId The ID of the channel for Wi-Fi P2P to operate on. 766 * @throws WifiP2pManagerException If the channel is not created. 767 */ 768 @Rpc(description = "Unset the Upnp service response callback set by " 769 + "`wifiP2pSetUpnpResponseListener`.") wifiP2pUnsetUpnpResponseListener(@pcDefaultvalue = "0") Integer channelId)770 public void wifiP2pUnsetUpnpResponseListener(@RpcDefault(value = "0") Integer channelId) 771 throws WifiP2pManagerException { 772 WifiP2pManager.Channel channel = getChannel(channelId); 773 mP2pManager.setUpnpServiceResponseListener(channel, null); 774 } 775 776 /** 777 * Set a callback to be invoked on receiving Bonjour service discovery response. 778 * 779 * @param callbackId The callback ID assigned by Mobly. 780 * @param channelId The ID of the channel for Wi-Fi P2P to operate on. 781 * @throws WifiP2pManagerException If the channel is not created. 782 */ 783 @AsyncRpc(description = "Set a callback to be invoked on receiving Bonjour service discovery" 784 + " response.") wifiP2pSetDnsSdResponseListeners(String callbackId, @RpcDefault(value = "0") Integer channelId)785 public void wifiP2pSetDnsSdResponseListeners(String callbackId, 786 @RpcDefault(value = "0") Integer channelId) 787 throws WifiP2pManagerException { 788 WifiP2pManager.Channel channel = getChannel(channelId); 789 mP2pManager.setDnsSdResponseListeners(channel, new DnsSdServiceResponseListener(callbackId), 790 new DnsSdTxtRecordListener(callbackId)); 791 } 792 793 /** 794 * Unset the Bonjour service response callback set by `wifiP2pSetDnsSdResponseListeners`. 795 * 796 * @param channelId The ID of the channel for Wi-Fi P2P to operate on. 797 * @throws WifiP2pManagerException If the channel is not created. 798 */ 799 @Rpc(description = "Unset the Bonjour service response callback set by " 800 + "`wifiP2pSetDnsSdResponseListeners`.") wifiP2pUnsetDnsSdResponseListeners(@pcDefaultvalue = "0") Integer channelId)801 public void wifiP2pUnsetDnsSdResponseListeners(@RpcDefault(value = "0") Integer channelId) 802 throws WifiP2pManagerException { 803 WifiP2pManager.Channel channel = getChannel(channelId); 804 mP2pManager.setDnsSdResponseListeners(channel, null, null); 805 } 806 807 /** 808 * Initiate service discovery. 809 * 810 * @param channelId The ID of the channel for Wi-Fi P2P to operate on. 811 * @throws Throwable If the P2P operation failed or timed out, or got invalid channel ID. 812 */ 813 @Rpc(description = "Initiate service discovery.") wifiP2pDiscoverServices( @pcDefaultvalue = "0") Integer channelId )814 public void wifiP2pDiscoverServices( 815 @RpcDefault(value = "0") Integer channelId 816 ) throws Throwable { 817 WifiP2pManager.Channel channel = getChannel(channelId); 818 String callbackId = UUID.randomUUID().toString(); 819 mP2pManager.discoverServices(channel, new ActionListener(callbackId)); 820 verifyActionListenerSucceed(callbackId); 821 } 822 823 /** 824 * Close the current P2P connection and indicate to the P2P service that connections created by 825 * the app can be removed. 826 */ 827 @Rpc(description = "Close all P2P connections and indicate to the P2P service that" 828 + " connections created by the app can be removed.") p2pClose()829 public void p2pClose() { 830 for (Map.Entry<Integer, WifiP2pManager.Channel> entry : mChannels.entrySet()) { 831 Integer channelId = entry.getKey(); 832 WifiP2pManager.Channel channel = entry.getValue(); 833 Log.d("Cleaning p2p resources associated with channelId=" + channelId); 834 if (channel != null) { 835 try { 836 wifiP2pClearServiceRequests(channelId); 837 } catch (Throwable e) { 838 Log.e("Failed to clear service requests on channelId=" + channelId 839 + ", error message: " + e.getMessage()); 840 } 841 mP2pManager.setDnsSdResponseListeners(channel, null, null); 842 mP2pManager.setUpnpServiceResponseListener(channel, null); 843 try { 844 wifiP2pClearLocalServices(channelId); 845 } catch (Throwable e) { 846 Log.e("Failed to clear local services on channelId=" + channelId 847 + ", error message: " + e.getMessage()); 848 } 849 channel.close(); 850 } 851 } 852 mChannels.clear(); 853 mChannelCnt = -1; 854 if (mStateChangedReceiver != null) { 855 mContext.unregisterReceiver(mStateChangedReceiver); 856 mStateChangedReceiver = null; 857 } 858 } 859 860 @Override shutdown()861 public void shutdown() { 862 p2pClose(); 863 } 864 865 private class WifiP2pStateChangedReceiver extends BroadcastReceiver { 866 private String mCallbackId; 867 WifiP2pStateChangedReceiver(@onNull String callbackId)868 private WifiP2pStateChangedReceiver(@NonNull String callbackId) { 869 this.mCallbackId = callbackId; 870 } 871 872 @Override onReceive(Context mContext, Intent intent)873 public void onReceive(Context mContext, Intent intent) { 874 String action = intent.getAction(); 875 SnippetEvent event = new SnippetEvent(mCallbackId, action); 876 String logPrefix = TAG + ": WifiP2pStateChangedReceiver: onReceive: Got intent: action=" 877 + action + ", "; 878 switch (action) { 879 case WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION: 880 int wifiP2pState = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, 0); 881 Log.d(logPrefix + "wifiP2pState=" + wifiP2pState); 882 event.getData().putInt(WifiP2pManager.EXTRA_WIFI_STATE, wifiP2pState); 883 break; 884 case WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION: 885 WifiP2pDeviceList peerList = (WifiP2pDeviceList) intent.getParcelableExtra( 886 WifiP2pManager.EXTRA_P2P_DEVICE_LIST); 887 Log.d(logPrefix + "p2pPeerList=" + BundleUtils.fromWifiP2pDeviceList(peerList)); 888 event.getData().putParcelableArrayList( 889 EVENT_KEY_PEER_LIST, BundleUtils.fromWifiP2pDeviceList(peerList)); 890 break; 891 case WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION: 892 NetworkInfo networkInfo = intent.getParcelableExtra( 893 WifiP2pManager.EXTRA_NETWORK_INFO); 894 WifiP2pInfo p2pInfo = (WifiP2pInfo) intent.getParcelableExtra( 895 WifiP2pManager.EXTRA_WIFI_P2P_INFO); 896 WifiP2pGroup p2pGroup = (WifiP2pGroup) intent.getParcelableExtra( 897 WifiP2pManager.EXTRA_WIFI_P2P_GROUP); 898 Log.d(logPrefix + "networkInfo=" + String.valueOf(networkInfo) + ", p2pInfo=" 899 + String.valueOf(p2pInfo) + ", p2pGroup=" + String.valueOf(p2pGroup)); 900 if (networkInfo != null) { 901 event.getData().putBoolean("isConnected", networkInfo.isConnected()); 902 } else { 903 event.getData().putBoolean("isConnected", false); 904 } 905 event.getData().putBundle( 906 EVENT_KEY_P2P_INFO, BundleUtils.fromWifiP2pInfo(p2pInfo)); 907 event.getData().putBundle( 908 EVENT_KEY_P2P_GROUP, BundleUtils.fromWifiP2pGroup(p2pGroup)); 909 break; 910 } 911 EventCache.getInstance().postEvent(event); 912 } 913 } 914 915 private static class ActionListener implements WifiP2pManager.ActionListener { 916 public static final String CALLBACK_EVENT_NAME = "WifiP2pManagerActionListenerCallback"; 917 918 private final String mCallbackId; 919 ActionListener(String callbackId)920 ActionListener(String callbackId) { 921 this.mCallbackId = callbackId; 922 } 923 924 @Override onSuccess()925 public void onSuccess() { 926 SnippetEvent event = new SnippetEvent(mCallbackId, CALLBACK_EVENT_NAME); 927 event.getData().putString(EVENT_KEY_CALLBACK_NAME, ACTION_LISTENER_ON_SUCCESS); 928 EventCache.getInstance().postEvent(event); 929 } 930 931 @Override onFailure(int reason)932 public void onFailure(int reason) { 933 SnippetEvent event = new SnippetEvent(mCallbackId, CALLBACK_EVENT_NAME); 934 event.getData().putString(EVENT_KEY_CALLBACK_NAME, ACTION_LISTENER_ON_FAILURE); 935 event.getData().putInt(EVENT_KEY_REASON, reason); 936 EventCache.getInstance().postEvent(event); 937 } 938 } 939 940 private static class WifiP2pConnectionInfoListener 941 implements WifiP2pManager.ConnectionInfoListener { 942 public static final String EVENT_NAME_ON_CONNECTION_INFO = 943 "WifiP2pOnConnectionInfoAvailable"; 944 private final String mCallbackId; 945 WifiP2pConnectionInfoListener(String callbackId)946 public WifiP2pConnectionInfoListener(String callbackId) { 947 this.mCallbackId = callbackId; 948 } 949 950 @Override onConnectionInfoAvailable(WifiP2pInfo info)951 public void onConnectionInfoAvailable(WifiP2pInfo info) { 952 Log.d(TAG + ": onConnectionInfoAvailable: " + info.toString()); 953 SnippetEvent event = new SnippetEvent(mCallbackId, EVENT_NAME_ON_CONNECTION_INFO); 954 event.getData().putBoolean("groupFormed", info.groupFormed); 955 event.getData().putBoolean("isGroupOwner", info.isGroupOwner); 956 event.getData().putString("groupOwnerHostAddress", info.groupOwnerAddress.toString()); 957 EventCache.getInstance().postEvent(event); 958 } 959 } 960 961 private static class DeviceInfoListener implements WifiP2pManager.DeviceInfoListener { 962 public static final String EVENT_NAME_ON_DEVICE_INFO = "WifiP2pOnDeviceInfoAvailable"; 963 964 private final String mCallbackId; 965 DeviceInfoListener(String callbackId)966 DeviceInfoListener(String callbackId) { 967 this.mCallbackId = callbackId; 968 } 969 970 @Override onDeviceInfoAvailable(WifiP2pDevice device)971 public void onDeviceInfoAvailable(WifiP2pDevice device) { 972 if (device == null) { 973 return; 974 } 975 Log.d(TAG + ": onDeviceInfoAvailable: " + device.toString()); 976 SnippetEvent event = new SnippetEvent(mCallbackId, EVENT_NAME_ON_DEVICE_INFO); 977 event.getData().putBundle(EVENT_KEY_P2P_DEVICE, BundleUtils.fromWifiP2pDevice(device)); 978 EventCache.getInstance().postEvent(event); 979 } 980 } 981 982 private static class PeerListListener implements WifiP2pManager.PeerListListener { 983 private final String mCallbackId; 984 PeerListListener(String callbackId)985 PeerListListener(String callbackId) { 986 this.mCallbackId = callbackId; 987 } 988 989 @Override onPeersAvailable(WifiP2pDeviceList newPeers)990 public void onPeersAvailable(WifiP2pDeviceList newPeers) { 991 Log.d(TAG + ": onPeersAvailable: " + newPeers.getDeviceList()); 992 ArrayList<Bundle> devices = BundleUtils.fromWifiP2pDeviceList(newPeers); 993 SnippetEvent event = new SnippetEvent(mCallbackId, "WifiP2pOnPeersAvailable"); 994 event.getData().putParcelableArrayList(EVENT_KEY_PEER_LIST, devices); 995 event.getData().putLong(EVENT_KEY_TIMESTAMP_MS, System.currentTimeMillis()); 996 EventCache.getInstance().postEvent(event); 997 } 998 } 999 1000 private static class PersistentGroupInfoListener 1001 implements WifiP2pManager.PersistentGroupInfoListener { 1002 private final String mCallbackId; 1003 PersistentGroupInfoListener(String callbackId)1004 PersistentGroupInfoListener(String callbackId) { 1005 this.mCallbackId = callbackId; 1006 } 1007 1008 @Override onPersistentGroupInfoAvailable(@onNull WifiP2pGroupList groups)1009 public void onPersistentGroupInfoAvailable(@NonNull WifiP2pGroupList groups) { 1010 Log.d(TAG + ": onPersistentGroupInfoAvailable: " + groups.toString()); 1011 SnippetEvent event = new SnippetEvent(mCallbackId, "onPersistentGroupInfoAvailable"); 1012 event.getData() 1013 .putParcelableArrayList("groupList", BundleUtils.fromWifiP2pGroupList(groups)); 1014 EventCache.getInstance().postEvent(event); 1015 } 1016 } 1017 1018 private static class UpnpServiceResponseListener 1019 implements WifiP2pManager.UpnpServiceResponseListener { 1020 private final String mCallbackId; 1021 UpnpServiceResponseListener(String callbackId)1022 UpnpServiceResponseListener(String callbackId) { 1023 this.mCallbackId = callbackId; 1024 } 1025 1026 @Override onUpnpServiceAvailable(List<String> uniqueServiceNames, WifiP2pDevice srcDevice)1027 public void onUpnpServiceAvailable(List<String> uniqueServiceNames, 1028 WifiP2pDevice srcDevice) { 1029 Log.d(TAG + ": onUpnpServiceAvailable: service names: " + uniqueServiceNames); 1030 SnippetEvent event = new SnippetEvent(mCallbackId, "onUpnpServiceAvailable"); 1031 event.getData() 1032 .putBundle(EVENT_KEY_SOURCE_DEVICE, BundleUtils.fromWifiP2pDevice(srcDevice)); 1033 event.getData() 1034 .putStringArrayList(EVENT_KEY_SERVICE_LIST, new ArrayList(uniqueServiceNames)); 1035 EventCache.getInstance().postEvent(event); 1036 } 1037 } 1038 1039 private static class DnsSdServiceResponseListener 1040 implements WifiP2pManager.DnsSdServiceResponseListener { 1041 private final String mCallbackId; 1042 DnsSdServiceResponseListener(String callbackId)1043 DnsSdServiceResponseListener(String callbackId) { 1044 this.mCallbackId = callbackId; 1045 } 1046 1047 @Override onDnsSdServiceAvailable(String instanceName, String registrationType, WifiP2pDevice srcDevice)1048 public void onDnsSdServiceAvailable(String instanceName, String registrationType, 1049 WifiP2pDevice srcDevice) { 1050 SnippetEvent event = new SnippetEvent(mCallbackId, "onDnsSdServiceAvailable"); 1051 event.getData().putString(EVENT_KEY_INSTANCE_NAME, instanceName); 1052 event.getData().putString(EVENT_KEY_REGISTRATION_TYPE, registrationType); 1053 event.getData() 1054 .putBundle(EVENT_KEY_SOURCE_DEVICE, BundleUtils.fromWifiP2pDevice(srcDevice)); 1055 EventCache.getInstance().postEvent(event); 1056 } 1057 } 1058 1059 private static class DnsSdTxtRecordListener implements WifiP2pManager.DnsSdTxtRecordListener { 1060 private final String mCallbackId; 1061 DnsSdTxtRecordListener(String callbackId)1062 DnsSdTxtRecordListener(String callbackId) { 1063 this.mCallbackId = callbackId; 1064 } 1065 1066 @Override onDnsSdTxtRecordAvailable(String fullDomainName, Map<String, String> txtRecordMap, WifiP2pDevice srcDevice)1067 public void onDnsSdTxtRecordAvailable(String fullDomainName, 1068 Map<String, String> txtRecordMap, WifiP2pDevice srcDevice) { 1069 SnippetEvent event = new SnippetEvent(mCallbackId, "onDnsSdTxtRecordAvailable"); 1070 event.getData().putString(EVENT_KEY_FULL_DOMAIN_NAME, fullDomainName); 1071 Bundle txtMap = new Bundle(); 1072 for (String key : txtRecordMap.keySet()) { 1073 txtMap.putString(key, txtRecordMap.get(key)); 1074 } 1075 event.getData().putBundle(EVENT_KEY_TXT_RECORD_MAP, txtMap); 1076 event.getData() 1077 .putBundle(EVENT_KEY_SOURCE_DEVICE, BundleUtils.fromWifiP2pDevice(srcDevice)); 1078 EventCache.getInstance().postEvent(event); 1079 } 1080 } 1081 1082 /** 1083 * Get the channel by channel ID. 1084 * 1085 * @param channelId The ID of the channel for Wi-Fi P2P to operate on. 1086 * @return The channel. 1087 * @throws WifiP2pManagerException If the channel is not created. 1088 */ getChannel(int channelId)1089 private WifiP2pManager.Channel getChannel(int channelId) 1090 throws WifiP2pManagerException { 1091 WifiP2pManager.Channel channel = mChannels.get(channelId); 1092 if (channel == null) { 1093 Log.e(TAG + ": getChannel : channel keys" + mChannels.keySet()); 1094 throw new WifiP2pManagerException( 1095 "The channelId " + channelId + " is wrong. Please use the channelId returned " 1096 + "by calling `wifiP2pInitialize` or `wifiP2pInitExtraChannel`."); 1097 } 1098 return channel; 1099 } 1100 1101 /** 1102 * Check if the device supports Wi-Fi Direct. 1103 * 1104 * @throws WifiP2pManagerException If the device does not support Wi-Fi Direct. 1105 */ checkP2pManager()1106 private void checkP2pManager() throws WifiP2pManagerException { 1107 if (mP2pManager == null) { 1108 throw new WifiP2pManagerException("Device does not support Wi-Fi Direct."); 1109 } 1110 } 1111 1112 /** 1113 * Check permissions for the given permissions. 1114 * 1115 * @param context The context to check permissions. 1116 * @param permissions The permissions to check. 1117 */ checkPermissions(Context context, String... permissions)1118 private static void checkPermissions(Context context, String... permissions) { 1119 for (String permission : permissions) { 1120 if (context.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) { 1121 throw new SecurityException( 1122 "Permission denied (missing " + permission + " permission)"); 1123 } 1124 } 1125 } 1126 1127 /** 1128 * Wait until any callback of {@link ActionListener} is triggered. 1129 * 1130 * @param callbackId The callback ID associated with the action listener. 1131 * @return The event posted by the callback methods of {@link ActionListener}. 1132 * @throws Throwable If the action timed out. 1133 */ waitActionListenerResult(String callbackId)1134 private Bundle waitActionListenerResult(String callbackId) throws Throwable { 1135 SnippetEvent event = waitForSnippetEvent(callbackId, ActionListener.CALLBACK_EVENT_NAME, 1136 TIMEOUT_SHORT_MS); 1137 Log.d("Got action listener result event: " + event.getData().toString()); 1138 return event.getData(); 1139 } 1140 1141 /** 1142 * Wait until any callback of {@link ActionListener} is triggered and verify it succeeded. 1143 * 1144 * @param callbackId The callback ID associated with the action listener. 1145 * @throws Throwable If the action timed out or failed. 1146 */ verifyActionListenerSucceed(String callbackId)1147 private void verifyActionListenerSucceed(String callbackId) throws Throwable { 1148 Bundle eventData = waitActionListenerResult(callbackId); 1149 String result = eventData.getString(EVENT_KEY_CALLBACK_NAME); 1150 if (Objects.equals(ACTION_LISTENER_ON_SUCCESS, result)) { 1151 return; 1152 } 1153 if (Objects.equals(ACTION_LISTENER_ON_FAILURE, result)) { 1154 // Please keep reason code in error message for client side to check the reason. 1155 throw new WifiP2pManagerException( 1156 "Action failed with reason_code=" + eventData.getInt(EVENT_KEY_REASON) 1157 ); 1158 } 1159 throw new WifiP2pManagerException("Action got unknown event: " + eventData.toString()); 1160 } 1161 1162 /** 1163 * Wait for a SnippetEvent with the given callbackId and eventName. 1164 * 1165 * @param callbackId The callback ID associated with the action listener. 1166 * @param eventName The event name to wait for. 1167 * @param timeout The timeout in milliseconds. 1168 * @return The SnippetEvent. 1169 * @throws Throwable If the action timed out. 1170 */ waitForSnippetEvent(String callbackId, String eventName, Integer timeout)1171 private static SnippetEvent waitForSnippetEvent(String callbackId, String eventName, 1172 Integer timeout) throws Throwable { 1173 String qId = EventCache.getQueueId(callbackId, eventName); 1174 LinkedBlockingDeque<SnippetEvent> q = EventCache.getInstance().getEventDeque(qId); 1175 SnippetEvent result; 1176 try { 1177 result = q.pollFirst(timeout, TimeUnit.MILLISECONDS); 1178 } catch (InterruptedException e) { 1179 throw e.getCause(); 1180 } 1181 1182 if (result == null) { 1183 throw new TimeoutException( 1184 "Timed out waiting(" + timeout + " millis) for SnippetEvent: " + callbackId); 1185 } 1186 return result; 1187 } 1188 } 1189