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