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