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