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