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