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