• 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             mWifiP2pManager.setMiracastMode(WifiP2pManager.MIRACAST_DISABLED);
485             setRemoteSubmixOn(false);
486             unadvertiseDisplay();
487 
488             // continue to next step
489         }
490 
491         // Step 2. Before we try to connect to a new device, disconnect from the old one.
492         if (mDisconnectingDevice != null) {
493             return; // wait for asynchronous callback
494         }
495         if (mConnectedDevice != null && mConnectedDevice != mDesiredDevice) {
496             Slog.i(TAG, "Disconnecting from Wifi display: " + mConnectedDevice.deviceName);
497             mDisconnectingDevice = mConnectedDevice;
498             mConnectedDevice = null;
499 
500             unadvertiseDisplay();
501 
502             final WifiP2pDevice oldDevice = mDisconnectingDevice;
503             mWifiP2pManager.removeGroup(mWifiP2pChannel, new ActionListener() {
504                 @Override
505                 public void onSuccess() {
506                     Slog.i(TAG, "Disconnected from Wifi display: " + oldDevice.deviceName);
507                     next();
508                 }
509 
510                 @Override
511                 public void onFailure(int reason) {
512                     Slog.i(TAG, "Failed to disconnect from Wifi display: "
513                             + oldDevice.deviceName + ", reason=" + reason);
514                     next();
515                 }
516 
517                 private void next() {
518                     if (mDisconnectingDevice == oldDevice) {
519                         mDisconnectingDevice = null;
520                         updateConnection();
521                     }
522                 }
523             });
524             return; // wait for asynchronous callback
525         }
526 
527         // Step 3. Before we try to connect to a new device, stop trying to connect
528         // to the old one.
529         if (mCancelingDevice != null) {
530             return; // wait for asynchronous callback
531         }
532         if (mConnectingDevice != null && mConnectingDevice != mDesiredDevice) {
533             Slog.i(TAG, "Canceling connection to Wifi display: " + mConnectingDevice.deviceName);
534             mCancelingDevice = mConnectingDevice;
535             mConnectingDevice = null;
536 
537             unadvertiseDisplay();
538             mHandler.removeCallbacks(mConnectionTimeout);
539 
540             final WifiP2pDevice oldDevice = mCancelingDevice;
541             mWifiP2pManager.cancelConnect(mWifiP2pChannel, new ActionListener() {
542                 @Override
543                 public void onSuccess() {
544                     Slog.i(TAG, "Canceled connection to Wifi display: " + oldDevice.deviceName);
545                     next();
546                 }
547 
548                 @Override
549                 public void onFailure(int reason) {
550                     Slog.i(TAG, "Failed to cancel connection to Wifi display: "
551                             + oldDevice.deviceName + ", reason=" + reason);
552                     next();
553                 }
554 
555                 private void next() {
556                     if (mCancelingDevice == oldDevice) {
557                         mCancelingDevice = null;
558                         updateConnection();
559                     }
560                 }
561             });
562             return; // wait for asynchronous callback
563         }
564 
565         // Step 4. If we wanted to disconnect, then mission accomplished.
566         if (mDesiredDevice == null) {
567             unadvertiseDisplay();
568             return; // done
569         }
570 
571         // Step 5. Try to connect.
572         if (mConnectedDevice == null && mConnectingDevice == null) {
573             Slog.i(TAG, "Connecting to Wifi display: " + mDesiredDevice.deviceName);
574 
575             mConnectingDevice = mDesiredDevice;
576             WifiP2pConfig config = new WifiP2pConfig();
577             WpsInfo wps = new WpsInfo();
578             if (mConnectingDevice.wpsPbcSupported()) {
579                 wps.setup = WpsInfo.PBC;
580             } else if (mConnectingDevice.wpsDisplaySupported()) {
581                 // We do keypad if peer does display
582                 wps.setup = WpsInfo.KEYPAD;
583             } else {
584                 wps.setup = WpsInfo.DISPLAY;
585             }
586             config.wps = wps;
587             config.deviceAddress = mConnectingDevice.deviceAddress;
588             // Helps with STA & P2P concurrency
589             config.groupOwnerIntent = WifiP2pConfig.MIN_GROUP_OWNER_INTENT;
590 
591             WifiDisplay display = createWifiDisplay(mConnectingDevice);
592             advertiseDisplay(display, null, 0, 0, 0);
593 
594             final WifiP2pDevice newDevice = mDesiredDevice;
595             mWifiP2pManager.connect(mWifiP2pChannel, config, new ActionListener() {
596                 @Override
597                 public void onSuccess() {
598                     // The connection may not yet be established.  We still need to wait
599                     // for WIFI_P2P_CONNECTION_CHANGED_ACTION.  However, we might never
600                     // get that broadcast, so we register a timeout.
601                     Slog.i(TAG, "Initiated connection to Wifi display: " + newDevice.deviceName);
602 
603                     mHandler.postDelayed(mConnectionTimeout, CONNECTION_TIMEOUT_SECONDS * 1000);
604                 }
605 
606                 @Override
607                 public void onFailure(int reason) {
608                     if (mConnectingDevice == newDevice) {
609                         Slog.i(TAG, "Failed to initiate connection to Wifi display: "
610                                 + newDevice.deviceName + ", reason=" + reason);
611                         mConnectingDevice = null;
612                         handleConnectionFailure(false);
613                     }
614                 }
615             });
616             return; // wait for asynchronous callback
617         }
618 
619         // Step 6. Listen for incoming connections.
620         if (mConnectedDevice != null && mRemoteDisplay == null) {
621             Inet4Address addr = getInterfaceAddress(mConnectedDeviceGroupInfo);
622             if (addr == null) {
623                 Slog.i(TAG, "Failed to get local interface address for communicating "
624                         + "with Wifi display: " + mConnectedDevice.deviceName);
625                 handleConnectionFailure(false);
626                 return; // done
627             }
628 
629             setRemoteSubmixOn(true);
630             mWifiP2pManager.setMiracastMode(WifiP2pManager.MIRACAST_SOURCE);
631 
632             final WifiP2pDevice oldDevice = mConnectedDevice;
633             final int port = getPortNumber(mConnectedDevice);
634             final String iface = addr.getHostAddress() + ":" + port;
635             mRemoteDisplayInterface = iface;
636 
637             Slog.i(TAG, "Listening for RTSP connection on " + iface
638                     + " from Wifi display: " + mConnectedDevice.deviceName);
639 
640             mRemoteDisplay = RemoteDisplay.listen(iface, new RemoteDisplay.Listener() {
641                 @Override
642                 public void onDisplayConnected(Surface surface,
643                         int width, int height, int flags) {
644                     if (mConnectedDevice == oldDevice && !mRemoteDisplayConnected) {
645                         Slog.i(TAG, "Opened RTSP connection with Wifi display: "
646                                 + mConnectedDevice.deviceName);
647                         mRemoteDisplayConnected = true;
648                         mHandler.removeCallbacks(mRtspTimeout);
649 
650                         final WifiDisplay display = createWifiDisplay(mConnectedDevice);
651                         advertiseDisplay(display, surface, width, height, flags);
652                     }
653                 }
654 
655                 @Override
656                 public void onDisplayDisconnected() {
657                     if (mConnectedDevice == oldDevice) {
658                         Slog.i(TAG, "Closed RTSP connection with Wifi display: "
659                                 + mConnectedDevice.deviceName);
660                         mHandler.removeCallbacks(mRtspTimeout);
661                         disconnect();
662                     }
663                 }
664 
665                 @Override
666                 public void onDisplayError(int error) {
667                     if (mConnectedDevice == oldDevice) {
668                         Slog.i(TAG, "Lost RTSP connection with Wifi display due to error "
669                                 + error + ": " + mConnectedDevice.deviceName);
670                         mHandler.removeCallbacks(mRtspTimeout);
671                         handleConnectionFailure(false);
672                     }
673                 }
674             }, mHandler);
675 
676             mHandler.postDelayed(mRtspTimeout, RTSP_TIMEOUT_SECONDS * 1000);
677         }
678     }
679 
setRemoteSubmixOn(boolean on)680     private void setRemoteSubmixOn(boolean on) {
681         if (mRemoteSubmixOn != on) {
682             mRemoteSubmixOn = on;
683             mAudioManager.setRemoteSubmixOn(on, REMOTE_SUBMIX_ADDRESS);
684         }
685     }
686 
handleStateChanged(boolean enabled)687     private void handleStateChanged(boolean enabled) {
688         mWifiP2pEnabled = enabled;
689         updateWfdEnableState();
690     }
691 
handlePeersChanged()692     private void handlePeersChanged() {
693         // Even if wfd is disabled, it is best to get the latest set of peers to
694         // keep in sync with the p2p framework
695         requestPeers();
696     }
697 
handleConnectionChanged(NetworkInfo networkInfo)698     private void handleConnectionChanged(NetworkInfo networkInfo) {
699         mNetworkInfo = networkInfo;
700         if (mWfdEnabled && networkInfo.isConnected()) {
701             if (mDesiredDevice != null) {
702                 mWifiP2pManager.requestGroupInfo(mWifiP2pChannel, new GroupInfoListener() {
703                     @Override
704                     public void onGroupInfoAvailable(WifiP2pGroup info) {
705                         if (DEBUG) {
706                             Slog.d(TAG, "Received group info: " + describeWifiP2pGroup(info));
707                         }
708 
709                         if (mConnectingDevice != null && !info.contains(mConnectingDevice)) {
710                             Slog.i(TAG, "Aborting connection to Wifi display because "
711                                     + "the current P2P group does not contain the device "
712                                     + "we expected to find: " + mConnectingDevice.deviceName
713                                     + ", group info was: " + describeWifiP2pGroup(info));
714                             handleConnectionFailure(false);
715                             return;
716                         }
717 
718                         if (mDesiredDevice != null && !info.contains(mDesiredDevice)) {
719                             disconnect();
720                             return;
721                         }
722 
723                         if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) {
724                             Slog.i(TAG, "Connected to Wifi display: "
725                                     + mConnectingDevice.deviceName);
726 
727                             mHandler.removeCallbacks(mConnectionTimeout);
728                             mConnectedDeviceGroupInfo = info;
729                             mConnectedDevice = mConnectingDevice;
730                             mConnectingDevice = null;
731                             updateConnection();
732                         }
733                     }
734                 });
735             }
736         } else {
737             disconnect();
738 
739             // After disconnection for a group, for some reason we have a tendency
740             // to get a peer change notification with an empty list of peers.
741             // Perform a fresh scan.
742             if (mWfdEnabled) {
743                 requestPeers();
744             }
745         }
746     }
747 
748     private final Runnable mConnectionTimeout = new Runnable() {
749         @Override
750         public void run() {
751             if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) {
752                 Slog.i(TAG, "Timed out waiting for Wifi display connection after "
753                         + CONNECTION_TIMEOUT_SECONDS + " seconds: "
754                         + mConnectingDevice.deviceName);
755                 handleConnectionFailure(true);
756             }
757         }
758     };
759 
760     private final Runnable mRtspTimeout = new Runnable() {
761         @Override
762         public void run() {
763             if (mConnectedDevice != null
764                     && mRemoteDisplay != null && !mRemoteDisplayConnected) {
765                 Slog.i(TAG, "Timed out waiting for Wifi display RTSP connection after "
766                         + RTSP_TIMEOUT_SECONDS + " seconds: "
767                         + mConnectedDevice.deviceName);
768                 handleConnectionFailure(true);
769             }
770         }
771     };
772 
handleConnectionFailure(boolean timeoutOccurred)773     private void handleConnectionFailure(boolean timeoutOccurred) {
774         Slog.i(TAG, "Wifi display connection failed!");
775 
776         if (mDesiredDevice != null) {
777             if (mConnectionRetriesLeft > 0) {
778                 final WifiP2pDevice oldDevice = mDesiredDevice;
779                 mHandler.postDelayed(new Runnable() {
780                     @Override
781                     public void run() {
782                         if (mDesiredDevice == oldDevice && mConnectionRetriesLeft > 0) {
783                             mConnectionRetriesLeft -= 1;
784                             Slog.i(TAG, "Retrying Wifi display connection.  Retries left: "
785                                     + mConnectionRetriesLeft);
786                             retryConnection();
787                         }
788                     }
789                 }, timeoutOccurred ? 0 : CONNECT_RETRY_DELAY_MILLIS);
790             } else {
791                 disconnect();
792             }
793         }
794     }
795 
advertiseDisplay(final WifiDisplay display, final Surface surface, final int width, final int height, final int flags)796     private void advertiseDisplay(final WifiDisplay display,
797             final Surface surface, final int width, final int height, final int flags) {
798         if (!Objects.equal(mAdvertisedDisplay, display)
799                 || mAdvertisedDisplaySurface != surface
800                 || mAdvertisedDisplayWidth != width
801                 || mAdvertisedDisplayHeight != height
802                 || mAdvertisedDisplayFlags != flags) {
803             final WifiDisplay oldDisplay = mAdvertisedDisplay;
804             final Surface oldSurface = mAdvertisedDisplaySurface;
805 
806             mAdvertisedDisplay = display;
807             mAdvertisedDisplaySurface = surface;
808             mAdvertisedDisplayWidth = width;
809             mAdvertisedDisplayHeight = height;
810             mAdvertisedDisplayFlags = flags;
811 
812             mHandler.post(new Runnable() {
813                 @Override
814                 public void run() {
815                     if (oldSurface != null && surface != oldSurface) {
816                         mListener.onDisplayDisconnected();
817                     } else if (oldDisplay != null && !oldDisplay.hasSameAddress(display)) {
818                         mListener.onDisplayConnectionFailed();
819                     }
820 
821                     if (display != null) {
822                         if (!display.hasSameAddress(oldDisplay)) {
823                             mListener.onDisplayConnecting(display);
824                         } else if (!display.equals(oldDisplay)) {
825                             // The address is the same but some other property such as the
826                             // name must have changed.
827                             mListener.onDisplayChanged(display);
828                         }
829                         if (surface != null && surface != oldSurface) {
830                             mListener.onDisplayConnected(display, surface, width, height, flags);
831                         }
832                     }
833                 }
834             });
835         }
836     }
837 
unadvertiseDisplay()838     private void unadvertiseDisplay() {
839         advertiseDisplay(null, null, 0, 0, 0);
840     }
841 
readvertiseDisplay(WifiDisplay display)842     private void readvertiseDisplay(WifiDisplay display) {
843         advertiseDisplay(display, mAdvertisedDisplaySurface,
844                 mAdvertisedDisplayWidth, mAdvertisedDisplayHeight,
845                 mAdvertisedDisplayFlags);
846     }
847 
getInterfaceAddress(WifiP2pGroup info)848     private static Inet4Address getInterfaceAddress(WifiP2pGroup info) {
849         NetworkInterface iface;
850         try {
851             iface = NetworkInterface.getByName(info.getInterface());
852         } catch (SocketException ex) {
853             Slog.w(TAG, "Could not obtain address of network interface "
854                     + info.getInterface(), ex);
855             return null;
856         }
857 
858         Enumeration<InetAddress> addrs = iface.getInetAddresses();
859         while (addrs.hasMoreElements()) {
860             InetAddress addr = addrs.nextElement();
861             if (addr instanceof Inet4Address) {
862                 return (Inet4Address)addr;
863             }
864         }
865 
866         Slog.w(TAG, "Could not obtain address of network interface "
867                 + info.getInterface() + " because it had no IPv4 addresses.");
868         return null;
869     }
870 
getPortNumber(WifiP2pDevice device)871     private static int getPortNumber(WifiP2pDevice device) {
872         if (device.deviceName.startsWith("DIRECT-")
873                 && device.deviceName.endsWith("Broadcom")) {
874             // These dongles ignore the port we broadcast in our WFD IE.
875             return 8554;
876         }
877         return DEFAULT_CONTROL_PORT;
878     }
879 
isWifiDisplay(WifiP2pDevice device)880     private static boolean isWifiDisplay(WifiP2pDevice device) {
881         return device.wfdInfo != null
882                 && device.wfdInfo.isWfdEnabled()
883                 && isPrimarySinkDeviceType(device.wfdInfo.getDeviceType());
884     }
885 
isPrimarySinkDeviceType(int deviceType)886     private static boolean isPrimarySinkDeviceType(int deviceType) {
887         return deviceType == WifiP2pWfdInfo.PRIMARY_SINK
888                 || deviceType == WifiP2pWfdInfo.SOURCE_OR_PRIMARY_SINK;
889     }
890 
describeWifiP2pDevice(WifiP2pDevice device)891     private static String describeWifiP2pDevice(WifiP2pDevice device) {
892         return device != null ? device.toString().replace('\n', ',') : "null";
893     }
894 
describeWifiP2pGroup(WifiP2pGroup group)895     private static String describeWifiP2pGroup(WifiP2pGroup group) {
896         return group != null ? group.toString().replace('\n', ',') : "null";
897     }
898 
createWifiDisplay(WifiP2pDevice device)899     private static WifiDisplay createWifiDisplay(WifiP2pDevice device) {
900         return new WifiDisplay(device.deviceAddress, device.deviceName, null);
901     }
902 
903     private final BroadcastReceiver mWifiP2pReceiver = new BroadcastReceiver() {
904         @Override
905         public void onReceive(Context context, Intent intent) {
906             final String action = intent.getAction();
907             if (action.equals(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)) {
908                 // This broadcast is sticky so we'll always get the initial Wifi P2P state
909                 // on startup.
910                 boolean enabled = (intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE,
911                         WifiP2pManager.WIFI_P2P_STATE_DISABLED)) ==
912                         WifiP2pManager.WIFI_P2P_STATE_ENABLED;
913                 if (DEBUG) {
914                     Slog.d(TAG, "Received WIFI_P2P_STATE_CHANGED_ACTION: enabled="
915                             + enabled);
916                 }
917 
918                 handleStateChanged(enabled);
919             } else if (action.equals(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)) {
920                 if (DEBUG) {
921                     Slog.d(TAG, "Received WIFI_P2P_PEERS_CHANGED_ACTION.");
922                 }
923 
924                 handlePeersChanged();
925             } else if (action.equals(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)) {
926                 NetworkInfo networkInfo = (NetworkInfo)intent.getParcelableExtra(
927                         WifiP2pManager.EXTRA_NETWORK_INFO);
928                 if (DEBUG) {
929                     Slog.d(TAG, "Received WIFI_P2P_CONNECTION_CHANGED_ACTION: networkInfo="
930                             + networkInfo);
931                 }
932 
933                 handleConnectionChanged(networkInfo);
934             }
935         }
936     };
937 
938     /**
939      * Called on the handler thread when displays are connected or disconnected.
940      */
941     public interface Listener {
onFeatureStateChanged(int featureState)942         void onFeatureStateChanged(int featureState);
943 
onScanStarted()944         void onScanStarted();
onScanFinished(WifiDisplay[] availableDisplays)945         void onScanFinished(WifiDisplay[] availableDisplays);
946 
onDisplayConnecting(WifiDisplay display)947         void onDisplayConnecting(WifiDisplay display);
onDisplayConnectionFailed()948         void onDisplayConnectionFailed();
onDisplayChanged(WifiDisplay display)949         void onDisplayChanged(WifiDisplay display);
onDisplayConnected(WifiDisplay display, Surface surface, int width, int height, int flags)950         void onDisplayConnected(WifiDisplay display,
951                 Surface surface, int width, int height, int flags);
onDisplayDisconnected()952         void onDisplayDisconnected();
953     }
954 }
955