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. 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 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 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 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 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 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 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 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 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 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 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 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 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 320 private void updateRememberedDisplaysLocked() { 321 mRememberedDisplays = mPersistentDataStore.getRememberedWifiDisplays(); 322 mActiveDisplay = mPersistentDataStore.applyWifiDisplayAlias(mActiveDisplay); 323 mAvailableDisplays = mPersistentDataStore.applyWifiDisplayAliases(mAvailableDisplays); 324 updateDisplaysLocked(); 325 } 326 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 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 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 389 private void removeDisplayDeviceLocked() { 390 if (mDisplayDevice != null) { 391 mDisplayDevice.destroyLocked(); 392 sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_REMOVED); 393 mDisplayDevice = null; 394 } 395 } 396 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 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. 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 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 } 602 603 public void destroyLocked() { 604 if (mSurface != null) { 605 mSurface.release(); 606 mSurface = null; 607 } 608 SurfaceControl.destroyDisplay(getDisplayTokenLocked()); 609 } 610 611 public void setNameLocked(String name) { 612 mName = name; 613 mInfo = null; 614 } 615 616 @Override 617 public void performTraversalInTransactionLocked() { 618 if (mSurface != null) { 619 setSurfaceInTransactionLocked(mSurface); 620 } 621 } 622 623 @Override 624 public DisplayDeviceInfo getDisplayDeviceInfoLocked() { 625 if (mInfo == null) { 626 mInfo = new DisplayDeviceInfo(); 627 mInfo.name = mName; 628 mInfo.uniqueId = getUniqueId(); 629 mInfo.width = mWidth; 630 mInfo.height = mHeight; 631 mInfo.refreshRate = mRefreshRate; 632 mInfo.supportedRefreshRates = new float[] { mRefreshRate }; 633 mInfo.presentationDeadlineNanos = 1000000000L / (int) mRefreshRate; // 1 frame 634 mInfo.flags = mFlags; 635 mInfo.type = Display.TYPE_WIFI; 636 mInfo.address = mAddress; 637 mInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL; 638 mInfo.setAssumedDensityForExternalDisplay(mWidth, mHeight); 639 } 640 return mInfo; 641 } 642 } 643 644 private final class WifiDisplayHandler extends Handler { 645 public WifiDisplayHandler(Looper looper) { 646 super(looper, null, true /*async*/); 647 } 648 649 @Override 650 public void handleMessage(Message msg) { 651 switch (msg.what) { 652 case MSG_SEND_STATUS_CHANGE_BROADCAST: 653 handleSendStatusChangeBroadcast(); 654 break; 655 } 656 } 657 } 658 } 659