summaryrefslogtreecommitdiffstats
path: root/services/java/com/android/server/display/WifiDisplayController.java
diff options
context:
space:
mode:
authorJeff Brown <jeffbrown@google.com>2012-09-07 16:00:12 -0700
committerAndroid (Google) Code Review <android-gerrit@google.com>2012-09-07 16:00:13 -0700
commit7017e48380ab0c1be033594bb2a9331898ad5be8 (patch)
tree1e24e747937706deb96ac1770f71a9c4cb6ebc65 /services/java/com/android/server/display/WifiDisplayController.java
parentcd620591b764cd999f18878985444fba01d5b710 (diff)
parentcbad976b2a36a0895ca94510d5208a86f66cf596 (diff)
downloadframeworks_base-7017e48380ab0c1be033594bb2a9331898ad5be8.zip
frameworks_base-7017e48380ab0c1be033594bb2a9331898ad5be8.tar.gz
frameworks_base-7017e48380ab0c1be033594bb2a9331898ad5be8.tar.bz2
Merge "Add support for Wifi display." into jb-mr1-dev
Diffstat (limited to 'services/java/com/android/server/display/WifiDisplayController.java')
-rw-r--r--services/java/com/android/server/display/WifiDisplayController.java554
1 files changed, 554 insertions, 0 deletions
diff --git a/services/java/com/android/server/display/WifiDisplayController.java b/services/java/com/android/server/display/WifiDisplayController.java
new file mode 100644
index 0000000..2c32e73
--- /dev/null
+++ b/services/java/com/android/server/display/WifiDisplayController.java
@@ -0,0 +1,554 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import com.android.internal.util.DumpUtils;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.NetworkInfo;
+import android.net.wifi.p2p.WifiP2pConfig;
+import android.net.wifi.p2p.WifiP2pDevice;
+import android.net.wifi.p2p.WifiP2pDeviceList;
+import android.net.wifi.p2p.WifiP2pGroup;
+import android.net.wifi.p2p.WifiP2pManager;
+import android.net.wifi.p2p.WifiP2pWfdInfo;
+import android.net.wifi.p2p.WifiP2pManager.ActionListener;
+import android.net.wifi.p2p.WifiP2pManager.Channel;
+import android.net.wifi.p2p.WifiP2pManager.GroupInfoListener;
+import android.net.wifi.p2p.WifiP2pManager.PeerListListener;
+import android.os.Handler;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+
+/**
+ * Manages all of the various asynchronous interactions with the {@link WifiP2pManager}
+ * on behalf of {@link WifiDisplayAdapter}.
+ * <p>
+ * This code is isolated from {@link WifiDisplayAdapter} so that we can avoid
+ * accidentally introducing any deadlocks due to the display manager calling
+ * outside of itself while holding its lock. It's also way easier to write this
+ * asynchronous code if we can assume that it is single-threaded.
+ * </p><p>
+ * The controller must be instantiated on the handler thread.
+ * </p>
+ */
+final class WifiDisplayController implements DumpUtils.Dump {
+ private static final String TAG = "WifiDisplayController";
+ private static final boolean DEBUG = true;
+
+ private static final int DEFAULT_CONTROL_PORT = 7236;
+ private static final int MAX_THROUGHPUT = 50;
+ private static final int CONNECTION_TIMEOUT_SECONDS = 30;
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private final Listener mListener;
+ private final WifiP2pManager mWifiP2pManager;
+ private final Channel mWifiP2pChannel;
+
+ private boolean mWifiP2pEnabled;
+ private boolean mWfdEnabled;
+ private boolean mWfdEnabling;
+ private NetworkInfo mNetworkInfo;
+
+ private final ArrayList<WifiP2pDevice> mKnownWifiDisplayPeers =
+ new ArrayList<WifiP2pDevice>();
+
+ // The device to which we want to connect, or null if we want to be disconnected.
+ private WifiP2pDevice mDesiredDevice;
+
+ // The device to which we are currently connecting, or null if we have already connected
+ // or are not trying to connect.
+ private WifiP2pDevice mConnectingDevice;
+
+ // The device to which we are currently connected, which means we have an active P2P group.
+ private WifiP2pDevice mConnectedDevice;
+
+ // The group info obtained after connecting.
+ private WifiP2pGroup mConnectedDeviceGroupInfo;
+
+ // The device that we announced to the rest of the system.
+ private WifiP2pDevice mPublishedDevice;
+
+ public WifiDisplayController(Context context, Handler handler, Listener listener) {
+ mContext = context;
+ mHandler = handler;
+ mListener = listener;
+
+ mWifiP2pManager = (WifiP2pManager)context.getSystemService(Context.WIFI_P2P_SERVICE);
+ mWifiP2pChannel = mWifiP2pManager.initialize(context, handler.getLooper(), null);
+
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
+ intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
+ intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
+ context.registerReceiver(mWifiP2pReceiver, intentFilter);
+ }
+
+ public void dump(PrintWriter pw) {
+ pw.println("mWifiP2pEnabled=" + mWifiP2pEnabled);
+ pw.println("mWfdEnabled=" + mWfdEnabled);
+ pw.println("mWfdEnabling=" + mWfdEnabling);
+ pw.println("mNetworkInfo=" + mNetworkInfo);
+ pw.println("mDesiredDevice=" + describeWifiP2pDevice(mDesiredDevice));
+ pw.println("mConnectingDisplay=" + describeWifiP2pDevice(mConnectingDevice));
+ pw.println("mConnectedDevice=" + describeWifiP2pDevice(mConnectedDevice));
+ pw.println("mPublishedDevice=" + describeWifiP2pDevice(mPublishedDevice));
+
+ pw.println("mKnownWifiDisplayPeers: size=" + mKnownWifiDisplayPeers.size());
+ for (WifiP2pDevice device : mKnownWifiDisplayPeers) {
+ pw.println(" " + describeWifiP2pDevice(device));
+ }
+ }
+
+ private void enableWfd() {
+ if (!mWfdEnabled && !mWfdEnabling) {
+ mWfdEnabling = true;
+
+ WifiP2pWfdInfo wfdInfo = new WifiP2pWfdInfo();
+ wfdInfo.setWfdEnabled(true);
+ wfdInfo.setDeviceType(WifiP2pWfdInfo.WFD_SOURCE);
+ wfdInfo.setSessionAvailable(true);
+ wfdInfo.setControlPort(DEFAULT_CONTROL_PORT);
+ wfdInfo.setMaxThroughput(MAX_THROUGHPUT);
+ mWifiP2pManager.setWFDInfo(mWifiP2pChannel, wfdInfo, new ActionListener() {
+ @Override
+ public void onSuccess() {
+ if (DEBUG) {
+ Slog.d(TAG, "Successfully set WFD info.");
+ }
+ if (mWfdEnabling) {
+ mWfdEnabled = true;
+ mWfdEnabling = false;
+ discoverPeers();
+ }
+ }
+
+ @Override
+ public void onFailure(int reason) {
+ if (DEBUG) {
+ Slog.d(TAG, "Failed to set WFD info with reason " + reason + ".");
+ }
+ mWfdEnabling = false;
+ }
+ });
+ }
+ }
+
+ private void discoverPeers() {
+ mWifiP2pManager.discoverPeers(mWifiP2pChannel, new ActionListener() {
+ @Override
+ public void onSuccess() {
+ if (DEBUG) {
+ Slog.d(TAG, "Discover peers succeeded. Requesting peers now.");
+ }
+
+ requestPeers();
+ }
+
+ @Override
+ public void onFailure(int reason) {
+ if (DEBUG) {
+ Slog.d(TAG, "Discover peers failed with reason " + reason + ".");
+ }
+ }
+ });
+ }
+
+ private void requestPeers() {
+ mWifiP2pManager.requestPeers(mWifiP2pChannel, new PeerListListener() {
+ @Override
+ public void onPeersAvailable(WifiP2pDeviceList peers) {
+ if (DEBUG) {
+ Slog.d(TAG, "Received list of peers.");
+ }
+
+ mKnownWifiDisplayPeers.clear();
+ for (WifiP2pDevice device : peers.getDeviceList()) {
+ if (DEBUG) {
+ Slog.d(TAG, " " + describeWifiP2pDevice(device));
+ }
+
+ if (isWifiDisplay(device)) {
+ mKnownWifiDisplayPeers.add(device);
+ }
+ }
+
+ // TODO: shouldn't auto-connect like this, let UI do it explicitly
+ if (!mKnownWifiDisplayPeers.isEmpty()) {
+ final WifiP2pDevice device = mKnownWifiDisplayPeers.get(0);
+
+ if (device.status == WifiP2pDevice.AVAILABLE) {
+ connect(device);
+ }
+ }
+
+ // TODO: publish this information to applications
+ }
+ });
+ }
+
+ private void connect(final WifiP2pDevice device) {
+ if (mDesiredDevice != null
+ && !mDesiredDevice.deviceAddress.equals(device.deviceAddress)) {
+ if (DEBUG) {
+ Slog.d(TAG, "connect: nothing to do, already connecting to "
+ + describeWifiP2pDevice(device));
+ }
+ return;
+ }
+
+ if (mConnectedDevice != null
+ && !mConnectedDevice.deviceAddress.equals(device.deviceAddress)
+ && mDesiredDevice == null) {
+ if (DEBUG) {
+ Slog.d(TAG, "connect: nothing to do, already connected to "
+ + describeWifiP2pDevice(device) + " and not part way through "
+ + "connecting to a different device.");
+ }
+ }
+
+ mDesiredDevice = device;
+ updateConnection();
+ }
+
+ private void disconnect() {
+ mDesiredDevice = null;
+ updateConnection();
+ }
+
+ /**
+ * This function is called repeatedly after each asynchronous operation
+ * until all preconditions for the connection have been satisfied and the
+ * connection is established (or not).
+ */
+ private void updateConnection() {
+ // Step 1. Before we try to connect to a new device, tell the system we
+ // have disconnected from the old one.
+ if (mPublishedDevice != null && mPublishedDevice != mDesiredDevice) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onDisplayDisconnected();
+ }
+ });
+ mPublishedDevice = null;
+
+ // continue to next step
+ }
+
+ // Step 2. Before we try to connect to a new device, disconnect from the old one.
+ if (mConnectedDevice != null && mConnectedDevice != mDesiredDevice) {
+ Slog.i(TAG, "Disconnecting from Wifi display: " + mConnectedDevice.deviceName);
+
+ final WifiP2pDevice oldDevice = mConnectedDevice;
+ mWifiP2pManager.removeGroup(mWifiP2pChannel, new ActionListener() {
+ @Override
+ public void onSuccess() {
+ Slog.i(TAG, "Disconnected from Wifi display: " + oldDevice.deviceName);
+ next();
+ }
+
+ @Override
+ public void onFailure(int reason) {
+ Slog.i(TAG, "Failed to disconnect from Wifi display: "
+ + oldDevice.deviceName + ", reason=" + reason);
+ next();
+ }
+
+ private void next() {
+ if (mConnectedDevice == oldDevice) {
+ mConnectedDevice = null;
+ updateConnection();
+ }
+ }
+ });
+ return; // wait for asynchronous callback
+ }
+
+ // Step 3. Before we try to connect to a new device, stop trying to connect
+ // to the old one.
+ if (mConnectingDevice != null && mConnectingDevice != mDesiredDevice) {
+ Slog.i(TAG, "Canceling connection to Wifi display: " + mConnectingDevice.deviceName);
+
+ mHandler.removeCallbacks(mConnectionTimeout);
+
+ final WifiP2pDevice oldDevice = mConnectingDevice;
+ mWifiP2pManager.cancelConnect(mWifiP2pChannel, new ActionListener() {
+ @Override
+ public void onSuccess() {
+ Slog.i(TAG, "Canceled connection to Wifi display: " + oldDevice.deviceName);
+ next();
+ }
+
+ @Override
+ public void onFailure(int reason) {
+ Slog.i(TAG, "Failed to cancel connection to Wifi display: "
+ + oldDevice.deviceName + ", reason=" + reason);
+ next();
+ }
+
+ private void next() {
+ if (mConnectingDevice == oldDevice) {
+ mConnectingDevice = null;
+ updateConnection();
+ }
+ }
+ });
+ return; // wait for asynchronous callback
+ }
+
+ // Step 4. If we wanted to disconnect, then mission accomplished.
+ if (mDesiredDevice == null) {
+ return; // done
+ }
+
+ // Step 5. Try to connect.
+ if (mConnectedDevice == null && mConnectingDevice == null) {
+ Slog.i(TAG, "Connecting to Wifi display: " + mDesiredDevice.deviceName);
+
+ mConnectingDevice = mDesiredDevice;
+ WifiP2pConfig config = new WifiP2pConfig();
+ config.deviceAddress = mConnectingDevice.deviceAddress;
+
+ final WifiP2pDevice newDevice = mDesiredDevice;
+ mWifiP2pManager.connect(mWifiP2pChannel, config, new ActionListener() {
+ @Override
+ public void onSuccess() {
+ // The connection may not yet be established. We still need to wait
+ // for WIFI_P2P_CONNECTION_CHANGED_ACTION. However, we might never
+ // get that broadcast, so we register a timeout.
+ Slog.i(TAG, "Initiated connection to Wifi display: " + newDevice.deviceName);
+
+ mHandler.postDelayed(mConnectionTimeout, CONNECTION_TIMEOUT_SECONDS * 1000);
+ }
+
+ @Override
+ public void onFailure(int reason) {
+ Slog.i(TAG, "Failed to initiate connection to Wifi display: "
+ + newDevice.deviceName + ", reason=" + reason);
+ if (mConnectingDevice == newDevice) {
+ mConnectingDevice = null;
+ handleConnectionFailure();
+ }
+ }
+ });
+ return; // wait for asynchronous callback
+ }
+
+ // Step 6. Publish the new connection.
+ if (mConnectedDevice != null && mPublishedDevice == null) {
+ Inet4Address addr = getInterfaceAddress(mConnectedDeviceGroupInfo);
+ if (addr == null) {
+ Slog.i(TAG, "Failed to get local interface address for communicating "
+ + "with Wifi display: " + mConnectedDevice.deviceName);
+ handleConnectionFailure();
+ return; // done
+ }
+
+ WifiP2pWfdInfo wfdInfo = mConnectedDevice.wfdInfo;
+ int port = (wfdInfo != null ? wfdInfo.getControlPort() : DEFAULT_CONTROL_PORT);
+ final String name = mConnectedDevice.deviceName;
+ final String iface = addr.getHostAddress() + ":" + port;
+
+ mPublishedDevice = mConnectedDevice;
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onDisplayConnected(name, iface);
+ }
+ });
+ }
+ }
+
+ private void handleStateChanged(boolean enabled) {
+ if (mWifiP2pEnabled != enabled) {
+ mWifiP2pEnabled = enabled;
+ if (enabled) {
+ if (mWfdEnabled) {
+ discoverPeers();
+ } else {
+ enableWfd();
+ }
+ } else {
+ mWfdEnabled = false;
+ disconnect();
+ }
+ }
+ }
+
+ private void handlePeersChanged() {
+ if (mWifiP2pEnabled) {
+ if (mWfdEnabled) {
+ requestPeers();
+ } else {
+ enableWfd();
+ }
+ }
+ }
+
+ private void handleConnectionChanged(NetworkInfo networkInfo) {
+ mNetworkInfo = networkInfo;
+ if (mWifiP2pEnabled && mWfdEnabled && networkInfo.isConnected()) {
+ if (mDesiredDevice != null) {
+ mWifiP2pManager.requestGroupInfo(mWifiP2pChannel, new GroupInfoListener() {
+ @Override
+ public void onGroupInfoAvailable(WifiP2pGroup info) {
+ if (DEBUG) {
+ Slog.d(TAG, "Received group info: " + describeWifiP2pGroup(info));
+ }
+
+ if (mConnectingDevice != null && !info.contains(mConnectingDevice)) {
+ Slog.i(TAG, "Aborting connection to Wifi display because "
+ + "the current P2P group does not contain the device "
+ + "we expected to find: " + mConnectingDevice.deviceName);
+ handleConnectionFailure();
+ return;
+ }
+
+ if (mDesiredDevice != null && !info.contains(mDesiredDevice)) {
+ disconnect();
+ return;
+ }
+
+ if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) {
+ Slog.i(TAG, "Connected to Wifi display: " + mConnectingDevice.deviceName);
+
+ mHandler.removeCallbacks(mConnectionTimeout);
+ mConnectedDeviceGroupInfo = info;
+ mConnectedDevice = mConnectingDevice;
+ mConnectingDevice = null;
+ updateConnection();
+ }
+ }
+ });
+ }
+ } else {
+ disconnect();
+ }
+ }
+
+ private final Runnable mConnectionTimeout = new Runnable() {
+ @Override
+ public void run() {
+ if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) {
+ Slog.i(TAG, "Timed out waiting for Wifi display connection after "
+ + CONNECTION_TIMEOUT_SECONDS + " seconds: "
+ + mConnectingDevice.deviceName);
+ handleConnectionFailure();
+ }
+ }
+ };
+
+ private void handleConnectionFailure() {
+ if (mDesiredDevice != null) {
+ Slog.i(TAG, "Wifi display connection failed!");
+ disconnect();
+ }
+ }
+
+ private static Inet4Address getInterfaceAddress(WifiP2pGroup info) {
+ NetworkInterface iface;
+ try {
+ iface = NetworkInterface.getByName(info.getInterface());
+ } catch (SocketException ex) {
+ Slog.w(TAG, "Could not obtain address of network interface "
+ + info.getInterface(), ex);
+ return null;
+ }
+
+ Enumeration<InetAddress> addrs = iface.getInetAddresses();
+ while (addrs.hasMoreElements()) {
+ InetAddress addr = addrs.nextElement();
+ if (addr instanceof Inet4Address) {
+ return (Inet4Address)addr;
+ }
+ }
+
+ Slog.w(TAG, "Could not obtain address of network interface "
+ + info.getInterface() + " because it had no IPv4 addresses.");
+ return null;
+ }
+
+ private static boolean isWifiDisplay(WifiP2pDevice device) {
+ // FIXME: the wfdInfo API doesn't work yet
+ return device.deviceName.equals("DWD-300-22ACC2");
+ //return device.deviceName.startsWith("DWD-")
+ // || device.deviceName.startsWith("DIRECT-")
+ // || device.deviceName.startsWith("CAVM-");
+ //return device.wfdInfo != null && device.wfdInfo.isWfdEnabled();
+ }
+
+ private static String describeWifiP2pDevice(WifiP2pDevice device) {
+ return device != null ? device.toString().replace('\n', ',') : "null";
+ }
+
+ private static String describeWifiP2pGroup(WifiP2pGroup group) {
+ return group != null ? group.toString().replace('\n', ',') : "null";
+ }
+
+ private final BroadcastReceiver mWifiP2pReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (action.equals(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)) {
+ boolean enabled = (intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE,
+ WifiP2pManager.WIFI_P2P_STATE_DISABLED)) ==
+ WifiP2pManager.WIFI_P2P_STATE_ENABLED;
+ if (DEBUG) {
+ Slog.d(TAG, "Received WIFI_P2P_STATE_CHANGED_ACTION: enabled="
+ + enabled);
+ }
+
+ handleStateChanged(enabled);
+ } else if (action.equals(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Received WIFI_P2P_PEERS_CHANGED_ACTION.");
+ }
+
+ handlePeersChanged();
+ } else if (action.equals(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)) {
+ NetworkInfo networkInfo = (NetworkInfo)intent.getParcelableExtra(
+ WifiP2pManager.EXTRA_NETWORK_INFO);
+ if (DEBUG) {
+ Slog.d(TAG, "Received WIFI_P2P_CONNECTION_CHANGED_ACTION: networkInfo="
+ + networkInfo);
+ }
+
+ handleConnectionChanged(networkInfo);
+ }
+ }
+ };
+
+ /**
+ * Called on the handler thread when displays are connected or disconnected.
+ */
+ public interface Listener {
+ void onDisplayConnected(String deviceName, String iface);
+ void onDisplayDisconnected();
+ }
+}