• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.shadows;
2 
3 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
4 
5 import android.content.res.Configuration;
6 import android.hardware.display.DisplayManager;
7 import android.hardware.display.DisplayManagerGlobal;
8 import android.os.Build;
9 import android.util.DisplayMetrics;
10 import android.view.Display;
11 import android.view.DisplayInfo;
12 import android.view.Surface;
13 import org.robolectric.RuntimeEnvironment;
14 import org.robolectric.android.Bootstrap;
15 import org.robolectric.android.internal.DisplayConfig;
16 import org.robolectric.annotation.Implements;
17 import org.robolectric.res.Qualifiers;
18 import org.robolectric.shadow.api.Shadow;
19 import org.robolectric.util.Consumer;
20 
21 /**
22  * For tests, display properties may be changed and devices may be added or removed
23  * programmatically.
24  */
25 @Implements(value = DisplayManager.class, minSdk = JELLY_BEAN_MR1)
26 public class ShadowDisplayManager {
27 
28   /**
29    * Adds a simulated display.
30    *
31    * @param qualifiersStr the {@link Qualifiers} string representing characteristics of the new
32    *     display.
33    * @return the new display's ID
34    */
addDisplay(String qualifiersStr)35   public static int addDisplay(String qualifiersStr) {
36     return getShadowDisplayManagerGlobal().addDisplay(createDisplayInfo(qualifiersStr, null));
37   }
38 
39   /** internal only */
configureDefaultDisplay(Configuration configuration, DisplayMetrics displayMetrics)40   public static void configureDefaultDisplay(Configuration configuration, DisplayMetrics displayMetrics) {
41     ShadowDisplayManagerGlobal shadowDisplayManagerGlobal = getShadowDisplayManagerGlobal();
42     if (DisplayManagerGlobal.getInstance().getDisplayIds().length != 0) {
43       throw new IllegalStateException("this method should only be called by Robolectric");
44     }
45 
46     shadowDisplayManagerGlobal.addDisplay(createDisplayInfo(configuration, displayMetrics));
47   }
48 
createDisplayInfo(Configuration configuration, DisplayMetrics displayMetrics)49   private static DisplayInfo createDisplayInfo(Configuration configuration, DisplayMetrics displayMetrics) {
50     int widthPx = (int) (configuration.screenWidthDp * displayMetrics.density);
51     int heightPx = (int) (configuration.screenHeightDp * displayMetrics.density);
52 
53     DisplayInfo displayInfo = new DisplayInfo();
54     displayInfo.name = "Built-in screen";
55     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
56       displayInfo.uniqueId = "screen0";
57     }
58     displayInfo.appWidth = widthPx;
59     displayInfo.appHeight = heightPx;
60     fixNominalDimens(displayInfo);
61     displayInfo.logicalWidth = widthPx;
62     displayInfo.logicalHeight = heightPx;
63     displayInfo.rotation = configuration.orientation == Configuration.ORIENTATION_PORTRAIT
64         ? Surface.ROTATION_0
65         : Surface.ROTATION_90;
66     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
67       displayInfo.modeId = 0;
68       displayInfo.defaultModeId = 0;
69       displayInfo.supportedModes = new Display.Mode[] {
70           new Display.Mode(0, widthPx, heightPx, 60)
71       };
72     }
73     displayInfo.logicalDensityDpi = displayMetrics.densityDpi;
74     displayInfo.physicalXDpi = displayMetrics.densityDpi;
75     displayInfo.physicalYDpi = displayMetrics.densityDpi;
76     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
77       displayInfo.state = Display.STATE_ON;
78     }
79 
80     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
81       displayInfo.getAppMetrics(displayMetrics);
82     }
83 
84     return displayInfo;
85   }
86 
fixNominalDimens(DisplayInfo displayInfo)87   private static void fixNominalDimens(DisplayInfo displayInfo) {
88     int smallest = Math.min(displayInfo.appWidth, displayInfo.appHeight);
89     int largest = Math.max(displayInfo.appWidth, displayInfo.appHeight);
90 
91     displayInfo.smallestNominalAppWidth = smallest;
92     displayInfo.smallestNominalAppHeight = smallest;
93     displayInfo.largestNominalAppWidth = largest;
94     displayInfo.largestNominalAppHeight = largest;
95   }
96 
createDisplayInfo(String qualifiersStr, DisplayInfo baseDisplayInfo)97   private static DisplayInfo createDisplayInfo(String qualifiersStr, DisplayInfo baseDisplayInfo) {
98     Configuration configuration = new Configuration();
99     DisplayMetrics displayMetrics = new DisplayMetrics();
100 
101     if (qualifiersStr.startsWith("+") && baseDisplayInfo != null) {
102       configuration.orientation =
103           (baseDisplayInfo.rotation == Surface.ROTATION_0
104               || baseDisplayInfo.rotation == Surface.ROTATION_180)
105               ? Configuration.ORIENTATION_PORTRAIT
106               : Configuration.ORIENTATION_LANDSCAPE;
107       configuration.screenWidthDp = baseDisplayInfo.logicalWidth * DisplayMetrics.DENSITY_DEFAULT
108           / baseDisplayInfo.logicalDensityDpi;
109       configuration.screenHeightDp = baseDisplayInfo.logicalHeight * DisplayMetrics.DENSITY_DEFAULT
110           / baseDisplayInfo.logicalDensityDpi;
111       configuration.densityDpi = baseDisplayInfo.logicalDensityDpi;
112       displayMetrics.densityDpi = baseDisplayInfo.logicalDensityDpi;
113       displayMetrics.density =
114           baseDisplayInfo.logicalDensityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
115     }
116 
117     Bootstrap.applyQualifiers(qualifiersStr, RuntimeEnvironment.getApiLevel(), configuration,
118         displayMetrics);
119 
120     return createDisplayInfo(configuration, displayMetrics);
121   }
122 
123   /**
124    * Changes properties of a simulated display. If `qualifiersStr` starts with a plus (`+`) sign,
125    * the display's previous configuration is modified with the given qualifiers; otherwise defaults
126    * are applied as described [here](http://robolectric.org/device-configuration/).
127    *
128    *
129    * @param displayId the display id to change
130    * @param qualifiersStr the {@link Qualifiers} string representing characteristics of the new
131    *     display
132    */
changeDisplay(int displayId, String qualifiersStr)133   public static void changeDisplay(int displayId, String qualifiersStr) {
134     DisplayInfo baseDisplayInfo = DisplayManagerGlobal.getInstance().getDisplayInfo(displayId);
135     DisplayInfo displayInfo = createDisplayInfo(qualifiersStr, baseDisplayInfo);
136     getShadowDisplayManagerGlobal().changeDisplay(displayId, displayInfo);
137   }
138 
139   /**
140    * Changes properties of a simulated display. The original properties will be passed to the
141    * `consumer`, which may modify them in place. The display will be updated with the new
142    * properties.
143    *
144    * @param displayId the display id to change
145    * @param consumer a function which modifies the display properties
146    */
changeDisplay(int displayId, Consumer<DisplayConfig> consumer)147   static void changeDisplay(int displayId, Consumer<DisplayConfig> consumer) {
148     DisplayInfo displayInfo = DisplayManagerGlobal.getInstance().getDisplayInfo(displayId);
149     if (displayInfo != null) {
150       DisplayConfig displayConfig = new DisplayConfig(displayInfo);
151       consumer.accept(displayConfig);
152       displayConfig.copyTo(displayInfo);
153       fixNominalDimens(displayInfo);
154     }
155 
156     getShadowDisplayManagerGlobal().changeDisplay(displayId, displayInfo);
157   }
158 
159   /**
160    * Removes a simulated display.
161    *
162    * @param displayId the display id to remove
163    */
removeDisplay(int displayId)164   public static void removeDisplay(int displayId) {
165     getShadowDisplayManagerGlobal().removeDisplay(displayId);
166   }
167 
getShadowDisplayManagerGlobal()168   private static ShadowDisplayManagerGlobal getShadowDisplayManagerGlobal() {
169     if (Build.VERSION.SDK_INT < JELLY_BEAN_MR1) {
170       throw new UnsupportedOperationException("multiple displays not supported in Jelly Bean");
171     }
172 
173     return Shadow.extract(DisplayManagerGlobal.getInstance());
174   }
175 }
176