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