WifiDisplayController.java revision c9bd4ca005768cb30aaaa44c1171e113c13cd107
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    // True if the remote submix is enabled.
133    private boolean mRemoteSubmixOn;
134
135    public WifiDisplayController(Context context, Handler handler, Listener listener) {
136        mContext = context;
137        mHandler = handler;
138        mListener = listener;
139
140        mWifiP2pManager = (WifiP2pManager)context.getSystemService(Context.WIFI_P2P_SERVICE);
141        mWifiP2pChannel = mWifiP2pManager.initialize(context, handler.getLooper(), null);
142
143        mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
144
145        IntentFilter intentFilter = new IntentFilter();
146        intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
147        intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
148        intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
149        context.registerReceiver(mWifiP2pReceiver, intentFilter);
150    }
151
152    public void dump(PrintWriter pw) {
153        pw.println("mWifiP2pEnabled=" + mWifiP2pEnabled);
154        pw.println("mWfdEnabled=" + mWfdEnabled);
155        pw.println("mWfdEnabling=" + mWfdEnabling);
156        pw.println("mNetworkInfo=" + mNetworkInfo);
157        pw.println("mDiscoverPeersInProgress=" + mDiscoverPeersInProgress);
158        pw.println("mDiscoverPeersRetriesLeft=" + mDiscoverPeersRetriesLeft);
159        pw.println("mDesiredDevice=" + describeWifiP2pDevice(mDesiredDevice));
160        pw.println("mConnectingDisplay=" + describeWifiP2pDevice(mConnectingDevice));
161        pw.println("mConnectedDevice=" + describeWifiP2pDevice(mConnectedDevice));
162        pw.println("mConnectionRetriesLeft=" + mConnectionRetriesLeft);
163        pw.println("mRemoteDisplay=" + mRemoteDisplay);
164        pw.println("mRemoteDisplayInterface=" + mRemoteDisplayInterface);
165        pw.println("mRemoteDisplayConnected=" + mRemoteDisplayConnected);
166        pw.println("mRemoteSubmixOn=" + mRemoteSubmixOn);
167
168        pw.println("mKnownWifiDisplayPeers: size=" + mKnownWifiDisplayPeers.size());
169        for (WifiP2pDevice device : mKnownWifiDisplayPeers) {
170            pw.println("  " + describeWifiP2pDevice(device));
171        }
172    }
173
174    public void requestScan() {
175        discoverPeers();
176    }
177
178    public void requestConnect(String address) {
179        for (WifiP2pDevice device : mKnownWifiDisplayPeers) {
180            if (device.deviceAddress.equals(address)) {
181                connect(device);
182            }
183        }
184    }
185
186    public void requestDisconnect() {
187        disconnect();
188    }
189
190    private void enableWfd() {
191        if (!mWfdEnabled && !mWfdEnabling) {
192            mWfdEnabling = true;
193
194            WifiP2pWfdInfo wfdInfo = new WifiP2pWfdInfo();
195            wfdInfo.setWfdEnabled(true);
196            wfdInfo.setDeviceType(WifiP2pWfdInfo.WFD_SOURCE);
197            wfdInfo.setSessionAvailable(true);
198            wfdInfo.setControlPort(DEFAULT_CONTROL_PORT);
199            wfdInfo.setMaxThroughput(MAX_THROUGHPUT);
200            mWifiP2pManager.setWFDInfo(mWifiP2pChannel, wfdInfo, new ActionListener() {
201                @Override
202                public void onSuccess() {
203                    if (DEBUG) {
204                        Slog.d(TAG, "Successfully set WFD info.");
205                    }
206                    if (mWfdEnabling) {
207                        mWfdEnabling = false;
208                        setWfdEnabled(true);
209                    }
210                }
211
212                @Override
213                public void onFailure(int reason) {
214                    if (DEBUG) {
215                        Slog.d(TAG, "Failed to set WFD info with reason " + reason + ".");
216                    }
217                    mWfdEnabling = false;
218                }
219            });
220        }
221    }
222
223    private void setWfdEnabled(final boolean enabled) {
224        if (mWfdEnabled != enabled) {
225            mWfdEnabled = enabled;
226            mHandler.post(new Runnable() {
227                @Override
228                public void run() {
229                    mListener.onEnablementChanged(enabled);
230                }
231            });
232        }
233    }
234
235    private void discoverPeers() {
236        if (!mDiscoverPeersInProgress) {
237            mDiscoverPeersInProgress = true;
238            mDiscoverPeersRetriesLeft = DISCOVER_PEERS_MAX_RETRIES;
239            handleScanStarted();
240            tryDiscoverPeers();
241        }
242    }
243
244    private void tryDiscoverPeers() {
245        mWifiP2pManager.discoverPeers(mWifiP2pChannel, new ActionListener() {
246            @Override
247            public void onSuccess() {
248                if (DEBUG) {
249                    Slog.d(TAG, "Discover peers succeeded.  Requesting peers now.");
250                }
251
252                mDiscoverPeersInProgress = false;
253                requestPeers();
254            }
255
256            @Override
257            public void onFailure(int reason) {
258                if (DEBUG) {
259                    Slog.d(TAG, "Discover peers failed with reason " + reason + ".");
260                }
261
262                if (mDiscoverPeersInProgress) {
263                    if (reason == 0 && mDiscoverPeersRetriesLeft > 0 && mWfdEnabled) {
264                        mHandler.postDelayed(new Runnable() {
265                            @Override
266                            public void run() {
267                                if (mDiscoverPeersInProgress) {
268                                    if (mDiscoverPeersRetriesLeft > 0 && mWfdEnabled) {
269                                        mDiscoverPeersRetriesLeft -= 1;
270                                        if (DEBUG) {
271                                            Slog.d(TAG, "Retrying discovery.  Retries left: "
272                                                    + mDiscoverPeersRetriesLeft);
273                                        }
274                                        tryDiscoverPeers();
275                                    } else {
276                                        handleScanFinished();
277                                        mDiscoverPeersInProgress = false;
278                                    }
279                                }
280                            }
281                        }, DISCOVER_PEERS_RETRY_DELAY_MILLIS);
282                    } else {
283                        handleScanFinished();
284                        mDiscoverPeersInProgress = false;
285                    }
286                }
287            }
288        });
289    }
290
291    private void requestPeers() {
292        mWifiP2pManager.requestPeers(mWifiP2pChannel, new PeerListListener() {
293            @Override
294            public void onPeersAvailable(WifiP2pDeviceList peers) {
295                if (DEBUG) {
296                    Slog.d(TAG, "Received list of peers.");
297                }
298
299                mKnownWifiDisplayPeers.clear();
300                for (WifiP2pDevice device : peers.getDeviceList()) {
301                    if (DEBUG) {
302                        Slog.d(TAG, "  " + describeWifiP2pDevice(device));
303                    }
304
305                    if (isWifiDisplay(device)) {
306                        mKnownWifiDisplayPeers.add(device);
307                    }
308                }
309
310                handleScanFinished();
311            }
312        });
313    }
314
315    private void handleScanStarted() {
316        mHandler.post(new Runnable() {
317            @Override
318            public void run() {
319                mListener.onScanStarted();
320            }
321        });
322    }
323
324    private void handleScanFinished() {
325        final int count = mKnownWifiDisplayPeers.size();
326        final WifiDisplay[] displays = WifiDisplay.CREATOR.newArray(count);
327        for (int i = 0; i < count; i++) {
328            displays[i] = createWifiDisplay(mKnownWifiDisplayPeers.get(i));
329        }
330
331        mHandler.post(new Runnable() {
332            @Override
333            public void run() {
334                mListener.onScanFinished(displays);
335            }
336        });
337    }
338
339    private void connect(final WifiP2pDevice device) {
340        if (mDesiredDevice != null
341                && !mDesiredDevice.deviceAddress.equals(device.deviceAddress)) {
342            if (DEBUG) {
343                Slog.d(TAG, "connect: nothing to do, already connecting to "
344                        + describeWifiP2pDevice(device));
345            }
346            return;
347        }
348
349        if (mConnectedDevice != null
350                && !mConnectedDevice.deviceAddress.equals(device.deviceAddress)
351                && mDesiredDevice == null) {
352            if (DEBUG) {
353                Slog.d(TAG, "connect: nothing to do, already connected to "
354                        + describeWifiP2pDevice(device) + " and not part way through "
355                        + "connecting to a different device.");
356            }
357            return;
358        }
359
360        mDesiredDevice = device;
361        mConnectionRetriesLeft = CONNECT_MAX_RETRIES;
362        updateConnection();
363    }
364
365    private void disconnect() {
366        mDesiredDevice = null;
367        updateConnection();
368    }
369
370    private void retryConnection() {
371        if (mDesiredDevice != null && mConnectedDevice != mDesiredDevice
372                && mConnectionRetriesLeft > 0) {
373            mConnectionRetriesLeft -= 1;
374            Slog.i(TAG, "Retrying Wifi display connection.  Retries left: "
375                    + mConnectionRetriesLeft);
376
377            // Cheap hack.  Make a new instance of the device object so that we
378            // can distinguish it from the previous connection attempt.
379            // This will cause us to tear everything down before we try again.
380            mDesiredDevice = new WifiP2pDevice(mDesiredDevice);
381            updateConnection();
382        }
383    }
384
385    /**
386     * This function is called repeatedly after each asynchronous operation
387     * until all preconditions for the connection have been satisfied and the
388     * connection is established (or not).
389     */
390    private void updateConnection() {
391        // Step 1. Before we try to connect to a new device, tell the system we
392        // have disconnected from the old one.
393        if (mRemoteDisplay != null && mConnectedDevice != mDesiredDevice) {
394            Slog.i(TAG, "Stopped listening for RTSP connection on " + mRemoteDisplayInterface
395                    + " from Wifi display: " + mConnectedDevice.deviceName);
396
397            mRemoteDisplay.dispose();
398            mRemoteDisplay = null;
399            mRemoteDisplayInterface = null;
400            mRemoteDisplayConnected = false;
401            mHandler.removeCallbacks(mRtspTimeout);
402
403            setRemoteSubmixOn(false);
404
405            mHandler.post(new Runnable() {
406                @Override
407                public void run() {
408                    mListener.onDisplayDisconnected();
409                }
410            });
411
412            // continue to next step
413        }
414
415        // Step 2. Before we try to connect to a new device, disconnect from the old one.
416        if (mConnectedDevice != null && mConnectedDevice != mDesiredDevice) {
417            Slog.i(TAG, "Disconnecting from Wifi display: " + mConnectedDevice.deviceName);
418
419            final WifiP2pDevice oldDevice = mConnectedDevice;
420            mWifiP2pManager.removeGroup(mWifiP2pChannel, new ActionListener() {
421                @Override
422                public void onSuccess() {
423                    Slog.i(TAG, "Disconnected from Wifi display: " + oldDevice.deviceName);
424                    next();
425                }
426
427                @Override
428                public void onFailure(int reason) {
429                    Slog.i(TAG, "Failed to disconnect from Wifi display: "
430                            + oldDevice.deviceName + ", reason=" + reason);
431                    next();
432                }
433
434                private void next() {
435                    if (mConnectedDevice == oldDevice) {
436                        mConnectedDevice = null;
437                        updateConnection();
438                    }
439                }
440            });
441            return; // wait for asynchronous callback
442        }
443
444        // Step 3. Before we try to connect to a new device, stop trying to connect
445        // to the old one.
446        if (mConnectingDevice != null && mConnectingDevice != mDesiredDevice) {
447            Slog.i(TAG, "Canceling connection to Wifi display: " + mConnectingDevice.deviceName);
448
449            mHandler.removeCallbacks(mConnectionTimeout);
450
451            final WifiP2pDevice oldDevice = mConnectingDevice;
452            mWifiP2pManager.cancelConnect(mWifiP2pChannel, new ActionListener() {
453                @Override
454                public void onSuccess() {
455                    Slog.i(TAG, "Canceled connection to Wifi display: " + oldDevice.deviceName);
456                    next();
457                }
458
459                @Override
460                public void onFailure(int reason) {
461                    Slog.i(TAG, "Failed to cancel connection to Wifi display: "
462                            + oldDevice.deviceName + ", reason=" + reason);
463                    next();
464                }
465
466                private void next() {
467                    if (mConnectingDevice == oldDevice) {
468                        mConnectingDevice = null;
469                        updateConnection();
470                    }
471                }
472            });
473            return; // wait for asynchronous callback
474        }
475
476        // Step 4. If we wanted to disconnect, then mission accomplished.
477        if (mDesiredDevice == null) {
478            return; // done
479        }
480
481        // Step 5. Try to connect.
482        if (mConnectedDevice == null && mConnectingDevice == null) {
483            Slog.i(TAG, "Connecting to Wifi display: " + mDesiredDevice.deviceName);
484
485            mConnectingDevice = mDesiredDevice;
486            WifiP2pConfig config = new WifiP2pConfig();
487            config.deviceAddress = mConnectingDevice.deviceAddress;
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            setRemoteSubmixOn(true);
533
534            final WifiP2pDevice oldDevice = mConnectedDevice;
535            final int port = getPortNumber(mConnectedDevice);
536            final String iface = addr.getHostAddress() + ":" + port;
537            mRemoteDisplayInterface = iface;
538
539            Slog.i(TAG, "Listening for RTSP connection on " + iface
540                    + " from Wifi display: " + mConnectedDevice.deviceName);
541
542            mRemoteDisplay = RemoteDisplay.listen(iface, new RemoteDisplay.Listener() {
543                @Override
544                public void onDisplayConnected(final Surface surface,
545                        final int width, final int height, final int flags) {
546                    if (mConnectedDevice == oldDevice && !mRemoteDisplayConnected) {
547                        Slog.i(TAG, "Opened RTSP connection with Wifi display: "
548                                + mConnectedDevice.deviceName);
549                        mRemoteDisplayConnected = true;
550                        mHandler.removeCallbacks(mRtspTimeout);
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 setRemoteSubmixOn(boolean on) {
589        if (mRemoteSubmixOn != on) {
590            mRemoteSubmixOn = on;
591            mAudioManager.setRemoteSubmixOn(on, REMOTE_SUBMIX_ADDRESS);
592        }
593    }
594
595    private void handleStateChanged(boolean enabled) {
596        if (mWifiP2pEnabled != enabled) {
597            mWifiP2pEnabled = enabled;
598            if (enabled) {
599                if (!mWfdEnabled) {
600                    enableWfd();
601                }
602            } else {
603                setWfdEnabled(false);
604                disconnect();
605            }
606        }
607    }
608
609    private void handlePeersChanged() {
610        if (mWifiP2pEnabled) {
611            if (mWfdEnabled) {
612                requestPeers();
613            } else {
614                enableWfd();
615            }
616        }
617    }
618
619    private void handleConnectionChanged(NetworkInfo networkInfo) {
620        mNetworkInfo = networkInfo;
621        if (mWfdEnabled && networkInfo.isConnected()) {
622            if (mDesiredDevice != null) {
623                mWifiP2pManager.requestGroupInfo(mWifiP2pChannel, new GroupInfoListener() {
624                    @Override
625                    public void onGroupInfoAvailable(WifiP2pGroup info) {
626                        if (DEBUG) {
627                            Slog.d(TAG, "Received group info: " + describeWifiP2pGroup(info));
628                        }
629
630                        if (mConnectingDevice != null && !info.contains(mConnectingDevice)) {
631                            Slog.i(TAG, "Aborting connection to Wifi display because "
632                                    + "the current P2P group does not contain the device "
633                                    + "we expected to find: " + mConnectingDevice.deviceName);
634                            handleConnectionFailure(false);
635                            return;
636                        }
637
638                        if (mDesiredDevice != null && !info.contains(mDesiredDevice)) {
639                            disconnect();
640                            return;
641                        }
642
643                        if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) {
644                            Slog.i(TAG, "Connected to Wifi display: "
645                                    + mConnectingDevice.deviceName);
646
647                            mHandler.removeCallbacks(mConnectionTimeout);
648                            mConnectedDeviceGroupInfo = info;
649                            mConnectedDevice = mConnectingDevice;
650                            mConnectingDevice = null;
651                            updateConnection();
652                        }
653                    }
654                });
655            }
656        } else {
657            disconnect();
658
659            // After disconnection for a group, for some reason we have a tendency
660            // to get a peer change notification with an empty list of peers.
661            // Perform a fresh scan.
662            if (mWfdEnabled) {
663                requestPeers();
664            }
665        }
666    }
667
668    private final Runnable mConnectionTimeout = new Runnable() {
669        @Override
670        public void run() {
671            if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) {
672                Slog.i(TAG, "Timed out waiting for Wifi display connection after "
673                        + CONNECTION_TIMEOUT_SECONDS + " seconds: "
674                        + mConnectingDevice.deviceName);
675                handleConnectionFailure(true);
676            }
677        }
678    };
679
680    private final Runnable mRtspTimeout = new Runnable() {
681        @Override
682        public void run() {
683            if (mConnectedDevice != null
684                    && mRemoteDisplay != null && !mRemoteDisplayConnected) {
685                Slog.i(TAG, "Timed out waiting for Wifi display RTSP connection after "
686                        + RTSP_TIMEOUT_SECONDS + " seconds: "
687                        + mConnectedDevice.deviceName);
688                handleConnectionFailure(true);
689            }
690        }
691    };
692
693    private void handleConnectionFailure(boolean timeoutOccurred) {
694        Slog.i(TAG, "Wifi display connection failed!");
695
696        mHandler.post(new Runnable() {
697            @Override
698            public void run() {
699                mListener.onDisplayConnectionFailed();
700            }
701        });
702
703        if (mDesiredDevice != null) {
704            if (mConnectionRetriesLeft > 0) {
705                mHandler.postDelayed(new Runnable() {
706                    @Override
707                    public void run() {
708                        retryConnection();
709                    }
710                }, timeoutOccurred ? 0 : CONNECT_RETRY_DELAY_MILLIS);
711            } else {
712                disconnect();
713            }
714        }
715    }
716
717    private static Inet4Address getInterfaceAddress(WifiP2pGroup info) {
718        NetworkInterface iface;
719        try {
720            iface = NetworkInterface.getByName(info.getInterface());
721        } catch (SocketException ex) {
722            Slog.w(TAG, "Could not obtain address of network interface "
723                    + info.getInterface(), ex);
724            return null;
725        }
726
727        Enumeration<InetAddress> addrs = iface.getInetAddresses();
728        while (addrs.hasMoreElements()) {
729            InetAddress addr = addrs.nextElement();
730            if (addr instanceof Inet4Address) {
731                return (Inet4Address)addr;
732            }
733        }
734
735        Slog.w(TAG, "Could not obtain address of network interface "
736                + info.getInterface() + " because it had no IPv4 addresses.");
737        return null;
738    }
739
740    private static int getPortNumber(WifiP2pDevice device) {
741        if (device.deviceName.startsWith("DIRECT-")
742                && device.deviceName.endsWith("Broadcom")) {
743            // These dongles ignore the port we broadcast in our WFD IE.
744            return 8554;
745        }
746        return DEFAULT_CONTROL_PORT;
747    }
748
749    private static boolean isWifiDisplay(WifiP2pDevice device) {
750        return device.wfdInfo != null
751                && device.wfdInfo.isWfdEnabled()
752                && isPrimarySinkDeviceType(device.wfdInfo.getDeviceType());
753    }
754
755    private static boolean isPrimarySinkDeviceType(int deviceType) {
756        return deviceType == WifiP2pWfdInfo.PRIMARY_SINK
757                || deviceType == WifiP2pWfdInfo.SOURCE_OR_PRIMARY_SINK;
758    }
759
760    private static String describeWifiP2pDevice(WifiP2pDevice device) {
761        return device != null ? device.toString().replace('\n', ',') : "null";
762    }
763
764    private static String describeWifiP2pGroup(WifiP2pGroup group) {
765        return group != null ? group.toString().replace('\n', ',') : "null";
766    }
767
768    private static WifiDisplay createWifiDisplay(WifiP2pDevice device) {
769        return new WifiDisplay(device.deviceAddress, device.deviceName);
770    }
771
772    private final BroadcastReceiver mWifiP2pReceiver = new BroadcastReceiver() {
773        @Override
774        public void onReceive(Context context, Intent intent) {
775            final String action = intent.getAction();
776            if (action.equals(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)) {
777                boolean enabled = (intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE,
778                        WifiP2pManager.WIFI_P2P_STATE_DISABLED)) ==
779                        WifiP2pManager.WIFI_P2P_STATE_ENABLED;
780                if (DEBUG) {
781                    Slog.d(TAG, "Received WIFI_P2P_STATE_CHANGED_ACTION: enabled="
782                            + enabled);
783                }
784
785                handleStateChanged(enabled);
786            } else if (action.equals(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)) {
787                if (DEBUG) {
788                    Slog.d(TAG, "Received WIFI_P2P_PEERS_CHANGED_ACTION.");
789                }
790
791                handlePeersChanged();
792            } else if (action.equals(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)) {
793                NetworkInfo networkInfo = (NetworkInfo)intent.getParcelableExtra(
794                        WifiP2pManager.EXTRA_NETWORK_INFO);
795                if (DEBUG) {
796                    Slog.d(TAG, "Received WIFI_P2P_CONNECTION_CHANGED_ACTION: networkInfo="
797                            + networkInfo);
798                }
799
800                handleConnectionChanged(networkInfo);
801            }
802        }
803    };
804
805    /**
806     * Called on the handler thread when displays are connected or disconnected.
807     */
808    public interface Listener {
809        void onEnablementChanged(boolean enabled);
810
811        void onScanStarted();
812        void onScanFinished(WifiDisplay[] knownDisplays);
813
814        void onDisplayConnecting(WifiDisplay display);
815        void onDisplayConnectionFailed();
816        void onDisplayConnected(WifiDisplay display,
817                Surface surface, int width, int height, int flags);
818        void onDisplayDisconnected();
819    }
820}
821