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