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