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 WifiDisplayController(Context context, Handler handler, Listener listener)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 updateSettings()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 dump(PrintWriter pw)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 requestScan()227 public void requestScan() { 228 discoverPeers(); 229 } 230 requestConnect(String address)231 public void requestConnect(String address) { 232 for (WifiP2pDevice device : mAvailableWifiDisplayPeers) { 233 if (device.deviceAddress.equals(address)) { 234 connect(device); 235 } 236 } 237 } 238 requestDisconnect()239 public void requestDisconnect() { 240 disconnect(); 241 } 242 updateWfdEnableState()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 reportFeatureState()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 computeFeatureState()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 discoverPeers()304 private void discoverPeers() { 305 if (!mDiscoverPeersInProgress) { 306 mDiscoverPeersInProgress = true; 307 mDiscoverPeersRetriesLeft = DISCOVER_PEERS_MAX_RETRIES; 308 handleScanStarted(); 309 tryDiscoverPeers(); 310 } 311 } 312 tryDiscoverPeers()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 requestPeers()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 handleScanStarted()384 private void handleScanStarted() { 385 mHandler.post(new Runnable() { 386 @Override 387 public void run() { 388 mListener.onScanStarted(); 389 } 390 }); 391 } 392 handleScanFinished()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 updateDesiredDevice(WifiP2pDevice device)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 connect(final WifiP2pDevice device)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 disconnect()453 private void disconnect() { 454 mDesiredDevice = null; 455 updateConnection(); 456 } 457 retryConnection()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 */ updateConnection()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 setRemoteSubmixOn(false); 485 unadvertiseDisplay(); 486 487 // continue to next step 488 } 489 490 // Step 2. Before we try to connect to a new device, disconnect from the old one. 491 if (mDisconnectingDevice != null) { 492 return; // wait for asynchronous callback 493 } 494 if (mConnectedDevice != null && mConnectedDevice != mDesiredDevice) { 495 Slog.i(TAG, "Disconnecting from Wifi display: " + mConnectedDevice.deviceName); 496 mDisconnectingDevice = mConnectedDevice; 497 mConnectedDevice = null; 498 499 unadvertiseDisplay(); 500 501 final WifiP2pDevice oldDevice = mDisconnectingDevice; 502 mWifiP2pManager.removeGroup(mWifiP2pChannel, new ActionListener() { 503 @Override 504 public void onSuccess() { 505 Slog.i(TAG, "Disconnected from Wifi display: " + oldDevice.deviceName); 506 next(); 507 } 508 509 @Override 510 public void onFailure(int reason) { 511 Slog.i(TAG, "Failed to disconnect from Wifi display: " 512 + oldDevice.deviceName + ", reason=" + reason); 513 next(); 514 } 515 516 private void next() { 517 if (mDisconnectingDevice == oldDevice) { 518 mDisconnectingDevice = null; 519 updateConnection(); 520 } 521 } 522 }); 523 return; // wait for asynchronous callback 524 } 525 526 // Step 3. Before we try to connect to a new device, stop trying to connect 527 // to the old one. 528 if (mCancelingDevice != null) { 529 return; // wait for asynchronous callback 530 } 531 if (mConnectingDevice != null && mConnectingDevice != mDesiredDevice) { 532 Slog.i(TAG, "Canceling connection to Wifi display: " + mConnectingDevice.deviceName); 533 mCancelingDevice = mConnectingDevice; 534 mConnectingDevice = null; 535 536 unadvertiseDisplay(); 537 mHandler.removeCallbacks(mConnectionTimeout); 538 539 final WifiP2pDevice oldDevice = mCancelingDevice; 540 mWifiP2pManager.cancelConnect(mWifiP2pChannel, new ActionListener() { 541 @Override 542 public void onSuccess() { 543 Slog.i(TAG, "Canceled connection to Wifi display: " + oldDevice.deviceName); 544 next(); 545 } 546 547 @Override 548 public void onFailure(int reason) { 549 Slog.i(TAG, "Failed to cancel connection to Wifi display: " 550 + oldDevice.deviceName + ", reason=" + reason); 551 next(); 552 } 553 554 private void next() { 555 if (mCancelingDevice == oldDevice) { 556 mCancelingDevice = null; 557 updateConnection(); 558 } 559 } 560 }); 561 return; // wait for asynchronous callback 562 } 563 564 // Step 4. If we wanted to disconnect, then mission accomplished. 565 if (mDesiredDevice == null) { 566 unadvertiseDisplay(); 567 return; // done 568 } 569 570 // Step 5. Try to connect. 571 if (mConnectedDevice == null && mConnectingDevice == null) { 572 Slog.i(TAG, "Connecting to Wifi display: " + mDesiredDevice.deviceName); 573 574 mConnectingDevice = mDesiredDevice; 575 WifiP2pConfig config = new WifiP2pConfig(); 576 WpsInfo wps = new WpsInfo(); 577 if (mConnectingDevice.wpsPbcSupported()) { 578 wps.setup = WpsInfo.PBC; 579 } else if (mConnectingDevice.wpsDisplaySupported()) { 580 // We do keypad if peer does display 581 wps.setup = WpsInfo.KEYPAD; 582 } else { 583 wps.setup = WpsInfo.DISPLAY; 584 } 585 config.wps = wps; 586 config.deviceAddress = mConnectingDevice.deviceAddress; 587 // Helps with STA & P2P concurrency 588 config.groupOwnerIntent = WifiP2pConfig.MIN_GROUP_OWNER_INTENT; 589 590 WifiDisplay display = createWifiDisplay(mConnectingDevice); 591 advertiseDisplay(display, null, 0, 0, 0); 592 593 final WifiP2pDevice newDevice = mDesiredDevice; 594 mWifiP2pManager.connect(mWifiP2pChannel, config, new ActionListener() { 595 @Override 596 public void onSuccess() { 597 // The connection may not yet be established. We still need to wait 598 // for WIFI_P2P_CONNECTION_CHANGED_ACTION. However, we might never 599 // get that broadcast, so we register a timeout. 600 Slog.i(TAG, "Initiated connection to Wifi display: " + newDevice.deviceName); 601 602 mHandler.postDelayed(mConnectionTimeout, CONNECTION_TIMEOUT_SECONDS * 1000); 603 } 604 605 @Override 606 public void onFailure(int reason) { 607 if (mConnectingDevice == newDevice) { 608 Slog.i(TAG, "Failed to initiate connection to Wifi display: " 609 + newDevice.deviceName + ", reason=" + reason); 610 mConnectingDevice = null; 611 handleConnectionFailure(false); 612 } 613 } 614 }); 615 return; // wait for asynchronous callback 616 } 617 618 // Step 6. Listen for incoming connections. 619 if (mConnectedDevice != null && mRemoteDisplay == null) { 620 Inet4Address addr = getInterfaceAddress(mConnectedDeviceGroupInfo); 621 if (addr == null) { 622 Slog.i(TAG, "Failed to get local interface address for communicating " 623 + "with Wifi display: " + mConnectedDevice.deviceName); 624 handleConnectionFailure(false); 625 return; // done 626 } 627 628 setRemoteSubmixOn(true); 629 630 final WifiP2pDevice oldDevice = mConnectedDevice; 631 final int port = getPortNumber(mConnectedDevice); 632 final String iface = addr.getHostAddress() + ":" + port; 633 mRemoteDisplayInterface = iface; 634 635 Slog.i(TAG, "Listening for RTSP connection on " + iface 636 + " from Wifi display: " + mConnectedDevice.deviceName); 637 638 mRemoteDisplay = RemoteDisplay.listen(iface, new RemoteDisplay.Listener() { 639 @Override 640 public void onDisplayConnected(Surface surface, 641 int width, int height, int flags) { 642 if (mConnectedDevice == oldDevice && !mRemoteDisplayConnected) { 643 Slog.i(TAG, "Opened RTSP connection with Wifi display: " 644 + mConnectedDevice.deviceName); 645 mRemoteDisplayConnected = true; 646 mHandler.removeCallbacks(mRtspTimeout); 647 648 final WifiDisplay display = createWifiDisplay(mConnectedDevice); 649 advertiseDisplay(display, surface, width, height, flags); 650 } 651 } 652 653 @Override 654 public void onDisplayDisconnected() { 655 if (mConnectedDevice == oldDevice) { 656 Slog.i(TAG, "Closed RTSP connection with Wifi display: " 657 + mConnectedDevice.deviceName); 658 mHandler.removeCallbacks(mRtspTimeout); 659 disconnect(); 660 } 661 } 662 663 @Override 664 public void onDisplayError(int error) { 665 if (mConnectedDevice == oldDevice) { 666 Slog.i(TAG, "Lost RTSP connection with Wifi display due to error " 667 + error + ": " + mConnectedDevice.deviceName); 668 mHandler.removeCallbacks(mRtspTimeout); 669 handleConnectionFailure(false); 670 } 671 } 672 }, mHandler); 673 674 mHandler.postDelayed(mRtspTimeout, RTSP_TIMEOUT_SECONDS * 1000); 675 } 676 } 677 setRemoteSubmixOn(boolean on)678 private void setRemoteSubmixOn(boolean on) { 679 if (mRemoteSubmixOn != on) { 680 mRemoteSubmixOn = on; 681 mAudioManager.setRemoteSubmixOn(on, REMOTE_SUBMIX_ADDRESS); 682 } 683 } 684 handleStateChanged(boolean enabled)685 private void handleStateChanged(boolean enabled) { 686 mWifiP2pEnabled = enabled; 687 updateWfdEnableState(); 688 } 689 handlePeersChanged()690 private void handlePeersChanged() { 691 // Even if wfd is disabled, it is best to get the latest set of peers to 692 // keep in sync with the p2p framework 693 requestPeers(); 694 } 695 handleConnectionChanged(NetworkInfo networkInfo)696 private void handleConnectionChanged(NetworkInfo networkInfo) { 697 mNetworkInfo = networkInfo; 698 if (mWfdEnabled && networkInfo.isConnected()) { 699 if (mDesiredDevice != null) { 700 mWifiP2pManager.requestGroupInfo(mWifiP2pChannel, new GroupInfoListener() { 701 @Override 702 public void onGroupInfoAvailable(WifiP2pGroup info) { 703 if (DEBUG) { 704 Slog.d(TAG, "Received group info: " + describeWifiP2pGroup(info)); 705 } 706 707 if (mConnectingDevice != null && !info.contains(mConnectingDevice)) { 708 Slog.i(TAG, "Aborting connection to Wifi display because " 709 + "the current P2P group does not contain the device " 710 + "we expected to find: " + mConnectingDevice.deviceName 711 + ", group info was: " + describeWifiP2pGroup(info)); 712 handleConnectionFailure(false); 713 return; 714 } 715 716 if (mDesiredDevice != null && !info.contains(mDesiredDevice)) { 717 disconnect(); 718 return; 719 } 720 721 if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) { 722 Slog.i(TAG, "Connected to Wifi display: " 723 + mConnectingDevice.deviceName); 724 725 mHandler.removeCallbacks(mConnectionTimeout); 726 mConnectedDeviceGroupInfo = info; 727 mConnectedDevice = mConnectingDevice; 728 mConnectingDevice = null; 729 updateConnection(); 730 } 731 } 732 }); 733 } 734 } else { 735 disconnect(); 736 737 // After disconnection for a group, for some reason we have a tendency 738 // to get a peer change notification with an empty list of peers. 739 // Perform a fresh scan. 740 if (mWfdEnabled) { 741 requestPeers(); 742 } 743 } 744 } 745 746 private final Runnable mConnectionTimeout = new Runnable() { 747 @Override 748 public void run() { 749 if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) { 750 Slog.i(TAG, "Timed out waiting for Wifi display connection after " 751 + CONNECTION_TIMEOUT_SECONDS + " seconds: " 752 + mConnectingDevice.deviceName); 753 handleConnectionFailure(true); 754 } 755 } 756 }; 757 758 private final Runnable mRtspTimeout = new Runnable() { 759 @Override 760 public void run() { 761 if (mConnectedDevice != null 762 && mRemoteDisplay != null && !mRemoteDisplayConnected) { 763 Slog.i(TAG, "Timed out waiting for Wifi display RTSP connection after " 764 + RTSP_TIMEOUT_SECONDS + " seconds: " 765 + mConnectedDevice.deviceName); 766 handleConnectionFailure(true); 767 } 768 } 769 }; 770 handleConnectionFailure(boolean timeoutOccurred)771 private void handleConnectionFailure(boolean timeoutOccurred) { 772 Slog.i(TAG, "Wifi display connection failed!"); 773 774 if (mDesiredDevice != null) { 775 if (mConnectionRetriesLeft > 0) { 776 final WifiP2pDevice oldDevice = mDesiredDevice; 777 mHandler.postDelayed(new Runnable() { 778 @Override 779 public void run() { 780 if (mDesiredDevice == oldDevice && mConnectionRetriesLeft > 0) { 781 mConnectionRetriesLeft -= 1; 782 Slog.i(TAG, "Retrying Wifi display connection. Retries left: " 783 + mConnectionRetriesLeft); 784 retryConnection(); 785 } 786 } 787 }, timeoutOccurred ? 0 : CONNECT_RETRY_DELAY_MILLIS); 788 } else { 789 disconnect(); 790 } 791 } 792 } 793 advertiseDisplay(final WifiDisplay display, final Surface surface, final int width, final int height, final int flags)794 private void advertiseDisplay(final WifiDisplay display, 795 final Surface surface, final int width, final int height, final int flags) { 796 if (!Objects.equal(mAdvertisedDisplay, display) 797 || mAdvertisedDisplaySurface != surface 798 || mAdvertisedDisplayWidth != width 799 || mAdvertisedDisplayHeight != height 800 || mAdvertisedDisplayFlags != flags) { 801 final WifiDisplay oldDisplay = mAdvertisedDisplay; 802 final Surface oldSurface = mAdvertisedDisplaySurface; 803 804 mAdvertisedDisplay = display; 805 mAdvertisedDisplaySurface = surface; 806 mAdvertisedDisplayWidth = width; 807 mAdvertisedDisplayHeight = height; 808 mAdvertisedDisplayFlags = flags; 809 810 mHandler.post(new Runnable() { 811 @Override 812 public void run() { 813 if (oldSurface != null && surface != oldSurface) { 814 mListener.onDisplayDisconnected(); 815 } else if (oldDisplay != null && !oldDisplay.hasSameAddress(display)) { 816 mListener.onDisplayConnectionFailed(); 817 } 818 819 if (display != null) { 820 if (!display.hasSameAddress(oldDisplay)) { 821 mListener.onDisplayConnecting(display); 822 } else if (!display.equals(oldDisplay)) { 823 // The address is the same but some other property such as the 824 // name must have changed. 825 mListener.onDisplayChanged(display); 826 } 827 if (surface != null && surface != oldSurface) { 828 mListener.onDisplayConnected(display, surface, width, height, flags); 829 } 830 } 831 } 832 }); 833 } 834 } 835 unadvertiseDisplay()836 private void unadvertiseDisplay() { 837 advertiseDisplay(null, null, 0, 0, 0); 838 } 839 readvertiseDisplay(WifiDisplay display)840 private void readvertiseDisplay(WifiDisplay display) { 841 advertiseDisplay(display, mAdvertisedDisplaySurface, 842 mAdvertisedDisplayWidth, mAdvertisedDisplayHeight, 843 mAdvertisedDisplayFlags); 844 } 845 getInterfaceAddress(WifiP2pGroup info)846 private static Inet4Address getInterfaceAddress(WifiP2pGroup info) { 847 NetworkInterface iface; 848 try { 849 iface = NetworkInterface.getByName(info.getInterface()); 850 } catch (SocketException ex) { 851 Slog.w(TAG, "Could not obtain address of network interface " 852 + info.getInterface(), ex); 853 return null; 854 } 855 856 Enumeration<InetAddress> addrs = iface.getInetAddresses(); 857 while (addrs.hasMoreElements()) { 858 InetAddress addr = addrs.nextElement(); 859 if (addr instanceof Inet4Address) { 860 return (Inet4Address)addr; 861 } 862 } 863 864 Slog.w(TAG, "Could not obtain address of network interface " 865 + info.getInterface() + " because it had no IPv4 addresses."); 866 return null; 867 } 868 getPortNumber(WifiP2pDevice device)869 private static int getPortNumber(WifiP2pDevice device) { 870 if (device.deviceName.startsWith("DIRECT-") 871 && device.deviceName.endsWith("Broadcom")) { 872 // These dongles ignore the port we broadcast in our WFD IE. 873 return 8554; 874 } 875 return DEFAULT_CONTROL_PORT; 876 } 877 isWifiDisplay(WifiP2pDevice device)878 private static boolean isWifiDisplay(WifiP2pDevice device) { 879 return device.wfdInfo != null 880 && device.wfdInfo.isWfdEnabled() 881 && isPrimarySinkDeviceType(device.wfdInfo.getDeviceType()); 882 } 883 isPrimarySinkDeviceType(int deviceType)884 private static boolean isPrimarySinkDeviceType(int deviceType) { 885 return deviceType == WifiP2pWfdInfo.PRIMARY_SINK 886 || deviceType == WifiP2pWfdInfo.SOURCE_OR_PRIMARY_SINK; 887 } 888 describeWifiP2pDevice(WifiP2pDevice device)889 private static String describeWifiP2pDevice(WifiP2pDevice device) { 890 return device != null ? device.toString().replace('\n', ',') : "null"; 891 } 892 describeWifiP2pGroup(WifiP2pGroup group)893 private static String describeWifiP2pGroup(WifiP2pGroup group) { 894 return group != null ? group.toString().replace('\n', ',') : "null"; 895 } 896 createWifiDisplay(WifiP2pDevice device)897 private static WifiDisplay createWifiDisplay(WifiP2pDevice device) { 898 return new WifiDisplay(device.deviceAddress, device.deviceName, null); 899 } 900 901 private final BroadcastReceiver mWifiP2pReceiver = new BroadcastReceiver() { 902 @Override 903 public void onReceive(Context context, Intent intent) { 904 final String action = intent.getAction(); 905 if (action.equals(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)) { 906 // This broadcast is sticky so we'll always get the initial Wifi P2P state 907 // on startup. 908 boolean enabled = (intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, 909 WifiP2pManager.WIFI_P2P_STATE_DISABLED)) == 910 WifiP2pManager.WIFI_P2P_STATE_ENABLED; 911 if (DEBUG) { 912 Slog.d(TAG, "Received WIFI_P2P_STATE_CHANGED_ACTION: enabled=" 913 + enabled); 914 } 915 916 handleStateChanged(enabled); 917 } else if (action.equals(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)) { 918 if (DEBUG) { 919 Slog.d(TAG, "Received WIFI_P2P_PEERS_CHANGED_ACTION."); 920 } 921 922 handlePeersChanged(); 923 } else if (action.equals(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)) { 924 NetworkInfo networkInfo = (NetworkInfo)intent.getParcelableExtra( 925 WifiP2pManager.EXTRA_NETWORK_INFO); 926 if (DEBUG) { 927 Slog.d(TAG, "Received WIFI_P2P_CONNECTION_CHANGED_ACTION: networkInfo=" 928 + networkInfo); 929 } 930 931 handleConnectionChanged(networkInfo); 932 } 933 } 934 }; 935 936 /** 937 * Called on the handler thread when displays are connected or disconnected. 938 */ 939 public interface Listener { onFeatureStateChanged(int featureState)940 void onFeatureStateChanged(int featureState); 941 onScanStarted()942 void onScanStarted(); onScanFinished(WifiDisplay[] availableDisplays)943 void onScanFinished(WifiDisplay[] availableDisplays); 944 onDisplayConnecting(WifiDisplay display)945 void onDisplayConnecting(WifiDisplay display); onDisplayConnectionFailed()946 void onDisplayConnectionFailed(); onDisplayChanged(WifiDisplay display)947 void onDisplayChanged(WifiDisplay display); onDisplayConnected(WifiDisplay display, Surface surface, int width, int height, int flags)948 void onDisplayConnected(WifiDisplay display, 949 Surface surface, int width, int height, int flags); onDisplayDisconnected()950 void onDisplayDisconnected(); 951 } 952 } 953