1 package org.robolectric.res.android;
2 
3 import static org.robolectric.res.android.Util.isTruthy;
4 
5 import com.google.common.base.Charsets;
6 import com.google.common.collect.Iterators;
7 import com.google.common.collect.PeekingIterator;
8 import java.util.Arrays;
9 import java.util.Objects;
10 import java.util.regex.Matcher;
11 import java.util.regex.Pattern;
12 
13 /**
14  * transliterated from
15  * https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/tools/aapt2/ConfigDescription.cpp
16  */
17 public class ConfigDescription {
18   public static final int SDK_CUPCAKE = 3;
19   public static final int SDK_DONUT = 4;
20   public static final int SDK_ECLAIR = 5;
21   public static final int SDK_ECLAIR_0_1 = 6;
22   public static final int SDK_ECLAIR_MR1 = 7;
23   public static final int SDK_FROYO = 8;
24   public static final int SDK_GINGERBREAD = 9;
25   public static final int SDK_GINGERBREAD_MR1 = 10;
26   public static final int SDK_HONEYCOMB = 11;
27   public static final int SDK_HONEYCOMB_MR1 = 12;
28   public static final int SDK_HONEYCOMB_MR2 = 13;
29   public static final int SDK_ICE_CREAM_SANDWICH = 14;
30   public static final int SDK_ICE_CREAM_SANDWICH_MR1 = 15;
31   public static final int SDK_JELLY_BEAN = 16;
32   public static final int SDK_JELLY_BEAN_MR1 = 17;
33   public static final int SDK_JELLY_BEAN_MR2 = 18;
34   public static final int SDK_KITKAT = 19;
35   public static final int SDK_KITKAT_WATCH = 20;
36   public static final int SDK_LOLLIPOP = 21;
37   public static final int SDK_LOLLIPOP_MR1 = 22;
38   public static final int SDK_MNC = 23;
39   public static final int SDK_NOUGAT = 24;
40   public static final int SDK_NOUGAT_MR1 = 25;
41   public static final int SDK_O = 26;
42 
43   /**
44    * Constant used to to represent MNC (Mobile Network Code) zero.
45    * 0 cannot be used, since it is used to represent an undefined MNC.
46    */
47   private static final int ACONFIGURATION_MNC_ZERO = 0xffff;
48 
49   private static final String kWildcardName = "any";
50 
51   private static final Pattern MCC_PATTERN = Pattern.compile("mcc([\\d]+)");
52   private static final Pattern MNC_PATTERN = Pattern.compile("mnc([\\d]+)");
53   private static final Pattern SMALLEST_SCREEN_WIDTH_PATTERN = Pattern.compile("^sw([0-9]+)dp");
54   private static final Pattern SCREEN_WIDTH_PATTERN = Pattern.compile("^w([0-9]+)dp");
55   private static final Pattern SCREEN_HEIGHT_PATTERN = Pattern.compile("^h([0-9]+)dp");
56   private static final Pattern DENSITY_PATTERN = Pattern.compile("^([0-9]+)dpi");
57   private static final Pattern HEIGHT_WIDTH_PATTERN = Pattern.compile("^([0-9]+)x([0-9]+)");
58   private static final Pattern VERSION_QUALIFIER_PATTERN = Pattern.compile("v([0-9]+)$");
59 
60   public static class LocaleValue {
61 
62     String language;
63     String region;
64     String script;
65     String variant;
66 
set_language(String language_chars)67     void set_language(String language_chars) {
68       language = language_chars.trim().toLowerCase();
69     }
70 
set_region(String region_chars)71     void set_region(String region_chars) {
72       region = region_chars.trim().toUpperCase();
73     }
74 
set_script(String script_chars)75     void set_script(String script_chars) {
76       script = String.valueOf(Character.toUpperCase(script_chars.charAt(0))) +
77           script_chars.substring(1).toLowerCase();
78     }
79 
set_variant(String variant_chars)80     void set_variant(String variant_chars) {
81       variant = variant_chars.trim();
82     }
83 
84 
is_alpha(final String str)85     static boolean is_alpha(final String str) {
86       for (int i = 0; i < str.length(); i++) {
87         if (!Character.isAlphabetic(str.charAt(i))) {
88           return false;
89         }
90       }
91 
92       return true;
93     }
94 
initFromParts(PeekingIterator<String> iter)95     int initFromParts(PeekingIterator<String> iter) {
96 
97       String part = iter.peek();
98       if (part.startsWith("b+")) {
99         // This is a "modified" BCP 47 language tag. Same semantics as BCP 47 tags,
100         // except that the separator is "+" and not "-".
101         String[] subtags = part.substring(2).toLowerCase().split("\\+", 0);
102         if (subtags.length == 1) {
103           set_language(subtags[0]);
104         } else if (subtags.length == 2) {
105           set_language(subtags[0]);
106 
107           // The second tag can either be a region, a variant or a script.
108           switch (subtags[1].length()) {
109             case 2:
110             case 3:
111               set_region(subtags[1]);
112               break;
113             case 4:
114               if ('0' <= subtags[1].charAt(0) && subtags[1].charAt(0) <= '9') {
115                 // This is a variant: fall through
116               } else {
117                 set_script(subtags[1]);
118                 break;
119               }
120               // fall through
121             case 5:
122             case 6:
123             case 7:
124             case 8:
125               set_variant(subtags[1]);
126               break;
127             default:
128               return -1;
129           }
130         } else if (subtags.length == 3) {
131           // The language is always the first subtag.
132           set_language(subtags[0]);
133 
134           // The second subtag can either be a script or a region code.
135           // If its size is 4, it's a script code, else it's a region code.
136           if (subtags[1].length() == 4) {
137             set_script(subtags[1]);
138           } else if (subtags[1].length() == 2 || subtags[1].length() == 3) {
139             set_region(subtags[1]);
140           } else {
141             return -1;
142           }
143 
144           // The third tag can either be a region code (if the second tag was
145           // a script), else a variant code.
146           if (subtags[2].length() >= 4) {
147             set_variant(subtags[2]);
148           } else {
149             set_region(subtags[2]);
150           }
151         } else if (subtags.length == 4) {
152           set_language(subtags[0]);
153           set_script(subtags[1]);
154           set_region(subtags[2]);
155           set_variant(subtags[3]);
156         } else {
157           return -1;
158         }
159 
160         iter.next();
161 
162       } else {
163         if ((part.length() == 2 || part.length() == 3) && is_alpha(part) &&
164             !Objects.equals(part, "car")) {
165           set_language(part);
166           iter.next();
167 
168           if (iter.hasNext()) {
169             final String region_part = iter.peek();
170             if (region_part.charAt(0) == 'r' && region_part.length() == 3) {
171               set_region(region_part.substring(1));
172               iter.next();
173             }
174           }
175         }
176       }
177 
178       return 0;
179     }
180 
writeTo(ResTable_config out)181     public void writeTo(ResTable_config out) {
182       out.packLanguage(language);
183       out.packRegion(region);
184 
185       Arrays.fill(out.localeScript, (byte) 0);
186       byte[] scriptBytes = script == null ? new byte[4] : script.getBytes(Charsets.UTF_8);
187       System.arraycopy(scriptBytes, 0, out.localeScript, 0, scriptBytes.length);
188 
189       Arrays.fill(out.localeVariant, (byte) 0);
190       byte[] variantBytes = variant == null ? new byte[8] : variant.getBytes(Charsets.UTF_8);
191       System.arraycopy(variantBytes, 0, out.localeVariant, 0, variantBytes.length);
192     }
193   }
194 
parse(final String str, ResTable_config out)195   public static boolean parse(final String str, ResTable_config out) {
196     return parse(str, out, true);
197   }
198 
parse(final String str, ResTable_config out, boolean applyVersionForCompat)199   public static boolean parse(final String str, ResTable_config out, boolean applyVersionForCompat) {
200     PeekingIterator<String> part_iter = Iterators
201         .peekingIterator(Arrays.asList(str.toLowerCase().split("-")).iterator());
202 
203     LocaleValue locale = new LocaleValue();
204 
205     boolean success = !part_iter.hasNext();
206     if (part_iter.hasNext() && parseMcc(part_iter.peek(), out)) {
207       part_iter.next();
208       if (!part_iter.hasNext()) {
209         success = !part_iter.hasNext();
210       }
211     }
212 
213     if (part_iter.hasNext() && parseMnc(part_iter.peek(), out)) {
214       part_iter.next();
215       if (!part_iter.hasNext()) {
216         success = !part_iter.hasNext();
217       }
218     }
219 
220     if (part_iter.hasNext()) {
221       // Locale spans a few '-' separators, so we let it
222       // control the index.
223       int parts_consumed = locale.initFromParts(part_iter);
224       if (parts_consumed < 0) {
225         return false;
226       } else {
227         locale.writeTo(out);
228         if (!part_iter.hasNext()) {
229           success = !part_iter.hasNext();
230         }
231       }
232     }
233 
234     if (part_iter.hasNext() && parseLayoutDirection(part_iter.peek(), out)) {
235       part_iter.next();
236       if (!part_iter.hasNext()) {
237         success = !part_iter.hasNext();
238       }
239     }
240 
241     if (part_iter.hasNext() && parseSmallestScreenWidthDp(part_iter.peek(), out)) {
242       part_iter.next();
243       if (!part_iter.hasNext()) {
244         success = !part_iter.hasNext();
245       }
246     }
247 
248     if (part_iter.hasNext() && parseScreenWidthDp(part_iter.peek(), out)) {
249       part_iter.next();
250       if (!part_iter.hasNext()) {
251         success = !part_iter.hasNext();
252       }
253     }
254 
255     if (part_iter.hasNext() && parseScreenHeightDp(part_iter.peek(), out)) {
256       part_iter.next();
257       if (!part_iter.hasNext()) {
258         success = !part_iter.hasNext();
259       }
260     }
261 
262     if (part_iter.hasNext() && parseScreenLayoutSize(part_iter.peek(), out)) {
263       part_iter.next();
264       if (!part_iter.hasNext()) {
265         success = !part_iter.hasNext();
266       }
267     }
268 
269     if (part_iter.hasNext() && parseScreenLayoutLong(part_iter.peek(), out)) {
270       part_iter.next();
271       if (!part_iter.hasNext()) {
272         success = !part_iter.hasNext();
273       }
274     }
275 
276     if (part_iter.hasNext() && parseScreenRound(part_iter.peek(), out)) {
277       part_iter.next();
278       if (!part_iter.hasNext()) {
279         success = !part_iter.hasNext();
280       }
281     }
282 
283     if (part_iter.hasNext() && parseWideColorGamut(part_iter.peek(), out)) {
284       part_iter.next();
285       if (!part_iter.hasNext()) {
286         success = !part_iter.hasNext();
287       }
288     }
289 
290     if (part_iter.hasNext() && parseHdr(part_iter.peek(), out)) {
291       part_iter.next();
292       if (!part_iter.hasNext()) {
293         success = !part_iter.hasNext();
294       }
295     }
296 
297     if (part_iter.hasNext() && parseOrientation(part_iter.peek(), out)) {
298       part_iter.next();
299       if (!part_iter.hasNext()) {
300         success = !part_iter.hasNext();
301       }
302     }
303 
304     if (part_iter.hasNext() && parseUiModeType(part_iter.peek(), out)) {
305       part_iter.next();
306       if (!part_iter.hasNext()) {
307         success = !part_iter.hasNext();
308       }
309     }
310 
311     if (part_iter.hasNext() && parseUiModeNight(part_iter.peek(), out)) {
312       part_iter.next();
313       if (!part_iter.hasNext()) {
314         success = !part_iter.hasNext();
315       }
316     }
317 
318     if (part_iter.hasNext() && parseDensity(part_iter.peek(), out)) {
319       part_iter.next();
320       if (!part_iter.hasNext()) {
321         success = !part_iter.hasNext();
322       }
323     }
324 
325     if (part_iter.hasNext() && parseTouchscreen(part_iter.peek(), out)) {
326       part_iter.next();
327       if (!part_iter.hasNext()) {
328         success = !part_iter.hasNext();
329       }
330     }
331 
332     if (part_iter.hasNext() && parseKeysHidden(part_iter.peek(), out)) {
333       part_iter.next();
334       if (!part_iter.hasNext()) {
335         success = !part_iter.hasNext();
336       }
337     }
338 
339     if (part_iter.hasNext() && parseKeyboard(part_iter.peek(), out)) {
340       part_iter.next();
341       if (!part_iter.hasNext()) {
342         success = !part_iter.hasNext();
343       }
344     }
345 
346     if (part_iter.hasNext() && parseNavHidden(part_iter.peek(), out)) {
347       part_iter.next();
348       if (!part_iter.hasNext()) {
349         success = !part_iter.hasNext();
350       }
351     }
352 
353     if (part_iter.hasNext() && parseNavigation(part_iter.peek(), out)) {
354       part_iter.next();
355       if (!part_iter.hasNext()) {
356         success = !part_iter.hasNext();
357       }
358     }
359 
360     if (part_iter.hasNext() && parseScreenSize(part_iter.peek(), out)) {
361       part_iter.next();
362       if (!part_iter.hasNext()) {
363         success = !part_iter.hasNext();
364       }
365     }
366 
367     if (part_iter.hasNext() && parseVersion(part_iter.peek(), out)) {
368       part_iter.next();
369       if (!part_iter.hasNext()) {
370         success = !part_iter.hasNext();
371       }
372     }
373 
374     if (!success) {
375       // Unrecognized.
376       return false;
377     }
378 
379     if (out != null && applyVersionForCompat) {
380       applyVersionForCompatibility(out);
381     }
382     return true;
383   }
384 
parseLayoutDirection(String name, ResTable_config out)385   private static boolean parseLayoutDirection(String name, ResTable_config out) {
386     if (Objects.equals(name, kWildcardName)) {
387       if (out != null) {
388         out.screenLayout =
389             (out.screenLayout & ~ResTable_config.MASK_LAYOUTDIR) |
390                 ResTable_config.LAYOUTDIR_ANY;
391       }
392       return true;
393     } else if (Objects.equals(name, "ldltr")) {
394       if (out != null) {
395         out.screenLayout =
396             (out.screenLayout & ~ResTable_config.MASK_LAYOUTDIR) |
397                 ResTable_config.LAYOUTDIR_LTR;
398       }
399       return true;
400     } else if (Objects.equals(name, "ldrtl")) {
401       if (out != null) {
402         out.screenLayout =
403             (out.screenLayout & ~ResTable_config.MASK_LAYOUTDIR) |
404                 ResTable_config.LAYOUTDIR_RTL;
405       }
406       return true;
407     }
408 
409     return false;
410   }
411 
parseSmallestScreenWidthDp(String name, ResTable_config out)412   private static boolean parseSmallestScreenWidthDp(String name, ResTable_config out) {
413     if (Objects.equals(name, kWildcardName)) {
414       if (out != null) {
415         out.smallestScreenWidthDp = ResTable_config.SCREENWIDTH_ANY;
416       }
417       return true;
418     }
419 
420     Matcher matcher = SMALLEST_SCREEN_WIDTH_PATTERN.matcher(name);
421     if (matcher.matches()) {
422       out.smallestScreenWidthDp = Integer.parseInt(matcher.group(1));
423       return true;
424     }
425     return false;
426   }
427 
parseScreenWidthDp(String name, ResTable_config out)428   private static boolean parseScreenWidthDp(String name, ResTable_config out) {
429     if (Objects.equals(name, kWildcardName)) {
430       if (out != null) {
431         out.screenWidthDp = ResTable_config.SCREENWIDTH_ANY;
432       }
433       return true;
434     }
435 
436     Matcher matcher = SCREEN_WIDTH_PATTERN.matcher(name);
437     if (matcher.matches()) {
438       out.screenWidthDp = Integer.parseInt(matcher.group(1));
439       return true;
440     }
441     return false;
442   }
443 
parseScreenHeightDp(String name, ResTable_config out)444   private static boolean parseScreenHeightDp(String name, ResTable_config out) {
445     if (Objects.equals(name, kWildcardName)) {
446       if (out != null) {
447         out.screenHeightDp = ResTable_config.SCREENWIDTH_ANY;
448       }
449       return true;
450     }
451 
452     Matcher matcher = SCREEN_HEIGHT_PATTERN.matcher(name);
453     if (matcher.matches()) {
454       out.screenHeightDp = Integer.parseInt(matcher.group(1));
455       return true;
456     }
457     return false;
458   }
459 
parseScreenLayoutSize(String name, ResTable_config out)460   private static boolean parseScreenLayoutSize(String name, ResTable_config out) {
461     if (Objects.equals(name, kWildcardName)) {
462       if (out != null) {
463         out.screenLayout =
464             (out.screenLayout & ~ResTable_config.MASK_SCREENSIZE) |
465                 ResTable_config.SCREENSIZE_ANY;
466       }
467       return true;
468     } else if (Objects.equals(name, "small")) {
469       if (out != null) {
470         out.screenLayout =
471             (out.screenLayout & ~ResTable_config.MASK_SCREENSIZE) |
472                 ResTable_config.SCREENSIZE_SMALL;
473       }
474       return true;
475     } else if (Objects.equals(name, "normal")) {
476       if (out != null) {
477         out.screenLayout =
478             (out.screenLayout & ~ResTable_config.MASK_SCREENSIZE) |
479                 ResTable_config.SCREENSIZE_NORMAL;
480       }
481       return true;
482     } else if (Objects.equals(name, "large")) {
483       if (out != null) {
484         out.screenLayout =
485             (out.screenLayout & ~ResTable_config.MASK_SCREENSIZE) |
486                 ResTable_config.SCREENSIZE_LARGE;
487       }
488       return true;
489     } else if (Objects.equals(name, "xlarge")) {
490       if (out != null) {
491         out.screenLayout =
492             (out.screenLayout & ~ResTable_config.MASK_SCREENSIZE) |
493                 ResTable_config.SCREENSIZE_XLARGE;
494       }
495       return true;
496     }
497 
498     return false;
499   }
500 
parseScreenLayoutLong(final String name, ResTable_config out)501   static boolean parseScreenLayoutLong(final String name, ResTable_config out) {
502     if (Objects.equals(name, kWildcardName)) {
503       if (out != null) {
504         out.screenLayout =
505             (out.screenLayout&~ResTable_config.MASK_SCREENLONG)
506                 | ResTable_config.SCREENLONG_ANY;
507       }
508       return true;
509     } else if (Objects.equals(name, "long")) {
510       if (out != null) out.screenLayout =
511           (out.screenLayout&~ResTable_config.MASK_SCREENLONG)
512               | ResTable_config.SCREENLONG_YES;
513       return true;
514     } else if (Objects.equals(name, "notlong")) {
515       if (out != null) out.screenLayout =
516           (out.screenLayout&~ResTable_config.MASK_SCREENLONG)
517               | ResTable_config.SCREENLONG_NO;
518       return true;
519     }
520     return false;
521   }
522 
parseScreenRound(String name, ResTable_config out)523   private static boolean parseScreenRound(String name, ResTable_config out) {
524     if (Objects.equals(name, kWildcardName)) {
525       if (out != null) {
526         out.screenLayout2 =
527             (byte) ((out.screenLayout2 & ~ResTable_config.MASK_SCREENROUND) |
528                             ResTable_config.SCREENROUND_ANY);
529       }
530       return true;
531     } else if (Objects.equals(name, "round")) {
532       if (out != null) {
533         out.screenLayout2 =
534             (byte) ((out.screenLayout2 & ~ResTable_config.MASK_SCREENROUND) |
535                             ResTable_config.SCREENROUND_YES);
536       }
537       return true;
538     } else if (Objects.equals(name, "notround")) {
539       if (out != null) {
540         out.screenLayout2 =
541             (byte) ((out.screenLayout2 & ~ResTable_config.MASK_SCREENROUND) |
542                 ResTable_config.SCREENROUND_NO);
543       }
544       return true;
545     }
546     return false;
547   }
548 
parseWideColorGamut(String name, ResTable_config out)549   private static boolean parseWideColorGamut(String name, ResTable_config out) {
550     if (Objects.equals(name, kWildcardName)) {
551       if (out != null)
552         out.colorMode =
553             (byte) ((out.colorMode & ~ResTable_config.MASK_WIDE_COLOR_GAMUT) |
554                             ResTable_config.WIDE_COLOR_GAMUT_ANY);
555       return true;
556     } else if (Objects.equals(name, "widecg")) {
557       if (out != null)
558         out.colorMode =
559             (byte) ((out.colorMode & ~ResTable_config.MASK_WIDE_COLOR_GAMUT) |
560                             ResTable_config.WIDE_COLOR_GAMUT_YES);
561       return true;
562     } else if (Objects.equals(name, "nowidecg")) {
563       if (out != null)
564         out.colorMode =
565             (byte) ((out.colorMode & ~ResTable_config.MASK_WIDE_COLOR_GAMUT) |
566                             ResTable_config.WIDE_COLOR_GAMUT_NO);
567       return true;
568     }
569     return false;
570   }
571 
parseHdr(String name, ResTable_config out)572   private static boolean parseHdr(String name, ResTable_config out) {
573     if (Objects.equals(name, kWildcardName)) {
574       if (out != null)
575         out.colorMode =
576             (byte) ((out.colorMode & ~ResTable_config.MASK_HDR) |
577                             ResTable_config.HDR_ANY);
578       return true;
579     } else if (Objects.equals(name, "highdr")) {
580       if (out != null)
581         out.colorMode =
582             (byte) ((out.colorMode & ~ResTable_config.MASK_HDR) |
583                             ResTable_config.HDR_YES);
584       return true;
585     } else if (Objects.equals(name, "lowdr")) {
586       if (out != null)
587         out.colorMode =
588             (byte) ((out.colorMode & ~ResTable_config.MASK_HDR) |
589                             ResTable_config.HDR_NO);
590       return true;
591     }
592     return false;
593   }
594 
parseOrientation(String name, ResTable_config out)595   private static boolean parseOrientation(String name, ResTable_config out) {
596     if (Objects.equals(name, kWildcardName)) {
597       if (out != null) {
598         out.orientation = ResTable_config.ORIENTATION_ANY;
599       }
600       return true;
601     } else if (Objects.equals(name, "port")) {
602       if (out != null) {
603         out.orientation = ResTable_config.ORIENTATION_PORT;
604       }
605       return true;
606     } else if (Objects.equals(name, "land")) {
607       if (out != null) {
608         out.orientation = ResTable_config.ORIENTATION_LAND;
609       }
610       return true;
611     } else if (Objects.equals(name, "square")) {
612       if (out != null) {
613         out.orientation = ResTable_config.ORIENTATION_SQUARE;
614       }
615       return true;
616     }
617 
618     return false;
619   }
620 
parseUiModeType(String name, ResTable_config out)621   private static boolean parseUiModeType(String name, ResTable_config out) {
622     if (Objects.equals(name, kWildcardName)) {
623       if (out != null) {
624         out.uiMode = (out.uiMode & ~ResTable_config.MASK_UI_MODE_TYPE) |
625             ResTable_config.UI_MODE_TYPE_ANY;
626       }
627       return true;
628     } else if (Objects.equals(name, "desk")) {
629       if (out != null) {
630         out.uiMode = (out.uiMode & ~ResTable_config.MASK_UI_MODE_TYPE) |
631             ResTable_config.UI_MODE_TYPE_DESK;
632       }
633       return true;
634     } else if (Objects.equals(name, "car")) {
635       if (out != null) {
636         out.uiMode = (out.uiMode & ~ResTable_config.MASK_UI_MODE_TYPE) |
637             ResTable_config.UI_MODE_TYPE_CAR;
638       }
639       return true;
640     } else if (Objects.equals(name, "television")) {
641       if (out != null) {
642         out.uiMode = (out.uiMode & ~ResTable_config.MASK_UI_MODE_TYPE) |
643             ResTable_config.UI_MODE_TYPE_TELEVISION;
644       }
645       return true;
646     } else if (Objects.equals(name, "appliance")) {
647       if (out != null) {
648         out.uiMode = (out.uiMode & ~ResTable_config.MASK_UI_MODE_TYPE) |
649             ResTable_config.UI_MODE_TYPE_APPLIANCE;
650       }
651       return true;
652     } else if (Objects.equals(name, "watch")) {
653       if (out != null) {
654         out.uiMode = (out.uiMode & ~ResTable_config.MASK_UI_MODE_TYPE) |
655             ResTable_config.UI_MODE_TYPE_WATCH;
656       }
657       return true;
658     } else if (Objects.equals(name, "vrheadset")) {
659       if (out != null) {
660         out.uiMode = (out.uiMode & ~ResTable_config.MASK_UI_MODE_TYPE) |
661             ResTable_config.UI_MODE_TYPE_VR_HEADSET;
662       }
663       return true;
664     }
665 
666     return false;
667   }
668 
parseUiModeNight(String name, ResTable_config out)669   private static boolean parseUiModeNight(String name, ResTable_config out) {
670     if (Objects.equals(name, kWildcardName)) {
671       if (out != null) {
672         out.uiMode = (out.uiMode & ~ResTable_config.MASK_UI_MODE_NIGHT) |
673             ResTable_config.UI_MODE_NIGHT_ANY;
674       }
675       return true;
676     } else if (Objects.equals(name, "night")) {
677       if (out != null) {
678         out.uiMode = (out.uiMode & ~ResTable_config.MASK_UI_MODE_NIGHT) |
679             ResTable_config.UI_MODE_NIGHT_YES;
680       }
681       return true;
682     } else if (Objects.equals(name, "notnight")) {
683       if (out != null) {
684         out.uiMode = (out.uiMode & ~ResTable_config.MASK_UI_MODE_NIGHT) |
685             ResTable_config.UI_MODE_NIGHT_NO;
686       }
687       return true;
688     }
689 
690     return false;
691   }
692 
parseDensity(String name, ResTable_config out)693   private static boolean parseDensity(String name, ResTable_config out) {
694     if (Objects.equals(name, kWildcardName)) {
695       if (out != null) {
696         out.density = ResTable_config.DENSITY_DEFAULT;
697       }
698       return true;
699     }
700 
701     if (Objects.equals(name, "anydpi")) {
702       if (out != null) {
703         out.density = ResTable_config.DENSITY_ANY;
704       }
705       return true;
706     }
707 
708     if (Objects.equals(name, "nodpi")) {
709       if (out != null) {
710         out.density = ResTable_config.DENSITY_NONE;
711       }
712       return true;
713     }
714 
715     if (Objects.equals(name, "ldpi")) {
716       if (out != null) {
717         out.density = ResTable_config.DENSITY_LOW;
718       }
719       return true;
720     }
721 
722     if (Objects.equals(name, "mdpi")) {
723       if (out != null) {
724         out.density = ResTable_config.DENSITY_MEDIUM;
725       }
726       return true;
727     }
728 
729     if (Objects.equals(name, "tvdpi")) {
730       if (out != null) {
731         out.density = ResTable_config.DENSITY_TV;
732       }
733       return true;
734     }
735 
736     if (Objects.equals(name, "hdpi")) {
737       if (out != null) {
738         out.density = ResTable_config.DENSITY_HIGH;
739       }
740       return true;
741     }
742 
743     if (Objects.equals(name, "xhdpi")) {
744       if (out != null) {
745         out.density = ResTable_config.DENSITY_XHIGH;
746       }
747       return true;
748     }
749 
750     if (Objects.equals(name, "xxhdpi")) {
751       if (out != null) {
752         out.density = ResTable_config.DENSITY_XXHIGH;
753       }
754       return true;
755     }
756 
757     if (Objects.equals(name, "xxxhdpi")) {
758       if (out != null) {
759         out.density = ResTable_config.DENSITY_XXXHIGH;
760       }
761       return true;
762     }
763 
764     // check that we have 'dpi' after the last digit.
765     Matcher matcher = DENSITY_PATTERN.matcher(name);
766     if (matcher.matches()) {
767       out.density = Integer.parseInt(matcher.group(1));
768       return true;
769     }
770     return false;
771   }
772 
parseTouchscreen(String name, ResTable_config out)773   private static boolean parseTouchscreen(String name, ResTable_config out) {
774     if (Objects.equals(name, kWildcardName)) {
775       if (out != null) {
776         out.touchscreen = ResTable_config.TOUCHSCREEN_ANY;
777       }
778       return true;
779     } else if (Objects.equals(name, "notouch")) {
780       if (out != null) {
781         out.touchscreen = ResTable_config.TOUCHSCREEN_NOTOUCH;
782       }
783       return true;
784     } else if (Objects.equals(name, "stylus")) {
785       if (out != null) {
786         out.touchscreen = ResTable_config.TOUCHSCREEN_STYLUS;
787       }
788       return true;
789     } else if (Objects.equals(name, "finger")) {
790       if (out != null) {
791         out.touchscreen = ResTable_config.TOUCHSCREEN_FINGER;
792       }
793       return true;
794     }
795 
796     return false;
797   }
798 
parseKeysHidden(String name, ResTable_config out)799   private static boolean parseKeysHidden(String name, ResTable_config out) {
800     byte mask = 0;
801     byte value = 0;
802     if (Objects.equals(name, kWildcardName)) {
803       mask = ResTable_config.MASK_KEYSHIDDEN;
804       value = ResTable_config.KEYSHIDDEN_ANY;
805     } else if (Objects.equals(name, "keysexposed")) {
806       mask = ResTable_config.MASK_KEYSHIDDEN;
807       value = ResTable_config.KEYSHIDDEN_NO;
808     } else if (Objects.equals(name, "keyshidden")) {
809       mask = ResTable_config.MASK_KEYSHIDDEN;
810       value = ResTable_config.KEYSHIDDEN_YES;
811     } else if (Objects.equals(name, "keyssoft")) {
812       mask = ResTable_config.MASK_KEYSHIDDEN;
813       value = ResTable_config.KEYSHIDDEN_SOFT;
814     }
815 
816     if (mask != 0) {
817       if (out != null) {
818         out.inputFlags = (out.inputFlags & ~mask) | value;
819       }
820       return true;
821     }
822 
823     return false;
824   }
825 
parseKeyboard(String name, ResTable_config out)826   private static boolean parseKeyboard(String name, ResTable_config out) {
827     if (Objects.equals(name, kWildcardName)) {
828       if (out != null) {
829         out.keyboard = ResTable_config.KEYBOARD_ANY;
830       }
831       return true;
832     } else if (Objects.equals(name, "nokeys")) {
833       if (out != null) {
834         out.keyboard = ResTable_config.KEYBOARD_NOKEYS;
835       }
836       return true;
837     } else if (Objects.equals(name, "qwerty")) {
838       if (out != null) {
839         out.keyboard = ResTable_config.KEYBOARD_QWERTY;
840       }
841       return true;
842     } else if (Objects.equals(name, "12key")) {
843       if (out != null) {
844         out.keyboard = ResTable_config.KEYBOARD_12KEY;
845       }
846       return true;
847     }
848 
849     return false;
850   }
851 
parseNavHidden(String name, ResTable_config out)852   private static boolean parseNavHidden(String name, ResTable_config out) {
853     byte mask = 0;
854     byte value = 0;
855     if (Objects.equals(name, kWildcardName)) {
856       mask = ResTable_config.MASK_NAVHIDDEN;
857       value = ResTable_config.NAVHIDDEN_ANY;
858     } else if (Objects.equals(name, "navexposed")) {
859       mask = ResTable_config.MASK_NAVHIDDEN;
860       value = ResTable_config.NAVHIDDEN_NO;
861     } else if (Objects.equals(name, "navhidden")) {
862       mask = ResTable_config.MASK_NAVHIDDEN;
863       value = ResTable_config.NAVHIDDEN_YES;
864     }
865 
866     if (mask != 0) {
867       if (out != null) {
868         out.inputFlags = (out.inputFlags & ~mask) | value;
869       }
870       return true;
871     }
872 
873     return false;
874   }
875 
parseNavigation(String name, ResTable_config out)876   private static boolean parseNavigation(String name, ResTable_config out) {
877     if (Objects.equals(name, kWildcardName)) {
878       if (out != null) {
879         out.navigation = ResTable_config.NAVIGATION_ANY;
880       }
881       return true;
882     } else if (Objects.equals(name, "nonav")) {
883       if (out != null) {
884         out.navigation = ResTable_config.NAVIGATION_NONAV;
885       }
886       return true;
887     } else if (Objects.equals(name, "dpad")) {
888       if (out != null) {
889         out.navigation = ResTable_config.NAVIGATION_DPAD;
890       }
891       return true;
892     } else if (Objects.equals(name, "trackball")) {
893       if (out != null) {
894         out.navigation = ResTable_config.NAVIGATION_TRACKBALL;
895       }
896       return true;
897     } else if (Objects.equals(name, "wheel")) {
898       if (out != null) {
899         out.navigation = ResTable_config.NAVIGATION_WHEEL;
900       }
901       return true;
902     }
903 
904     return false;
905   }
906 
parseScreenSize(String name, ResTable_config out)907   private static boolean parseScreenSize(String name, ResTable_config out) {
908     if (Objects.equals(name, kWildcardName)) {
909       if (out != null) {
910         out.screenWidth = ResTable_config.SCREENWIDTH_ANY;
911         out.screenHeight = ResTable_config.SCREENHEIGHT_ANY;
912       }
913       return true;
914     }
915 
916     Matcher matcher = HEIGHT_WIDTH_PATTERN.matcher(name);
917     if (matcher.matches()) {
918       int w = Integer.parseInt(matcher.group(1));
919       int h = Integer.parseInt(matcher.group(2));
920       if (w < h) {
921         return false;
922       }
923       out.screenWidth = w;
924       out.screenHeight = h;
925       return true;
926     }
927     return false;
928   }
929 
parseVersion(String name, ResTable_config out)930   private static boolean parseVersion(String name, ResTable_config out) {
931     if (Objects.equals(name, kWildcardName)) {
932       if (out != null) {
933         out.sdkVersion = ResTable_config.SDKVERSION_ANY;
934         out.minorVersion = ResTable_config.MINORVERSION_ANY;
935       }
936       return true;
937     }
938 
939     Matcher matcher = VERSION_QUALIFIER_PATTERN.matcher(name);
940     if (matcher.matches()) {
941       out.sdkVersion = Integer.parseInt(matcher.group(1));
942       out.minorVersion = 0;
943       return true;
944     }
945     return false;
946   }
947 
parseMnc(String name, ResTable_config out)948   private static boolean parseMnc(String name, ResTable_config out) {
949     if (Objects.equals(name, kWildcardName)) {
950       if (out != null) {
951         out.mnc = 0;
952       }
953       return true;
954     }
955 
956     Matcher matcher = MNC_PATTERN.matcher(name);
957     if (matcher.matches()) {
958       out.mnc = Integer.parseInt(matcher.group(1));
959       if (out.mnc == 0) {
960         out.mnc = ACONFIGURATION_MNC_ZERO;
961       }
962       return true;
963     }
964     return false;
965   }
966 
parseMcc(final String name, ResTable_config out)967   private static boolean parseMcc(final String name, ResTable_config out) {
968     if (Objects.equals(name, kWildcardName)) {
969       if (out != null) {
970         out.mcc = 0;
971       }
972       return true;
973     }
974 
975     Matcher matcher = MCC_PATTERN.matcher(name);
976     if (matcher.matches()) {
977       out.mcc = Integer.parseInt(matcher.group(1));
978       return true;
979     }
980     return false;
981   }
982 
983   // transliterated from https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/tools/aapt/AaptConfig.cpp
applyVersionForCompatibility(ResTable_config config)984   private static void applyVersionForCompatibility(ResTable_config config) {
985     if (config == null) {
986       return;
987     }
988     int min_sdk = 0;
989     if (((config.uiMode & ResTable_config.MASK_UI_MODE_TYPE)
990         == ResTable_config.UI_MODE_TYPE_VR_HEADSET) ||
991         (config.colorMode & ResTable_config.MASK_WIDE_COLOR_GAMUT) != 0 ||
992             (config.colorMode & ResTable_config.MASK_HDR) != 0) {
993       min_sdk = SDK_O;
994     } else if (isTruthy(config.screenLayout2 & ResTable_config.MASK_SCREENROUND)) {
995       min_sdk = SDK_MNC;
996     } else if (config.density == ResTable_config.DENSITY_ANY) {
997       min_sdk = SDK_LOLLIPOP;
998     } else if (config.smallestScreenWidthDp != ResTable_config.SCREENWIDTH_ANY
999         || config.screenWidthDp != ResTable_config.SCREENWIDTH_ANY
1000         || config.screenHeightDp != ResTable_config.SCREENHEIGHT_ANY) {
1001       min_sdk = SDK_HONEYCOMB_MR2;
1002     } else if ((config.uiMode & ResTable_config.MASK_UI_MODE_TYPE)
1003         != ResTable_config.UI_MODE_TYPE_ANY
1004         ||  (config.uiMode & ResTable_config.MASK_UI_MODE_NIGHT)
1005         != ResTable_config.UI_MODE_NIGHT_ANY) {
1006       min_sdk = SDK_FROYO;
1007     } else if ((config.screenLayout & ResTable_config.MASK_SCREENSIZE)
1008         != ResTable_config.SCREENSIZE_ANY
1009         ||  (config.screenLayout & ResTable_config.MASK_SCREENLONG)
1010         != ResTable_config.SCREENLONG_ANY
1011         || config.density != ResTable_config.DENSITY_DEFAULT) {
1012       min_sdk = SDK_DONUT;
1013     }
1014     if (min_sdk > config.sdkVersion) {
1015       config.sdkVersion = min_sdk;
1016     }
1017   }
1018 }
1019