1 package org.robolectric.shadows; 2 3 import static android.os.Build.VERSION_CODES.O_MR1; 4 import static android.os.Build.VERSION_CODES.P; 5 import static android.os.Build.VERSION_CODES.Q; 6 import static org.robolectric.util.reflector.Reflector.reflector; 7 8 import android.graphics.Point; 9 import android.hardware.display.BrightnessChangeEvent; 10 import android.hardware.display.BrightnessConfiguration; 11 import android.hardware.display.DisplayManagerGlobal; 12 import android.hardware.display.IDisplayManager; 13 import android.hardware.display.IDisplayManagerCallback; 14 import android.hardware.display.IVirtualDisplayCallback; 15 import android.hardware.display.VirtualDisplayConfig; 16 import android.hardware.display.WifiDisplayStatus; 17 import android.media.projection.IMediaProjection; 18 import android.os.Handler; 19 import android.os.RemoteException; 20 import android.util.SparseArray; 21 import android.view.Display; 22 import android.view.DisplayInfo; 23 import android.view.Surface; 24 import com.google.common.annotations.VisibleForTesting; 25 import java.lang.reflect.Field; 26 import java.util.ArrayList; 27 import java.util.HashMap; 28 import java.util.List; 29 import java.util.Map; 30 import java.util.TreeMap; 31 import java.util.concurrent.CopyOnWriteArrayList; 32 import javax.annotation.Nullable; 33 import org.robolectric.RuntimeEnvironment; 34 import org.robolectric.android.Bootstrap; 35 import org.robolectric.annotation.ClassName; 36 import org.robolectric.annotation.HiddenApi; 37 import org.robolectric.annotation.Implementation; 38 import org.robolectric.annotation.Implements; 39 import org.robolectric.annotation.Resetter; 40 import org.robolectric.shadow.api.Shadow; 41 import org.robolectric.util.ReflectionHelpers; 42 import org.robolectric.util.reflector.Accessor; 43 import org.robolectric.util.reflector.ForType; 44 45 /** Shadow for {@link DisplayManagerGlobal}. */ 46 @Implements(value = DisplayManagerGlobal.class, isInAndroidSdk = false) 47 public class ShadowDisplayManagerGlobal { 48 private static final String TOPOLOGY_LISTENERS_FIELD_NAME = "mTopologyListeners"; 49 private static DisplayManagerGlobal instance; 50 51 private float saturationLevel = 1f; 52 private final SparseArray<BrightnessConfiguration> brightnessConfiguration = new SparseArray<>(); 53 private final List<BrightnessChangeEvent> brightnessChangeEvents = new ArrayList<>(); 54 private Object defaultBrightnessConfiguration; 55 56 private DisplayManagerProxyDelegate mDm; 57 58 @Resetter reset()59 public static void reset() { 60 instance = null; 61 } 62 63 @Implementation __constructor__(IDisplayManager dm)64 protected void __constructor__(IDisplayManager dm) { 65 // No-op the constructor. The real constructor references the ColorSpace named constants, which 66 // require native calls to instantiate. This will cause native graphics libraries to be loaded 67 // any time an Application object is created. Instead override the constructor to avoid 68 // referencing the ColorSpace named constants, making application creation around 0.75s faster. 69 } 70 71 @Implementation getInstance()72 public static synchronized DisplayManagerGlobal getInstance() { 73 if (instance == null) { 74 DisplayManagerProxyDelegate displayManagerProxyDelegate = new DisplayManagerProxyDelegate(); 75 IDisplayManager proxy = 76 ReflectionHelpers.createDelegatingProxy( 77 IDisplayManager.class, displayManagerProxyDelegate); 78 instance = newDisplayManagerGlobal(proxy); 79 ShadowDisplayManagerGlobal shadow = Shadow.extract(instance); 80 shadow.mDm = displayManagerProxyDelegate; 81 Bootstrap.setUpDisplay(); 82 } 83 return instance; 84 } 85 newDisplayManagerGlobal(IDisplayManager displayManager)86 private static DisplayManagerGlobal newDisplayManagerGlobal(IDisplayManager displayManager) { 87 instance = Shadow.newInstanceOf(DisplayManagerGlobal.class); 88 DisplayManagerGlobalReflector displayManagerGlobal = 89 reflector(DisplayManagerGlobalReflector.class, instance); 90 displayManagerGlobal.setDm(displayManager); 91 displayManagerGlobal.setLock(new Object()); 92 List<Handler> displayListeners = createDisplayListeners(); 93 displayManagerGlobal.setDisplayListeners(displayListeners); 94 if (ReflectionHelpers.hasField(DisplayManagerGlobal.class, 95 TOPOLOGY_LISTENERS_FIELD_NAME)) { 96 displayManagerGlobal.setTopologyListeners(new CopyOnWriteArrayList<>()); 97 } 98 displayManagerGlobal.setDisplayInfoCache(new SparseArray<>()); 99 return instance; 100 } 101 createDisplayListeners()102 private static List<Handler> createDisplayListeners() { 103 try { 104 // The type for mDisplayListeners was changed from ArrayList to CopyOnWriteArrayList 105 // in some branches of T and U, so we need to reflect on DisplayManagerGlobal class 106 // to check the type of mDisplayListeners member before initializing appropriately. 107 Field f = DisplayManagerGlobal.class.getDeclaredField("mDisplayListeners"); 108 if (f.getType().isAssignableFrom(ArrayList.class)) { 109 return new ArrayList<>(); 110 } else { 111 return new CopyOnWriteArrayList<>(); 112 } 113 } catch (NoSuchFieldException e) { 114 throw new RuntimeException(e); 115 } 116 } 117 118 @VisibleForTesting getGlobalInstance()119 static DisplayManagerGlobal getGlobalInstance() { 120 return instance; 121 } 122 123 @Implementation getWifiDisplayStatus()124 protected WifiDisplayStatus getWifiDisplayStatus() { 125 return new WifiDisplayStatus(); 126 } 127 128 /** Returns the 'natural' dimensions of the default display. */ 129 @Implementation(minSdk = O_MR1) getStableDisplaySize()130 public Point getStableDisplaySize() throws RemoteException { 131 DisplayInfo defaultDisplayInfo = mDm.getDisplayInfo(Display.DEFAULT_DISPLAY); 132 return new Point(defaultDisplayInfo.getNaturalWidth(), defaultDisplayInfo.getNaturalHeight()); 133 } 134 addDisplay(DisplayInfo displayInfo)135 int addDisplay(DisplayInfo displayInfo) { 136 fixNominalDimens(displayInfo); 137 138 return mDm.addDisplay(displayInfo); 139 } 140 fixNominalDimens(DisplayInfo displayInfo)141 private void fixNominalDimens(DisplayInfo displayInfo) { 142 int min = Math.min(displayInfo.appWidth, displayInfo.appHeight); 143 int max = Math.max(displayInfo.appWidth, displayInfo.appHeight); 144 displayInfo.smallestNominalAppHeight = displayInfo.smallestNominalAppWidth = min; 145 displayInfo.largestNominalAppHeight = displayInfo.largestNominalAppWidth = max; 146 } 147 changeDisplay(int displayId, DisplayInfo displayInfo)148 void changeDisplay(int displayId, DisplayInfo displayInfo) { 149 mDm.changeDisplay(displayId, displayInfo); 150 } 151 removeDisplay(int displayId)152 void removeDisplay(int displayId) { 153 mDm.removeDisplay(displayId); 154 } 155 getSystemUi(int displayId)156 SystemUi getSystemUi(int displayId) { 157 return mDm.getSystemUi(displayId); 158 } 159 160 /** 161 * A delegating proxy for the IDisplayManager system service. 162 * 163 * <p>The method signatures here must exactly match the IDisplayManager interface. 164 * 165 * @see ReflectionHelpers#createDelegatingProxy(Class, Object) 166 */ 167 private static class DisplayManagerProxyDelegate { 168 private final TreeMap<Integer, DisplayInfo> displayInfos = new TreeMap<>(); 169 private final Map<Integer, SystemUi> systemUis = new HashMap<>(); 170 private int nextDisplayId = 0; 171 private final List<IDisplayManagerCallback> callbacks = new ArrayList<>(); 172 private final Map<IVirtualDisplayCallback, Integer> virtualDisplayIds = new HashMap<>(); 173 174 // @Override getDisplayInfo(int i)175 public DisplayInfo getDisplayInfo(int i) throws RemoteException { 176 DisplayInfo displayInfo = displayInfos.get(i); 177 return displayInfo == null ? null : new DisplayInfo(displayInfo); 178 } 179 180 // @Override // todo: use @Implements/@Implementation for signature checking getDisplayIds()181 public int[] getDisplayIds() { 182 int[] ids = new int[displayInfos.size()]; 183 int i = 0; 184 for (Integer displayId : displayInfos.keySet()) { 185 ids[i++] = displayId; 186 } 187 return ids; 188 } 189 190 // Added in Android T 191 @SuppressWarnings("unused") getDisplayIds(boolean ignoredIncludeDisabled)192 public int[] getDisplayIds(boolean ignoredIncludeDisabled) { 193 return getDisplayIds(); 194 } 195 getSystemUi(int displayId)196 public SystemUi getSystemUi(int displayId) { 197 return systemUis.get(displayId); 198 } 199 200 // @Override registerCallback(IDisplayManagerCallback iDisplayManagerCallback)201 public void registerCallback(IDisplayManagerCallback iDisplayManagerCallback) 202 throws RemoteException { 203 this.callbacks.add(iDisplayManagerCallback); 204 } 205 registerCallbackWithEventMask( IDisplayManagerCallback iDisplayManagerCallback, long ignoredEventsMask)206 public void registerCallbackWithEventMask( 207 IDisplayManagerCallback iDisplayManagerCallback, long ignoredEventsMask) 208 throws RemoteException { 209 registerCallback(iDisplayManagerCallback); 210 } 211 212 // for android R+ (SDK 30+) 213 // Use Object here instead of VirtualDisplayConfig to avoid breaking projects that still 214 // compile against SDKs < R createVirtualDisplay( @lassName"android.hardware.display.VirtualDisplayConfig") Object virtualDisplayConfigObject, IVirtualDisplayCallback callbackWrapper, IMediaProjection projectionToken, String packageName)215 public int createVirtualDisplay( 216 @ClassName("android.hardware.display.VirtualDisplayConfig") 217 Object virtualDisplayConfigObject, 218 IVirtualDisplayCallback callbackWrapper, 219 IMediaProjection projectionToken, 220 String packageName) { 221 VirtualDisplayConfig config = (VirtualDisplayConfig) virtualDisplayConfigObject; 222 DisplayInfo displayInfo = new DisplayInfo(); 223 displayInfo.flags = config.getFlags(); 224 displayInfo.type = Display.TYPE_VIRTUAL; 225 displayInfo.name = config.getName(); 226 displayInfo.logicalDensityDpi = config.getDensityDpi(); 227 displayInfo.physicalXDpi = config.getDensityDpi(); 228 displayInfo.physicalYDpi = config.getDensityDpi(); 229 displayInfo.ownerPackageName = packageName; 230 displayInfo.appWidth = config.getWidth(); 231 displayInfo.logicalWidth = config.getWidth(); 232 displayInfo.appHeight = config.getHeight(); 233 displayInfo.logicalHeight = config.getHeight(); 234 displayInfo.state = Display.STATE_ON; 235 int id = addDisplay(displayInfo); 236 virtualDisplayIds.put(callbackWrapper, id); 237 return id; 238 } 239 240 // for android Q (SDK 29) and below createVirtualDisplay( IVirtualDisplayCallback callbackWrapper, IMediaProjection projectionToken, String packageName, String name, int width, int height, int densityDpi, Surface surface, int flags, String uniqueId)241 public int createVirtualDisplay( 242 IVirtualDisplayCallback callbackWrapper, 243 IMediaProjection projectionToken, 244 String packageName, 245 String name, 246 int width, 247 int height, 248 int densityDpi, 249 Surface surface, 250 int flags, 251 String uniqueId) { 252 DisplayInfo displayInfo = new DisplayInfo(); 253 displayInfo.flags = flags; 254 displayInfo.type = Display.TYPE_VIRTUAL; 255 displayInfo.name = name; 256 displayInfo.logicalDensityDpi = densityDpi; 257 displayInfo.physicalXDpi = densityDpi; 258 displayInfo.physicalYDpi = densityDpi; 259 displayInfo.ownerPackageName = packageName; 260 displayInfo.appWidth = width; 261 displayInfo.logicalWidth = width; 262 displayInfo.appHeight = height; 263 displayInfo.logicalHeight = height; 264 displayInfo.state = Display.STATE_ON; 265 int id = addDisplay(displayInfo); 266 virtualDisplayIds.put(callbackWrapper, id); 267 return id; 268 } 269 270 // for android U resizeVirtualDisplay( IVirtualDisplayCallback token, int width, int height, int densityDpi)271 public void resizeVirtualDisplay( 272 IVirtualDisplayCallback token, int width, int height, int densityDpi) { 273 Integer id = virtualDisplayIds.get(token); 274 DisplayInfo displayInfo = displayInfos.get(id); 275 276 displayInfo.logicalDensityDpi = densityDpi; 277 displayInfo.appWidth = width; 278 displayInfo.logicalWidth = width; 279 displayInfo.appHeight = height; 280 displayInfo.logicalHeight = height; 281 changeDisplay(id, displayInfo); 282 } 283 284 // for android U releaseVirtualDisplay(IVirtualDisplayCallback token)285 public void releaseVirtualDisplay(IVirtualDisplayCallback token) { 286 if (virtualDisplayIds.containsKey(token)) { 287 removeDisplay(virtualDisplayIds.remove(token)); 288 } 289 } 290 291 // for android Q through V 292 // @Override setVirtualDisplayState(IVirtualDisplayCallback token, boolean isOn)293 public void setVirtualDisplayState(IVirtualDisplayCallback token, boolean isOn) { 294 Integer id = virtualDisplayIds.get(token); 295 DisplayInfo displayInfo = displayInfos.get(id); 296 int newState = isOn ? Display.STATE_ON : Display.STATE_OFF; 297 if (displayInfo.state != newState) { 298 displayInfo.state = newState; 299 changeDisplay(id, displayInfo); 300 } 301 } 302 setVirtualDisplaySurface(IVirtualDisplayCallback token, Surface surface)303 public void setVirtualDisplaySurface(IVirtualDisplayCallback token, Surface surface) { 304 // in post android V, the setVirtualDisplayState has been removed and the virtual device 305 // state is propagated from system service 306 // TODO: also check power group state if > android V 307 setVirtualDisplayState(token, surface != null); 308 } 309 addDisplay(DisplayInfo displayInfo)310 private synchronized int addDisplay(DisplayInfo displayInfo) { 311 int nextId = nextDisplayId++; 312 displayInfos.put(nextId, displayInfo); 313 if (RuntimeEnvironment.getApiLevel() >= Q) { 314 displayInfo.displayId = nextId; 315 } 316 systemUis.put(nextId, new SystemUi(nextId)); 317 notifyListeners(nextId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED); 318 return nextId; 319 } 320 changeDisplay(int displayId, DisplayInfo displayInfo)321 private synchronized void changeDisplay(int displayId, DisplayInfo displayInfo) { 322 if (!displayInfos.containsKey(displayId)) { 323 throw new IllegalStateException("no display " + displayId); 324 } 325 326 displayInfos.put(displayId, displayInfo); 327 notifyListeners(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); 328 } 329 removeDisplay(int displayId)330 private synchronized void removeDisplay(int displayId) { 331 if (!displayInfos.containsKey(displayId)) { 332 throw new IllegalStateException("no display " + displayId); 333 } 334 335 displayInfos.remove(displayId); 336 systemUis.remove(displayId); 337 notifyListeners(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED); 338 } 339 notifyListeners(int nextId, int event)340 private void notifyListeners(int nextId, int event) { 341 for (IDisplayManagerCallback callback : callbacks) { 342 try { 343 callback.onDisplayEvent(nextId, event); 344 } catch (RemoteException e) { 345 throw new RuntimeException(e); 346 } 347 } 348 } 349 } 350 351 @Implementation(minSdk = P, maxSdk = P) setSaturationLevel(float level)352 protected void setSaturationLevel(float level) { 353 if (level < 0f || level > 1f) { 354 throw new IllegalArgumentException("Saturation level must be between 0 and 1"); 355 } 356 saturationLevel = level; 357 } 358 359 /** 360 * Returns the current display saturation level; {@link android.os.Build.VERSION_CODES.P} only. 361 */ getSaturationLevel()362 float getSaturationLevel() { 363 return saturationLevel; 364 } 365 366 @Implementation(minSdk = P) 367 @HiddenApi setBrightnessConfigurationForUser( @lassName"android.hardware.display.BrightnessConfiguration") Object configObject, int userId, String packageName)368 protected void setBrightnessConfigurationForUser( 369 @ClassName("android.hardware.display.BrightnessConfiguration") Object configObject, 370 int userId, 371 String packageName) { 372 BrightnessConfiguration config = (BrightnessConfiguration) configObject; 373 brightnessConfiguration.put((int) userId, config); 374 } 375 376 @Implementation(minSdk = P) 377 @HiddenApi 378 protected @ClassName("android.hardware.display.BrightnessConfiguration") Object getBrightnessConfigurationForUser(int userId)379 getBrightnessConfigurationForUser(int userId) { 380 BrightnessConfiguration config = brightnessConfiguration.get(userId); 381 if (config != null) { 382 return config; 383 } else { 384 return getDefaultBrightnessConfiguration(); 385 } 386 } 387 388 @Implementation(minSdk = P) 389 @HiddenApi 390 protected @ClassName("android.hardware.display.BrightnessConfiguration") Object getDefaultBrightnessConfiguration()391 getDefaultBrightnessConfiguration() { 392 return defaultBrightnessConfiguration; 393 } 394 setDefaultBrightnessConfiguration(@ullable Object configObject)395 void setDefaultBrightnessConfiguration(@Nullable Object configObject) { 396 BrightnessConfiguration config = (BrightnessConfiguration) configObject; 397 defaultBrightnessConfiguration = config; 398 } 399 400 @Implementation(minSdk = P) 401 @HiddenApi getBrightnessEvents(String callingPackage)402 protected List<BrightnessChangeEvent> getBrightnessEvents(String callingPackage) { 403 return brightnessChangeEvents; 404 } 405 setBrightnessEvents(List<BrightnessChangeEvent> events)406 void setBrightnessEvents(List<BrightnessChangeEvent> events) { 407 brightnessChangeEvents.clear(); 408 brightnessChangeEvents.addAll(events); 409 } 410 411 @ForType(DisplayManagerGlobal.class) 412 interface DisplayManagerGlobalReflector { 413 @Accessor("mDm") setDm(IDisplayManager displayManager)414 void setDm(IDisplayManager displayManager); 415 416 @Accessor("mLock") setLock(Object lock)417 void setLock(Object lock); 418 419 @Accessor("mDisplayListeners") setDisplayListeners(List<Handler> list)420 void setDisplayListeners(List<Handler> list); 421 422 @Accessor(TOPOLOGY_LISTENERS_FIELD_NAME) setTopologyListeners(List<Handler> list)423 void setTopologyListeners(List<Handler> list); 424 425 @Accessor("mDisplayInfoCache") setDisplayInfoCache(SparseArray<DisplayInfo> displayInfoCache)426 void setDisplayInfoCache(SparseArray<DisplayInfo> displayInfoCache); 427 } 428 } 429