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