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 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 setRemoteSubmixOn(boolean on)680 private void setRemoteSubmixOn(boolean on) { 681 if (mRemoteSubmixOn != on) { 682 mRemoteSubmixOn = on; 683 mAudioManager.setRemoteSubmixOn(on, REMOTE_SUBMIX_ADDRESS); 684 } 685 } 686 handleStateChanged(boolean enabled)687 private void handleStateChanged(boolean enabled) { 688 mWifiP2pEnabled = enabled; 689 updateWfdEnableState(); 690 } 691 handlePeersChanged()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 handleConnectionChanged(NetworkInfo networkInfo)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 handleConnectionFailure(boolean timeoutOccurred)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 advertiseDisplay(final WifiDisplay display, final Surface surface, final int width, final int height, final int flags)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 unadvertiseDisplay()838 private void unadvertiseDisplay() { 839 advertiseDisplay(null, null, 0, 0, 0); 840 } 841 readvertiseDisplay(WifiDisplay display)842 private void readvertiseDisplay(WifiDisplay display) { 843 advertiseDisplay(display, mAdvertisedDisplaySurface, 844 mAdvertisedDisplayWidth, mAdvertisedDisplayHeight, 845 mAdvertisedDisplayFlags); 846 } 847 getInterfaceAddress(WifiP2pGroup info)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 getPortNumber(WifiP2pDevice device)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 isWifiDisplay(WifiP2pDevice device)880 private static boolean isWifiDisplay(WifiP2pDevice device) { 881 return device.wfdInfo != null 882 && device.wfdInfo.isWfdEnabled() 883 && isPrimarySinkDeviceType(device.wfdInfo.getDeviceType()); 884 } 885 isPrimarySinkDeviceType(int deviceType)886 private static boolean isPrimarySinkDeviceType(int deviceType) { 887 return deviceType == WifiP2pWfdInfo.PRIMARY_SINK 888 || deviceType == WifiP2pWfdInfo.SOURCE_OR_PRIMARY_SINK; 889 } 890 describeWifiP2pDevice(WifiP2pDevice device)891 private static String describeWifiP2pDevice(WifiP2pDevice device) { 892 return device != null ? device.toString().replace('\n', ',') : "null"; 893 } 894 describeWifiP2pGroup(WifiP2pGroup group)895 private static String describeWifiP2pGroup(WifiP2pGroup group) { 896 return group != null ? group.toString().replace('\n', ',') : "null"; 897 } 898 createWifiDisplay(WifiP2pDevice device)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 { onFeatureStateChanged(int featureState)942 void onFeatureStateChanged(int featureState); 943 onScanStarted()944 void onScanStarted(); onScanFinished(WifiDisplay[] availableDisplays)945 void onScanFinished(WifiDisplay[] availableDisplays); 946 onDisplayConnecting(WifiDisplay display)947 void onDisplayConnecting(WifiDisplay display); onDisplayConnectionFailed()948 void onDisplayConnectionFailed(); onDisplayChanged(WifiDisplay display)949 void onDisplayChanged(WifiDisplay display); onDisplayConnected(WifiDisplay display, Surface surface, int width, int height, int flags)950 void onDisplayConnected(WifiDisplay display, 951 Surface surface, int width, int height, int flags); onDisplayDisconnected()952 void onDisplayDisconnected(); 953 } 954 } 955