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