Home | History | Annotate | Download | only in display
      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 
     17 package com.android.server.display;
     18 
     19 import com.android.internal.util.DumpUtils;
     20 
     21 import android.content.BroadcastReceiver;
     22 import android.content.ContentResolver;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.content.IntentFilter;
     26 import android.database.ContentObserver;
     27 import android.hardware.display.WifiDisplay;
     28 import android.hardware.display.WifiDisplayStatus;
     29 import android.media.AudioManager;
     30 import android.media.RemoteDisplay;
     31 import android.net.NetworkInfo;
     32 import android.net.Uri;
     33 import android.net.wifi.WpsInfo;
     34 import android.net.wifi.p2p.WifiP2pConfig;
     35 import android.net.wifi.p2p.WifiP2pDevice;
     36 import android.net.wifi.p2p.WifiP2pDeviceList;
     37 import android.net.wifi.p2p.WifiP2pGroup;
     38 import android.net.wifi.p2p.WifiP2pManager;
     39 import android.net.wifi.p2p.WifiP2pWfdInfo;
     40 import android.net.wifi.p2p.WifiP2pManager.ActionListener;
     41 import android.net.wifi.p2p.WifiP2pManager.Channel;
     42 import android.net.wifi.p2p.WifiP2pManager.GroupInfoListener;
     43 import android.net.wifi.p2p.WifiP2pManager.PeerListListener;
     44 import android.os.Handler;
     45 import android.provider.Settings;
     46 import android.util.Slog;
     47 import android.view.Surface;
     48 
     49 import java.io.PrintWriter;
     50 import java.net.Inet4Address;
     51 import java.net.InetAddress;
     52 import java.net.NetworkInterface;
     53 import java.net.SocketException;
     54 import java.util.ArrayList;
     55 import java.util.Enumeration;
     56 
     57 import libcore.util.Objects;
     58 
     59 /**
     60  * Manages all of the various asynchronous interactions with the {@link WifiP2pManager}
     61  * on behalf of {@link WifiDisplayAdapter}.
     62  * <p>
     63  * This code is isolated from {@link WifiDisplayAdapter} so that we can avoid
     64  * accidentally introducing any deadlocks due to the display manager calling
     65  * outside of itself while holding its lock.  It's also way easier to write this
     66  * asynchronous code if we can assume that it is single-threaded.
     67  * </p><p>
     68  * The controller must be instantiated on the handler thread.
     69  * </p>
     70  */
     71 final class WifiDisplayController implements DumpUtils.Dump {
     72     private static final String TAG = "WifiDisplayController";
     73     private static final boolean DEBUG = false;
     74 
     75     private static final int DEFAULT_CONTROL_PORT = 7236;
     76     private static final int MAX_THROUGHPUT = 50;
     77     private static final int CONNECTION_TIMEOUT_SECONDS = 60;
     78     private static final int RTSP_TIMEOUT_SECONDS = 15;
     79 
     80     private static final int DISCOVER_PEERS_MAX_RETRIES = 10;
     81     private static final int DISCOVER_PEERS_RETRY_DELAY_MILLIS = 500;
     82 
     83     private static final int CONNECT_MAX_RETRIES = 3;
     84     private static final int CONNECT_RETRY_DELAY_MILLIS = 500;
     85 
     86     // A unique token to identify the remote submix that is managed by Wifi display.
     87     // It must match what the media server uses when it starts recording the submix
     88     // for transmission.  We use 0 although the actual value is currently ignored.
     89     private static final int REMOTE_SUBMIX_ADDRESS = 0;
     90 
     91     private final Context mContext;
     92     private final Handler mHandler;
     93     private final Listener mListener;
     94 
     95     private final WifiP2pManager mWifiP2pManager;
     96     private final Channel mWifiP2pChannel;
     97 
     98     private final AudioManager mAudioManager;
     99 
    100     private boolean mWifiP2pEnabled;
    101     private boolean mWfdEnabled;
    102     private boolean mWfdEnabling;
    103     private NetworkInfo mNetworkInfo;
    104 
    105     private final ArrayList<WifiP2pDevice> mAvailableWifiDisplayPeers =
    106             new ArrayList<WifiP2pDevice>();
    107 
    108     // True if Wifi display is enabled by the user.
    109     private boolean mWifiDisplayOnSetting;
    110 
    111     // True if there is a call to discoverPeers in progress.
    112     private boolean mDiscoverPeersInProgress;
    113 
    114     // Number of discover peers retries remaining.
    115     private int mDiscoverPeersRetriesLeft;
    116 
    117     // The device to which we want to connect, or null if we want to be disconnected.
    118     private WifiP2pDevice mDesiredDevice;
    119 
    120     // The device to which we are currently connecting, or null if we have already connected
    121     // or are not trying to connect.
    122     private WifiP2pDevice mConnectingDevice;
    123 
    124     // The device from which we are currently disconnecting.
    125     private WifiP2pDevice mDisconnectingDevice;
    126 
    127     // The device to which we were previously trying to connect and are now canceling.
    128     private WifiP2pDevice mCancelingDevice;
    129 
    130     // The device to which we are currently connected, which means we have an active P2P group.
    131     private WifiP2pDevice mConnectedDevice;
    132 
    133     // The group info obtained after connecting.
    134     private WifiP2pGroup mConnectedDeviceGroupInfo;
    135 
    136     // Number of connection retries remaining.
    137     private int mConnectionRetriesLeft;
    138 
    139     // The remote display that is listening on the connection.
    140     // Created after the Wifi P2P network is connected.
    141     private RemoteDisplay mRemoteDisplay;
    142 
    143     // The remote display interface.
    144     private String mRemoteDisplayInterface;
    145 
    146     // True if RTSP has connected.
    147     private boolean mRemoteDisplayConnected;
    148 
    149     // True if the remote submix is enabled.
    150     private boolean mRemoteSubmixOn;
    151 
    152     // The information we have most recently told WifiDisplayAdapter about.
    153     private WifiDisplay mAdvertisedDisplay;
    154     private Surface mAdvertisedDisplaySurface;
    155     private int mAdvertisedDisplayWidth;
    156     private int mAdvertisedDisplayHeight;
    157     private int mAdvertisedDisplayFlags;
    158 
    159     public WifiDisplayController(Context context, Handler handler, Listener listener) {
    160         mContext = context;
    161         mHandler = handler;
    162         mListener = listener;
    163 
    164         mWifiP2pManager = (WifiP2pManager)context.getSystemService(Context.WIFI_P2P_SERVICE);
    165         mWifiP2pChannel = mWifiP2pManager.initialize(context, handler.getLooper(), null);
    166 
    167         mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
    168 
    169         IntentFilter intentFilter = new IntentFilter();
    170         intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
    171         intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
    172         intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
    173         context.registerReceiver(mWifiP2pReceiver, intentFilter, null, mHandler);
    174 
    175         ContentObserver settingsObserver = new ContentObserver(mHandler) {
    176             @Override
    177             public void onChange(boolean selfChange, Uri uri) {
    178                 updateSettings();
    179             }
    180         };
    181 
    182         final ContentResolver resolver = mContext.getContentResolver();
    183         resolver.registerContentObserver(Settings.Global.getUriFor(
    184                 Settings.Global.WIFI_DISPLAY_ON), false, settingsObserver);
    185         updateSettings();
    186     }
    187 
    188     private void updateSettings() {
    189         final ContentResolver resolver = mContext.getContentResolver();
    190         mWifiDisplayOnSetting = Settings.Global.getInt(resolver,
    191                 Settings.Global.WIFI_DISPLAY_ON, 0) != 0;
    192 
    193         updateWfdEnableState();
    194     }
    195 
    196     @Override
    197     public void dump(PrintWriter pw) {
    198         pw.println("mWifiDisplayOnSetting=" + mWifiDisplayOnSetting);
    199         pw.println("mWifiP2pEnabled=" + mWifiP2pEnabled);
    200         pw.println("mWfdEnabled=" + mWfdEnabled);
    201         pw.println("mWfdEnabling=" + mWfdEnabling);
    202         pw.println("mNetworkInfo=" + mNetworkInfo);
    203         pw.println("mDiscoverPeersInProgress=" + mDiscoverPeersInProgress);
    204         pw.println("mDiscoverPeersRetriesLeft=" + mDiscoverPeersRetriesLeft);
    205         pw.println("mDesiredDevice=" + describeWifiP2pDevice(mDesiredDevice));
    206         pw.println("mConnectingDisplay=" + describeWifiP2pDevice(mConnectingDevice));
    207         pw.println("mDisconnectingDisplay=" + describeWifiP2pDevice(mDisconnectingDevice));
    208         pw.println("mCancelingDisplay=" + describeWifiP2pDevice(mCancelingDevice));
    209         pw.println("mConnectedDevice=" + describeWifiP2pDevice(mConnectedDevice));
    210         pw.println("mConnectionRetriesLeft=" + mConnectionRetriesLeft);
    211         pw.println("mRemoteDisplay=" + mRemoteDisplay);
    212         pw.println("mRemoteDisplayInterface=" + mRemoteDisplayInterface);
    213         pw.println("mRemoteDisplayConnected=" + mRemoteDisplayConnected);
    214         pw.println("mRemoteSubmixOn=" + mRemoteSubmixOn);
    215         pw.println("mAdvertisedDisplay=" + mAdvertisedDisplay);
    216         pw.println("mAdvertisedDisplaySurface=" + mAdvertisedDisplaySurface);
    217         pw.println("mAdvertisedDisplayWidth=" + mAdvertisedDisplayWidth);
    218         pw.println("mAdvertisedDisplayHeight=" + mAdvertisedDisplayHeight);
    219         pw.println("mAdvertisedDisplayFlags=" + mAdvertisedDisplayFlags);
    220 
    221         pw.println("mAvailableWifiDisplayPeers: size=" + mAvailableWifiDisplayPeers.size());
    222         for (WifiP2pDevice device : mAvailableWifiDisplayPeers) {
    223             pw.println("  " + describeWifiP2pDevice(device));
    224         }
    225     }
    226 
    227     public void requestScan() {
    228         discoverPeers();
    229     }
    230 
    231     public void requestConnect(String address) {
    232         for (WifiP2pDevice device : mAvailableWifiDisplayPeers) {
    233             if (device.deviceAddress.equals(address)) {
    234                 connect(device);
    235             }
    236         }
    237     }
    238 
    239     public void requestDisconnect() {
    240         disconnect();
    241     }
    242 
    243     private void updateWfdEnableState() {
    244         if (mWifiDisplayOnSetting && mWifiP2pEnabled) {
    245             // WFD should be enabled.
    246             if (!mWfdEnabled && !mWfdEnabling) {
    247                 mWfdEnabling = true;
    248 
    249                 WifiP2pWfdInfo wfdInfo = new WifiP2pWfdInfo();
    250                 wfdInfo.setWfdEnabled(true);
    251                 wfdInfo.setDeviceType(WifiP2pWfdInfo.WFD_SOURCE);
    252                 wfdInfo.setSessionAvailable(true);
    253                 wfdInfo.setControlPort(DEFAULT_CONTROL_PORT);
    254                 wfdInfo.setMaxThroughput(MAX_THROUGHPUT);
    255                 mWifiP2pManager.setWFDInfo(mWifiP2pChannel, wfdInfo, new ActionListener() {
    256                     @Override
    257                     public void onSuccess() {
    258                         if (DEBUG) {
    259                             Slog.d(TAG, "Successfully set WFD info.");
    260                         }
    261                         if (mWfdEnabling) {
    262                             mWfdEnabling = false;
    263                             mWfdEnabled = true;
    264                             reportFeatureState();
    265                         }
    266                     }
    267 
    268                     @Override
    269                     public void onFailure(int reason) {
    270                         if (DEBUG) {
    271                             Slog.d(TAG, "Failed to set WFD info with reason " + reason + ".");
    272                         }
    273                         mWfdEnabling = false;
    274                     }
    275                 });
    276             }
    277         } else {
    278             // WFD should be disabled.
    279             mWfdEnabling = false;
    280             mWfdEnabled = false;
    281             reportFeatureState();
    282             disconnect();
    283         }
    284     }
    285 
    286     private void reportFeatureState() {
    287         final int featureState = computeFeatureState();
    288         mHandler.post(new Runnable() {
    289             @Override
    290             public void run() {
    291                 mListener.onFeatureStateChanged(featureState);
    292             }
    293         });
    294     }
    295 
    296     private int computeFeatureState() {
    297         if (!mWifiP2pEnabled) {
    298             return WifiDisplayStatus.FEATURE_STATE_DISABLED;
    299         }
    300         return mWifiDisplayOnSetting ? WifiDisplayStatus.FEATURE_STATE_ON :
    301                 WifiDisplayStatus.FEATURE_STATE_OFF;
    302     }
    303 
    304     private void discoverPeers() {
    305         if (!mDiscoverPeersInProgress) {
    306             mDiscoverPeersInProgress = true;
    307             mDiscoverPeersRetriesLeft = DISCOVER_PEERS_MAX_RETRIES;
    308             handleScanStarted();
    309             tryDiscoverPeers();
    310         }
    311     }
    312 
    313     private void tryDiscoverPeers() {
    314         mWifiP2pManager.discoverPeers(mWifiP2pChannel, new ActionListener() {
    315             @Override
    316             public void onSuccess() {
    317                 if (DEBUG) {
    318                     Slog.d(TAG, "Discover peers succeeded.  Requesting peers now.");
    319                 }
    320 
    321                 mDiscoverPeersInProgress = false;
    322                 requestPeers();
    323             }
    324 
    325             @Override
    326             public void onFailure(int reason) {
    327                 if (DEBUG) {
    328                     Slog.d(TAG, "Discover peers failed with reason " + reason + ".");
    329                 }
    330 
    331                 if (mDiscoverPeersInProgress) {
    332                     if (reason == 0 && mDiscoverPeersRetriesLeft > 0 && mWfdEnabled) {
    333                         mHandler.postDelayed(new Runnable() {
    334                             @Override
    335                             public void run() {
    336                                 if (mDiscoverPeersInProgress) {
    337                                     if (mDiscoverPeersRetriesLeft > 0 && mWfdEnabled) {
    338                                         mDiscoverPeersRetriesLeft -= 1;
    339                                         if (DEBUG) {
    340                                             Slog.d(TAG, "Retrying discovery.  Retries left: "
    341                                                     + mDiscoverPeersRetriesLeft);
    342                                         }
    343                                         tryDiscoverPeers();
    344                                     } else {
    345                                         handleScanFinished();
    346                                         mDiscoverPeersInProgress = false;
    347                                     }
    348                                 }
    349                             }
    350                         }, DISCOVER_PEERS_RETRY_DELAY_MILLIS);
    351                     } else {
    352                         handleScanFinished();
    353                         mDiscoverPeersInProgress = false;
    354                     }
    355                 }
    356             }
    357         });
    358     }
    359 
    360     private void requestPeers() {
    361         mWifiP2pManager.requestPeers(mWifiP2pChannel, new PeerListListener() {
    362             @Override
    363             public void onPeersAvailable(WifiP2pDeviceList peers) {
    364                 if (DEBUG) {
    365                     Slog.d(TAG, "Received list of peers.");
    366                 }
    367 
    368                 mAvailableWifiDisplayPeers.clear();
    369                 for (WifiP2pDevice device : peers.getDeviceList()) {
    370                     if (DEBUG) {
    371                         Slog.d(TAG, "  " + describeWifiP2pDevice(device));
    372                     }
    373 
    374                     if (isWifiDisplay(device)) {
    375                         mAvailableWifiDisplayPeers.add(device);
    376                     }
    377                 }
    378 
    379                 handleScanFinished();
    380             }
    381         });
    382     }
    383 
    384     private void handleScanStarted() {
    385         mHandler.post(new Runnable() {
    386             @Override
    387             public void run() {
    388                 mListener.onScanStarted();
    389             }
    390         });
    391     }
    392 
    393     private void handleScanFinished() {
    394         final int count = mAvailableWifiDisplayPeers.size();
    395         final WifiDisplay[] displays = WifiDisplay.CREATOR.newArray(count);
    396         for (int i = 0; i < count; i++) {
    397             WifiP2pDevice device = mAvailableWifiDisplayPeers.get(i);
    398             displays[i] = createWifiDisplay(device);
    399             updateDesiredDevice(device);
    400         }
    401 
    402         mHandler.post(new Runnable() {
    403             @Override
    404             public void run() {
    405                 mListener.onScanFinished(displays);
    406             }
    407         });
    408     }
    409 
    410     private void updateDesiredDevice(WifiP2pDevice device) {
    411         // Handle the case where the device to which we are connecting or connected
    412         // may have been renamed or reported different properties in the latest scan.
    413         final String address = device.deviceAddress;
    414         if (mDesiredDevice != null && mDesiredDevice.deviceAddress.equals(address)) {
    415             if (DEBUG) {
    416                 Slog.d(TAG, "updateDesiredDevice: new information "
    417                         + describeWifiP2pDevice(device));
    418             }
    419             mDesiredDevice.update(device);
    420             if (mAdvertisedDisplay != null
    421                     && mAdvertisedDisplay.getDeviceAddress().equals(address)) {
    422                 readvertiseDisplay(createWifiDisplay(mDesiredDevice));
    423             }
    424         }
    425     }
    426 
    427     private void connect(final WifiP2pDevice device) {
    428         if (mDesiredDevice != null
    429                 && !mDesiredDevice.deviceAddress.equals(device.deviceAddress)) {
    430             if (DEBUG) {
    431                 Slog.d(TAG, "connect: nothing to do, already connecting to "
    432                         + describeWifiP2pDevice(device));
    433             }
    434             return;
    435         }
    436 
    437         if (mConnectedDevice != null
    438                 && !mConnectedDevice.deviceAddress.equals(device.deviceAddress)
    439                 && mDesiredDevice == null) {
    440             if (DEBUG) {
    441                 Slog.d(TAG, "connect: nothing to do, already connected to "
    442                         + describeWifiP2pDevice(device) + " and not part way through "
    443                         + "connecting to a different device.");
    444             }
    445             return;
    446         }
    447 
    448         mDesiredDevice = device;
    449         mConnectionRetriesLeft = CONNECT_MAX_RETRIES;
    450         updateConnection();
    451     }
    452 
    453     private void disconnect() {
    454         mDesiredDevice = null;
    455         updateConnection();
    456     }
    457 
    458     private void retryConnection() {
    459         // Cheap hack.  Make a new instance of the device object so that we
    460         // can distinguish it from the previous connection attempt.
    461         // This will cause us to tear everything down before we try again.
    462         mDesiredDevice = new WifiP2pDevice(mDesiredDevice);
    463         updateConnection();
    464     }
    465 
    466     /**
    467      * This function is called repeatedly after each asynchronous operation
    468      * until all preconditions for the connection have been satisfied and the
    469      * connection is established (or not).
    470      */
    471     private void updateConnection() {
    472         // Step 1. Before we try to connect to a new device, tell the system we
    473         // have disconnected from the old one.
    474         if (mRemoteDisplay != null && mConnectedDevice != mDesiredDevice) {
    475             Slog.i(TAG, "Stopped listening for RTSP connection on " + mRemoteDisplayInterface
    476                     + " from Wifi display: " + mConnectedDevice.deviceName);
    477 
    478             mRemoteDisplay.dispose();
    479             mRemoteDisplay = null;
    480             mRemoteDisplayInterface = null;
    481             mRemoteDisplayConnected = false;
    482             mHandler.removeCallbacks(mRtspTimeout);
    483 
    484             mWifiP2pManager.setMiracastMode(WifiP2pManager.MIRACAST_DISABLED);
    485             setRemoteSubmixOn(false);
    486             unadvertiseDisplay();
    487 
    488             // continue to next step
    489         }
    490 
    491         // Step 2. Before we try to connect to a new device, disconnect from the old one.
    492         if (mDisconnectingDevice != null) {
    493             return; // wait for asynchronous callback
    494         }
    495         if (mConnectedDevice != null && mConnectedDevice != mDesiredDevice) {
    496             Slog.i(TAG, "Disconnecting from Wifi display: " + mConnectedDevice.deviceName);
    497             mDisconnectingDevice = mConnectedDevice;
    498             mConnectedDevice = null;
    499 
    500             unadvertiseDisplay();
    501 
    502             final WifiP2pDevice oldDevice = mDisconnectingDevice;
    503             mWifiP2pManager.removeGroup(mWifiP2pChannel, new ActionListener() {
    504                 @Override
    505                 public void onSuccess() {
    506                     Slog.i(TAG, "Disconnected from Wifi display: " + oldDevice.deviceName);
    507                     next();
    508                 }
    509 
    510                 @Override
    511                 public void onFailure(int reason) {
    512                     Slog.i(TAG, "Failed to disconnect from Wifi display: "
    513                             + oldDevice.deviceName + ", reason=" + reason);
    514                     next();
    515                 }
    516 
    517                 private void next() {
    518                     if (mDisconnectingDevice == oldDevice) {
    519                         mDisconnectingDevice = null;
    520                         updateConnection();
    521                     }
    522                 }
    523             });
    524             return; // wait for asynchronous callback
    525         }
    526 
    527         // Step 3. Before we try to connect to a new device, stop trying to connect
    528         // to the old one.
    529         if (mCancelingDevice != null) {
    530             return; // wait for asynchronous callback
    531         }
    532         if (mConnectingDevice != null && mConnectingDevice != mDesiredDevice) {
    533             Slog.i(TAG, "Canceling connection to Wifi display: " + mConnectingDevice.deviceName);
    534             mCancelingDevice = mConnectingDevice;
    535             mConnectingDevice = null;
    536 
    537             unadvertiseDisplay();
    538             mHandler.removeCallbacks(mConnectionTimeout);
    539 
    540             final WifiP2pDevice oldDevice = mCancelingDevice;
    541             mWifiP2pManager.cancelConnect(mWifiP2pChannel, new ActionListener() {
    542                 @Override
    543                 public void onSuccess() {
    544                     Slog.i(TAG, "Canceled connection to Wifi display: " + oldDevice.deviceName);
    545                     next();
    546                 }
    547 
    548                 @Override
    549                 public void onFailure(int reason) {
    550                     Slog.i(TAG, "Failed to cancel connection to Wifi display: "
    551                             + oldDevice.deviceName + ", reason=" + reason);
    552                     next();
    553                 }
    554 
    555                 private void next() {
    556                     if (mCancelingDevice == oldDevice) {
    557                         mCancelingDevice = null;
    558                         updateConnection();
    559                     }
    560                 }
    561             });
    562             return; // wait for asynchronous callback
    563         }
    564 
    565         // Step 4. If we wanted to disconnect, then mission accomplished.
    566         if (mDesiredDevice == null) {
    567             unadvertiseDisplay();
    568             return; // done
    569         }
    570 
    571         // Step 5. Try to connect.
    572         if (mConnectedDevice == null && mConnectingDevice == null) {
    573             Slog.i(TAG, "Connecting to Wifi display: " + mDesiredDevice.deviceName);
    574 
    575             mConnectingDevice = mDesiredDevice;
    576             WifiP2pConfig config = new WifiP2pConfig();
    577             WpsInfo wps = new WpsInfo();
    578             if (mConnectingDevice.wpsPbcSupported()) {
    579                 wps.setup = WpsInfo.PBC;
    580             } else if (mConnectingDevice.wpsDisplaySupported()) {
    581                 // We do keypad if peer does display
    582                 wps.setup = WpsInfo.KEYPAD;
    583             } else {
    584                 wps.setup = WpsInfo.DISPLAY;
    585             }
    586             config.wps = wps;
    587             config.deviceAddress = mConnectingDevice.deviceAddress;
    588             // Helps with STA & P2P concurrency
    589             config.groupOwnerIntent = WifiP2pConfig.MIN_GROUP_OWNER_INTENT;
    590 
    591             WifiDisplay display = createWifiDisplay(mConnectingDevice);
    592             advertiseDisplay(display, null, 0, 0, 0);
    593 
    594             final WifiP2pDevice newDevice = mDesiredDevice;
    595             mWifiP2pManager.connect(mWifiP2pChannel, config, new ActionListener() {
    596                 @Override
    597                 public void onSuccess() {
    598                     // The connection may not yet be established.  We still need to wait
    599                     // for WIFI_P2P_CONNECTION_CHANGED_ACTION.  However, we might never
    600                     // get that broadcast, so we register a timeout.
    601                     Slog.i(TAG, "Initiated connection to Wifi display: " + newDevice.deviceName);
    602 
    603                     mHandler.postDelayed(mConnectionTimeout, CONNECTION_TIMEOUT_SECONDS * 1000);
    604                 }
    605 
    606                 @Override
    607                 public void onFailure(int reason) {
    608                     if (mConnectingDevice == newDevice) {
    609                         Slog.i(TAG, "Failed to initiate connection to Wifi display: "
    610                                 + newDevice.deviceName + ", reason=" + reason);
    611                         mConnectingDevice = null;
    612                         handleConnectionFailure(false);
    613                     }
    614                 }
    615             });
    616             return; // wait for asynchronous callback
    617         }
    618 
    619         // Step 6. Listen for incoming connections.
    620         if (mConnectedDevice != null && mRemoteDisplay == null) {
    621             Inet4Address addr = getInterfaceAddress(mConnectedDeviceGroupInfo);
    622             if (addr == null) {
    623                 Slog.i(TAG, "Failed to get local interface address for communicating "
    624                         + "with Wifi display: " + mConnectedDevice.deviceName);
    625                 handleConnectionFailure(false);
    626                 return; // done
    627             }
    628 
    629             setRemoteSubmixOn(true);
    630             mWifiP2pManager.setMiracastMode(WifiP2pManager.MIRACAST_SOURCE);
    631 
    632             final WifiP2pDevice oldDevice = mConnectedDevice;
    633             final int port = getPortNumber(mConnectedDevice);
    634             final String iface = addr.getHostAddress() + ":" + port;
    635             mRemoteDisplayInterface = iface;
    636 
    637             Slog.i(TAG, "Listening for RTSP connection on " + iface
    638                     + " from Wifi display: " + mConnectedDevice.deviceName);
    639 
    640             mRemoteDisplay = RemoteDisplay.listen(iface, new RemoteDisplay.Listener() {
    641                 @Override
    642                 public void onDisplayConnected(Surface surface,
    643                         int width, int height, int flags) {
    644                     if (mConnectedDevice == oldDevice && !mRemoteDisplayConnected) {
    645                         Slog.i(TAG, "Opened RTSP connection with Wifi display: "
    646                                 + mConnectedDevice.deviceName);
    647                         mRemoteDisplayConnected = true;
    648                         mHandler.removeCallbacks(mRtspTimeout);
    649 
    650                         final WifiDisplay display = createWifiDisplay(mConnectedDevice);
    651                         advertiseDisplay(display, surface, width, height, flags);
    652                     }
    653                 }
    654 
    655                 @Override
    656                 public void onDisplayDisconnected() {
    657                     if (mConnectedDevice == oldDevice) {
    658                         Slog.i(TAG, "Closed RTSP connection with Wifi display: "
    659                                 + mConnectedDevice.deviceName);
    660                         mHandler.removeCallbacks(mRtspTimeout);
    661                         disconnect();
    662                     }
    663                 }
    664 
    665                 @Override
    666                 public void onDisplayError(int error) {
    667                     if (mConnectedDevice == oldDevice) {
    668                         Slog.i(TAG, "Lost RTSP connection with Wifi display due to error "
    669                                 + error + ": " + mConnectedDevice.deviceName);
    670                         mHandler.removeCallbacks(mRtspTimeout);
    671                         handleConnectionFailure(false);
    672                     }
    673                 }
    674             }, mHandler);
    675 
    676             mHandler.postDelayed(mRtspTimeout, RTSP_TIMEOUT_SECONDS * 1000);
    677         }
    678     }
    679 
    680     private void setRemoteSubmixOn(boolean on) {
    681         if (mRemoteSubmixOn != on) {
    682             mRemoteSubmixOn = on;
    683             mAudioManager.setRemoteSubmixOn(on, REMOTE_SUBMIX_ADDRESS);
    684         }
    685     }
    686 
    687     private void handleStateChanged(boolean enabled) {
    688         mWifiP2pEnabled = enabled;
    689         updateWfdEnableState();
    690     }
    691 
    692     private void handlePeersChanged() {
    693         // Even if wfd is disabled, it is best to get the latest set of peers to
    694         // keep in sync with the p2p framework
    695         requestPeers();
    696     }
    697 
    698     private void handleConnectionChanged(NetworkInfo networkInfo) {
    699         mNetworkInfo = networkInfo;
    700         if (mWfdEnabled && networkInfo.isConnected()) {
    701             if (mDesiredDevice != null) {
    702                 mWifiP2pManager.requestGroupInfo(mWifiP2pChannel, new GroupInfoListener() {
    703                     @Override
    704                     public void onGroupInfoAvailable(WifiP2pGroup info) {
    705                         if (DEBUG) {
    706                             Slog.d(TAG, "Received group info: " + describeWifiP2pGroup(info));
    707                         }
    708 
    709                         if (mConnectingDevice != null && !info.contains(mConnectingDevice)) {
    710                             Slog.i(TAG, "Aborting connection to Wifi display because "
    711                                     + "the current P2P group does not contain the device "
    712                                     + "we expected to find: " + mConnectingDevice.deviceName
    713                                     + ", group info was: " + describeWifiP2pGroup(info));
    714                             handleConnectionFailure(false);
    715                             return;
    716                         }
    717 
    718                         if (mDesiredDevice != null && !info.contains(mDesiredDevice)) {
    719                             disconnect();
    720                             return;
    721                         }
    722 
    723                         if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) {
    724                             Slog.i(TAG, "Connected to Wifi display: "
    725                                     + mConnectingDevice.deviceName);
    726 
    727                             mHandler.removeCallbacks(mConnectionTimeout);
    728                             mConnectedDeviceGroupInfo = info;
    729                             mConnectedDevice = mConnectingDevice;
    730                             mConnectingDevice = null;
    731                             updateConnection();
    732                         }
    733                     }
    734                 });
    735             }
    736         } else {
    737             disconnect();
    738 
    739             // After disconnection for a group, for some reason we have a tendency
    740             // to get a peer change notification with an empty list of peers.
    741             // Perform a fresh scan.
    742             if (mWfdEnabled) {
    743                 requestPeers();
    744             }
    745         }
    746     }
    747 
    748     private final Runnable mConnectionTimeout = new Runnable() {
    749         @Override
    750         public void run() {
    751             if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) {
    752                 Slog.i(TAG, "Timed out waiting for Wifi display connection after "
    753                         + CONNECTION_TIMEOUT_SECONDS + " seconds: "
    754                         + mConnectingDevice.deviceName);
    755                 handleConnectionFailure(true);
    756             }
    757         }
    758     };
    759 
    760     private final Runnable mRtspTimeout = new Runnable() {
    761         @Override
    762         public void run() {
    763             if (mConnectedDevice != null
    764                     && mRemoteDisplay != null && !mRemoteDisplayConnected) {
    765                 Slog.i(TAG, "Timed out waiting for Wifi display RTSP connection after "
    766                         + RTSP_TIMEOUT_SECONDS + " seconds: "
    767                         + mConnectedDevice.deviceName);
    768                 handleConnectionFailure(true);
    769             }
    770         }
    771     };
    772 
    773     private void handleConnectionFailure(boolean timeoutOccurred) {
    774         Slog.i(TAG, "Wifi display connection failed!");
    775 
    776         if (mDesiredDevice != null) {
    777             if (mConnectionRetriesLeft > 0) {
    778                 final WifiP2pDevice oldDevice = mDesiredDevice;
    779                 mHandler.postDelayed(new Runnable() {
    780                     @Override
    781                     public void run() {
    782                         if (mDesiredDevice == oldDevice && mConnectionRetriesLeft > 0) {
    783                             mConnectionRetriesLeft -= 1;
    784                             Slog.i(TAG, "Retrying Wifi display connection.  Retries left: "
    785                                     + mConnectionRetriesLeft);
    786                             retryConnection();
    787                         }
    788                     }
    789                 }, timeoutOccurred ? 0 : CONNECT_RETRY_DELAY_MILLIS);
    790             } else {
    791                 disconnect();
    792             }
    793         }
    794     }
    795 
    796     private void advertiseDisplay(final WifiDisplay display,
    797             final Surface surface, final int width, final int height, final int flags) {
    798         if (!Objects.equal(mAdvertisedDisplay, display)
    799                 || mAdvertisedDisplaySurface != surface
    800                 || mAdvertisedDisplayWidth != width
    801                 || mAdvertisedDisplayHeight != height
    802                 || mAdvertisedDisplayFlags != flags) {
    803             final WifiDisplay oldDisplay = mAdvertisedDisplay;
    804             final Surface oldSurface = mAdvertisedDisplaySurface;
    805 
    806             mAdvertisedDisplay = display;
    807             mAdvertisedDisplaySurface = surface;
    808             mAdvertisedDisplayWidth = width;
    809             mAdvertisedDisplayHeight = height;
    810             mAdvertisedDisplayFlags = flags;
    811 
    812             mHandler.post(new Runnable() {
    813                 @Override
    814                 public void run() {
    815                     if (oldSurface != null && surface != oldSurface) {
    816                         mListener.onDisplayDisconnected();
    817                     } else if (oldDisplay != null && !oldDisplay.hasSameAddress(display)) {
    818                         mListener.onDisplayConnectionFailed();
    819                     }
    820 
    821                     if (display != null) {
    822                         if (!display.hasSameAddress(oldDisplay)) {
    823                             mListener.onDisplayConnecting(display);
    824                         } else if (!display.equals(oldDisplay)) {
    825                             // The address is the same but some other property such as the
    826                             // name must have changed.
    827                             mListener.onDisplayChanged(display);
    828                         }
    829                         if (surface != null && surface != oldSurface) {
    830                             mListener.onDisplayConnected(display, surface, width, height, flags);
    831                         }
    832                     }
    833                 }
    834             });
    835         }
    836     }
    837 
    838     private void unadvertiseDisplay() {
    839         advertiseDisplay(null, null, 0, 0, 0);
    840     }
    841 
    842     private void readvertiseDisplay(WifiDisplay display) {
    843         advertiseDisplay(display, mAdvertisedDisplaySurface,
    844                 mAdvertisedDisplayWidth, mAdvertisedDisplayHeight,
    845                 mAdvertisedDisplayFlags);
    846     }
    847 
    848     private static Inet4Address getInterfaceAddress(WifiP2pGroup info) {
    849         NetworkInterface iface;
    850         try {
    851             iface = NetworkInterface.getByName(info.getInterface());
    852         } catch (SocketException ex) {
    853             Slog.w(TAG, "Could not obtain address of network interface "
    854                     + info.getInterface(), ex);
    855             return null;
    856         }
    857 
    858         Enumeration<InetAddress> addrs = iface.getInetAddresses();
    859         while (addrs.hasMoreElements()) {
    860             InetAddress addr = addrs.nextElement();
    861             if (addr instanceof Inet4Address) {
    862                 return (Inet4Address)addr;
    863             }
    864         }
    865 
    866         Slog.w(TAG, "Could not obtain address of network interface "
    867                 + info.getInterface() + " because it had no IPv4 addresses.");
    868         return null;
    869     }
    870 
    871     private static int getPortNumber(WifiP2pDevice device) {
    872         if (device.deviceName.startsWith("DIRECT-")
    873                 && device.deviceName.endsWith("Broadcom")) {
    874             // These dongles ignore the port we broadcast in our WFD IE.
    875             return 8554;
    876         }
    877         return DEFAULT_CONTROL_PORT;
    878     }
    879 
    880     private static boolean isWifiDisplay(WifiP2pDevice device) {
    881         return device.wfdInfo != null
    882                 && device.wfdInfo.isWfdEnabled()
    883                 && isPrimarySinkDeviceType(device.wfdInfo.getDeviceType());
    884     }
    885 
    886     private static boolean isPrimarySinkDeviceType(int deviceType) {
    887         return deviceType == WifiP2pWfdInfo.PRIMARY_SINK
    888                 || deviceType == WifiP2pWfdInfo.SOURCE_OR_PRIMARY_SINK;
    889     }
    890 
    891     private static String describeWifiP2pDevice(WifiP2pDevice device) {
    892         return device != null ? device.toString().replace('\n', ',') : "null";
    893     }
    894 
    895     private static String describeWifiP2pGroup(WifiP2pGroup group) {
    896         return group != null ? group.toString().replace('\n', ',') : "null";
    897     }
    898 
    899     private static WifiDisplay createWifiDisplay(WifiP2pDevice device) {
    900         return new WifiDisplay(device.deviceAddress, device.deviceName, null);
    901     }
    902 
    903     private final BroadcastReceiver mWifiP2pReceiver = new BroadcastReceiver() {
    904         @Override
    905         public void onReceive(Context context, Intent intent) {
    906             final String action = intent.getAction();
    907             if (action.equals(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)) {
    908                 // This broadcast is sticky so we'll always get the initial Wifi P2P state
    909                 // on startup.
    910                 boolean enabled = (intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE,
    911                         WifiP2pManager.WIFI_P2P_STATE_DISABLED)) ==
    912                         WifiP2pManager.WIFI_P2P_STATE_ENABLED;
    913                 if (DEBUG) {
    914                     Slog.d(TAG, "Received WIFI_P2P_STATE_CHANGED_ACTION: enabled="
    915                             + enabled);
    916                 }
    917 
    918                 handleStateChanged(enabled);
    919             } else if (action.equals(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)) {
    920                 if (DEBUG) {
    921                     Slog.d(TAG, "Received WIFI_P2P_PEERS_CHANGED_ACTION.");
    922                 }
    923 
    924                 handlePeersChanged();
    925             } else if (action.equals(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)) {
    926                 NetworkInfo networkInfo = (NetworkInfo)intent.getParcelableExtra(
    927                         WifiP2pManager.EXTRA_NETWORK_INFO);
    928                 if (DEBUG) {
    929                     Slog.d(TAG, "Received WIFI_P2P_CONNECTION_CHANGED_ACTION: networkInfo="
    930                             + networkInfo);
    931                 }
    932 
    933                 handleConnectionChanged(networkInfo);
    934             }
    935         }
    936     };
    937 
    938     /**
    939      * Called on the handler thread when displays are connected or disconnected.
    940      */
    941     public interface Listener {
    942         void onFeatureStateChanged(int featureState);
    943 
    944         void onScanStarted();
    945         void onScanFinished(WifiDisplay[] availableDisplays);
    946 
    947         void onDisplayConnecting(WifiDisplay display);
    948         void onDisplayConnectionFailed();
    949         void onDisplayChanged(WifiDisplay display);
    950         void onDisplayConnected(WifiDisplay display,
    951                 Surface surface, int width, int height, int flags);
    952         void onDisplayDisconnected();
    953     }
    954 }
    955