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