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