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