WifiDisplayController.java revision 88469e56c8294a928e65398352e8444d66bdb75a
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 = true;
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
487            final WifiDisplay display = createWifiDisplay(mConnectingDevice);
488            mHandler.post(new Runnable() {
489                @Override
490                public void run() {
491                    mListener.onDisplayConnecting(display);
492                }
493            });
494
495            final WifiP2pDevice newDevice = mDesiredDevice;
496            mWifiP2pManager.connect(mWifiP2pChannel, config, new ActionListener() {
497                @Override
498                public void onSuccess() {
499                    // The connection may not yet be established.  We still need to wait
500                    // for WIFI_P2P_CONNECTION_CHANGED_ACTION.  However, we might never
501                    // get that broadcast, so we register a timeout.
502                    Slog.i(TAG, "Initiated connection to Wifi display: " + newDevice.deviceName);
503
504                    mHandler.postDelayed(mConnectionTimeout, CONNECTION_TIMEOUT_SECONDS * 1000);
505                }
506
507                @Override
508                public void onFailure(int reason) {
509                    if (mConnectingDevice == newDevice) {
510                        Slog.i(TAG, "Failed to initiate connection to Wifi display: "
511                                + newDevice.deviceName + ", reason=" + reason);
512                        mConnectingDevice = null;
513                        handleConnectionFailure(false);
514                    }
515                }
516            });
517            return; // wait for asynchronous callback
518        }
519
520        // Step 6. Listen for incoming connections.
521        if (mConnectedDevice != null && mRemoteDisplay == null) {
522            Inet4Address addr = getInterfaceAddress(mConnectedDeviceGroupInfo);
523            if (addr == null) {
524                Slog.i(TAG, "Failed to get local interface address for communicating "
525                        + "with Wifi display: " + mConnectedDevice.deviceName);
526                handleConnectionFailure(false);
527                return; // done
528            }
529
530            final WifiP2pDevice oldDevice = mConnectedDevice;
531            final int port = getPortNumber(mConnectedDevice);
532            final String iface = addr.getHostAddress() + ":" + port;
533            mRemoteDisplayInterface = iface;
534
535            Slog.i(TAG, "Listening for RTSP connection on " + iface
536                    + " from Wifi display: " + mConnectedDevice.deviceName);
537
538            mRemoteDisplay = RemoteDisplay.listen(iface, new RemoteDisplay.Listener() {
539                @Override
540                public void onDisplayConnected(final Surface surface,
541                        final int width, final int height, final int flags) {
542                    if (mConnectedDevice == oldDevice && !mRemoteDisplayConnected) {
543                        Slog.i(TAG, "Opened RTSP connection with Wifi display: "
544                                + mConnectedDevice.deviceName);
545                        mRemoteDisplayConnected = true;
546                        mHandler.removeCallbacks(mRtspTimeout);
547
548                        mAudioManager.setRemoteSubmixOn(true, REMOTE_SUBMIX_ADDRESS);
549
550                        final WifiDisplay display = createWifiDisplay(mConnectedDevice);
551                        mHandler.post(new Runnable() {
552                            @Override
553                            public void run() {
554                                mListener.onDisplayConnected(display,
555                                        surface, width, height, flags);
556                            }
557                        });
558                    }
559                }
560
561                @Override
562                public void onDisplayDisconnected() {
563                    if (mConnectedDevice == oldDevice) {
564                        Slog.i(TAG, "Closed RTSP connection with Wifi display: "
565                                + mConnectedDevice.deviceName);
566                        mHandler.removeCallbacks(mRtspTimeout);
567                        disconnect();
568                    }
569                }
570
571                @Override
572                public void onDisplayError(int error) {
573                    if (mConnectedDevice == oldDevice) {
574                        Slog.i(TAG, "Lost RTSP connection with Wifi display due to error "
575                                + error + ": " + mConnectedDevice.deviceName);
576                        mHandler.removeCallbacks(mRtspTimeout);
577                        handleConnectionFailure(false);
578                    }
579                }
580            }, mHandler);
581
582            mHandler.postDelayed(mRtspTimeout, RTSP_TIMEOUT_SECONDS * 1000);
583        }
584    }
585
586    private void handleStateChanged(boolean enabled) {
587        if (mWifiP2pEnabled != enabled) {
588            mWifiP2pEnabled = enabled;
589            if (enabled) {
590                if (!mWfdEnabled) {
591                    enableWfd();
592                }
593            } else {
594                setWfdEnabled(false);
595                disconnect();
596            }
597        }
598    }
599
600    private void handlePeersChanged() {
601        if (mWifiP2pEnabled) {
602            if (mWfdEnabled) {
603                requestPeers();
604            } else {
605                enableWfd();
606            }
607        }
608    }
609
610    private void handleConnectionChanged(NetworkInfo networkInfo) {
611        mNetworkInfo = networkInfo;
612        if (mWfdEnabled && networkInfo.isConnected()) {
613            if (mDesiredDevice != null) {
614                mWifiP2pManager.requestGroupInfo(mWifiP2pChannel, new GroupInfoListener() {
615                    @Override
616                    public void onGroupInfoAvailable(WifiP2pGroup info) {
617                        if (DEBUG) {
618                            Slog.d(TAG, "Received group info: " + describeWifiP2pGroup(info));
619                        }
620
621                        if (mConnectingDevice != null && !info.contains(mConnectingDevice)) {
622                            Slog.i(TAG, "Aborting connection to Wifi display because "
623                                    + "the current P2P group does not contain the device "
624                                    + "we expected to find: " + mConnectingDevice.deviceName);
625                            handleConnectionFailure(false);
626                            return;
627                        }
628
629                        if (mDesiredDevice != null && !info.contains(mDesiredDevice)) {
630                            disconnect();
631                            return;
632                        }
633
634                        if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) {
635                            Slog.i(TAG, "Connected to Wifi display: "
636                                    + mConnectingDevice.deviceName);
637
638                            mHandler.removeCallbacks(mConnectionTimeout);
639                            mConnectedDeviceGroupInfo = info;
640                            mConnectedDevice = mConnectingDevice;
641                            mConnectingDevice = null;
642                            updateConnection();
643                        }
644                    }
645                });
646            }
647        } else {
648            disconnect();
649
650            // After disconnection for a group, for some reason we have a tendency
651            // to get a peer change notification with an empty list of peers.
652            // Perform a fresh scan.
653            if (mWfdEnabled) {
654                requestPeers();
655            }
656        }
657    }
658
659    private final Runnable mConnectionTimeout = new Runnable() {
660        @Override
661        public void run() {
662            if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) {
663                Slog.i(TAG, "Timed out waiting for Wifi display connection after "
664                        + CONNECTION_TIMEOUT_SECONDS + " seconds: "
665                        + mConnectingDevice.deviceName);
666                handleConnectionFailure(true);
667            }
668        }
669    };
670
671    private final Runnable mRtspTimeout = new Runnable() {
672        @Override
673        public void run() {
674            if (mConnectedDevice != null
675                    && mRemoteDisplay != null && !mRemoteDisplayConnected) {
676                Slog.i(TAG, "Timed out waiting for Wifi display RTSP connection after "
677                        + RTSP_TIMEOUT_SECONDS + " seconds: "
678                        + mConnectedDevice.deviceName);
679                handleConnectionFailure(true);
680            }
681        }
682    };
683
684    private void handleConnectionFailure(boolean timeoutOccurred) {
685        Slog.i(TAG, "Wifi display connection failed!");
686
687        mHandler.post(new Runnable() {
688            @Override
689            public void run() {
690                mListener.onDisplayConnectionFailed();
691            }
692        });
693
694        if (mDesiredDevice != null) {
695            if (mConnectionRetriesLeft > 0) {
696                mHandler.postDelayed(new Runnable() {
697                    @Override
698                    public void run() {
699                        retryConnection();
700                    }
701                }, timeoutOccurred ? 0 : CONNECT_RETRY_DELAY_MILLIS);
702            } else {
703                disconnect();
704            }
705        }
706    }
707
708    private static Inet4Address getInterfaceAddress(WifiP2pGroup info) {
709        NetworkInterface iface;
710        try {
711            iface = NetworkInterface.getByName(info.getInterface());
712        } catch (SocketException ex) {
713            Slog.w(TAG, "Could not obtain address of network interface "
714                    + info.getInterface(), ex);
715            return null;
716        }
717
718        Enumeration<InetAddress> addrs = iface.getInetAddresses();
719        while (addrs.hasMoreElements()) {
720            InetAddress addr = addrs.nextElement();
721            if (addr instanceof Inet4Address) {
722                return (Inet4Address)addr;
723            }
724        }
725
726        Slog.w(TAG, "Could not obtain address of network interface "
727                + info.getInterface() + " because it had no IPv4 addresses.");
728        return null;
729    }
730
731    private static int getPortNumber(WifiP2pDevice device) {
732        if (device.deviceName.startsWith("DIRECT-")
733                && device.deviceName.endsWith("Broadcom")) {
734            // These dongles ignore the port we broadcast in our WFD IE.
735            return 8554;
736        }
737        return DEFAULT_CONTROL_PORT;
738    }
739
740    private static boolean isWifiDisplay(WifiP2pDevice device) {
741        return device.wfdInfo != null
742                && device.wfdInfo.isWfdEnabled()
743                && isPrimarySinkDeviceType(device.wfdInfo.getDeviceType());
744    }
745
746    private static boolean isPrimarySinkDeviceType(int deviceType) {
747        return deviceType == WifiP2pWfdInfo.PRIMARY_SINK
748                || deviceType == WifiP2pWfdInfo.SOURCE_OR_PRIMARY_SINK;
749    }
750
751    private static String describeWifiP2pDevice(WifiP2pDevice device) {
752        return device != null ? device.toString().replace('\n', ',') : "null";
753    }
754
755    private static String describeWifiP2pGroup(WifiP2pGroup group) {
756        return group != null ? group.toString().replace('\n', ',') : "null";
757    }
758
759    private static WifiDisplay createWifiDisplay(WifiP2pDevice device) {
760        return new WifiDisplay(device.deviceAddress, device.deviceName);
761    }
762
763    private final BroadcastReceiver mWifiP2pReceiver = new BroadcastReceiver() {
764        @Override
765        public void onReceive(Context context, Intent intent) {
766            final String action = intent.getAction();
767            if (action.equals(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)) {
768                boolean enabled = (intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE,
769                        WifiP2pManager.WIFI_P2P_STATE_DISABLED)) ==
770                        WifiP2pManager.WIFI_P2P_STATE_ENABLED;
771                if (DEBUG) {
772                    Slog.d(TAG, "Received WIFI_P2P_STATE_CHANGED_ACTION: enabled="
773                            + enabled);
774                }
775
776                handleStateChanged(enabled);
777            } else if (action.equals(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)) {
778                if (DEBUG) {
779                    Slog.d(TAG, "Received WIFI_P2P_PEERS_CHANGED_ACTION.");
780                }
781
782                handlePeersChanged();
783            } else if (action.equals(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)) {
784                NetworkInfo networkInfo = (NetworkInfo)intent.getParcelableExtra(
785                        WifiP2pManager.EXTRA_NETWORK_INFO);
786                if (DEBUG) {
787                    Slog.d(TAG, "Received WIFI_P2P_CONNECTION_CHANGED_ACTION: networkInfo="
788                            + networkInfo);
789                }
790
791                handleConnectionChanged(networkInfo);
792            }
793        }
794    };
795
796    /**
797     * Called on the handler thread when displays are connected or disconnected.
798     */
799    public interface Listener {
800        void onEnablementChanged(boolean enabled);
801
802        void onScanStarted();
803        void onScanFinished(WifiDisplay[] knownDisplays);
804
805        void onDisplayConnecting(WifiDisplay display);
806        void onDisplayConnectionFailed();
807        void onDisplayConnected(WifiDisplay display,
808                Surface surface, int width, int height, int flags);
809        void onDisplayDisconnected();
810    }
811}
812