WifiDisplayAdapter.java revision a7f9c966bc90e52baaf0cf0c165463e5f44354c5
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.WifiDisplayStatus;
34import android.media.RemoteDisplay;
35import android.os.Handler;
36import android.os.IBinder;
37import android.os.Looper;
38import android.os.Message;
39import android.os.UserHandle;
40import android.provider.Settings;
41import android.util.Slog;
42import android.view.Surface;
43
44import java.io.PrintWriter;
45import java.util.Arrays;
46
47/**
48 * Connects to Wifi displays that implement the Miracast protocol.
49 * <p>
50 * The Wifi display protocol relies on Wifi direct for discovering and pairing
51 * with the display.  Once connected, the Media Server opens an RTSP socket and accepts
52 * a connection from the display.  After session negotiation, the Media Server
53 * streams encoded buffers to the display.
54 * </p><p>
55 * This class is responsible for connecting to Wifi displays and mediating
56 * the interactions between Media Server, Surface Flinger and the Display Manager Service.
57 * </p><p>
58 * Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock.
59 * </p>
60 */
61final class WifiDisplayAdapter extends DisplayAdapter {
62    private static final String TAG = "WifiDisplayAdapter";
63
64    private static final boolean DEBUG = false;
65
66    private static final int MSG_SEND_STATUS_CHANGE_BROADCAST = 1;
67    private static final int MSG_UPDATE_NOTIFICATION = 2;
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    private final NotificationManager mNotificationManager;
75
76    private final PendingIntent mSettingsPendingIntent;
77    private final PendingIntent mDisconnectPendingIntent;
78
79    private WifiDisplayController mDisplayController;
80    private WifiDisplayDevice mDisplayDevice;
81
82    private WifiDisplayStatus mCurrentStatus;
83    private int mFeatureState;
84    private int mScanState;
85    private int mActiveDisplayState;
86    private WifiDisplay mActiveDisplay;
87    private WifiDisplay[] mAvailableDisplays = WifiDisplay.EMPTY_ARRAY;
88    private WifiDisplay[] mRememberedDisplays = WifiDisplay.EMPTY_ARRAY;
89
90    private boolean mPendingStatusChangeBroadcast;
91    private boolean mPendingNotificationUpdate;
92
93    public WifiDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
94            Context context, Handler handler, Listener listener,
95            PersistentDataStore persistentDataStore) {
96        super(syncRoot, context, handler, listener, TAG);
97        mHandler = new WifiDisplayHandler(handler.getLooper());
98        mPersistentDataStore = persistentDataStore;
99        mSupportsProtectedBuffers = context.getResources().getBoolean(
100                com.android.internal.R.bool.config_wifiDisplaySupportsProtectedBuffers);
101        mNotificationManager = (NotificationManager)context.getSystemService(
102                Context.NOTIFICATION_SERVICE);
103
104        Intent settingsIntent = new Intent(Settings.ACTION_WIFI_DISPLAY_SETTINGS);
105        settingsIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
106                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
107                | Intent.FLAG_ACTIVITY_CLEAR_TOP);
108        mSettingsPendingIntent = PendingIntent.getActivityAsUser(
109                context, 0, settingsIntent, 0, null, UserHandle.CURRENT);
110
111        Intent disconnectIntent = new Intent(ACTION_DISCONNECT);
112        mDisconnectPendingIntent = PendingIntent.getBroadcastAsUser(
113                context, 0, disconnectIntent, 0, UserHandle.CURRENT);
114
115        context.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
116                new IntentFilter(ACTION_DISCONNECT), null, mHandler);
117    }
118
119    @Override
120    public void dumpLocked(PrintWriter pw) {
121        super.dumpLocked(pw);
122
123        pw.println("mCurrentStatus=" + getWifiDisplayStatusLocked());
124        pw.println("mFeatureState=" + mFeatureState);
125        pw.println("mScanState=" + mScanState);
126        pw.println("mActiveDisplayState=" + mActiveDisplayState);
127        pw.println("mActiveDisplay=" + mActiveDisplay);
128        pw.println("mAvailableDisplays=" + Arrays.toString(mAvailableDisplays));
129        pw.println("mRememberedDisplays=" + Arrays.toString(mRememberedDisplays));
130        pw.println("mPendingStatusChangeBroadcast=" + mPendingStatusChangeBroadcast);
131        pw.println("mPendingNotificationUpdate=" + mPendingNotificationUpdate);
132        pw.println("mSupportsProtectedBuffers=" + mSupportsProtectedBuffers);
133
134        // Try to dump the controller state.
135        if (mDisplayController == null) {
136            pw.println("mDisplayController=null");
137        } else {
138            pw.println("mDisplayController:");
139            final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
140            ipw.increaseIndent();
141            DumpUtils.dumpAsync(getHandler(), mDisplayController, ipw, 200);
142        }
143    }
144
145    @Override
146    public void registerLocked() {
147        super.registerLocked();
148
149        updateRememberedDisplaysLocked();
150
151        getHandler().post(new Runnable() {
152            @Override
153            public void run() {
154                mDisplayController = new WifiDisplayController(
155                        getContext(), getHandler(), mWifiDisplayListener);
156            }
157        });
158    }
159
160    public void requestScanLocked() {
161        if (DEBUG) {
162            Slog.d(TAG, "requestScanLocked");
163        }
164
165        getHandler().post(new Runnable() {
166            @Override
167            public void run() {
168                if (mDisplayController != null) {
169                    mDisplayController.requestScan();
170                }
171            }
172        });
173    }
174
175    public void requestConnectLocked(final String address, final boolean trusted) {
176        if (DEBUG) {
177            Slog.d(TAG, "requestConnectLocked: address=" + address + ", trusted=" + trusted);
178        }
179
180        if (!trusted) {
181            synchronized (getSyncRoot()) {
182                if (!isRememberedDisplayLocked(address)) {
183                    Slog.w(TAG, "Ignoring request by an untrusted client to connect to "
184                            + "an unknown wifi display: " + address);
185                    return;
186                }
187            }
188        }
189
190        getHandler().post(new Runnable() {
191            @Override
192            public void run() {
193                if (mDisplayController != null) {
194                    mDisplayController.requestConnect(address);
195                }
196            }
197        });
198    }
199
200    private boolean isRememberedDisplayLocked(String address) {
201        for (WifiDisplay display : mRememberedDisplays) {
202            if (display.getDeviceAddress().equals(address)) {
203                return true;
204            }
205        }
206        return false;
207    }
208
209    public void requestDisconnectLocked() {
210        if (DEBUG) {
211            Slog.d(TAG, "requestDisconnectedLocked");
212        }
213
214        getHandler().post(new Runnable() {
215            @Override
216            public void run() {
217                if (mDisplayController != null) {
218                    mDisplayController.requestDisconnect();
219                }
220            }
221        });
222    }
223
224    public void requestRenameLocked(String address, String alias) {
225        if (DEBUG) {
226            Slog.d(TAG, "requestRenameLocked: address=" + address + ", alias=" + alias);
227        }
228
229        if (alias != null) {
230            alias = alias.trim();
231            if (alias.isEmpty() || alias.equals(address)) {
232                alias = null;
233            }
234        }
235
236        if (mPersistentDataStore.renameWifiDisplay(address, alias)) {
237            mPersistentDataStore.saveIfNeeded();
238            updateRememberedDisplaysLocked();
239            scheduleStatusChangedBroadcastLocked();
240        }
241
242        if (mActiveDisplay != null && mActiveDisplay.getDeviceAddress().equals(address)
243                && mDisplayDevice != null) {
244            mDisplayDevice.setNameLocked(mActiveDisplay.getFriendlyDisplayName());
245            sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_CHANGED);
246        }
247    }
248
249    public void requestForgetLocked(String address) {
250        if (DEBUG) {
251            Slog.d(TAG, "requestForgetLocked: address=" + address);
252        }
253
254        if (mPersistentDataStore.forgetWifiDisplay(address)) {
255            mPersistentDataStore.saveIfNeeded();
256            updateRememberedDisplaysLocked();
257            scheduleStatusChangedBroadcastLocked();
258        }
259
260        if (mActiveDisplay != null && mActiveDisplay.getDeviceAddress().equals(address)) {
261            requestDisconnectLocked();
262        }
263    }
264
265    public WifiDisplayStatus getWifiDisplayStatusLocked() {
266        if (mCurrentStatus == null) {
267            mCurrentStatus = new WifiDisplayStatus(
268                    mFeatureState, mScanState, mActiveDisplayState,
269                    mActiveDisplay, mAvailableDisplays, mRememberedDisplays);
270        }
271
272        if (DEBUG) {
273            Slog.d(TAG, "getWifiDisplayStatusLocked: result=" + mCurrentStatus);
274        }
275        return mCurrentStatus;
276    }
277
278    private void updateRememberedDisplaysLocked() {
279        mRememberedDisplays = mPersistentDataStore.getRememberedWifiDisplays();
280        mActiveDisplay = mPersistentDataStore.applyWifiDisplayAlias(mActiveDisplay);
281        mAvailableDisplays = mPersistentDataStore.applyWifiDisplayAliases(mAvailableDisplays);
282    }
283
284    private void handleConnectLocked(WifiDisplay display,
285            Surface surface, int width, int height, int flags) {
286        handleDisconnectLocked();
287
288        if (mPersistentDataStore.rememberWifiDisplay(display)) {
289            mPersistentDataStore.saveIfNeeded();
290            updateRememberedDisplaysLocked();
291            scheduleStatusChangedBroadcastLocked();
292        }
293
294        int deviceFlags = 0;
295        if ((flags & RemoteDisplay.DISPLAY_FLAG_SECURE) != 0) {
296            deviceFlags |= DisplayDeviceInfo.FLAG_SECURE;
297        }
298        if (mSupportsProtectedBuffers) {
299            deviceFlags |= DisplayDeviceInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS;
300        }
301
302        float refreshRate = 60.0f; // TODO: get this for real
303
304        String name = display.getFriendlyDisplayName();
305        IBinder displayToken = Surface.createDisplay(name);
306        mDisplayDevice = new WifiDisplayDevice(displayToken, name, width, height,
307                refreshRate, deviceFlags, surface);
308        sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_ADDED);
309
310        scheduleUpdateNotificationLocked();
311    }
312
313    private void handleDisconnectLocked() {
314        if (mDisplayDevice != null) {
315            mDisplayDevice.clearSurfaceLocked();
316            sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_REMOVED);
317            mDisplayDevice = null;
318
319            scheduleUpdateNotificationLocked();
320        }
321    }
322
323    private void scheduleStatusChangedBroadcastLocked() {
324        mCurrentStatus = null;
325        if (!mPendingStatusChangeBroadcast) {
326            mPendingStatusChangeBroadcast = true;
327            mHandler.sendEmptyMessage(MSG_SEND_STATUS_CHANGE_BROADCAST);
328        }
329    }
330
331    private void scheduleUpdateNotificationLocked() {
332        if (!mPendingNotificationUpdate) {
333            mPendingNotificationUpdate = true;
334            mHandler.sendEmptyMessage(MSG_UPDATE_NOTIFICATION);
335        }
336    }
337
338    // Runs on the handler.
339    private void handleSendStatusChangeBroadcast() {
340        final Intent intent;
341        synchronized (getSyncRoot()) {
342            if (!mPendingStatusChangeBroadcast) {
343                return;
344            }
345
346            mPendingStatusChangeBroadcast = false;
347            intent = new Intent(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED);
348            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
349            intent.putExtra(DisplayManager.EXTRA_WIFI_DISPLAY_STATUS,
350                    getWifiDisplayStatusLocked());
351        }
352
353        // Send protected broadcast about wifi display status to registered receivers.
354        getContext().sendBroadcastAsUser(intent, UserHandle.ALL);
355    }
356
357    // Runs on the handler.
358    private void handleUpdateNotification() {
359        final boolean isConnected;
360        synchronized (getSyncRoot()) {
361            if (!mPendingNotificationUpdate) {
362                return;
363            }
364
365            mPendingNotificationUpdate = false;
366            isConnected = (mDisplayDevice != null);
367        }
368
369        mNotificationManager.cancelAsUser(null,
370                R.string.wifi_display_notification_title, UserHandle.ALL);
371
372        if (isConnected) {
373            Context context = getContext();
374
375            Resources r = context.getResources();
376            Notification notification = new Notification.Builder(context)
377                    .setContentTitle(r.getString(
378                            R.string.wifi_display_notification_title))
379                    .setContentText(r.getString(
380                            R.string.wifi_display_notification_message))
381                    .setContentIntent(mSettingsPendingIntent)
382                    .setSmallIcon(R.drawable.ic_notify_wifidisplay)
383                    .setOngoing(true)
384                    .addAction(android.R.drawable.ic_menu_close_clear_cancel,
385                            r.getString(R.string.wifi_display_notification_disconnect),
386                            mDisconnectPendingIntent)
387                    .build();
388            mNotificationManager.notifyAsUser(null,
389                    R.string.wifi_display_notification_title,
390                    notification, UserHandle.ALL);
391        }
392    }
393
394    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
395        @Override
396        public void onReceive(Context context, Intent intent) {
397            if (intent.getAction().equals(ACTION_DISCONNECT)) {
398                synchronized (getSyncRoot()) {
399                    requestDisconnectLocked();
400                }
401            }
402        }
403    };
404
405    private final WifiDisplayController.Listener mWifiDisplayListener =
406            new WifiDisplayController.Listener() {
407        @Override
408        public void onFeatureStateChanged(int featureState) {
409            synchronized (getSyncRoot()) {
410                if (mFeatureState != featureState) {
411                    mFeatureState = featureState;
412                    scheduleStatusChangedBroadcastLocked();
413                }
414            }
415        }
416
417        @Override
418        public void onScanStarted() {
419            synchronized (getSyncRoot()) {
420                if (mScanState != WifiDisplayStatus.SCAN_STATE_SCANNING) {
421                    mScanState = WifiDisplayStatus.SCAN_STATE_SCANNING;
422                    scheduleStatusChangedBroadcastLocked();
423                }
424            }
425        }
426
427        @Override
428        public void onScanFinished(WifiDisplay[] availableDisplays) {
429            synchronized (getSyncRoot()) {
430                availableDisplays = mPersistentDataStore.applyWifiDisplayAliases(
431                        availableDisplays);
432
433                if (mScanState != WifiDisplayStatus.SCAN_STATE_NOT_SCANNING
434                        || !Arrays.equals(mAvailableDisplays, availableDisplays)) {
435                    mScanState = WifiDisplayStatus.SCAN_STATE_NOT_SCANNING;
436                    mAvailableDisplays = availableDisplays;
437                    scheduleStatusChangedBroadcastLocked();
438                }
439            }
440        }
441
442        @Override
443        public void onDisplayConnecting(WifiDisplay display) {
444            synchronized (getSyncRoot()) {
445                display = mPersistentDataStore.applyWifiDisplayAlias(display);
446
447                if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_CONNECTING
448                        || mActiveDisplay == null
449                        || !mActiveDisplay.equals(display)) {
450                    mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_CONNECTING;
451                    mActiveDisplay = display;
452                    scheduleStatusChangedBroadcastLocked();
453                }
454            }
455        }
456
457        @Override
458        public void onDisplayConnectionFailed() {
459            synchronized (getSyncRoot()) {
460                if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED
461                        || mActiveDisplay != null) {
462                    mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED;
463                    mActiveDisplay = null;
464                    scheduleStatusChangedBroadcastLocked();
465                }
466            }
467        }
468
469        @Override
470        public void onDisplayConnected(WifiDisplay display, Surface surface,
471                int width, int height, int flags) {
472            synchronized (getSyncRoot()) {
473                display = mPersistentDataStore.applyWifiDisplayAlias(display);
474                handleConnectLocked(display, surface, width, height, flags);
475
476                if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_CONNECTED
477                        || mActiveDisplay == null
478                        || !mActiveDisplay.equals(display)) {
479                    mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_CONNECTED;
480                    mActiveDisplay = display;
481                    scheduleStatusChangedBroadcastLocked();
482                }
483            }
484        }
485
486        @Override
487        public void onDisplayDisconnected() {
488            // Stop listening.
489            synchronized (getSyncRoot()) {
490                handleDisconnectLocked();
491
492                if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED
493                        || mActiveDisplay != null) {
494                    mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED;
495                    mActiveDisplay = null;
496                    scheduleStatusChangedBroadcastLocked();
497                }
498            }
499        }
500    };
501
502    private final class WifiDisplayDevice extends DisplayDevice {
503        private String mName;
504        private final int mWidth;
505        private final int mHeight;
506        private final float mRefreshRate;
507        private final int mFlags;
508
509        private Surface mSurface;
510        private DisplayDeviceInfo mInfo;
511
512        public WifiDisplayDevice(IBinder displayToken, String name,
513                int width, int height, float refreshRate, int flags,
514                Surface surface) {
515            super(WifiDisplayAdapter.this, displayToken);
516            mName = name;
517            mWidth = width;
518            mHeight = height;
519            mRefreshRate = refreshRate;
520            mFlags = flags;
521            mSurface = surface;
522        }
523
524        public void clearSurfaceLocked() {
525            mSurface = null;
526            sendTraversalRequestLocked();
527        }
528
529        public void setNameLocked(String name) {
530            mName = name;
531            mInfo = null;
532        }
533
534        @Override
535        public void performTraversalInTransactionLocked() {
536            setSurfaceInTransactionLocked(mSurface);
537        }
538
539        @Override
540        public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
541            if (mInfo == null) {
542                mInfo = new DisplayDeviceInfo();
543                mInfo.name = mName;
544                mInfo.width = mWidth;
545                mInfo.height = mHeight;
546                mInfo.refreshRate = mRefreshRate;
547                mInfo.flags = mFlags;
548                mInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL;
549                mInfo.setAssumedDensityForExternalDisplay(mWidth, mHeight);
550            }
551            return mInfo;
552        }
553    }
554
555    private final class WifiDisplayHandler extends Handler {
556        public WifiDisplayHandler(Looper looper) {
557            super(looper, null, true /*async*/);
558        }
559
560        @Override
561        public void handleMessage(Message msg) {
562            switch (msg.what) {
563                case MSG_SEND_STATUS_CHANGE_BROADCAST:
564                    handleSendStatusChangeBroadcast();
565                    break;
566
567                case MSG_UPDATE_NOTIFICATION:
568                    handleUpdateNotification();
569                    break;
570            }
571        }
572    }
573}
574