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