1 package org.robolectric.shadows; 2 3 import static org.robolectric.util.reflector.Reflector.reflector; 4 5 import android.content.Context; 6 import android.os.Build; 7 import android.os.Build.VERSION_CODES; 8 import android.util.DisplayMetrics; 9 import android.view.Display; 10 import android.view.Display.HdrCapabilities; 11 import android.view.Surface; 12 import android.view.WindowManager; 13 import org.robolectric.RuntimeEnvironment; 14 import org.robolectric.annotation.Implementation; 15 import org.robolectric.annotation.Implements; 16 import org.robolectric.annotation.RealObject; 17 import org.robolectric.util.reflector.Accessor; 18 import org.robolectric.util.reflector.Direct; 19 import org.robolectric.util.reflector.ForType; 20 21 /** 22 * It is possible to override some display properties using setters on {@link ShadowDisplay}. 23 * 24 * @see <a href="http://robolectric.org/device-configuration/">device configuration</a> for details. 25 */ 26 @SuppressWarnings({"UnusedDeclaration"}) 27 @Implements(value = Display.class) 28 public class ShadowDisplay { 29 30 /** 31 * Returns the default display. 32 * 33 * @return the default display 34 */ getDefaultDisplay()35 public static Display getDefaultDisplay() { 36 WindowManager windowManager = 37 (WindowManager) 38 RuntimeEnvironment.getApplication().getSystemService(Context.WINDOW_SERVICE); 39 return windowManager.getDefaultDisplay(); 40 } 41 42 @RealObject Display realObject; 43 44 private Float refreshRate; 45 private Float scaledDensity; 46 47 /** 48 * If {@link #setScaledDensity(float)} has been called, {@link DisplayMetrics#scaledDensity} will 49 * be modified to reflect the value specified. Note that this is not a realistic state. 50 * 51 * @deprecated This behavior is deprecated and will be removed in Robolectric 4.13. 52 */ 53 @Deprecated 54 @Implementation getMetrics(DisplayMetrics outMetrics)55 protected void getMetrics(DisplayMetrics outMetrics) { 56 reflector(_Display_.class, realObject).getMetrics(outMetrics); 57 if (scaledDensity != null) { 58 outMetrics.scaledDensity = scaledDensity; 59 } 60 } 61 62 /** 63 * If {@link #setScaledDensity(float)} has been called, {@link DisplayMetrics#scaledDensity} will 64 * be modified to reflect the value specified. Note that this is not a realistic state. 65 * 66 * @deprecated This behavior is deprecated and will be removed in Robolectric 4.13. 67 */ 68 @Deprecated 69 @Implementation getRealMetrics(DisplayMetrics outMetrics)70 protected void getRealMetrics(DisplayMetrics outMetrics) { 71 reflector(_Display_.class, realObject).getRealMetrics(outMetrics); 72 if (scaledDensity != null) { 73 outMetrics.scaledDensity = scaledDensity; 74 } 75 } 76 77 /** 78 * Changes the scaled density for this display. 79 * 80 * @deprecated This method is deprecated and will be removed in Robolectric 4.13. 81 */ 82 @Deprecated setScaledDensity(float scaledDensity)83 public void setScaledDensity(float scaledDensity) { 84 this.scaledDensity = scaledDensity; 85 } 86 87 /** 88 * If {@link #setRefreshRate(float)} has been called, this method will return the specified value. 89 * 90 * @deprecated This behavior is deprecated and will be removed in Robolectric 3.7. 91 */ 92 @Deprecated 93 @Implementation getRefreshRate()94 protected float getRefreshRate() { 95 if (refreshRate != null) { 96 return refreshRate; 97 } 98 float realRefreshRate = reflector(_Display_.class, realObject).getRefreshRate(); 99 // refresh rate may be set by native code. if its 0, set to 60fps 100 if (realRefreshRate < 0.1) { 101 realRefreshRate = 60; 102 } 103 return realRefreshRate; 104 } 105 106 /** 107 * Changes the density for this display. 108 * 109 * <p>Any registered {@link android.hardware.display.DisplayManager.DisplayListener}s will be 110 * notified of the change. 111 */ setDensity(float density)112 public void setDensity(float density) { 113 setDensityDpi((int) (density * DisplayMetrics.DENSITY_DEFAULT)); 114 } 115 116 /** 117 * Changes the density for this display. 118 * 119 * <p>Any registered {@link android.hardware.display.DisplayManager.DisplayListener}s will be 120 * notified of the change. 121 */ setDensityDpi(int densityDpi)122 public void setDensityDpi(int densityDpi) { 123 ShadowDisplayManager.changeDisplay( 124 realObject.getDisplayId(), di -> di.logicalDensityDpi = densityDpi); 125 } 126 127 /** 128 * Changes the horizontal DPI for this display. 129 * 130 * <p>Any registered {@link android.hardware.display.DisplayManager.DisplayListener}s will be 131 * notified of the change. 132 */ setXdpi(float xdpi)133 public void setXdpi(float xdpi) { 134 ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.physicalXDpi = xdpi); 135 } 136 137 /** 138 * Changes the vertical DPI for this display. 139 * 140 * <p>Any registered {@link android.hardware.display.DisplayManager.DisplayListener}s will be 141 * notified of the change. 142 */ setYdpi(float ydpi)143 public void setYdpi(float ydpi) { 144 ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.physicalYDpi = ydpi); 145 } 146 147 /** 148 * Changes the name for this display. 149 * 150 * <p>Any registered {@link android.hardware.display.DisplayManager.DisplayListener}s will be 151 * notified of the change. 152 */ setName(String name)153 public void setName(String name) { 154 ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.name = name); 155 } 156 157 /** 158 * Changes the flags for this display. 159 * 160 * <p>Any registered {@link android.hardware.display.DisplayManager.DisplayListener}s will be 161 * notified of the change. 162 */ setFlags(int flags)163 public void setFlags(int flags) { 164 reflector(_Display_.class, realObject).setFlags(flags); 165 166 ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.flags = flags); 167 } 168 169 /** 170 * Changes the width available to the application for this display. 171 * 172 * <p>Any registered {@link android.hardware.display.DisplayManager.DisplayListener}s will be 173 * notified of the change. 174 * 175 * @param width the new width in pixels 176 */ setWidth(int width)177 public void setWidth(int width) { 178 ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.appWidth = width); 179 } 180 181 /** 182 * Changes the height available to the application for this display. 183 * 184 * <p>Any registered {@link android.hardware.display.DisplayManager.DisplayListener}s will be 185 * notified of the change. 186 * 187 * @param height new height in pixels 188 */ setHeight(int height)189 public void setHeight(int height) { 190 ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.appHeight = height); 191 } 192 193 /** 194 * Changes the simulated physical width for this display. 195 * 196 * <p>Any registered {@link android.hardware.display.DisplayManager.DisplayListener}s will be 197 * notified of the change. 198 * 199 * @param width the new width in pixels 200 */ setRealWidth(int width)201 public void setRealWidth(int width) { 202 ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.logicalWidth = width); 203 } 204 205 /** 206 * Changes the simulated physical height for this display. 207 * 208 * <p>Any registered {@link android.hardware.display.DisplayManager.DisplayListener}s will be 209 * notified of the change. 210 * 211 * @param height the new height in pixels 212 */ setRealHeight(int height)213 public void setRealHeight(int height) { 214 ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.logicalHeight = height); 215 } 216 217 /** 218 * Changes the refresh rate for this display. 219 * 220 * <p>Any registered {@link android.hardware.display.DisplayManager.DisplayListener}s will be 221 * notified of the change. 222 */ setRefreshRate(float refreshRate)223 public void setRefreshRate(float refreshRate) { 224 this.refreshRate = refreshRate; 225 } 226 227 /** 228 * Changes the rotation for this display. 229 * 230 * <p>Any registered {@link android.hardware.display.DisplayManager.DisplayListener}s will be 231 * notified of the change. 232 * 233 * @param rotation one of {@link Surface#ROTATION_0}, {@link Surface#ROTATION_90}, {@link 234 * Surface#ROTATION_180}, {@link Surface#ROTATION_270} 235 */ setRotation(int rotation)236 public void setRotation(int rotation) { 237 ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.rotation = rotation); 238 } 239 240 /** 241 * Changes the simulated state for this display, such as whether it is on or off 242 * 243 * <p>Any registered {@link android.hardware.display.DisplayManager.DisplayListener}s will be 244 * notified of the change. 245 * 246 * @param state the new state: one of {@link Display#STATE_OFF}, {@link Display#STATE_ON}, {@link 247 * Display#STATE_DOZE}, {@link Display#STATE_DOZE_SUSPEND}, or {@link Display#STATE_UNKNOWN}. 248 */ setState(int state)249 public void setState(int state) { 250 ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.state = state); 251 } 252 253 /** 254 * Set HDR capabilities to the display sourced with displayId. see {@link HdrCapabilities} for 255 * supportedHdrTypes. 256 * 257 * @throws UnsupportedOperationException if the method is called below Android vesrsion N. 258 */ setDisplayHdrCapabilities( int displayId, float maxLuminance, float maxAverageLuminance, float minLuminance, int... supportedHdrTypes)259 public void setDisplayHdrCapabilities( 260 int displayId, 261 float maxLuminance, 262 float maxAverageLuminance, 263 float minLuminance, 264 int... supportedHdrTypes) { 265 if (Build.VERSION.SDK_INT < VERSION_CODES.N) { 266 throw new UnsupportedOperationException("HDR capabilities are not supported below Android N"); 267 } 268 269 if (RuntimeEnvironment.getApiLevel() > VERSION_CODES.TIRAMISU) { 270 reflector(DisplayModeReflector.class, realObject.getMode()) 271 .setSupportedHdrTypes(supportedHdrTypes); 272 } 273 274 ShadowDisplayManager.changeDisplay( 275 displayId, 276 displayConfig -> { 277 displayConfig.hdrCapabilities = 278 new HdrCapabilities( 279 supportedHdrTypes, maxLuminance, maxAverageLuminance, minLuminance); 280 }); 281 } 282 283 /** 284 * Sets current hdr/sdr ratio expressed as the ratio of targetHdrPeakBrightnessInNits / 285 * targetSdrWhitePointInNits. This will have the intended side effect of making {@link 286 * Display#isHdrSdrRatioAvailable()} equal to true if set to any value other than {@link 287 * Float#NaN}. 288 * 289 * @throws UnsupportedOperationException if the method is called below Android version U. 290 */ setHdrSdrRatio(float hdrSdrRatio)291 public void setHdrSdrRatio(float hdrSdrRatio) { 292 if (Build.VERSION.SDK_INT < VERSION_CODES.UPSIDE_DOWN_CAKE) { 293 throw new UnsupportedOperationException("setHdrSdrRatio is not supported below Android U"); 294 } 295 296 ShadowDisplayManager.changeDisplay( 297 realObject.getDisplayId(), displayConfig -> displayConfig.hdrSdrRatio = hdrSdrRatio); 298 } 299 300 /** 301 * Changes the display cutout for this display. 302 * 303 * @throws UnsupportedOperationException if the method is called below Android version Q. 304 */ setDisplayCutout(Object displayCutout)305 public void setDisplayCutout(Object displayCutout) { 306 if (Build.VERSION.SDK_INT < VERSION_CODES.Q) { 307 throw new UnsupportedOperationException("Display cutouts are not supported below Android Q"); 308 } 309 310 ShadowDisplayManager.changeDisplay( 311 realObject.getDisplayId(), displayConfig -> displayConfig.displayCutout = displayCutout); 312 } 313 314 /** Reflector interface for {@link Display}'s internals. */ 315 @ForType(Display.class) 316 interface _Display_ { 317 @Direct getMetrics(DisplayMetrics outMetrics)318 void getMetrics(DisplayMetrics outMetrics); 319 320 @Direct getRealMetrics(DisplayMetrics outMetrics)321 void getRealMetrics(DisplayMetrics outMetrics); 322 323 @Direct getRefreshRate()324 float getRefreshRate(); 325 326 @Accessor("mFlags") setFlags(int flags)327 void setFlags(int flags); 328 } 329 330 @ForType(Display.Mode.class) 331 interface DisplayModeReflector { 332 @Accessor("mSupportedHdrTypes") setSupportedHdrTypes(int[] types)333 void setSupportedHdrTypes(int[] types); 334 } 335 } 336