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 
WifiDisplayController(Context context, Handler handler, Listener listener)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 
updateSettings()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
dump(PrintWriter pw, String prefix)212     public void dump(PrintWriter pw, String prefix) {
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 
requestStartScan()241     public void requestStartScan() {
242         if (!mScanRequested) {
243             mScanRequested = true;
244             updateScanState();
245         }
246     }
247 
requestStopScan()248     public void requestStopScan() {
249         if (mScanRequested) {
250             mScanRequested = false;
251             updateScanState();
252         }
253     }
254 
requestConnect(String address)255     public void requestConnect(String address) {
256         for (WifiP2pDevice device : mAvailableWifiDisplayPeers) {
257             if (device.deviceAddress.equals(address)) {
258                 connect(device);
259             }
260         }
261     }
262 
requestPause()263     public void requestPause() {
264         if (mRemoteDisplay != null) {
265             mRemoteDisplay.pause();
266         }
267     }
268 
requestResume()269     public void requestResume() {
270         if (mRemoteDisplay != null) {
271             mRemoteDisplay.resume();
272         }
273     }
274 
requestDisconnect()275     public void requestDisconnect() {
276         disconnect();
277     }
278 
updateWfdEnableState()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 
reportFeatureState()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 
computeFeatureState()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 
updateScanState()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 
tryDiscoverPeers()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 
stopPeerDiscovery()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 
requestPeers()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 
handleScanStarted()459     private void handleScanStarted() {
460         mHandler.post(new Runnable() {
461             @Override
462             public void run() {
463                 mListener.onScanStarted();
464             }
465         });
466     }
467 
handleScanResults()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 
handleScanFinished()485     private void handleScanFinished() {
486         mHandler.post(new Runnable() {
487             @Override
488             public void run() {
489                 mListener.onScanFinished();
490             }
491         });
492     }
493 
updateDesiredDevice(WifiP2pDevice device)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 
connect(final WifiP2pDevice device)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 
disconnect()543     private void disconnect() {
544         mDesiredDevice = null;
545         updateConnection();
546     }
547 
retryConnection()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      */
updateConnection()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, mContext.getOpPackageName());
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 
getSessionInfo(WifiP2pGroup info, int session)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 
handleStateChanged(boolean enabled)805     private void handleStateChanged(boolean enabled) {
806         mWifiP2pEnabled = enabled;
807         updateWfdEnableState();
808     }
809 
handlePeersChanged()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 
handleConnectionChanged(NetworkInfo networkInfo)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 
handleConnectionFailure(boolean timeoutOccurred)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 
advertiseDisplay(final WifiDisplay display, final Surface surface, final int width, final int height, final int flags)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 
unadvertiseDisplay()987     private void unadvertiseDisplay() {
988         advertiseDisplay(null, null, 0, 0, 0);
989     }
990 
readvertiseDisplay(WifiDisplay display)991     private void readvertiseDisplay(WifiDisplay display) {
992         advertiseDisplay(display, mAdvertisedDisplaySurface,
993                 mAdvertisedDisplayWidth, mAdvertisedDisplayHeight,
994                 mAdvertisedDisplayFlags);
995     }
996 
getInterfaceAddress(WifiP2pGroup info)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 
getPortNumber(WifiP2pDevice device)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 
isWifiDisplay(WifiP2pDevice device)1029     private static boolean isWifiDisplay(WifiP2pDevice device) {
1030         return device.wfdInfo != null
1031                 && device.wfdInfo.isWfdEnabled()
1032                 && isPrimarySinkDeviceType(device.wfdInfo.getDeviceType());
1033     }
1034 
isPrimarySinkDeviceType(int deviceType)1035     private static boolean isPrimarySinkDeviceType(int deviceType) {
1036         return deviceType == WifiP2pWfdInfo.PRIMARY_SINK
1037                 || deviceType == WifiP2pWfdInfo.SOURCE_OR_PRIMARY_SINK;
1038     }
1039 
describeWifiP2pDevice(WifiP2pDevice device)1040     private static String describeWifiP2pDevice(WifiP2pDevice device) {
1041         return device != null ? device.toString().replace('\n', ',') : "null";
1042     }
1043 
describeWifiP2pGroup(WifiP2pGroup group)1044     private static String describeWifiP2pGroup(WifiP2pGroup group) {
1045         return group != null ? group.toString().replace('\n', ',') : "null";
1046     }
1047 
createWifiDisplay(WifiP2pDevice device)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 {
onFeatureStateChanged(int featureState)1099         void onFeatureStateChanged(int featureState);
1100 
onScanStarted()1101         void onScanStarted();
onScanResults(WifiDisplay[] availableDisplays)1102         void onScanResults(WifiDisplay[] availableDisplays);
onScanFinished()1103         void onScanFinished();
1104 
onDisplayConnecting(WifiDisplay display)1105         void onDisplayConnecting(WifiDisplay display);
onDisplayConnectionFailed()1106         void onDisplayConnectionFailed();
onDisplayChanged(WifiDisplay display)1107         void onDisplayChanged(WifiDisplay display);
onDisplayConnected(WifiDisplay display, Surface surface, int width, int height, int flags)1108         void onDisplayConnected(WifiDisplay display,
1109                 Surface surface, int width, int height, int flags);
onDisplaySessionInfo(WifiDisplaySessionInfo sessionInfo)1110         void onDisplaySessionInfo(WifiDisplaySessionInfo sessionInfo);
onDisplayDisconnected()1111         void onDisplayDisconnected();
1112     }
1113 }
1114