WifiDisplayAdapter.java revision 2444ae7e2b8658a4a90f996e678423558744b4a2
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 static final boolean DEBUG = false;
54
55    private final PersistentDataStore mPersistentDataStore;
56    private final boolean mSupportsProtectedBuffers;
57
58    private WifiDisplayController mDisplayController;
59    private WifiDisplayDevice mDisplayDevice;
60
61    private WifiDisplayStatus mCurrentStatus;
62    private int mFeatureState;
63    private int mScanState;
64    private int mActiveDisplayState;
65    private WifiDisplay mActiveDisplay;
66    private WifiDisplay[] mAvailableDisplays = WifiDisplay.EMPTY_ARRAY;
67    private WifiDisplay[] mRememberedDisplays = WifiDisplay.EMPTY_ARRAY;
68
69    private boolean mPendingStatusChangeBroadcast;
70
71    public WifiDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
72            Context context, Handler handler, Listener listener,
73            PersistentDataStore persistentDataStore) {
74        super(syncRoot, context, handler, listener, TAG);
75        mPersistentDataStore = persistentDataStore;
76        mSupportsProtectedBuffers = context.getResources().getBoolean(
77                com.android.internal.R.bool.config_wifiDisplaySupportsProtectedBuffers);
78    }
79
80    @Override
81    public void dumpLocked(PrintWriter pw) {
82        super.dumpLocked(pw);
83
84        pw.println("mCurrentStatus=" + getWifiDisplayStatusLocked());
85        pw.println("mFeatureState=" + mFeatureState);
86        pw.println("mScanState=" + mScanState);
87        pw.println("mActiveDisplayState=" + mActiveDisplayState);
88        pw.println("mActiveDisplay=" + mActiveDisplay);
89        pw.println("mAvailableDisplays=" + Arrays.toString(mAvailableDisplays));
90        pw.println("mRememberedDisplays=" + Arrays.toString(mRememberedDisplays));
91        pw.println("mPendingStatusChangeBroadcast=" + mPendingStatusChangeBroadcast);
92        pw.println("mSupportsProtectedBuffers=" + mSupportsProtectedBuffers);
93
94        // Try to dump the controller state.
95        if (mDisplayController == null) {
96            pw.println("mDisplayController=null");
97        } else {
98            pw.println("mDisplayController:");
99            final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
100            ipw.increaseIndent();
101            DumpUtils.dumpAsync(getHandler(), mDisplayController, ipw, 200);
102        }
103    }
104
105    @Override
106    public void registerLocked() {
107        super.registerLocked();
108
109        updateRememberedDisplaysLocked();
110
111        getHandler().post(new Runnable() {
112            @Override
113            public void run() {
114                mDisplayController = new WifiDisplayController(
115                        getContext(), getHandler(), mWifiDisplayListener);
116            }
117        });
118    }
119
120    public void requestScanLocked() {
121        if (DEBUG) {
122            Slog.d(TAG, "requestScanLocked");
123        }
124
125        getHandler().post(new Runnable() {
126            @Override
127            public void run() {
128                if (mDisplayController != null) {
129                    mDisplayController.requestScan();
130                }
131            }
132        });
133    }
134
135    public void requestConnectLocked(final String address, final boolean trusted) {
136        if (DEBUG) {
137            Slog.d(TAG, "requestConnectLocked: address=" + address + ", trusted=" + trusted);
138        }
139
140        if (!trusted) {
141            synchronized (getSyncRoot()) {
142                if (!isRememberedDisplayLocked(address)) {
143                    Slog.w(TAG, "Ignoring request by an untrusted client to connect to "
144                            + "an unknown wifi display: " + address);
145                    return;
146                }
147            }
148        }
149
150        getHandler().post(new Runnable() {
151            @Override
152            public void run() {
153                if (mDisplayController != null) {
154                    mDisplayController.requestConnect(address);
155                }
156            }
157        });
158    }
159
160    private boolean isRememberedDisplayLocked(String address) {
161        for (WifiDisplay display : mRememberedDisplays) {
162            if (display.getDeviceAddress().equals(address)) {
163                return true;
164            }
165        }
166        return false;
167    }
168
169    public void requestDisconnectLocked() {
170        if (DEBUG) {
171            Slog.d(TAG, "requestDisconnectedLocked");
172        }
173
174        getHandler().post(new Runnable() {
175            @Override
176            public void run() {
177                if (mDisplayController != null) {
178                    mDisplayController.requestDisconnect();
179                }
180            }
181        });
182    }
183
184    public void requestRenameLocked(String address, String alias) {
185        if (DEBUG) {
186            Slog.d(TAG, "requestRenameLocked: address=" + address + ", alias=" + alias);
187        }
188
189        if (alias != null) {
190            alias = alias.trim();
191            if (alias.isEmpty() || alias.equals(address)) {
192                alias = null;
193            }
194        }
195
196        if (mPersistentDataStore.renameWifiDisplay(address, alias)) {
197            mPersistentDataStore.saveIfNeeded();
198            updateRememberedDisplaysLocked();
199            scheduleStatusChangedBroadcastLocked();
200        }
201    }
202
203    public void requestForgetLocked(String address) {
204        if (DEBUG) {
205            Slog.d(TAG, "requestForgetLocked: address=" + address);
206        }
207
208        if (mPersistentDataStore.forgetWifiDisplay(address)) {
209            mPersistentDataStore.saveIfNeeded();
210            updateRememberedDisplaysLocked();
211            scheduleStatusChangedBroadcastLocked();
212        }
213
214        if (mActiveDisplay != null && mActiveDisplay.getDeviceAddress().equals(address)) {
215            requestDisconnectLocked();
216        }
217    }
218
219    public WifiDisplayStatus getWifiDisplayStatusLocked() {
220        if (mCurrentStatus == null) {
221            mCurrentStatus = new WifiDisplayStatus(
222                    mFeatureState, mScanState, mActiveDisplayState,
223                    mActiveDisplay, mAvailableDisplays, mRememberedDisplays);
224        }
225
226        if (DEBUG) {
227            Slog.d(TAG, "getWifiDisplayStatusLocked: result=" + mCurrentStatus);
228        }
229        return mCurrentStatus;
230    }
231
232    private void updateRememberedDisplaysLocked() {
233        mRememberedDisplays = mPersistentDataStore.getRememberedWifiDisplays();
234        mActiveDisplay = mPersistentDataStore.applyWifiDisplayAlias(mActiveDisplay);
235        mAvailableDisplays = mPersistentDataStore.applyWifiDisplayAliases(mAvailableDisplays);
236    }
237
238    private void handleConnectLocked(WifiDisplay display,
239            Surface surface, int width, int height, int flags) {
240        handleDisconnectLocked();
241
242        if (mPersistentDataStore.rememberWifiDisplay(display)) {
243            mPersistentDataStore.saveIfNeeded();
244            updateRememberedDisplaysLocked();
245            scheduleStatusChangedBroadcastLocked();
246        }
247
248        int deviceFlags = 0;
249        if ((flags & RemoteDisplay.DISPLAY_FLAG_SECURE) != 0) {
250            deviceFlags |= DisplayDeviceInfo.FLAG_SECURE;
251        }
252        if (mSupportsProtectedBuffers) {
253            deviceFlags |= DisplayDeviceInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS;
254        }
255
256        float refreshRate = 60.0f; // TODO: get this for real
257
258        String name = display.getFriendlyDisplayName();
259        IBinder displayToken = Surface.createDisplay(name);
260        mDisplayDevice = new WifiDisplayDevice(displayToken, name, width, height,
261                refreshRate, deviceFlags, surface);
262        sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_ADDED);
263    }
264
265    private void handleDisconnectLocked() {
266        if (mDisplayDevice != null) {
267            mDisplayDevice.clearSurfaceLocked();
268            sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_REMOVED);
269            mDisplayDevice = null;
270        }
271    }
272
273    private void scheduleStatusChangedBroadcastLocked() {
274        mCurrentStatus = null;
275        if (!mPendingStatusChangeBroadcast) {
276            mPendingStatusChangeBroadcast = true;
277            getHandler().post(mStatusChangeBroadcast);
278        }
279    }
280
281    private final Runnable mStatusChangeBroadcast = new Runnable() {
282        @Override
283        public void run() {
284            final Intent intent;
285            synchronized (getSyncRoot()) {
286                if (!mPendingStatusChangeBroadcast) {
287                    return;
288                }
289
290                mPendingStatusChangeBroadcast = false;
291                intent = new Intent(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED);
292                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
293                intent.putExtra(DisplayManager.EXTRA_WIFI_DISPLAY_STATUS,
294                        getWifiDisplayStatusLocked());
295            }
296
297            // Send protected broadcast about wifi display status to registered receivers.
298            getContext().sendBroadcast(intent);
299        }
300    };
301
302    private final WifiDisplayController.Listener mWifiDisplayListener =
303            new WifiDisplayController.Listener() {
304        @Override
305        public void onFeatureStateChanged(int featureState) {
306            synchronized (getSyncRoot()) {
307                if (mFeatureState != featureState) {
308                    mFeatureState = featureState;
309                    scheduleStatusChangedBroadcastLocked();
310                }
311            }
312        }
313
314        @Override
315        public void onScanStarted() {
316            synchronized (getSyncRoot()) {
317                if (mScanState != WifiDisplayStatus.SCAN_STATE_SCANNING) {
318                    mScanState = WifiDisplayStatus.SCAN_STATE_SCANNING;
319                    scheduleStatusChangedBroadcastLocked();
320                }
321            }
322        }
323
324        @Override
325        public void onScanFinished(WifiDisplay[] availableDisplays) {
326            synchronized (getSyncRoot()) {
327                availableDisplays = mPersistentDataStore.applyWifiDisplayAliases(
328                        availableDisplays);
329
330                if (mScanState != WifiDisplayStatus.SCAN_STATE_NOT_SCANNING
331                        || !Arrays.equals(mAvailableDisplays, availableDisplays)) {
332                    mScanState = WifiDisplayStatus.SCAN_STATE_NOT_SCANNING;
333                    mAvailableDisplays = availableDisplays;
334                    scheduleStatusChangedBroadcastLocked();
335                }
336            }
337        }
338
339        @Override
340        public void onDisplayConnecting(WifiDisplay display) {
341            synchronized (getSyncRoot()) {
342                display = mPersistentDataStore.applyWifiDisplayAlias(display);
343
344                if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_CONNECTING
345                        || mActiveDisplay == null
346                        || !mActiveDisplay.equals(display)) {
347                    mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_CONNECTING;
348                    mActiveDisplay = display;
349                    scheduleStatusChangedBroadcastLocked();
350                }
351            }
352        }
353
354        @Override
355        public void onDisplayConnectionFailed() {
356            synchronized (getSyncRoot()) {
357                if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED
358                        || mActiveDisplay != null) {
359                    mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED;
360                    mActiveDisplay = null;
361                    scheduleStatusChangedBroadcastLocked();
362                }
363            }
364        }
365
366        @Override
367        public void onDisplayConnected(WifiDisplay display, Surface surface,
368                int width, int height, int flags) {
369            synchronized (getSyncRoot()) {
370                display = mPersistentDataStore.applyWifiDisplayAlias(display);
371                handleConnectLocked(display, surface, width, height, flags);
372
373                if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_CONNECTED
374                        || mActiveDisplay == null
375                        || !mActiveDisplay.equals(display)) {
376                    mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_CONNECTED;
377                    mActiveDisplay = display;
378                    scheduleStatusChangedBroadcastLocked();
379                }
380            }
381        }
382
383        @Override
384        public void onDisplayDisconnected() {
385            // Stop listening.
386            synchronized (getSyncRoot()) {
387                handleDisconnectLocked();
388
389                if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED
390                        || mActiveDisplay != null) {
391                    mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED;
392                    mActiveDisplay = null;
393                    scheduleStatusChangedBroadcastLocked();
394                }
395            }
396        }
397    };
398
399    private final class WifiDisplayDevice extends DisplayDevice {
400        private final String mName;
401        private final int mWidth;
402        private final int mHeight;
403        private final float mRefreshRate;
404        private final int mFlags;
405
406        private Surface mSurface;
407        private DisplayDeviceInfo mInfo;
408
409        public WifiDisplayDevice(IBinder displayToken, String name,
410                int width, int height, float refreshRate, int flags,
411                Surface surface) {
412            super(WifiDisplayAdapter.this, displayToken);
413            mName = name;
414            mWidth = width;
415            mHeight = height;
416            mRefreshRate = refreshRate;
417            mFlags = flags;
418            mSurface = surface;
419        }
420
421        public void clearSurfaceLocked() {
422            mSurface = null;
423            sendTraversalRequestLocked();
424        }
425
426        @Override
427        public void performTraversalInTransactionLocked() {
428            setSurfaceInTransactionLocked(mSurface);
429        }
430
431        @Override
432        public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
433            if (mInfo == null) {
434                mInfo = new DisplayDeviceInfo();
435                mInfo.name = mName;
436                mInfo.width = mWidth;
437                mInfo.height = mHeight;
438                mInfo.refreshRate = mRefreshRate;
439                mInfo.flags = mFlags;
440                mInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL;
441                mInfo.setAssumedDensityForExternalDisplay(mWidth, mHeight);
442            }
443            return mInfo;
444        }
445    }
446}
447