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