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