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