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