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