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