• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.WifiDisplayStatus;
29 import android.media.AudioManager;
30 import android.media.RemoteDisplay;
31 import android.net.NetworkInfo;
32 import android.net.Uri;
33 import android.net.wifi.WpsInfo;
34 import android.net.wifi.p2p.WifiP2pConfig;
35 import android.net.wifi.p2p.WifiP2pDevice;
36 import android.net.wifi.p2p.WifiP2pDeviceList;
37 import android.net.wifi.p2p.WifiP2pGroup;
38 import android.net.wifi.p2p.WifiP2pManager;
39 import android.net.wifi.p2p.WifiP2pWfdInfo;
40 import android.net.wifi.p2p.WifiP2pManager.ActionListener;
41 import android.net.wifi.p2p.WifiP2pManager.Channel;
42 import android.net.wifi.p2p.WifiP2pManager.GroupInfoListener;
43 import android.net.wifi.p2p.WifiP2pManager.PeerListListener;
44 import android.os.Handler;
45 import android.provider.Settings;
46 import android.util.Slog;
47 import android.view.Surface;
48 
49 import java.io.PrintWriter;
50 import java.net.Inet4Address;
51 import java.net.InetAddress;
52 import java.net.NetworkInterface;
53 import java.net.SocketException;
54 import java.util.ArrayList;
55 import java.util.Enumeration;
56 
57 import libcore.util.Objects;
58 
59 /**
60  * Manages all of the various asynchronous interactions with the {@link WifiP2pManager}
61  * on behalf of {@link WifiDisplayAdapter}.
62  * <p>
63  * This code is isolated from {@link WifiDisplayAdapter} so that we can avoid
64  * accidentally introducing any deadlocks due to the display manager calling
65  * outside of itself while holding its lock.  It's also way easier to write this
66  * asynchronous code if we can assume that it is single-threaded.
67  * </p><p>
68  * The controller must be instantiated on the handler thread.
69  * </p>
70  */
71 final class WifiDisplayController implements DumpUtils.Dump {
72     private static final String TAG = "WifiDisplayController";
73     private static final boolean DEBUG = false;
74 
75     private static final int DEFAULT_CONTROL_PORT = 7236;
76     private static final int MAX_THROUGHPUT = 50;
77     private static final int CONNECTION_TIMEOUT_SECONDS = 60;
78     private static final int RTSP_TIMEOUT_SECONDS = 15;
79 
80     private static final int DISCOVER_PEERS_MAX_RETRIES = 10;
81     private static final int DISCOVER_PEERS_RETRY_DELAY_MILLIS = 500;
82 
83     private static final int CONNECT_MAX_RETRIES = 3;
84     private static final int CONNECT_RETRY_DELAY_MILLIS = 500;
85 
86     // A unique token to identify the remote submix that is managed by Wifi display.
87     // It must match what the media server uses when it starts recording the submix
88     // for transmission.  We use 0 although the actual value is currently ignored.
89     private static final int REMOTE_SUBMIX_ADDRESS = 0;
90 
91     private final Context mContext;
92     private final Handler mHandler;
93     private final Listener mListener;
94 
95     private final WifiP2pManager mWifiP2pManager;
96     private final Channel mWifiP2pChannel;
97 
98     private final AudioManager mAudioManager;
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 there is a call to discoverPeers in progress.
112     private boolean mDiscoverPeersInProgress;
113 
114     // Number of discover peers retries remaining.
115     private int mDiscoverPeersRetriesLeft;
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     // True if the remote submix is enabled.
150     private boolean mRemoteSubmixOn;
151 
152     // The information we have most recently told WifiDisplayAdapter about.
153     private WifiDisplay mAdvertisedDisplay;
154     private Surface mAdvertisedDisplaySurface;
155     private int mAdvertisedDisplayWidth;
156     private int mAdvertisedDisplayHeight;
157     private int mAdvertisedDisplayFlags;
158 
WifiDisplayController(Context context, Handler handler, Listener listener)159     public WifiDisplayController(Context context, Handler handler, Listener listener) {
160         mContext = context;
161         mHandler = handler;
162         mListener = listener;
163 
164         mWifiP2pManager = (WifiP2pManager)context.getSystemService(Context.WIFI_P2P_SERVICE);
165         mWifiP2pChannel = mWifiP2pManager.initialize(context, handler.getLooper(), null);
166 
167         mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
168 
169         IntentFilter intentFilter = new IntentFilter();
170         intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
171         intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
172         intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
173         context.registerReceiver(mWifiP2pReceiver, intentFilter, null, mHandler);
174 
175         ContentObserver settingsObserver = new ContentObserver(mHandler) {
176             @Override
177             public void onChange(boolean selfChange, Uri uri) {
178                 updateSettings();
179             }
180         };
181 
182         final ContentResolver resolver = mContext.getContentResolver();
183         resolver.registerContentObserver(Settings.Global.getUriFor(
184                 Settings.Global.WIFI_DISPLAY_ON), false, settingsObserver);
185         updateSettings();
186     }
187 
updateSettings()188     private void updateSettings() {
189         final ContentResolver resolver = mContext.getContentResolver();
190         mWifiDisplayOnSetting = Settings.Global.getInt(resolver,
191                 Settings.Global.WIFI_DISPLAY_ON, 0) != 0;
192 
193         updateWfdEnableState();
194     }
195 
196     @Override
dump(PrintWriter pw)197     public void dump(PrintWriter pw) {
198         pw.println("mWifiDisplayOnSetting=" + mWifiDisplayOnSetting);
199         pw.println("mWifiP2pEnabled=" + mWifiP2pEnabled);
200         pw.println("mWfdEnabled=" + mWfdEnabled);
201         pw.println("mWfdEnabling=" + mWfdEnabling);
202         pw.println("mNetworkInfo=" + mNetworkInfo);
203         pw.println("mDiscoverPeersInProgress=" + mDiscoverPeersInProgress);
204         pw.println("mDiscoverPeersRetriesLeft=" + mDiscoverPeersRetriesLeft);
205         pw.println("mDesiredDevice=" + describeWifiP2pDevice(mDesiredDevice));
206         pw.println("mConnectingDisplay=" + describeWifiP2pDevice(mConnectingDevice));
207         pw.println("mDisconnectingDisplay=" + describeWifiP2pDevice(mDisconnectingDevice));
208         pw.println("mCancelingDisplay=" + describeWifiP2pDevice(mCancelingDevice));
209         pw.println("mConnectedDevice=" + describeWifiP2pDevice(mConnectedDevice));
210         pw.println("mConnectionRetriesLeft=" + mConnectionRetriesLeft);
211         pw.println("mRemoteDisplay=" + mRemoteDisplay);
212         pw.println("mRemoteDisplayInterface=" + mRemoteDisplayInterface);
213         pw.println("mRemoteDisplayConnected=" + mRemoteDisplayConnected);
214         pw.println("mRemoteSubmixOn=" + mRemoteSubmixOn);
215         pw.println("mAdvertisedDisplay=" + mAdvertisedDisplay);
216         pw.println("mAdvertisedDisplaySurface=" + mAdvertisedDisplaySurface);
217         pw.println("mAdvertisedDisplayWidth=" + mAdvertisedDisplayWidth);
218         pw.println("mAdvertisedDisplayHeight=" + mAdvertisedDisplayHeight);
219         pw.println("mAdvertisedDisplayFlags=" + mAdvertisedDisplayFlags);
220 
221         pw.println("mAvailableWifiDisplayPeers: size=" + mAvailableWifiDisplayPeers.size());
222         for (WifiP2pDevice device : mAvailableWifiDisplayPeers) {
223             pw.println("  " + describeWifiP2pDevice(device));
224         }
225     }
226 
requestScan()227     public void requestScan() {
228         discoverPeers();
229     }
230 
requestConnect(String address)231     public void requestConnect(String address) {
232         for (WifiP2pDevice device : mAvailableWifiDisplayPeers) {
233             if (device.deviceAddress.equals(address)) {
234                 connect(device);
235             }
236         }
237     }
238 
requestDisconnect()239     public void requestDisconnect() {
240         disconnect();
241     }
242 
updateWfdEnableState()243     private void updateWfdEnableState() {
244         if (mWifiDisplayOnSetting && mWifiP2pEnabled) {
245             // WFD should be enabled.
246             if (!mWfdEnabled && !mWfdEnabling) {
247                 mWfdEnabling = true;
248 
249                 WifiP2pWfdInfo wfdInfo = new WifiP2pWfdInfo();
250                 wfdInfo.setWfdEnabled(true);
251                 wfdInfo.setDeviceType(WifiP2pWfdInfo.WFD_SOURCE);
252                 wfdInfo.setSessionAvailable(true);
253                 wfdInfo.setControlPort(DEFAULT_CONTROL_PORT);
254                 wfdInfo.setMaxThroughput(MAX_THROUGHPUT);
255                 mWifiP2pManager.setWFDInfo(mWifiP2pChannel, wfdInfo, new ActionListener() {
256                     @Override
257                     public void onSuccess() {
258                         if (DEBUG) {
259                             Slog.d(TAG, "Successfully set WFD info.");
260                         }
261                         if (mWfdEnabling) {
262                             mWfdEnabling = false;
263                             mWfdEnabled = true;
264                             reportFeatureState();
265                         }
266                     }
267 
268                     @Override
269                     public void onFailure(int reason) {
270                         if (DEBUG) {
271                             Slog.d(TAG, "Failed to set WFD info with reason " + reason + ".");
272                         }
273                         mWfdEnabling = false;
274                     }
275                 });
276             }
277         } else {
278             // WFD should be disabled.
279             mWfdEnabling = false;
280             mWfdEnabled = false;
281             reportFeatureState();
282             disconnect();
283         }
284     }
285 
reportFeatureState()286     private void reportFeatureState() {
287         final int featureState = computeFeatureState();
288         mHandler.post(new Runnable() {
289             @Override
290             public void run() {
291                 mListener.onFeatureStateChanged(featureState);
292             }
293         });
294     }
295 
computeFeatureState()296     private int computeFeatureState() {
297         if (!mWifiP2pEnabled) {
298             return WifiDisplayStatus.FEATURE_STATE_DISABLED;
299         }
300         return mWifiDisplayOnSetting ? WifiDisplayStatus.FEATURE_STATE_ON :
301                 WifiDisplayStatus.FEATURE_STATE_OFF;
302     }
303 
discoverPeers()304     private void discoverPeers() {
305         if (!mDiscoverPeersInProgress) {
306             mDiscoverPeersInProgress = true;
307             mDiscoverPeersRetriesLeft = DISCOVER_PEERS_MAX_RETRIES;
308             handleScanStarted();
309             tryDiscoverPeers();
310         }
311     }
312 
tryDiscoverPeers()313     private void tryDiscoverPeers() {
314         mWifiP2pManager.discoverPeers(mWifiP2pChannel, new ActionListener() {
315             @Override
316             public void onSuccess() {
317                 if (DEBUG) {
318                     Slog.d(TAG, "Discover peers succeeded.  Requesting peers now.");
319                 }
320 
321                 mDiscoverPeersInProgress = false;
322                 requestPeers();
323             }
324 
325             @Override
326             public void onFailure(int reason) {
327                 if (DEBUG) {
328                     Slog.d(TAG, "Discover peers failed with reason " + reason + ".");
329                 }
330 
331                 if (mDiscoverPeersInProgress) {
332                     if (reason == 0 && mDiscoverPeersRetriesLeft > 0 && mWfdEnabled) {
333                         mHandler.postDelayed(new Runnable() {
334                             @Override
335                             public void run() {
336                                 if (mDiscoverPeersInProgress) {
337                                     if (mDiscoverPeersRetriesLeft > 0 && mWfdEnabled) {
338                                         mDiscoverPeersRetriesLeft -= 1;
339                                         if (DEBUG) {
340                                             Slog.d(TAG, "Retrying discovery.  Retries left: "
341                                                     + mDiscoverPeersRetriesLeft);
342                                         }
343                                         tryDiscoverPeers();
344                                     } else {
345                                         handleScanFinished();
346                                         mDiscoverPeersInProgress = false;
347                                     }
348                                 }
349                             }
350                         }, DISCOVER_PEERS_RETRY_DELAY_MILLIS);
351                     } else {
352                         handleScanFinished();
353                         mDiscoverPeersInProgress = false;
354                     }
355                 }
356             }
357         });
358     }
359 
requestPeers()360     private void requestPeers() {
361         mWifiP2pManager.requestPeers(mWifiP2pChannel, new PeerListListener() {
362             @Override
363             public void onPeersAvailable(WifiP2pDeviceList peers) {
364                 if (DEBUG) {
365                     Slog.d(TAG, "Received list of peers.");
366                 }
367 
368                 mAvailableWifiDisplayPeers.clear();
369                 for (WifiP2pDevice device : peers.getDeviceList()) {
370                     if (DEBUG) {
371                         Slog.d(TAG, "  " + describeWifiP2pDevice(device));
372                     }
373 
374                     if (isWifiDisplay(device)) {
375                         mAvailableWifiDisplayPeers.add(device);
376                     }
377                 }
378 
379                 handleScanFinished();
380             }
381         });
382     }
383 
handleScanStarted()384     private void handleScanStarted() {
385         mHandler.post(new Runnable() {
386             @Override
387             public void run() {
388                 mListener.onScanStarted();
389             }
390         });
391     }
392 
handleScanFinished()393     private void handleScanFinished() {
394         final int count = mAvailableWifiDisplayPeers.size();
395         final WifiDisplay[] displays = WifiDisplay.CREATOR.newArray(count);
396         for (int i = 0; i < count; i++) {
397             WifiP2pDevice device = mAvailableWifiDisplayPeers.get(i);
398             displays[i] = createWifiDisplay(device);
399             updateDesiredDevice(device);
400         }
401 
402         mHandler.post(new Runnable() {
403             @Override
404             public void run() {
405                 mListener.onScanFinished(displays);
406             }
407         });
408     }
409 
updateDesiredDevice(WifiP2pDevice device)410     private void updateDesiredDevice(WifiP2pDevice device) {
411         // Handle the case where the device to which we are connecting or connected
412         // may have been renamed or reported different properties in the latest scan.
413         final String address = device.deviceAddress;
414         if (mDesiredDevice != null && mDesiredDevice.deviceAddress.equals(address)) {
415             if (DEBUG) {
416                 Slog.d(TAG, "updateDesiredDevice: new information "
417                         + describeWifiP2pDevice(device));
418             }
419             mDesiredDevice.update(device);
420             if (mAdvertisedDisplay != null
421                     && mAdvertisedDisplay.getDeviceAddress().equals(address)) {
422                 readvertiseDisplay(createWifiDisplay(mDesiredDevice));
423             }
424         }
425     }
426 
connect(final WifiP2pDevice device)427     private void connect(final WifiP2pDevice device) {
428         if (mDesiredDevice != null
429                 && !mDesiredDevice.deviceAddress.equals(device.deviceAddress)) {
430             if (DEBUG) {
431                 Slog.d(TAG, "connect: nothing to do, already connecting to "
432                         + describeWifiP2pDevice(device));
433             }
434             return;
435         }
436 
437         if (mConnectedDevice != null
438                 && !mConnectedDevice.deviceAddress.equals(device.deviceAddress)
439                 && mDesiredDevice == null) {
440             if (DEBUG) {
441                 Slog.d(TAG, "connect: nothing to do, already connected to "
442                         + describeWifiP2pDevice(device) + " and not part way through "
443                         + "connecting to a different device.");
444             }
445             return;
446         }
447 
448         mDesiredDevice = device;
449         mConnectionRetriesLeft = CONNECT_MAX_RETRIES;
450         updateConnection();
451     }
452 
disconnect()453     private void disconnect() {
454         mDesiredDevice = null;
455         updateConnection();
456     }
457 
retryConnection()458     private void retryConnection() {
459         // Cheap hack.  Make a new instance of the device object so that we
460         // can distinguish it from the previous connection attempt.
461         // This will cause us to tear everything down before we try again.
462         mDesiredDevice = new WifiP2pDevice(mDesiredDevice);
463         updateConnection();
464     }
465 
466     /**
467      * This function is called repeatedly after each asynchronous operation
468      * until all preconditions for the connection have been satisfied and the
469      * connection is established (or not).
470      */
updateConnection()471     private void updateConnection() {
472         // Step 1. Before we try to connect to a new device, tell the system we
473         // have disconnected from the old one.
474         if (mRemoteDisplay != null && mConnectedDevice != mDesiredDevice) {
475             Slog.i(TAG, "Stopped listening for RTSP connection on " + mRemoteDisplayInterface
476                     + " from Wifi display: " + mConnectedDevice.deviceName);
477 
478             mRemoteDisplay.dispose();
479             mRemoteDisplay = null;
480             mRemoteDisplayInterface = null;
481             mRemoteDisplayConnected = false;
482             mHandler.removeCallbacks(mRtspTimeout);
483 
484             setRemoteSubmixOn(false);
485             unadvertiseDisplay();
486 
487             // continue to next step
488         }
489 
490         // Step 2. Before we try to connect to a new device, disconnect from the old one.
491         if (mDisconnectingDevice != null) {
492             return; // wait for asynchronous callback
493         }
494         if (mConnectedDevice != null && mConnectedDevice != mDesiredDevice) {
495             Slog.i(TAG, "Disconnecting from Wifi display: " + mConnectedDevice.deviceName);
496             mDisconnectingDevice = mConnectedDevice;
497             mConnectedDevice = null;
498 
499             unadvertiseDisplay();
500 
501             final WifiP2pDevice oldDevice = mDisconnectingDevice;
502             mWifiP2pManager.removeGroup(mWifiP2pChannel, new ActionListener() {
503                 @Override
504                 public void onSuccess() {
505                     Slog.i(TAG, "Disconnected from Wifi display: " + oldDevice.deviceName);
506                     next();
507                 }
508 
509                 @Override
510                 public void onFailure(int reason) {
511                     Slog.i(TAG, "Failed to disconnect from Wifi display: "
512                             + oldDevice.deviceName + ", reason=" + reason);
513                     next();
514                 }
515 
516                 private void next() {
517                     if (mDisconnectingDevice == oldDevice) {
518                         mDisconnectingDevice = null;
519                         updateConnection();
520                     }
521                 }
522             });
523             return; // wait for asynchronous callback
524         }
525 
526         // Step 3. Before we try to connect to a new device, stop trying to connect
527         // to the old one.
528         if (mCancelingDevice != null) {
529             return; // wait for asynchronous callback
530         }
531         if (mConnectingDevice != null && mConnectingDevice != mDesiredDevice) {
532             Slog.i(TAG, "Canceling connection to Wifi display: " + mConnectingDevice.deviceName);
533             mCancelingDevice = mConnectingDevice;
534             mConnectingDevice = null;
535 
536             unadvertiseDisplay();
537             mHandler.removeCallbacks(mConnectionTimeout);
538 
539             final WifiP2pDevice oldDevice = mCancelingDevice;
540             mWifiP2pManager.cancelConnect(mWifiP2pChannel, new ActionListener() {
541                 @Override
542                 public void onSuccess() {
543                     Slog.i(TAG, "Canceled connection to Wifi display: " + oldDevice.deviceName);
544                     next();
545                 }
546 
547                 @Override
548                 public void onFailure(int reason) {
549                     Slog.i(TAG, "Failed to cancel connection to Wifi display: "
550                             + oldDevice.deviceName + ", reason=" + reason);
551                     next();
552                 }
553 
554                 private void next() {
555                     if (mCancelingDevice == oldDevice) {
556                         mCancelingDevice = null;
557                         updateConnection();
558                     }
559                 }
560             });
561             return; // wait for asynchronous callback
562         }
563 
564         // Step 4. If we wanted to disconnect, then mission accomplished.
565         if (mDesiredDevice == null) {
566             unadvertiseDisplay();
567             return; // done
568         }
569 
570         // Step 5. Try to connect.
571         if (mConnectedDevice == null && mConnectingDevice == null) {
572             Slog.i(TAG, "Connecting to Wifi display: " + mDesiredDevice.deviceName);
573 
574             mConnectingDevice = mDesiredDevice;
575             WifiP2pConfig config = new WifiP2pConfig();
576             WpsInfo wps = new WpsInfo();
577             if (mConnectingDevice.wpsPbcSupported()) {
578                 wps.setup = WpsInfo.PBC;
579             } else if (mConnectingDevice.wpsDisplaySupported()) {
580                 // We do keypad if peer does display
581                 wps.setup = WpsInfo.KEYPAD;
582             } else {
583                 wps.setup = WpsInfo.DISPLAY;
584             }
585             config.wps = wps;
586             config.deviceAddress = mConnectingDevice.deviceAddress;
587             // Helps with STA & P2P concurrency
588             config.groupOwnerIntent = WifiP2pConfig.MIN_GROUP_OWNER_INTENT;
589 
590             WifiDisplay display = createWifiDisplay(mConnectingDevice);
591             advertiseDisplay(display, null, 0, 0, 0);
592 
593             final WifiP2pDevice newDevice = mDesiredDevice;
594             mWifiP2pManager.connect(mWifiP2pChannel, config, new ActionListener() {
595                 @Override
596                 public void onSuccess() {
597                     // The connection may not yet be established.  We still need to wait
598                     // for WIFI_P2P_CONNECTION_CHANGED_ACTION.  However, we might never
599                     // get that broadcast, so we register a timeout.
600                     Slog.i(TAG, "Initiated connection to Wifi display: " + newDevice.deviceName);
601 
602                     mHandler.postDelayed(mConnectionTimeout, CONNECTION_TIMEOUT_SECONDS * 1000);
603                 }
604 
605                 @Override
606                 public void onFailure(int reason) {
607                     if (mConnectingDevice == newDevice) {
608                         Slog.i(TAG, "Failed to initiate connection to Wifi display: "
609                                 + newDevice.deviceName + ", reason=" + reason);
610                         mConnectingDevice = null;
611                         handleConnectionFailure(false);
612                     }
613                 }
614             });
615             return; // wait for asynchronous callback
616         }
617 
618         // Step 6. Listen for incoming connections.
619         if (mConnectedDevice != null && mRemoteDisplay == null) {
620             Inet4Address addr = getInterfaceAddress(mConnectedDeviceGroupInfo);
621             if (addr == null) {
622                 Slog.i(TAG, "Failed to get local interface address for communicating "
623                         + "with Wifi display: " + mConnectedDevice.deviceName);
624                 handleConnectionFailure(false);
625                 return; // done
626             }
627 
628             setRemoteSubmixOn(true);
629 
630             final WifiP2pDevice oldDevice = mConnectedDevice;
631             final int port = getPortNumber(mConnectedDevice);
632             final String iface = addr.getHostAddress() + ":" + port;
633             mRemoteDisplayInterface = iface;
634 
635             Slog.i(TAG, "Listening for RTSP connection on " + iface
636                     + " from Wifi display: " + mConnectedDevice.deviceName);
637 
638             mRemoteDisplay = RemoteDisplay.listen(iface, new RemoteDisplay.Listener() {
639                 @Override
640                 public void onDisplayConnected(Surface surface,
641                         int width, int height, int flags) {
642                     if (mConnectedDevice == oldDevice && !mRemoteDisplayConnected) {
643                         Slog.i(TAG, "Opened RTSP connection with Wifi display: "
644                                 + mConnectedDevice.deviceName);
645                         mRemoteDisplayConnected = true;
646                         mHandler.removeCallbacks(mRtspTimeout);
647 
648                         final WifiDisplay display = createWifiDisplay(mConnectedDevice);
649                         advertiseDisplay(display, surface, width, height, flags);
650                     }
651                 }
652 
653                 @Override
654                 public void onDisplayDisconnected() {
655                     if (mConnectedDevice == oldDevice) {
656                         Slog.i(TAG, "Closed RTSP connection with Wifi display: "
657                                 + mConnectedDevice.deviceName);
658                         mHandler.removeCallbacks(mRtspTimeout);
659                         disconnect();
660                     }
661                 }
662 
663                 @Override
664                 public void onDisplayError(int error) {
665                     if (mConnectedDevice == oldDevice) {
666                         Slog.i(TAG, "Lost RTSP connection with Wifi display due to error "
667                                 + error + ": " + mConnectedDevice.deviceName);
668                         mHandler.removeCallbacks(mRtspTimeout);
669                         handleConnectionFailure(false);
670                     }
671                 }
672             }, mHandler);
673 
674             mHandler.postDelayed(mRtspTimeout, RTSP_TIMEOUT_SECONDS * 1000);
675         }
676     }
677 
setRemoteSubmixOn(boolean on)678     private void setRemoteSubmixOn(boolean on) {
679         if (mRemoteSubmixOn != on) {
680             mRemoteSubmixOn = on;
681             mAudioManager.setRemoteSubmixOn(on, REMOTE_SUBMIX_ADDRESS);
682         }
683     }
684 
handleStateChanged(boolean enabled)685     private void handleStateChanged(boolean enabled) {
686         mWifiP2pEnabled = enabled;
687         updateWfdEnableState();
688     }
689 
handlePeersChanged()690     private void handlePeersChanged() {
691         // Even if wfd is disabled, it is best to get the latest set of peers to
692         // keep in sync with the p2p framework
693         requestPeers();
694     }
695 
handleConnectionChanged(NetworkInfo networkInfo)696     private void handleConnectionChanged(NetworkInfo networkInfo) {
697         mNetworkInfo = networkInfo;
698         if (mWfdEnabled && networkInfo.isConnected()) {
699             if (mDesiredDevice != null) {
700                 mWifiP2pManager.requestGroupInfo(mWifiP2pChannel, new GroupInfoListener() {
701                     @Override
702                     public void onGroupInfoAvailable(WifiP2pGroup info) {
703                         if (DEBUG) {
704                             Slog.d(TAG, "Received group info: " + describeWifiP2pGroup(info));
705                         }
706 
707                         if (mConnectingDevice != null && !info.contains(mConnectingDevice)) {
708                             Slog.i(TAG, "Aborting connection to Wifi display because "
709                                     + "the current P2P group does not contain the device "
710                                     + "we expected to find: " + mConnectingDevice.deviceName
711                                     + ", group info was: " + describeWifiP2pGroup(info));
712                             handleConnectionFailure(false);
713                             return;
714                         }
715 
716                         if (mDesiredDevice != null && !info.contains(mDesiredDevice)) {
717                             disconnect();
718                             return;
719                         }
720 
721                         if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) {
722                             Slog.i(TAG, "Connected to Wifi display: "
723                                     + mConnectingDevice.deviceName);
724 
725                             mHandler.removeCallbacks(mConnectionTimeout);
726                             mConnectedDeviceGroupInfo = info;
727                             mConnectedDevice = mConnectingDevice;
728                             mConnectingDevice = null;
729                             updateConnection();
730                         }
731                     }
732                 });
733             }
734         } else {
735             disconnect();
736 
737             // After disconnection for a group, for some reason we have a tendency
738             // to get a peer change notification with an empty list of peers.
739             // Perform a fresh scan.
740             if (mWfdEnabled) {
741                 requestPeers();
742             }
743         }
744     }
745 
746     private final Runnable mConnectionTimeout = new Runnable() {
747         @Override
748         public void run() {
749             if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) {
750                 Slog.i(TAG, "Timed out waiting for Wifi display connection after "
751                         + CONNECTION_TIMEOUT_SECONDS + " seconds: "
752                         + mConnectingDevice.deviceName);
753                 handleConnectionFailure(true);
754             }
755         }
756     };
757 
758     private final Runnable mRtspTimeout = new Runnable() {
759         @Override
760         public void run() {
761             if (mConnectedDevice != null
762                     && mRemoteDisplay != null && !mRemoteDisplayConnected) {
763                 Slog.i(TAG, "Timed out waiting for Wifi display RTSP connection after "
764                         + RTSP_TIMEOUT_SECONDS + " seconds: "
765                         + mConnectedDevice.deviceName);
766                 handleConnectionFailure(true);
767             }
768         }
769     };
770 
handleConnectionFailure(boolean timeoutOccurred)771     private void handleConnectionFailure(boolean timeoutOccurred) {
772         Slog.i(TAG, "Wifi display connection failed!");
773 
774         if (mDesiredDevice != null) {
775             if (mConnectionRetriesLeft > 0) {
776                 final WifiP2pDevice oldDevice = mDesiredDevice;
777                 mHandler.postDelayed(new Runnable() {
778                     @Override
779                     public void run() {
780                         if (mDesiredDevice == oldDevice && mConnectionRetriesLeft > 0) {
781                             mConnectionRetriesLeft -= 1;
782                             Slog.i(TAG, "Retrying Wifi display connection.  Retries left: "
783                                     + mConnectionRetriesLeft);
784                             retryConnection();
785                         }
786                     }
787                 }, timeoutOccurred ? 0 : CONNECT_RETRY_DELAY_MILLIS);
788             } else {
789                 disconnect();
790             }
791         }
792     }
793 
advertiseDisplay(final WifiDisplay display, final Surface surface, final int width, final int height, final int flags)794     private void advertiseDisplay(final WifiDisplay display,
795             final Surface surface, final int width, final int height, final int flags) {
796         if (!Objects.equal(mAdvertisedDisplay, display)
797                 || mAdvertisedDisplaySurface != surface
798                 || mAdvertisedDisplayWidth != width
799                 || mAdvertisedDisplayHeight != height
800                 || mAdvertisedDisplayFlags != flags) {
801             final WifiDisplay oldDisplay = mAdvertisedDisplay;
802             final Surface oldSurface = mAdvertisedDisplaySurface;
803 
804             mAdvertisedDisplay = display;
805             mAdvertisedDisplaySurface = surface;
806             mAdvertisedDisplayWidth = width;
807             mAdvertisedDisplayHeight = height;
808             mAdvertisedDisplayFlags = flags;
809 
810             mHandler.post(new Runnable() {
811                 @Override
812                 public void run() {
813                     if (oldSurface != null && surface != oldSurface) {
814                         mListener.onDisplayDisconnected();
815                     } else if (oldDisplay != null && !oldDisplay.hasSameAddress(display)) {
816                         mListener.onDisplayConnectionFailed();
817                     }
818 
819                     if (display != null) {
820                         if (!display.hasSameAddress(oldDisplay)) {
821                             mListener.onDisplayConnecting(display);
822                         } else if (!display.equals(oldDisplay)) {
823                             // The address is the same but some other property such as the
824                             // name must have changed.
825                             mListener.onDisplayChanged(display);
826                         }
827                         if (surface != null && surface != oldSurface) {
828                             mListener.onDisplayConnected(display, surface, width, height, flags);
829                         }
830                     }
831                 }
832             });
833         }
834     }
835 
unadvertiseDisplay()836     private void unadvertiseDisplay() {
837         advertiseDisplay(null, null, 0, 0, 0);
838     }
839 
readvertiseDisplay(WifiDisplay display)840     private void readvertiseDisplay(WifiDisplay display) {
841         advertiseDisplay(display, mAdvertisedDisplaySurface,
842                 mAdvertisedDisplayWidth, mAdvertisedDisplayHeight,
843                 mAdvertisedDisplayFlags);
844     }
845 
getInterfaceAddress(WifiP2pGroup info)846     private static Inet4Address getInterfaceAddress(WifiP2pGroup info) {
847         NetworkInterface iface;
848         try {
849             iface = NetworkInterface.getByName(info.getInterface());
850         } catch (SocketException ex) {
851             Slog.w(TAG, "Could not obtain address of network interface "
852                     + info.getInterface(), ex);
853             return null;
854         }
855 
856         Enumeration<InetAddress> addrs = iface.getInetAddresses();
857         while (addrs.hasMoreElements()) {
858             InetAddress addr = addrs.nextElement();
859             if (addr instanceof Inet4Address) {
860                 return (Inet4Address)addr;
861             }
862         }
863 
864         Slog.w(TAG, "Could not obtain address of network interface "
865                 + info.getInterface() + " because it had no IPv4 addresses.");
866         return null;
867     }
868 
getPortNumber(WifiP2pDevice device)869     private static int getPortNumber(WifiP2pDevice device) {
870         if (device.deviceName.startsWith("DIRECT-")
871                 && device.deviceName.endsWith("Broadcom")) {
872             // These dongles ignore the port we broadcast in our WFD IE.
873             return 8554;
874         }
875         return DEFAULT_CONTROL_PORT;
876     }
877 
isWifiDisplay(WifiP2pDevice device)878     private static boolean isWifiDisplay(WifiP2pDevice device) {
879         return device.wfdInfo != null
880                 && device.wfdInfo.isWfdEnabled()
881                 && isPrimarySinkDeviceType(device.wfdInfo.getDeviceType());
882     }
883 
isPrimarySinkDeviceType(int deviceType)884     private static boolean isPrimarySinkDeviceType(int deviceType) {
885         return deviceType == WifiP2pWfdInfo.PRIMARY_SINK
886                 || deviceType == WifiP2pWfdInfo.SOURCE_OR_PRIMARY_SINK;
887     }
888 
describeWifiP2pDevice(WifiP2pDevice device)889     private static String describeWifiP2pDevice(WifiP2pDevice device) {
890         return device != null ? device.toString().replace('\n', ',') : "null";
891     }
892 
describeWifiP2pGroup(WifiP2pGroup group)893     private static String describeWifiP2pGroup(WifiP2pGroup group) {
894         return group != null ? group.toString().replace('\n', ',') : "null";
895     }
896 
createWifiDisplay(WifiP2pDevice device)897     private static WifiDisplay createWifiDisplay(WifiP2pDevice device) {
898         return new WifiDisplay(device.deviceAddress, device.deviceName, null);
899     }
900 
901     private final BroadcastReceiver mWifiP2pReceiver = new BroadcastReceiver() {
902         @Override
903         public void onReceive(Context context, Intent intent) {
904             final String action = intent.getAction();
905             if (action.equals(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)) {
906                 // This broadcast is sticky so we'll always get the initial Wifi P2P state
907                 // on startup.
908                 boolean enabled = (intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE,
909                         WifiP2pManager.WIFI_P2P_STATE_DISABLED)) ==
910                         WifiP2pManager.WIFI_P2P_STATE_ENABLED;
911                 if (DEBUG) {
912                     Slog.d(TAG, "Received WIFI_P2P_STATE_CHANGED_ACTION: enabled="
913                             + enabled);
914                 }
915 
916                 handleStateChanged(enabled);
917             } else if (action.equals(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)) {
918                 if (DEBUG) {
919                     Slog.d(TAG, "Received WIFI_P2P_PEERS_CHANGED_ACTION.");
920                 }
921 
922                 handlePeersChanged();
923             } else if (action.equals(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)) {
924                 NetworkInfo networkInfo = (NetworkInfo)intent.getParcelableExtra(
925                         WifiP2pManager.EXTRA_NETWORK_INFO);
926                 if (DEBUG) {
927                     Slog.d(TAG, "Received WIFI_P2P_CONNECTION_CHANGED_ACTION: networkInfo="
928                             + networkInfo);
929                 }
930 
931                 handleConnectionChanged(networkInfo);
932             }
933         }
934     };
935 
936     /**
937      * Called on the handler thread when displays are connected or disconnected.
938      */
939     public interface Listener {
onFeatureStateChanged(int featureState)940         void onFeatureStateChanged(int featureState);
941 
onScanStarted()942         void onScanStarted();
onScanFinished(WifiDisplay[] availableDisplays)943         void onScanFinished(WifiDisplay[] availableDisplays);
944 
onDisplayConnecting(WifiDisplay display)945         void onDisplayConnecting(WifiDisplay display);
onDisplayConnectionFailed()946         void onDisplayConnectionFailed();
onDisplayChanged(WifiDisplay display)947         void onDisplayChanged(WifiDisplay display);
onDisplayConnected(WifiDisplay display, Surface surface, int width, int height, int flags)948         void onDisplayConnected(WifiDisplay display,
949                 Surface surface, int width, int height, int flags);
onDisplayDisconnected()950         void onDisplayDisconnected();
951     }
952 }
953