| /* |
| * 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.WifiDisplayStatus; |
| import android.media.AudioManager; |
| import android.media.RemoteDisplay; |
| import android.net.NetworkInfo; |
| import android.net.Uri; |
| 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 = 60; |
| private static final int RTSP_TIMEOUT_SECONDS = 15; |
| |
| private static final int DISCOVER_PEERS_MAX_RETRIES = 10; |
| private static final int DISCOVER_PEERS_RETRY_DELAY_MILLIS = 500; |
| |
| private static final int CONNECT_MAX_RETRIES = 3; |
| private static final int CONNECT_RETRY_DELAY_MILLIS = 500; |
| |
| // A unique token to identify the remote submix that is managed by Wifi display. |
| // It must match what the media server uses when it starts recording the submix |
| // for transmission. We use 0 although the actual value is currently ignored. |
| private static final int REMOTE_SUBMIX_ADDRESS = 0; |
| |
| private final Context mContext; |
| private final Handler mHandler; |
| private final Listener mListener; |
| |
| private final WifiP2pManager mWifiP2pManager; |
| private final Channel mWifiP2pChannel; |
| |
| private final AudioManager mAudioManager; |
| |
| 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 there is a call to discoverPeers in progress. |
| private boolean mDiscoverPeersInProgress; |
| |
| // Number of discover peers retries remaining. |
| private int mDiscoverPeersRetriesLeft; |
| |
| // 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; |
| |
| // 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; |
| |
| // True if the remote submix is enabled. |
| private boolean mRemoteSubmixOn; |
| |
| // The information we have most recently told WifiDisplayAdapter about. |
| private WifiDisplay mAdvertisedDisplay; |
| private Surface mAdvertisedDisplaySurface; |
| private int mAdvertisedDisplayWidth; |
| private int mAdvertisedDisplayHeight; |
| private int mAdvertisedDisplayFlags; |
| |
| 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); |
| |
| mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE); |
| |
| 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, 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); |
| updateSettings(); |
| } |
| |
| private void updateSettings() { |
| final ContentResolver resolver = mContext.getContentResolver(); |
| mWifiDisplayOnSetting = Settings.Global.getInt(resolver, |
| Settings.Global.WIFI_DISPLAY_ON, 0) != 0; |
| |
| updateWfdEnableState(); |
| } |
| |
| 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("mDiscoverPeersInProgress=" + mDiscoverPeersInProgress); |
| pw.println("mDiscoverPeersRetriesLeft=" + mDiscoverPeersRetriesLeft); |
| pw.println("mDesiredDevice=" + describeWifiP2pDevice(mDesiredDevice)); |
| pw.println("mConnectingDisplay=" + describeWifiP2pDevice(mConnectingDevice)); |
| pw.println("mConnectedDevice=" + describeWifiP2pDevice(mConnectedDevice)); |
| pw.println("mConnectionRetriesLeft=" + mConnectionRetriesLeft); |
| pw.println("mRemoteDisplay=" + mRemoteDisplay); |
| pw.println("mRemoteDisplayInterface=" + mRemoteDisplayInterface); |
| pw.println("mRemoteDisplayConnected=" + mRemoteDisplayConnected); |
| pw.println("mRemoteSubmixOn=" + mRemoteSubmixOn); |
| 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 requestScan() { |
| discoverPeers(); |
| } |
| |
| public void requestConnect(String address) { |
| for (WifiP2pDevice device : mAvailableWifiDisplayPeers) { |
| if (device.deviceAddress.equals(address)) { |
| connect(device); |
| } |
| } |
| } |
| |
| 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(); |
| } |
| } |
| |
| @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. |
| mWfdEnabling = false; |
| mWfdEnabled = false; |
| reportFeatureState(); |
| 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 discoverPeers() { |
| if (!mDiscoverPeersInProgress) { |
| mDiscoverPeersInProgress = true; |
| mDiscoverPeersRetriesLeft = DISCOVER_PEERS_MAX_RETRIES; |
| handleScanStarted(); |
| tryDiscoverPeers(); |
| } |
| } |
| |
| private void tryDiscoverPeers() { |
| mWifiP2pManager.discoverPeers(mWifiP2pChannel, new ActionListener() { |
| @Override |
| public void onSuccess() { |
| if (DEBUG) { |
| Slog.d(TAG, "Discover peers succeeded. Requesting peers now."); |
| } |
| |
| mDiscoverPeersInProgress = false; |
| requestPeers(); |
| } |
| |
| @Override |
| public void onFailure(int reason) { |
| if (DEBUG) { |
| Slog.d(TAG, "Discover peers failed with reason " + reason + "."); |
| } |
| |
| if (mDiscoverPeersInProgress) { |
| if (reason == 0 && mDiscoverPeersRetriesLeft > 0 && mWfdEnabled) { |
| mHandler.postDelayed(new Runnable() { |
| @Override |
| public void run() { |
| if (mDiscoverPeersInProgress) { |
| if (mDiscoverPeersRetriesLeft > 0 && mWfdEnabled) { |
| mDiscoverPeersRetriesLeft -= 1; |
| if (DEBUG) { |
| Slog.d(TAG, "Retrying discovery. Retries left: " |
| + mDiscoverPeersRetriesLeft); |
| } |
| tryDiscoverPeers(); |
| } else { |
| handleScanFinished(); |
| mDiscoverPeersInProgress = false; |
| } |
| } |
| } |
| }, DISCOVER_PEERS_RETRY_DELAY_MILLIS); |
| } else { |
| handleScanFinished(); |
| mDiscoverPeersInProgress = false; |
| } |
| } |
| } |
| }); |
| } |
| |
| 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); |
| } |
| } |
| |
| handleScanFinished(); |
| } |
| }); |
| } |
| |
| private void handleScanStarted() { |
| mHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| mListener.onScanStarted(); |
| } |
| }); |
| } |
| |
| private void handleScanFinished() { |
| final int count = mAvailableWifiDisplayPeers.size(); |
| final WifiDisplay[] displays = WifiDisplay.CREATOR.newArray(count); |
| for (int i = 0; i < count; i++) { |
| displays[i] = createWifiDisplay(mAvailableWifiDisplayPeers.get(i)); |
| } |
| |
| mHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| mListener.onScanFinished(displays); |
| } |
| }); |
| } |
| |
| 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; |
| } |
| |
| 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 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); |
| |
| setRemoteSubmixOn(false); |
| unadvertiseDisplay(); |
| |
| // 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); |
| |
| unadvertiseDisplay(); |
| |
| 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); |
| |
| unadvertiseDisplay(); |
| 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) { |
| 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(); |
| 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 connections. |
| 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 |
| } |
| |
| setRemoteSubmixOn(true); |
| |
| 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) { |
| if (mConnectedDevice == oldDevice && !mRemoteDisplayConnected) { |
| Slog.i(TAG, "Opened RTSP connection with Wifi display: " |
| + mConnectedDevice.deviceName); |
| mRemoteDisplayConnected = true; |
| mHandler.removeCallbacks(mRtspTimeout); |
| |
| 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); |
| |
| mHandler.postDelayed(mRtspTimeout, RTSP_TIMEOUT_SECONDS * 1000); |
| } |
| } |
| |
| private void setRemoteSubmixOn(boolean on) { |
| if (mRemoteSubmixOn != on) { |
| mRemoteSubmixOn = on; |
| mAudioManager.setRemoteSubmixOn(on, REMOTE_SUBMIX_ADDRESS); |
| } |
| } |
| |
| 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) { |
| 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 (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(); |
| |
| // 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 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 && !Objects.equal(display, oldDisplay)) { |
| mListener.onDisplayConnectionFailed(); |
| } |
| |
| if (display != null) { |
| if (!Objects.equal(display, oldDisplay)) { |
| mListener.onDisplayConnecting(display); |
| } |
| if (surface != null && surface != oldSurface) { |
| mListener.onDisplayConnected(display, surface, width, height, flags); |
| } |
| } |
| } |
| }); |
| } |
| } |
| |
| private void unadvertiseDisplay() { |
| advertiseDisplay(null, null, 0, 0, 0); |
| } |
| |
| 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); |
| } |
| |
| 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); |
| } |
| } |
| }; |
| |
| /** |
| * Called on the handler thread when displays are connected or disconnected. |
| */ |
| public interface Listener { |
| void onFeatureStateChanged(int featureState); |
| |
| void onScanStarted(); |
| void onScanFinished(WifiDisplay[] availableDisplays); |
| |
| void onDisplayConnecting(WifiDisplay display); |
| void onDisplayConnectionFailed(); |
| void onDisplayConnected(WifiDisplay display, |
| Surface surface, int width, int height, int flags); |
| void onDisplayDisconnected(); |
| } |
| } |