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 android.hardware.display; 18 19 import android.content.Context; 20 import android.hardware.display.DisplayManager.DisplayListener; 21 import android.media.projection.MediaProjection; 22 import android.media.projection.IMediaProjection; 23 import android.os.Binder; 24 import android.os.Handler; 25 import android.os.IBinder; 26 import android.os.Looper; 27 import android.os.Message; 28 import android.os.RemoteException; 29 import android.os.ServiceManager; 30 import android.text.TextUtils; 31 import android.util.Log; 32 import android.util.SparseArray; 33 import android.view.DisplayAdjustments; 34 import android.view.Display; 35 import android.view.DisplayInfo; 36 import android.view.Surface; 37 38 import java.util.ArrayList; 39 40 /** 41 * Manager communication with the display manager service on behalf of 42 * an application process. You're probably looking for {@link DisplayManager}. 43 * 44 * @hide 45 */ 46 public final class DisplayManagerGlobal { 47 private static final String TAG = "DisplayManager"; 48 private static final boolean DEBUG = false; 49 50 // True if display info and display ids should be cached. 51 // 52 // FIXME: The cache is currently disabled because it's unclear whether we have the 53 // necessary guarantees that the caches will always be flushed before clients 54 // attempt to observe their new state. For example, depending on the order 55 // in which the binder transactions take place, we might have a problem where 56 // an application could start processing a configuration change due to a display 57 // orientation change before the display info cache has actually been invalidated. 58 private static final boolean USE_CACHE = false; 59 60 public static final int EVENT_DISPLAY_ADDED = 1; 61 public static final int EVENT_DISPLAY_CHANGED = 2; 62 public static final int EVENT_DISPLAY_REMOVED = 3; 63 64 private static DisplayManagerGlobal sInstance; 65 66 private final Object mLock = new Object(); 67 68 private final IDisplayManager mDm; 69 70 private DisplayManagerCallback mCallback; 71 private final ArrayList<DisplayListenerDelegate> mDisplayListeners = 72 new ArrayList<DisplayListenerDelegate>(); 73 74 private final SparseArray<DisplayInfo> mDisplayInfoCache = new SparseArray<DisplayInfo>(); 75 private int[] mDisplayIdCache; 76 77 private int mWifiDisplayScanNestCount; 78 79 private DisplayManagerGlobal(IDisplayManager dm) { 80 mDm = dm; 81 } 82 83 /** 84 * Gets an instance of the display manager global singleton. 85 * 86 * @return The display manager instance, may be null early in system startup 87 * before the display manager has been fully initialized. 88 */ 89 public static DisplayManagerGlobal getInstance() { 90 synchronized (DisplayManagerGlobal.class) { 91 if (sInstance == null) { 92 IBinder b = ServiceManager.getService(Context.DISPLAY_SERVICE); 93 if (b != null) { 94 sInstance = new DisplayManagerGlobal(IDisplayManager.Stub.asInterface(b)); 95 } 96 } 97 return sInstance; 98 } 99 } 100 101 /** 102 * Get information about a particular logical display. 103 * 104 * @param displayId The logical display id. 105 * @return Information about the specified display, or null if it does not exist. 106 * This object belongs to an internal cache and should be treated as if it were immutable. 107 */ 108 public DisplayInfo getDisplayInfo(int displayId) { 109 try { 110 synchronized (mLock) { 111 DisplayInfo info; 112 if (USE_CACHE) { 113 info = mDisplayInfoCache.get(displayId); 114 if (info != null) { 115 return info; 116 } 117 } 118 119 info = mDm.getDisplayInfo(displayId); 120 if (info == null) { 121 return null; 122 } 123 124 if (USE_CACHE) { 125 mDisplayInfoCache.put(displayId, info); 126 } 127 registerCallbackIfNeededLocked(); 128 129 if (DEBUG) { 130 Log.d(TAG, "getDisplayInfo: displayId=" + displayId + ", info=" + info); 131 } 132 return info; 133 } 134 } catch (RemoteException ex) { 135 Log.e(TAG, "Could not get display information from display manager.", ex); 136 return null; 137 } 138 } 139 140 /** 141 * Gets all currently valid logical display ids. 142 * 143 * @return An array containing all display ids. 144 */ 145 public int[] getDisplayIds() { 146 try { 147 synchronized (mLock) { 148 if (USE_CACHE) { 149 if (mDisplayIdCache != null) { 150 return mDisplayIdCache; 151 } 152 } 153 154 int[] displayIds = mDm.getDisplayIds(); 155 if (USE_CACHE) { 156 mDisplayIdCache = displayIds; 157 } 158 registerCallbackIfNeededLocked(); 159 return displayIds; 160 } 161 } catch (RemoteException ex) { 162 Log.e(TAG, "Could not get display ids from display manager.", ex); 163 return new int[] { Display.DEFAULT_DISPLAY }; 164 } 165 } 166 167 /** 168 * Gets information about a logical display. 169 * 170 * The display metrics may be adjusted to provide compatibility 171 * for legacy applications or limited screen areas. 172 * 173 * @param displayId The logical display id. 174 * @param daj The compatibility info and activityToken. 175 * @return The display object, or null if there is no display with the given id. 176 */ 177 public Display getCompatibleDisplay(int displayId, DisplayAdjustments daj) { 178 DisplayInfo displayInfo = getDisplayInfo(displayId); 179 if (displayInfo == null) { 180 return null; 181 } 182 return new Display(this, displayId, displayInfo, daj); 183 } 184 185 /** 186 * Gets information about a logical display without applying any compatibility metrics. 187 * 188 * @param displayId The logical display id. 189 * @return The display object, or null if there is no display with the given id. 190 */ 191 public Display getRealDisplay(int displayId) { 192 return getCompatibleDisplay(displayId, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); 193 } 194 195 /** 196 * Gets information about a logical display without applying any compatibility metrics. 197 * 198 * @param displayId The logical display id. 199 * @param IBinder the activity token for this display. 200 * @return The display object, or null if there is no display with the given id. 201 */ 202 public Display getRealDisplay(int displayId, IBinder token) { 203 return getCompatibleDisplay(displayId, new DisplayAdjustments(token)); 204 } 205 206 public void registerDisplayListener(DisplayListener listener, Handler handler) { 207 if (listener == null) { 208 throw new IllegalArgumentException("listener must not be null"); 209 } 210 211 synchronized (mLock) { 212 int index = findDisplayListenerLocked(listener); 213 if (index < 0) { 214 mDisplayListeners.add(new DisplayListenerDelegate(listener, handler)); 215 registerCallbackIfNeededLocked(); 216 } 217 } 218 } 219 220 public void unregisterDisplayListener(DisplayListener listener) { 221 if (listener == null) { 222 throw new IllegalArgumentException("listener must not be null"); 223 } 224 225 synchronized (mLock) { 226 int index = findDisplayListenerLocked(listener); 227 if (index >= 0) { 228 DisplayListenerDelegate d = mDisplayListeners.get(index); 229 d.clearEvents(); 230 mDisplayListeners.remove(index); 231 } 232 } 233 } 234 235 private int findDisplayListenerLocked(DisplayListener listener) { 236 final int numListeners = mDisplayListeners.size(); 237 for (int i = 0; i < numListeners; i++) { 238 if (mDisplayListeners.get(i).mListener == listener) { 239 return i; 240 } 241 } 242 return -1; 243 } 244 245 private void registerCallbackIfNeededLocked() { 246 if (mCallback == null) { 247 mCallback = new DisplayManagerCallback(); 248 try { 249 mDm.registerCallback(mCallback); 250 } catch (RemoteException ex) { 251 Log.e(TAG, "Failed to register callback with display manager service.", ex); 252 mCallback = null; 253 } 254 } 255 } 256 257 private void handleDisplayEvent(int displayId, int event) { 258 synchronized (mLock) { 259 if (USE_CACHE) { 260 mDisplayInfoCache.remove(displayId); 261 262 if (event == EVENT_DISPLAY_ADDED || event == EVENT_DISPLAY_REMOVED) { 263 mDisplayIdCache = null; 264 } 265 } 266 267 final int numListeners = mDisplayListeners.size(); 268 for (int i = 0; i < numListeners; i++) { 269 mDisplayListeners.get(i).sendDisplayEvent(displayId, event); 270 } 271 } 272 } 273 274 public void startWifiDisplayScan() { 275 synchronized (mLock) { 276 if (mWifiDisplayScanNestCount++ == 0) { 277 registerCallbackIfNeededLocked(); 278 try { 279 mDm.startWifiDisplayScan(); 280 } catch (RemoteException ex) { 281 Log.e(TAG, "Failed to scan for Wifi displays.", ex); 282 } 283 } 284 } 285 } 286 287 public void stopWifiDisplayScan() { 288 synchronized (mLock) { 289 if (--mWifiDisplayScanNestCount == 0) { 290 try { 291 mDm.stopWifiDisplayScan(); 292 } catch (RemoteException ex) { 293 Log.e(TAG, "Failed to scan for Wifi displays.", ex); 294 } 295 } else if (mWifiDisplayScanNestCount < 0) { 296 Log.wtf(TAG, "Wifi display scan nest count became negative: " 297 + mWifiDisplayScanNestCount); 298 mWifiDisplayScanNestCount = 0; 299 } 300 } 301 } 302 303 public void connectWifiDisplay(String deviceAddress) { 304 if (deviceAddress == null) { 305 throw new IllegalArgumentException("deviceAddress must not be null"); 306 } 307 308 try { 309 mDm.connectWifiDisplay(deviceAddress); 310 } catch (RemoteException ex) { 311 Log.e(TAG, "Failed to connect to Wifi display " + deviceAddress + ".", ex); 312 } 313 } 314 315 public void pauseWifiDisplay() { 316 try { 317 mDm.pauseWifiDisplay(); 318 } catch (RemoteException ex) { 319 Log.e(TAG, "Failed to pause Wifi display.", ex); 320 } 321 } 322 323 public void resumeWifiDisplay() { 324 try { 325 mDm.resumeWifiDisplay(); 326 } catch (RemoteException ex) { 327 Log.e(TAG, "Failed to resume Wifi display.", ex); 328 } 329 } 330 331 public void disconnectWifiDisplay() { 332 try { 333 mDm.disconnectWifiDisplay(); 334 } catch (RemoteException ex) { 335 Log.e(TAG, "Failed to disconnect from Wifi display.", ex); 336 } 337 } 338 339 public void renameWifiDisplay(String deviceAddress, String alias) { 340 if (deviceAddress == null) { 341 throw new IllegalArgumentException("deviceAddress must not be null"); 342 } 343 344 try { 345 mDm.renameWifiDisplay(deviceAddress, alias); 346 } catch (RemoteException ex) { 347 Log.e(TAG, "Failed to rename Wifi display " + deviceAddress 348 + " with alias " + alias + ".", ex); 349 } 350 } 351 352 public void forgetWifiDisplay(String deviceAddress) { 353 if (deviceAddress == null) { 354 throw new IllegalArgumentException("deviceAddress must not be null"); 355 } 356 357 try { 358 mDm.forgetWifiDisplay(deviceAddress); 359 } catch (RemoteException ex) { 360 Log.e(TAG, "Failed to forget Wifi display.", ex); 361 } 362 } 363 364 public WifiDisplayStatus getWifiDisplayStatus() { 365 try { 366 return mDm.getWifiDisplayStatus(); 367 } catch (RemoteException ex) { 368 Log.e(TAG, "Failed to get Wifi display status.", ex); 369 return new WifiDisplayStatus(); 370 } 371 } 372 373 public VirtualDisplay createVirtualDisplay(Context context, MediaProjection projection, 374 String name, int width, int height, int densityDpi, Surface surface, int flags, 375 VirtualDisplay.Callback callback, Handler handler) { 376 if (TextUtils.isEmpty(name)) { 377 throw new IllegalArgumentException("name must be non-null and non-empty"); 378 } 379 if (width <= 0 || height <= 0 || densityDpi <= 0) { 380 throw new IllegalArgumentException("width, height, and densityDpi must be " 381 + "greater than 0"); 382 } 383 384 VirtualDisplayCallback callbackWrapper = new VirtualDisplayCallback(callback, handler); 385 IMediaProjection projectionToken = projection != null ? projection.getProjection() : null; 386 int displayId; 387 try { 388 displayId = mDm.createVirtualDisplay(callbackWrapper, projectionToken, 389 context.getPackageName(), name, width, height, densityDpi, surface, flags); 390 } catch (RemoteException ex) { 391 Log.e(TAG, "Could not create virtual display: " + name, ex); 392 return null; 393 } 394 if (displayId < 0) { 395 Log.e(TAG, "Could not create virtual display: " + name); 396 return null; 397 } 398 Display display = getRealDisplay(displayId); 399 if (display == null) { 400 Log.wtf(TAG, "Could not obtain display info for newly created " 401 + "virtual display: " + name); 402 try { 403 mDm.releaseVirtualDisplay(callbackWrapper); 404 } catch (RemoteException ex) { 405 } 406 return null; 407 } 408 return new VirtualDisplay(this, display, callbackWrapper, surface); 409 } 410 411 public void setVirtualDisplaySurface(IVirtualDisplayCallback token, Surface surface) { 412 try { 413 mDm.setVirtualDisplaySurface(token, surface); 414 } catch (RemoteException ex) { 415 Log.w(TAG, "Failed to set virtual display surface.", ex); 416 } 417 } 418 419 public void resizeVirtualDisplay(IVirtualDisplayCallback token, 420 int width, int height, int densityDpi) { 421 try { 422 mDm.resizeVirtualDisplay(token, width, height, densityDpi); 423 } catch (RemoteException ex) { 424 Log.w(TAG, "Failed to resize virtual display.", ex); 425 } 426 } 427 428 public void releaseVirtualDisplay(IVirtualDisplayCallback token) { 429 try { 430 mDm.releaseVirtualDisplay(token); 431 } catch (RemoteException ex) { 432 Log.w(TAG, "Failed to release virtual display.", ex); 433 } 434 } 435 436 private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub { 437 @Override 438 public void onDisplayEvent(int displayId, int event) { 439 if (DEBUG) { 440 Log.d(TAG, "onDisplayEvent: displayId=" + displayId + ", event=" + event); 441 } 442 handleDisplayEvent(displayId, event); 443 } 444 } 445 446 private static final class DisplayListenerDelegate extends Handler { 447 public final DisplayListener mListener; 448 449 public DisplayListenerDelegate(DisplayListener listener, Handler handler) { 450 super(handler != null ? handler.getLooper() : Looper.myLooper(), null, true /*async*/); 451 mListener = listener; 452 } 453 454 public void sendDisplayEvent(int displayId, int event) { 455 Message msg = obtainMessage(event, displayId, 0); 456 sendMessage(msg); 457 } 458 459 public void clearEvents() { 460 removeCallbacksAndMessages(null); 461 } 462 463 @Override 464 public void handleMessage(Message msg) { 465 switch (msg.what) { 466 case EVENT_DISPLAY_ADDED: 467 mListener.onDisplayAdded(msg.arg1); 468 break; 469 case EVENT_DISPLAY_CHANGED: 470 mListener.onDisplayChanged(msg.arg1); 471 break; 472 case EVENT_DISPLAY_REMOVED: 473 mListener.onDisplayRemoved(msg.arg1); 474 break; 475 } 476 } 477 } 478 479 private final static class VirtualDisplayCallback extends IVirtualDisplayCallback.Stub { 480 private VirtualDisplayCallbackDelegate mDelegate; 481 482 public VirtualDisplayCallback(VirtualDisplay.Callback callback, Handler handler) { 483 if (callback != null) { 484 mDelegate = new VirtualDisplayCallbackDelegate(callback, handler); 485 } 486 } 487 488 @Override // Binder call 489 public void onPaused() { 490 if (mDelegate != null) { 491 mDelegate.sendEmptyMessage(VirtualDisplayCallbackDelegate.MSG_DISPLAY_PAUSED); 492 } 493 } 494 495 @Override // Binder call 496 public void onResumed() { 497 if (mDelegate != null) { 498 mDelegate.sendEmptyMessage(VirtualDisplayCallbackDelegate.MSG_DISPLAY_RESUMED); 499 } 500 } 501 502 @Override // Binder call 503 public void onStopped() { 504 if (mDelegate != null) { 505 mDelegate.sendEmptyMessage(VirtualDisplayCallbackDelegate.MSG_DISPLAY_STOPPED); 506 } 507 } 508 } 509 510 private final static class VirtualDisplayCallbackDelegate extends Handler { 511 public static final int MSG_DISPLAY_PAUSED = 0; 512 public static final int MSG_DISPLAY_RESUMED = 1; 513 public static final int MSG_DISPLAY_STOPPED = 2; 514 515 private final VirtualDisplay.Callback mCallback; 516 517 public VirtualDisplayCallbackDelegate(VirtualDisplay.Callback callback, 518 Handler handler) { 519 super(handler != null ? handler.getLooper() : Looper.myLooper(), null, true /*async*/); 520 mCallback = callback; 521 } 522 523 @Override 524 public void handleMessage(Message msg) { 525 switch (msg.what) { 526 case MSG_DISPLAY_PAUSED: 527 mCallback.onPaused(); 528 break; 529 case MSG_DISPLAY_RESUMED: 530 mCallback.onResumed(); 531 break; 532 case MSG_DISPLAY_STOPPED: 533 mCallback.onStopped(); 534 break; 535 } 536 } 537 } 538 } 539