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