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