WifiDisplayAdapter.java revision e08ae388d63c4db8f9d9a7ecd634f9a51f6e91b9
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.Context;
23import android.content.Intent;
24import android.hardware.display.DisplayManager;
25import android.hardware.display.WifiDisplay;
26import android.hardware.display.WifiDisplayStatus;
27import android.media.RemoteDisplay;
28import android.os.Handler;
29import android.os.IBinder;
30import android.util.Slog;
31import android.view.Surface;
32
33import java.io.PrintWriter;
34import java.util.Arrays;
35
36/**
37 * Connects to Wifi displays that implement the Miracast protocol.
38 * <p>
39 * The Wifi display protocol relies on Wifi direct for discovering and pairing
40 * with the display.  Once connected, the Media Server opens an RTSP socket and accepts
41 * a connection from the display.  After session negotiation, the Media Server
42 * streams encoded buffers to the display.
43 * </p><p>
44 * This class is responsible for connecting to Wifi displays and mediating
45 * the interactions between Media Server, Surface Flinger and the Display Manager Service.
46 * </p><p>
47 * Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock.
48 * </p>
49 */
50final class WifiDisplayAdapter extends DisplayAdapter {
51    private static final String TAG = "WifiDisplayAdapter";
52
53    private WifiDisplayHandle mDisplayHandle;
54    private WifiDisplayController mDisplayController;
55
56    private WifiDisplayStatus mCurrentStatus;
57    private boolean mEnabled;
58    private WifiDisplay mConnectedDisplay;
59    private WifiDisplay[] mKnownDisplays = WifiDisplay.EMPTY_ARRAY;
60    private boolean mScanInProgress;
61    private boolean mConnectionInProgress;
62
63    private boolean mPendingStatusChangeBroadcast;
64
65    public WifiDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
66            Context context, Handler handler, Listener listener) {
67        super(syncRoot, context, handler, listener, TAG);
68    }
69
70    @Override
71    public void dumpLocked(PrintWriter pw) {
72        super.dumpLocked(pw);
73
74        if (mDisplayHandle == null) {
75            pw.println("mDisplayHandle=null");
76        } else {
77            pw.println("mDisplayHandle:");
78            mDisplayHandle.dumpLocked(pw);
79        }
80
81        pw.println("mCurrentStatus=" + getWifiDisplayStatusLocked());
82        pw.println("mEnabled=" + mEnabled);
83        pw.println("mConnectedDisplay=" + mConnectedDisplay);
84        pw.println("mKnownDisplays=" + Arrays.toString(mKnownDisplays));
85        pw.println("mScanInProgress=" + mScanInProgress);
86        pw.println("mConnectionInProgress=" + mConnectionInProgress);
87        pw.println("mPendingStatusChangeBroadcast=" + mPendingStatusChangeBroadcast);
88
89        // Try to dump the controller state.
90        if (mDisplayController == null) {
91            pw.println("mDisplayController=null");
92        } else {
93            pw.println("mDisplayController:");
94            final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
95            ipw.increaseIndent();
96            DumpUtils.dumpAsync(getHandler(), mDisplayController, ipw, 200);
97        }
98    }
99
100    @Override
101    public void registerLocked() {
102        super.registerLocked();
103
104        getHandler().post(new Runnable() {
105            @Override
106            public void run() {
107                mDisplayController = new WifiDisplayController(
108                        getContext(), getHandler(), mWifiDisplayListener);
109            }
110        });
111    }
112
113    public void requestScanLocked() {
114        getHandler().post(new Runnable() {
115            @Override
116            public void run() {
117                if (mDisplayController != null) {
118                    mDisplayController.requestScan();
119                }
120            }
121        });
122    }
123
124    public void requestConnectLocked(final String address) {
125        getHandler().post(new Runnable() {
126            @Override
127            public void run() {
128                if (mDisplayController != null) {
129                    mDisplayController.requestConnect(address);
130                }
131            }
132        });
133    }
134
135    public void requestDisconnectLocked() {
136        getHandler().post(new Runnable() {
137            @Override
138            public void run() {
139                if (mDisplayController != null) {
140                    mDisplayController.requestDisconnect();
141                }
142            }
143        });
144    }
145
146    public WifiDisplayStatus getWifiDisplayStatusLocked() {
147        if (mCurrentStatus == null) {
148            mCurrentStatus = new WifiDisplayStatus(mEnabled,
149                    mConnectedDisplay, mKnownDisplays,
150                    mScanInProgress, mConnectionInProgress);
151        }
152        return mCurrentStatus;
153    }
154
155    private void handleConnectLocked(WifiDisplay display, String iface) {
156        handleDisconnectLocked();
157
158        mDisplayHandle = new WifiDisplayHandle(display.getDeviceName(), iface);
159    }
160
161    private void handleDisconnectLocked() {
162        if (mDisplayHandle != null) {
163            mDisplayHandle.disposeLocked();
164            mDisplayHandle = null;
165        }
166    }
167
168    private void scheduleStatusChangedBroadcastLocked() {
169        if (!mPendingStatusChangeBroadcast) {
170            mPendingStatusChangeBroadcast = true;
171            getHandler().post(mStatusChangeBroadcast);
172        }
173    }
174
175    private final Runnable mStatusChangeBroadcast = new Runnable() {
176        @Override
177        public void run() {
178            final Intent intent;
179            synchronized (getSyncRoot()) {
180                if (!mPendingStatusChangeBroadcast) {
181                    return;
182                }
183
184                mPendingStatusChangeBroadcast = false;
185                intent = new Intent(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED);
186                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
187                intent.putExtra(DisplayManager.EXTRA_WIFI_DISPLAY_STATUS,
188                        getWifiDisplayStatusLocked());
189            }
190
191            // Send protected broadcast about wifi display status to receivers that
192            // have the required permission.
193            getContext().sendBroadcast(intent,
194                    android.Manifest.permission.CONFIGURE_WIFI_DISPLAY);
195        }
196    };
197
198    private final WifiDisplayController.Listener mWifiDisplayListener =
199            new WifiDisplayController.Listener() {
200        @Override
201        public void onEnablementChanged(boolean enabled) {
202            synchronized (getSyncRoot()) {
203                if (mEnabled != enabled) {
204                    mCurrentStatus = null;
205                    mEnabled = enabled;
206                    scheduleStatusChangedBroadcastLocked();
207                }
208            }
209        }
210
211        @Override
212        public void onScanStarted() {
213            synchronized (getSyncRoot()) {
214                if (!mScanInProgress) {
215                    mCurrentStatus = null;
216                    mScanInProgress = true;
217                    scheduleStatusChangedBroadcastLocked();
218                }
219            }
220        }
221
222        public void onScanFinished(WifiDisplay[] knownDisplays) {
223            synchronized (getSyncRoot()) {
224                if (!Arrays.equals(mKnownDisplays, knownDisplays) || mScanInProgress) {
225                    mCurrentStatus = null;
226                    mKnownDisplays = knownDisplays;
227                    mScanInProgress = false;
228                    scheduleStatusChangedBroadcastLocked();
229                }
230            }
231        }
232
233        @Override
234        public void onDisplayConnecting(WifiDisplay display) {
235            synchronized (getSyncRoot()) {
236                if (!mConnectionInProgress) {
237                    mCurrentStatus = null;
238                    mConnectionInProgress = true;
239                    scheduleStatusChangedBroadcastLocked();
240                }
241            }
242        }
243
244        @Override
245        public void onDisplayConnectionFailed() {
246            synchronized (getSyncRoot()) {
247                if (mConnectionInProgress) {
248                    mCurrentStatus = null;
249                    mConnectionInProgress = false;
250                    scheduleStatusChangedBroadcastLocked();
251                }
252            }
253        }
254
255        @Override
256        public void onDisplayConnected(WifiDisplay display, String iface) {
257            synchronized (getSyncRoot()) {
258                handleConnectLocked(display, iface);
259
260                if (mConnectedDisplay == null || !mConnectedDisplay.equals(display)
261                        || mConnectionInProgress) {
262                    mCurrentStatus = null;
263                    mConnectedDisplay = display;
264                    mConnectionInProgress = false;
265                    scheduleStatusChangedBroadcastLocked();
266                }
267            }
268        }
269
270        @Override
271        public void onDisplayDisconnected() {
272            // Stop listening.
273            synchronized (getSyncRoot()) {
274                handleDisconnectLocked();
275
276                if (mConnectedDisplay != null || mConnectionInProgress) {
277                    mCurrentStatus = null;
278                    mConnectedDisplay = null;
279                    mConnectionInProgress = false;
280                    scheduleStatusChangedBroadcastLocked();
281                }
282            }
283        }
284    };
285
286    private final class WifiDisplayDevice extends DisplayDevice {
287        private final String mName;
288        private final int mWidth;
289        private final int mHeight;
290        private final float mRefreshRate;
291        private final int mFlags;
292
293        private Surface mSurface;
294        private DisplayDeviceInfo mInfo;
295
296        public WifiDisplayDevice(IBinder displayToken, String name,
297                int width, int height, float refreshRate, int flags,
298                Surface surface) {
299            super(WifiDisplayAdapter.this, displayToken);
300            mName = name;
301            mWidth = width;
302            mHeight = height;
303            mRefreshRate = refreshRate;
304            mFlags = flags;
305            mSurface = surface;
306        }
307
308        public void clearSurfaceLocked() {
309            mSurface = null;
310            sendTraversalRequestLocked();
311        }
312
313        @Override
314        public void performTraversalInTransactionLocked() {
315            setSurfaceInTransactionLocked(mSurface);
316        }
317
318        @Override
319        public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
320            if (mInfo == null) {
321                mInfo = new DisplayDeviceInfo();
322                mInfo.name = mName;
323                mInfo.width = mWidth;
324                mInfo.height = mHeight;
325                mInfo.refreshRate = mRefreshRate;
326                mInfo.flags = mFlags;
327                mInfo.setAssumedDensityForExternalDisplay(mWidth, mHeight);
328            }
329            return mInfo;
330        }
331    }
332
333    private final class WifiDisplayHandle implements RemoteDisplay.Listener {
334        private final String mName;
335        private final String mIface;
336        private final RemoteDisplay mRemoteDisplay;
337
338        private WifiDisplayDevice mDevice;
339        private int mLastError;
340
341        public WifiDisplayHandle(String name, String iface) {
342            mName = name;
343            mIface = iface;
344            mRemoteDisplay = RemoteDisplay.listen(iface, this, getHandler());
345
346            Slog.i(TAG, "Listening for Wifi display connections on " + iface
347                    + " from " + mName);
348        }
349
350        public void disposeLocked() {
351            Slog.i(TAG, "Stopped listening for Wifi display connections on " + mIface
352                    + " from " + mName);
353
354            removeDisplayLocked();
355            mRemoteDisplay.dispose();
356        }
357
358        public void dumpLocked(PrintWriter pw) {
359            pw.println("  " + mName + ": " + (mDevice != null ? "connected" : "disconnected"));
360            pw.println("    mIface=" + mIface);
361            pw.println("    mLastError=" + mLastError);
362        }
363
364        // Called on the handler thread.
365        @Override
366        public void onDisplayConnected(Surface surface, int width, int height, int flags) {
367            synchronized (getSyncRoot()) {
368                mLastError = 0;
369                removeDisplayLocked();
370                addDisplayLocked(surface, width, height, flags);
371
372                Slog.i(TAG, "Wifi display connected: " + mName);
373            }
374        }
375
376        // Called on the handler thread.
377        @Override
378        public void onDisplayDisconnected() {
379            synchronized (getSyncRoot()) {
380                mLastError = 0;
381                removeDisplayLocked();
382
383                Slog.i(TAG, "Wifi display disconnected: " + mName);
384            }
385        }
386
387        // Called on the handler thread.
388        @Override
389        public void onDisplayError(int error) {
390            synchronized (getSyncRoot()) {
391                mLastError = error;
392                removeDisplayLocked();
393
394                Slog.i(TAG, "Wifi display disconnected due to error " + error + ": " + mName);
395            }
396        }
397
398        private void addDisplayLocked(Surface surface, int width, int height, int flags) {
399            int deviceFlags = 0;
400            if ((flags & RemoteDisplay.DISPLAY_FLAG_SECURE) != 0) {
401                deviceFlags |= DisplayDeviceInfo.FLAG_SECURE;
402            }
403
404            float refreshRate = 60.0f; // TODO: get this for real
405
406            IBinder displayToken = Surface.createDisplay(mName);
407            mDevice = new WifiDisplayDevice(displayToken, mName, width, height,
408                    refreshRate, deviceFlags, surface);
409            sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_ADDED);
410        }
411
412        private void removeDisplayLocked() {
413            if (mDevice != null) {
414                mDevice.clearSurfaceLocked();
415                sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_REMOVED);
416                mDevice = null;
417            }
418        }
419    }
420}
421