xref: /aosp_15_r20/external/robolectric/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplay.java (revision e6ba16074e6af37d123cb567d575f496bf0a58ee)
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