• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.android;
2 
3 import static com.google.common.base.Strings.isNullOrEmpty;
4 
5 import android.content.res.Configuration;
6 import android.os.Build;
7 import android.os.Build.VERSION_CODES;
8 import android.util.DisplayMetrics;
9 import java.util.Locale;
10 import org.robolectric.res.Qualifiers;
11 import org.robolectric.res.android.ConfigDescription;
12 import org.robolectric.res.android.ResTable_config;
13 
14 /**
15  * Supports device configuration for Robolectric tests.
16  *
17  * @see [Device Configuration](http://robolectric.org/device-configuration/)
18  */
19 @SuppressWarnings("NewApi")
20 public class DeviceConfig {
21   public static final int DEFAULT_DENSITY = ResTable_config.DENSITY_DPI_MDPI;
22   public static final ScreenSize DEFAULT_SCREEN_SIZE = ScreenSize.normal;
23 
24   /**
25    * Standard sizes for the
26    * [screen size qualifier](https://developer.android.com/guide/topics/resources/providing-resources.html#ScreenSizeQualifier).
27    */
28   public enum ScreenSize {
29     small(320, 426, Configuration.SCREENLAYOUT_SIZE_SMALL),
30     normal(320, 470, Configuration.SCREENLAYOUT_SIZE_NORMAL),
31     large(480, 640, Configuration.SCREENLAYOUT_SIZE_LARGE),
32     xlarge(720, 960, Configuration.SCREENLAYOUT_SIZE_XLARGE);
33 
34     public final int width;
35     public final int height;
36     public final int landscapeWidth;
37     public final int landscapeHeight;
38     private final int configValue;
39 
ScreenSize(int width, int height, int configValue)40     ScreenSize(int width, int height, int configValue) {
41       this.width = width;
42       this.height = height;
43 
44       //noinspection SuspiciousNameCombination
45       this.landscapeWidth = height;
46       //noinspection SuspiciousNameCombination
47       this.landscapeHeight = width;
48 
49       this.configValue = configValue;
50     }
51 
isSmallerThanOrEqualTo(int x, int y)52     private boolean isSmallerThanOrEqualTo(int x, int y) {
53       if (y < x) {
54         int oldY = y;
55         //noinspection SuspiciousNameCombination
56         y = x;
57         //noinspection SuspiciousNameCombination
58         x = oldY;
59       }
60 
61       return width <= x && height <= y;
62     }
63 
find(int configValue)64     static ScreenSize find(int configValue) {
65       switch (configValue) {
66         case Configuration.SCREENLAYOUT_SIZE_SMALL:
67           return small;
68         case Configuration.SCREENLAYOUT_SIZE_NORMAL:
69           return normal;
70         case Configuration.SCREENLAYOUT_SIZE_LARGE:
71           return large;
72         case Configuration.SCREENLAYOUT_SIZE_XLARGE:
73           return xlarge;
74         case Configuration.SCREENLAYOUT_SIZE_UNDEFINED:
75           return null;
76         default:
77           throw new IllegalArgumentException();
78       }
79     }
80 
match(int x, int y)81     static ScreenSize match(int x, int y) {
82       ScreenSize bestMatch = small;
83 
84       for (ScreenSize screenSize : values()) {
85         if (screenSize.isSmallerThanOrEqualTo(x, y)) {
86           bestMatch = screenSize;
87         }
88       }
89 
90       return bestMatch;
91     }
92   }
93 
DeviceConfig()94   private DeviceConfig() {
95   }
96 
applyToConfiguration(Qualifiers qualifiers, int apiLevel, Configuration configuration, DisplayMetrics displayMetrics)97   static void applyToConfiguration(Qualifiers qualifiers, int apiLevel,
98       Configuration configuration, DisplayMetrics displayMetrics) {
99     ResTable_config resTab = qualifiers.getConfig();
100 
101     if (resTab.mcc != 0) {
102       configuration.mcc = resTab.mcc;
103     }
104 
105     if (resTab.mnc != 0) {
106       configuration.mnc = resTab.mnc;
107     }
108 
109     // screenLayout includes size, long, layoutdir, and round.
110     // layoutdir may be overridden by setLocale(), so do this first:
111     int screenLayoutSize = getScreenLayoutSize(configuration);
112     int resTabSize = resTab.screenLayoutSize();
113     if (resTabSize != ResTable_config.SCREENSIZE_ANY) {
114       screenLayoutSize = resTabSize;
115 
116       if (resTab.screenWidthDp == 0) {
117         configuration.screenWidthDp = 0;
118       }
119 
120       if (resTab.screenHeightDp == 0) {
121         configuration.screenHeightDp = 0;
122       }
123     }
124 
125     int screenLayoutLong = getScreenLayoutLong(configuration);
126     int resTabLong = resTab.screenLayoutLong();
127     if (resTabLong != ResTable_config.SCREENLONG_ANY) {
128       screenLayoutLong = resTabLong;
129     }
130 
131     int screenLayoutLayoutDir = getScreenLayoutLayoutDir(configuration);
132     int resTabLayoutDir = resTab.screenLayoutDirection();
133     if (resTabLayoutDir != ResTable_config.LAYOUTDIR_ANY) {
134       screenLayoutLayoutDir = resTabLayoutDir;
135     }
136 
137     int screenLayoutRound = getScreenLayoutRound(configuration);
138     int resTabRound = resTab.screenLayoutRound();
139     if (resTabRound != ResTable_config.SCREENROUND_ANY) {
140       screenLayoutRound = resTabRound << 8;
141     }
142 
143     configuration.screenLayout =
144         screenLayoutSize | screenLayoutLong | screenLayoutLayoutDir | screenLayoutRound;
145 
146     // locale...
147     String lang = resTab.languageString();
148     String region = resTab.regionString();
149     String script = resTab.scriptString();
150 
151     Locale locale;
152     if (isNullOrEmpty(lang) && isNullOrEmpty(region) && isNullOrEmpty(script)) {
153       locale = null;
154     } else {
155       locale = new Locale.Builder()
156           .setLanguage(lang)
157           .setRegion(region)
158           .setScript(script == null ? "" : script)
159           .build();
160     }
161     if (locale != null) {
162       setLocale(apiLevel, configuration, locale);
163     }
164 
165     if (resTab.smallestScreenWidthDp != 0) {
166       configuration.smallestScreenWidthDp = resTab.smallestScreenWidthDp;
167     }
168 
169     if (resTab.screenWidthDp != 0) {
170       configuration.screenWidthDp = resTab.screenWidthDp;
171     }
172 
173     if (resTab.screenHeightDp != 0) {
174       configuration.screenHeightDp = resTab.screenHeightDp;
175     }
176 
177     if (resTab.orientation != ResTable_config.ORIENTATION_ANY) {
178       configuration.orientation = resTab.orientation;
179     }
180 
181     // uiMode includes type and night...
182     int uiModeType = getUiModeType(configuration);
183     int resTabType = resTab.uiModeType();
184     if (resTabType != ResTable_config.UI_MODE_TYPE_ANY) {
185       uiModeType = resTabType;
186     }
187 
188     int uiModeNight = getUiModeNight(configuration);
189     int resTabNight = resTab.uiModeNight();
190     if (resTabNight != ResTable_config.UI_MODE_NIGHT_ANY) {
191       uiModeNight = resTabNight;
192     }
193     configuration.uiMode = uiModeType | uiModeNight;
194 
195     if (resTab.density != ResTable_config.DENSITY_DEFAULT) {
196       setDensity(resTab.density, apiLevel, configuration, displayMetrics);
197     }
198 
199     if (resTab.touchscreen != ResTable_config.TOUCHSCREEN_ANY) {
200       configuration.touchscreen = resTab.touchscreen;
201     }
202 
203     if (resTab.keyboard != ResTable_config.KEYBOARD_ANY) {
204       configuration.keyboard = resTab.keyboard;
205     }
206 
207     if (resTab.keyboardHidden() != ResTable_config.KEYSHIDDEN_ANY) {
208       configuration.keyboardHidden = resTab.keyboardHidden();
209     }
210 
211     if (resTab.navigation != ResTable_config.NAVIGATION_ANY) {
212       configuration.navigation = resTab.navigation;
213     }
214 
215     if (resTab.navigationHidden() != ResTable_config.NAVHIDDEN_ANY) {
216       configuration.navigationHidden = resTab.navigationHidden();
217     }
218 
219     if (apiLevel >= VERSION_CODES.O) {
220       if (resTab.colorModeWideColorGamut() != ResTable_config.WIDE_COLOR_GAMUT_ANY) {
221         setColorModeGamut(configuration, resTab.colorMode & ResTable_config.MASK_WIDE_COLOR_GAMUT);
222       }
223 
224       if (resTab.colorModeHdr() != ResTable_config.HDR_ANY) {
225         setColorModeHdr(configuration, resTab.colorMode & ResTable_config.MASK_HDR);
226       }
227     }
228   }
229 
setDensity(int densityDpi, int apiLevel, Configuration configuration, DisplayMetrics displayMetrics)230   private static void setDensity(int densityDpi, int apiLevel, Configuration configuration,
231       DisplayMetrics displayMetrics) {
232     if (apiLevel >= VERSION_CODES.JELLY_BEAN_MR1) {
233       configuration.densityDpi = densityDpi;
234     }
235     displayMetrics.densityDpi = densityDpi;
236     displayMetrics.density = displayMetrics.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
237   }
238 
239   /**
240    * Makes a given configuration, which may have undefined values, conform to the rules declared
241    * [here](http://robolectric.org/device-configuration/).
242    */
applyRules(Configuration configuration, DisplayMetrics displayMetrics, int apiLevel)243   static void applyRules(Configuration configuration, DisplayMetrics displayMetrics, int apiLevel) {
244     Locale locale = getLocale(configuration, apiLevel);
245 
246     String language = locale == null ? "" : locale.getLanguage();
247     if (language.isEmpty()) {
248       language = "en";
249 
250       String country = locale == null ? "" : locale.getCountry();
251       if (country.isEmpty()) {
252         country = "us";
253       }
254 
255       locale = new Locale(language, country);
256       setLocale(apiLevel, configuration, locale);
257     }
258 
259     if (apiLevel <= ConfigDescription.SDK_JELLY_BEAN &&
260         getScreenLayoutLayoutDir(configuration) == Configuration.SCREENLAYOUT_LAYOUTDIR_UNDEFINED) {
261       setScreenLayoutLayoutDir(configuration, Configuration.SCREENLAYOUT_LAYOUTDIR_LTR);
262     }
263 
264     ScreenSize requestedScreenSize = getScreenSize(configuration);
265     if (requestedScreenSize == null) {
266       requestedScreenSize = DEFAULT_SCREEN_SIZE;
267     }
268 
269     if (configuration.orientation == Configuration.ORIENTATION_UNDEFINED
270         && configuration.screenWidthDp != 0 && configuration.screenHeightDp != 0) {
271       configuration.orientation = (configuration.screenWidthDp > configuration.screenHeightDp)
272           ? Configuration.ORIENTATION_LANDSCAPE
273           : Configuration.ORIENTATION_PORTRAIT;
274     }
275 
276     if (configuration.screenWidthDp == 0) {
277       configuration.screenWidthDp = requestedScreenSize.width;
278     }
279 
280     if (configuration.screenHeightDp == 0) {
281       configuration.screenHeightDp = requestedScreenSize.height;
282 
283       if ((configuration.screenLayout & Configuration.SCREENLAYOUT_LONG_MASK)
284           == Configuration.SCREENLAYOUT_LONG_YES) {
285         configuration.screenHeightDp = (int) (configuration.screenHeightDp * 1.25f);
286       }
287     }
288 
289     int lesserDimenPx = Math.min(configuration.screenWidthDp, configuration.screenHeightDp);
290     int greaterDimenPx = Math.max(configuration.screenWidthDp, configuration.screenHeightDp);
291 
292     if (configuration.smallestScreenWidthDp == 0) {
293       configuration.smallestScreenWidthDp = lesserDimenPx;
294     }
295 
296     if (getScreenLayoutSize(configuration) == Configuration.SCREENLAYOUT_SIZE_UNDEFINED) {
297       ScreenSize screenSize =
298           ScreenSize.match(configuration.screenWidthDp, configuration.screenHeightDp);
299       setScreenLayoutSize(configuration, screenSize.configValue);
300     }
301 
302     if (getScreenLayoutLong(configuration) == Configuration.SCREENLAYOUT_LONG_UNDEFINED) {
303       setScreenLayoutLong(configuration,
304           ((float) greaterDimenPx) / lesserDimenPx >= 1.75
305               ? Configuration.SCREENLAYOUT_LONG_YES
306               : Configuration.SCREENLAYOUT_LONG_NO);
307     }
308 
309     if (getScreenLayoutRound(configuration) == Configuration.SCREENLAYOUT_ROUND_UNDEFINED) {
310       setScreenLayoutRound(configuration, Configuration.SCREENLAYOUT_ROUND_NO);
311     }
312 
313     if (configuration.orientation == Configuration.ORIENTATION_UNDEFINED) {
314       configuration.orientation = configuration.screenWidthDp > configuration.screenHeightDp
315           ? Configuration.ORIENTATION_LANDSCAPE
316           : Configuration.ORIENTATION_PORTRAIT;
317     } else if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT
318         && configuration.screenWidthDp > configuration.screenHeightDp) {
319       swapXY(configuration);
320     } else if (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
321         && configuration.screenWidthDp < configuration.screenHeightDp) {
322       swapXY(configuration);
323     }
324 
325     if (getUiModeType(configuration) == Configuration.UI_MODE_TYPE_UNDEFINED) {
326       setUiModeType(configuration, Configuration.UI_MODE_TYPE_NORMAL);
327     }
328 
329     if (getUiModeNight(configuration) == Configuration.UI_MODE_NIGHT_UNDEFINED) {
330       setUiModeNight(configuration, Configuration.UI_MODE_NIGHT_NO);
331     }
332 
333     switch (displayMetrics.densityDpi) {
334       case ResTable_config.DENSITY_DPI_ANY:
335         throw new IllegalArgumentException("'anydpi' isn't actually a dpi");
336       case ResTable_config.DENSITY_DPI_NONE:
337         throw new IllegalArgumentException("'nodpi' isn't actually a dpi");
338       case ResTable_config.DENSITY_DPI_UNDEFINED:
339         // DisplayMetrics.DENSITY_DEFAULT is mdpi
340         setDensity(DEFAULT_DENSITY, apiLevel, configuration, displayMetrics);
341     }
342 
343     if (configuration.touchscreen == Configuration.TOUCHSCREEN_UNDEFINED) {
344       configuration.touchscreen = Configuration.TOUCHSCREEN_FINGER;
345     }
346 
347     if (configuration.keyboardHidden == Configuration.KEYBOARDHIDDEN_UNDEFINED) {
348       configuration.keyboardHidden = Configuration.KEYBOARDHIDDEN_SOFT;
349     }
350 
351     if (configuration.keyboard == Configuration.KEYBOARD_UNDEFINED) {
352       configuration.keyboard = Configuration.KEYBOARD_NOKEYS;
353     }
354 
355     if (configuration.navigationHidden == Configuration.NAVIGATIONHIDDEN_UNDEFINED) {
356       configuration.navigationHidden = Configuration.NAVIGATIONHIDDEN_YES;
357     }
358 
359     if (configuration.navigation == Configuration.NAVIGATION_UNDEFINED) {
360       configuration.navigation = Configuration.NAVIGATION_NONAV;
361     }
362 
363     if (apiLevel >= VERSION_CODES.O) {
364       if (getColorModeGamut(configuration) == Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED) {
365         setColorModeGamut(configuration, Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_NO);
366       }
367 
368       if (getColorModeHdr(configuration) == Configuration.COLOR_MODE_HDR_UNDEFINED) {
369         setColorModeHdr(configuration, Configuration.COLOR_MODE_HDR_NO);
370       }
371     }
372   }
373 
getScreenSize(Configuration configuration)374   public static ScreenSize getScreenSize(Configuration configuration) {
375     return ScreenSize.find(getScreenLayoutSize(configuration));
376   }
377 
swapXY(Configuration configuration)378   private static void swapXY(Configuration configuration) {
379     int oldWidth = configuration.screenWidthDp;
380     //noinspection SuspiciousNameCombination
381     configuration.screenWidthDp = configuration.screenHeightDp;
382     //noinspection SuspiciousNameCombination
383     configuration.screenHeightDp = oldWidth;
384   }
385 
setLocale(int apiLevel, Configuration configuration, Locale locale)386   private static void setLocale(int apiLevel, Configuration configuration, Locale locale) {
387     if (apiLevel >= VERSION_CODES.JELLY_BEAN_MR1) {
388       configuration.setLocale(locale);
389     } else {
390       configuration.locale = locale;
391     }
392   }
393 
getLocale(Configuration configuration, int apiLevel)394   private static Locale getLocale(Configuration configuration, int apiLevel) {
395     Locale locale;
396     if (apiLevel > Build.VERSION_CODES.M) {
397       locale = configuration.getLocales().get(0);
398     } else {
399       locale = configuration.locale;
400     }
401     return locale;
402   }
403 
getScreenLayoutSize(Configuration configuration)404   private static int getScreenLayoutSize(Configuration configuration) {
405     return configuration.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK;
406   }
407 
setScreenLayoutSize(Configuration configuration, int value)408   private static void setScreenLayoutSize(Configuration configuration, int value) {
409     configuration.screenLayout =
410         (configuration.screenLayout & ~Configuration.SCREENLAYOUT_SIZE_MASK)
411             | value;
412   }
413 
getScreenLayoutLong(Configuration configuration)414   private static int getScreenLayoutLong(Configuration configuration) {
415     return configuration.screenLayout & Configuration.SCREENLAYOUT_LONG_MASK;
416   }
417 
setScreenLayoutLong(Configuration configuration, int value)418   private static void setScreenLayoutLong(Configuration configuration, int value) {
419     configuration.screenLayout =
420         (configuration.screenLayout & ~Configuration.SCREENLAYOUT_LONG_MASK)
421             | value;
422   }
423 
getScreenLayoutLayoutDir(Configuration configuration)424   private static int getScreenLayoutLayoutDir(Configuration configuration) {
425     return configuration.screenLayout & Configuration.SCREENLAYOUT_LAYOUTDIR_MASK;
426   }
427 
setScreenLayoutLayoutDir(Configuration configuration, int value)428   private static void setScreenLayoutLayoutDir(Configuration configuration, int value) {
429     configuration.screenLayout =
430         (configuration.screenLayout & ~Configuration.SCREENLAYOUT_LAYOUTDIR_MASK)
431             | value;
432   }
433 
getScreenLayoutRound(Configuration configuration)434   private static int getScreenLayoutRound(Configuration configuration) {
435     return configuration.screenLayout & Configuration.SCREENLAYOUT_ROUND_MASK;
436   }
437 
setScreenLayoutRound(Configuration configuration, int value)438   private static void setScreenLayoutRound(Configuration configuration, int value) {
439     configuration.screenLayout =
440         (configuration.screenLayout & ~Configuration.SCREENLAYOUT_ROUND_MASK)
441             | value;
442   }
443 
getUiModeType(Configuration configuration)444   private static int getUiModeType(Configuration configuration) {
445     return configuration.uiMode & Configuration.UI_MODE_TYPE_MASK;
446   }
447 
setUiModeType(Configuration configuration, int value)448   private static void setUiModeType(Configuration configuration, int value) {
449     configuration.uiMode = (configuration.uiMode & ~Configuration.UI_MODE_TYPE_MASK) | value;
450   }
451 
getUiModeNight(Configuration configuration)452   private static int getUiModeNight(Configuration configuration) {
453     return configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK;
454   }
455 
setUiModeNight(Configuration configuration, int value)456   private static void setUiModeNight(Configuration configuration, int value) {
457     configuration.uiMode = (configuration.uiMode & ~Configuration.UI_MODE_NIGHT_MASK) | value;
458   }
459 
getColorModeGamut(Configuration configuration)460   private static int getColorModeGamut(Configuration configuration) {
461     return configuration.colorMode & Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_MASK;
462   }
463 
setColorModeGamut(Configuration configuration, int value)464   private static void setColorModeGamut(Configuration configuration, int value) {
465     configuration.colorMode = (configuration.colorMode & ~Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_MASK) | value;
466   }
467 
getColorModeHdr(Configuration configuration)468   private static int getColorModeHdr(Configuration configuration) {
469     return configuration.colorMode & Configuration.COLOR_MODE_HDR_MASK;
470   }
471 
setColorModeHdr(Configuration configuration, int value)472   private static void setColorModeHdr(Configuration configuration, int value) {
473     configuration.colorMode = (configuration.colorMode & ~Configuration.COLOR_MODE_HDR_MASK) | value;
474   }
475 }
476