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