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