1 package org.robolectric.android; 2 3 import static android.content.res.Configuration.DENSITY_DPI_ANY; 4 import static android.content.res.Configuration.DENSITY_DPI_NONE; 5 import static android.content.res.Configuration.DENSITY_DPI_UNDEFINED; 6 7 import android.content.res.Configuration; 8 import android.os.Build; 9 import android.os.Build.VERSION_CODES; 10 import android.os.LocaleList; 11 import android.text.TextUtils; 12 import android.util.DisplayMetrics; 13 import java.util.ArrayList; 14 import java.util.List; 15 import java.util.Locale; 16 import org.robolectric.RuntimeEnvironment; 17 18 // adapted from https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/core/java/android/content/res/Configuration.java 19 public class ConfigurationV25 { 20 localesToResourceQualifier(List<Locale> locs)21 private static String localesToResourceQualifier(List<Locale> locs) { 22 final StringBuilder sb = new StringBuilder(); 23 for (int i = 0; i < locs.size(); i++) { 24 final Locale loc = locs.get(i); 25 final int l = loc.getLanguage().length(); 26 if (l == 0) { 27 continue; 28 } 29 final int s = loc.getScript().length(); 30 final int c = loc.getCountry().length(); 31 final int v = loc.getVariant().length(); 32 // We ignore locale extensions, since they are not supported by AAPT 33 34 if (sb.length() != 0) { 35 sb.append(","); 36 } 37 if (l == 2 && s == 0 && (c == 0 || c == 2) && v == 0) { 38 // Traditional locale format: xx or xx-rYY 39 sb.append(loc.getLanguage()); 40 if (c == 2) { 41 sb.append("-r").append(loc.getCountry()); 42 } 43 } else { 44 sb.append("b+"); 45 sb.append(loc.getLanguage()); 46 if (s != 0) { 47 sb.append("+"); 48 sb.append(loc.getScript()); 49 } 50 if (c != 0) { 51 sb.append("+"); 52 sb.append(loc.getCountry()); 53 } 54 if (v != 0) { 55 sb.append("+"); 56 sb.append(loc.getVariant()); 57 } 58 } 59 } 60 return sb.toString(); 61 } 62 63 64 /** 65 * Returns a string representation of the configuration that can be parsed 66 * by build tools (like AAPT). 67 * 68 * @hide 69 */ resourceQualifierString(Configuration config, DisplayMetrics displayMetrics)70 public static String resourceQualifierString(Configuration config, DisplayMetrics displayMetrics) { 71 return resourceQualifierString(config, displayMetrics, true); 72 } 73 resourceQualifierString(Configuration config, DisplayMetrics displayMetrics, boolean includeSdk)74 public static String resourceQualifierString(Configuration config, DisplayMetrics displayMetrics, boolean includeSdk) { 75 ArrayList<String> parts = new ArrayList<String>(); 76 77 if (config.mcc != 0) { 78 parts.add("mcc" + config.mcc); 79 if (config.mnc != 0) { 80 parts.add("mnc" + config.mnc); 81 } 82 } 83 84 List<Locale> locales = getLocales(config); 85 if (!locales.isEmpty()) { 86 final String resourceQualifier = localesToResourceQualifier(locales); 87 if (!resourceQualifier.isEmpty()) { 88 parts.add(resourceQualifier); 89 } 90 } 91 92 switch (config.screenLayout & Configuration.SCREENLAYOUT_LAYOUTDIR_MASK) { 93 case Configuration.SCREENLAYOUT_LAYOUTDIR_LTR: 94 parts.add("ldltr"); 95 break; 96 case Configuration.SCREENLAYOUT_LAYOUTDIR_RTL: 97 parts.add("ldrtl"); 98 break; 99 default: 100 break; 101 } 102 103 if (config.smallestScreenWidthDp != 0) { 104 parts.add("sw" + config.smallestScreenWidthDp + "dp"); 105 } 106 107 if (config.screenWidthDp != 0) { 108 parts.add("w" + config.screenWidthDp + "dp"); 109 } 110 111 if (config.screenHeightDp != 0) { 112 parts.add("h" + config.screenHeightDp + "dp"); 113 } 114 115 switch (config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) { 116 case Configuration.SCREENLAYOUT_SIZE_SMALL: 117 parts.add("small"); 118 break; 119 case Configuration.SCREENLAYOUT_SIZE_NORMAL: 120 parts.add("normal"); 121 break; 122 case Configuration.SCREENLAYOUT_SIZE_LARGE: 123 parts.add("large"); 124 break; 125 case Configuration.SCREENLAYOUT_SIZE_XLARGE: 126 parts.add("xlarge"); 127 break; 128 default: 129 break; 130 } 131 132 switch (config.screenLayout & Configuration.SCREENLAYOUT_LONG_MASK) { 133 case Configuration.SCREENLAYOUT_LONG_YES: 134 parts.add("long"); 135 break; 136 case Configuration.SCREENLAYOUT_LONG_NO: 137 parts.add("notlong"); 138 break; 139 default: 140 break; 141 } 142 143 switch (config.screenLayout & Configuration.SCREENLAYOUT_ROUND_MASK) { 144 case Configuration.SCREENLAYOUT_ROUND_YES: 145 parts.add("round"); 146 break; 147 case Configuration.SCREENLAYOUT_ROUND_NO: 148 parts.add("notround"); 149 break; 150 default: 151 break; 152 } 153 154 if (RuntimeEnvironment.getApiLevel() >= VERSION_CODES.O) { 155 switch (config.colorMode & Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_MASK) { 156 case Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_YES: 157 parts.add("widecg"); 158 break; 159 case Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_NO: 160 parts.add("nowidecg"); 161 break; 162 default: 163 break; 164 } 165 166 switch (config.colorMode & Configuration.COLOR_MODE_HDR_MASK) { 167 case Configuration.COLOR_MODE_HDR_YES: 168 parts.add("highdr"); 169 break; 170 case Configuration.COLOR_MODE_HDR_NO: 171 parts.add("lowdr"); 172 break; 173 default: 174 break; 175 } 176 } 177 178 switch (config.orientation) { 179 case Configuration.ORIENTATION_LANDSCAPE: 180 parts.add("land"); 181 break; 182 case Configuration.ORIENTATION_PORTRAIT: 183 parts.add("port"); 184 break; 185 default: 186 break; 187 } 188 189 switch (config.uiMode & Configuration.UI_MODE_TYPE_MASK) { 190 case Configuration.UI_MODE_TYPE_APPLIANCE: 191 parts.add("appliance"); 192 break; 193 case Configuration.UI_MODE_TYPE_DESK: 194 parts.add("desk"); 195 break; 196 case Configuration.UI_MODE_TYPE_TELEVISION: 197 parts.add("television"); 198 break; 199 case Configuration.UI_MODE_TYPE_CAR: 200 parts.add("car"); 201 break; 202 case Configuration.UI_MODE_TYPE_WATCH: 203 parts.add("watch"); 204 break; 205 case Configuration.UI_MODE_TYPE_VR_HEADSET: 206 parts.add("vrheadset"); 207 break; 208 case Configuration.UI_MODE_TYPE_NORMAL: 209 default: 210 break; 211 } 212 213 switch (config.uiMode & Configuration.UI_MODE_NIGHT_MASK) { 214 case Configuration.UI_MODE_NIGHT_YES: 215 parts.add("night"); 216 break; 217 case Configuration.UI_MODE_NIGHT_NO: 218 parts.add("notnight"); 219 break; 220 default: 221 break; 222 } 223 224 int densityDpi; 225 if (RuntimeEnvironment.getApiLevel() > VERSION_CODES.JELLY_BEAN) { 226 densityDpi = config.densityDpi; 227 } else { 228 densityDpi = displayMetrics.densityDpi; 229 } 230 231 switch (densityDpi) { 232 case DENSITY_DPI_UNDEFINED: 233 break; 234 case 120: 235 parts.add("ldpi"); 236 break; 237 case 160: 238 parts.add("mdpi"); 239 break; 240 case 213: 241 parts.add("tvdpi"); 242 break; 243 case 240: 244 parts.add("hdpi"); 245 break; 246 case 320: 247 parts.add("xhdpi"); 248 break; 249 case 480: 250 parts.add("xxhdpi"); 251 break; 252 case 640: 253 parts.add("xxxhdpi"); 254 break; 255 case DENSITY_DPI_ANY: 256 parts.add("anydpi"); 257 break; 258 case DENSITY_DPI_NONE: 259 parts.add("nodpi"); 260 break; 261 default: 262 parts.add(densityDpi + "dpi"); 263 break; 264 } 265 266 switch (config.touchscreen) { 267 case Configuration.TOUCHSCREEN_NOTOUCH: 268 parts.add("notouch"); 269 break; 270 case Configuration.TOUCHSCREEN_FINGER: 271 parts.add("finger"); 272 break; 273 default: 274 break; 275 } 276 277 switch (config.keyboardHidden) { 278 case Configuration.KEYBOARDHIDDEN_NO: 279 parts.add("keysexposed"); 280 break; 281 case Configuration.KEYBOARDHIDDEN_YES: 282 parts.add("keyshidden"); 283 break; 284 case Configuration.KEYBOARDHIDDEN_SOFT: 285 parts.add("keyssoft"); 286 break; 287 default: 288 break; 289 } 290 291 switch (config.keyboard) { 292 case Configuration.KEYBOARD_NOKEYS: 293 parts.add("nokeys"); 294 break; 295 case Configuration.KEYBOARD_QWERTY: 296 parts.add("qwerty"); 297 break; 298 case Configuration.KEYBOARD_12KEY: 299 parts.add("12key"); 300 break; 301 default: 302 break; 303 } 304 305 switch (config.navigationHidden) { 306 case Configuration.NAVIGATIONHIDDEN_NO: 307 parts.add("navexposed"); 308 break; 309 case Configuration.NAVIGATIONHIDDEN_YES: 310 parts.add("navhidden"); 311 break; 312 default: 313 break; 314 } 315 316 switch (config.navigation) { 317 case Configuration.NAVIGATION_NONAV: 318 parts.add("nonav"); 319 break; 320 case Configuration.NAVIGATION_DPAD: 321 parts.add("dpad"); 322 break; 323 case Configuration.NAVIGATION_TRACKBALL: 324 parts.add("trackball"); 325 break; 326 case Configuration.NAVIGATION_WHEEL: 327 parts.add("wheel"); 328 break; 329 default: 330 break; 331 } 332 333 if (includeSdk) { 334 parts.add("v" + Build.VERSION.RESOURCES_SDK_INT); 335 } 336 337 return TextUtils.join("-", parts); 338 } 339 getLocales(Configuration config)340 private static List<Locale> getLocales(Configuration config) { 341 List<Locale> locales = new ArrayList<>(); 342 if (RuntimeEnvironment.getApiLevel() > Build.VERSION_CODES.M) { 343 LocaleList localeList = config.getLocales(); 344 for (int i = 0; i < localeList.size(); i++) { 345 locales.add(localeList.get(i)); 346 } 347 } else if (config.locale != null) { 348 locales.add(config.locale); 349 } 350 return locales; 351 } 352 } 353