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