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