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