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