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