WifiDisplayAdapter.java revision e8b1aeb51e1e5da64f1d4fd40f2ee1e815886fe5
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                        .build();
506            } else {
507                notification = new Notification.Builder(context)
508                        .setContentTitle(r.getString(
509                                R.string.wifi_display_notification_connected_title))
510                        .setContentText(r.getString(
511                                R.string.wifi_display_notification_connected_message,
512                                display.getFriendlyDisplayName()))
513                        .setContentIntent(mSettingsPendingIntent)
514                        .setSmallIcon(R.drawable.ic_notification_cast_on)
515                        .setOngoing(true)
516                        .addAction(android.R.drawable.ic_menu_close_clear_cancel,
517                                r.getString(R.string.wifi_display_notification_disconnect),
518                                mDisconnectPendingIntent)
519                        .build();
520            }
521            mNotificationManager.notifyAsUser(null,
522                    R.string.wifi_display_notification_disconnect,
523                    notification, UserHandle.ALL);
524        }
525    }
526
527    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
528        @Override
529        public void onReceive(Context context, Intent intent) {
530            if (intent.getAction().equals(ACTION_DISCONNECT)) {
531                synchronized (getSyncRoot()) {
532                    requestDisconnectLocked();
533                }
534            }
535        }
536    };
537
538    private final WifiDisplayController.Listener mWifiDisplayListener =
539            new WifiDisplayController.Listener() {
540        @Override
541        public void onFeatureStateChanged(int featureState) {
542            synchronized (getSyncRoot()) {
543                if (mFeatureState != featureState) {
544                    mFeatureState = featureState;
545                    scheduleStatusChangedBroadcastLocked();
546                }
547            }
548        }
549
550        @Override
551        public void onScanStarted() {
552            synchronized (getSyncRoot()) {
553                if (mScanState != WifiDisplayStatus.SCAN_STATE_SCANNING) {
554                    mScanState = WifiDisplayStatus.SCAN_STATE_SCANNING;
555                    scheduleStatusChangedBroadcastLocked();
556                }
557            }
558        }
559
560        @Override
561        public void onScanResults(WifiDisplay[] availableDisplays) {
562            synchronized (getSyncRoot()) {
563                availableDisplays = mPersistentDataStore.applyWifiDisplayAliases(
564                        availableDisplays);
565
566                boolean changed = !Arrays.equals(mAvailableDisplays, availableDisplays);
567
568                // Check whether any of the available displays changed canConnect status.
569                for (int i = 0; !changed && i<availableDisplays.length; i++) {
570                    changed = availableDisplays[i].canConnect()
571                            != mAvailableDisplays[i].canConnect();
572                }
573
574                if (changed) {
575                    mAvailableDisplays = availableDisplays;
576                    fixRememberedDisplayNamesFromAvailableDisplaysLocked();
577                    updateDisplaysLocked();
578                    scheduleStatusChangedBroadcastLocked();
579                }
580            }
581        }
582
583        @Override
584        public void onScanFinished() {
585            synchronized (getSyncRoot()) {
586                if (mScanState != WifiDisplayStatus.SCAN_STATE_NOT_SCANNING) {
587                    mScanState = WifiDisplayStatus.SCAN_STATE_NOT_SCANNING;
588                    scheduleStatusChangedBroadcastLocked();
589                }
590            }
591        }
592
593        @Override
594        public void onDisplayConnecting(WifiDisplay display) {
595            synchronized (getSyncRoot()) {
596                display = mPersistentDataStore.applyWifiDisplayAlias(display);
597
598                if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_CONNECTING
599                        || mActiveDisplay == null
600                        || !mActiveDisplay.equals(display)) {
601                    mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_CONNECTING;
602                    mActiveDisplay = display;
603                    scheduleStatusChangedBroadcastLocked();
604                    scheduleUpdateNotificationLocked();
605                }
606            }
607        }
608
609        @Override
610        public void onDisplayConnectionFailed() {
611            synchronized (getSyncRoot()) {
612                if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED
613                        || mActiveDisplay != null) {
614                    mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED;
615                    mActiveDisplay = null;
616                    scheduleStatusChangedBroadcastLocked();
617                    scheduleUpdateNotificationLocked();
618                }
619            }
620        }
621
622        @Override
623        public void onDisplayConnected(WifiDisplay display, Surface surface,
624                int width, int height, int flags) {
625            synchronized (getSyncRoot()) {
626                display = mPersistentDataStore.applyWifiDisplayAlias(display);
627                addDisplayDeviceLocked(display, surface, width, height, flags);
628
629                if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_CONNECTED
630                        || mActiveDisplay == null
631                        || !mActiveDisplay.equals(display)) {
632                    mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_CONNECTED;
633                    mActiveDisplay = display;
634                    scheduleStatusChangedBroadcastLocked();
635                    scheduleUpdateNotificationLocked();
636                }
637            }
638        }
639
640        @Override
641        public void onDisplaySessionInfo(WifiDisplaySessionInfo sessionInfo) {
642            synchronized (getSyncRoot()) {
643                mSessionInfo = sessionInfo;
644                scheduleStatusChangedBroadcastLocked();
645            }
646        }
647
648        @Override
649        public void onDisplayChanged(WifiDisplay display) {
650            synchronized (getSyncRoot()) {
651                display = mPersistentDataStore.applyWifiDisplayAlias(display);
652                if (mActiveDisplay != null
653                        && mActiveDisplay.hasSameAddress(display)
654                        && !mActiveDisplay.equals(display)) {
655                    mActiveDisplay = display;
656                    renameDisplayDeviceLocked(display.getFriendlyDisplayName());
657                    scheduleStatusChangedBroadcastLocked();
658                    scheduleUpdateNotificationLocked();
659                }
660            }
661        }
662
663        @Override
664        public void onDisplayDisconnected() {
665            // Stop listening.
666            synchronized (getSyncRoot()) {
667                removeDisplayDeviceLocked();
668
669                if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED
670                        || mActiveDisplay != null) {
671                    mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED;
672                    mActiveDisplay = null;
673                    scheduleStatusChangedBroadcastLocked();
674                    scheduleUpdateNotificationLocked();
675                }
676            }
677        }
678    };
679
680    private final class WifiDisplayDevice extends DisplayDevice {
681        private String mName;
682        private final int mWidth;
683        private final int mHeight;
684        private final float mRefreshRate;
685        private final int mFlags;
686        private final String mAddress;
687
688        private Surface mSurface;
689        private DisplayDeviceInfo mInfo;
690
691        public WifiDisplayDevice(IBinder displayToken, String name,
692                int width, int height, float refreshRate, int flags, String address,
693                Surface surface) {
694            super(WifiDisplayAdapter.this, displayToken);
695            mName = name;
696            mWidth = width;
697            mHeight = height;
698            mRefreshRate = refreshRate;
699            mFlags = flags;
700            mAddress = address;
701            mSurface = surface;
702        }
703
704        public void destroyLocked() {
705            if (mSurface != null) {
706                mSurface.release();
707                mSurface = null;
708            }
709            SurfaceControl.destroyDisplay(getDisplayTokenLocked());
710        }
711
712        public void setNameLocked(String name) {
713            mName = name;
714            mInfo = null;
715        }
716
717        @Override
718        public void performTraversalInTransactionLocked() {
719            if (mSurface != null) {
720                setSurfaceInTransactionLocked(mSurface);
721            }
722        }
723
724        @Override
725        public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
726            if (mInfo == null) {
727                mInfo = new DisplayDeviceInfo();
728                mInfo.name = mName;
729                mInfo.width = mWidth;
730                mInfo.height = mHeight;
731                mInfo.refreshRate = mRefreshRate;
732                mInfo.presentationDeadlineNanos = 1000000000L / (int) mRefreshRate; // 1 frame
733                mInfo.flags = mFlags;
734                mInfo.type = Display.TYPE_WIFI;
735                mInfo.address = mAddress;
736                mInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL;
737                mInfo.setAssumedDensityForExternalDisplay(mWidth, mHeight);
738            }
739            return mInfo;
740        }
741    }
742
743    private final class WifiDisplayHandler extends Handler {
744        public WifiDisplayHandler(Looper looper) {
745            super(looper, null, true /*async*/);
746        }
747
748        @Override
749        public void handleMessage(Message msg) {
750            switch (msg.what) {
751                case MSG_SEND_STATUS_CHANGE_BROADCAST:
752                    handleSendStatusChangeBroadcast();
753                    break;
754
755                case MSG_UPDATE_NOTIFICATION:
756                    handleUpdateNotification();
757                    break;
758            }
759        }
760    }
761}
762