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 WifiDisplayController(Context context, Handler handler, Listener listener)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 updateSettings()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 dump(PrintWriter pw, String prefix)212 public void dump(PrintWriter pw, String prefix) { 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 requestStartScan()241 public void requestStartScan() { 242 if (!mScanRequested) { 243 mScanRequested = true; 244 updateScanState(); 245 } 246 } 247 requestStopScan()248 public void requestStopScan() { 249 if (mScanRequested) { 250 mScanRequested = false; 251 updateScanState(); 252 } 253 } 254 requestConnect(String address)255 public void requestConnect(String address) { 256 for (WifiP2pDevice device : mAvailableWifiDisplayPeers) { 257 if (device.deviceAddress.equals(address)) { 258 connect(device); 259 } 260 } 261 } 262 requestPause()263 public void requestPause() { 264 if (mRemoteDisplay != null) { 265 mRemoteDisplay.pause(); 266 } 267 } 268 requestResume()269 public void requestResume() { 270 if (mRemoteDisplay != null) { 271 mRemoteDisplay.resume(); 272 } 273 } 274 requestDisconnect()275 public void requestDisconnect() { 276 disconnect(); 277 } 278 updateWfdEnableState()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 reportFeatureState()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 computeFeatureState()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 updateScanState()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 tryDiscoverPeers()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 stopPeerDiscovery()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 requestPeers()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 handleScanStarted()459 private void handleScanStarted() { 460 mHandler.post(new Runnable() { 461 @Override 462 public void run() { 463 mListener.onScanStarted(); 464 } 465 }); 466 } 467 handleScanResults()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 handleScanFinished()485 private void handleScanFinished() { 486 mHandler.post(new Runnable() { 487 @Override 488 public void run() { 489 mListener.onScanFinished(); 490 } 491 }); 492 } 493 updateDesiredDevice(WifiP2pDevice device)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 connect(final WifiP2pDevice device)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 disconnect()543 private void disconnect() { 544 mDesiredDevice = null; 545 updateConnection(); 546 } 547 retryConnection()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 */ updateConnection()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, mContext.getOpPackageName()); 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 getSessionInfo(WifiP2pGroup info, int session)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 handleStateChanged(boolean enabled)805 private void handleStateChanged(boolean enabled) { 806 mWifiP2pEnabled = enabled; 807 updateWfdEnableState(); 808 } 809 handlePeersChanged()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 handleConnectionChanged(NetworkInfo networkInfo)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 handleConnectionFailure(boolean timeoutOccurred)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 advertiseDisplay(final WifiDisplay display, final Surface surface, final int width, final int height, final int flags)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 unadvertiseDisplay()987 private void unadvertiseDisplay() { 988 advertiseDisplay(null, null, 0, 0, 0); 989 } 990 readvertiseDisplay(WifiDisplay display)991 private void readvertiseDisplay(WifiDisplay display) { 992 advertiseDisplay(display, mAdvertisedDisplaySurface, 993 mAdvertisedDisplayWidth, mAdvertisedDisplayHeight, 994 mAdvertisedDisplayFlags); 995 } 996 getInterfaceAddress(WifiP2pGroup info)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 getPortNumber(WifiP2pDevice device)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 isWifiDisplay(WifiP2pDevice device)1029 private static boolean isWifiDisplay(WifiP2pDevice device) { 1030 return device.wfdInfo != null 1031 && device.wfdInfo.isWfdEnabled() 1032 && isPrimarySinkDeviceType(device.wfdInfo.getDeviceType()); 1033 } 1034 isPrimarySinkDeviceType(int deviceType)1035 private static boolean isPrimarySinkDeviceType(int deviceType) { 1036 return deviceType == WifiP2pWfdInfo.PRIMARY_SINK 1037 || deviceType == WifiP2pWfdInfo.SOURCE_OR_PRIMARY_SINK; 1038 } 1039 describeWifiP2pDevice(WifiP2pDevice device)1040 private static String describeWifiP2pDevice(WifiP2pDevice device) { 1041 return device != null ? device.toString().replace('\n', ',') : "null"; 1042 } 1043 describeWifiP2pGroup(WifiP2pGroup group)1044 private static String describeWifiP2pGroup(WifiP2pGroup group) { 1045 return group != null ? group.toString().replace('\n', ',') : "null"; 1046 } 1047 createWifiDisplay(WifiP2pDevice device)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 { onFeatureStateChanged(int featureState)1099 void onFeatureStateChanged(int featureState); 1100 onScanStarted()1101 void onScanStarted(); onScanResults(WifiDisplay[] availableDisplays)1102 void onScanResults(WifiDisplay[] availableDisplays); onScanFinished()1103 void onScanFinished(); 1104 onDisplayConnecting(WifiDisplay display)1105 void onDisplayConnecting(WifiDisplay display); onDisplayConnectionFailed()1106 void onDisplayConnectionFailed(); onDisplayChanged(WifiDisplay display)1107 void onDisplayChanged(WifiDisplay display); onDisplayConnected(WifiDisplay display, Surface surface, int width, int height, int flags)1108 void onDisplayConnected(WifiDisplay display, 1109 Surface surface, int width, int height, int flags); onDisplaySessionInfo(WifiDisplaySessionInfo sessionInfo)1110 void onDisplaySessionInfo(WifiDisplaySessionInfo sessionInfo); onDisplayDisconnected()1111 void onDisplayDisconnected(); 1112 } 1113 } 1114