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