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