WifiDisplayController.java revision 0cfebf28b15e85a42981a8f9e6a09556bef36ea3
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.Context; 23import android.content.Intent; 24import android.content.IntentFilter; 25import android.net.NetworkInfo; 26import android.net.wifi.p2p.WifiP2pConfig; 27import android.net.wifi.p2p.WifiP2pDevice; 28import android.net.wifi.p2p.WifiP2pDeviceList; 29import android.net.wifi.p2p.WifiP2pGroup; 30import android.net.wifi.p2p.WifiP2pManager; 31import android.net.wifi.p2p.WifiP2pWfdInfo; 32import android.net.wifi.p2p.WifiP2pManager.ActionListener; 33import android.net.wifi.p2p.WifiP2pManager.Channel; 34import android.net.wifi.p2p.WifiP2pManager.GroupInfoListener; 35import android.net.wifi.p2p.WifiP2pManager.PeerListListener; 36import android.os.Handler; 37import android.util.Slog; 38 39import java.io.PrintWriter; 40import java.net.Inet4Address; 41import java.net.InetAddress; 42import java.net.NetworkInterface; 43import java.net.SocketException; 44import java.util.ArrayList; 45import java.util.Enumeration; 46 47/** 48 * Manages all of the various asynchronous interactions with the {@link WifiP2pManager} 49 * on behalf of {@link WifiDisplayAdapter}. 50 * <p> 51 * This code is isolated from {@link WifiDisplayAdapter} so that we can avoid 52 * accidentally introducing any deadlocks due to the display manager calling 53 * outside of itself while holding its lock. It's also way easier to write this 54 * asynchronous code if we can assume that it is single-threaded. 55 * </p><p> 56 * The controller must be instantiated on the handler thread. 57 * </p> 58 */ 59final class WifiDisplayController implements DumpUtils.Dump { 60 private static final String TAG = "WifiDisplayController"; 61 private static final boolean DEBUG = true; 62 63 private static final int DEFAULT_CONTROL_PORT = 7236; 64 private static final int MAX_THROUGHPUT = 50; 65 private static final int CONNECTION_TIMEOUT_SECONDS = 30; 66 67 private static final int DISCOVER_PEERS_MAX_RETRIES = 10; 68 private static final int DISCOVER_PEERS_RETRY_DELAY_MILLIS = 500; 69 70 private static final int CONNECT_MAX_RETRIES = 3; 71 private static final int CONNECT_RETRY_DELAY_MILLIS = 500; 72 73 private final Context mContext; 74 private final Handler mHandler; 75 private final Listener mListener; 76 private final WifiP2pManager mWifiP2pManager; 77 private final Channel mWifiP2pChannel; 78 79 private boolean mWifiP2pEnabled; 80 private boolean mWfdEnabled; 81 private boolean mWfdEnabling; 82 private NetworkInfo mNetworkInfo; 83 84 private final ArrayList<WifiP2pDevice> mKnownWifiDisplayPeers = 85 new ArrayList<WifiP2pDevice>(); 86 87 // True if there is a call to discoverPeers in progress. 88 private boolean mDiscoverPeersInProgress; 89 90 // Number of discover peers retries remaining. 91 private int mDiscoverPeersRetriesLeft; 92 93 // The device to which we want to connect, or null if we want to be disconnected. 94 private WifiP2pDevice mDesiredDevice; 95 96 // The device to which we are currently connecting, or null if we have already connected 97 // or are not trying to connect. 98 private WifiP2pDevice mConnectingDevice; 99 100 // The device to which we are currently connected, which means we have an active P2P group. 101 private WifiP2pDevice mConnectedDevice; 102 103 // The group info obtained after connecting. 104 private WifiP2pGroup mConnectedDeviceGroupInfo; 105 106 // The device that we announced to the rest of the system. 107 private WifiP2pDevice mPublishedDevice; 108 109 // Number of connection retries remaining. 110 private int mConnectionRetriesLeft; 111 112 public WifiDisplayController(Context context, Handler handler, Listener listener) { 113 mContext = context; 114 mHandler = handler; 115 mListener = listener; 116 117 mWifiP2pManager = (WifiP2pManager)context.getSystemService(Context.WIFI_P2P_SERVICE); 118 mWifiP2pChannel = mWifiP2pManager.initialize(context, handler.getLooper(), null); 119 120 IntentFilter intentFilter = new IntentFilter(); 121 intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION); 122 intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION); 123 intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); 124 context.registerReceiver(mWifiP2pReceiver, intentFilter); 125 } 126 127 public void dump(PrintWriter pw) { 128 pw.println("mWifiP2pEnabled=" + mWifiP2pEnabled); 129 pw.println("mWfdEnabled=" + mWfdEnabled); 130 pw.println("mWfdEnabling=" + mWfdEnabling); 131 pw.println("mNetworkInfo=" + mNetworkInfo); 132 pw.println("mDiscoverPeersInProgress=" + mDiscoverPeersInProgress); 133 pw.println("mDiscoverPeersRetriesLeft=" + mDiscoverPeersRetriesLeft); 134 pw.println("mDesiredDevice=" + describeWifiP2pDevice(mDesiredDevice)); 135 pw.println("mConnectingDisplay=" + describeWifiP2pDevice(mConnectingDevice)); 136 pw.println("mConnectedDevice=" + describeWifiP2pDevice(mConnectedDevice)); 137 pw.println("mPublishedDevice=" + describeWifiP2pDevice(mPublishedDevice)); 138 pw.println("mConnectionRetriesLeft=" + mConnectionRetriesLeft); 139 140 pw.println("mKnownWifiDisplayPeers: size=" + mKnownWifiDisplayPeers.size()); 141 for (WifiP2pDevice device : mKnownWifiDisplayPeers) { 142 pw.println(" " + describeWifiP2pDevice(device)); 143 } 144 } 145 146 private void enableWfd() { 147 if (!mWfdEnabled && !mWfdEnabling) { 148 mWfdEnabling = true; 149 150 WifiP2pWfdInfo wfdInfo = new WifiP2pWfdInfo(); 151 wfdInfo.setWfdEnabled(true); 152 wfdInfo.setDeviceType(WifiP2pWfdInfo.WFD_SOURCE); 153 wfdInfo.setSessionAvailable(true); 154 wfdInfo.setControlPort(DEFAULT_CONTROL_PORT); 155 wfdInfo.setMaxThroughput(MAX_THROUGHPUT); 156 mWifiP2pManager.setWFDInfo(mWifiP2pChannel, wfdInfo, new ActionListener() { 157 @Override 158 public void onSuccess() { 159 if (DEBUG) { 160 Slog.d(TAG, "Successfully set WFD info."); 161 } 162 if (mWfdEnabling) { 163 mWfdEnabled = true; 164 mWfdEnabling = false; 165 discoverPeers(); 166 } 167 } 168 169 @Override 170 public void onFailure(int reason) { 171 if (DEBUG) { 172 Slog.d(TAG, "Failed to set WFD info with reason " + reason + "."); 173 } 174 mWfdEnabling = false; 175 } 176 }); 177 } 178 } 179 180 private void discoverPeers() { 181 if (!mDiscoverPeersInProgress) { 182 mDiscoverPeersInProgress = true; 183 mDiscoverPeersRetriesLeft = DISCOVER_PEERS_MAX_RETRIES; 184 tryDiscoverPeers(); 185 } 186 } 187 188 private void tryDiscoverPeers() { 189 mWifiP2pManager.discoverPeers(mWifiP2pChannel, new ActionListener() { 190 @Override 191 public void onSuccess() { 192 if (DEBUG) { 193 Slog.d(TAG, "Discover peers succeeded. Requesting peers now."); 194 } 195 196 mDiscoverPeersInProgress = false; 197 requestPeers(); 198 } 199 200 @Override 201 public void onFailure(int reason) { 202 if (DEBUG) { 203 Slog.d(TAG, "Discover peers failed with reason " + reason + "."); 204 } 205 206 if (mDiscoverPeersInProgress) { 207 if (reason == 0 && mDiscoverPeersRetriesLeft > 0 && mWfdEnabled) { 208 mHandler.postDelayed(new Runnable() { 209 @Override 210 public void run() { 211 if (mDiscoverPeersInProgress) { 212 if (mDiscoverPeersRetriesLeft > 0 && mWfdEnabled) { 213 mDiscoverPeersRetriesLeft -= 1; 214 if (DEBUG) { 215 Slog.d(TAG, "Retrying discovery. Retries left: " 216 + mDiscoverPeersRetriesLeft); 217 } 218 tryDiscoverPeers(); 219 } else { 220 mDiscoverPeersInProgress = false; 221 } 222 } 223 } 224 }, DISCOVER_PEERS_RETRY_DELAY_MILLIS); 225 } else { 226 mDiscoverPeersInProgress = false; 227 } 228 } 229 } 230 }); 231 } 232 233 private void requestPeers() { 234 mWifiP2pManager.requestPeers(mWifiP2pChannel, new PeerListListener() { 235 @Override 236 public void onPeersAvailable(WifiP2pDeviceList peers) { 237 if (DEBUG) { 238 Slog.d(TAG, "Received list of peers."); 239 } 240 241 mKnownWifiDisplayPeers.clear(); 242 for (WifiP2pDevice device : peers.getDeviceList()) { 243 if (DEBUG) { 244 Slog.d(TAG, " " + describeWifiP2pDevice(device)); 245 } 246 247 if (isWifiDisplay(device)) { 248 mKnownWifiDisplayPeers.add(device); 249 } 250 } 251 252 // TODO: shouldn't auto-connect like this, let UI do it explicitly 253 if (!mKnownWifiDisplayPeers.isEmpty()) { 254 final WifiP2pDevice device = mKnownWifiDisplayPeers.get(0); 255 256 if (device.status == WifiP2pDevice.AVAILABLE) { 257 connect(device); 258 } 259 } 260 261 // TODO: publish this information to applications 262 } 263 }); 264 } 265 266 private void connect(final WifiP2pDevice device) { 267 if (mDesiredDevice != null 268 && !mDesiredDevice.deviceAddress.equals(device.deviceAddress)) { 269 if (DEBUG) { 270 Slog.d(TAG, "connect: nothing to do, already connecting to " 271 + describeWifiP2pDevice(device)); 272 } 273 return; 274 } 275 276 if (mConnectedDevice != null 277 && !mConnectedDevice.deviceAddress.equals(device.deviceAddress) 278 && mDesiredDevice == null) { 279 if (DEBUG) { 280 Slog.d(TAG, "connect: nothing to do, already connected to " 281 + describeWifiP2pDevice(device) + " and not part way through " 282 + "connecting to a different device."); 283 } 284 return; 285 } 286 287 mDesiredDevice = device; 288 mConnectionRetriesLeft = CONNECT_MAX_RETRIES; 289 updateConnection(); 290 } 291 292 private void disconnect() { 293 mDesiredDevice = null; 294 updateConnection(); 295 } 296 297 private void retryConnection() { 298 if (mDesiredDevice != null && mPublishedDevice != mDesiredDevice 299 && mConnectionRetriesLeft > 0) { 300 mConnectionRetriesLeft -= 1; 301 Slog.i(TAG, "Retrying Wifi display connection. Retries left: " 302 + mConnectionRetriesLeft); 303 304 // Cheap hack. Make a new instance of the device object so that we 305 // can distinguish it from the previous connection attempt. 306 // This will cause us to tear everything down before we try again. 307 mDesiredDevice = new WifiP2pDevice(mDesiredDevice); 308 updateConnection(); 309 } 310 } 311 312 /** 313 * This function is called repeatedly after each asynchronous operation 314 * until all preconditions for the connection have been satisfied and the 315 * connection is established (or not). 316 */ 317 private void updateConnection() { 318 // Step 1. Before we try to connect to a new device, tell the system we 319 // have disconnected from the old one. 320 if (mPublishedDevice != null && mPublishedDevice != mDesiredDevice) { 321 mHandler.post(new Runnable() { 322 @Override 323 public void run() { 324 mListener.onDisplayDisconnected(); 325 } 326 }); 327 mPublishedDevice = null; 328 329 // continue to next step 330 } 331 332 // Step 2. Before we try to connect to a new device, disconnect from the old one. 333 if (mConnectedDevice != null && mConnectedDevice != mDesiredDevice) { 334 Slog.i(TAG, "Disconnecting from Wifi display: " + mConnectedDevice.deviceName); 335 336 final WifiP2pDevice oldDevice = mConnectedDevice; 337 mWifiP2pManager.removeGroup(mWifiP2pChannel, new ActionListener() { 338 @Override 339 public void onSuccess() { 340 Slog.i(TAG, "Disconnected from Wifi display: " + oldDevice.deviceName); 341 next(); 342 } 343 344 @Override 345 public void onFailure(int reason) { 346 Slog.i(TAG, "Failed to disconnect from Wifi display: " 347 + oldDevice.deviceName + ", reason=" + reason); 348 next(); 349 } 350 351 private void next() { 352 if (mConnectedDevice == oldDevice) { 353 mConnectedDevice = null; 354 updateConnection(); 355 } 356 } 357 }); 358 return; // wait for asynchronous callback 359 } 360 361 // Step 3. Before we try to connect to a new device, stop trying to connect 362 // to the old one. 363 if (mConnectingDevice != null && mConnectingDevice != mDesiredDevice) { 364 Slog.i(TAG, "Canceling connection to Wifi display: " + mConnectingDevice.deviceName); 365 366 mHandler.removeCallbacks(mConnectionTimeout); 367 368 final WifiP2pDevice oldDevice = mConnectingDevice; 369 mWifiP2pManager.cancelConnect(mWifiP2pChannel, new ActionListener() { 370 @Override 371 public void onSuccess() { 372 Slog.i(TAG, "Canceled connection to Wifi display: " + oldDevice.deviceName); 373 next(); 374 } 375 376 @Override 377 public void onFailure(int reason) { 378 Slog.i(TAG, "Failed to cancel connection to Wifi display: " 379 + oldDevice.deviceName + ", reason=" + reason); 380 next(); 381 } 382 383 private void next() { 384 if (mConnectingDevice == oldDevice) { 385 mConnectingDevice = null; 386 updateConnection(); 387 } 388 } 389 }); 390 return; // wait for asynchronous callback 391 } 392 393 // Step 4. If we wanted to disconnect, then mission accomplished. 394 if (mDesiredDevice == null) { 395 return; // done 396 } 397 398 // Step 5. Try to connect. 399 if (mConnectedDevice == null && mConnectingDevice == null) { 400 Slog.i(TAG, "Connecting to Wifi display: " + mDesiredDevice.deviceName); 401 402 mConnectingDevice = mDesiredDevice; 403 WifiP2pConfig config = new WifiP2pConfig(); 404 config.deviceAddress = mConnectingDevice.deviceAddress; 405 406 final WifiP2pDevice newDevice = mDesiredDevice; 407 mWifiP2pManager.connect(mWifiP2pChannel, config, new ActionListener() { 408 @Override 409 public void onSuccess() { 410 // The connection may not yet be established. We still need to wait 411 // for WIFI_P2P_CONNECTION_CHANGED_ACTION. However, we might never 412 // get that broadcast, so we register a timeout. 413 Slog.i(TAG, "Initiated connection to Wifi display: " + newDevice.deviceName); 414 415 mHandler.postDelayed(mConnectionTimeout, CONNECTION_TIMEOUT_SECONDS * 1000); 416 } 417 418 @Override 419 public void onFailure(int reason) { 420 Slog.i(TAG, "Failed to initiate connection to Wifi display: " 421 + newDevice.deviceName + ", reason=" + reason); 422 if (mConnectingDevice == newDevice) { 423 mConnectingDevice = null; 424 handleConnectionFailure(false); 425 } 426 } 427 }); 428 return; // wait for asynchronous callback 429 } 430 431 // Step 6. Publish the new connection. 432 if (mConnectedDevice != null && mPublishedDevice == null) { 433 Inet4Address addr = getInterfaceAddress(mConnectedDeviceGroupInfo); 434 if (addr == null) { 435 Slog.i(TAG, "Failed to get local interface address for communicating " 436 + "with Wifi display: " + mConnectedDevice.deviceName); 437 handleConnectionFailure(false); 438 return; // done 439 } 440 441 WifiP2pWfdInfo wfdInfo = mConnectedDevice.wfdInfo; 442 int port = (wfdInfo != null ? wfdInfo.getControlPort() : DEFAULT_CONTROL_PORT); 443 final String name = mConnectedDevice.deviceName; 444 final String iface = addr.getHostAddress() + ":" + port; 445 446 mPublishedDevice = mConnectedDevice; 447 mHandler.post(new Runnable() { 448 @Override 449 public void run() { 450 mListener.onDisplayConnected(name, iface); 451 } 452 }); 453 } 454 } 455 456 private void handleStateChanged(boolean enabled) { 457 if (mWifiP2pEnabled != enabled) { 458 mWifiP2pEnabled = enabled; 459 if (enabled) { 460 if (mWfdEnabled) { 461 discoverPeers(); 462 } else { 463 enableWfd(); 464 } 465 } else { 466 mWfdEnabled = false; 467 disconnect(); 468 } 469 } 470 } 471 472 private void handlePeersChanged() { 473 if (mWifiP2pEnabled) { 474 if (mWfdEnabled) { 475 requestPeers(); 476 } else { 477 enableWfd(); 478 } 479 } 480 } 481 482 private void handleConnectionChanged(NetworkInfo networkInfo) { 483 mNetworkInfo = networkInfo; 484 if (mWifiP2pEnabled && mWfdEnabled && networkInfo.isConnected()) { 485 if (mDesiredDevice != null) { 486 mWifiP2pManager.requestGroupInfo(mWifiP2pChannel, new GroupInfoListener() { 487 @Override 488 public void onGroupInfoAvailable(WifiP2pGroup info) { 489 if (DEBUG) { 490 Slog.d(TAG, "Received group info: " + describeWifiP2pGroup(info)); 491 } 492 493 if (mConnectingDevice != null && !info.contains(mConnectingDevice)) { 494 Slog.i(TAG, "Aborting connection to Wifi display because " 495 + "the current P2P group does not contain the device " 496 + "we expected to find: " + mConnectingDevice.deviceName); 497 handleConnectionFailure(false); 498 return; 499 } 500 501 if (mDesiredDevice != null && !info.contains(mDesiredDevice)) { 502 disconnect(); 503 return; 504 } 505 506 if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) { 507 Slog.i(TAG, "Connected to Wifi display: " 508 + mConnectingDevice.deviceName); 509 510 mHandler.removeCallbacks(mConnectionTimeout); 511 mConnectedDeviceGroupInfo = info; 512 mConnectedDevice = mConnectingDevice; 513 mConnectingDevice = null; 514 updateConnection(); 515 } 516 } 517 }); 518 } 519 } else { 520 disconnect(); 521 } 522 } 523 524 private final Runnable mConnectionTimeout = new Runnable() { 525 @Override 526 public void run() { 527 if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) { 528 Slog.i(TAG, "Timed out waiting for Wifi display connection after " 529 + CONNECTION_TIMEOUT_SECONDS + " seconds: " 530 + mConnectingDevice.deviceName); 531 handleConnectionFailure(true); 532 } 533 } 534 }; 535 536 private void handleConnectionFailure(boolean timeoutOccurred) { 537 if (mDesiredDevice != null) { 538 Slog.i(TAG, "Wifi display connection failed!"); 539 540 if (mConnectionRetriesLeft > 0) { 541 mHandler.postDelayed(new Runnable() { 542 @Override 543 public void run() { 544 retryConnection(); 545 } 546 }, timeoutOccurred ? 0 : CONNECT_RETRY_DELAY_MILLIS); 547 } else { 548 disconnect(); 549 } 550 } 551 } 552 553 private static Inet4Address getInterfaceAddress(WifiP2pGroup info) { 554 NetworkInterface iface; 555 try { 556 iface = NetworkInterface.getByName(info.getInterface()); 557 } catch (SocketException ex) { 558 Slog.w(TAG, "Could not obtain address of network interface " 559 + info.getInterface(), ex); 560 return null; 561 } 562 563 Enumeration<InetAddress> addrs = iface.getInetAddresses(); 564 while (addrs.hasMoreElements()) { 565 InetAddress addr = addrs.nextElement(); 566 if (addr instanceof Inet4Address) { 567 return (Inet4Address)addr; 568 } 569 } 570 571 Slog.w(TAG, "Could not obtain address of network interface " 572 + info.getInterface() + " because it had no IPv4 addresses."); 573 return null; 574 } 575 576 private static boolean isWifiDisplay(WifiP2pDevice device) { 577 // FIXME: the wfdInfo API doesn't work yet 578 return false; 579 //return device.deviceName.equals("DWD-300-22ACC2"); 580 //return device.deviceName.startsWith("DWD-") 581 // || device.deviceName.startsWith("DIRECT-") 582 // || device.deviceName.startsWith("CAVM-"); 583 //return device.wfdInfo != null && device.wfdInfo.isWfdEnabled(); 584 } 585 586 private static String describeWifiP2pDevice(WifiP2pDevice device) { 587 return device != null ? device.toString().replace('\n', ',') : "null"; 588 } 589 590 private static String describeWifiP2pGroup(WifiP2pGroup group) { 591 return group != null ? group.toString().replace('\n', ',') : "null"; 592 } 593 594 private final BroadcastReceiver mWifiP2pReceiver = new BroadcastReceiver() { 595 @Override 596 public void onReceive(Context context, Intent intent) { 597 final String action = intent.getAction(); 598 if (action.equals(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)) { 599 boolean enabled = (intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, 600 WifiP2pManager.WIFI_P2P_STATE_DISABLED)) == 601 WifiP2pManager.WIFI_P2P_STATE_ENABLED; 602 if (DEBUG) { 603 Slog.d(TAG, "Received WIFI_P2P_STATE_CHANGED_ACTION: enabled=" 604 + enabled); 605 } 606 607 handleStateChanged(enabled); 608 } else if (action.equals(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)) { 609 if (DEBUG) { 610 Slog.d(TAG, "Received WIFI_P2P_PEERS_CHANGED_ACTION."); 611 } 612 613 handlePeersChanged(); 614 } else if (action.equals(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)) { 615 NetworkInfo networkInfo = (NetworkInfo)intent.getParcelableExtra( 616 WifiP2pManager.EXTRA_NETWORK_INFO); 617 if (DEBUG) { 618 Slog.d(TAG, "Received WIFI_P2P_CONNECTION_CHANGED_ACTION: networkInfo=" 619 + networkInfo); 620 } 621 622 handleConnectionChanged(networkInfo); 623 } 624 } 625 }; 626 627 /** 628 * Called on the handler thread when displays are connected or disconnected. 629 */ 630 public interface Listener { 631 void onDisplayConnected(String deviceName, String iface); 632 void onDisplayDisconnected(); 633 } 634} 635