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.content.res.Configuration; 21 import android.hardware.display.DisplayManager.DisplayListener; 22 import android.media.projection.MediaProjection; 23 import android.media.projection.IMediaProjection; 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 public void registerDisplayListener(DisplayListener listener, Handler handler) { 196 if (listener == null) { 197 throw new IllegalArgumentException("listener must not be null"); 198 } 199 200 synchronized (mLock) { 201 int index = findDisplayListenerLocked(listener); 202 if (index < 0) { 203 mDisplayListeners.add(new DisplayListenerDelegate(listener, handler)); 204 registerCallbackIfNeededLocked(); 205 } 206 } 207 } 208 209 public void unregisterDisplayListener(DisplayListener listener) { 210 if (listener == null) { 211 throw new IllegalArgumentException("listener must not be null"); 212 } 213 214 synchronized (mLock) { 215 int index = findDisplayListenerLocked(listener); 216 if (index >= 0) { 217 DisplayListenerDelegate d = mDisplayListeners.get(index); 218 d.clearEvents(); 219 mDisplayListeners.remove(index); 220 } 221 } 222 } 223 224 private int findDisplayListenerLocked(DisplayListener listener) { 225 final int numListeners = mDisplayListeners.size(); 226 for (int i = 0; i < numListeners; i++) { 227 if (mDisplayListeners.get(i).mListener == listener) { 228 return i; 229 } 230 } 231 return -1; 232 } 233 234 private void registerCallbackIfNeededLocked() { 235 if (mCallback == null) { 236 mCallback = new DisplayManagerCallback(); 237 try { 238 mDm.registerCallback(mCallback); 239 } catch (RemoteException ex) { 240 Log.e(TAG, "Failed to register callback with display manager service.", ex); 241 mCallback = null; 242 } 243 } 244 } 245 246 private void handleDisplayEvent(int displayId, int event) { 247 synchronized (mLock) { 248 if (USE_CACHE) { 249 mDisplayInfoCache.remove(displayId); 250 251 if (event == EVENT_DISPLAY_ADDED || event == EVENT_DISPLAY_REMOVED) { 252 mDisplayIdCache = null; 253 } 254 } 255 256 final int numListeners = mDisplayListeners.size(); 257 for (int i = 0; i < numListeners; i++) { 258 mDisplayListeners.get(i).sendDisplayEvent(displayId, event); 259 } 260 } 261 } 262 263 public void startWifiDisplayScan() { 264 synchronized (mLock) { 265 if (mWifiDisplayScanNestCount++ == 0) { 266 registerCallbackIfNeededLocked(); 267 try { 268 mDm.startWifiDisplayScan(); 269 } catch (RemoteException ex) { 270 Log.e(TAG, "Failed to scan for Wifi displays.", ex); 271 } 272 } 273 } 274 } 275 276 public void stopWifiDisplayScan() { 277 synchronized (mLock) { 278 if (--mWifiDisplayScanNestCount == 0) { 279 try { 280 mDm.stopWifiDisplayScan(); 281 } catch (RemoteException ex) { 282 Log.e(TAG, "Failed to scan for Wifi displays.", ex); 283 } 284 } else if (mWifiDisplayScanNestCount < 0) { 285 Log.wtf(TAG, "Wifi display scan nest count became negative: " 286 + mWifiDisplayScanNestCount); 287 mWifiDisplayScanNestCount = 0; 288 } 289 } 290 } 291 292 public void connectWifiDisplay(String deviceAddress) { 293 if (deviceAddress == null) { 294 throw new IllegalArgumentException("deviceAddress must not be null"); 295 } 296 297 try { 298 mDm.connectWifiDisplay(deviceAddress); 299 } catch (RemoteException ex) { 300 Log.e(TAG, "Failed to connect to Wifi display " + deviceAddress + ".", ex); 301 } 302 } 303 304 public void pauseWifiDisplay() { 305 try { 306 mDm.pauseWifiDisplay(); 307 } catch (RemoteException ex) { 308 Log.e(TAG, "Failed to pause Wifi display.", ex); 309 } 310 } 311 312 public void resumeWifiDisplay() { 313 try { 314 mDm.resumeWifiDisplay(); 315 } catch (RemoteException ex) { 316 Log.e(TAG, "Failed to resume Wifi display.", ex); 317 } 318 } 319 320 public void disconnectWifiDisplay() { 321 try { 322 mDm.disconnectWifiDisplay(); 323 } catch (RemoteException ex) { 324 Log.e(TAG, "Failed to disconnect from Wifi display.", ex); 325 } 326 } 327 328 public void renameWifiDisplay(String deviceAddress, String alias) { 329 if (deviceAddress == null) { 330 throw new IllegalArgumentException("deviceAddress must not be null"); 331 } 332 333 try { 334 mDm.renameWifiDisplay(deviceAddress, alias); 335 } catch (RemoteException ex) { 336 Log.e(TAG, "Failed to rename Wifi display " + deviceAddress 337 + " with alias " + alias + ".", ex); 338 } 339 } 340 341 public void forgetWifiDisplay(String deviceAddress) { 342 if (deviceAddress == null) { 343 throw new IllegalArgumentException("deviceAddress must not be null"); 344 } 345 346 try { 347 mDm.forgetWifiDisplay(deviceAddress); 348 } catch (RemoteException ex) { 349 Log.e(TAG, "Failed to forget Wifi display.", ex); 350 } 351 } 352 353 public WifiDisplayStatus getWifiDisplayStatus() { 354 try { 355 return mDm.getWifiDisplayStatus(); 356 } catch (RemoteException ex) { 357 Log.e(TAG, "Failed to get Wifi display status.", ex); 358 return new WifiDisplayStatus(); 359 } 360 } 361 362 public VirtualDisplay createVirtualDisplay(Context context, MediaProjection projection, 363 String name, int width, int height, int densityDpi, Surface surface, int flags, 364 VirtualDisplay.Callback callback, Handler handler) { 365 if (TextUtils.isEmpty(name)) { 366 throw new IllegalArgumentException("name must be non-null and non-empty"); 367 } 368 if (width <= 0 || height <= 0 || densityDpi <= 0) { 369 throw new IllegalArgumentException("width, height, and densityDpi must be " 370 + "greater than 0"); 371 } 372 373 VirtualDisplayCallback callbackWrapper = new VirtualDisplayCallback(callback, handler); 374 IMediaProjection projectionToken = projection != null ? projection.getProjection() : null; 375 int displayId; 376 try { 377 displayId = mDm.createVirtualDisplay(callbackWrapper, projectionToken, 378 context.getPackageName(), name, width, height, densityDpi, surface, flags); 379 } catch (RemoteException ex) { 380 Log.e(TAG, "Could not create virtual display: " + name, ex); 381 return null; 382 } 383 if (displayId < 0) { 384 Log.e(TAG, "Could not create virtual display: " + name); 385 return null; 386 } 387 Display display = getRealDisplay(displayId); 388 if (display == null) { 389 Log.wtf(TAG, "Could not obtain display info for newly created " 390 + "virtual display: " + name); 391 try { 392 mDm.releaseVirtualDisplay(callbackWrapper); 393 } catch (RemoteException ex) { 394 } 395 return null; 396 } 397 return new VirtualDisplay(this, display, callbackWrapper, surface); 398 } 399 400 public void setVirtualDisplaySurface(IVirtualDisplayCallback token, Surface surface) { 401 try { 402 mDm.setVirtualDisplaySurface(token, surface); 403 } catch (RemoteException ex) { 404 Log.w(TAG, "Failed to set virtual display surface.", ex); 405 } 406 } 407 408 public void resizeVirtualDisplay(IVirtualDisplayCallback token, 409 int width, int height, int densityDpi) { 410 try { 411 mDm.resizeVirtualDisplay(token, width, height, densityDpi); 412 } catch (RemoteException ex) { 413 Log.w(TAG, "Failed to resize virtual display.", ex); 414 } 415 } 416 417 public void releaseVirtualDisplay(IVirtualDisplayCallback token) { 418 try { 419 mDm.releaseVirtualDisplay(token); 420 } catch (RemoteException ex) { 421 Log.w(TAG, "Failed to release virtual display.", ex); 422 } 423 } 424 425 private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub { 426 @Override 427 public void onDisplayEvent(int displayId, int event) { 428 if (DEBUG) { 429 Log.d(TAG, "onDisplayEvent: displayId=" + displayId + ", event=" + event); 430 } 431 handleDisplayEvent(displayId, event); 432 } 433 } 434 435 private static final class DisplayListenerDelegate extends Handler { 436 public final DisplayListener mListener; 437 438 public DisplayListenerDelegate(DisplayListener listener, Handler handler) { 439 super(handler != null ? handler.getLooper() : Looper.myLooper(), null, true /*async*/); 440 mListener = listener; 441 } 442 443 public void sendDisplayEvent(int displayId, int event) { 444 Message msg = obtainMessage(event, displayId, 0); 445 sendMessage(msg); 446 } 447 448 public void clearEvents() { 449 removeCallbacksAndMessages(null); 450 } 451 452 @Override 453 public void handleMessage(Message msg) { 454 switch (msg.what) { 455 case EVENT_DISPLAY_ADDED: 456 mListener.onDisplayAdded(msg.arg1); 457 break; 458 case EVENT_DISPLAY_CHANGED: 459 mListener.onDisplayChanged(msg.arg1); 460 break; 461 case EVENT_DISPLAY_REMOVED: 462 mListener.onDisplayRemoved(msg.arg1); 463 break; 464 } 465 } 466 } 467 468 private final static class VirtualDisplayCallback extends IVirtualDisplayCallback.Stub { 469 private VirtualDisplayCallbackDelegate mDelegate; 470 471 public VirtualDisplayCallback(VirtualDisplay.Callback callback, Handler handler) { 472 if (callback != null) { 473 mDelegate = new VirtualDisplayCallbackDelegate(callback, handler); 474 } 475 } 476 477 @Override // Binder call 478 public void onPaused() { 479 if (mDelegate != null) { 480 mDelegate.sendEmptyMessage(VirtualDisplayCallbackDelegate.MSG_DISPLAY_PAUSED); 481 } 482 } 483 484 @Override // Binder call 485 public void onResumed() { 486 if (mDelegate != null) { 487 mDelegate.sendEmptyMessage(VirtualDisplayCallbackDelegate.MSG_DISPLAY_RESUMED); 488 } 489 } 490 491 @Override // Binder call 492 public void onStopped() { 493 if (mDelegate != null) { 494 mDelegate.sendEmptyMessage(VirtualDisplayCallbackDelegate.MSG_DISPLAY_STOPPED); 495 } 496 } 497 } 498 499 private final static class VirtualDisplayCallbackDelegate extends Handler { 500 public static final int MSG_DISPLAY_PAUSED = 0; 501 public static final int MSG_DISPLAY_RESUMED = 1; 502 public static final int MSG_DISPLAY_STOPPED = 2; 503 504 private final VirtualDisplay.Callback mCallback; 505 506 public VirtualDisplayCallbackDelegate(VirtualDisplay.Callback callback, 507 Handler handler) { 508 super(handler != null ? handler.getLooper() : Looper.myLooper(), null, true /*async*/); 509 mCallback = callback; 510 } 511 512 @Override 513 public void handleMessage(Message msg) { 514 switch (msg.what) { 515 case MSG_DISPLAY_PAUSED: 516 mCallback.onPaused(); 517 break; 518 case MSG_DISPLAY_RESUMED: 519 mCallback.onResumed(); 520 break; 521 case MSG_DISPLAY_STOPPED: 522 mCallback.onStopped(); 523 break; 524 } 525 } 526 } 527 } 528