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