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.WifiDisplaySessionInfo;
     29 import android.hardware.display.WifiDisplayStatus;
     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 = 30;
     78     private static final int RTSP_TIMEOUT_SECONDS = 30;
     79     private static final int RTSP_TIMEOUT_SECONDS_CERT_MODE = 120;
     80 
     81     // We repeatedly issue calls to discover peers every so often for a few reasons.
     82     // 1. The initial request may fail and need to retried.
     83     // 2. Discovery will self-abort after any group is initiated, which may not necessarily
     84     //    be what we want to have happen.
     85     // 3. Discovery will self-timeout after 2 minutes, whereas we want discovery to
     86     //    be occur for as long as a client is requesting it be.
     87     // 4. We don't seem to get updated results for displays we've already found until
     88     //    we ask to discover again, particularly for the isSessionAvailable() property.
     89     private static final int DISCOVER_PEERS_INTERVAL_MILLIS = 10000;
     90 
     91     private static final int CONNECT_MAX_RETRIES = 3;
     92     private static final int CONNECT_RETRY_DELAY_MILLIS = 500;
     93 
     94     private final Context mContext;
     95     private final Handler mHandler;
     96     private final Listener mListener;
     97 
     98     private final WifiP2pManager mWifiP2pManager;
     99     private final Channel mWifiP2pChannel;
    100 
    101     private boolean mWifiP2pEnabled;
    102     private boolean mWfdEnabled;
    103     private boolean mWfdEnabling;
    104     private NetworkInfo mNetworkInfo;
    105 
    106     private final ArrayList<WifiP2pDevice> mAvailableWifiDisplayPeers =
    107             new ArrayList<WifiP2pDevice>();
    108 
    109     // True if Wifi display is enabled by the user.
    110     private boolean mWifiDisplayOnSetting;
    111 
    112     // True if a scan was requested independent of whether one is actually in progress.
    113     private boolean mScanRequested;
    114 
    115     // True if there is a call to discoverPeers in progress.
    116     private boolean mDiscoverPeersInProgress;
    117 
    118     // The device to which we want to connect, or null if we want to be disconnected.
    119     private WifiP2pDevice mDesiredDevice;
    120 
    121     // The device to which we are currently connecting, or null if we have already connected
    122     // or are not trying to connect.
    123     private WifiP2pDevice mConnectingDevice;
    124 
    125     // The device from which we are currently disconnecting.
    126     private WifiP2pDevice mDisconnectingDevice;
    127 
    128     // The device to which we were previously trying to connect and are now canceling.
    129     private WifiP2pDevice mCancelingDevice;
    130 
    131     // The device to which we are currently connected, which means we have an active P2P group.
    132     private WifiP2pDevice mConnectedDevice;
    133 
    134     // The group info obtained after connecting.
    135     private WifiP2pGroup mConnectedDeviceGroupInfo;
    136 
    137     // Number of connection retries remaining.
    138     private int mConnectionRetriesLeft;
    139 
    140     // The remote display that is listening on the connection.
    141     // Created after the Wifi P2P network is connected.
    142     private RemoteDisplay mRemoteDisplay;
    143 
    144     // The remote display interface.
    145     private String mRemoteDisplayInterface;
    146 
    147     // True if RTSP has connected.
    148     private boolean mRemoteDisplayConnected;
    149 
    150     // The information we have most recently told WifiDisplayAdapter about.
    151     private WifiDisplay mAdvertisedDisplay;
    152     private Surface mAdvertisedDisplaySurface;
    153     private int mAdvertisedDisplayWidth;
    154     private int mAdvertisedDisplayHeight;
    155     private int mAdvertisedDisplayFlags;
    156 
    157     // Certification
    158     private boolean mWifiDisplayCertMode;
    159     private int mWifiDisplayWpsConfig = WpsInfo.INVALID;
    160 
    161     private WifiP2pDevice mThisDevice;
    162 
    163     public WifiDisplayController(Context context, Handler handler, Listener listener) {
    164         mContext = context;
    165         mHandler = handler;
    166         mListener = listener;
    167 
    168         mWifiP2pManager = (WifiP2pManager)context.getSystemService(Context.WIFI_P2P_SERVICE);
    169         mWifiP2pChannel = mWifiP2pManager.initialize(context, handler.getLooper(), null);
    170 
    171         IntentFilter intentFilter = new IntentFilter();
    172         intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
    173         intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
    174         intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
    175         intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
    176         context.registerReceiver(mWifiP2pReceiver, intentFilter, null, mHandler);
    177 
    178         ContentObserver settingsObserver = new ContentObserver(mHandler) {
    179             @Override
    180             public void onChange(boolean selfChange, Uri uri) {
    181                 updateSettings();
    182             }
    183         };
    184 
    185         final ContentResolver resolver = mContext.getContentResolver();
    186         resolver.registerContentObserver(Settings.Global.getUriFor(
    187                 Settings.Global.WIFI_DISPLAY_ON), false, settingsObserver);
    188         resolver.registerContentObserver(Settings.Global.getUriFor(
    189                 Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON), false, settingsObserver);
    190         resolver.registerContentObserver(Settings.Global.getUriFor(
    191                 Settings.Global.WIFI_DISPLAY_WPS_CONFIG), false, settingsObserver);
    192         updateSettings();
    193     }
    194 
    195     private void updateSettings() {
    196         final ContentResolver resolver = mContext.getContentResolver();
    197         mWifiDisplayOnSetting = Settings.Global.getInt(resolver,
    198                 Settings.Global.WIFI_DISPLAY_ON, 0) != 0;
    199         mWifiDisplayCertMode = Settings.Global.getInt(resolver,
    200                 Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON, 0) != 0;
    201 
    202         mWifiDisplayWpsConfig = WpsInfo.INVALID;
    203         if (mWifiDisplayCertMode) {
    204             mWifiDisplayWpsConfig = Settings.Global.getInt(resolver,
    205                   Settings.Global.WIFI_DISPLAY_WPS_CONFIG, WpsInfo.INVALID);
    206         }
    207 
    208         updateWfdEnableState();
    209     }
    210 
    211     @Override
    212     public void dump(PrintWriter pw) {
    213         pw.println("mWifiDisplayOnSetting=" + mWifiDisplayOnSetting);
    214         pw.println("mWifiP2pEnabled=" + mWifiP2pEnabled);
    215         pw.println("mWfdEnabled=" + mWfdEnabled);
    216         pw.println("mWfdEnabling=" + mWfdEnabling);
    217         pw.println("mNetworkInfo=" + mNetworkInfo);
    218         pw.println("mScanRequested=" + mScanRequested);
    219         pw.println("mDiscoverPeersInProgress=" + mDiscoverPeersInProgress);
    220         pw.println("mDesiredDevice=" + describeWifiP2pDevice(mDesiredDevice));
    221         pw.println("mConnectingDisplay=" + describeWifiP2pDevice(mConnectingDevice));
    222         pw.println("mDisconnectingDisplay=" + describeWifiP2pDevice(mDisconnectingDevice));
    223         pw.println("mCancelingDisplay=" + describeWifiP2pDevice(mCancelingDevice));
    224         pw.println("mConnectedDevice=" + describeWifiP2pDevice(mConnectedDevice));
    225         pw.println("mConnectionRetriesLeft=" + mConnectionRetriesLeft);
    226         pw.println("mRemoteDisplay=" + mRemoteDisplay);
    227         pw.println("mRemoteDisplayInterface=" + mRemoteDisplayInterface);
    228         pw.println("mRemoteDisplayConnected=" + mRemoteDisplayConnected);
    229         pw.println("mAdvertisedDisplay=" + mAdvertisedDisplay);
    230         pw.println("mAdvertisedDisplaySurface=" + mAdvertisedDisplaySurface);
    231         pw.println("mAdvertisedDisplayWidth=" + mAdvertisedDisplayWidth);
    232         pw.println("mAdvertisedDisplayHeight=" + mAdvertisedDisplayHeight);
    233         pw.println("mAdvertisedDisplayFlags=" + mAdvertisedDisplayFlags);
    234 
    235         pw.println("mAvailableWifiDisplayPeers: size=" + mAvailableWifiDisplayPeers.size());
    236         for (WifiP2pDevice device : mAvailableWifiDisplayPeers) {
    237             pw.println("  " + describeWifiP2pDevice(device));
    238         }
    239     }
    240 
    241     public void requestStartScan() {
    242         if (!mScanRequested) {
    243             mScanRequested = true;
    244             updateScanState();
    245         }
    246     }
    247 
    248     public void requestStopScan() {
    249         if (mScanRequested) {
    250             mScanRequested = false;
    251             updateScanState();
    252         }
    253     }
    254 
    255     public void requestConnect(String address) {
    256         for (WifiP2pDevice device : mAvailableWifiDisplayPeers) {
    257             if (device.deviceAddress.equals(address)) {
    258                 connect(device);
    259             }
    260         }
    261     }
    262 
    263     public void requestPause() {
    264         if (mRemoteDisplay != null) {
    265             mRemoteDisplay.pause();
    266         }
    267     }
    268 
    269     public void requestResume() {
    270         if (mRemoteDisplay != null) {
    271             mRemoteDisplay.resume();
    272         }
    273     }
    274 
    275     public void requestDisconnect() {
    276         disconnect();
    277     }
    278 
    279     private void updateWfdEnableState() {
    280         if (mWifiDisplayOnSetting && mWifiP2pEnabled) {
    281             // WFD should be enabled.
    282             if (!mWfdEnabled && !mWfdEnabling) {
    283                 mWfdEnabling = true;
    284 
    285                 WifiP2pWfdInfo wfdInfo = new WifiP2pWfdInfo();
    286                 wfdInfo.setWfdEnabled(true);
    287                 wfdInfo.setDeviceType(WifiP2pWfdInfo.WFD_SOURCE);
    288                 wfdInfo.setSessionAvailable(true);
    289                 wfdInfo.setControlPort(DEFAULT_CONTROL_PORT);
    290                 wfdInfo.setMaxThroughput(MAX_THROUGHPUT);
    291                 mWifiP2pManager.setWFDInfo(mWifiP2pChannel, wfdInfo, new ActionListener() {
    292                     @Override
    293                     public void onSuccess() {
    294                         if (DEBUG) {
    295                             Slog.d(TAG, "Successfully set WFD info.");
    296                         }
    297                         if (mWfdEnabling) {
    298                             mWfdEnabling = false;
    299                             mWfdEnabled = true;
    300                             reportFeatureState();
    301                             updateScanState();
    302                         }
    303                     }
    304 
    305                     @Override
    306                     public void onFailure(int reason) {
    307                         if (DEBUG) {
    308                             Slog.d(TAG, "Failed to set WFD info with reason " + reason + ".");
    309                         }
    310                         mWfdEnabling = false;
    311                     }
    312                 });
    313             }
    314         } else {
    315             // WFD should be disabled.
    316             if (mWfdEnabled || mWfdEnabling) {
    317                 WifiP2pWfdInfo wfdInfo = new WifiP2pWfdInfo();
    318                 wfdInfo.setWfdEnabled(false);
    319                 mWifiP2pManager.setWFDInfo(mWifiP2pChannel, wfdInfo, new ActionListener() {
    320                     @Override
    321                     public void onSuccess() {
    322                         if (DEBUG) {
    323                             Slog.d(TAG, "Successfully set WFD info.");
    324                         }
    325                     }
    326 
    327                     @Override
    328                     public void onFailure(int reason) {
    329                         if (DEBUG) {
    330                             Slog.d(TAG, "Failed to set WFD info with reason " + reason + ".");
    331                         }
    332                     }
    333                 });
    334             }
    335             mWfdEnabling = false;
    336             mWfdEnabled = false;
    337             reportFeatureState();
    338             updateScanState();
    339             disconnect();
    340         }
    341     }
    342 
    343     private void reportFeatureState() {
    344         final int featureState = computeFeatureState();
    345         mHandler.post(new Runnable() {
    346             @Override
    347             public void run() {
    348                 mListener.onFeatureStateChanged(featureState);
    349             }
    350         });
    351     }
    352 
    353     private int computeFeatureState() {
    354         if (!mWifiP2pEnabled) {
    355             return WifiDisplayStatus.FEATURE_STATE_DISABLED;
    356         }
    357         return mWifiDisplayOnSetting ? WifiDisplayStatus.FEATURE_STATE_ON :
    358                 WifiDisplayStatus.FEATURE_STATE_OFF;
    359     }
    360 
    361     private void updateScanState() {
    362         if (mScanRequested && mWfdEnabled && mDesiredDevice == null) {
    363             if (!mDiscoverPeersInProgress) {
    364                 Slog.i(TAG, "Starting Wifi display scan.");
    365                 mDiscoverPeersInProgress = true;
    366                 handleScanStarted();
    367                 tryDiscoverPeers();
    368             }
    369         } else {
    370             if (mDiscoverPeersInProgress) {
    371                 // Cancel automatic retry right away.
    372                 mHandler.removeCallbacks(mDiscoverPeers);
    373 
    374                 // Defer actually stopping discovery if we have a connection attempt in progress.
    375                 // The wifi display connection attempt often fails if we are not in discovery
    376                 // mode.  So we allow discovery to continue until we give up trying to connect.
    377                 if (mDesiredDevice == null || mDesiredDevice == mConnectedDevice) {
    378                     Slog.i(TAG, "Stopping Wifi display scan.");
    379                     mDiscoverPeersInProgress = false;
    380                     stopPeerDiscovery();
    381                     handleScanFinished();
    382                 }
    383             }
    384         }
    385     }
    386 
    387     private void tryDiscoverPeers() {
    388         mWifiP2pManager.discoverPeers(mWifiP2pChannel, new ActionListener() {
    389             @Override
    390             public void onSuccess() {
    391                 if (DEBUG) {
    392                     Slog.d(TAG, "Discover peers succeeded.  Requesting peers now.");
    393                 }
    394 
    395                 if (mDiscoverPeersInProgress) {
    396                     requestPeers();
    397                 }
    398             }
    399 
    400             @Override
    401             public void onFailure(int reason) {
    402                 if (DEBUG) {
    403                     Slog.d(TAG, "Discover peers failed with reason " + reason + ".");
    404                 }
    405 
    406                 // Ignore the error.
    407                 // We will retry automatically in a little bit.
    408             }
    409         });
    410 
    411         // Retry discover peers periodically until stopped.
    412         mHandler.postDelayed(mDiscoverPeers, DISCOVER_PEERS_INTERVAL_MILLIS);
    413     }
    414 
    415     private void stopPeerDiscovery() {
    416         mWifiP2pManager.stopPeerDiscovery(mWifiP2pChannel, new ActionListener() {
    417             @Override
    418             public void onSuccess() {
    419                 if (DEBUG) {
    420                     Slog.d(TAG, "Stop peer discovery succeeded.");
    421                 }
    422             }
    423 
    424             @Override
    425             public void onFailure(int reason) {
    426                 if (DEBUG) {
    427                     Slog.d(TAG, "Stop peer discovery failed with reason " + reason + ".");
    428                 }
    429             }
    430         });
    431     }
    432 
    433     private void requestPeers() {
    434         mWifiP2pManager.requestPeers(mWifiP2pChannel, new PeerListListener() {
    435             @Override
    436             public void onPeersAvailable(WifiP2pDeviceList peers) {
    437                 if (DEBUG) {
    438                     Slog.d(TAG, "Received list of peers.");
    439                 }
    440 
    441                 mAvailableWifiDisplayPeers.clear();
    442                 for (WifiP2pDevice device : peers.getDeviceList()) {
    443                     if (DEBUG) {
    444                         Slog.d(TAG, "  " + describeWifiP2pDevice(device));
    445                     }
    446 
    447                     if (isWifiDisplay(device)) {
    448                         mAvailableWifiDisplayPeers.add(device);
    449                     }
    450                 }
    451 
    452                 if (mDiscoverPeersInProgress) {
    453                     handleScanResults();
    454                 }
    455             }
    456         });
    457     }
    458 
    459     private void handleScanStarted() {
    460         mHandler.post(new Runnable() {
    461             @Override
    462             public void run() {
    463                 mListener.onScanStarted();
    464             }
    465         });
    466     }
    467 
    468     private void handleScanResults() {
    469         final int count = mAvailableWifiDisplayPeers.size();
    470         final WifiDisplay[] displays = WifiDisplay.CREATOR.newArray(count);
    471         for (int i = 0; i < count; i++) {
    472             WifiP2pDevice device = mAvailableWifiDisplayPeers.get(i);
    473             displays[i] = createWifiDisplay(device);
    474             updateDesiredDevice(device);
    475         }
    476 
    477         mHandler.post(new Runnable() {
    478             @Override
    479             public void run() {
    480                 mListener.onScanResults(displays);
    481             }
    482         });
    483     }
    484 
    485     private void handleScanFinished() {
    486         mHandler.post(new Runnable() {
    487             @Override
    488             public void run() {
    489                 mListener.onScanFinished();
    490             }
    491         });
    492     }
    493 
    494     private void updateDesiredDevice(WifiP2pDevice device) {
    495         // Handle the case where the device to which we are connecting or connected
    496         // may have been renamed or reported different properties in the latest scan.
    497         final String address = device.deviceAddress;
    498         if (mDesiredDevice != null && mDesiredDevice.deviceAddress.equals(address)) {
    499             if (DEBUG) {
    500                 Slog.d(TAG, "updateDesiredDevice: new information "
    501                         + describeWifiP2pDevice(device));
    502             }
    503             mDesiredDevice.update(device);
    504             if (mAdvertisedDisplay != null
    505                     && mAdvertisedDisplay.getDeviceAddress().equals(address)) {
    506                 readvertiseDisplay(createWifiDisplay(mDesiredDevice));
    507             }
    508         }
    509     }
    510 
    511     private void connect(final WifiP2pDevice device) {
    512         if (mDesiredDevice != null
    513                 && !mDesiredDevice.deviceAddress.equals(device.deviceAddress)) {
    514             if (DEBUG) {
    515                 Slog.d(TAG, "connect: nothing to do, already connecting to "
    516                         + describeWifiP2pDevice(device));
    517             }
    518             return;
    519         }
    520 
    521         if (mConnectedDevice != null
    522                 && !mConnectedDevice.deviceAddress.equals(device.deviceAddress)
    523                 && mDesiredDevice == null) {
    524             if (DEBUG) {
    525                 Slog.d(TAG, "connect: nothing to do, already connected to "
    526                         + describeWifiP2pDevice(device) + " and not part way through "
    527                         + "connecting to a different device.");
    528             }
    529             return;
    530         }
    531 
    532         if (!mWfdEnabled) {
    533             Slog.i(TAG, "Ignoring request to connect to Wifi display because the "
    534                     +" feature is currently disabled: " + device.deviceName);
    535             return;
    536         }
    537 
    538         mDesiredDevice = device;
    539         mConnectionRetriesLeft = CONNECT_MAX_RETRIES;
    540         updateConnection();
    541     }
    542 
    543     private void disconnect() {
    544         mDesiredDevice = null;
    545         updateConnection();
    546     }
    547 
    548     private void retryConnection() {
    549         // Cheap hack.  Make a new instance of the device object so that we
    550         // can distinguish it from the previous connection attempt.
    551         // This will cause us to tear everything down before we try again.
    552         mDesiredDevice = new WifiP2pDevice(mDesiredDevice);
    553         updateConnection();
    554     }
    555 
    556     /**
    557      * This function is called repeatedly after each asynchronous operation
    558      * until all preconditions for the connection have been satisfied and the
    559      * connection is established (or not).
    560      */
    561     private void updateConnection() {
    562         // Step 0. Stop scans if necessary to prevent interference while connected.
    563         // Resume scans later when no longer attempting to connect.
    564         updateScanState();
    565 
    566         // Step 1. Before we try to connect to a new device, tell the system we
    567         // have disconnected from the old one.
    568         if (mRemoteDisplay != null && mConnectedDevice != mDesiredDevice) {
    569             Slog.i(TAG, "Stopped listening for RTSP connection on " + mRemoteDisplayInterface
    570                     + " from Wifi display: " + mConnectedDevice.deviceName);
    571 
    572             mRemoteDisplay.dispose();
    573             mRemoteDisplay = null;
    574             mRemoteDisplayInterface = null;
    575             mRemoteDisplayConnected = false;
    576             mHandler.removeCallbacks(mRtspTimeout);
    577 
    578             mWifiP2pManager.setMiracastMode(WifiP2pManager.MIRACAST_DISABLED);
    579             unadvertiseDisplay();
    580 
    581             // continue to next step
    582         }
    583 
    584         // Step 2. Before we try to connect to a new device, disconnect from the old one.
    585         if (mDisconnectingDevice != null) {
    586             return; // wait for asynchronous callback
    587         }
    588         if (mConnectedDevice != null && mConnectedDevice != mDesiredDevice) {
    589             Slog.i(TAG, "Disconnecting from Wifi display: " + mConnectedDevice.deviceName);
    590             mDisconnectingDevice = mConnectedDevice;
    591             mConnectedDevice = null;
    592             mConnectedDeviceGroupInfo = null;
    593 
    594             unadvertiseDisplay();
    595 
    596             final WifiP2pDevice oldDevice = mDisconnectingDevice;
    597             mWifiP2pManager.removeGroup(mWifiP2pChannel, new ActionListener() {
    598                 @Override
    599                 public void onSuccess() {
    600                     Slog.i(TAG, "Disconnected from Wifi display: " + oldDevice.deviceName);
    601                     next();
    602                 }
    603 
    604                 @Override
    605                 public void onFailure(int reason) {
    606                     Slog.i(TAG, "Failed to disconnect from Wifi display: "
    607                             + oldDevice.deviceName + ", reason=" + reason);
    608                     next();
    609                 }
    610 
    611                 private void next() {
    612                     if (mDisconnectingDevice == oldDevice) {
    613                         mDisconnectingDevice = null;
    614                         updateConnection();
    615                     }
    616                 }
    617             });
    618             return; // wait for asynchronous callback
    619         }
    620 
    621         // Step 3. Before we try to connect to a new device, stop trying to connect
    622         // to the old one.
    623         if (mCancelingDevice != null) {
    624             return; // wait for asynchronous callback
    625         }
    626         if (mConnectingDevice != null && mConnectingDevice != mDesiredDevice) {
    627             Slog.i(TAG, "Canceling connection to Wifi display: " + mConnectingDevice.deviceName);
    628             mCancelingDevice = mConnectingDevice;
    629             mConnectingDevice = null;
    630 
    631             unadvertiseDisplay();
    632             mHandler.removeCallbacks(mConnectionTimeout);
    633 
    634             final WifiP2pDevice oldDevice = mCancelingDevice;
    635             mWifiP2pManager.cancelConnect(mWifiP2pChannel, new ActionListener() {
    636                 @Override
    637                 public void onSuccess() {
    638                     Slog.i(TAG, "Canceled connection to Wifi display: " + oldDevice.deviceName);
    639                     next();
    640                 }
    641 
    642                 @Override
    643                 public void onFailure(int reason) {
    644                     Slog.i(TAG, "Failed to cancel connection to Wifi display: "
    645                             + oldDevice.deviceName + ", reason=" + reason);
    646                     next();
    647                 }
    648 
    649                 private void next() {
    650                     if (mCancelingDevice == oldDevice) {
    651                         mCancelingDevice = null;
    652                         updateConnection();
    653                     }
    654                 }
    655             });
    656             return; // wait for asynchronous callback
    657         }
    658 
    659         // Step 4. If we wanted to disconnect, or we're updating after starting an
    660         // autonomous GO, then mission accomplished.
    661         if (mDesiredDevice == null) {
    662             if (mWifiDisplayCertMode) {
    663                 mListener.onDisplaySessionInfo(getSessionInfo(mConnectedDeviceGroupInfo, 0));
    664             }
    665             unadvertiseDisplay();
    666             return; // done
    667         }
    668 
    669         // Step 5. Try to connect.
    670         if (mConnectedDevice == null && mConnectingDevice == null) {
    671             Slog.i(TAG, "Connecting to Wifi display: " + mDesiredDevice.deviceName);
    672 
    673             mConnectingDevice = mDesiredDevice;
    674             WifiP2pConfig config = new WifiP2pConfig();
    675             WpsInfo wps = new WpsInfo();
    676             if (mWifiDisplayWpsConfig != WpsInfo.INVALID) {
    677                 wps.setup = mWifiDisplayWpsConfig;
    678             } else if (mConnectingDevice.wpsPbcSupported()) {
    679                 wps.setup = WpsInfo.PBC;
    680             } else if (mConnectingDevice.wpsDisplaySupported()) {
    681                 // We do keypad if peer does display
    682                 wps.setup = WpsInfo.KEYPAD;
    683             } else {
    684                 wps.setup = WpsInfo.DISPLAY;
    685             }
    686             config.wps = wps;
    687             config.deviceAddress = mConnectingDevice.deviceAddress;
    688             // Helps with STA & P2P concurrency
    689             config.groupOwnerIntent = WifiP2pConfig.MIN_GROUP_OWNER_INTENT;
    690 
    691             WifiDisplay display = createWifiDisplay(mConnectingDevice);
    692             advertiseDisplay(display, null, 0, 0, 0);
    693 
    694             final WifiP2pDevice newDevice = mDesiredDevice;
    695             mWifiP2pManager.connect(mWifiP2pChannel, config, new ActionListener() {
    696                 @Override
    697                 public void onSuccess() {
    698                     // The connection may not yet be established.  We still need to wait
    699                     // for WIFI_P2P_CONNECTION_CHANGED_ACTION.  However, we might never
    700                     // get that broadcast, so we register a timeout.
    701                     Slog.i(TAG, "Initiated connection to Wifi display: " + newDevice.deviceName);
    702 
    703                     mHandler.postDelayed(mConnectionTimeout, CONNECTION_TIMEOUT_SECONDS * 1000);
    704                 }
    705 
    706                 @Override
    707                 public void onFailure(int reason) {
    708                     if (mConnectingDevice == newDevice) {
    709                         Slog.i(TAG, "Failed to initiate connection to Wifi display: "
    710                                 + newDevice.deviceName + ", reason=" + reason);
    711                         mConnectingDevice = null;
    712                         handleConnectionFailure(false);
    713                     }
    714                 }
    715             });
    716             return; // wait for asynchronous callback
    717         }
    718 
    719         // Step 6. Listen for incoming RTSP connection.
    720         if (mConnectedDevice != null && mRemoteDisplay == null) {
    721             Inet4Address addr = getInterfaceAddress(mConnectedDeviceGroupInfo);
    722             if (addr == null) {
    723                 Slog.i(TAG, "Failed to get local interface address for communicating "
    724                         + "with Wifi display: " + mConnectedDevice.deviceName);
    725                 handleConnectionFailure(false);
    726                 return; // done
    727             }
    728 
    729             mWifiP2pManager.setMiracastMode(WifiP2pManager.MIRACAST_SOURCE);
    730 
    731             final WifiP2pDevice oldDevice = mConnectedDevice;
    732             final int port = getPortNumber(mConnectedDevice);
    733             final String iface = addr.getHostAddress() + ":" + port;
    734             mRemoteDisplayInterface = iface;
    735 
    736             Slog.i(TAG, "Listening for RTSP connection on " + iface
    737                     + " from Wifi display: " + mConnectedDevice.deviceName);
    738 
    739             mRemoteDisplay = RemoteDisplay.listen(iface, new RemoteDisplay.Listener() {
    740                 @Override
    741                 public void onDisplayConnected(Surface surface,
    742                         int width, int height, int flags, int session) {
    743                     if (mConnectedDevice == oldDevice && !mRemoteDisplayConnected) {
    744                         Slog.i(TAG, "Opened RTSP connection with Wifi display: "
    745                                 + mConnectedDevice.deviceName);
    746                         mRemoteDisplayConnected = true;
    747                         mHandler.removeCallbacks(mRtspTimeout);
    748 
    749                         if (mWifiDisplayCertMode) {
    750                             mListener.onDisplaySessionInfo(
    751                                     getSessionInfo(mConnectedDeviceGroupInfo, session));
    752                         }
    753 
    754                         final WifiDisplay display = createWifiDisplay(mConnectedDevice);
    755                         advertiseDisplay(display, surface, width, height, flags);
    756                     }
    757                 }
    758 
    759                 @Override
    760                 public void onDisplayDisconnected() {
    761                     if (mConnectedDevice == oldDevice) {
    762                         Slog.i(TAG, "Closed RTSP connection with Wifi display: "
    763                                 + mConnectedDevice.deviceName);
    764                         mHandler.removeCallbacks(mRtspTimeout);
    765                         disconnect();
    766                     }
    767                 }
    768 
    769                 @Override
    770                 public void onDisplayError(int error) {
    771                     if (mConnectedDevice == oldDevice) {
    772                         Slog.i(TAG, "Lost RTSP connection with Wifi display due to error "
    773                                 + error + ": " + mConnectedDevice.deviceName);
    774                         mHandler.removeCallbacks(mRtspTimeout);
    775                         handleConnectionFailure(false);
    776                     }
    777                 }
    778             }, mHandler);
    779 
    780             // Use extended timeout value for certification, as some tests require user inputs
    781             int rtspTimeout = mWifiDisplayCertMode ?
    782                     RTSP_TIMEOUT_SECONDS_CERT_MODE : RTSP_TIMEOUT_SECONDS;
    783 
    784             mHandler.postDelayed(mRtspTimeout, rtspTimeout * 1000);
    785         }
    786     }
    787 
    788     private WifiDisplaySessionInfo getSessionInfo(WifiP2pGroup info, int session) {
    789         if (info == null) {
    790             return null;
    791         }
    792         Inet4Address addr = getInterfaceAddress(info);
    793         WifiDisplaySessionInfo sessionInfo = new WifiDisplaySessionInfo(
    794                 !info.getOwner().deviceAddress.equals(mThisDevice.deviceAddress),
    795                 session,
    796                 info.getOwner().deviceAddress + " " + info.getNetworkName(),
    797                 info.getPassphrase(),
    798                 (addr != null) ? addr.getHostAddress() : "");
    799         if (DEBUG) {
    800             Slog.d(TAG, sessionInfo.toString());
    801         }
    802         return sessionInfo;
    803     }
    804 
    805     private void handleStateChanged(boolean enabled) {
    806         mWifiP2pEnabled = enabled;
    807         updateWfdEnableState();
    808     }
    809 
    810     private void handlePeersChanged() {
    811         // Even if wfd is disabled, it is best to get the latest set of peers to
    812         // keep in sync with the p2p framework
    813         requestPeers();
    814     }
    815 
    816     private void handleConnectionChanged(NetworkInfo networkInfo) {
    817         mNetworkInfo = networkInfo;
    818         if (mWfdEnabled && networkInfo.isConnected()) {
    819             if (mDesiredDevice != null || mWifiDisplayCertMode) {
    820                 mWifiP2pManager.requestGroupInfo(mWifiP2pChannel, new GroupInfoListener() {
    821                     @Override
    822                     public void onGroupInfoAvailable(WifiP2pGroup info) {
    823                         if (DEBUG) {
    824                             Slog.d(TAG, "Received group info: " + describeWifiP2pGroup(info));
    825                         }
    826 
    827                         if (mConnectingDevice != null && !info.contains(mConnectingDevice)) {
    828                             Slog.i(TAG, "Aborting connection to Wifi display because "
    829                                     + "the current P2P group does not contain the device "
    830                                     + "we expected to find: " + mConnectingDevice.deviceName
    831                                     + ", group info was: " + describeWifiP2pGroup(info));
    832                             handleConnectionFailure(false);
    833                             return;
    834                         }
    835 
    836                         if (mDesiredDevice != null && !info.contains(mDesiredDevice)) {
    837                             disconnect();
    838                             return;
    839                         }
    840 
    841                         if (mWifiDisplayCertMode) {
    842                             boolean owner = info.getOwner().deviceAddress
    843                                     .equals(mThisDevice.deviceAddress);
    844                             if (owner && info.getClientList().isEmpty()) {
    845                                 // this is the case when we started Autonomous GO,
    846                                 // and no client has connected, save group info
    847                                 // and updateConnection()
    848                                 mConnectingDevice = mDesiredDevice = null;
    849                                 mConnectedDeviceGroupInfo = info;
    850                                 updateConnection();
    851                             } else if (mConnectingDevice == null && mDesiredDevice == null) {
    852                                 // this is the case when we received an incoming connection
    853                                 // from the sink, update both mConnectingDevice and mDesiredDevice
    854                                 // then proceed to updateConnection() below
    855                                 mConnectingDevice = mDesiredDevice = owner ?
    856                                         info.getClientList().iterator().next() : info.getOwner();
    857                             }
    858                         }
    859 
    860                         if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) {
    861                             Slog.i(TAG, "Connected to Wifi display: "
    862                                     + mConnectingDevice.deviceName);
    863 
    864                             mHandler.removeCallbacks(mConnectionTimeout);
    865                             mConnectedDeviceGroupInfo = info;
    866                             mConnectedDevice = mConnectingDevice;
    867                             mConnectingDevice = null;
    868                             updateConnection();
    869                         }
    870                     }
    871                 });
    872             }
    873         } else {
    874             mConnectedDeviceGroupInfo = null;
    875 
    876             // Disconnect if we lost the network while connecting or connected to a display.
    877             if (mConnectingDevice != null || mConnectedDevice != null) {
    878                 disconnect();
    879             }
    880 
    881             // After disconnection for a group, for some reason we have a tendency
    882             // to get a peer change notification with an empty list of peers.
    883             // Perform a fresh scan.
    884             if (mWfdEnabled) {
    885                 requestPeers();
    886             }
    887         }
    888     }
    889 
    890     private final Runnable mDiscoverPeers = new Runnable() {
    891         @Override
    892         public void run() {
    893             tryDiscoverPeers();
    894         }
    895     };
    896 
    897     private final Runnable mConnectionTimeout = new Runnable() {
    898         @Override
    899         public void run() {
    900             if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) {
    901                 Slog.i(TAG, "Timed out waiting for Wifi display connection after "
    902                         + CONNECTION_TIMEOUT_SECONDS + " seconds: "
    903                         + mConnectingDevice.deviceName);
    904                 handleConnectionFailure(true);
    905             }
    906         }
    907     };
    908 
    909     private final Runnable mRtspTimeout = new Runnable() {
    910         @Override
    911         public void run() {
    912             if (mConnectedDevice != null
    913                     && mRemoteDisplay != null && !mRemoteDisplayConnected) {
    914                 Slog.i(TAG, "Timed out waiting for Wifi display RTSP connection after "
    915                         + RTSP_TIMEOUT_SECONDS + " seconds: "
    916                         + mConnectedDevice.deviceName);
    917                 handleConnectionFailure(true);
    918             }
    919         }
    920     };
    921 
    922     private void handleConnectionFailure(boolean timeoutOccurred) {
    923         Slog.i(TAG, "Wifi display connection failed!");
    924 
    925         if (mDesiredDevice != null) {
    926             if (mConnectionRetriesLeft > 0) {
    927                 final WifiP2pDevice oldDevice = mDesiredDevice;
    928                 mHandler.postDelayed(new Runnable() {
    929                     @Override
    930                     public void run() {
    931                         if (mDesiredDevice == oldDevice && mConnectionRetriesLeft > 0) {
    932                             mConnectionRetriesLeft -= 1;
    933                             Slog.i(TAG, "Retrying Wifi display connection.  Retries left: "
    934                                     + mConnectionRetriesLeft);
    935                             retryConnection();
    936                         }
    937                     }
    938                 }, timeoutOccurred ? 0 : CONNECT_RETRY_DELAY_MILLIS);
    939             } else {
    940                 disconnect();
    941             }
    942         }
    943     }
    944 
    945     private void advertiseDisplay(final WifiDisplay display,
    946             final Surface surface, final int width, final int height, final int flags) {
    947         if (!Objects.equal(mAdvertisedDisplay, display)
    948                 || mAdvertisedDisplaySurface != surface
    949                 || mAdvertisedDisplayWidth != width
    950                 || mAdvertisedDisplayHeight != height
    951                 || mAdvertisedDisplayFlags != flags) {
    952             final WifiDisplay oldDisplay = mAdvertisedDisplay;
    953             final Surface oldSurface = mAdvertisedDisplaySurface;
    954 
    955             mAdvertisedDisplay = display;
    956             mAdvertisedDisplaySurface = surface;
    957             mAdvertisedDisplayWidth = width;
    958             mAdvertisedDisplayHeight = height;
    959             mAdvertisedDisplayFlags = flags;
    960 
    961             mHandler.post(new Runnable() {
    962                 @Override
    963                 public void run() {
    964                     if (oldSurface != null && surface != oldSurface) {
    965                         mListener.onDisplayDisconnected();
    966                     } else if (oldDisplay != null && !oldDisplay.hasSameAddress(display)) {
    967                         mListener.onDisplayConnectionFailed();
    968                     }
    969 
    970                     if (display != null) {
    971                         if (!display.hasSameAddress(oldDisplay)) {
    972                             mListener.onDisplayConnecting(display);
    973                         } else if (!display.equals(oldDisplay)) {
    974                             // The address is the same but some other property such as the
    975                             // name must have changed.
    976                             mListener.onDisplayChanged(display);
    977                         }
    978                         if (surface != null && surface != oldSurface) {
    979                             mListener.onDisplayConnected(display, surface, width, height, flags);
    980                         }
    981                     }
    982                 }
    983             });
    984         }
    985     }
    986 
    987     private void unadvertiseDisplay() {
    988         advertiseDisplay(null, null, 0, 0, 0);
    989     }
    990 
    991     private void readvertiseDisplay(WifiDisplay display) {
    992         advertiseDisplay(display, mAdvertisedDisplaySurface,
    993                 mAdvertisedDisplayWidth, mAdvertisedDisplayHeight,
    994                 mAdvertisedDisplayFlags);
    995     }
    996 
    997     private static Inet4Address getInterfaceAddress(WifiP2pGroup info) {
    998         NetworkInterface iface;
    999         try {
   1000             iface = NetworkInterface.getByName(info.getInterface());
   1001         } catch (SocketException ex) {
   1002             Slog.w(TAG, "Could not obtain address of network interface "
   1003                     + info.getInterface(), ex);
   1004             return null;
   1005         }
   1006 
   1007         Enumeration<InetAddress> addrs = iface.getInetAddresses();
   1008         while (addrs.hasMoreElements()) {
   1009             InetAddress addr = addrs.nextElement();
   1010             if (addr instanceof Inet4Address) {
   1011                 return (Inet4Address)addr;
   1012             }
   1013         }
   1014 
   1015         Slog.w(TAG, "Could not obtain address of network interface "
   1016                 + info.getInterface() + " because it had no IPv4 addresses.");
   1017         return null;
   1018     }
   1019 
   1020     private static int getPortNumber(WifiP2pDevice device) {
   1021         if (device.deviceName.startsWith("DIRECT-")
   1022                 && device.deviceName.endsWith("Broadcom")) {
   1023             // These dongles ignore the port we broadcast in our WFD IE.
   1024             return 8554;
   1025         }
   1026         return DEFAULT_CONTROL_PORT;
   1027     }
   1028 
   1029     private static boolean isWifiDisplay(WifiP2pDevice device) {
   1030         return device.wfdInfo != null
   1031                 && device.wfdInfo.isWfdEnabled()
   1032                 && isPrimarySinkDeviceType(device.wfdInfo.getDeviceType());
   1033     }
   1034 
   1035     private static boolean isPrimarySinkDeviceType(int deviceType) {
   1036         return deviceType == WifiP2pWfdInfo.PRIMARY_SINK
   1037                 || deviceType == WifiP2pWfdInfo.SOURCE_OR_PRIMARY_SINK;
   1038     }
   1039 
   1040     private static String describeWifiP2pDevice(WifiP2pDevice device) {
   1041         return device != null ? device.toString().replace('\n', ',') : "null";
   1042     }
   1043 
   1044     private static String describeWifiP2pGroup(WifiP2pGroup group) {
   1045         return group != null ? group.toString().replace('\n', ',') : "null";
   1046     }
   1047 
   1048     private static WifiDisplay createWifiDisplay(WifiP2pDevice device) {
   1049         return new WifiDisplay(device.deviceAddress, device.deviceName, null,
   1050                 true, device.wfdInfo.isSessionAvailable(), false);
   1051     }
   1052 
   1053     private final BroadcastReceiver mWifiP2pReceiver = new BroadcastReceiver() {
   1054         @Override
   1055         public void onReceive(Context context, Intent intent) {
   1056             final String action = intent.getAction();
   1057             if (action.equals(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)) {
   1058                 // This broadcast is sticky so we'll always get the initial Wifi P2P state
   1059                 // on startup.
   1060                 boolean enabled = (intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE,
   1061                         WifiP2pManager.WIFI_P2P_STATE_DISABLED)) ==
   1062                         WifiP2pManager.WIFI_P2P_STATE_ENABLED;
   1063                 if (DEBUG) {
   1064                     Slog.d(TAG, "Received WIFI_P2P_STATE_CHANGED_ACTION: enabled="
   1065                             + enabled);
   1066                 }
   1067 
   1068                 handleStateChanged(enabled);
   1069             } else if (action.equals(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)) {
   1070                 if (DEBUG) {
   1071                     Slog.d(TAG, "Received WIFI_P2P_PEERS_CHANGED_ACTION.");
   1072                 }
   1073 
   1074                 handlePeersChanged();
   1075             } else if (action.equals(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)) {
   1076                 NetworkInfo networkInfo = (NetworkInfo)intent.getParcelableExtra(
   1077                         WifiP2pManager.EXTRA_NETWORK_INFO);
   1078                 if (DEBUG) {
   1079                     Slog.d(TAG, "Received WIFI_P2P_CONNECTION_CHANGED_ACTION: networkInfo="
   1080                             + networkInfo);
   1081                 }
   1082 
   1083                 handleConnectionChanged(networkInfo);
   1084             } else if (action.equals(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION)) {
   1085                 mThisDevice = (WifiP2pDevice) intent.getParcelableExtra(
   1086                         WifiP2pManager.EXTRA_WIFI_P2P_DEVICE);
   1087                 if (DEBUG) {
   1088                     Slog.d(TAG, "Received WIFI_P2P_THIS_DEVICE_CHANGED_ACTION: mThisDevice= "
   1089                             + mThisDevice);
   1090                 }
   1091             }
   1092         }
   1093     };
   1094 
   1095     /**
   1096      * Called on the handler thread when displays are connected or disconnected.
   1097      */
   1098     public interface Listener {
   1099         void onFeatureStateChanged(int featureState);
   1100 
   1101         void onScanStarted();
   1102         void onScanResults(WifiDisplay[] availableDisplays);
   1103         void onScanFinished();
   1104 
   1105         void onDisplayConnecting(WifiDisplay display);
   1106         void onDisplayConnectionFailed();
   1107         void onDisplayChanged(WifiDisplay display);
   1108         void onDisplayConnected(WifiDisplay display,
   1109                 Surface surface, int width, int height, int flags);
   1110         void onDisplaySessionInfo(WifiDisplaySessionInfo sessionInfo);
   1111         void onDisplayDisconnected();
   1112     }
   1113 }
   1114