WifiDisplayController.java revision 7ab7260697de617aced0b0ba594304a42e5395bd
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
17package com.android.server.display;
18
19import com.android.internal.util.DumpUtils;
20
21import android.content.BroadcastReceiver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.hardware.display.WifiDisplay;
26import android.media.AudioManager;
27import android.media.RemoteDisplay;
28import android.net.NetworkInfo;
29import android.net.wifi.p2p.WifiP2pConfig;
30import android.net.wifi.p2p.WifiP2pDevice;
31import android.net.wifi.p2p.WifiP2pDeviceList;
32import android.net.wifi.p2p.WifiP2pGroup;
33import android.net.wifi.p2p.WifiP2pManager;
34import android.net.wifi.p2p.WifiP2pWfdInfo;
35import android.net.wifi.p2p.WifiP2pManager.ActionListener;
36import android.net.wifi.p2p.WifiP2pManager.Channel;
37import android.net.wifi.p2p.WifiP2pManager.GroupInfoListener;
38import android.net.wifi.p2p.WifiP2pManager.PeerListListener;
39import android.os.Handler;
40import android.util.Slog;
41import android.view.Surface;
42
43import java.io.PrintWriter;
44import java.net.Inet4Address;
45import java.net.InetAddress;
46import java.net.NetworkInterface;
47import java.net.SocketException;
48import java.util.ArrayList;
49import java.util.Enumeration;
50
51/**
52 * Manages all of the various asynchronous interactions with the {@link WifiP2pManager}
53 * on behalf of {@link WifiDisplayAdapter}.
54 * <p>
55 * This code is isolated from {@link WifiDisplayAdapter} so that we can avoid
56 * accidentally introducing any deadlocks due to the display manager calling
57 * outside of itself while holding its lock.  It's also way easier to write this
58 * asynchronous code if we can assume that it is single-threaded.
59 * </p><p>
60 * The controller must be instantiated on the handler thread.
61 * </p>
62 */
63final class WifiDisplayController implements DumpUtils.Dump {
64    private static final String TAG = "WifiDisplayController";
65    private static final boolean DEBUG = false;
66
67    private static final int DEFAULT_CONTROL_PORT = 7236;
68    private static final int MAX_THROUGHPUT = 50;
69    private static final int CONNECTION_TIMEOUT_SECONDS = 30;
70    private static final int RTSP_TIMEOUT_SECONDS = 15;
71
72    private static final int DISCOVER_PEERS_MAX_RETRIES = 10;
73    private static final int DISCOVER_PEERS_RETRY_DELAY_MILLIS = 500;
74
75    private static final int CONNECT_MAX_RETRIES = 3;
76    private static final int CONNECT_RETRY_DELAY_MILLIS = 500;
77
78    // A unique token to identify the remote submix that is managed by Wifi display.
79    // It must match what the media server uses when it starts recording the submix
80    // for transmission.  We use 0 although the actual value is currently ignored.
81    private static final int REMOTE_SUBMIX_ADDRESS = 0;
82
83    private final Context mContext;
84    private final Handler mHandler;
85    private final Listener mListener;
86
87    private final WifiP2pManager mWifiP2pManager;
88    private final Channel mWifiP2pChannel;
89
90    private final AudioManager mAudioManager;
91
92    private boolean mWifiP2pEnabled;
93    private boolean mWfdEnabled;
94    private boolean mWfdEnabling;
95    private NetworkInfo mNetworkInfo;
96
97    private final ArrayList<WifiP2pDevice> mKnownWifiDisplayPeers =
98            new ArrayList<WifiP2pDevice>();
99
100    // True if there is a call to discoverPeers in progress.
101    private boolean mDiscoverPeersInProgress;
102
103    // Number of discover peers retries remaining.
104    private int mDiscoverPeersRetriesLeft;
105
106    // The device to which we want to connect, or null if we want to be disconnected.
107    private WifiP2pDevice mDesiredDevice;
108
109    // The device to which we are currently connecting, or null if we have already connected
110    // or are not trying to connect.
111    private WifiP2pDevice mConnectingDevice;
112
113    // The device to which we are currently connected, which means we have an active P2P group.
114    private WifiP2pDevice mConnectedDevice;
115
116    // The group info obtained after connecting.
117    private WifiP2pGroup mConnectedDeviceGroupInfo;
118
119    // Number of connection retries remaining.
120    private int mConnectionRetriesLeft;
121
122    // The remote display that is listening on the connection.
123    // Created after the Wifi P2P network is connected.
124    private RemoteDisplay mRemoteDisplay;
125
126    // The remote display interface.
127    private String mRemoteDisplayInterface;
128
129    // True if RTSP has connected.
130    private boolean mRemoteDisplayConnected;
131
132    public WifiDisplayController(Context context, Handler handler, Listener listener) {
133        mContext = context;
134        mHandler = handler;
135        mListener = listener;
136
137        mWifiP2pManager = (WifiP2pManager)context.getSystemService(Context.WIFI_P2P_SERVICE);
138        mWifiP2pChannel = mWifiP2pManager.initialize(context, handler.getLooper(), null);
139
140        mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
141
142        IntentFilter intentFilter = new IntentFilter();
143        intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
144        intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
145        intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
146        context.registerReceiver(mWifiP2pReceiver, intentFilter);
147    }
148
149    public void dump(PrintWriter pw) {
150        pw.println("mWifiP2pEnabled=" + mWifiP2pEnabled);
151        pw.println("mWfdEnabled=" + mWfdEnabled);
152        pw.println("mWfdEnabling=" + mWfdEnabling);
153        pw.println("mNetworkInfo=" + mNetworkInfo);
154        pw.println("mDiscoverPeersInProgress=" + mDiscoverPeersInProgress);
155        pw.println("mDiscoverPeersRetriesLeft=" + mDiscoverPeersRetriesLeft);
156        pw.println("mDesiredDevice=" + describeWifiP2pDevice(mDesiredDevice));
157        pw.println("mConnectingDisplay=" + describeWifiP2pDevice(mConnectingDevice));
158        pw.println("mConnectedDevice=" + describeWifiP2pDevice(mConnectedDevice));
159        pw.println("mConnectionRetriesLeft=" + mConnectionRetriesLeft);
160        pw.println("mRemoteDisplay=" + mRemoteDisplay);
161        pw.println("mRemoteDisplayInterface=" + mRemoteDisplayInterface);
162        pw.println("mRemoteDisplayConnected=" + mRemoteDisplayConnected);
163
164        pw.println("mKnownWifiDisplayPeers: size=" + mKnownWifiDisplayPeers.size());
165        for (WifiP2pDevice device : mKnownWifiDisplayPeers) {
166            pw.println("  " + describeWifiP2pDevice(device));
167        }
168    }
169
170    public void requestScan() {
171        discoverPeers();
172    }
173
174    public void requestConnect(String address) {
175        for (WifiP2pDevice device : mKnownWifiDisplayPeers) {
176            if (device.deviceAddress.equals(address)) {
177                connect(device);
178            }
179        }
180    }
181
182    public void requestDisconnect() {
183        disconnect();
184    }
185
186    private void enableWfd() {
187        if (!mWfdEnabled && !mWfdEnabling) {
188            mWfdEnabling = true;
189
190            WifiP2pWfdInfo wfdInfo = new WifiP2pWfdInfo();
191            wfdInfo.setWfdEnabled(true);
192            wfdInfo.setDeviceType(WifiP2pWfdInfo.WFD_SOURCE);
193            wfdInfo.setSessionAvailable(true);
194            wfdInfo.setControlPort(DEFAULT_CONTROL_PORT);
195            wfdInfo.setMaxThroughput(MAX_THROUGHPUT);
196            mWifiP2pManager.setWFDInfo(mWifiP2pChannel, wfdInfo, new ActionListener() {
197                @Override
198                public void onSuccess() {
199                    if (DEBUG) {
200                        Slog.d(TAG, "Successfully set WFD info.");
201                    }
202                    if (mWfdEnabling) {
203                        mWfdEnabling = false;
204                        setWfdEnabled(true);
205                    }
206                }
207
208                @Override
209                public void onFailure(int reason) {
210                    if (DEBUG) {
211                        Slog.d(TAG, "Failed to set WFD info with reason " + reason + ".");
212                    }
213                    mWfdEnabling = false;
214                }
215            });
216        }
217    }
218
219    private void setWfdEnabled(final boolean enabled) {
220        if (mWfdEnabled != enabled) {
221            mWfdEnabled = enabled;
222            mHandler.post(new Runnable() {
223                @Override
224                public void run() {
225                    mListener.onEnablementChanged(enabled);
226                }
227            });
228        }
229    }
230
231    private void discoverPeers() {
232        if (!mDiscoverPeersInProgress) {
233            mDiscoverPeersInProgress = true;
234            mDiscoverPeersRetriesLeft = DISCOVER_PEERS_MAX_RETRIES;
235            handleScanStarted();
236            tryDiscoverPeers();
237        }
238    }
239
240    private void tryDiscoverPeers() {
241        mWifiP2pManager.discoverPeers(mWifiP2pChannel, new ActionListener() {
242            @Override
243            public void onSuccess() {
244                if (DEBUG) {
245                    Slog.d(TAG, "Discover peers succeeded.  Requesting peers now.");
246                }
247
248                mDiscoverPeersInProgress = false;
249                requestPeers();
250            }
251
252            @Override
253            public void onFailure(int reason) {
254                if (DEBUG) {
255                    Slog.d(TAG, "Discover peers failed with reason " + reason + ".");
256                }
257
258                if (mDiscoverPeersInProgress) {
259                    if (reason == 0 && mDiscoverPeersRetriesLeft > 0 && mWfdEnabled) {
260                        mHandler.postDelayed(new Runnable() {
261                            @Override
262                            public void run() {
263                                if (mDiscoverPeersInProgress) {
264                                    if (mDiscoverPeersRetriesLeft > 0 && mWfdEnabled) {
265                                        mDiscoverPeersRetriesLeft -= 1;
266                                        if (DEBUG) {
267                                            Slog.d(TAG, "Retrying discovery.  Retries left: "
268                                                    + mDiscoverPeersRetriesLeft);
269                                        }
270                                        tryDiscoverPeers();
271                                    } else {
272                                        handleScanFinished();
273                                        mDiscoverPeersInProgress = false;
274                                    }
275                                }
276                            }
277                        }, DISCOVER_PEERS_RETRY_DELAY_MILLIS);
278                    } else {
279                        handleScanFinished();
280                        mDiscoverPeersInProgress = false;
281                    }
282                }
283            }
284        });
285    }
286
287    private void requestPeers() {
288        mWifiP2pManager.requestPeers(mWifiP2pChannel, new PeerListListener() {
289            @Override
290            public void onPeersAvailable(WifiP2pDeviceList peers) {
291                if (DEBUG) {
292                    Slog.d(TAG, "Received list of peers.");
293                }
294
295                mKnownWifiDisplayPeers.clear();
296                for (WifiP2pDevice device : peers.getDeviceList()) {
297                    if (DEBUG) {
298                        Slog.d(TAG, "  " + describeWifiP2pDevice(device));
299                    }
300
301                    if (isWifiDisplay(device)) {
302                        mKnownWifiDisplayPeers.add(device);
303                    }
304                }
305
306                handleScanFinished();
307            }
308        });
309    }
310
311    private void handleScanStarted() {
312        mHandler.post(new Runnable() {
313            @Override
314            public void run() {
315                mListener.onScanStarted();
316            }
317        });
318    }
319
320    private void handleScanFinished() {
321        final int count = mKnownWifiDisplayPeers.size();
322        final WifiDisplay[] displays = WifiDisplay.CREATOR.newArray(count);
323        for (int i = 0; i < count; i++) {
324            displays[i] = createWifiDisplay(mKnownWifiDisplayPeers.get(i));
325        }
326
327        mHandler.post(new Runnable() {
328            @Override
329            public void run() {
330                mListener.onScanFinished(displays);
331            }
332        });
333    }
334
335    private void connect(final WifiP2pDevice device) {
336        if (mDesiredDevice != null
337                && !mDesiredDevice.deviceAddress.equals(device.deviceAddress)) {
338            if (DEBUG) {
339                Slog.d(TAG, "connect: nothing to do, already connecting to "
340                        + describeWifiP2pDevice(device));
341            }
342            return;
343        }
344
345        if (mConnectedDevice != null
346                && !mConnectedDevice.deviceAddress.equals(device.deviceAddress)
347                && mDesiredDevice == null) {
348            if (DEBUG) {
349                Slog.d(TAG, "connect: nothing to do, already connected to "
350                        + describeWifiP2pDevice(device) + " and not part way through "
351                        + "connecting to a different device.");
352            }
353            return;
354        }
355
356        mDesiredDevice = device;
357        mConnectionRetriesLeft = CONNECT_MAX_RETRIES;
358        updateConnection();
359    }
360
361    private void disconnect() {
362        mDesiredDevice = null;
363        updateConnection();
364    }
365
366    private void retryConnection() {
367        if (mDesiredDevice != null && mConnectedDevice != mDesiredDevice
368                && mConnectionRetriesLeft > 0) {
369            mConnectionRetriesLeft -= 1;
370            Slog.i(TAG, "Retrying Wifi display connection.  Retries left: "
371                    + mConnectionRetriesLeft);
372
373            // Cheap hack.  Make a new instance of the device object so that we
374            // can distinguish it from the previous connection attempt.
375            // This will cause us to tear everything down before we try again.
376            mDesiredDevice = new WifiP2pDevice(mDesiredDevice);
377            updateConnection();
378        }
379    }
380
381    /**
382     * This function is called repeatedly after each asynchronous operation
383     * until all preconditions for the connection have been satisfied and the
384     * connection is established (or not).
385     */
386    private void updateConnection() {
387        // Step 1. Before we try to connect to a new device, tell the system we
388        // have disconnected from the old one.
389        if (mRemoteDisplay != null && mConnectedDevice != mDesiredDevice) {
390            Slog.i(TAG, "Stopped listening for RTSP connection on " + mRemoteDisplayInterface
391                    + " from Wifi display: " + mConnectedDevice.deviceName);
392
393            if (mRemoteDisplayConnected) {
394                mAudioManager.setRemoteSubmixOn(false, REMOTE_SUBMIX_ADDRESS);
395            }
396
397            mRemoteDisplay.dispose();
398            mRemoteDisplay = null;
399            mRemoteDisplayInterface = null;
400            mRemoteDisplayConnected = false;
401            mHandler.removeCallbacks(mRtspTimeout);
402
403            mHandler.post(new Runnable() {
404                @Override
405                public void run() {
406                    mListener.onDisplayDisconnected();
407                }
408            });
409
410            // continue to next step
411        }
412
413        // Step 2. Before we try to connect to a new device, disconnect from the old one.
414        if (mConnectedDevice != null && mConnectedDevice != mDesiredDevice) {
415            Slog.i(TAG, "Disconnecting from Wifi display: " + mConnectedDevice.deviceName);
416
417            final WifiP2pDevice oldDevice = mConnectedDevice;
418            mWifiP2pManager.removeGroup(mWifiP2pChannel, new ActionListener() {
419                @Override
420                public void onSuccess() {
421                    Slog.i(TAG, "Disconnected from Wifi display: " + oldDevice.deviceName);
422                    next();
423                }
424
425                @Override
426                public void onFailure(int reason) {
427                    Slog.i(TAG, "Failed to disconnect from Wifi display: "
428                            + oldDevice.deviceName + ", reason=" + reason);
429                    next();
430                }
431
432                private void next() {
433                    if (mConnectedDevice == oldDevice) {
434                        mConnectedDevice = null;
435                        updateConnection();
436                    }
437                }
438            });
439            return; // wait for asynchronous callback
440        }
441
442        // Step 3. Before we try to connect to a new device, stop trying to connect
443        // to the old one.
444        if (mConnectingDevice != null && mConnectingDevice != mDesiredDevice) {
445            Slog.i(TAG, "Canceling connection to Wifi display: " + mConnectingDevice.deviceName);
446
447            mHandler.removeCallbacks(mConnectionTimeout);
448
449            final WifiP2pDevice oldDevice = mConnectingDevice;
450            mWifiP2pManager.cancelConnect(mWifiP2pChannel, new ActionListener() {
451                @Override
452                public void onSuccess() {
453                    Slog.i(TAG, "Canceled connection to Wifi display: " + oldDevice.deviceName);
454                    next();
455                }
456
457                @Override
458                public void onFailure(int reason) {
459                    Slog.i(TAG, "Failed to cancel connection to Wifi display: "
460                            + oldDevice.deviceName + ", reason=" + reason);
461                    next();
462                }
463
464                private void next() {
465                    if (mConnectingDevice == oldDevice) {
466                        mConnectingDevice = null;
467                        updateConnection();
468                    }
469                }
470            });
471            return; // wait for asynchronous callback
472        }
473
474        // Step 4. If we wanted to disconnect, then mission accomplished.
475        if (mDesiredDevice == null) {
476            return; // done
477        }
478
479        // Step 5. Try to connect.
480        if (mConnectedDevice == null && mConnectingDevice == null) {
481            Slog.i(TAG, "Connecting to Wifi display: " + mDesiredDevice.deviceName);
482
483            mConnectingDevice = mDesiredDevice;
484            WifiP2pConfig config = new WifiP2pConfig();
485            config.deviceAddress = mConnectingDevice.deviceAddress;
486            // Helps with STA & P2P concurrency
487            config.groupOwnerIntent = WifiP2pConfig.MAX_GO_INTENT;
488
489            final WifiDisplay display = createWifiDisplay(mConnectingDevice);
490            mHandler.post(new Runnable() {
491                @Override
492                public void run() {
493                    mListener.onDisplayConnecting(display);
494                }
495            });
496
497            final WifiP2pDevice newDevice = mDesiredDevice;
498            mWifiP2pManager.connect(mWifiP2pChannel, config, new ActionListener() {
499                @Override
500                public void onSuccess() {
501                    // The connection may not yet be established.  We still need to wait
502                    // for WIFI_P2P_CONNECTION_CHANGED_ACTION.  However, we might never
503                    // get that broadcast, so we register a timeout.
504                    Slog.i(TAG, "Initiated connection to Wifi display: " + newDevice.deviceName);
505
506                    mHandler.postDelayed(mConnectionTimeout, CONNECTION_TIMEOUT_SECONDS * 1000);
507                }
508
509                @Override
510                public void onFailure(int reason) {
511                    if (mConnectingDevice == newDevice) {
512                        Slog.i(TAG, "Failed to initiate connection to Wifi display: "
513                                + newDevice.deviceName + ", reason=" + reason);
514                        mConnectingDevice = null;
515                        handleConnectionFailure(false);
516                    }
517                }
518            });
519            return; // wait for asynchronous callback
520        }
521
522        // Step 6. Listen for incoming connections.
523        if (mConnectedDevice != null && mRemoteDisplay == null) {
524            Inet4Address addr = getInterfaceAddress(mConnectedDeviceGroupInfo);
525            if (addr == null) {
526                Slog.i(TAG, "Failed to get local interface address for communicating "
527                        + "with Wifi display: " + mConnectedDevice.deviceName);
528                handleConnectionFailure(false);
529                return; // done
530            }
531
532            final WifiP2pDevice oldDevice = mConnectedDevice;
533            final int port = getPortNumber(mConnectedDevice);
534            final String iface = addr.getHostAddress() + ":" + port;
535            mRemoteDisplayInterface = iface;
536
537            Slog.i(TAG, "Listening for RTSP connection on " + iface
538                    + " from Wifi display: " + mConnectedDevice.deviceName);
539
540            mRemoteDisplay = RemoteDisplay.listen(iface, new RemoteDisplay.Listener() {
541                @Override
542                public void onDisplayConnected(final Surface surface,
543                        final int width, final int height, final int flags) {
544                    if (mConnectedDevice == oldDevice && !mRemoteDisplayConnected) {
545                        Slog.i(TAG, "Opened RTSP connection with Wifi display: "
546                                + mConnectedDevice.deviceName);
547                        mRemoteDisplayConnected = true;
548                        mHandler.removeCallbacks(mRtspTimeout);
549
550                        mAudioManager.setRemoteSubmixOn(true, REMOTE_SUBMIX_ADDRESS);
551
552                        final WifiDisplay display = createWifiDisplay(mConnectedDevice);
553                        mHandler.post(new Runnable() {
554                            @Override
555                            public void run() {
556                                mListener.onDisplayConnected(display,
557                                        surface, width, height, flags);
558                            }
559                        });
560                    }
561                }
562
563                @Override
564                public void onDisplayDisconnected() {
565                    if (mConnectedDevice == oldDevice) {
566                        Slog.i(TAG, "Closed RTSP connection with Wifi display: "
567                                + mConnectedDevice.deviceName);
568                        mHandler.removeCallbacks(mRtspTimeout);
569                        disconnect();
570                    }
571                }
572
573                @Override
574                public void onDisplayError(int error) {
575                    if (mConnectedDevice == oldDevice) {
576                        Slog.i(TAG, "Lost RTSP connection with Wifi display due to error "
577                                + error + ": " + mConnectedDevice.deviceName);
578                        mHandler.removeCallbacks(mRtspTimeout);
579                        handleConnectionFailure(false);
580                    }
581                }
582            }, mHandler);
583
584            mHandler.postDelayed(mRtspTimeout, RTSP_TIMEOUT_SECONDS * 1000);
585        }
586    }
587
588    private void handleStateChanged(boolean enabled) {
589        if (mWifiP2pEnabled != enabled) {
590            mWifiP2pEnabled = enabled;
591            if (enabled) {
592                if (!mWfdEnabled) {
593                    enableWfd();
594                }
595            } else {
596                setWfdEnabled(false);
597                disconnect();
598            }
599        }
600    }
601
602    private void handlePeersChanged() {
603        if (mWifiP2pEnabled) {
604            if (mWfdEnabled) {
605                requestPeers();
606            } else {
607                enableWfd();
608            }
609        }
610    }
611
612    private void handleConnectionChanged(NetworkInfo networkInfo) {
613        mNetworkInfo = networkInfo;
614        if (mWfdEnabled && networkInfo.isConnected()) {
615            if (mDesiredDevice != null) {
616                mWifiP2pManager.requestGroupInfo(mWifiP2pChannel, new GroupInfoListener() {
617                    @Override
618                    public void onGroupInfoAvailable(WifiP2pGroup info) {
619                        if (DEBUG) {
620                            Slog.d(TAG, "Received group info: " + describeWifiP2pGroup(info));
621                        }
622
623                        if (mConnectingDevice != null && !info.contains(mConnectingDevice)) {
624                            Slog.i(TAG, "Aborting connection to Wifi display because "
625                                    + "the current P2P group does not contain the device "
626                                    + "we expected to find: " + mConnectingDevice.deviceName);
627                            handleConnectionFailure(false);
628                            return;
629                        }
630
631                        if (mDesiredDevice != null && !info.contains(mDesiredDevice)) {
632                            disconnect();
633                            return;
634                        }
635
636                        if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) {
637                            Slog.i(TAG, "Connected to Wifi display: "
638                                    + mConnectingDevice.deviceName);
639
640                            mHandler.removeCallbacks(mConnectionTimeout);
641                            mConnectedDeviceGroupInfo = info;
642                            mConnectedDevice = mConnectingDevice;
643                            mConnectingDevice = null;
644                            updateConnection();
645                        }
646                    }
647                });
648            }
649        } else {
650            disconnect();
651
652            // After disconnection for a group, for some reason we have a tendency
653            // to get a peer change notification with an empty list of peers.
654            // Perform a fresh scan.
655            if (mWfdEnabled) {
656                requestPeers();
657            }
658        }
659    }
660
661    private final Runnable mConnectionTimeout = new Runnable() {
662        @Override
663        public void run() {
664            if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) {
665                Slog.i(TAG, "Timed out waiting for Wifi display connection after "
666                        + CONNECTION_TIMEOUT_SECONDS + " seconds: "
667                        + mConnectingDevice.deviceName);
668                handleConnectionFailure(true);
669            }
670        }
671    };
672
673    private final Runnable mRtspTimeout = new Runnable() {
674        @Override
675        public void run() {
676            if (mConnectedDevice != null
677                    && mRemoteDisplay != null && !mRemoteDisplayConnected) {
678                Slog.i(TAG, "Timed out waiting for Wifi display RTSP connection after "
679                        + RTSP_TIMEOUT_SECONDS + " seconds: "
680                        + mConnectedDevice.deviceName);
681                handleConnectionFailure(true);
682            }
683        }
684    };
685
686    private void handleConnectionFailure(boolean timeoutOccurred) {
687        Slog.i(TAG, "Wifi display connection failed!");
688
689        mHandler.post(new Runnable() {
690            @Override
691            public void run() {
692                mListener.onDisplayConnectionFailed();
693            }
694        });
695
696        if (mDesiredDevice != null) {
697            if (mConnectionRetriesLeft > 0) {
698                mHandler.postDelayed(new Runnable() {
699                    @Override
700                    public void run() {
701                        retryConnection();
702                    }
703                }, timeoutOccurred ? 0 : CONNECT_RETRY_DELAY_MILLIS);
704            } else {
705                disconnect();
706            }
707        }
708    }
709
710    private static Inet4Address getInterfaceAddress(WifiP2pGroup info) {
711        NetworkInterface iface;
712        try {
713            iface = NetworkInterface.getByName(info.getInterface());
714        } catch (SocketException ex) {
715            Slog.w(TAG, "Could not obtain address of network interface "
716                    + info.getInterface(), ex);
717            return null;
718        }
719
720        Enumeration<InetAddress> addrs = iface.getInetAddresses();
721        while (addrs.hasMoreElements()) {
722            InetAddress addr = addrs.nextElement();
723            if (addr instanceof Inet4Address) {
724                return (Inet4Address)addr;
725            }
726        }
727
728        Slog.w(TAG, "Could not obtain address of network interface "
729                + info.getInterface() + " because it had no IPv4 addresses.");
730        return null;
731    }
732
733    private static int getPortNumber(WifiP2pDevice device) {
734        if (device.deviceName.startsWith("DIRECT-")
735                && device.deviceName.endsWith("Broadcom")) {
736            // These dongles ignore the port we broadcast in our WFD IE.
737            return 8554;
738        }
739        return DEFAULT_CONTROL_PORT;
740    }
741
742    private static boolean isWifiDisplay(WifiP2pDevice device) {
743        return device.wfdInfo != null
744                && device.wfdInfo.isWfdEnabled()
745                && isPrimarySinkDeviceType(device.wfdInfo.getDeviceType());
746    }
747
748    private static boolean isPrimarySinkDeviceType(int deviceType) {
749        return deviceType == WifiP2pWfdInfo.PRIMARY_SINK
750                || deviceType == WifiP2pWfdInfo.SOURCE_OR_PRIMARY_SINK;
751    }
752
753    private static String describeWifiP2pDevice(WifiP2pDevice device) {
754        return device != null ? device.toString().replace('\n', ',') : "null";
755    }
756
757    private static String describeWifiP2pGroup(WifiP2pGroup group) {
758        return group != null ? group.toString().replace('\n', ',') : "null";
759    }
760
761    private static WifiDisplay createWifiDisplay(WifiP2pDevice device) {
762        return new WifiDisplay(device.deviceAddress, device.deviceName);
763    }
764
765    private final BroadcastReceiver mWifiP2pReceiver = new BroadcastReceiver() {
766        @Override
767        public void onReceive(Context context, Intent intent) {
768            final String action = intent.getAction();
769            if (action.equals(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)) {
770                boolean enabled = (intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE,
771                        WifiP2pManager.WIFI_P2P_STATE_DISABLED)) ==
772                        WifiP2pManager.WIFI_P2P_STATE_ENABLED;
773                if (DEBUG) {
774                    Slog.d(TAG, "Received WIFI_P2P_STATE_CHANGED_ACTION: enabled="
775                            + enabled);
776                }
777
778                handleStateChanged(enabled);
779            } else if (action.equals(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)) {
780                if (DEBUG) {
781                    Slog.d(TAG, "Received WIFI_P2P_PEERS_CHANGED_ACTION.");
782                }
783
784                handlePeersChanged();
785            } else if (action.equals(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)) {
786                NetworkInfo networkInfo = (NetworkInfo)intent.getParcelableExtra(
787                        WifiP2pManager.EXTRA_NETWORK_INFO);
788                if (DEBUG) {
789                    Slog.d(TAG, "Received WIFI_P2P_CONNECTION_CHANGED_ACTION: networkInfo="
790                            + networkInfo);
791                }
792
793                handleConnectionChanged(networkInfo);
794            }
795        }
796    };
797
798    /**
799     * Called on the handler thread when displays are connected or disconnected.
800     */
801    public interface Listener {
802        void onEnablementChanged(boolean enabled);
803
804        void onScanStarted();
805        void onScanFinished(WifiDisplay[] knownDisplays);
806
807        void onDisplayConnecting(WifiDisplay display);
808        void onDisplayConnectionFailed();
809        void onDisplayConnected(WifiDisplay display,
810                Surface surface, int width, int height, int flags);
811        void onDisplayDisconnected();
812    }
813}
814