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 17package com.android.server.display; 18 19import com.android.internal.util.DumpUtils; 20 21import android.content.BroadcastReceiver; 22import android.content.ContentResolver; 23import android.content.Context; 24import android.content.Intent; 25import android.content.IntentFilter; 26import android.database.ContentObserver; 27import android.hardware.display.WifiDisplay; 28import android.hardware.display.WifiDisplayStatus; 29import android.media.AudioManager; 30import android.media.RemoteDisplay; 31import android.net.NetworkInfo; 32import android.net.Uri; 33import android.net.wifi.p2p.WifiP2pConfig; 34import android.net.wifi.p2p.WifiP2pDevice; 35import android.net.wifi.p2p.WifiP2pDeviceList; 36import android.net.wifi.p2p.WifiP2pGroup; 37import android.net.wifi.p2p.WifiP2pManager; 38import android.net.wifi.p2p.WifiP2pWfdInfo; 39import android.net.wifi.p2p.WifiP2pManager.ActionListener; 40import android.net.wifi.p2p.WifiP2pManager.Channel; 41import android.net.wifi.p2p.WifiP2pManager.GroupInfoListener; 42import android.net.wifi.p2p.WifiP2pManager.PeerListListener; 43import android.os.Handler; 44import android.provider.Settings; 45import android.util.Slog; 46import android.view.Surface; 47 48import java.io.PrintWriter; 49import java.net.Inet4Address; 50import java.net.InetAddress; 51import java.net.NetworkInterface; 52import java.net.SocketException; 53import java.util.ArrayList; 54import java.util.Enumeration; 55 56import libcore.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 */ 70final 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 = 60; 77 private static final int RTSP_TIMEOUT_SECONDS = 15; 78 79 private static final int DISCOVER_PEERS_MAX_RETRIES = 10; 80 private static final int DISCOVER_PEERS_RETRY_DELAY_MILLIS = 500; 81 82 private static final int CONNECT_MAX_RETRIES = 3; 83 private static final int CONNECT_RETRY_DELAY_MILLIS = 500; 84 85 // A unique token to identify the remote submix that is managed by Wifi display. 86 // It must match what the media server uses when it starts recording the submix 87 // for transmission. We use 0 although the actual value is currently ignored. 88 private static final int REMOTE_SUBMIX_ADDRESS = 0; 89 90 private final Context mContext; 91 private final Handler mHandler; 92 private final Listener mListener; 93 94 private final WifiP2pManager mWifiP2pManager; 95 private final Channel mWifiP2pChannel; 96 97 private final AudioManager mAudioManager; 98 99 private boolean mWifiP2pEnabled; 100 private boolean mWfdEnabled; 101 private boolean mWfdEnabling; 102 private NetworkInfo mNetworkInfo; 103 104 private final ArrayList<WifiP2pDevice> mAvailableWifiDisplayPeers = 105 new ArrayList<WifiP2pDevice>(); 106 107 // True if Wifi display is enabled by the user. 108 private boolean mWifiDisplayOnSetting; 109 110 // True if there is a call to discoverPeers in progress. 111 private boolean mDiscoverPeersInProgress; 112 113 // Number of discover peers retries remaining. 114 private int mDiscoverPeersRetriesLeft; 115 116 // The device to which we want to connect, or null if we want to be disconnected. 117 private WifiP2pDevice mDesiredDevice; 118 119 // The device to which we are currently connecting, or null if we have already connected 120 // or are not trying to connect. 121 private WifiP2pDevice mConnectingDevice; 122 123 // The device to which we are currently connected, which means we have an active P2P group. 124 private WifiP2pDevice mConnectedDevice; 125 126 // The group info obtained after connecting. 127 private WifiP2pGroup mConnectedDeviceGroupInfo; 128 129 // Number of connection retries remaining. 130 private int mConnectionRetriesLeft; 131 132 // The remote display that is listening on the connection. 133 // Created after the Wifi P2P network is connected. 134 private RemoteDisplay mRemoteDisplay; 135 136 // The remote display interface. 137 private String mRemoteDisplayInterface; 138 139 // True if RTSP has connected. 140 private boolean mRemoteDisplayConnected; 141 142 // True if the remote submix is enabled. 143 private boolean mRemoteSubmixOn; 144 145 // The information we have most recently told WifiDisplayAdapter about. 146 private WifiDisplay mAdvertisedDisplay; 147 private Surface mAdvertisedDisplaySurface; 148 private int mAdvertisedDisplayWidth; 149 private int mAdvertisedDisplayHeight; 150 private int mAdvertisedDisplayFlags; 151 152 public WifiDisplayController(Context context, Handler handler, Listener listener) { 153 mContext = context; 154 mHandler = handler; 155 mListener = listener; 156 157 mWifiP2pManager = (WifiP2pManager)context.getSystemService(Context.WIFI_P2P_SERVICE); 158 mWifiP2pChannel = mWifiP2pManager.initialize(context, handler.getLooper(), null); 159 160 mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE); 161 162 IntentFilter intentFilter = new IntentFilter(); 163 intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION); 164 intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION); 165 intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); 166 context.registerReceiver(mWifiP2pReceiver, intentFilter, null, mHandler); 167 168 ContentObserver settingsObserver = new ContentObserver(mHandler) { 169 @Override 170 public void onChange(boolean selfChange, Uri uri) { 171 updateSettings(); 172 } 173 }; 174 175 final ContentResolver resolver = mContext.getContentResolver(); 176 resolver.registerContentObserver(Settings.Global.getUriFor( 177 Settings.Global.WIFI_DISPLAY_ON), false, settingsObserver); 178 updateSettings(); 179 } 180 181 private void updateSettings() { 182 final ContentResolver resolver = mContext.getContentResolver(); 183 mWifiDisplayOnSetting = Settings.Global.getInt(resolver, 184 Settings.Global.WIFI_DISPLAY_ON, 0) != 0; 185 186 updateWfdEnableState(); 187 } 188 189 public void dump(PrintWriter pw) { 190 pw.println("mWifiDisplayOnSetting=" + mWifiDisplayOnSetting); 191 pw.println("mWifiP2pEnabled=" + mWifiP2pEnabled); 192 pw.println("mWfdEnabled=" + mWfdEnabled); 193 pw.println("mWfdEnabling=" + mWfdEnabling); 194 pw.println("mNetworkInfo=" + mNetworkInfo); 195 pw.println("mDiscoverPeersInProgress=" + mDiscoverPeersInProgress); 196 pw.println("mDiscoverPeersRetriesLeft=" + mDiscoverPeersRetriesLeft); 197 pw.println("mDesiredDevice=" + describeWifiP2pDevice(mDesiredDevice)); 198 pw.println("mConnectingDisplay=" + describeWifiP2pDevice(mConnectingDevice)); 199 pw.println("mConnectedDevice=" + describeWifiP2pDevice(mConnectedDevice)); 200 pw.println("mConnectionRetriesLeft=" + mConnectionRetriesLeft); 201 pw.println("mRemoteDisplay=" + mRemoteDisplay); 202 pw.println("mRemoteDisplayInterface=" + mRemoteDisplayInterface); 203 pw.println("mRemoteDisplayConnected=" + mRemoteDisplayConnected); 204 pw.println("mRemoteSubmixOn=" + mRemoteSubmixOn); 205 pw.println("mAdvertisedDisplay=" + mAdvertisedDisplay); 206 pw.println("mAdvertisedDisplaySurface=" + mAdvertisedDisplaySurface); 207 pw.println("mAdvertisedDisplayWidth=" + mAdvertisedDisplayWidth); 208 pw.println("mAdvertisedDisplayHeight=" + mAdvertisedDisplayHeight); 209 pw.println("mAdvertisedDisplayFlags=" + mAdvertisedDisplayFlags); 210 211 pw.println("mAvailableWifiDisplayPeers: size=" + mAvailableWifiDisplayPeers.size()); 212 for (WifiP2pDevice device : mAvailableWifiDisplayPeers) { 213 pw.println(" " + describeWifiP2pDevice(device)); 214 } 215 } 216 217 public void requestScan() { 218 discoverPeers(); 219 } 220 221 public void requestConnect(String address) { 222 for (WifiP2pDevice device : mAvailableWifiDisplayPeers) { 223 if (device.deviceAddress.equals(address)) { 224 connect(device); 225 } 226 } 227 } 228 229 public void requestDisconnect() { 230 disconnect(); 231 } 232 233 private void updateWfdEnableState() { 234 if (mWifiDisplayOnSetting && mWifiP2pEnabled) { 235 // WFD should be enabled. 236 if (!mWfdEnabled && !mWfdEnabling) { 237 mWfdEnabling = true; 238 239 WifiP2pWfdInfo wfdInfo = new WifiP2pWfdInfo(); 240 wfdInfo.setWfdEnabled(true); 241 wfdInfo.setDeviceType(WifiP2pWfdInfo.WFD_SOURCE); 242 wfdInfo.setSessionAvailable(true); 243 wfdInfo.setControlPort(DEFAULT_CONTROL_PORT); 244 wfdInfo.setMaxThroughput(MAX_THROUGHPUT); 245 mWifiP2pManager.setWFDInfo(mWifiP2pChannel, wfdInfo, new ActionListener() { 246 @Override 247 public void onSuccess() { 248 if (DEBUG) { 249 Slog.d(TAG, "Successfully set WFD info."); 250 } 251 if (mWfdEnabling) { 252 mWfdEnabling = false; 253 mWfdEnabled = true; 254 reportFeatureState(); 255 } 256 } 257 258 @Override 259 public void onFailure(int reason) { 260 if (DEBUG) { 261 Slog.d(TAG, "Failed to set WFD info with reason " + reason + "."); 262 } 263 mWfdEnabling = false; 264 } 265 }); 266 } 267 } else { 268 // WFD should be disabled. 269 mWfdEnabling = false; 270 mWfdEnabled = false; 271 reportFeatureState(); 272 disconnect(); 273 } 274 } 275 276 private void reportFeatureState() { 277 final int featureState = computeFeatureState(); 278 mHandler.post(new Runnable() { 279 @Override 280 public void run() { 281 mListener.onFeatureStateChanged(featureState); 282 } 283 }); 284 } 285 286 private int computeFeatureState() { 287 if (!mWifiP2pEnabled) { 288 return WifiDisplayStatus.FEATURE_STATE_DISABLED; 289 } 290 return mWifiDisplayOnSetting ? WifiDisplayStatus.FEATURE_STATE_ON : 291 WifiDisplayStatus.FEATURE_STATE_OFF; 292 } 293 294 private void discoverPeers() { 295 if (!mDiscoverPeersInProgress) { 296 mDiscoverPeersInProgress = true; 297 mDiscoverPeersRetriesLeft = DISCOVER_PEERS_MAX_RETRIES; 298 handleScanStarted(); 299 tryDiscoverPeers(); 300 } 301 } 302 303 private void tryDiscoverPeers() { 304 mWifiP2pManager.discoverPeers(mWifiP2pChannel, new ActionListener() { 305 @Override 306 public void onSuccess() { 307 if (DEBUG) { 308 Slog.d(TAG, "Discover peers succeeded. Requesting peers now."); 309 } 310 311 mDiscoverPeersInProgress = false; 312 requestPeers(); 313 } 314 315 @Override 316 public void onFailure(int reason) { 317 if (DEBUG) { 318 Slog.d(TAG, "Discover peers failed with reason " + reason + "."); 319 } 320 321 if (mDiscoverPeersInProgress) { 322 if (reason == 0 && mDiscoverPeersRetriesLeft > 0 && mWfdEnabled) { 323 mHandler.postDelayed(new Runnable() { 324 @Override 325 public void run() { 326 if (mDiscoverPeersInProgress) { 327 if (mDiscoverPeersRetriesLeft > 0 && mWfdEnabled) { 328 mDiscoverPeersRetriesLeft -= 1; 329 if (DEBUG) { 330 Slog.d(TAG, "Retrying discovery. Retries left: " 331 + mDiscoverPeersRetriesLeft); 332 } 333 tryDiscoverPeers(); 334 } else { 335 handleScanFinished(); 336 mDiscoverPeersInProgress = false; 337 } 338 } 339 } 340 }, DISCOVER_PEERS_RETRY_DELAY_MILLIS); 341 } else { 342 handleScanFinished(); 343 mDiscoverPeersInProgress = false; 344 } 345 } 346 } 347 }); 348 } 349 350 private void requestPeers() { 351 mWifiP2pManager.requestPeers(mWifiP2pChannel, new PeerListListener() { 352 @Override 353 public void onPeersAvailable(WifiP2pDeviceList peers) { 354 if (DEBUG) { 355 Slog.d(TAG, "Received list of peers."); 356 } 357 358 mAvailableWifiDisplayPeers.clear(); 359 for (WifiP2pDevice device : peers.getDeviceList()) { 360 if (DEBUG) { 361 Slog.d(TAG, " " + describeWifiP2pDevice(device)); 362 } 363 364 if (isWifiDisplay(device)) { 365 mAvailableWifiDisplayPeers.add(device); 366 } 367 } 368 369 handleScanFinished(); 370 } 371 }); 372 } 373 374 private void handleScanStarted() { 375 mHandler.post(new Runnable() { 376 @Override 377 public void run() { 378 mListener.onScanStarted(); 379 } 380 }); 381 } 382 383 private void handleScanFinished() { 384 final int count = mAvailableWifiDisplayPeers.size(); 385 final WifiDisplay[] displays = WifiDisplay.CREATOR.newArray(count); 386 for (int i = 0; i < count; i++) { 387 displays[i] = createWifiDisplay(mAvailableWifiDisplayPeers.get(i)); 388 } 389 390 mHandler.post(new Runnable() { 391 @Override 392 public void run() { 393 mListener.onScanFinished(displays); 394 } 395 }); 396 } 397 398 private void connect(final WifiP2pDevice device) { 399 if (mDesiredDevice != null 400 && !mDesiredDevice.deviceAddress.equals(device.deviceAddress)) { 401 if (DEBUG) { 402 Slog.d(TAG, "connect: nothing to do, already connecting to " 403 + describeWifiP2pDevice(device)); 404 } 405 return; 406 } 407 408 if (mConnectedDevice != null 409 && !mConnectedDevice.deviceAddress.equals(device.deviceAddress) 410 && mDesiredDevice == null) { 411 if (DEBUG) { 412 Slog.d(TAG, "connect: nothing to do, already connected to " 413 + describeWifiP2pDevice(device) + " and not part way through " 414 + "connecting to a different device."); 415 } 416 return; 417 } 418 419 mDesiredDevice = device; 420 mConnectionRetriesLeft = CONNECT_MAX_RETRIES; 421 updateConnection(); 422 } 423 424 private void disconnect() { 425 mDesiredDevice = null; 426 updateConnection(); 427 } 428 429 private void retryConnection() { 430 // Cheap hack. Make a new instance of the device object so that we 431 // can distinguish it from the previous connection attempt. 432 // This will cause us to tear everything down before we try again. 433 mDesiredDevice = new WifiP2pDevice(mDesiredDevice); 434 updateConnection(); 435 } 436 437 /** 438 * This function is called repeatedly after each asynchronous operation 439 * until all preconditions for the connection have been satisfied and the 440 * connection is established (or not). 441 */ 442 private void updateConnection() { 443 // Step 1. Before we try to connect to a new device, tell the system we 444 // have disconnected from the old one. 445 if (mRemoteDisplay != null && mConnectedDevice != mDesiredDevice) { 446 Slog.i(TAG, "Stopped listening for RTSP connection on " + mRemoteDisplayInterface 447 + " from Wifi display: " + mConnectedDevice.deviceName); 448 449 mRemoteDisplay.dispose(); 450 mRemoteDisplay = null; 451 mRemoteDisplayInterface = null; 452 mRemoteDisplayConnected = false; 453 mHandler.removeCallbacks(mRtspTimeout); 454 455 setRemoteSubmixOn(false); 456 unadvertiseDisplay(); 457 458 // continue to next step 459 } 460 461 // Step 2. Before we try to connect to a new device, disconnect from the old one. 462 if (mConnectedDevice != null && mConnectedDevice != mDesiredDevice) { 463 Slog.i(TAG, "Disconnecting from Wifi display: " + mConnectedDevice.deviceName); 464 465 unadvertiseDisplay(); 466 467 final WifiP2pDevice oldDevice = mConnectedDevice; 468 mWifiP2pManager.removeGroup(mWifiP2pChannel, new ActionListener() { 469 @Override 470 public void onSuccess() { 471 Slog.i(TAG, "Disconnected from Wifi display: " + oldDevice.deviceName); 472 next(); 473 } 474 475 @Override 476 public void onFailure(int reason) { 477 Slog.i(TAG, "Failed to disconnect from Wifi display: " 478 + oldDevice.deviceName + ", reason=" + reason); 479 next(); 480 } 481 482 private void next() { 483 if (mConnectedDevice == oldDevice) { 484 mConnectedDevice = null; 485 updateConnection(); 486 } 487 } 488 }); 489 return; // wait for asynchronous callback 490 } 491 492 // Step 3. Before we try to connect to a new device, stop trying to connect 493 // to the old one. 494 if (mConnectingDevice != null && mConnectingDevice != mDesiredDevice) { 495 Slog.i(TAG, "Canceling connection to Wifi display: " + mConnectingDevice.deviceName); 496 497 unadvertiseDisplay(); 498 mHandler.removeCallbacks(mConnectionTimeout); 499 500 final WifiP2pDevice oldDevice = mConnectingDevice; 501 mWifiP2pManager.cancelConnect(mWifiP2pChannel, new ActionListener() { 502 @Override 503 public void onSuccess() { 504 Slog.i(TAG, "Canceled connection to Wifi display: " + oldDevice.deviceName); 505 next(); 506 } 507 508 @Override 509 public void onFailure(int reason) { 510 Slog.i(TAG, "Failed to cancel connection to Wifi display: " 511 + oldDevice.deviceName + ", reason=" + reason); 512 next(); 513 } 514 515 private void next() { 516 if (mConnectingDevice == oldDevice) { 517 mConnectingDevice = null; 518 updateConnection(); 519 } 520 } 521 }); 522 return; // wait for asynchronous callback 523 } 524 525 // Step 4. If we wanted to disconnect, then mission accomplished. 526 if (mDesiredDevice == null) { 527 unadvertiseDisplay(); 528 return; // done 529 } 530 531 // Step 5. Try to connect. 532 if (mConnectedDevice == null && mConnectingDevice == null) { 533 Slog.i(TAG, "Connecting to Wifi display: " + mDesiredDevice.deviceName); 534 535 mConnectingDevice = mDesiredDevice; 536 WifiP2pConfig config = new WifiP2pConfig(); 537 config.deviceAddress = mConnectingDevice.deviceAddress; 538 // Helps with STA & P2P concurrency 539 config.groupOwnerIntent = WifiP2pConfig.MIN_GROUP_OWNER_INTENT; 540 541 WifiDisplay display = createWifiDisplay(mConnectingDevice); 542 advertiseDisplay(display, null, 0, 0, 0); 543 544 final WifiP2pDevice newDevice = mDesiredDevice; 545 mWifiP2pManager.connect(mWifiP2pChannel, config, new ActionListener() { 546 @Override 547 public void onSuccess() { 548 // The connection may not yet be established. We still need to wait 549 // for WIFI_P2P_CONNECTION_CHANGED_ACTION. However, we might never 550 // get that broadcast, so we register a timeout. 551 Slog.i(TAG, "Initiated connection to Wifi display: " + newDevice.deviceName); 552 553 mHandler.postDelayed(mConnectionTimeout, CONNECTION_TIMEOUT_SECONDS * 1000); 554 } 555 556 @Override 557 public void onFailure(int reason) { 558 if (mConnectingDevice == newDevice) { 559 Slog.i(TAG, "Failed to initiate connection to Wifi display: " 560 + newDevice.deviceName + ", reason=" + reason); 561 mConnectingDevice = null; 562 handleConnectionFailure(false); 563 } 564 } 565 }); 566 return; // wait for asynchronous callback 567 } 568 569 // Step 6. Listen for incoming connections. 570 if (mConnectedDevice != null && mRemoteDisplay == null) { 571 Inet4Address addr = getInterfaceAddress(mConnectedDeviceGroupInfo); 572 if (addr == null) { 573 Slog.i(TAG, "Failed to get local interface address for communicating " 574 + "with Wifi display: " + mConnectedDevice.deviceName); 575 handleConnectionFailure(false); 576 return; // done 577 } 578 579 setRemoteSubmixOn(true); 580 581 final WifiP2pDevice oldDevice = mConnectedDevice; 582 final int port = getPortNumber(mConnectedDevice); 583 final String iface = addr.getHostAddress() + ":" + port; 584 mRemoteDisplayInterface = iface; 585 586 Slog.i(TAG, "Listening for RTSP connection on " + iface 587 + " from Wifi display: " + mConnectedDevice.deviceName); 588 589 mRemoteDisplay = RemoteDisplay.listen(iface, new RemoteDisplay.Listener() { 590 @Override 591 public void onDisplayConnected(Surface surface, 592 int width, int height, int flags) { 593 if (mConnectedDevice == oldDevice && !mRemoteDisplayConnected) { 594 Slog.i(TAG, "Opened RTSP connection with Wifi display: " 595 + mConnectedDevice.deviceName); 596 mRemoteDisplayConnected = true; 597 mHandler.removeCallbacks(mRtspTimeout); 598 599 final WifiDisplay display = createWifiDisplay(mConnectedDevice); 600 advertiseDisplay(display, surface, width, height, flags); 601 } 602 } 603 604 @Override 605 public void onDisplayDisconnected() { 606 if (mConnectedDevice == oldDevice) { 607 Slog.i(TAG, "Closed RTSP connection with Wifi display: " 608 + mConnectedDevice.deviceName); 609 mHandler.removeCallbacks(mRtspTimeout); 610 disconnect(); 611 } 612 } 613 614 @Override 615 public void onDisplayError(int error) { 616 if (mConnectedDevice == oldDevice) { 617 Slog.i(TAG, "Lost RTSP connection with Wifi display due to error " 618 + error + ": " + mConnectedDevice.deviceName); 619 mHandler.removeCallbacks(mRtspTimeout); 620 handleConnectionFailure(false); 621 } 622 } 623 }, mHandler); 624 625 mHandler.postDelayed(mRtspTimeout, RTSP_TIMEOUT_SECONDS * 1000); 626 } 627 } 628 629 private void setRemoteSubmixOn(boolean on) { 630 if (mRemoteSubmixOn != on) { 631 mRemoteSubmixOn = on; 632 mAudioManager.setRemoteSubmixOn(on, REMOTE_SUBMIX_ADDRESS); 633 } 634 } 635 636 private void handleStateChanged(boolean enabled) { 637 mWifiP2pEnabled = enabled; 638 updateWfdEnableState(); 639 } 640 641 private void handlePeersChanged() { 642 // Even if wfd is disabled, it is best to get the latest set of peers to 643 // keep in sync with the p2p framework 644 requestPeers(); 645 } 646 647 private void handleConnectionChanged(NetworkInfo networkInfo) { 648 mNetworkInfo = networkInfo; 649 if (mWfdEnabled && networkInfo.isConnected()) { 650 if (mDesiredDevice != null) { 651 mWifiP2pManager.requestGroupInfo(mWifiP2pChannel, new GroupInfoListener() { 652 @Override 653 public void onGroupInfoAvailable(WifiP2pGroup info) { 654 if (DEBUG) { 655 Slog.d(TAG, "Received group info: " + describeWifiP2pGroup(info)); 656 } 657 658 if (mConnectingDevice != null && !info.contains(mConnectingDevice)) { 659 Slog.i(TAG, "Aborting connection to Wifi display because " 660 + "the current P2P group does not contain the device " 661 + "we expected to find: " + mConnectingDevice.deviceName 662 + ", group info was: " + describeWifiP2pGroup(info)); 663 handleConnectionFailure(false); 664 return; 665 } 666 667 if (mDesiredDevice != null && !info.contains(mDesiredDevice)) { 668 disconnect(); 669 return; 670 } 671 672 if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) { 673 Slog.i(TAG, "Connected to Wifi display: " 674 + mConnectingDevice.deviceName); 675 676 mHandler.removeCallbacks(mConnectionTimeout); 677 mConnectedDeviceGroupInfo = info; 678 mConnectedDevice = mConnectingDevice; 679 mConnectingDevice = null; 680 updateConnection(); 681 } 682 } 683 }); 684 } 685 } else { 686 disconnect(); 687 688 // After disconnection for a group, for some reason we have a tendency 689 // to get a peer change notification with an empty list of peers. 690 // Perform a fresh scan. 691 if (mWfdEnabled) { 692 requestPeers(); 693 } 694 } 695 } 696 697 private final Runnable mConnectionTimeout = new Runnable() { 698 @Override 699 public void run() { 700 if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) { 701 Slog.i(TAG, "Timed out waiting for Wifi display connection after " 702 + CONNECTION_TIMEOUT_SECONDS + " seconds: " 703 + mConnectingDevice.deviceName); 704 handleConnectionFailure(true); 705 } 706 } 707 }; 708 709 private final Runnable mRtspTimeout = new Runnable() { 710 @Override 711 public void run() { 712 if (mConnectedDevice != null 713 && mRemoteDisplay != null && !mRemoteDisplayConnected) { 714 Slog.i(TAG, "Timed out waiting for Wifi display RTSP connection after " 715 + RTSP_TIMEOUT_SECONDS + " seconds: " 716 + mConnectedDevice.deviceName); 717 handleConnectionFailure(true); 718 } 719 } 720 }; 721 722 private void handleConnectionFailure(boolean timeoutOccurred) { 723 Slog.i(TAG, "Wifi display connection failed!"); 724 725 if (mDesiredDevice != null) { 726 if (mConnectionRetriesLeft > 0) { 727 final WifiP2pDevice oldDevice = mDesiredDevice; 728 mHandler.postDelayed(new Runnable() { 729 @Override 730 public void run() { 731 if (mDesiredDevice == oldDevice && mConnectionRetriesLeft > 0) { 732 mConnectionRetriesLeft -= 1; 733 Slog.i(TAG, "Retrying Wifi display connection. Retries left: " 734 + mConnectionRetriesLeft); 735 retryConnection(); 736 } 737 } 738 }, timeoutOccurred ? 0 : CONNECT_RETRY_DELAY_MILLIS); 739 } else { 740 disconnect(); 741 } 742 } 743 } 744 745 private void advertiseDisplay(final WifiDisplay display, 746 final Surface surface, final int width, final int height, final int flags) { 747 if (!Objects.equal(mAdvertisedDisplay, display) 748 || mAdvertisedDisplaySurface != surface 749 || mAdvertisedDisplayWidth != width 750 || mAdvertisedDisplayHeight != height 751 || mAdvertisedDisplayFlags != flags) { 752 final WifiDisplay oldDisplay = mAdvertisedDisplay; 753 final Surface oldSurface = mAdvertisedDisplaySurface; 754 755 mAdvertisedDisplay = display; 756 mAdvertisedDisplaySurface = surface; 757 mAdvertisedDisplayWidth = width; 758 mAdvertisedDisplayHeight = height; 759 mAdvertisedDisplayFlags = flags; 760 761 mHandler.post(new Runnable() { 762 @Override 763 public void run() { 764 if (oldSurface != null && surface != oldSurface) { 765 mListener.onDisplayDisconnected(); 766 } else if (oldDisplay != null && !Objects.equal(display, oldDisplay)) { 767 mListener.onDisplayConnectionFailed(); 768 } 769 770 if (display != null) { 771 if (!Objects.equal(display, oldDisplay)) { 772 mListener.onDisplayConnecting(display); 773 } 774 if (surface != null && surface != oldSurface) { 775 mListener.onDisplayConnected(display, surface, width, height, flags); 776 } 777 } 778 } 779 }); 780 } 781 } 782 783 private void unadvertiseDisplay() { 784 advertiseDisplay(null, null, 0, 0, 0); 785 } 786 787 private static Inet4Address getInterfaceAddress(WifiP2pGroup info) { 788 NetworkInterface iface; 789 try { 790 iface = NetworkInterface.getByName(info.getInterface()); 791 } catch (SocketException ex) { 792 Slog.w(TAG, "Could not obtain address of network interface " 793 + info.getInterface(), ex); 794 return null; 795 } 796 797 Enumeration<InetAddress> addrs = iface.getInetAddresses(); 798 while (addrs.hasMoreElements()) { 799 InetAddress addr = addrs.nextElement(); 800 if (addr instanceof Inet4Address) { 801 return (Inet4Address)addr; 802 } 803 } 804 805 Slog.w(TAG, "Could not obtain address of network interface " 806 + info.getInterface() + " because it had no IPv4 addresses."); 807 return null; 808 } 809 810 private static int getPortNumber(WifiP2pDevice device) { 811 if (device.deviceName.startsWith("DIRECT-") 812 && device.deviceName.endsWith("Broadcom")) { 813 // These dongles ignore the port we broadcast in our WFD IE. 814 return 8554; 815 } 816 return DEFAULT_CONTROL_PORT; 817 } 818 819 private static boolean isWifiDisplay(WifiP2pDevice device) { 820 return device.wfdInfo != null 821 && device.wfdInfo.isWfdEnabled() 822 && isPrimarySinkDeviceType(device.wfdInfo.getDeviceType()); 823 } 824 825 private static boolean isPrimarySinkDeviceType(int deviceType) { 826 return deviceType == WifiP2pWfdInfo.PRIMARY_SINK 827 || deviceType == WifiP2pWfdInfo.SOURCE_OR_PRIMARY_SINK; 828 } 829 830 private static String describeWifiP2pDevice(WifiP2pDevice device) { 831 return device != null ? device.toString().replace('\n', ',') : "null"; 832 } 833 834 private static String describeWifiP2pGroup(WifiP2pGroup group) { 835 return group != null ? group.toString().replace('\n', ',') : "null"; 836 } 837 838 private static WifiDisplay createWifiDisplay(WifiP2pDevice device) { 839 return new WifiDisplay(device.deviceAddress, device.deviceName, null); 840 } 841 842 private final BroadcastReceiver mWifiP2pReceiver = new BroadcastReceiver() { 843 @Override 844 public void onReceive(Context context, Intent intent) { 845 final String action = intent.getAction(); 846 if (action.equals(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)) { 847 // This broadcast is sticky so we'll always get the initial Wifi P2P state 848 // on startup. 849 boolean enabled = (intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, 850 WifiP2pManager.WIFI_P2P_STATE_DISABLED)) == 851 WifiP2pManager.WIFI_P2P_STATE_ENABLED; 852 if (DEBUG) { 853 Slog.d(TAG, "Received WIFI_P2P_STATE_CHANGED_ACTION: enabled=" 854 + enabled); 855 } 856 857 handleStateChanged(enabled); 858 } else if (action.equals(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)) { 859 if (DEBUG) { 860 Slog.d(TAG, "Received WIFI_P2P_PEERS_CHANGED_ACTION."); 861 } 862 863 handlePeersChanged(); 864 } else if (action.equals(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)) { 865 NetworkInfo networkInfo = (NetworkInfo)intent.getParcelableExtra( 866 WifiP2pManager.EXTRA_NETWORK_INFO); 867 if (DEBUG) { 868 Slog.d(TAG, "Received WIFI_P2P_CONNECTION_CHANGED_ACTION: networkInfo=" 869 + networkInfo); 870 } 871 872 handleConnectionChanged(networkInfo); 873 } 874 } 875 }; 876 877 /** 878 * Called on the handler thread when displays are connected or disconnected. 879 */ 880 public interface Listener { 881 void onFeatureStateChanged(int featureState); 882 883 void onScanStarted(); 884 void onScanFinished(WifiDisplay[] availableDisplays); 885 886 void onDisplayConnecting(WifiDisplay display); 887 void onDisplayConnectionFailed(); 888 void onDisplayConnected(WifiDisplay display, 889 Surface surface, int width, int height, int flags); 890 void onDisplayDisconnected(); 891 } 892} 893