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