WifiDisplayController.java revision c9bd4ca005768cb30aaaa44c1171e113c13cd107
1/* 2 * Copyright (C) 2012 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 17package com.android.server.display; 18 19import com.android.internal.util.DumpUtils; 20 21import android.content.BroadcastReceiver; 22import android.content.Context; 23import android.content.Intent; 24import android.content.IntentFilter; 25import android.hardware.display.WifiDisplay; 26import android.media.AudioManager; 27import android.media.RemoteDisplay; 28import android.net.NetworkInfo; 29import android.net.wifi.p2p.WifiP2pConfig; 30import android.net.wifi.p2p.WifiP2pDevice; 31import android.net.wifi.p2p.WifiP2pDeviceList; 32import android.net.wifi.p2p.WifiP2pGroup; 33import android.net.wifi.p2p.WifiP2pManager; 34import android.net.wifi.p2p.WifiP2pWfdInfo; 35import android.net.wifi.p2p.WifiP2pManager.ActionListener; 36import android.net.wifi.p2p.WifiP2pManager.Channel; 37import android.net.wifi.p2p.WifiP2pManager.GroupInfoListener; 38import android.net.wifi.p2p.WifiP2pManager.PeerListListener; 39import android.os.Handler; 40import android.util.Slog; 41import android.view.Surface; 42 43import java.io.PrintWriter; 44import java.net.Inet4Address; 45import java.net.InetAddress; 46import java.net.NetworkInterface; 47import java.net.SocketException; 48import java.util.ArrayList; 49import java.util.Enumeration; 50 51/** 52 * Manages all of the various asynchronous interactions with the {@link WifiP2pManager} 53 * on behalf of {@link WifiDisplayAdapter}. 54 * <p> 55 * This code is isolated from {@link WifiDisplayAdapter} so that we can avoid 56 * accidentally introducing any deadlocks due to the display manager calling 57 * outside of itself while holding its lock. It's also way easier to write this 58 * asynchronous code if we can assume that it is single-threaded. 59 * </p><p> 60 * The controller must be instantiated on the handler thread. 61 * </p> 62 */ 63final class WifiDisplayController implements DumpUtils.Dump { 64 private static final String TAG = "WifiDisplayController"; 65 private static final boolean DEBUG = false; 66 67 private static final int DEFAULT_CONTROL_PORT = 7236; 68 private static final int MAX_THROUGHPUT = 50; 69 private static final int CONNECTION_TIMEOUT_SECONDS = 30; 70 private static final int RTSP_TIMEOUT_SECONDS = 15; 71 72 private static final int DISCOVER_PEERS_MAX_RETRIES = 10; 73 private static final int DISCOVER_PEERS_RETRY_DELAY_MILLIS = 500; 74 75 private static final int CONNECT_MAX_RETRIES = 3; 76 private static final int CONNECT_RETRY_DELAY_MILLIS = 500; 77 78 // A unique token to identify the remote submix that is managed by Wifi display. 79 // It must match what the media server uses when it starts recording the submix 80 // for transmission. We use 0 although the actual value is currently ignored. 81 private static final int REMOTE_SUBMIX_ADDRESS = 0; 82 83 private final Context mContext; 84 private final Handler mHandler; 85 private final Listener mListener; 86 87 private final WifiP2pManager mWifiP2pManager; 88 private final Channel mWifiP2pChannel; 89 90 private final AudioManager mAudioManager; 91 92 private boolean mWifiP2pEnabled; 93 private boolean mWfdEnabled; 94 private boolean mWfdEnabling; 95 private NetworkInfo mNetworkInfo; 96 97 private final ArrayList<WifiP2pDevice> mKnownWifiDisplayPeers = 98 new ArrayList<WifiP2pDevice>(); 99 100 // True if there is a call to discoverPeers in progress. 101 private boolean mDiscoverPeersInProgress; 102 103 // Number of discover peers retries remaining. 104 private int mDiscoverPeersRetriesLeft; 105 106 // The device to which we want to connect, or null if we want to be disconnected. 107 private WifiP2pDevice mDesiredDevice; 108 109 // The device to which we are currently connecting, or null if we have already connected 110 // or are not trying to connect. 111 private WifiP2pDevice mConnectingDevice; 112 113 // The device to which we are currently connected, which means we have an active P2P group. 114 private WifiP2pDevice mConnectedDevice; 115 116 // The group info obtained after connecting. 117 private WifiP2pGroup mConnectedDeviceGroupInfo; 118 119 // Number of connection retries remaining. 120 private int mConnectionRetriesLeft; 121 122 // The remote display that is listening on the connection. 123 // Created after the Wifi P2P network is connected. 124 private RemoteDisplay mRemoteDisplay; 125 126 // The remote display interface. 127 private String mRemoteDisplayInterface; 128 129 // True if RTSP has connected. 130 private boolean mRemoteDisplayConnected; 131 132 // True if the remote submix is enabled. 133 private boolean mRemoteSubmixOn; 134 135 public WifiDisplayController(Context context, Handler handler, Listener listener) { 136 mContext = context; 137 mHandler = handler; 138 mListener = listener; 139 140 mWifiP2pManager = (WifiP2pManager)context.getSystemService(Context.WIFI_P2P_SERVICE); 141 mWifiP2pChannel = mWifiP2pManager.initialize(context, handler.getLooper(), null); 142 143 mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE); 144 145 IntentFilter intentFilter = new IntentFilter(); 146 intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION); 147 intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION); 148 intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); 149 context.registerReceiver(mWifiP2pReceiver, intentFilter); 150 } 151 152 public void dump(PrintWriter pw) { 153 pw.println("mWifiP2pEnabled=" + mWifiP2pEnabled); 154 pw.println("mWfdEnabled=" + mWfdEnabled); 155 pw.println("mWfdEnabling=" + mWfdEnabling); 156 pw.println("mNetworkInfo=" + mNetworkInfo); 157 pw.println("mDiscoverPeersInProgress=" + mDiscoverPeersInProgress); 158 pw.println("mDiscoverPeersRetriesLeft=" + mDiscoverPeersRetriesLeft); 159 pw.println("mDesiredDevice=" + describeWifiP2pDevice(mDesiredDevice)); 160 pw.println("mConnectingDisplay=" + describeWifiP2pDevice(mConnectingDevice)); 161 pw.println("mConnectedDevice=" + describeWifiP2pDevice(mConnectedDevice)); 162 pw.println("mConnectionRetriesLeft=" + mConnectionRetriesLeft); 163 pw.println("mRemoteDisplay=" + mRemoteDisplay); 164 pw.println("mRemoteDisplayInterface=" + mRemoteDisplayInterface); 165 pw.println("mRemoteDisplayConnected=" + mRemoteDisplayConnected); 166 pw.println("mRemoteSubmixOn=" + mRemoteSubmixOn); 167 168 pw.println("mKnownWifiDisplayPeers: size=" + mKnownWifiDisplayPeers.size()); 169 for (WifiP2pDevice device : mKnownWifiDisplayPeers) { 170 pw.println(" " + describeWifiP2pDevice(device)); 171 } 172 } 173 174 public void requestScan() { 175 discoverPeers(); 176 } 177 178 public void requestConnect(String address) { 179 for (WifiP2pDevice device : mKnownWifiDisplayPeers) { 180 if (device.deviceAddress.equals(address)) { 181 connect(device); 182 } 183 } 184 } 185 186 public void requestDisconnect() { 187 disconnect(); 188 } 189 190 private void enableWfd() { 191 if (!mWfdEnabled && !mWfdEnabling) { 192 mWfdEnabling = true; 193 194 WifiP2pWfdInfo wfdInfo = new WifiP2pWfdInfo(); 195 wfdInfo.setWfdEnabled(true); 196 wfdInfo.setDeviceType(WifiP2pWfdInfo.WFD_SOURCE); 197 wfdInfo.setSessionAvailable(true); 198 wfdInfo.setControlPort(DEFAULT_CONTROL_PORT); 199 wfdInfo.setMaxThroughput(MAX_THROUGHPUT); 200 mWifiP2pManager.setWFDInfo(mWifiP2pChannel, wfdInfo, new ActionListener() { 201 @Override 202 public void onSuccess() { 203 if (DEBUG) { 204 Slog.d(TAG, "Successfully set WFD info."); 205 } 206 if (mWfdEnabling) { 207 mWfdEnabling = false; 208 setWfdEnabled(true); 209 } 210 } 211 212 @Override 213 public void onFailure(int reason) { 214 if (DEBUG) { 215 Slog.d(TAG, "Failed to set WFD info with reason " + reason + "."); 216 } 217 mWfdEnabling = false; 218 } 219 }); 220 } 221 } 222 223 private void setWfdEnabled(final boolean enabled) { 224 if (mWfdEnabled != enabled) { 225 mWfdEnabled = enabled; 226 mHandler.post(new Runnable() { 227 @Override 228 public void run() { 229 mListener.onEnablementChanged(enabled); 230 } 231 }); 232 } 233 } 234 235 private void discoverPeers() { 236 if (!mDiscoverPeersInProgress) { 237 mDiscoverPeersInProgress = true; 238 mDiscoverPeersRetriesLeft = DISCOVER_PEERS_MAX_RETRIES; 239 handleScanStarted(); 240 tryDiscoverPeers(); 241 } 242 } 243 244 private void tryDiscoverPeers() { 245 mWifiP2pManager.discoverPeers(mWifiP2pChannel, new ActionListener() { 246 @Override 247 public void onSuccess() { 248 if (DEBUG) { 249 Slog.d(TAG, "Discover peers succeeded. Requesting peers now."); 250 } 251 252 mDiscoverPeersInProgress = false; 253 requestPeers(); 254 } 255 256 @Override 257 public void onFailure(int reason) { 258 if (DEBUG) { 259 Slog.d(TAG, "Discover peers failed with reason " + reason + "."); 260 } 261 262 if (mDiscoverPeersInProgress) { 263 if (reason == 0 && mDiscoverPeersRetriesLeft > 0 && mWfdEnabled) { 264 mHandler.postDelayed(new Runnable() { 265 @Override 266 public void run() { 267 if (mDiscoverPeersInProgress) { 268 if (mDiscoverPeersRetriesLeft > 0 && mWfdEnabled) { 269 mDiscoverPeersRetriesLeft -= 1; 270 if (DEBUG) { 271 Slog.d(TAG, "Retrying discovery. Retries left: " 272 + mDiscoverPeersRetriesLeft); 273 } 274 tryDiscoverPeers(); 275 } else { 276 handleScanFinished(); 277 mDiscoverPeersInProgress = false; 278 } 279 } 280 } 281 }, DISCOVER_PEERS_RETRY_DELAY_MILLIS); 282 } else { 283 handleScanFinished(); 284 mDiscoverPeersInProgress = false; 285 } 286 } 287 } 288 }); 289 } 290 291 private void requestPeers() { 292 mWifiP2pManager.requestPeers(mWifiP2pChannel, new PeerListListener() { 293 @Override 294 public void onPeersAvailable(WifiP2pDeviceList peers) { 295 if (DEBUG) { 296 Slog.d(TAG, "Received list of peers."); 297 } 298 299 mKnownWifiDisplayPeers.clear(); 300 for (WifiP2pDevice device : peers.getDeviceList()) { 301 if (DEBUG) { 302 Slog.d(TAG, " " + describeWifiP2pDevice(device)); 303 } 304 305 if (isWifiDisplay(device)) { 306 mKnownWifiDisplayPeers.add(device); 307 } 308 } 309 310 handleScanFinished(); 311 } 312 }); 313 } 314 315 private void handleScanStarted() { 316 mHandler.post(new Runnable() { 317 @Override 318 public void run() { 319 mListener.onScanStarted(); 320 } 321 }); 322 } 323 324 private void handleScanFinished() { 325 final int count = mKnownWifiDisplayPeers.size(); 326 final WifiDisplay[] displays = WifiDisplay.CREATOR.newArray(count); 327 for (int i = 0; i < count; i++) { 328 displays[i] = createWifiDisplay(mKnownWifiDisplayPeers.get(i)); 329 } 330 331 mHandler.post(new Runnable() { 332 @Override 333 public void run() { 334 mListener.onScanFinished(displays); 335 } 336 }); 337 } 338 339 private void connect(final WifiP2pDevice device) { 340 if (mDesiredDevice != null 341 && !mDesiredDevice.deviceAddress.equals(device.deviceAddress)) { 342 if (DEBUG) { 343 Slog.d(TAG, "connect: nothing to do, already connecting to " 344 + describeWifiP2pDevice(device)); 345 } 346 return; 347 } 348 349 if (mConnectedDevice != null 350 && !mConnectedDevice.deviceAddress.equals(device.deviceAddress) 351 && mDesiredDevice == null) { 352 if (DEBUG) { 353 Slog.d(TAG, "connect: nothing to do, already connected to " 354 + describeWifiP2pDevice(device) + " and not part way through " 355 + "connecting to a different device."); 356 } 357 return; 358 } 359 360 mDesiredDevice = device; 361 mConnectionRetriesLeft = CONNECT_MAX_RETRIES; 362 updateConnection(); 363 } 364 365 private void disconnect() { 366 mDesiredDevice = null; 367 updateConnection(); 368 } 369 370 private void retryConnection() { 371 if (mDesiredDevice != null && mConnectedDevice != mDesiredDevice 372 && mConnectionRetriesLeft > 0) { 373 mConnectionRetriesLeft -= 1; 374 Slog.i(TAG, "Retrying Wifi display connection. Retries left: " 375 + mConnectionRetriesLeft); 376 377 // Cheap hack. Make a new instance of the device object so that we 378 // can distinguish it from the previous connection attempt. 379 // This will cause us to tear everything down before we try again. 380 mDesiredDevice = new WifiP2pDevice(mDesiredDevice); 381 updateConnection(); 382 } 383 } 384 385 /** 386 * This function is called repeatedly after each asynchronous operation 387 * until all preconditions for the connection have been satisfied and the 388 * connection is established (or not). 389 */ 390 private void updateConnection() { 391 // Step 1. Before we try to connect to a new device, tell the system we 392 // have disconnected from the old one. 393 if (mRemoteDisplay != null && mConnectedDevice != mDesiredDevice) { 394 Slog.i(TAG, "Stopped listening for RTSP connection on " + mRemoteDisplayInterface 395 + " from Wifi display: " + mConnectedDevice.deviceName); 396 397 mRemoteDisplay.dispose(); 398 mRemoteDisplay = null; 399 mRemoteDisplayInterface = null; 400 mRemoteDisplayConnected = false; 401 mHandler.removeCallbacks(mRtspTimeout); 402 403 setRemoteSubmixOn(false); 404 405 mHandler.post(new Runnable() { 406 @Override 407 public void run() { 408 mListener.onDisplayDisconnected(); 409 } 410 }); 411 412 // continue to next step 413 } 414 415 // Step 2. Before we try to connect to a new device, disconnect from the old one. 416 if (mConnectedDevice != null && mConnectedDevice != mDesiredDevice) { 417 Slog.i(TAG, "Disconnecting from Wifi display: " + mConnectedDevice.deviceName); 418 419 final WifiP2pDevice oldDevice = mConnectedDevice; 420 mWifiP2pManager.removeGroup(mWifiP2pChannel, new ActionListener() { 421 @Override 422 public void onSuccess() { 423 Slog.i(TAG, "Disconnected from Wifi display: " + oldDevice.deviceName); 424 next(); 425 } 426 427 @Override 428 public void onFailure(int reason) { 429 Slog.i(TAG, "Failed to disconnect from Wifi display: " 430 + oldDevice.deviceName + ", reason=" + reason); 431 next(); 432 } 433 434 private void next() { 435 if (mConnectedDevice == oldDevice) { 436 mConnectedDevice = null; 437 updateConnection(); 438 } 439 } 440 }); 441 return; // wait for asynchronous callback 442 } 443 444 // Step 3. Before we try to connect to a new device, stop trying to connect 445 // to the old one. 446 if (mConnectingDevice != null && mConnectingDevice != mDesiredDevice) { 447 Slog.i(TAG, "Canceling connection to Wifi display: " + mConnectingDevice.deviceName); 448 449 mHandler.removeCallbacks(mConnectionTimeout); 450 451 final WifiP2pDevice oldDevice = mConnectingDevice; 452 mWifiP2pManager.cancelConnect(mWifiP2pChannel, new ActionListener() { 453 @Override 454 public void onSuccess() { 455 Slog.i(TAG, "Canceled connection to Wifi display: " + oldDevice.deviceName); 456 next(); 457 } 458 459 @Override 460 public void onFailure(int reason) { 461 Slog.i(TAG, "Failed to cancel connection to Wifi display: " 462 + oldDevice.deviceName + ", reason=" + reason); 463 next(); 464 } 465 466 private void next() { 467 if (mConnectingDevice == oldDevice) { 468 mConnectingDevice = null; 469 updateConnection(); 470 } 471 } 472 }); 473 return; // wait for asynchronous callback 474 } 475 476 // Step 4. If we wanted to disconnect, then mission accomplished. 477 if (mDesiredDevice == null) { 478 return; // done 479 } 480 481 // Step 5. Try to connect. 482 if (mConnectedDevice == null && mConnectingDevice == null) { 483 Slog.i(TAG, "Connecting to Wifi display: " + mDesiredDevice.deviceName); 484 485 mConnectingDevice = mDesiredDevice; 486 WifiP2pConfig config = new WifiP2pConfig(); 487 config.deviceAddress = mConnectingDevice.deviceAddress; 488 489 final WifiDisplay display = createWifiDisplay(mConnectingDevice); 490 mHandler.post(new Runnable() { 491 @Override 492 public void run() { 493 mListener.onDisplayConnecting(display); 494 } 495 }); 496 497 final WifiP2pDevice newDevice = mDesiredDevice; 498 mWifiP2pManager.connect(mWifiP2pChannel, config, new ActionListener() { 499 @Override 500 public void onSuccess() { 501 // The connection may not yet be established. We still need to wait 502 // for WIFI_P2P_CONNECTION_CHANGED_ACTION. However, we might never 503 // get that broadcast, so we register a timeout. 504 Slog.i(TAG, "Initiated connection to Wifi display: " + newDevice.deviceName); 505 506 mHandler.postDelayed(mConnectionTimeout, CONNECTION_TIMEOUT_SECONDS * 1000); 507 } 508 509 @Override 510 public void onFailure(int reason) { 511 if (mConnectingDevice == newDevice) { 512 Slog.i(TAG, "Failed to initiate connection to Wifi display: " 513 + newDevice.deviceName + ", reason=" + reason); 514 mConnectingDevice = null; 515 handleConnectionFailure(false); 516 } 517 } 518 }); 519 return; // wait for asynchronous callback 520 } 521 522 // Step 6. Listen for incoming connections. 523 if (mConnectedDevice != null && mRemoteDisplay == null) { 524 Inet4Address addr = getInterfaceAddress(mConnectedDeviceGroupInfo); 525 if (addr == null) { 526 Slog.i(TAG, "Failed to get local interface address for communicating " 527 + "with Wifi display: " + mConnectedDevice.deviceName); 528 handleConnectionFailure(false); 529 return; // done 530 } 531 532 setRemoteSubmixOn(true); 533 534 final WifiP2pDevice oldDevice = mConnectedDevice; 535 final int port = getPortNumber(mConnectedDevice); 536 final String iface = addr.getHostAddress() + ":" + port; 537 mRemoteDisplayInterface = iface; 538 539 Slog.i(TAG, "Listening for RTSP connection on " + iface 540 + " from Wifi display: " + mConnectedDevice.deviceName); 541 542 mRemoteDisplay = RemoteDisplay.listen(iface, new RemoteDisplay.Listener() { 543 @Override 544 public void onDisplayConnected(final Surface surface, 545 final int width, final int height, final int flags) { 546 if (mConnectedDevice == oldDevice && !mRemoteDisplayConnected) { 547 Slog.i(TAG, "Opened RTSP connection with Wifi display: " 548 + mConnectedDevice.deviceName); 549 mRemoteDisplayConnected = true; 550 mHandler.removeCallbacks(mRtspTimeout); 551 552 final WifiDisplay display = createWifiDisplay(mConnectedDevice); 553 mHandler.post(new Runnable() { 554 @Override 555 public void run() { 556 mListener.onDisplayConnected(display, 557 surface, width, height, flags); 558 } 559 }); 560 } 561 } 562 563 @Override 564 public void onDisplayDisconnected() { 565 if (mConnectedDevice == oldDevice) { 566 Slog.i(TAG, "Closed RTSP connection with Wifi display: " 567 + mConnectedDevice.deviceName); 568 mHandler.removeCallbacks(mRtspTimeout); 569 disconnect(); 570 } 571 } 572 573 @Override 574 public void onDisplayError(int error) { 575 if (mConnectedDevice == oldDevice) { 576 Slog.i(TAG, "Lost RTSP connection with Wifi display due to error " 577 + error + ": " + mConnectedDevice.deviceName); 578 mHandler.removeCallbacks(mRtspTimeout); 579 handleConnectionFailure(false); 580 } 581 } 582 }, mHandler); 583 584 mHandler.postDelayed(mRtspTimeout, RTSP_TIMEOUT_SECONDS * 1000); 585 } 586 } 587 588 private void setRemoteSubmixOn(boolean on) { 589 if (mRemoteSubmixOn != on) { 590 mRemoteSubmixOn = on; 591 mAudioManager.setRemoteSubmixOn(on, REMOTE_SUBMIX_ADDRESS); 592 } 593 } 594 595 private void handleStateChanged(boolean enabled) { 596 if (mWifiP2pEnabled != enabled) { 597 mWifiP2pEnabled = enabled; 598 if (enabled) { 599 if (!mWfdEnabled) { 600 enableWfd(); 601 } 602 } else { 603 setWfdEnabled(false); 604 disconnect(); 605 } 606 } 607 } 608 609 private void handlePeersChanged() { 610 if (mWifiP2pEnabled) { 611 if (mWfdEnabled) { 612 requestPeers(); 613 } else { 614 enableWfd(); 615 } 616 } 617 } 618 619 private void handleConnectionChanged(NetworkInfo networkInfo) { 620 mNetworkInfo = networkInfo; 621 if (mWfdEnabled && networkInfo.isConnected()) { 622 if (mDesiredDevice != null) { 623 mWifiP2pManager.requestGroupInfo(mWifiP2pChannel, new GroupInfoListener() { 624 @Override 625 public void onGroupInfoAvailable(WifiP2pGroup info) { 626 if (DEBUG) { 627 Slog.d(TAG, "Received group info: " + describeWifiP2pGroup(info)); 628 } 629 630 if (mConnectingDevice != null && !info.contains(mConnectingDevice)) { 631 Slog.i(TAG, "Aborting connection to Wifi display because " 632 + "the current P2P group does not contain the device " 633 + "we expected to find: " + mConnectingDevice.deviceName); 634 handleConnectionFailure(false); 635 return; 636 } 637 638 if (mDesiredDevice != null && !info.contains(mDesiredDevice)) { 639 disconnect(); 640 return; 641 } 642 643 if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) { 644 Slog.i(TAG, "Connected to Wifi display: " 645 + mConnectingDevice.deviceName); 646 647 mHandler.removeCallbacks(mConnectionTimeout); 648 mConnectedDeviceGroupInfo = info; 649 mConnectedDevice = mConnectingDevice; 650 mConnectingDevice = null; 651 updateConnection(); 652 } 653 } 654 }); 655 } 656 } else { 657 disconnect(); 658 659 // After disconnection for a group, for some reason we have a tendency 660 // to get a peer change notification with an empty list of peers. 661 // Perform a fresh scan. 662 if (mWfdEnabled) { 663 requestPeers(); 664 } 665 } 666 } 667 668 private final Runnable mConnectionTimeout = new Runnable() { 669 @Override 670 public void run() { 671 if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) { 672 Slog.i(TAG, "Timed out waiting for Wifi display connection after " 673 + CONNECTION_TIMEOUT_SECONDS + " seconds: " 674 + mConnectingDevice.deviceName); 675 handleConnectionFailure(true); 676 } 677 } 678 }; 679 680 private final Runnable mRtspTimeout = new Runnable() { 681 @Override 682 public void run() { 683 if (mConnectedDevice != null 684 && mRemoteDisplay != null && !mRemoteDisplayConnected) { 685 Slog.i(TAG, "Timed out waiting for Wifi display RTSP connection after " 686 + RTSP_TIMEOUT_SECONDS + " seconds: " 687 + mConnectedDevice.deviceName); 688 handleConnectionFailure(true); 689 } 690 } 691 }; 692 693 private void handleConnectionFailure(boolean timeoutOccurred) { 694 Slog.i(TAG, "Wifi display connection failed!"); 695 696 mHandler.post(new Runnable() { 697 @Override 698 public void run() { 699 mListener.onDisplayConnectionFailed(); 700 } 701 }); 702 703 if (mDesiredDevice != null) { 704 if (mConnectionRetriesLeft > 0) { 705 mHandler.postDelayed(new Runnable() { 706 @Override 707 public void run() { 708 retryConnection(); 709 } 710 }, timeoutOccurred ? 0 : CONNECT_RETRY_DELAY_MILLIS); 711 } else { 712 disconnect(); 713 } 714 } 715 } 716 717 private static Inet4Address getInterfaceAddress(WifiP2pGroup info) { 718 NetworkInterface iface; 719 try { 720 iface = NetworkInterface.getByName(info.getInterface()); 721 } catch (SocketException ex) { 722 Slog.w(TAG, "Could not obtain address of network interface " 723 + info.getInterface(), ex); 724 return null; 725 } 726 727 Enumeration<InetAddress> addrs = iface.getInetAddresses(); 728 while (addrs.hasMoreElements()) { 729 InetAddress addr = addrs.nextElement(); 730 if (addr instanceof Inet4Address) { 731 return (Inet4Address)addr; 732 } 733 } 734 735 Slog.w(TAG, "Could not obtain address of network interface " 736 + info.getInterface() + " because it had no IPv4 addresses."); 737 return null; 738 } 739 740 private static int getPortNumber(WifiP2pDevice device) { 741 if (device.deviceName.startsWith("DIRECT-") 742 && device.deviceName.endsWith("Broadcom")) { 743 // These dongles ignore the port we broadcast in our WFD IE. 744 return 8554; 745 } 746 return DEFAULT_CONTROL_PORT; 747 } 748 749 private static boolean isWifiDisplay(WifiP2pDevice device) { 750 return device.wfdInfo != null 751 && device.wfdInfo.isWfdEnabled() 752 && isPrimarySinkDeviceType(device.wfdInfo.getDeviceType()); 753 } 754 755 private static boolean isPrimarySinkDeviceType(int deviceType) { 756 return deviceType == WifiP2pWfdInfo.PRIMARY_SINK 757 || deviceType == WifiP2pWfdInfo.SOURCE_OR_PRIMARY_SINK; 758 } 759 760 private static String describeWifiP2pDevice(WifiP2pDevice device) { 761 return device != null ? device.toString().replace('\n', ',') : "null"; 762 } 763 764 private static String describeWifiP2pGroup(WifiP2pGroup group) { 765 return group != null ? group.toString().replace('\n', ',') : "null"; 766 } 767 768 private static WifiDisplay createWifiDisplay(WifiP2pDevice device) { 769 return new WifiDisplay(device.deviceAddress, device.deviceName); 770 } 771 772 private final BroadcastReceiver mWifiP2pReceiver = new BroadcastReceiver() { 773 @Override 774 public void onReceive(Context context, Intent intent) { 775 final String action = intent.getAction(); 776 if (action.equals(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)) { 777 boolean enabled = (intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, 778 WifiP2pManager.WIFI_P2P_STATE_DISABLED)) == 779 WifiP2pManager.WIFI_P2P_STATE_ENABLED; 780 if (DEBUG) { 781 Slog.d(TAG, "Received WIFI_P2P_STATE_CHANGED_ACTION: enabled=" 782 + enabled); 783 } 784 785 handleStateChanged(enabled); 786 } else if (action.equals(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)) { 787 if (DEBUG) { 788 Slog.d(TAG, "Received WIFI_P2P_PEERS_CHANGED_ACTION."); 789 } 790 791 handlePeersChanged(); 792 } else if (action.equals(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)) { 793 NetworkInfo networkInfo = (NetworkInfo)intent.getParcelableExtra( 794 WifiP2pManager.EXTRA_NETWORK_INFO); 795 if (DEBUG) { 796 Slog.d(TAG, "Received WIFI_P2P_CONNECTION_CHANGED_ACTION: networkInfo=" 797 + networkInfo); 798 } 799 800 handleConnectionChanged(networkInfo); 801 } 802 } 803 }; 804 805 /** 806 * Called on the handler thread when displays are connected or disconnected. 807 */ 808 public interface Listener { 809 void onEnablementChanged(boolean enabled); 810 811 void onScanStarted(); 812 void onScanFinished(WifiDisplay[] knownDisplays); 813 814 void onDisplayConnecting(WifiDisplay display); 815 void onDisplayConnectionFailed(); 816 void onDisplayConnected(WifiDisplay display, 817 Surface surface, int width, int height, int flags); 818 void onDisplayDisconnected(); 819 } 820} 821