summaryrefslogtreecommitdiffstats
path: root/services/core/java/com/android/server/display/WifiDisplayController.java
diff options
context:
space:
mode:
Diffstat (limited to 'services/core/java/com/android/server/display/WifiDisplayController.java')
-rw-r--r--services/core/java/com/android/server/display/WifiDisplayController.java1113
1 files changed, 1113 insertions, 0 deletions
diff --git a/services/core/java/com/android/server/display/WifiDisplayController.java b/services/core/java/com/android/server/display/WifiDisplayController.java
new file mode 100644
index 0000000..dbb59b2
--- /dev/null
+++ b/services/core/java/com/android/server/display/WifiDisplayController.java
@@ -0,0 +1,1113 @@
+/*
+ * 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.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.hardware.display.WifiDisplay;
+import android.hardware.display.WifiDisplaySessionInfo;
+import android.hardware.display.WifiDisplayStatus;
+import android.media.RemoteDisplay;
+import android.net.NetworkInfo;
+import android.net.Uri;
+import android.net.wifi.WpsInfo;
+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.provider.Settings;
+import android.util.Slog;
+import android.view.Surface;
+
+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;
+
+import libcore.util.Objects;
+
+/**
+ * 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 = false;
+
+ private static final int DEFAULT_CONTROL_PORT = 7236;
+ private static final int MAX_THROUGHPUT = 50;
+ private static final int CONNECTION_TIMEOUT_SECONDS = 30;
+ private static final int RTSP_TIMEOUT_SECONDS = 30;
+ private static final int RTSP_TIMEOUT_SECONDS_CERT_MODE = 120;
+
+ // We repeatedly issue calls to discover peers every so often for a few reasons.
+ // 1. The initial request may fail and need to retried.
+ // 2. Discovery will self-abort after any group is initiated, which may not necessarily
+ // be what we want to have happen.
+ // 3. Discovery will self-timeout after 2 minutes, whereas we want discovery to
+ // be occur for as long as a client is requesting it be.
+ // 4. We don't seem to get updated results for displays we've already found until
+ // we ask to discover again, particularly for the isSessionAvailable() property.
+ private static final int DISCOVER_PEERS_INTERVAL_MILLIS = 10000;
+
+ private static final int CONNECT_MAX_RETRIES = 3;
+ private static final int CONNECT_RETRY_DELAY_MILLIS = 500;
+
+ 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> mAvailableWifiDisplayPeers =
+ new ArrayList<WifiP2pDevice>();
+
+ // True if Wifi display is enabled by the user.
+ private boolean mWifiDisplayOnSetting;
+
+ // True if a scan was requested independent of whether one is actually in progress.
+ private boolean mScanRequested;
+
+ // True if there is a call to discoverPeers in progress.
+ private boolean mDiscoverPeersInProgress;
+
+ // 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 from which we are currently disconnecting.
+ private WifiP2pDevice mDisconnectingDevice;
+
+ // The device to which we were previously trying to connect and are now canceling.
+ private WifiP2pDevice mCancelingDevice;
+
+ // 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;
+
+ // Number of connection retries remaining.
+ private int mConnectionRetriesLeft;
+
+ // The remote display that is listening on the connection.
+ // Created after the Wifi P2P network is connected.
+ private RemoteDisplay mRemoteDisplay;
+
+ // The remote display interface.
+ private String mRemoteDisplayInterface;
+
+ // True if RTSP has connected.
+ private boolean mRemoteDisplayConnected;
+
+ // The information we have most recently told WifiDisplayAdapter about.
+ private WifiDisplay mAdvertisedDisplay;
+ private Surface mAdvertisedDisplaySurface;
+ private int mAdvertisedDisplayWidth;
+ private int mAdvertisedDisplayHeight;
+ private int mAdvertisedDisplayFlags;
+
+ // Certification
+ private boolean mWifiDisplayCertMode;
+ private int mWifiDisplayWpsConfig = WpsInfo.INVALID;
+
+ private WifiP2pDevice mThisDevice;
+
+ 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);
+ intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
+ context.registerReceiver(mWifiP2pReceiver, intentFilter, null, mHandler);
+
+ ContentObserver settingsObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ updateSettings();
+ }
+ };
+
+ final ContentResolver resolver = mContext.getContentResolver();
+ resolver.registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.WIFI_DISPLAY_ON), false, settingsObserver);
+ resolver.registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON), false, settingsObserver);
+ resolver.registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.WIFI_DISPLAY_WPS_CONFIG), false, settingsObserver);
+ updateSettings();
+ }
+
+ private void updateSettings() {
+ final ContentResolver resolver = mContext.getContentResolver();
+ mWifiDisplayOnSetting = Settings.Global.getInt(resolver,
+ Settings.Global.WIFI_DISPLAY_ON, 0) != 0;
+ mWifiDisplayCertMode = Settings.Global.getInt(resolver,
+ Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON, 0) != 0;
+
+ mWifiDisplayWpsConfig = WpsInfo.INVALID;
+ if (mWifiDisplayCertMode) {
+ mWifiDisplayWpsConfig = Settings.Global.getInt(resolver,
+ Settings.Global.WIFI_DISPLAY_WPS_CONFIG, WpsInfo.INVALID);
+ }
+
+ updateWfdEnableState();
+ }
+
+ @Override
+ public void dump(PrintWriter pw) {
+ pw.println("mWifiDisplayOnSetting=" + mWifiDisplayOnSetting);
+ pw.println("mWifiP2pEnabled=" + mWifiP2pEnabled);
+ pw.println("mWfdEnabled=" + mWfdEnabled);
+ pw.println("mWfdEnabling=" + mWfdEnabling);
+ pw.println("mNetworkInfo=" + mNetworkInfo);
+ pw.println("mScanRequested=" + mScanRequested);
+ pw.println("mDiscoverPeersInProgress=" + mDiscoverPeersInProgress);
+ pw.println("mDesiredDevice=" + describeWifiP2pDevice(mDesiredDevice));
+ pw.println("mConnectingDisplay=" + describeWifiP2pDevice(mConnectingDevice));
+ pw.println("mDisconnectingDisplay=" + describeWifiP2pDevice(mDisconnectingDevice));
+ pw.println("mCancelingDisplay=" + describeWifiP2pDevice(mCancelingDevice));
+ pw.println("mConnectedDevice=" + describeWifiP2pDevice(mConnectedDevice));
+ pw.println("mConnectionRetriesLeft=" + mConnectionRetriesLeft);
+ pw.println("mRemoteDisplay=" + mRemoteDisplay);
+ pw.println("mRemoteDisplayInterface=" + mRemoteDisplayInterface);
+ pw.println("mRemoteDisplayConnected=" + mRemoteDisplayConnected);
+ pw.println("mAdvertisedDisplay=" + mAdvertisedDisplay);
+ pw.println("mAdvertisedDisplaySurface=" + mAdvertisedDisplaySurface);
+ pw.println("mAdvertisedDisplayWidth=" + mAdvertisedDisplayWidth);
+ pw.println("mAdvertisedDisplayHeight=" + mAdvertisedDisplayHeight);
+ pw.println("mAdvertisedDisplayFlags=" + mAdvertisedDisplayFlags);
+
+ pw.println("mAvailableWifiDisplayPeers: size=" + mAvailableWifiDisplayPeers.size());
+ for (WifiP2pDevice device : mAvailableWifiDisplayPeers) {
+ pw.println(" " + describeWifiP2pDevice(device));
+ }
+ }
+
+ public void requestStartScan() {
+ if (!mScanRequested) {
+ mScanRequested = true;
+ updateScanState();
+ }
+ }
+
+ public void requestStopScan() {
+ if (mScanRequested) {
+ mScanRequested = false;
+ updateScanState();
+ }
+ }
+
+ public void requestConnect(String address) {
+ for (WifiP2pDevice device : mAvailableWifiDisplayPeers) {
+ if (device.deviceAddress.equals(address)) {
+ connect(device);
+ }
+ }
+ }
+
+ public void requestPause() {
+ if (mRemoteDisplay != null) {
+ mRemoteDisplay.pause();
+ }
+ }
+
+ public void requestResume() {
+ if (mRemoteDisplay != null) {
+ mRemoteDisplay.resume();
+ }
+ }
+
+ public void requestDisconnect() {
+ disconnect();
+ }
+
+ private void updateWfdEnableState() {
+ if (mWifiDisplayOnSetting && mWifiP2pEnabled) {
+ // WFD should be enabled.
+ 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) {
+ mWfdEnabling = false;
+ mWfdEnabled = true;
+ reportFeatureState();
+ updateScanState();
+ }
+ }
+
+ @Override
+ public void onFailure(int reason) {
+ if (DEBUG) {
+ Slog.d(TAG, "Failed to set WFD info with reason " + reason + ".");
+ }
+ mWfdEnabling = false;
+ }
+ });
+ }
+ } else {
+ // WFD should be disabled.
+ if (mWfdEnabled || mWfdEnabling) {
+ WifiP2pWfdInfo wfdInfo = new WifiP2pWfdInfo();
+ wfdInfo.setWfdEnabled(false);
+ mWifiP2pManager.setWFDInfo(mWifiP2pChannel, wfdInfo, new ActionListener() {
+ @Override
+ public void onSuccess() {
+ if (DEBUG) {
+ Slog.d(TAG, "Successfully set WFD info.");
+ }
+ }
+
+ @Override
+ public void onFailure(int reason) {
+ if (DEBUG) {
+ Slog.d(TAG, "Failed to set WFD info with reason " + reason + ".");
+ }
+ }
+ });
+ }
+ mWfdEnabling = false;
+ mWfdEnabled = false;
+ reportFeatureState();
+ updateScanState();
+ disconnect();
+ }
+ }
+
+ private void reportFeatureState() {
+ final int featureState = computeFeatureState();
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onFeatureStateChanged(featureState);
+ }
+ });
+ }
+
+ private int computeFeatureState() {
+ if (!mWifiP2pEnabled) {
+ return WifiDisplayStatus.FEATURE_STATE_DISABLED;
+ }
+ return mWifiDisplayOnSetting ? WifiDisplayStatus.FEATURE_STATE_ON :
+ WifiDisplayStatus.FEATURE_STATE_OFF;
+ }
+
+ private void updateScanState() {
+ if (mScanRequested && mWfdEnabled && mDesiredDevice == null) {
+ if (!mDiscoverPeersInProgress) {
+ Slog.i(TAG, "Starting Wifi display scan.");
+ mDiscoverPeersInProgress = true;
+ handleScanStarted();
+ tryDiscoverPeers();
+ }
+ } else {
+ if (mDiscoverPeersInProgress) {
+ // Cancel automatic retry right away.
+ mHandler.removeCallbacks(mDiscoverPeers);
+
+ // Defer actually stopping discovery if we have a connection attempt in progress.
+ // The wifi display connection attempt often fails if we are not in discovery
+ // mode. So we allow discovery to continue until we give up trying to connect.
+ if (mDesiredDevice == null || mDesiredDevice == mConnectedDevice) {
+ Slog.i(TAG, "Stopping Wifi display scan.");
+ mDiscoverPeersInProgress = false;
+ stopPeerDiscovery();
+ handleScanFinished();
+ }
+ }
+ }
+ }
+
+ private void tryDiscoverPeers() {
+ mWifiP2pManager.discoverPeers(mWifiP2pChannel, new ActionListener() {
+ @Override
+ public void onSuccess() {
+ if (DEBUG) {
+ Slog.d(TAG, "Discover peers succeeded. Requesting peers now.");
+ }
+
+ if (mDiscoverPeersInProgress) {
+ requestPeers();
+ }
+ }
+
+ @Override
+ public void onFailure(int reason) {
+ if (DEBUG) {
+ Slog.d(TAG, "Discover peers failed with reason " + reason + ".");
+ }
+
+ // Ignore the error.
+ // We will retry automatically in a little bit.
+ }
+ });
+
+ // Retry discover peers periodically until stopped.
+ mHandler.postDelayed(mDiscoverPeers, DISCOVER_PEERS_INTERVAL_MILLIS);
+ }
+
+ private void stopPeerDiscovery() {
+ mWifiP2pManager.stopPeerDiscovery(mWifiP2pChannel, new ActionListener() {
+ @Override
+ public void onSuccess() {
+ if (DEBUG) {
+ Slog.d(TAG, "Stop peer discovery succeeded.");
+ }
+ }
+
+ @Override
+ public void onFailure(int reason) {
+ if (DEBUG) {
+ Slog.d(TAG, "Stop peer discovery 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.");
+ }
+
+ mAvailableWifiDisplayPeers.clear();
+ for (WifiP2pDevice device : peers.getDeviceList()) {
+ if (DEBUG) {
+ Slog.d(TAG, " " + describeWifiP2pDevice(device));
+ }
+
+ if (isWifiDisplay(device)) {
+ mAvailableWifiDisplayPeers.add(device);
+ }
+ }
+
+ if (mDiscoverPeersInProgress) {
+ handleScanResults();
+ }
+ }
+ });
+ }
+
+ private void handleScanStarted() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onScanStarted();
+ }
+ });
+ }
+
+ private void handleScanResults() {
+ final int count = mAvailableWifiDisplayPeers.size();
+ final WifiDisplay[] displays = WifiDisplay.CREATOR.newArray(count);
+ for (int i = 0; i < count; i++) {
+ WifiP2pDevice device = mAvailableWifiDisplayPeers.get(i);
+ displays[i] = createWifiDisplay(device);
+ updateDesiredDevice(device);
+ }
+
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onScanResults(displays);
+ }
+ });
+ }
+
+ private void handleScanFinished() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onScanFinished();
+ }
+ });
+ }
+
+ private void updateDesiredDevice(WifiP2pDevice device) {
+ // Handle the case where the device to which we are connecting or connected
+ // may have been renamed or reported different properties in the latest scan.
+ final String address = device.deviceAddress;
+ if (mDesiredDevice != null && mDesiredDevice.deviceAddress.equals(address)) {
+ if (DEBUG) {
+ Slog.d(TAG, "updateDesiredDevice: new information "
+ + describeWifiP2pDevice(device));
+ }
+ mDesiredDevice.update(device);
+ if (mAdvertisedDisplay != null
+ && mAdvertisedDisplay.getDeviceAddress().equals(address)) {
+ readvertiseDisplay(createWifiDisplay(mDesiredDevice));
+ }
+ }
+ }
+
+ 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.");
+ }
+ return;
+ }
+
+ if (!mWfdEnabled) {
+ Slog.i(TAG, "Ignoring request to connect to Wifi display because the "
+ +" feature is currently disabled: " + device.deviceName);
+ return;
+ }
+
+ mDesiredDevice = device;
+ mConnectionRetriesLeft = CONNECT_MAX_RETRIES;
+ updateConnection();
+ }
+
+ private void disconnect() {
+ mDesiredDevice = null;
+ updateConnection();
+ }
+
+ private void retryConnection() {
+ // Cheap hack. Make a new instance of the device object so that we
+ // can distinguish it from the previous connection attempt.
+ // This will cause us to tear everything down before we try again.
+ mDesiredDevice = new WifiP2pDevice(mDesiredDevice);
+ 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 0. Stop scans if necessary to prevent interference while connected.
+ // Resume scans later when no longer attempting to connect.
+ updateScanState();
+
+ // Step 1. Before we try to connect to a new device, tell the system we
+ // have disconnected from the old one.
+ if (mRemoteDisplay != null && mConnectedDevice != mDesiredDevice) {
+ Slog.i(TAG, "Stopped listening for RTSP connection on " + mRemoteDisplayInterface
+ + " from Wifi display: " + mConnectedDevice.deviceName);
+
+ mRemoteDisplay.dispose();
+ mRemoteDisplay = null;
+ mRemoteDisplayInterface = null;
+ mRemoteDisplayConnected = false;
+ mHandler.removeCallbacks(mRtspTimeout);
+
+ mWifiP2pManager.setMiracastMode(WifiP2pManager.MIRACAST_DISABLED);
+ unadvertiseDisplay();
+
+ // continue to next step
+ }
+
+ // Step 2. Before we try to connect to a new device, disconnect from the old one.
+ if (mDisconnectingDevice != null) {
+ return; // wait for asynchronous callback
+ }
+ if (mConnectedDevice != null && mConnectedDevice != mDesiredDevice) {
+ Slog.i(TAG, "Disconnecting from Wifi display: " + mConnectedDevice.deviceName);
+ mDisconnectingDevice = mConnectedDevice;
+ mConnectedDevice = null;
+ mConnectedDeviceGroupInfo = null;
+
+ unadvertiseDisplay();
+
+ final WifiP2pDevice oldDevice = mDisconnectingDevice;
+ 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 (mDisconnectingDevice == oldDevice) {
+ mDisconnectingDevice = 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 (mCancelingDevice != null) {
+ return; // wait for asynchronous callback
+ }
+ if (mConnectingDevice != null && mConnectingDevice != mDesiredDevice) {
+ Slog.i(TAG, "Canceling connection to Wifi display: " + mConnectingDevice.deviceName);
+ mCancelingDevice = mConnectingDevice;
+ mConnectingDevice = null;
+
+ unadvertiseDisplay();
+ mHandler.removeCallbacks(mConnectionTimeout);
+
+ final WifiP2pDevice oldDevice = mCancelingDevice;
+ 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 (mCancelingDevice == oldDevice) {
+ mCancelingDevice = null;
+ updateConnection();
+ }
+ }
+ });
+ return; // wait for asynchronous callback
+ }
+
+ // Step 4. If we wanted to disconnect, or we're updating after starting an
+ // autonomous GO, then mission accomplished.
+ if (mDesiredDevice == null) {
+ if (mWifiDisplayCertMode) {
+ mListener.onDisplaySessionInfo(getSessionInfo(mConnectedDeviceGroupInfo, 0));
+ }
+ unadvertiseDisplay();
+ 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();
+ WpsInfo wps = new WpsInfo();
+ if (mWifiDisplayWpsConfig != WpsInfo.INVALID) {
+ wps.setup = mWifiDisplayWpsConfig;
+ } else if (mConnectingDevice.wpsPbcSupported()) {
+ wps.setup = WpsInfo.PBC;
+ } else if (mConnectingDevice.wpsDisplaySupported()) {
+ // We do keypad if peer does display
+ wps.setup = WpsInfo.KEYPAD;
+ } else {
+ wps.setup = WpsInfo.DISPLAY;
+ }
+ config.wps = wps;
+ config.deviceAddress = mConnectingDevice.deviceAddress;
+ // Helps with STA & P2P concurrency
+ config.groupOwnerIntent = WifiP2pConfig.MIN_GROUP_OWNER_INTENT;
+
+ WifiDisplay display = createWifiDisplay(mConnectingDevice);
+ advertiseDisplay(display, null, 0, 0, 0);
+
+ 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) {
+ if (mConnectingDevice == newDevice) {
+ Slog.i(TAG, "Failed to initiate connection to Wifi display: "
+ + newDevice.deviceName + ", reason=" + reason);
+ mConnectingDevice = null;
+ handleConnectionFailure(false);
+ }
+ }
+ });
+ return; // wait for asynchronous callback
+ }
+
+ // Step 6. Listen for incoming RTSP connection.
+ if (mConnectedDevice != null && mRemoteDisplay == 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(false);
+ return; // done
+ }
+
+ mWifiP2pManager.setMiracastMode(WifiP2pManager.MIRACAST_SOURCE);
+
+ final WifiP2pDevice oldDevice = mConnectedDevice;
+ final int port = getPortNumber(mConnectedDevice);
+ final String iface = addr.getHostAddress() + ":" + port;
+ mRemoteDisplayInterface = iface;
+
+ Slog.i(TAG, "Listening for RTSP connection on " + iface
+ + " from Wifi display: " + mConnectedDevice.deviceName);
+
+ mRemoteDisplay = RemoteDisplay.listen(iface, new RemoteDisplay.Listener() {
+ @Override
+ public void onDisplayConnected(Surface surface,
+ int width, int height, int flags, int session) {
+ if (mConnectedDevice == oldDevice && !mRemoteDisplayConnected) {
+ Slog.i(TAG, "Opened RTSP connection with Wifi display: "
+ + mConnectedDevice.deviceName);
+ mRemoteDisplayConnected = true;
+ mHandler.removeCallbacks(mRtspTimeout);
+
+ if (mWifiDisplayCertMode) {
+ mListener.onDisplaySessionInfo(
+ getSessionInfo(mConnectedDeviceGroupInfo, session));
+ }
+
+ final WifiDisplay display = createWifiDisplay(mConnectedDevice);
+ advertiseDisplay(display, surface, width, height, flags);
+ }
+ }
+
+ @Override
+ public void onDisplayDisconnected() {
+ if (mConnectedDevice == oldDevice) {
+ Slog.i(TAG, "Closed RTSP connection with Wifi display: "
+ + mConnectedDevice.deviceName);
+ mHandler.removeCallbacks(mRtspTimeout);
+ disconnect();
+ }
+ }
+
+ @Override
+ public void onDisplayError(int error) {
+ if (mConnectedDevice == oldDevice) {
+ Slog.i(TAG, "Lost RTSP connection with Wifi display due to error "
+ + error + ": " + mConnectedDevice.deviceName);
+ mHandler.removeCallbacks(mRtspTimeout);
+ handleConnectionFailure(false);
+ }
+ }
+ }, mHandler);
+
+ // Use extended timeout value for certification, as some tests require user inputs
+ int rtspTimeout = mWifiDisplayCertMode ?
+ RTSP_TIMEOUT_SECONDS_CERT_MODE : RTSP_TIMEOUT_SECONDS;
+
+ mHandler.postDelayed(mRtspTimeout, rtspTimeout * 1000);
+ }
+ }
+
+ private WifiDisplaySessionInfo getSessionInfo(WifiP2pGroup info, int session) {
+ if (info == null) {
+ return null;
+ }
+ Inet4Address addr = getInterfaceAddress(info);
+ WifiDisplaySessionInfo sessionInfo = new WifiDisplaySessionInfo(
+ !info.getOwner().deviceAddress.equals(mThisDevice.deviceAddress),
+ session,
+ info.getOwner().deviceAddress + " " + info.getNetworkName(),
+ info.getPassphrase(),
+ (addr != null) ? addr.getHostAddress() : "");
+ if (DEBUG) {
+ Slog.d(TAG, sessionInfo.toString());
+ }
+ return sessionInfo;
+ }
+
+ private void handleStateChanged(boolean enabled) {
+ mWifiP2pEnabled = enabled;
+ updateWfdEnableState();
+ }
+
+ private void handlePeersChanged() {
+ // Even if wfd is disabled, it is best to get the latest set of peers to
+ // keep in sync with the p2p framework
+ requestPeers();
+ }
+
+ private void handleConnectionChanged(NetworkInfo networkInfo) {
+ mNetworkInfo = networkInfo;
+ if (mWfdEnabled && networkInfo.isConnected()) {
+ if (mDesiredDevice != null || mWifiDisplayCertMode) {
+ 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
+ + ", group info was: " + describeWifiP2pGroup(info));
+ handleConnectionFailure(false);
+ return;
+ }
+
+ if (mDesiredDevice != null && !info.contains(mDesiredDevice)) {
+ disconnect();
+ return;
+ }
+
+ if (mWifiDisplayCertMode) {
+ boolean owner = info.getOwner().deviceAddress
+ .equals(mThisDevice.deviceAddress);
+ if (owner && info.getClientList().isEmpty()) {
+ // this is the case when we started Autonomous GO,
+ // and no client has connected, save group info
+ // and updateConnection()
+ mConnectingDevice = mDesiredDevice = null;
+ mConnectedDeviceGroupInfo = info;
+ updateConnection();
+ } else if (mConnectingDevice == null && mDesiredDevice == null) {
+ // this is the case when we received an incoming connection
+ // from the sink, update both mConnectingDevice and mDesiredDevice
+ // then proceed to updateConnection() below
+ mConnectingDevice = mDesiredDevice = owner ?
+ info.getClientList().iterator().next() : info.getOwner();
+ }
+ }
+
+ 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 {
+ mConnectedDeviceGroupInfo = null;
+
+ // Disconnect if we lost the network while connecting or connected to a display.
+ if (mConnectingDevice != null || mConnectedDevice != null) {
+ disconnect();
+ }
+
+ // After disconnection for a group, for some reason we have a tendency
+ // to get a peer change notification with an empty list of peers.
+ // Perform a fresh scan.
+ if (mWfdEnabled) {
+ requestPeers();
+ }
+ }
+ }
+
+ private final Runnable mDiscoverPeers = new Runnable() {
+ @Override
+ public void run() {
+ tryDiscoverPeers();
+ }
+ };
+
+ 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(true);
+ }
+ }
+ };
+
+ private final Runnable mRtspTimeout = new Runnable() {
+ @Override
+ public void run() {
+ if (mConnectedDevice != null
+ && mRemoteDisplay != null && !mRemoteDisplayConnected) {
+ Slog.i(TAG, "Timed out waiting for Wifi display RTSP connection after "
+ + RTSP_TIMEOUT_SECONDS + " seconds: "
+ + mConnectedDevice.deviceName);
+ handleConnectionFailure(true);
+ }
+ }
+ };
+
+ private void handleConnectionFailure(boolean timeoutOccurred) {
+ Slog.i(TAG, "Wifi display connection failed!");
+
+ if (mDesiredDevice != null) {
+ if (mConnectionRetriesLeft > 0) {
+ final WifiP2pDevice oldDevice = mDesiredDevice;
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ if (mDesiredDevice == oldDevice && mConnectionRetriesLeft > 0) {
+ mConnectionRetriesLeft -= 1;
+ Slog.i(TAG, "Retrying Wifi display connection. Retries left: "
+ + mConnectionRetriesLeft);
+ retryConnection();
+ }
+ }
+ }, timeoutOccurred ? 0 : CONNECT_RETRY_DELAY_MILLIS);
+ } else {
+ disconnect();
+ }
+ }
+ }
+
+ private void advertiseDisplay(final WifiDisplay display,
+ final Surface surface, final int width, final int height, final int flags) {
+ if (!Objects.equal(mAdvertisedDisplay, display)
+ || mAdvertisedDisplaySurface != surface
+ || mAdvertisedDisplayWidth != width
+ || mAdvertisedDisplayHeight != height
+ || mAdvertisedDisplayFlags != flags) {
+ final WifiDisplay oldDisplay = mAdvertisedDisplay;
+ final Surface oldSurface = mAdvertisedDisplaySurface;
+
+ mAdvertisedDisplay = display;
+ mAdvertisedDisplaySurface = surface;
+ mAdvertisedDisplayWidth = width;
+ mAdvertisedDisplayHeight = height;
+ mAdvertisedDisplayFlags = flags;
+
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (oldSurface != null && surface != oldSurface) {
+ mListener.onDisplayDisconnected();
+ } else if (oldDisplay != null && !oldDisplay.hasSameAddress(display)) {
+ mListener.onDisplayConnectionFailed();
+ }
+
+ if (display != null) {
+ if (!display.hasSameAddress(oldDisplay)) {
+ mListener.onDisplayConnecting(display);
+ } else if (!display.equals(oldDisplay)) {
+ // The address is the same but some other property such as the
+ // name must have changed.
+ mListener.onDisplayChanged(display);
+ }
+ if (surface != null && surface != oldSurface) {
+ mListener.onDisplayConnected(display, surface, width, height, flags);
+ }
+ }
+ }
+ });
+ }
+ }
+
+ private void unadvertiseDisplay() {
+ advertiseDisplay(null, null, 0, 0, 0);
+ }
+
+ private void readvertiseDisplay(WifiDisplay display) {
+ advertiseDisplay(display, mAdvertisedDisplaySurface,
+ mAdvertisedDisplayWidth, mAdvertisedDisplayHeight,
+ mAdvertisedDisplayFlags);
+ }
+
+ 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 int getPortNumber(WifiP2pDevice device) {
+ if (device.deviceName.startsWith("DIRECT-")
+ && device.deviceName.endsWith("Broadcom")) {
+ // These dongles ignore the port we broadcast in our WFD IE.
+ return 8554;
+ }
+ return DEFAULT_CONTROL_PORT;
+ }
+
+ private static boolean isWifiDisplay(WifiP2pDevice device) {
+ return device.wfdInfo != null
+ && device.wfdInfo.isWfdEnabled()
+ && isPrimarySinkDeviceType(device.wfdInfo.getDeviceType());
+ }
+
+ private static boolean isPrimarySinkDeviceType(int deviceType) {
+ return deviceType == WifiP2pWfdInfo.PRIMARY_SINK
+ || deviceType == WifiP2pWfdInfo.SOURCE_OR_PRIMARY_SINK;
+ }
+
+ 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 static WifiDisplay createWifiDisplay(WifiP2pDevice device) {
+ return new WifiDisplay(device.deviceAddress, device.deviceName, null,
+ true, device.wfdInfo.isSessionAvailable(), false);
+ }
+
+ 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)) {
+ // This broadcast is sticky so we'll always get the initial Wifi P2P state
+ // on startup.
+ 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);
+ } else if (action.equals(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION)) {
+ mThisDevice = (WifiP2pDevice) intent.getParcelableExtra(
+ WifiP2pManager.EXTRA_WIFI_P2P_DEVICE);
+ if (DEBUG) {
+ Slog.d(TAG, "Received WIFI_P2P_THIS_DEVICE_CHANGED_ACTION: mThisDevice= "
+ + mThisDevice);
+ }
+ }
+ }
+ };
+
+ /**
+ * Called on the handler thread when displays are connected or disconnected.
+ */
+ public interface Listener {
+ void onFeatureStateChanged(int featureState);
+
+ void onScanStarted();
+ void onScanResults(WifiDisplay[] availableDisplays);
+ void onScanFinished();
+
+ void onDisplayConnecting(WifiDisplay display);
+ void onDisplayConnectionFailed();
+ void onDisplayChanged(WifiDisplay display);
+ void onDisplayConnected(WifiDisplay display,
+ Surface surface, int width, int height, int flags);
+ void onDisplaySessionInfo(WifiDisplaySessionInfo sessionInfo);
+ void onDisplayDisconnected();
+ }
+}