WifiDisplayAdapter.java revision 255dd04271088590fedc46c8e22b2fd4ab142d39
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.R;
20import com.android.internal.util.DumpUtils;
21import com.android.internal.util.IndentingPrintWriter;
22
23import android.app.Notification;
24import android.app.NotificationManager;
25import android.app.PendingIntent;
26import android.content.BroadcastReceiver;
27import android.content.Context;
28import android.content.Intent;
29import android.content.IntentFilter;
30import android.content.res.Resources;
31import android.hardware.display.DisplayManager;
32import android.hardware.display.WifiDisplay;
33import android.hardware.display.WifiDisplaySessionInfo;
34import android.hardware.display.WifiDisplayStatus;
35import android.media.RemoteDisplay;
36import android.os.Handler;
37import android.os.IBinder;
38import android.os.Looper;
39import android.os.Message;
40import android.os.UserHandle;
41import android.provider.Settings;
42import android.util.Slog;
43import android.view.Display;
44import android.view.Surface;
45import android.view.SurfaceControl;
46
47import java.io.PrintWriter;
48import java.util.Arrays;
49import java.util.List;
50import java.util.ArrayList;
51
52import libcore.util.Objects;
53
54/**
55 * Connects to Wifi displays that implement the Miracast protocol.
56 * <p>
57 * The Wifi display protocol relies on Wifi direct for discovering and pairing
58 * with the display.  Once connected, the Media Server opens an RTSP socket and accepts
59 * a connection from the display.  After session negotiation, the Media Server
60 * streams encoded buffers to the display.
61 * </p><p>
62 * This class is responsible for connecting to Wifi displays and mediating
63 * the interactions between Media Server, Surface Flinger and the Display Manager Service.
64 * </p><p>
65 * Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock.
66 * </p>
67 */
68final class WifiDisplayAdapter extends DisplayAdapter {
69    private static final String TAG = "WifiDisplayAdapter";
70
71    private static final boolean DEBUG = false;
72
73    private static final int MSG_SEND_STATUS_CHANGE_BROADCAST = 1;
74    private static final int MSG_UPDATE_NOTIFICATION = 2;
75
76    private static final String ACTION_DISCONNECT = "android.server.display.wfd.DISCONNECT";
77
78    private final WifiDisplayHandler mHandler;
79    private final PersistentDataStore mPersistentDataStore;
80    private final boolean mSupportsProtectedBuffers;
81    private final NotificationManager mNotificationManager;
82
83    private PendingIntent mSettingsPendingIntent;
84    private PendingIntent mDisconnectPendingIntent;
85
86    private WifiDisplayController mDisplayController;
87    private WifiDisplayDevice mDisplayDevice;
88
89    private WifiDisplayStatus mCurrentStatus;
90    private int mFeatureState;
91    private int mScanState;
92    private int mActiveDisplayState;
93    private WifiDisplay mActiveDisplay;
94    private WifiDisplay[] mDisplays = WifiDisplay.EMPTY_ARRAY;
95    private WifiDisplay[] mAvailableDisplays = WifiDisplay.EMPTY_ARRAY;
96    private WifiDisplay[] mRememberedDisplays = WifiDisplay.EMPTY_ARRAY;
97    private WifiDisplaySessionInfo mSessionInfo;
98
99    private boolean mPendingStatusChangeBroadcast;
100    private boolean mPendingNotificationUpdate;
101
102    // Called with SyncRoot lock held.
103    public WifiDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
104            Context context, Handler handler, Listener listener,
105            PersistentDataStore persistentDataStore) {
106        super(syncRoot, context, handler, listener, TAG);
107        mHandler = new WifiDisplayHandler(handler.getLooper());
108        mPersistentDataStore = persistentDataStore;
109        mSupportsProtectedBuffers = context.getResources().getBoolean(
110                com.android.internal.R.bool.config_wifiDisplaySupportsProtectedBuffers);
111        mNotificationManager = (NotificationManager)context.getSystemService(
112                Context.NOTIFICATION_SERVICE);
113    }
114
115    @Override
116    public void dumpLocked(PrintWriter pw) {
117        super.dumpLocked(pw);
118
119        pw.println("mCurrentStatus=" + getWifiDisplayStatusLocked());
120        pw.println("mFeatureState=" + mFeatureState);
121        pw.println("mScanState=" + mScanState);
122        pw.println("mActiveDisplayState=" + mActiveDisplayState);
123        pw.println("mActiveDisplay=" + mActiveDisplay);
124        pw.println("mDisplays=" + Arrays.toString(mDisplays));
125        pw.println("mAvailableDisplays=" + Arrays.toString(mAvailableDisplays));
126        pw.println("mRememberedDisplays=" + Arrays.toString(mRememberedDisplays));
127        pw.println("mPendingStatusChangeBroadcast=" + mPendingStatusChangeBroadcast);
128        pw.println("mPendingNotificationUpdate=" + mPendingNotificationUpdate);
129        pw.println("mSupportsProtectedBuffers=" + mSupportsProtectedBuffers);
130
131        // Try to dump the controller state.
132        if (mDisplayController == null) {
133            pw.println("mDisplayController=null");
134        } else {
135            pw.println("mDisplayController:");
136            final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
137            ipw.increaseIndent();
138            DumpUtils.dumpAsync(getHandler(), mDisplayController, ipw, 200);
139        }
140    }
141
142    @Override
143    public void registerLocked() {
144        super.registerLocked();
145
146        updateRememberedDisplaysLocked();
147
148        getHandler().post(new Runnable() {
149            @Override
150            public void run() {
151                mDisplayController = new WifiDisplayController(
152                        getContext(), getHandler(), mWifiDisplayListener);
153
154                getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
155                        new IntentFilter(ACTION_DISCONNECT), null, mHandler);
156            }
157        });
158    }
159
160    public void requestStartScanLocked() {
161        if (DEBUG) {
162            Slog.d(TAG, "requestStartScanLocked");
163        }
164
165        getHandler().post(new Runnable() {
166            @Override
167            public void run() {
168                if (mDisplayController != null) {
169                    mDisplayController.requestStartScan();
170                }
171            }
172        });
173    }
174
175    public void requestStopScanLocked() {
176        if (DEBUG) {
177            Slog.d(TAG, "requestStopScanLocked");
178        }
179
180        getHandler().post(new Runnable() {
181            @Override
182            public void run() {
183                if (mDisplayController != null) {
184                    mDisplayController.requestStopScan();
185                }
186            }
187        });
188    }
189
190    public void requestConnectLocked(final String address) {
191        if (DEBUG) {
192            Slog.d(TAG, "requestConnectLocked: address=" + address);
193        }
194
195        getHandler().post(new Runnable() {
196            @Override
197            public void run() {
198                if (mDisplayController != null) {
199                    mDisplayController.requestConnect(address);
200                }
201            }
202        });
203    }
204
205    public void requestPauseLocked() {
206        if (DEBUG) {
207            Slog.d(TAG, "requestPauseLocked");
208        }
209
210        getHandler().post(new Runnable() {
211            @Override
212            public void run() {
213                if (mDisplayController != null) {
214                    mDisplayController.requestPause();
215                }
216            }
217        });
218      }
219
220    public void requestResumeLocked() {
221        if (DEBUG) {
222            Slog.d(TAG, "requestResumeLocked");
223        }
224
225        getHandler().post(new Runnable() {
226            @Override
227            public void run() {
228                if (mDisplayController != null) {
229                    mDisplayController.requestResume();
230                }
231            }
232        });
233    }
234
235    public void requestDisconnectLocked() {
236        if (DEBUG) {
237            Slog.d(TAG, "requestDisconnectedLocked");
238        }
239
240        getHandler().post(new Runnable() {
241            @Override
242            public void run() {
243                if (mDisplayController != null) {
244                    mDisplayController.requestDisconnect();
245                }
246            }
247        });
248    }
249
250    public void requestRenameLocked(String address, String alias) {
251        if (DEBUG) {
252            Slog.d(TAG, "requestRenameLocked: address=" + address + ", alias=" + alias);
253        }
254
255        if (alias != null) {
256            alias = alias.trim();
257            if (alias.isEmpty() || alias.equals(address)) {
258                alias = null;
259            }
260        }
261
262        WifiDisplay display = mPersistentDataStore.getRememberedWifiDisplay(address);
263        if (display != null && !Objects.equal(display.getDeviceAlias(), alias)) {
264            display = new WifiDisplay(address, display.getDeviceName(), alias,
265                    false, false, false);
266            if (mPersistentDataStore.rememberWifiDisplay(display)) {
267                mPersistentDataStore.saveIfNeeded();
268                updateRememberedDisplaysLocked();
269                scheduleStatusChangedBroadcastLocked();
270            }
271        }
272
273        if (mActiveDisplay != null && mActiveDisplay.getDeviceAddress().equals(address)) {
274            renameDisplayDeviceLocked(mActiveDisplay.getFriendlyDisplayName());
275        }
276    }
277
278    public void requestForgetLocked(String address) {
279        if (DEBUG) {
280            Slog.d(TAG, "requestForgetLocked: address=" + address);
281        }
282
283        if (mPersistentDataStore.forgetWifiDisplay(address)) {
284            mPersistentDataStore.saveIfNeeded();
285            updateRememberedDisplaysLocked();
286            scheduleStatusChangedBroadcastLocked();
287        }
288
289        if (mActiveDisplay != null && mActiveDisplay.getDeviceAddress().equals(address)) {
290            requestDisconnectLocked();
291        }
292    }
293
294    public WifiDisplayStatus getWifiDisplayStatusLocked() {
295        if (mCurrentStatus == null) {
296            mCurrentStatus = new WifiDisplayStatus(
297                    mFeatureState, mScanState, mActiveDisplayState,
298                    mActiveDisplay, mDisplays, mSessionInfo);
299        }
300
301        if (DEBUG) {
302            Slog.d(TAG, "getWifiDisplayStatusLocked: result=" + mCurrentStatus);
303        }
304        return mCurrentStatus;
305    }
306
307    private void updateDisplaysLocked() {
308        List<WifiDisplay> displays = new ArrayList<WifiDisplay>(
309                mAvailableDisplays.length + mRememberedDisplays.length);
310        boolean[] remembered = new boolean[mAvailableDisplays.length];
311        for (WifiDisplay d : mRememberedDisplays) {
312            boolean available = false;
313            for (int i = 0; i < mAvailableDisplays.length; i++) {
314                if (d.equals(mAvailableDisplays[i])) {
315                    remembered[i] = available = true;
316                    break;
317                }
318            }
319            if (!available) {
320                displays.add(new WifiDisplay(d.getDeviceAddress(), d.getDeviceName(),
321                        d.getDeviceAlias(), false, false, true));
322            }
323        }
324        for (int i = 0; i < mAvailableDisplays.length; i++) {
325            WifiDisplay d = mAvailableDisplays[i];
326            displays.add(new WifiDisplay(d.getDeviceAddress(), d.getDeviceName(),
327                    d.getDeviceAlias(), true, d.canConnect(), remembered[i]));
328        }
329        mDisplays = displays.toArray(WifiDisplay.EMPTY_ARRAY);
330    }
331
332    private void updateRememberedDisplaysLocked() {
333        mRememberedDisplays = mPersistentDataStore.getRememberedWifiDisplays();
334        mActiveDisplay = mPersistentDataStore.applyWifiDisplayAlias(mActiveDisplay);
335        mAvailableDisplays = mPersistentDataStore.applyWifiDisplayAliases(mAvailableDisplays);
336        updateDisplaysLocked();
337    }
338
339    private void fixRememberedDisplayNamesFromAvailableDisplaysLocked() {
340        // It may happen that a display name has changed since it was remembered.
341        // Consult the list of available displays and update the name if needed.
342        // We don't do anything special for the active display here.  The display
343        // controller will send a separate event when it needs to be updates.
344        boolean changed = false;
345        for (int i = 0; i < mRememberedDisplays.length; i++) {
346            WifiDisplay rememberedDisplay = mRememberedDisplays[i];
347            WifiDisplay availableDisplay = findAvailableDisplayLocked(
348                    rememberedDisplay.getDeviceAddress());
349            if (availableDisplay != null && !rememberedDisplay.equals(availableDisplay)) {
350                if (DEBUG) {
351                    Slog.d(TAG, "fixRememberedDisplayNamesFromAvailableDisplaysLocked: "
352                            + "updating remembered display to " + availableDisplay);
353                }
354                mRememberedDisplays[i] = availableDisplay;
355                changed |= mPersistentDataStore.rememberWifiDisplay(availableDisplay);
356            }
357        }
358        if (changed) {
359            mPersistentDataStore.saveIfNeeded();
360        }
361    }
362
363    private WifiDisplay findAvailableDisplayLocked(String address) {
364        for (WifiDisplay display : mAvailableDisplays) {
365            if (display.getDeviceAddress().equals(address)) {
366                return display;
367            }
368        }
369        return null;
370    }
371
372    private void addDisplayDeviceLocked(WifiDisplay display,
373            Surface surface, int width, int height, int flags) {
374        removeDisplayDeviceLocked();
375
376        if (mPersistentDataStore.rememberWifiDisplay(display)) {
377            mPersistentDataStore.saveIfNeeded();
378            updateRememberedDisplaysLocked();
379            scheduleStatusChangedBroadcastLocked();
380        }
381
382        boolean secure = (flags & RemoteDisplay.DISPLAY_FLAG_SECURE) != 0;
383        int deviceFlags = DisplayDeviceInfo.FLAG_PRESENTATION;
384        if (secure) {
385            deviceFlags |= DisplayDeviceInfo.FLAG_SECURE;
386            if (mSupportsProtectedBuffers) {
387                deviceFlags |= DisplayDeviceInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS;
388            }
389        }
390
391        float refreshRate = 60.0f; // TODO: get this for real
392
393        String name = display.getFriendlyDisplayName();
394        String address = display.getDeviceAddress();
395        IBinder displayToken = SurfaceControl.createDisplay(name, secure);
396        mDisplayDevice = new WifiDisplayDevice(displayToken, name, width, height,
397                refreshRate, deviceFlags, address, surface);
398        sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_ADDED);
399    }
400
401    private void removeDisplayDeviceLocked() {
402        if (mDisplayDevice != null) {
403            mDisplayDevice.destroyLocked();
404            sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_REMOVED);
405            mDisplayDevice = null;
406        }
407    }
408
409    private void renameDisplayDeviceLocked(String name) {
410        if (mDisplayDevice != null && !mDisplayDevice.getNameLocked().equals(name)) {
411            mDisplayDevice.setNameLocked(name);
412            sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_CHANGED);
413        }
414    }
415
416    private void scheduleStatusChangedBroadcastLocked() {
417        mCurrentStatus = null;
418        if (!mPendingStatusChangeBroadcast) {
419            mPendingStatusChangeBroadcast = true;
420            mHandler.sendEmptyMessage(MSG_SEND_STATUS_CHANGE_BROADCAST);
421        }
422    }
423
424    private void scheduleUpdateNotificationLocked() {
425        if (!mPendingNotificationUpdate) {
426            mPendingNotificationUpdate = true;
427            mHandler.sendEmptyMessage(MSG_UPDATE_NOTIFICATION);
428        }
429    }
430
431    // Runs on the handler.
432    private void handleSendStatusChangeBroadcast() {
433        final Intent intent;
434        synchronized (getSyncRoot()) {
435            if (!mPendingStatusChangeBroadcast) {
436                return;
437            }
438
439            mPendingStatusChangeBroadcast = false;
440            intent = new Intent(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED);
441            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
442            intent.putExtra(DisplayManager.EXTRA_WIFI_DISPLAY_STATUS,
443                    getWifiDisplayStatusLocked());
444        }
445
446        // Send protected broadcast about wifi display status to registered receivers.
447        getContext().sendBroadcastAsUser(intent, UserHandle.ALL);
448    }
449
450    // Runs on the handler.
451    private void handleUpdateNotification() {
452        final int state;
453        final WifiDisplay display;
454        synchronized (getSyncRoot()) {
455            if (!mPendingNotificationUpdate) {
456                return;
457            }
458
459            mPendingNotificationUpdate = false;
460            state = mActiveDisplayState;
461            display = mActiveDisplay;
462        }
463
464        // Cancel the old notification if there is one.
465        mNotificationManager.cancelAsUser(null,
466                R.string.wifi_display_notification_disconnect, UserHandle.ALL);
467
468        if (state == WifiDisplayStatus.DISPLAY_STATE_CONNECTING
469                || state == WifiDisplayStatus.DISPLAY_STATE_CONNECTED) {
470            Context context = getContext();
471
472            // Initialize pending intents for the notification outside of the lock because
473            // creating a pending intent requires a call into the activity manager.
474            if (mSettingsPendingIntent == null) {
475                Intent settingsIntent = new Intent(Settings.ACTION_WIFI_DISPLAY_SETTINGS);
476                settingsIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
477                        | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
478                        | Intent.FLAG_ACTIVITY_CLEAR_TOP);
479                mSettingsPendingIntent = PendingIntent.getActivityAsUser(
480                        context, 0, settingsIntent, 0, null, UserHandle.CURRENT);
481            }
482
483            if (mDisconnectPendingIntent == null) {
484                Intent disconnectIntent = new Intent(ACTION_DISCONNECT);
485                mDisconnectPendingIntent = PendingIntent.getBroadcastAsUser(
486                        context, 0, disconnectIntent, 0, UserHandle.CURRENT);
487            }
488
489            // Post the notification.
490            Resources r = context.getResources();
491            Notification notification;
492            if (state == WifiDisplayStatus.DISPLAY_STATE_CONNECTING) {
493                notification = new Notification.Builder(context)
494                        .setContentTitle(r.getString(
495                                R.string.wifi_display_notification_connecting_title))
496                        .setContentText(r.getString(
497                                R.string.wifi_display_notification_connecting_message,
498                                display.getFriendlyDisplayName()))
499                        .setContentIntent(mSettingsPendingIntent)
500                        .setSmallIcon(R.drawable.ic_notification_cast_connecting)
501                        .setOngoing(true)
502                        .addAction(android.R.drawable.ic_menu_close_clear_cancel,
503                                r.getString(R.string.wifi_display_notification_disconnect),
504                                mDisconnectPendingIntent)
505                        .setColor(r.getColor(
506                                com.android.internal.R.color.system_notification_accent_color))
507                        .build();
508            } else {
509                notification = new Notification.Builder(context)
510                        .setContentTitle(r.getString(
511                                R.string.wifi_display_notification_connected_title))
512                        .setContentText(r.getString(
513                                R.string.wifi_display_notification_connected_message,
514                                display.getFriendlyDisplayName()))
515                        .setContentIntent(mSettingsPendingIntent)
516                        .setSmallIcon(R.drawable.ic_notification_cast_on)
517                        .setOngoing(true)
518                        .addAction(android.R.drawable.ic_menu_close_clear_cancel,
519                                r.getString(R.string.wifi_display_notification_disconnect),
520                                mDisconnectPendingIntent)
521                        .setColor(r.getColor(
522                                com.android.internal.R.color.system_notification_accent_color))
523                        .build();
524            }
525            mNotificationManager.notifyAsUser(null,
526                    R.string.wifi_display_notification_disconnect,
527                    notification, UserHandle.ALL);
528        }
529    }
530
531    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
532        @Override
533        public void onReceive(Context context, Intent intent) {
534            if (intent.getAction().equals(ACTION_DISCONNECT)) {
535                synchronized (getSyncRoot()) {
536                    requestDisconnectLocked();
537                }
538            }
539        }
540    };
541
542    private final WifiDisplayController.Listener mWifiDisplayListener =
543            new WifiDisplayController.Listener() {
544        @Override
545        public void onFeatureStateChanged(int featureState) {
546            synchronized (getSyncRoot()) {
547                if (mFeatureState != featureState) {
548                    mFeatureState = featureState;
549                    scheduleStatusChangedBroadcastLocked();
550                }
551            }
552        }
553
554        @Override
555        public void onScanStarted() {
556            synchronized (getSyncRoot()) {
557                if (mScanState != WifiDisplayStatus.SCAN_STATE_SCANNING) {
558                    mScanState = WifiDisplayStatus.SCAN_STATE_SCANNING;
559                    scheduleStatusChangedBroadcastLocked();
560                }
561            }
562        }
563
564        @Override
565        public void onScanResults(WifiDisplay[] availableDisplays) {
566            synchronized (getSyncRoot()) {
567                availableDisplays = mPersistentDataStore.applyWifiDisplayAliases(
568                        availableDisplays);
569
570                boolean changed = !Arrays.equals(mAvailableDisplays, availableDisplays);
571
572                // Check whether any of the available displays changed canConnect status.
573                for (int i = 0; !changed && i<availableDisplays.length; i++) {
574                    changed = availableDisplays[i].canConnect()
575                            != mAvailableDisplays[i].canConnect();
576                }
577
578                if (changed) {
579                    mAvailableDisplays = availableDisplays;
580                    fixRememberedDisplayNamesFromAvailableDisplaysLocked();
581                    updateDisplaysLocked();
582                    scheduleStatusChangedBroadcastLocked();
583                }
584            }
585        }
586
587        @Override
588        public void onScanFinished() {
589            synchronized (getSyncRoot()) {
590                if (mScanState != WifiDisplayStatus.SCAN_STATE_NOT_SCANNING) {
591                    mScanState = WifiDisplayStatus.SCAN_STATE_NOT_SCANNING;
592                    scheduleStatusChangedBroadcastLocked();
593                }
594            }
595        }
596
597        @Override
598        public void onDisplayConnecting(WifiDisplay display) {
599            synchronized (getSyncRoot()) {
600                display = mPersistentDataStore.applyWifiDisplayAlias(display);
601
602                if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_CONNECTING
603                        || mActiveDisplay == null
604                        || !mActiveDisplay.equals(display)) {
605                    mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_CONNECTING;
606                    mActiveDisplay = display;
607                    scheduleStatusChangedBroadcastLocked();
608                    scheduleUpdateNotificationLocked();
609                }
610            }
611        }
612
613        @Override
614        public void onDisplayConnectionFailed() {
615            synchronized (getSyncRoot()) {
616                if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED
617                        || mActiveDisplay != null) {
618                    mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED;
619                    mActiveDisplay = null;
620                    scheduleStatusChangedBroadcastLocked();
621                    scheduleUpdateNotificationLocked();
622                }
623            }
624        }
625
626        @Override
627        public void onDisplayConnected(WifiDisplay display, Surface surface,
628                int width, int height, int flags) {
629            synchronized (getSyncRoot()) {
630                display = mPersistentDataStore.applyWifiDisplayAlias(display);
631                addDisplayDeviceLocked(display, surface, width, height, flags);
632
633                if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_CONNECTED
634                        || mActiveDisplay == null
635                        || !mActiveDisplay.equals(display)) {
636                    mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_CONNECTED;
637                    mActiveDisplay = display;
638                    scheduleStatusChangedBroadcastLocked();
639                    scheduleUpdateNotificationLocked();
640                }
641            }
642        }
643
644        @Override
645        public void onDisplaySessionInfo(WifiDisplaySessionInfo sessionInfo) {
646            synchronized (getSyncRoot()) {
647                mSessionInfo = sessionInfo;
648                scheduleStatusChangedBroadcastLocked();
649            }
650        }
651
652        @Override
653        public void onDisplayChanged(WifiDisplay display) {
654            synchronized (getSyncRoot()) {
655                display = mPersistentDataStore.applyWifiDisplayAlias(display);
656                if (mActiveDisplay != null
657                        && mActiveDisplay.hasSameAddress(display)
658                        && !mActiveDisplay.equals(display)) {
659                    mActiveDisplay = display;
660                    renameDisplayDeviceLocked(display.getFriendlyDisplayName());
661                    scheduleStatusChangedBroadcastLocked();
662                    scheduleUpdateNotificationLocked();
663                }
664            }
665        }
666
667        @Override
668        public void onDisplayDisconnected() {
669            // Stop listening.
670            synchronized (getSyncRoot()) {
671                removeDisplayDeviceLocked();
672
673                if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED
674                        || mActiveDisplay != null) {
675                    mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED;
676                    mActiveDisplay = null;
677                    scheduleStatusChangedBroadcastLocked();
678                    scheduleUpdateNotificationLocked();
679                }
680            }
681        }
682    };
683
684    private final class WifiDisplayDevice extends DisplayDevice {
685        private String mName;
686        private final int mWidth;
687        private final int mHeight;
688        private final float mRefreshRate;
689        private final int mFlags;
690        private final String mAddress;
691
692        private Surface mSurface;
693        private DisplayDeviceInfo mInfo;
694
695        public WifiDisplayDevice(IBinder displayToken, String name,
696                int width, int height, float refreshRate, int flags, String address,
697                Surface surface) {
698            super(WifiDisplayAdapter.this, displayToken);
699            mName = name;
700            mWidth = width;
701            mHeight = height;
702            mRefreshRate = refreshRate;
703            mFlags = flags;
704            mAddress = address;
705            mSurface = surface;
706        }
707
708        public void destroyLocked() {
709            if (mSurface != null) {
710                mSurface.release();
711                mSurface = null;
712            }
713            SurfaceControl.destroyDisplay(getDisplayTokenLocked());
714        }
715
716        public void setNameLocked(String name) {
717            mName = name;
718            mInfo = null;
719        }
720
721        @Override
722        public void performTraversalInTransactionLocked() {
723            if (mSurface != null) {
724                setSurfaceInTransactionLocked(mSurface);
725            }
726        }
727
728        @Override
729        public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
730            if (mInfo == null) {
731                mInfo = new DisplayDeviceInfo();
732                mInfo.name = mName;
733                mInfo.width = mWidth;
734                mInfo.height = mHeight;
735                mInfo.refreshRate = mRefreshRate;
736                mInfo.presentationDeadlineNanos = 1000000000L / (int) mRefreshRate; // 1 frame
737                mInfo.flags = mFlags;
738                mInfo.type = Display.TYPE_WIFI;
739                mInfo.address = mAddress;
740                mInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL;
741                mInfo.setAssumedDensityForExternalDisplay(mWidth, mHeight);
742            }
743            return mInfo;
744        }
745    }
746
747    private final class WifiDisplayHandler extends Handler {
748        public WifiDisplayHandler(Looper looper) {
749            super(looper, null, true /*async*/);
750        }
751
752        @Override
753        public void handleMessage(Message msg) {
754            switch (msg.what) {
755                case MSG_SEND_STATUS_CHANGE_BROADCAST:
756                    handleSendStatusChangeBroadcast();
757                    break;
758
759                case MSG_UPDATE_NOTIFICATION:
760                    handleUpdateNotification();
761                    break;
762            }
763        }
764    }
765}
766