xref: /aosp_15_r20/external/robolectric/resources/src/main/java/org/robolectric/res/android/ResTable_config.java (revision e6ba16074e6af37d123cb567d575f496bf0a58ee)
1 package org.robolectric.res.android;
2 
3 import static java.nio.charset.StandardCharsets.US_ASCII;
4 import static java.nio.charset.StandardCharsets.UTF_8;
5 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_DENSITY_ANY;
6 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_DENSITY_DEFAULT;
7 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_DENSITY_HIGH;
8 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_DENSITY_LOW;
9 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_DENSITY_MEDIUM;
10 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_DENSITY_NONE;
11 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_DENSITY_TV;
12 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_DENSITY_XHIGH;
13 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_DENSITY_XXHIGH;
14 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_DENSITY_XXXHIGH;
15 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_HDR_ANY;
16 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_HDR_NO;
17 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_HDR_YES;
18 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_KEYBOARD_ANY;
19 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_KEYSHIDDEN_ANY;
20 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_KEYSHIDDEN_NO;
21 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_KEYSHIDDEN_SOFT;
22 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_KEYSHIDDEN_YES;
23 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_LAYOUTDIR_ANY;
24 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_LAYOUTDIR_LTR;
25 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_LAYOUTDIR_RTL;
26 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_NAVHIDDEN_ANY;
27 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_NAVHIDDEN_NO;
28 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_NAVHIDDEN_YES;
29 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_NAVIGATION_ANY;
30 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_ORIENTATION_ANY;
31 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_ORIENTATION_LAND;
32 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_ORIENTATION_PORT;
33 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_ORIENTATION_SQUARE;
34 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_SCREENLONG_ANY;
35 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_SCREENLONG_NO;
36 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_SCREENLONG_YES;
37 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_SCREENROUND_ANY;
38 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_SCREENROUND_NO;
39 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_SCREENROUND_YES;
40 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_SCREENSIZE_ANY;
41 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_SCREENSIZE_LARGE;
42 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_SCREENSIZE_NORMAL;
43 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_SCREENSIZE_SMALL;
44 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_SCREENSIZE_XLARGE;
45 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_TOUCHSCREEN_ANY;
46 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_UI_MODE_NIGHT_ANY;
47 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_UI_MODE_TYPE_ANY;
48 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_UI_MODE_TYPE_NORMAL;
49 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_WIDE_COLOR_GAMUT_ANY;
50 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_WIDE_COLOR_GAMUT_NO;
51 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_WIDE_COLOR_GAMUT_YES;
52 import static org.robolectric.res.android.LocaleData.localeDataCompareRegions;
53 import static org.robolectric.res.android.LocaleData.localeDataComputeScript;
54 import static org.robolectric.res.android.LocaleData.localeDataIsCloseToUsEnglish;
55 import static org.robolectric.res.android.ResTable.kDebugTableSuperNoisy;
56 import static org.robolectric.res.android.Util.ALOGI;
57 import static org.robolectric.res.android.Util.dtohl;
58 import static org.robolectric.res.android.Util.dtohs;
59 import static org.robolectric.res.android.Util.isTruthy;
60 
61 import com.google.common.base.Joiner;
62 import com.google.common.base.Preconditions;
63 import com.google.common.primitives.Bytes;
64 import com.google.common.primitives.UnsignedBytes;
65 import java.nio.ByteBuffer;
66 import java.util.Arrays;
67 import java.util.Collection;
68 import java.util.Collections;
69 import java.util.HashMap;
70 import java.util.LinkedHashMap;
71 import java.util.Map;
72 import javax.annotation.Nonnull;
73 
74 /**
75  * Describes a particular resource configuration.
76  *
77  * <p>Transliterated from: *
78  * https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/ResourceTypes.cpp
79  * *
80  * https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/include/ResourceTypes.h
81  * (struct ResTable_config)
82  *
83  * <p>Changes from 8.0.0_r4 partially applied.
84  */
85 @SuppressWarnings("NewApi")
86 public class ResTable_config {
87 
88   // The most specific locale can consist of:
89   //
90   // - a 3 char language code
91   // - a 3 char region code prefixed by a 'r'
92   // - a 4 char script code prefixed by a 's'
93   // - a 8 char variant code prefixed by a 'v'
94   //
95   // each separated by a single char separator, which sums up to a total of 24
96   // chars, (25 include the string terminator) rounded up to 28 to be 4 byte
97   // aligned.
98   public static final int RESTABLE_MAX_LOCALE_LEN = 28;
99 
100   /** The minimum size in bytes that this configuration must be to contain screen config info. */
101   private static final int SCREEN_CONFIG_MIN_SIZE = 32;
102 
103   /** The minimum size in bytes that this configuration must be to contain screen dp info. */
104   private static final int SCREEN_DP_MIN_SIZE = 36;
105 
106   /** The minimum size in bytes that this configuration must be to contain locale info. */
107   private static final int LOCALE_MIN_SIZE = 48;
108 
109   /** The minimum size in bytes that this config must be to contain the screenConfig extension. */
110   private static final int SCREEN_CONFIG_EXTENSION_MIN_SIZE = 52;
111 
112   public static final int SIZEOF = SCREEN_CONFIG_EXTENSION_MIN_SIZE;
113 
114   // Codes for specially handled languages and regions
115   static final byte[] kEnglish = new byte[] {'e', 'n'}; // packed version of "en"
116   static final byte[] kUnitedStates = new byte[] {'U', 'S'}; // packed version of "US"
117   static final byte[] kFilipino =
118       new byte[] {(byte) 0xAD, 0x05}; // packed version of "fil" ported from C {'\xAD', '\x05'}
119   static final byte[] kTagalog = new byte[] {'t', 'l'}; // packed version of "tl"
120 
createConfig(ByteBuffer buffer)121   static ResTable_config createConfig(ByteBuffer buffer) {
122     int startPosition = buffer.position(); // The starting buffer position to calculate bytes read.
123     int size = buffer.getInt();
124     int mcc = buffer.getShort() & 0xFFFF;
125     int mnc = buffer.getShort() & 0xFFFF;
126     byte[] language = new byte[2];
127     buffer.get(language);
128     byte[] region = new byte[2];
129     buffer.get(region);
130     int orientation = UnsignedBytes.toInt(buffer.get());
131     int touchscreen = UnsignedBytes.toInt(buffer.get());
132     int density = buffer.getShort() & 0xFFFF;
133     int keyboard = UnsignedBytes.toInt(buffer.get());
134     int navigation = UnsignedBytes.toInt(buffer.get());
135     int inputFlags = UnsignedBytes.toInt(buffer.get());
136     buffer.get(); // 1 byte of padding
137     int screenWidth = buffer.getShort() & 0xFFFF;
138     int screenHeight = buffer.getShort() & 0xFFFF;
139     int sdkVersion = buffer.getShort() & 0xFFFF;
140     int minorVersion = buffer.getShort() & 0xFFFF;
141 
142     // At this point, the configuration's size needs to be taken into account as not all
143     // configurations have all values.
144     int screenLayout = 0;
145     int uiMode = 0;
146     int smallestScreenWidthDp = 0;
147     int screenWidthDp = 0;
148     int screenHeightDp = 0;
149     byte[] localeScript = new byte[4];
150     byte[] localeVariant = new byte[8];
151     byte screenLayout2 = 0;
152     byte screenConfigPad1 = 0;
153     short screenConfigPad2 = 0;
154 
155     if (size >= SCREEN_CONFIG_MIN_SIZE) {
156       screenLayout = UnsignedBytes.toInt(buffer.get());
157       uiMode = UnsignedBytes.toInt(buffer.get());
158       smallestScreenWidthDp = buffer.getShort() & 0xFFFF;
159     }
160 
161     if (size >= SCREEN_DP_MIN_SIZE) {
162       screenWidthDp = buffer.getShort() & 0xFFFF;
163       screenHeightDp = buffer.getShort() & 0xFFFF;
164     }
165 
166     if (size >= LOCALE_MIN_SIZE) {
167       buffer.get(localeScript);
168       buffer.get(localeVariant);
169     }
170 
171     if (size >= SCREEN_CONFIG_EXTENSION_MIN_SIZE) {
172       screenLayout2 = (byte) UnsignedBytes.toInt(buffer.get());
173       screenConfigPad1 = buffer.get(); // Reserved padding
174       screenConfigPad2 = buffer.getShort(); // More reserved padding
175     }
176 
177     // After parsing everything that's known, account for anything that's unknown.
178     int bytesRead = buffer.position() - startPosition;
179     byte[] unknown = new byte[size - bytesRead];
180     buffer.get(unknown);
181 
182     return new ResTable_config(
183         size,
184         mcc,
185         mnc,
186         language,
187         region,
188         orientation,
189         touchscreen,
190         density,
191         keyboard,
192         navigation,
193         inputFlags,
194         screenWidth,
195         screenHeight,
196         sdkVersion,
197         minorVersion,
198         screenLayout,
199         uiMode,
200         smallestScreenWidthDp,
201         screenWidthDp,
202         screenHeightDp,
203         localeScript,
204         localeVariant,
205         screenLayout2,
206         screenConfigPad1,
207         screenConfigPad2,
208         unknown);
209   }
210 
211   /**
212    * The different types of configs that can be present in a {@link ResTable_config}.
213    *
214    * <p>The ordering of these types is roughly the same as {@code #isBetterThan}, but is not
215    * guaranteed to be the same.
216    */
217   public enum Type {
218     MCC,
219     MNC,
220     LANGUAGE_STRING,
221     LOCALE_SCRIPT_STRING,
222     REGION_STRING,
223     LOCALE_VARIANT_STRING,
224     SCREEN_LAYOUT_DIRECTION,
225     SMALLEST_SCREEN_WIDTH_DP,
226     SCREEN_WIDTH_DP,
227     SCREEN_HEIGHT_DP,
228     SCREEN_LAYOUT_SIZE,
229     SCREEN_LAYOUT_LONG,
230     SCREEN_LAYOUT_ROUND,
231     COLOR_MODE_WIDE_COLOR_GAMUT, // NB: COLOR_GAMUT takes priority over HDR in #isBetterThan.
232     COLOR_MODE_HDR,
233     ORIENTATION,
234     UI_MODE_TYPE,
235     UI_MODE_NIGHT,
236     DENSITY_DPI,
237     TOUCHSCREEN,
238     KEYBOARD_HIDDEN,
239     KEYBOARD,
240     NAVIGATION_HIDDEN,
241     NAVIGATION,
242     SCREEN_SIZE,
243     SDK_VERSION
244   }
245 
246   // screenLayout bits for layout direction.
247   //  public static final int MASK_LAYOUTDIR = 0xC0;
248   public static final int SHIFT_LAYOUTDIR = 6;
249   public static final int LAYOUTDIR_ANY = ACONFIGURATION_LAYOUTDIR_ANY << SHIFT_LAYOUTDIR;
250   public static final int LAYOUTDIR_LTR = ACONFIGURATION_LAYOUTDIR_LTR << SHIFT_LAYOUTDIR;
251   public static final int LAYOUTDIR_RTL = ACONFIGURATION_LAYOUTDIR_RTL << SHIFT_LAYOUTDIR;
252 
253   public static final int SCREENWIDTH_ANY = 0;
254   //  public static final int MASK_SCREENSIZE = 0x0f;
255   public static final int SCREENSIZE_ANY = ACONFIGURATION_SCREENSIZE_ANY;
256   public static final int SCREENSIZE_SMALL = ACONFIGURATION_SCREENSIZE_SMALL;
257   public static final int SCREENSIZE_NORMAL = ACONFIGURATION_SCREENSIZE_NORMAL;
258   public static final int SCREENSIZE_LARGE = ACONFIGURATION_SCREENSIZE_LARGE;
259   public static final int SCREENSIZE_XLARGE = ACONFIGURATION_SCREENSIZE_XLARGE;
260 
261   // uiMode bits for the mode type.
262   public static final int MASK_UI_MODE_TYPE = 0x0f;
263   public static final int UI_MODE_TYPE_ANY = ACONFIGURATION_UI_MODE_TYPE_ANY;
264   public static final int UI_MODE_TYPE_NORMAL = ACONFIGURATION_UI_MODE_TYPE_NORMAL;
265 
266   // uiMode bits for the night switch;
267   public static final int MASK_UI_MODE_NIGHT = 0x30;
268   public static final int SHIFT_UI_MODE_NIGHT = 4;
269   public static final int UI_MODE_NIGHT_ANY =
270       ACONFIGURATION_UI_MODE_NIGHT_ANY << SHIFT_UI_MODE_NIGHT;
271 
272   public static final int DENSITY_DEFAULT = ACONFIGURATION_DENSITY_DEFAULT;
273   public static final int DENSITY_LOW = ACONFIGURATION_DENSITY_LOW;
274   public static final int DENSITY_MEDIUM = ACONFIGURATION_DENSITY_MEDIUM;
275   public static final int DENSITY_TV = ACONFIGURATION_DENSITY_TV;
276   public static final int DENSITY_HIGH = ACONFIGURATION_DENSITY_HIGH;
277   public static final int DENSITY_XHIGH = ACONFIGURATION_DENSITY_XHIGH;
278   public static final int DENSITY_XXHIGH = ACONFIGURATION_DENSITY_XXHIGH;
279   public static final int DENSITY_XXXHIGH = ACONFIGURATION_DENSITY_XXXHIGH;
280   public static final int DENSITY_ANY = ACONFIGURATION_DENSITY_ANY;
281   public static final int DENSITY_NONE = ACONFIGURATION_DENSITY_NONE;
282 
283   public static final int TOUCHSCREEN_ANY = ACONFIGURATION_TOUCHSCREEN_ANY;
284 
285   public static final int MASK_KEYSHIDDEN = 0x0003;
286   public static final byte KEYSHIDDEN_ANY = ACONFIGURATION_KEYSHIDDEN_ANY;
287   public static final byte KEYSHIDDEN_NO = ACONFIGURATION_KEYSHIDDEN_NO;
288   public static final byte KEYSHIDDEN_YES = ACONFIGURATION_KEYSHIDDEN_YES;
289   public static final byte KEYSHIDDEN_SOFT = ACONFIGURATION_KEYSHIDDEN_SOFT;
290 
291   public static final int KEYBOARD_ANY = ACONFIGURATION_KEYBOARD_ANY;
292 
293   public static final int MASK_NAVHIDDEN = 0x000c;
294   public static final int SHIFT_NAVHIDDEN = 2;
295   public static final byte NAVHIDDEN_ANY = ACONFIGURATION_NAVHIDDEN_ANY << SHIFT_NAVHIDDEN;
296   public static final byte NAVHIDDEN_NO = ACONFIGURATION_NAVHIDDEN_NO << SHIFT_NAVHIDDEN;
297   public static final byte NAVHIDDEN_YES = ACONFIGURATION_NAVHIDDEN_YES << SHIFT_NAVHIDDEN;
298 
299   public static final int NAVIGATION_ANY = ACONFIGURATION_NAVIGATION_ANY;
300 
301   public static final int SCREENHEIGHT_ANY = 0;
302 
303   public static final int SDKVERSION_ANY = 0;
304   public static final int MINORVERSION_ANY = 0;
305 
306   // from
307   // https://github.com/google/android-arscblamer/blob/master/java/com/google/devrel/gmscore/tools/apk/arsc/ResourceConfiguration.java
308   /** The below constants are from android.content.res.Configuration. */
309   static final int COLOR_MODE_WIDE_COLOR_GAMUT_MASK = 0x03;
310 
311   public static final int WIDE_COLOR_GAMUT_ANY = ACONFIGURATION_WIDE_COLOR_GAMUT_ANY;
312   public static final int WIDE_COLOR_GAMUT_NO = ACONFIGURATION_WIDE_COLOR_GAMUT_NO;
313   public static final int WIDE_COLOR_GAMUT_YES = ACONFIGURATION_WIDE_COLOR_GAMUT_YES;
314   public static final int MASK_WIDE_COLOR_GAMUT = 0x03;
315   static final int COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED = 0;
316   static final int COLOR_MODE_WIDE_COLOR_GAMUT_NO = 0x01;
317   static final int COLOR_MODE_WIDE_COLOR_GAMUT_YES = 0x02;
318 
319   private static final Map<Integer, String> COLOR_MODE_WIDE_COLOR_GAMUT_VALUES;
320 
321   static {
322     Map<Integer, String> map = new HashMap<>();
map.put(COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED, "")323     map.put(COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED, "");
map.put(COLOR_MODE_WIDE_COLOR_GAMUT_NO, "nowidecg")324     map.put(COLOR_MODE_WIDE_COLOR_GAMUT_NO, "nowidecg");
map.put(COLOR_MODE_WIDE_COLOR_GAMUT_YES, "widecg")325     map.put(COLOR_MODE_WIDE_COLOR_GAMUT_YES, "widecg");
326     COLOR_MODE_WIDE_COLOR_GAMUT_VALUES = Collections.unmodifiableMap(map);
327   }
328 
329   public static final int HDR_ANY = ACONFIGURATION_HDR_ANY;
330   public static final int HDR_NO = ACONFIGURATION_HDR_NO << 2;
331   public static final int HDR_YES = ACONFIGURATION_HDR_YES << 2;
332   public static final int MASK_HDR = 0x0c;
333   static final int COLOR_MODE_HDR_MASK = 0x0C;
334   static final int COLOR_MODE_HDR_UNDEFINED = 0;
335   static final int COLOR_MODE_HDR_NO = 0x04;
336   static final int COLOR_MODE_HDR_YES = 0x08;
337 
338   private static final Map<Integer, String> COLOR_MODE_HDR_VALUES;
339 
340   static {
341     Map<Integer, String> map = new HashMap<>();
map.put(COLOR_MODE_HDR_UNDEFINED, "")342     map.put(COLOR_MODE_HDR_UNDEFINED, "");
map.put(COLOR_MODE_HDR_NO, "lowdr")343     map.put(COLOR_MODE_HDR_NO, "lowdr");
map.put(COLOR_MODE_HDR_YES, "highdr")344     map.put(COLOR_MODE_HDR_YES, "highdr");
345     COLOR_MODE_HDR_VALUES = Collections.unmodifiableMap(map);
346   }
347 
348   public static final int DENSITY_DPI_UNDEFINED = 0;
349   static final int DENSITY_DPI_LDPI = 120;
350   public static final int DENSITY_DPI_MDPI = 160;
351   static final int DENSITY_DPI_TVDPI = 213;
352   static final int DENSITY_DPI_HDPI = 240;
353   static final int DENSITY_DPI_XHDPI = 320;
354   static final int DENSITY_DPI_XXHDPI = 480;
355   static final int DENSITY_DPI_XXXHDPI = 640;
356   public static final int DENSITY_DPI_ANY = 0xFFFE;
357   public static final int DENSITY_DPI_NONE = 0xFFFF;
358 
359   private static final Map<Integer, String> DENSITY_DPI_VALUES;
360 
361   static {
362     Map<Integer, String> map = new HashMap<>();
map.put(DENSITY_DPI_UNDEFINED, "")363     map.put(DENSITY_DPI_UNDEFINED, "");
map.put(DENSITY_DPI_LDPI, "ldpi")364     map.put(DENSITY_DPI_LDPI, "ldpi");
map.put(DENSITY_DPI_MDPI, "mdpi")365     map.put(DENSITY_DPI_MDPI, "mdpi");
map.put(DENSITY_DPI_TVDPI, "tvdpi")366     map.put(DENSITY_DPI_TVDPI, "tvdpi");
map.put(DENSITY_DPI_HDPI, "hdpi")367     map.put(DENSITY_DPI_HDPI, "hdpi");
map.put(DENSITY_DPI_XHDPI, "xhdpi")368     map.put(DENSITY_DPI_XHDPI, "xhdpi");
map.put(DENSITY_DPI_XXHDPI, "xxhdpi")369     map.put(DENSITY_DPI_XXHDPI, "xxhdpi");
map.put(DENSITY_DPI_XXXHDPI, "xxxhdpi")370     map.put(DENSITY_DPI_XXXHDPI, "xxxhdpi");
map.put(DENSITY_DPI_ANY, "anydpi")371     map.put(DENSITY_DPI_ANY, "anydpi");
map.put(DENSITY_DPI_NONE, "nodpi")372     map.put(DENSITY_DPI_NONE, "nodpi");
373     DENSITY_DPI_VALUES = Collections.unmodifiableMap(map);
374   }
375 
376   static final int KEYBOARD_NOKEYS = 1;
377   static final int KEYBOARD_QWERTY = 2;
378   static final int KEYBOARD_12KEY = 3;
379 
380   private static final Map<Integer, String> KEYBOARD_VALUES;
381 
382   static {
383     Map<Integer, String> map = new HashMap<>();
map.put(KEYBOARD_NOKEYS, "nokeys")384     map.put(KEYBOARD_NOKEYS, "nokeys");
map.put(KEYBOARD_QWERTY, "qwerty")385     map.put(KEYBOARD_QWERTY, "qwerty");
map.put(KEYBOARD_12KEY, "12key")386     map.put(KEYBOARD_12KEY, "12key");
387     KEYBOARD_VALUES = Collections.unmodifiableMap(map);
388   }
389 
390   static final int KEYBOARDHIDDEN_MASK = 0x03;
391   static final int KEYBOARDHIDDEN_NO = 1;
392   static final int KEYBOARDHIDDEN_YES = 2;
393   static final int KEYBOARDHIDDEN_SOFT = 3;
394 
395   private static final Map<Integer, String> KEYBOARDHIDDEN_VALUES;
396 
397   static {
398     Map<Integer, String> map = new HashMap<>();
map.put(KEYBOARDHIDDEN_NO, "keysexposed")399     map.put(KEYBOARDHIDDEN_NO, "keysexposed");
map.put(KEYBOARDHIDDEN_YES, "keyshidden")400     map.put(KEYBOARDHIDDEN_YES, "keyshidden");
map.put(KEYBOARDHIDDEN_SOFT, "keyssoft")401     map.put(KEYBOARDHIDDEN_SOFT, "keyssoft");
402     KEYBOARDHIDDEN_VALUES = Collections.unmodifiableMap(map);
403   }
404 
405   static final int NAVIGATION_NONAV = 1;
406   static final int NAVIGATION_DPAD = 2;
407   static final int NAVIGATION_TRACKBALL = 3;
408   static final int NAVIGATION_WHEEL = 4;
409 
410   private static final Map<Integer, String> NAVIGATION_VALUES;
411 
412   static {
413     Map<Integer, String> map = new HashMap<>();
map.put(NAVIGATION_NONAV, "nonav")414     map.put(NAVIGATION_NONAV, "nonav");
map.put(NAVIGATION_DPAD, "dpad")415     map.put(NAVIGATION_DPAD, "dpad");
map.put(NAVIGATION_TRACKBALL, "trackball")416     map.put(NAVIGATION_TRACKBALL, "trackball");
map.put(NAVIGATION_WHEEL, "wheel")417     map.put(NAVIGATION_WHEEL, "wheel");
418     NAVIGATION_VALUES = Collections.unmodifiableMap(map);
419   }
420 
421   static final int NAVIGATIONHIDDEN_MASK = 0x0C;
422   static final int NAVIGATIONHIDDEN_NO = 0x04;
423   static final int NAVIGATIONHIDDEN_YES = 0x08;
424 
425   private static final Map<Integer, String> NAVIGATIONHIDDEN_VALUES;
426 
427   static {
428     Map<Integer, String> map = new HashMap<>();
map.put(NAVIGATIONHIDDEN_NO, "navexposed")429     map.put(NAVIGATIONHIDDEN_NO, "navexposed");
map.put(NAVIGATIONHIDDEN_YES, "navhidden")430     map.put(NAVIGATIONHIDDEN_YES, "navhidden");
431     NAVIGATIONHIDDEN_VALUES = Collections.unmodifiableMap(map);
432   }
433 
434   public static final int ORIENTATION_ANY = ACONFIGURATION_ORIENTATION_ANY;
435   public static final int ORIENTATION_PORT = ACONFIGURATION_ORIENTATION_PORT;
436   public static final int ORIENTATION_LAND = ACONFIGURATION_ORIENTATION_LAND;
437   public static final int ORIENTATION_SQUARE = ACONFIGURATION_ORIENTATION_SQUARE;
438   static final int ORIENTATION_PORTRAIT = 0x01;
439   static final int ORIENTATION_LANDSCAPE = 0x02;
440 
441   private static final Map<Integer, String> ORIENTATION_VALUES;
442 
443   static {
444     Map<Integer, String> map = new HashMap<>();
map.put(ORIENTATION_PORTRAIT, "port")445     map.put(ORIENTATION_PORTRAIT, "port");
map.put(ORIENTATION_LANDSCAPE, "land")446     map.put(ORIENTATION_LANDSCAPE, "land");
447     ORIENTATION_VALUES = Collections.unmodifiableMap(map);
448   }
449 
450   static final int SCREENLAYOUT_LAYOUTDIR_MASK = 0xC0;
451   static final int SCREENLAYOUT_LAYOUTDIR_LTR = 0x40;
452   static final int SCREENLAYOUT_LAYOUTDIR_RTL = 0x80;
453 
454   private static final Map<Integer, String> SCREENLAYOUT_LAYOUTDIR_VALUES;
455 
456   static {
457     Map<Integer, String> map = new HashMap<>();
map.put(SCREENLAYOUT_LAYOUTDIR_LTR, "ldltr")458     map.put(SCREENLAYOUT_LAYOUTDIR_LTR, "ldltr");
map.put(SCREENLAYOUT_LAYOUTDIR_RTL, "ldrtl")459     map.put(SCREENLAYOUT_LAYOUTDIR_RTL, "ldrtl");
460     SCREENLAYOUT_LAYOUTDIR_VALUES = Collections.unmodifiableMap(map);
461   }
462 
463   // screenLayout bits for wide/long screen variation.
464   public static final int MASK_SCREENLONG = 0x30;
465   public static final int SHIFT_SCREENLONG = 4;
466   public static final int SCREENLONG_ANY = ACONFIGURATION_SCREENLONG_ANY << SHIFT_SCREENLONG;
467   public static final int SCREENLONG_NO = ACONFIGURATION_SCREENLONG_NO << SHIFT_SCREENLONG;
468   public static final int SCREENLONG_YES = ACONFIGURATION_SCREENLONG_YES << SHIFT_SCREENLONG;
469   static final int SCREENLAYOUT_LONG_MASK = 0x30;
470   static final int SCREENLAYOUT_LONG_NO = 0x10;
471   static final int SCREENLAYOUT_LONG_YES = 0x20;
472 
473   private static final Map<Integer, String> SCREENLAYOUT_LONG_VALUES;
474 
475   static {
476     Map<Integer, String> map = new HashMap<>();
map.put(SCREENLAYOUT_LONG_NO, "notlong")477     map.put(SCREENLAYOUT_LONG_NO, "notlong");
map.put(SCREENLAYOUT_LONG_YES, "long")478     map.put(SCREENLAYOUT_LONG_YES, "long");
479     SCREENLAYOUT_LONG_VALUES = Collections.unmodifiableMap(map);
480   }
481 
482   // screenLayout2 bits for round/notround.
483   static final int MASK_SCREENROUND = 0x03;
484   public static final int SCREENROUND_ANY = ACONFIGURATION_SCREENROUND_ANY;
485   public static final int SCREENROUND_NO = ACONFIGURATION_SCREENROUND_NO;
486   public static final int SCREENROUND_YES = ACONFIGURATION_SCREENROUND_YES;
487 
488   static final int SCREENLAYOUT_ROUND_MASK = 0x03;
489   static final int SCREENLAYOUT_ROUND_NO = 0x01;
490   static final int SCREENLAYOUT_ROUND_YES = 0x02;
491 
492   private static final Map<Integer, String> SCREENLAYOUT_ROUND_VALUES;
493 
494   static {
495     Map<Integer, String> map = new HashMap<>();
map.put(SCREENLAYOUT_ROUND_NO, "notround")496     map.put(SCREENLAYOUT_ROUND_NO, "notround");
map.put(SCREENLAYOUT_ROUND_YES, "round")497     map.put(SCREENLAYOUT_ROUND_YES, "round");
498     SCREENLAYOUT_ROUND_VALUES = Collections.unmodifiableMap(map);
499   }
500 
501   static final int SCREENLAYOUT_SIZE_MASK = 0x0F;
502   static final int SCREENLAYOUT_SIZE_SMALL = 0x01;
503   static final int SCREENLAYOUT_SIZE_NORMAL = 0x02;
504   static final int SCREENLAYOUT_SIZE_LARGE = 0x03;
505   static final int SCREENLAYOUT_SIZE_XLARGE = 0x04;
506 
507   private static final Map<Integer, String> SCREENLAYOUT_SIZE_VALUES;
508 
509   static {
510     Map<Integer, String> map = new HashMap<>();
map.put(SCREENLAYOUT_SIZE_SMALL, "small")511     map.put(SCREENLAYOUT_SIZE_SMALL, "small");
map.put(SCREENLAYOUT_SIZE_NORMAL, "normal")512     map.put(SCREENLAYOUT_SIZE_NORMAL, "normal");
map.put(SCREENLAYOUT_SIZE_LARGE, "large")513     map.put(SCREENLAYOUT_SIZE_LARGE, "large");
map.put(SCREENLAYOUT_SIZE_XLARGE, "xlarge")514     map.put(SCREENLAYOUT_SIZE_XLARGE, "xlarge");
515     SCREENLAYOUT_SIZE_VALUES = Collections.unmodifiableMap(map);
516   }
517 
518   static final int TOUCHSCREEN_NOTOUCH = 1;
519   @Deprecated static final int TOUCHSCREEN_STYLUS = 2;
520   public static final int TOUCHSCREEN_FINGER = 3;
521 
522   private static final Map<Integer, String> TOUCHSCREEN_VALUES;
523 
524   static {
525     Map<Integer, String> map = new HashMap<>();
map.put(TOUCHSCREEN_NOTOUCH, "notouch")526     map.put(TOUCHSCREEN_NOTOUCH, "notouch");
map.put(TOUCHSCREEN_FINGER, "finger")527     map.put(TOUCHSCREEN_FINGER, "finger");
528     TOUCHSCREEN_VALUES = Collections.unmodifiableMap(map);
529   }
530 
531   static final int UI_MODE_NIGHT_MASK = 0x30;
532   public static final int UI_MODE_NIGHT_NO = 0x10;
533   static final int UI_MODE_NIGHT_YES = 0x20;
534 
535   private static final Map<Integer, String> UI_MODE_NIGHT_VALUES;
536 
537   static {
538     Map<Integer, String> map = new HashMap<>();
map.put(UI_MODE_NIGHT_NO, "notnight")539     map.put(UI_MODE_NIGHT_NO, "notnight");
map.put(UI_MODE_NIGHT_YES, "night")540     map.put(UI_MODE_NIGHT_YES, "night");
541     UI_MODE_NIGHT_VALUES = Collections.unmodifiableMap(map);
542   }
543 
544   static final int UI_MODE_TYPE_MASK = 0x0F;
545   static final int UI_MODE_TYPE_DESK = 0x02;
546   static final int UI_MODE_TYPE_CAR = 0x03;
547   static final int UI_MODE_TYPE_TELEVISION = 0x04;
548   static final int UI_MODE_TYPE_APPLIANCE = 0x05;
549   static final int UI_MODE_TYPE_WATCH = 0x06;
550   static final int UI_MODE_TYPE_VR_HEADSET = 0x07;
551 
552   private static final Map<Integer, String> UI_MODE_TYPE_VALUES;
553 
554   static {
555     Map<Integer, String> map = new HashMap<>();
map.put(UI_MODE_TYPE_DESK, "desk")556     map.put(UI_MODE_TYPE_DESK, "desk");
map.put(UI_MODE_TYPE_CAR, "car")557     map.put(UI_MODE_TYPE_CAR, "car");
map.put(UI_MODE_TYPE_TELEVISION, "television")558     map.put(UI_MODE_TYPE_TELEVISION, "television");
map.put(UI_MODE_TYPE_APPLIANCE, "appliance")559     map.put(UI_MODE_TYPE_APPLIANCE, "appliance");
map.put(UI_MODE_TYPE_WATCH, "watch")560     map.put(UI_MODE_TYPE_WATCH, "watch");
map.put(UI_MODE_TYPE_VR_HEADSET, "vrheadset")561     map.put(UI_MODE_TYPE_VR_HEADSET, "vrheadset");
562     UI_MODE_TYPE_VALUES = Collections.unmodifiableMap(map);
563   }
564 
565   /** The number of bytes that this resource configuration takes up. */
566   int size;
567 
568   public int mcc;
569   public int mnc;
570 
571   /** Returns a packed 2-byte language code. */
572   @SuppressWarnings("mutable")
573   public final byte[] language;
574 
575   /** Returns {@link #language} as an unpacked string representation. */
576   @Nonnull
languageString()577   public final String languageString() {
578     return unpackLanguage();
579   }
580 
581   /** Returns the {@link #localeScript} as a string. */
localeScriptString()582   public final String localeScriptString() {
583     return byteArrayToString(localeScript);
584   }
585 
586   /** Returns the {@link #localeVariant} as a string. */
localeVariantString()587   public final String localeVariantString() {
588     return byteArrayToString(localeVariant);
589   }
590 
byteArrayToString(byte[] data)591   private String byteArrayToString(byte[] data) {
592     int length = Bytes.indexOf(data, (byte) 0);
593     return new String(data, 0, length >= 0 ? length : data.length, US_ASCII);
594   }
595 
596   /** Returns the wide color gamut section of {@link #colorMode}. */
colorModeWideColorGamut()597   public final int colorModeWideColorGamut() {
598     return colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK;
599   }
600 
601   /** Returns the HDR section of {@link #colorMode}. */
colorModeHdr()602   public final int colorModeHdr() {
603     return colorMode & COLOR_MODE_HDR_MASK;
604   }
605 
606   /** Returns a packed 2-byte country code. */
607   @SuppressWarnings("mutable")
608   public final byte[] country;
609 
610   /** Returns {@link #country} as an unpacked string representation. */
611   @Nonnull
regionString()612   public final String regionString() {
613     return unpackRegion();
614   }
615 
scriptString()616   public final String scriptString() {
617     if (localeScript[0] != '\0') {
618       return new String(localeScript, UTF_8);
619     } else {
620       return null;
621     }
622   }
623 
624   public int orientation;
625   public int touchscreen;
626   public int density;
627   public int keyboard;
628   public int navigation;
629   public int inputFlags;
630 
keyboardHidden()631   public final int keyboardHidden() {
632     return inputFlags & KEYBOARDHIDDEN_MASK;
633   }
634 
keyboardHidden(int value)635   public final void keyboardHidden(int value) {
636     inputFlags = (inputFlags & ~KEYBOARDHIDDEN_MASK) | value;
637   }
638 
navigationHidden()639   public final int navigationHidden() {
640     return (inputFlags & NAVIGATIONHIDDEN_MASK) >> 2;
641   }
642 
navigationHidden(int value)643   public final void navigationHidden(int value) {
644     inputFlags = (inputFlags & ~NAVIGATIONHIDDEN_MASK) | value;
645   }
646 
647   public int screenWidth;
648   public int screenHeight;
649   public int sdkVersion;
650 
651   /**
652    * Returns a copy of this resource configuration with a different {@link #sdkVersion}, or this
653    * configuration if the {@code sdkVersion} is the same.
654    *
655    * @param sdkVersion The SDK version of the returned configuration.
656    * @return A copy of this configuration with the only difference being #sdkVersion.
657    */
withSdkVersion(int sdkVersion)658   public final ResTable_config withSdkVersion(int sdkVersion) {
659     if (sdkVersion == this.sdkVersion) {
660       return this;
661     }
662     return new ResTable_config(
663         size,
664         mcc,
665         mnc,
666         language,
667         country,
668         orientation,
669         touchscreen,
670         density,
671         keyboard,
672         navigation,
673         inputFlags,
674         screenWidth,
675         screenHeight,
676         sdkVersion,
677         minorVersion,
678         screenLayout,
679         uiMode,
680         smallestScreenWidthDp,
681         screenWidthDp,
682         screenHeightDp,
683         localeScript,
684         localeVariant,
685         screenLayout2,
686         colorMode,
687         screenConfigPad2,
688         unknown);
689   }
690 
ResTable_config(ResTable_config other)691   public ResTable_config(ResTable_config other) {
692     this.size = other.size;
693     this.mcc = other.mcc;
694     this.mnc = other.mnc;
695     this.language = other.language;
696     this.country = other.country;
697     this.orientation = other.orientation;
698     this.touchscreen = other.touchscreen;
699     this.density = other.density;
700     this.keyboard = other.keyboard;
701     this.navigation = other.navigation;
702     this.inputFlags = other.inputFlags;
703     this.screenWidth = other.screenWidth;
704     this.screenHeight = other.screenHeight;
705     this.sdkVersion = other.sdkVersion;
706     this.minorVersion = other.minorVersion;
707     this.screenLayout = other.screenLayout;
708     this.uiMode = other.uiMode;
709     this.smallestScreenWidthDp = other.smallestScreenWidthDp;
710     this.screenWidthDp = other.screenWidthDp;
711     this.screenHeightDp = other.screenHeightDp;
712     this.localeScript = other.localeScript;
713     this.localeVariant = other.localeVariant;
714     this.screenLayout2 = other.screenLayout2;
715     this.colorMode = other.colorMode;
716     this.screenConfigPad2 = other.screenConfigPad2;
717     this.unknown = other.unknown;
718   }
719 
ResTable_config( int size, int mcc, int mnc, byte[] language, byte[] country, int orientation, int touchscreen, int density, int keyboard, int navigation, int inputFlags, int screenWidth, int screenHeight, int sdkVersion, int minorVersion, int screenLayout, int uiMode, int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp, byte[] localeScript, byte[] localeVariant, byte screenLayout2, byte colorMode, short screenConfigPad2, byte[] unknown)720   public ResTable_config(
721       int size,
722       int mcc,
723       int mnc,
724       byte[] language,
725       byte[] country,
726       int orientation,
727       int touchscreen,
728       int density,
729       int keyboard,
730       int navigation,
731       int inputFlags,
732       int screenWidth,
733       int screenHeight,
734       int sdkVersion,
735       int minorVersion,
736       int screenLayout,
737       int uiMode,
738       int smallestScreenWidthDp,
739       int screenWidthDp,
740       int screenHeightDp,
741       byte[] localeScript,
742       byte[] localeVariant,
743       byte screenLayout2,
744       byte colorMode,
745       short screenConfigPad2,
746       byte[] unknown) {
747     this.size = size;
748     this.mcc = mcc;
749     this.mnc = mnc;
750     this.language = language;
751     this.country = country;
752     this.orientation = orientation;
753     this.touchscreen = touchscreen;
754     this.density = density;
755     this.keyboard = keyboard;
756     this.navigation = navigation;
757     this.inputFlags = inputFlags;
758     this.screenWidth = screenWidth;
759     this.screenHeight = screenHeight;
760     this.sdkVersion = sdkVersion;
761     this.minorVersion = minorVersion;
762     this.screenLayout = screenLayout;
763     this.uiMode = uiMode;
764     this.smallestScreenWidthDp = smallestScreenWidthDp;
765     this.screenWidthDp = screenWidthDp;
766     this.screenHeightDp = screenHeightDp;
767     this.localeScript = localeScript;
768     this.localeVariant = localeVariant;
769     this.screenLayout2 = screenLayout2;
770     this.colorMode = colorMode;
771     this.screenConfigPad2 = screenConfigPad2;
772     this.unknown = unknown;
773   }
774 
ResTable_config()775   public ResTable_config() {
776     this.language = new byte[2];
777     this.country = new byte[2];
778     this.localeScript = new byte[LocaleData.SCRIPT_LENGTH];
779     this.localeVariant = new byte[8];
780   }
781 
782   public int minorVersion;
783   public int screenLayout;
784 
screenLayoutDirection()785   public final int screenLayoutDirection() {
786     return screenLayout & SCREENLAYOUT_LAYOUTDIR_MASK;
787   }
788 
screenLayoutDirection(int value)789   public final void screenLayoutDirection(int value) {
790     screenLayout = (screenLayout & ~SCREENLAYOUT_LAYOUTDIR_MASK) | value;
791   }
792 
screenLayoutSize()793   public final int screenLayoutSize() {
794     return screenLayout & SCREENLAYOUT_SIZE_MASK;
795   }
796 
screenLayoutSize(int value)797   public final void screenLayoutSize(int value) {
798     screenLayout = (screenLayout & ~SCREENLAYOUT_SIZE_MASK) | value;
799   }
800 
screenLayoutLong()801   public final int screenLayoutLong() {
802     return screenLayout & SCREENLAYOUT_LONG_MASK;
803   }
804 
screenLayoutLong(int value)805   public final void screenLayoutLong(int value) {
806     screenLayout = (screenLayout & ~SCREENLAYOUT_LONG_MASK) | value;
807   }
808 
screenLayoutRound()809   public final int screenLayoutRound() {
810     return screenLayout2 & SCREENLAYOUT_ROUND_MASK;
811   }
812 
screenLayoutRound(int value)813   public final void screenLayoutRound(int value) {
814     screenLayout2 = (byte) ((screenLayout2 & ~SCREENLAYOUT_ROUND_MASK) | value);
815   }
816 
817   public int uiMode;
818 
uiModeType()819   public final int uiModeType() {
820     return uiMode & UI_MODE_TYPE_MASK;
821   }
822 
uiModeType(int value)823   public final void uiModeType(int value) {
824     uiMode = (uiMode & ~UI_MODE_TYPE_MASK) | value;
825   }
826 
uiModeNight()827   public final int uiModeNight() {
828     return uiMode & UI_MODE_NIGHT_MASK;
829   }
830 
uiModeNight(int value)831   public final void uiModeNight(int value) {
832     uiMode = (uiMode & ~UI_MODE_NIGHT_MASK) | value;
833   }
834 
835   public int smallestScreenWidthDp;
836   public int screenWidthDp;
837   public int screenHeightDp;
838 
839   /** The ISO-15924 short name for the script corresponding to this configuration. */
840   @SuppressWarnings("mutable")
841   public final byte[] localeScript;
842 
843   /** A single BCP-47 variant subtag. */
844   @SuppressWarnings("mutable")
845   public final byte[] localeVariant;
846 
847   /** An extension to {@link #screenLayout}. Contains round/notround qualifier. */
848   public byte screenLayout2; // Contains round/notround qualifier.
849 
850   public byte colorMode; // Wide-gamut, HDR, etc.
851   public short screenConfigPad2; // Reserved padding.
852 
853   /** Any remaining bytes in this resource configuration that are unaccounted for. */
854   @SuppressWarnings("mutable")
855   public byte[] unknown;
856 
857   /**
858    * // An extension of screenConfig. union { struct { uint8_t screenLayout2; // Contains
859    * round/notround qualifier. uint8_t screenConfigPad1; // Reserved padding. uint16_t
860    * screenConfigPad2; // Reserved padding. }; uint32_t screenConfig2; };
861    */
screenConfig2()862   private int screenConfig2() {
863     return ((screenLayout2 & 0xff) << 24)
864         | ((colorMode * 0xff) << 16)
865         | (screenConfigPad2 & 0xffff);
866   }
867 
868   // If false and localeScript is set, it means that the script of the locale
869   // was explicitly provided.
870   //
871   // If true, it means that localeScript was automatically computed.
872   // localeScript may still not be set in this case, which means that we
873   // tried but could not compute a script.
874   boolean localeScriptWasComputed;
875 
876   // The value of BCP 47 Unicode extension for key 'nu' (numbering system).
877   // Varies in length from 3 to 8 chars. Zero-filled value.
878   byte[] localeNumberingSystem = new byte[8];
879 
880   // --------------------------------------------------------------------
881   // --------------------------------------------------------------------
882   // --------------------------------------------------------------------
883 
884   //  void copyFromDeviceNoSwap(final ResTable_config o) {
885   //    final int size = dtohl(o.size);
886   //    if (size >= sizeof(ResTable_config)) {
887   //        *this = o;
888   //    } else {
889   //      memcpy(this, &o, size);
890   //      memset(((uint8_t*)this)+size, 0, sizeof(ResTable_config)-size);
891   //    }
892   //  }
893 
894   @Nonnull
unpackLanguageOrRegion(byte[] value, int base)895   private String unpackLanguageOrRegion(byte[] value, int base) {
896     Preconditions.checkState(value.length == 2, "Language or country value must be 2 bytes.");
897     if (value[0] == 0 && value[1] == 0) {
898       return "";
899     }
900     if (isTruthy(UnsignedBytes.toInt(value[0]) & 0x80)) {
901       byte[] result = new byte[3];
902       result[0] = (byte) (base + (value[1] & 0x1F));
903       result[1] = (byte) (base + ((value[1] & 0xE0) >>> 5) + ((value[0] & 0x03) << 3));
904       result[2] = (byte) (base + ((value[0] & 0x7C) >>> 2));
905       return new String(result, US_ASCII);
906     }
907     return new String(value, US_ASCII);
908   }
909 
packLanguageOrRegion(final String in, final byte base, final byte[] out)910   /* static */ void packLanguageOrRegion(final String in, final byte base, final byte[] out) {
911     if (in == null) {
912       out[0] = 0;
913       out[1] = 0;
914     } else if (in.length() < 3 || in.charAt(2) == 0 || in.charAt(2) == '-') {
915       out[0] = (byte) in.charAt(0);
916       out[1] = (byte) in.charAt(1);
917     } else {
918       byte first = (byte) ((in.charAt(0) - base) & 0x007f);
919       byte second = (byte) ((in.charAt(1) - base) & 0x007f);
920       byte third = (byte) ((in.charAt(2) - base) & 0x007f);
921 
922       out[0] = (byte) (0x80 | (third << 2) | (second >> 3));
923       out[1] = (byte) ((second << 5) | first);
924     }
925   }
926 
packLanguage(final String language)927   public void packLanguage(final String language) {
928     packLanguageOrRegion(language, (byte) 'a', this.language);
929   }
930 
packRegion(final String region)931   public void packRegion(final String region) {
932     packLanguageOrRegion(region, (byte) '0', this.country);
933   }
934 
935   @Nonnull
unpackLanguage()936   private String unpackLanguage() {
937     return unpackLanguageOrRegion(language, 0x61);
938   }
939 
unpackRegion()940   private String unpackRegion() {
941     return unpackLanguageOrRegion(country, 0x30);
942   }
943 
944   //  void copyFromDtoH(final ResTable_config o) {
945   //    copyFromDeviceNoSwap(o);
946   //    size = sizeof(ResTable_config);
947   //    mcc = dtohs(mcc);
948   //    mnc = dtohs(mnc);
949   //    density = dtohs(density);
950   //    screenWidth = dtohs(screenWidth);
951   //    screenHeight = dtohs(screenHeight);
952   //    sdkVersion = dtohs(sdkVersion);
953   //    minorVersion = dtohs(minorVersion);
954   //    smallestScreenWidthDp = dtohs(smallestScreenWidthDp);
955   //    screenWidthDp = dtohs(screenWidthDp);
956   //    screenHeightDp = dtohs(screenHeightDp);
957   //  }
958 
959   //  void ResTable_config::copyFromDtoH(const ResTable_config& o) {
fromDtoH(final ResTable_config o)960   static ResTable_config fromDtoH(final ResTable_config o) {
961     return new ResTable_config(
962         0 /*sizeof(ResTable_config)*/,
963         dtohs((short) o.mcc) & 0xFFFF,
964         dtohs((short) o.mnc) & 0xFFFF,
965         o.language,
966         o.country,
967         o.orientation,
968         o.touchscreen,
969         dtohl(o.density),
970         o.keyboard,
971         o.navigation,
972         o.inputFlags,
973         dtohs((short) o.screenWidth) & 0xFFFF,
974         dtohs((short) o.screenHeight) & 0xFFFF,
975         dtohs((short) o.sdkVersion) & 0xFFFF,
976         dtohs((short) o.minorVersion) & 0xFFFF,
977         o.screenLayout,
978         o.uiMode,
979         dtohs((short) o.smallestScreenWidthDp) & 0xFFFF,
980         dtohs((short) o.screenWidthDp) & 0xFFFF,
981         dtohs((short) o.screenHeightDp) & 0xFFFF,
982         o.localeScript,
983         o.localeVariant,
984         o.screenLayout2,
985         o.colorMode,
986         o.screenConfigPad2,
987         o.unknown);
988   }
989 
swapHtoD()990   void swapHtoD() {
991     //    size = htodl(size);
992     //    mcc = htods(mcc);
993     //    mnc = htods(mnc);
994     //    density = htods(density);
995     //    screenWidth = htods(screenWidth);
996     //    screenHeight = htods(screenHeight);
997     //    sdkVersion = htods(sdkVersion);
998     //    minorVersion = htods(minorVersion);
999     //    smallestScreenWidthDp = htods(smallestScreenWidthDp);
1000     //    screenWidthDp = htods(screenWidthDp);
1001     //    screenHeightDp = htods(screenHeightDp);
1002   }
1003 
compareLocales(final ResTable_config l, final ResTable_config r)1004   static final int compareLocales(final ResTable_config l, final ResTable_config r) {
1005     if (l.locale() != r.locale()) {
1006       // NOTE: This is the old behaviour with respect to comparison orders.
1007       // The diff value here doesn't make much sense (given our bit packing scheme)
1008       // but it's stable, and that's all we need.
1009       return (l.locale() > r.locale()) ? 1 : -1;
1010     }
1011 
1012     // The language & region are equal, so compare the scripts, variants and
1013     // numbering systms in this order. Comparison of variants and numbering
1014     // systems should happen very infrequently (if at all.)
1015     // The comparison code relies on memcmp low-level optimizations that make it
1016     // more efficient than strncmp.
1017     final byte emptyScript[] = {'\0', '\0', '\0', '\0'};
1018     final byte[] lScript = l.localeScriptWasComputed ? emptyScript : l.localeScript;
1019     final byte[] rScript = r.localeScriptWasComputed ? emptyScript : r.localeScript;
1020     //    int script = memcmp(lScript, rScript);
1021     //    if (script) {
1022     //      return script;
1023     //    }
1024     int d = arrayCompare(lScript, rScript);
1025     if (d != 0) return d;
1026 
1027     int variant = arrayCompare(l.localeVariant, r.localeVariant);
1028     if (isTruthy(variant)) {
1029       return variant;
1030     }
1031 
1032     return arrayCompare(l.localeNumberingSystem, r.localeNumberingSystem);
1033   }
1034 
arrayCompare(byte[] l, byte[] r)1035   private static int arrayCompare(byte[] l, byte[] r) {
1036     for (int i = 0; i < l.length; i++) {
1037       byte l0 = l[i];
1038       byte r0 = r[i];
1039       int d = l0 - r0;
1040       if (d != 0) return d;
1041     }
1042     return 0;
1043   }
1044 
1045   // Flags indicating a set of config values.  These flag constants must
1046   // match the corresponding ones in android.content.pm.ActivityInfo and
1047   // attrs_manifest.xml.
1048   private static final int CONFIG_MCC = AConfiguration.ACONFIGURATION_MCC;
1049   private static final int CONFIG_MNC = AConfiguration.ACONFIGURATION_MNC;
1050   private static final int CONFIG_LOCALE = AConfiguration.ACONFIGURATION_LOCALE;
1051   private static final int CONFIG_TOUCHSCREEN = AConfiguration.ACONFIGURATION_TOUCHSCREEN;
1052   private static final int CONFIG_KEYBOARD = AConfiguration.ACONFIGURATION_KEYBOARD;
1053   private static final int CONFIG_KEYBOARD_HIDDEN = AConfiguration.ACONFIGURATION_KEYBOARD_HIDDEN;
1054   private static final int CONFIG_NAVIGATION = AConfiguration.ACONFIGURATION_NAVIGATION;
1055   private static final int CONFIG_ORIENTATION = AConfiguration.ACONFIGURATION_ORIENTATION;
1056   private static final int CONFIG_DENSITY = AConfiguration.ACONFIGURATION_DENSITY;
1057   private static final int CONFIG_SCREEN_SIZE = AConfiguration.ACONFIGURATION_SCREEN_SIZE;
1058   private static final int CONFIG_SMALLEST_SCREEN_SIZE =
1059       AConfiguration.ACONFIGURATION_SMALLEST_SCREEN_SIZE;
1060   private static final int CONFIG_VERSION = AConfiguration.ACONFIGURATION_VERSION;
1061   private static final int CONFIG_SCREEN_LAYOUT = AConfiguration.ACONFIGURATION_SCREEN_LAYOUT;
1062   private static final int CONFIG_UI_MODE = AConfiguration.ACONFIGURATION_UI_MODE;
1063   private static final int CONFIG_LAYOUTDIR = AConfiguration.ACONFIGURATION_LAYOUTDIR;
1064   private static final int CONFIG_SCREEN_ROUND = AConfiguration.ACONFIGURATION_SCREEN_ROUND;
1065   private static final int CONFIG_COLOR_MODE = AConfiguration.ACONFIGURATION_COLOR_MODE;
1066 
1067   // Compare two configuration, returning CONFIG_* flags set for each value
1068   // that is different.
diff(final ResTable_config o)1069   int diff(final ResTable_config o) {
1070     int diffs = 0;
1071     if (mcc != o.mcc) diffs |= CONFIG_MCC;
1072     if (mnc != o.mnc) diffs |= CONFIG_MNC;
1073     if (orientation != o.orientation) diffs |= CONFIG_ORIENTATION;
1074     if (density != o.density) diffs |= CONFIG_DENSITY;
1075     if (touchscreen != o.touchscreen) diffs |= CONFIG_TOUCHSCREEN;
1076     if (((inputFlags ^ o.inputFlags) & (MASK_KEYSHIDDEN | MASK_NAVHIDDEN)) != 0)
1077       diffs |= CONFIG_KEYBOARD_HIDDEN;
1078     if (keyboard != o.keyboard) diffs |= CONFIG_KEYBOARD;
1079     if (navigation != o.navigation) diffs |= CONFIG_NAVIGATION;
1080     if (screenSize() != o.screenSize()) diffs |= CONFIG_SCREEN_SIZE;
1081     if (version() != o.version()) diffs |= CONFIG_VERSION;
1082     if ((screenLayout & MASK_LAYOUTDIR) != (o.screenLayout & MASK_LAYOUTDIR))
1083       diffs |= CONFIG_LAYOUTDIR;
1084     if ((screenLayout & ~MASK_LAYOUTDIR) != (o.screenLayout & ~MASK_LAYOUTDIR))
1085       diffs |= CONFIG_SCREEN_LAYOUT;
1086     if ((screenLayout2 & MASK_SCREENROUND) != (o.screenLayout2 & MASK_SCREENROUND))
1087       diffs |= CONFIG_SCREEN_ROUND;
1088     if ((colorMode & MASK_WIDE_COLOR_GAMUT) != (o.colorMode & MASK_WIDE_COLOR_GAMUT))
1089       diffs |= CONFIG_COLOR_MODE;
1090     if ((colorMode & MASK_HDR) != (o.colorMode & MASK_HDR)) diffs |= CONFIG_COLOR_MODE;
1091     if (uiMode != o.uiMode) diffs |= CONFIG_UI_MODE;
1092     if (smallestScreenWidthDp != o.smallestScreenWidthDp) diffs |= CONFIG_SMALLEST_SCREEN_SIZE;
1093     if (screenSizeDp() != o.screenSizeDp()) diffs |= CONFIG_SCREEN_SIZE;
1094 
1095     int diff = compareLocales(this, o);
1096     if (isTruthy(diff)) diffs |= CONFIG_LOCALE;
1097 
1098     return diffs;
1099   }
1100 
1101   // There isn't a well specified "importance" order between variants and
1102   // scripts. We can't easily tell whether, say "en-Latn-US" is more or less
1103   // specific than "en-US-POSIX".
1104   //
1105   // We therefore arbitrarily decide to give priority to variants over
1106   // scripts since it seems more useful to do so. We will consider
1107   // "en-US-POSIX" to be more specific than "en-Latn-US".
1108   //
1109   // Unicode extension keywords are considered to be less important than
1110   // scripts and variants.
getImportanceScoreOfLocale()1111   int getImportanceScoreOfLocale() {
1112     return (isTruthy(localeVariant[0]) ? 4 : 0)
1113         + (isTruthy(localeScript[0]) && !localeScriptWasComputed ? 2 : 0)
1114         + (isTruthy(localeNumberingSystem[0]) ? 1 : 0);
1115   }
1116 
compare(final ResTable_config o)1117   int compare(final ResTable_config o) {
1118     if (imsi() != o.imsi()) {
1119       return (imsi() > o.imsi()) ? 1 : -1;
1120     }
1121 
1122     int diff = compareLocales(this, o);
1123     if (diff < 0) {
1124       return -1;
1125     }
1126     if (diff > 0) {
1127       return 1;
1128     }
1129 
1130     if (screenType() != o.screenType()) {
1131       return (screenType() > o.screenType()) ? 1 : -1;
1132     }
1133     if (input() != o.input()) {
1134       return (input() > o.input()) ? 1 : -1;
1135     }
1136     if (screenSize() != o.screenSize()) {
1137       return (screenSize() > o.screenSize()) ? 1 : -1;
1138     }
1139     if (version() != o.version()) {
1140       return (version() > o.version()) ? 1 : -1;
1141     }
1142     if (screenLayout != o.screenLayout) {
1143       return (screenLayout > o.screenLayout) ? 1 : -1;
1144     }
1145     if (screenLayout2 != o.screenLayout2) {
1146       return (screenLayout2 > o.screenLayout2) ? 1 : -1;
1147     }
1148     if (colorMode != o.colorMode) {
1149       return (colorMode > o.colorMode) ? 1 : -1;
1150     }
1151     if (uiMode != o.uiMode) {
1152       return (uiMode > o.uiMode) ? 1 : -1;
1153     }
1154     if (smallestScreenWidthDp != o.smallestScreenWidthDp) {
1155       return (smallestScreenWidthDp > o.smallestScreenWidthDp) ? 1 : -1;
1156     }
1157     if (screenSizeDp() != o.screenSizeDp()) {
1158       return (screenSizeDp() > o.screenSizeDp()) ? 1 : -1;
1159     }
1160     return 0;
1161   }
1162 
1163   /** Returns true if this is the default "any" configuration. */
isDefault()1164   public final boolean isDefault() {
1165     return mcc == 0
1166         && mnc == 0
1167         && isZeroes(language)
1168         && isZeroes(country)
1169         && orientation == 0
1170         && touchscreen == 0
1171         && density == 0
1172         && keyboard == 0
1173         && navigation == 0
1174         && inputFlags == 0
1175         && screenWidth == 0
1176         && screenHeight == 0
1177         && sdkVersion == 0
1178         && minorVersion == 0
1179         && screenLayout == 0
1180         && uiMode == 0
1181         && smallestScreenWidthDp == 0
1182         && screenWidthDp == 0
1183         && screenHeightDp == 0
1184         && isZeroes(localeScript)
1185         && isZeroes(localeVariant)
1186         && screenLayout2 == 0
1187         && colorMode == 0;
1188   }
1189 
isZeroes(byte[] bytes1)1190   private boolean isZeroes(byte[] bytes1) {
1191     for (byte b : bytes1) {
1192       if (b != 0) {
1193         return false;
1194       }
1195     }
1196     return true;
1197   }
1198 
1199   @Override
toString()1200   public final String toString() {
1201     if (isDefault()) { // Prevent the default configuration from returning the empty string
1202       return "default";
1203     }
1204     Collection<String> parts = toStringParts().values();
1205     parts.removeAll(Collections.singleton(""));
1206     return Joiner.on('-').join(parts);
1207   }
1208 
1209   /**
1210    * Returns a map of the configuration parts for {@link #toString}.
1211    *
1212    * <p>If a configuration part is not defined for this {@link ResTable_config}, its value will be
1213    * the empty string.
1214    */
toStringParts()1215   public final Map<Type, String> toStringParts() {
1216     Map<Type, String> result = new LinkedHashMap<>(); // Preserve order for #toString().
1217     result.put(Type.MCC, mcc != 0 ? "mcc" + mcc : "");
1218     result.put(Type.MNC, mnc != 0 ? "mnc" + mnc : "");
1219     result.put(Type.LANGUAGE_STRING, languageString());
1220     result.put(Type.LOCALE_SCRIPT_STRING, localeScriptString());
1221     result.put(Type.REGION_STRING, !regionString().isEmpty() ? "r" + regionString() : "");
1222     result.put(Type.LOCALE_VARIANT_STRING, localeVariantString());
1223     result.put(
1224         Type.SCREEN_LAYOUT_DIRECTION,
1225         getOrDefault(SCREENLAYOUT_LAYOUTDIR_VALUES, screenLayoutDirection(), ""));
1226     result.put(
1227         Type.SMALLEST_SCREEN_WIDTH_DP,
1228         smallestScreenWidthDp != 0 ? "sw" + smallestScreenWidthDp + "dp" : "");
1229     result.put(Type.SCREEN_WIDTH_DP, screenWidthDp != 0 ? "w" + screenWidthDp + "dp" : "");
1230     result.put(Type.SCREEN_HEIGHT_DP, screenHeightDp != 0 ? "h" + screenHeightDp + "dp" : "");
1231     result.put(
1232         Type.SCREEN_LAYOUT_SIZE, getOrDefault(SCREENLAYOUT_SIZE_VALUES, screenLayoutSize(), ""));
1233     result.put(
1234         Type.SCREEN_LAYOUT_LONG, getOrDefault(SCREENLAYOUT_LONG_VALUES, screenLayoutLong(), ""));
1235     result.put(
1236         Type.SCREEN_LAYOUT_ROUND, getOrDefault(SCREENLAYOUT_ROUND_VALUES, screenLayoutRound(), ""));
1237     result.put(Type.COLOR_MODE_HDR, getOrDefault(COLOR_MODE_HDR_VALUES, colorModeHdr(), ""));
1238     result.put(
1239         Type.COLOR_MODE_WIDE_COLOR_GAMUT,
1240         getOrDefault(COLOR_MODE_WIDE_COLOR_GAMUT_VALUES, colorModeWideColorGamut(), ""));
1241     result.put(Type.ORIENTATION, getOrDefault(ORIENTATION_VALUES, orientation, ""));
1242     result.put(Type.UI_MODE_TYPE, getOrDefault(UI_MODE_TYPE_VALUES, uiModeType(), ""));
1243     result.put(Type.UI_MODE_NIGHT, getOrDefault(UI_MODE_NIGHT_VALUES, uiModeNight(), ""));
1244     result.put(Type.DENSITY_DPI, getOrDefault(DENSITY_DPI_VALUES, density, density + "dpi"));
1245     result.put(Type.TOUCHSCREEN, getOrDefault(TOUCHSCREEN_VALUES, touchscreen, ""));
1246     result.put(Type.KEYBOARD_HIDDEN, getOrDefault(KEYBOARDHIDDEN_VALUES, keyboardHidden(), ""));
1247     result.put(Type.KEYBOARD, getOrDefault(KEYBOARD_VALUES, keyboard, ""));
1248     result.put(
1249         Type.NAVIGATION_HIDDEN, getOrDefault(NAVIGATIONHIDDEN_VALUES, navigationHidden(), ""));
1250     result.put(Type.NAVIGATION, getOrDefault(NAVIGATION_VALUES, navigation, ""));
1251     result.put(
1252         Type.SCREEN_SIZE,
1253         screenWidth != 0 || screenHeight != 0 ? screenWidth + "x" + screenHeight : "");
1254 
1255     String sdkVersion = "";
1256     if (this.sdkVersion != 0) {
1257       sdkVersion = "v" + this.sdkVersion;
1258       if (minorVersion != 0) {
1259         sdkVersion += "." + minorVersion;
1260       }
1261     }
1262     result.put(Type.SDK_VERSION, sdkVersion);
1263     return result;
1264   }
1265 
getOrDefault(Map<K, V> map, K key, V defaultValue)1266   private <K, V> V getOrDefault(Map<K, V> map, K key, V defaultValue) {
1267     // TODO(acornwall): Remove this when Java 8's Map#getOrDefault is available.
1268     // Null is not returned, even if the map contains a key whose value is null. This is intended.
1269     V value = map.get(key);
1270     return value != null ? value : defaultValue;
1271   }
1272 
1273   // constants for isBetterThan...
1274   public static final int MASK_LAYOUTDIR = SCREENLAYOUT_LAYOUTDIR_MASK;
1275   static final int MASK_SCREENSIZE = SCREENLAYOUT_SIZE_MASK;
1276 
isBetterThan(ResTable_config o, ResTable_config requested)1277   public boolean isBetterThan(ResTable_config o, ResTable_config requested) {
1278     if (isTruthy(requested)) {
1279       if (isTruthy(imsi()) || isTruthy(o.imsi())) {
1280         if ((mcc != o.mcc) && isTruthy(requested.mcc)) {
1281           return isTruthy(mcc);
1282         }
1283 
1284         if ((mnc != o.mnc) && isTruthy(requested.mnc)) {
1285           return isTruthy(mnc);
1286         }
1287       }
1288 
1289       if (isLocaleBetterThan(o, requested)) {
1290         return true;
1291       }
1292 
1293       if (isTruthy(screenLayout) || isTruthy(o.screenLayout)) {
1294         if (isTruthy((screenLayout ^ o.screenLayout) & MASK_LAYOUTDIR)
1295             && isTruthy(requested.screenLayout & MASK_LAYOUTDIR)) {
1296           int myLayoutDir = screenLayout & MASK_LAYOUTDIR;
1297           int oLayoutDir = o.screenLayout & MASK_LAYOUTDIR;
1298           return (myLayoutDir > oLayoutDir);
1299         }
1300       }
1301 
1302       if (isTruthy(smallestScreenWidthDp) || isTruthy(o.smallestScreenWidthDp)) {
1303         // The configuration closest to the actual size is best.
1304         // We assume that larger configs have already been filtered
1305         // out at this point.  That means we just want the largest one.
1306         if (smallestScreenWidthDp != o.smallestScreenWidthDp) {
1307           return smallestScreenWidthDp > o.smallestScreenWidthDp;
1308         }
1309       }
1310 
1311       if (isTruthy(screenSizeDp()) || isTruthy(o.screenSizeDp())) {
1312         // "Better" is based on the sum of the difference between both
1313         // width and height from the requested dimensions.  We are
1314         // assuming the invalid configs (with smaller dimens) have
1315         // already been filtered.  Note that if a particular dimension
1316         // is unspecified, we will end up with a large value (the
1317         // difference between 0 and the requested dimension), which is
1318         // good since we will prefer a config that has specified a
1319         // dimension value.
1320         int myDelta = 0, otherDelta = 0;
1321         if (isTruthy(requested.screenWidthDp)) {
1322           myDelta += requested.screenWidthDp - screenWidthDp;
1323           otherDelta += requested.screenWidthDp - o.screenWidthDp;
1324         }
1325         if (isTruthy(requested.screenHeightDp)) {
1326           myDelta += requested.screenHeightDp - screenHeightDp;
1327           otherDelta += requested.screenHeightDp - o.screenHeightDp;
1328         }
1329 
1330         if (myDelta != otherDelta) {
1331           return myDelta < otherDelta;
1332         }
1333       }
1334 
1335       if (isTruthy(screenLayout) || isTruthy(o.screenLayout)) {
1336         if (isTruthy((screenLayout ^ o.screenLayout) & MASK_SCREENSIZE)
1337             && isTruthy(requested.screenLayout & MASK_SCREENSIZE)) {
1338           // A little backwards compatibility here: undefined is
1339           // considered equivalent to normal.  But only if the
1340           // requested size is at least normal; otherwise, small
1341           // is better than the default.
1342           int mySL = (screenLayout & MASK_SCREENSIZE);
1343           int oSL = (o.screenLayout & MASK_SCREENSIZE);
1344           int fixedMySL = mySL;
1345           int fixedOSL = oSL;
1346           if ((requested.screenLayout & MASK_SCREENSIZE) >= SCREENSIZE_NORMAL) {
1347             if (fixedMySL == 0) fixedMySL = SCREENSIZE_NORMAL;
1348             if (fixedOSL == 0) fixedOSL = SCREENSIZE_NORMAL;
1349           }
1350           // For screen size, the best match is the one that is
1351           // closest to the requested screen size, but not over
1352           // (the not over part is dealt with in match() below).
1353           if (fixedMySL == fixedOSL) {
1354             // If the two are the same, but 'this' is actually
1355             // undefined, then the other is really a better match.
1356             if (mySL == 0) return false;
1357             return true;
1358           }
1359           if (fixedMySL != fixedOSL) {
1360             return fixedMySL > fixedOSL;
1361           }
1362         }
1363         if (((screenLayout ^ o.screenLayout) & MASK_SCREENLONG) != 0
1364             && isTruthy(requested.screenLayout & MASK_SCREENLONG)) {
1365           return isTruthy(screenLayout & MASK_SCREENLONG);
1366         }
1367       }
1368 
1369       if (isTruthy(screenLayout2) || isTruthy(o.screenLayout2)) {
1370         if (((screenLayout2 ^ o.screenLayout2) & MASK_SCREENROUND) != 0
1371             && isTruthy(requested.screenLayout2 & MASK_SCREENROUND)) {
1372           return isTruthy(screenLayout2 & MASK_SCREENROUND);
1373         }
1374       }
1375 
1376       if (isTruthy(colorMode) || isTruthy(o.colorMode)) {
1377         if (((colorMode ^ o.colorMode) & MASK_WIDE_COLOR_GAMUT) != 0
1378             && isTruthy((requested.colorMode & MASK_WIDE_COLOR_GAMUT))) {
1379           return isTruthy(colorMode & MASK_WIDE_COLOR_GAMUT);
1380         }
1381         if (((colorMode ^ o.colorMode) & MASK_HDR) != 0
1382             && isTruthy((requested.colorMode & MASK_HDR))) {
1383           return isTruthy(colorMode & MASK_HDR);
1384         }
1385       }
1386 
1387       if ((orientation != o.orientation) && isTruthy(requested.orientation)) {
1388         return isTruthy(orientation);
1389       }
1390 
1391       if (isTruthy(uiMode) || isTruthy(o.uiMode)) {
1392         if (((uiMode ^ o.uiMode) & MASK_UI_MODE_TYPE) != 0
1393             && isTruthy(requested.uiMode & MASK_UI_MODE_TYPE)) {
1394           return isTruthy(uiMode & MASK_UI_MODE_TYPE);
1395         }
1396         if (((uiMode ^ o.uiMode) & MASK_UI_MODE_NIGHT) != 0
1397             && isTruthy(requested.uiMode & MASK_UI_MODE_NIGHT)) {
1398           return isTruthy(uiMode & MASK_UI_MODE_NIGHT);
1399         }
1400       }
1401 
1402       if (isTruthy(screenType()) || isTruthy(o.screenType())) {
1403         if (density != o.density) {
1404           // Use the system default density (DENSITY_MEDIUM, 160dpi) if none specified.
1405           final int thisDensity = isTruthy(density) ? density : DENSITY_MEDIUM;
1406           final int otherDensity = isTruthy(o.density) ? o.density : DENSITY_MEDIUM;
1407 
1408           // We always prefer DENSITY_ANY over scaling a density bucket.
1409           if (thisDensity == DENSITY_ANY) {
1410             return true;
1411           } else if (otherDensity == DENSITY_ANY) {
1412             return false;
1413           }
1414 
1415           int requestedDensity = requested.density;
1416           if (requested.density == 0 || requested.density == DENSITY_ANY) {
1417             requestedDensity = DENSITY_MEDIUM;
1418           }
1419 
1420           // DENSITY_ANY is now dealt with. We should look to
1421           // pick a density bucket and potentially scale it.
1422           // Any density is potentially useful
1423           // because the system will scale it.  Scaling down
1424           // is generally better than scaling up.
1425           int h = thisDensity;
1426           int l = otherDensity;
1427           boolean bImBigger = true;
1428           if (l > h) {
1429             int t = h;
1430             h = l;
1431             l = t;
1432             bImBigger = false;
1433           }
1434 
1435           if (requestedDensity >= h) {
1436             // requested value higher than both l and h, give h
1437             return bImBigger;
1438           }
1439           if (l >= requestedDensity) {
1440             // requested value lower than both l and h, give l
1441             return !bImBigger;
1442           }
1443           // saying that scaling down is 2x better than up
1444           if (((2 * l) - requestedDensity) * h > requestedDensity * requestedDensity) {
1445             return !bImBigger;
1446           } else {
1447             return bImBigger;
1448           }
1449         }
1450 
1451         if ((touchscreen != o.touchscreen) && isTruthy(requested.touchscreen)) {
1452           return isTruthy(touchscreen);
1453         }
1454       }
1455 
1456       if (isTruthy(input()) || isTruthy(o.input())) {
1457         final int keysHidden = inputFlags & MASK_KEYSHIDDEN;
1458         final int oKeysHidden = o.inputFlags & MASK_KEYSHIDDEN;
1459         if (keysHidden != oKeysHidden) {
1460           final int reqKeysHidden = requested.inputFlags & MASK_KEYSHIDDEN;
1461           if (isTruthy(reqKeysHidden)) {
1462 
1463             if (keysHidden == 0) return false;
1464             if (oKeysHidden == 0) return true;
1465             // For compatibility, we count KEYSHIDDEN_NO as being
1466             // the same as KEYSHIDDEN_SOFT.  Here we disambiguate
1467             // these by making an exact match more specific.
1468             if (reqKeysHidden == keysHidden) return true;
1469             if (reqKeysHidden == oKeysHidden) return false;
1470           }
1471         }
1472 
1473         final int navHidden = inputFlags & MASK_NAVHIDDEN;
1474         final int oNavHidden = o.inputFlags & MASK_NAVHIDDEN;
1475         if (navHidden != oNavHidden) {
1476           final int reqNavHidden = requested.inputFlags & MASK_NAVHIDDEN;
1477           if (isTruthy(reqNavHidden)) {
1478 
1479             if (navHidden == 0) return false;
1480             if (oNavHidden == 0) return true;
1481           }
1482         }
1483 
1484         if ((keyboard != o.keyboard) && isTruthy(requested.keyboard)) {
1485           return isTruthy(keyboard);
1486         }
1487 
1488         if ((navigation != o.navigation) && isTruthy(requested.navigation)) {
1489           return isTruthy(navigation);
1490         }
1491       }
1492 
1493       if (isTruthy(screenSize()) || isTruthy(o.screenSize())) {
1494         // "Better" is based on the sum of the difference between both
1495         // width and height from the requested dimensions.  We are
1496         // assuming the invalid configs (with smaller sizes) have
1497         // already been filtered.  Note that if a particular dimension
1498         // is unspecified, we will end up with a large value (the
1499         // difference between 0 and the requested dimension), which is
1500         // good since we will prefer a config that has specified a
1501         // size value.
1502         int myDelta = 0, otherDelta = 0;
1503         if (isTruthy(requested.screenWidth)) {
1504           myDelta += requested.screenWidth - screenWidth;
1505           otherDelta += requested.screenWidth - o.screenWidth;
1506         }
1507         if (isTruthy(requested.screenHeight)) {
1508           myDelta += requested.screenHeight - screenHeight;
1509           otherDelta += requested.screenHeight - o.screenHeight;
1510         }
1511         if (myDelta != otherDelta) {
1512           return myDelta < otherDelta;
1513         }
1514       }
1515 
1516       if (isTruthy(version()) || isTruthy(o.version())) {
1517         if ((sdkVersion != o.sdkVersion) && isTruthy(requested.sdkVersion)) {
1518           return (sdkVersion > o.sdkVersion);
1519         }
1520 
1521         if ((minorVersion != o.minorVersion) && isTruthy(requested.minorVersion)) {
1522           return isTruthy(minorVersion);
1523         }
1524       }
1525 
1526       return false;
1527     }
1528     return isMoreSpecificThan(o);
1529   }
1530 
1531   /*
1532     boolean match(final ResTable_config settings) {
1533       System.out.println(this + ".match(" + settings + ")");
1534       boolean result = match_(settings);
1535       System.out.println("    -> " + result);
1536       return result;
1537     }
1538   */
1539 
match(final ResTable_config settings)1540   public boolean match(final ResTable_config settings) {
1541     if (imsi() != 0) {
1542       if (mcc != 0 && mcc != settings.mcc) {
1543         return false;
1544       }
1545       if (mnc != 0 && mnc != settings.mnc) {
1546         return false;
1547       }
1548     }
1549     if (locale() != 0) {
1550       // Don't consider country and variants when deciding matches.
1551       // (Theoretically, the variant can also affect the script. For
1552       // example, "ar-alalc97" probably implies the Latin script, but since
1553       // CLDR doesn't support getting likely scripts for that, we'll assume
1554       // the variant doesn't change the script.)
1555       //
1556       // If two configs differ only in their country and variant,
1557       // they can be weeded out in the isMoreSpecificThan test.
1558       if (!langsAreEquivalent(language, settings.language)) {
1559         return false;
1560       }
1561 
1562       // For backward compatibility and supporting private-use locales, we
1563       // fall back to old behavior if we couldn't determine the script for
1564       // either of the desired locale or the provided locale. But if we could determine
1565       // the scripts, they should be the same for the locales to match.
1566       boolean countriesMustMatch = false;
1567       byte[] computed_script = new byte[4];
1568       byte[] script = null;
1569       if (settings.localeScript[0] == '\0') { // could not determine the request's script
1570         countriesMustMatch = true;
1571       } else {
1572         if (localeScript[0] == '\0' && !localeScriptWasComputed) {
1573           // script was not provided or computed, so we try to compute it
1574           localeDataComputeScript(computed_script, language, country);
1575           if (computed_script[0] == '\0') { // we could not compute the script
1576             countriesMustMatch = true;
1577           } else {
1578             script = computed_script;
1579           }
1580         } else { // script was provided, so just use it
1581           script = localeScript;
1582         }
1583       }
1584 
1585       if (countriesMustMatch) {
1586         if (country[0] != '\0' && !areIdentical(country, settings.country)) {
1587           return false;
1588         }
1589       } else {
1590         if (!Arrays.equals(script, settings.localeScript)) {
1591           return false;
1592         }
1593       }
1594     }
1595 
1596     if (screenConfig() != 0) {
1597       final int layoutDir = screenLayout & MASK_LAYOUTDIR;
1598       final int setLayoutDir = settings.screenLayout & MASK_LAYOUTDIR;
1599       if (layoutDir != 0 && layoutDir != setLayoutDir) {
1600         return false;
1601       }
1602 
1603       final int screenSize = screenLayout & MASK_SCREENSIZE;
1604       final int setScreenSize = settings.screenLayout & MASK_SCREENSIZE;
1605       // Any screen sizes for larger screens than the setting do not
1606       // match.
1607       if (screenSize != 0 && screenSize > setScreenSize) {
1608         return false;
1609       }
1610 
1611       final int screenLong = screenLayout & MASK_SCREENLONG;
1612       final int setScreenLong = settings.screenLayout & MASK_SCREENLONG;
1613       if (screenLong != 0 && screenLong != setScreenLong) {
1614         return false;
1615       }
1616 
1617       final int uiModeType = uiMode & MASK_UI_MODE_TYPE;
1618       final int setUiModeType = settings.uiMode & MASK_UI_MODE_TYPE;
1619       if (uiModeType != 0 && uiModeType != setUiModeType) {
1620         return false;
1621       }
1622 
1623       final int uiModeNight = uiMode & MASK_UI_MODE_NIGHT;
1624       final int setUiModeNight = settings.uiMode & MASK_UI_MODE_NIGHT;
1625       if (uiModeNight != 0 && uiModeNight != setUiModeNight) {
1626         return false;
1627       }
1628 
1629       if (smallestScreenWidthDp != 0 && smallestScreenWidthDp > settings.smallestScreenWidthDp) {
1630         return false;
1631       }
1632     }
1633 
1634     if (screenConfig2() != 0) {
1635       final int screenRound = screenLayout2 & MASK_SCREENROUND;
1636       final int setScreenRound = settings.screenLayout2 & MASK_SCREENROUND;
1637       if (screenRound != 0 && screenRound != setScreenRound) {
1638         return false;
1639       }
1640     }
1641 
1642     final int hdr = colorMode & MASK_HDR;
1643     final int setHdr = settings.colorMode & MASK_HDR;
1644     if (hdr != 0 && hdr != setHdr) {
1645       return false;
1646     }
1647 
1648     final int wideColorGamut = colorMode & MASK_WIDE_COLOR_GAMUT;
1649     final int setWideColorGamut = settings.colorMode & MASK_WIDE_COLOR_GAMUT;
1650     if (wideColorGamut != 0 && wideColorGamut != setWideColorGamut) {
1651       return false;
1652     }
1653 
1654     if (screenSizeDp() != 0) {
1655       if (screenWidthDp != 0 && screenWidthDp > settings.screenWidthDp) {
1656         if (kDebugTableSuperNoisy) {
1657           ALOGI("Filtering out width %d in requested %d", screenWidthDp, settings.screenWidthDp);
1658         }
1659         return false;
1660       }
1661       if (screenHeightDp != 0 && screenHeightDp > settings.screenHeightDp) {
1662         if (kDebugTableSuperNoisy) {
1663           ALOGI("Filtering out height %d in requested %d", screenHeightDp, settings.screenHeightDp);
1664         }
1665         return false;
1666       }
1667     }
1668     if (screenType() != 0) {
1669       if (orientation != 0 && orientation != settings.orientation) {
1670         return false;
1671       }
1672       // density always matches - we can scale it.  See isBetterThan
1673       if (touchscreen != 0 && touchscreen != settings.touchscreen) {
1674         return false;
1675       }
1676     }
1677     if (input() != 0) {
1678       final int keysHidden = inputFlags & MASK_KEYSHIDDEN;
1679       final int setKeysHidden = settings.inputFlags & MASK_KEYSHIDDEN;
1680       if (keysHidden != 0 && keysHidden != setKeysHidden) {
1681         // For compatibility, we count a request for KEYSHIDDEN_NO as also
1682         // matching the more recent KEYSHIDDEN_SOFT.  Basically
1683         // KEYSHIDDEN_NO means there is some kind of keyboard available.
1684         if (kDebugTableSuperNoisy) {
1685           ALOGI("Matching keysHidden: have=%d, config=%d\n", keysHidden, setKeysHidden);
1686         }
1687         if (keysHidden != KEYSHIDDEN_NO || setKeysHidden != KEYSHIDDEN_SOFT) {
1688           if (kDebugTableSuperNoisy) {
1689             ALOGI("No match!");
1690           }
1691           return false;
1692         }
1693       }
1694       final int navHidden = inputFlags & MASK_NAVHIDDEN;
1695       final int setNavHidden = settings.inputFlags & MASK_NAVHIDDEN;
1696       if (navHidden != 0 && navHidden != setNavHidden) {
1697         return false;
1698       }
1699       if (keyboard != 0 && keyboard != settings.keyboard) {
1700         return false;
1701       }
1702       if (navigation != 0 && navigation != settings.navigation) {
1703         return false;
1704       }
1705     }
1706     if (screenSize() != 0) {
1707       if (screenWidth != 0 && screenWidth > settings.screenWidth) {
1708         return false;
1709       }
1710       if (screenHeight != 0 && screenHeight > settings.screenHeight) {
1711         return false;
1712       }
1713     }
1714     if (version() != 0) {
1715       if (sdkVersion != 0 && sdkVersion > settings.sdkVersion) {
1716         return false;
1717       }
1718       if (minorVersion != 0 && minorVersion != settings.minorVersion) {
1719         return false;
1720       }
1721     }
1722     return true;
1723   }
1724 
1725   //  void appendDirLocale(String8& out) const {
1726   //    if (!language[0]) {
1727   //      return;
1728   //    }
1729   //    const bool scriptWasProvided = localeScript[0] != '\0' && !localeScriptWasComputed;
1730   //    if (!scriptWasProvided && !localeVariant[0] && !localeNumberingSystem[0]) {
1731   //      // Legacy format.
1732   //      if (out.size() > 0) {
1733   //        out.append("-");
1734   //      }
1735   //
1736   //      char buf[4];
1737   //      size_t len = unpackLanguage(buf);
1738   //      out.append(buf, len);
1739   //
1740   //      if (country[0]) {
1741   //        out.append("-r");
1742   //        len = unpackRegion(buf);
1743   //        out.append(buf, len);
1744   //      }
1745   //      return;
1746   //    }
1747   //
1748   //    // We are writing the modified BCP 47 tag.
1749   //    // It starts with 'b+' and uses '+' as a separator.
1750   //
1751   //    if (out.size() > 0) {
1752   //      out.append("-");
1753   //    }
1754   //    out.append("b+");
1755   //
1756   //    char buf[4];
1757   //    size_t len = unpackLanguage(buf);
1758   //    out.append(buf, len);
1759   //
1760   //    if (scriptWasProvided) {
1761   //      out.append("+");
1762   //      out.append(localeScript, sizeof(localeScript));
1763   //    }
1764   //
1765   //    if (country[0]) {
1766   //      out.append("+");
1767   //      len = unpackRegion(buf);
1768   //      out.append(buf, len);
1769   //    }
1770   //
1771   //    if (localeVariant[0]) {
1772   //      out.append("+");
1773   //      out.append(localeVariant, strnlen(localeVariant, sizeof(localeVariant)));
1774   //    }
1775   //
1776   //    if (localeNumberingSystem[0]) {
1777   //      out.append("+u+nu+");
1778   //      out.append(localeNumberingSystem,
1779   //                 strnlen(localeNumberingSystem, sizeof(localeNumberingSystem)));
1780   //    }
1781   //  }
1782 
1783   // returns string as return value instead of by mutating first arg
1784   // void ResTable_config::getBcp47Locale(char str[RESTABLE_MAX_LOCALE_LEN], bool canonicalize)
1785   // const {
getBcp47Locale(boolean canonicalize)1786   String getBcp47Locale(boolean canonicalize) {
1787     StringBuilder str = new StringBuilder();
1788 
1789     // This represents the "any" locale value, which has traditionally been
1790     // represented by the empty string.
1791     if (language[0] == '\0' && country[0] == '\0') {
1792       return "";
1793     }
1794 
1795     if (language[0] != '\0') {
1796       if (canonicalize && areIdentical(language, kTagalog)) {
1797         // Replace Tagalog with Filipino if we are canonicalizing
1798         str.setLength(0);
1799         str.append("fil"); // 3-letter code for Filipino
1800       } else {
1801         str.append(unpackLanguage());
1802       }
1803     }
1804 
1805     if (isTruthy(localeScript[0]) && !localeScriptWasComputed) {
1806       if (str.length() > 0) {
1807         str.append('-');
1808       }
1809       for (byte aLocaleScript : localeScript) {
1810         str.append((char) aLocaleScript);
1811       }
1812     }
1813 
1814     if (country[0] != '\0') {
1815       if (str.length() > 0) {
1816         str.append('-');
1817       }
1818       String regionStr = unpackRegion();
1819       str.append(regionStr);
1820     }
1821 
1822     if (isTruthy(localeVariant[0])) {
1823       if (str.length() > 0) {
1824         str.append('-');
1825       }
1826 
1827       for (byte aLocaleScript : localeVariant) {
1828         str.append((char) aLocaleScript);
1829       }
1830     }
1831 
1832     // Add Unicode extension only if at least one other locale component is present
1833     if (localeNumberingSystem[0] != '\0' && str.length() > 0) {
1834       String NU_PREFIX = "-u-nu-";
1835       str.append(NU_PREFIX);
1836       str.append(new String(localeNumberingSystem, UTF_8));
1837     }
1838 
1839     return str.toString();
1840   }
1841 
1842   enum State {
1843     BASE,
1844     UNICODE_EXTENSION,
1845     IGNORE_THE_REST
1846   }
1847 
1848   enum UnicodeState {
1849     /* Initial state after the Unicode singleton is detected. Either a keyword
1850      * or an attribute is expected. */
1851     NO_KEY,
1852     /* Unicode extension key (but not attribute) is expected. Next states:
1853      * NO_KEY, IGNORE_KEY or NUMBERING_SYSTEM. */
1854     EXPECT_KEY,
1855     /* A key is detected, however it is not supported for now. Ignore its
1856      * value. Next states: IGNORE_KEY or NUMBERING_SYSTEM. */
1857     IGNORE_KEY,
1858     /* Numbering system key was detected. Store its value in the configuration
1859      * localeNumberingSystem field. Next state: EXPECT_KEY */
1860     NUMBERING_SYSTEM
1861   }
1862 
1863   static class LocaleParserState {
1864     State parserState;
1865     UnicodeState unicodeState;
1866 
1867     // LocaleParserState(): parserState(BASE), unicodeState(NO_KEY) {}
LocaleParserState()1868     public LocaleParserState() {
1869       this.parserState = State.BASE;
1870       this.unicodeState = UnicodeState.NO_KEY;
1871     }
1872   }
1873 
assignLocaleComponent( ResTable_config config, final String start, int size, LocaleParserState state)1874   static LocaleParserState assignLocaleComponent(
1875       ResTable_config config, final String start, int size, LocaleParserState state) {
1876 
1877     /* It is assumed that this function is not invoked with state.parserState
1878      * set to IGNORE_THE_REST. The condition is checked by setBcp47Locale
1879      * function. */
1880 
1881     if (state.parserState == State.UNICODE_EXTENSION) {
1882       switch (size) {
1883         case 1:
1884           /* Other BCP 47 extensions are not supported at the moment */
1885           state.parserState = State.IGNORE_THE_REST;
1886           break;
1887         case 2:
1888           if (state.unicodeState == UnicodeState.NO_KEY
1889               || state.unicodeState == UnicodeState.EXPECT_KEY) {
1890             /* Analyze Unicode extension key. Currently only 'nu'
1891              * (numbering system) is supported.*/
1892             if ((start.charAt(0) == 'n' || start.charAt(0) == 'N')
1893                 && (start.charAt(1) == 'u' || start.charAt(1) == 'U')) {
1894               state.unicodeState = UnicodeState.NUMBERING_SYSTEM;
1895             } else {
1896               state.unicodeState = UnicodeState.IGNORE_KEY;
1897             }
1898           } else {
1899             /* Keys are not allowed in other state allowed, ignore the rest. */
1900             state.parserState = State.IGNORE_THE_REST;
1901           }
1902           break;
1903         case 3:
1904         case 4:
1905         case 5:
1906         case 6:
1907         case 7:
1908         case 8:
1909           switch (state.unicodeState) {
1910             case NUMBERING_SYSTEM:
1911               /* Accept only the first occurrence of the numbering system. */
1912               if (config.localeNumberingSystem[0] == '\0') {
1913                 for (int i = 0; i < size; ++i) {
1914                   config.localeNumberingSystem[i] = (byte) Character.toLowerCase(start.charAt(i));
1915                 }
1916                 state.unicodeState = UnicodeState.EXPECT_KEY;
1917               } else {
1918                 state.parserState = State.IGNORE_THE_REST;
1919               }
1920               break;
1921             case IGNORE_KEY:
1922               /* Unsupported Unicode keyword. Ignore. */
1923               state.unicodeState = UnicodeState.EXPECT_KEY;
1924               break;
1925             case EXPECT_KEY:
1926               /* A keyword followed by an attribute is not allowed. */
1927               state.parserState = State.IGNORE_THE_REST;
1928               break;
1929             case NO_KEY:
1930               /* Extension attribute. Do nothing. */
1931               break;
1932           }
1933           break;
1934         default:
1935           /* Unexpected field length - ignore the rest and treat as an error */
1936           state.parserState = State.IGNORE_THE_REST;
1937       }
1938       return state;
1939     }
1940 
1941     switch (size) {
1942       case 0:
1943         state.parserState = State.IGNORE_THE_REST;
1944         break;
1945       case 1:
1946         state.parserState =
1947             (start.charAt(0) == 'u' || start.charAt(0) == 'U')
1948                 ? State.UNICODE_EXTENSION
1949                 : State.IGNORE_THE_REST;
1950         break;
1951       case 2:
1952       case 3:
1953         if (isTruthy(config.language[0])) {
1954           config.packRegion(start);
1955         } else {
1956           config.packLanguage(start);
1957         }
1958         break;
1959       case 4:
1960         char start0 = start.charAt(0);
1961         if ('0' <= start0 && start0 <= '9') {
1962           // this is a variant, so fall through
1963         } else {
1964           config.localeScript[0] = (byte) Character.toUpperCase(start0);
1965           for (int i = 1; i < 4; ++i) {
1966             config.localeScript[i] = (byte) Character.toLowerCase(start.charAt(i));
1967           }
1968           break;
1969         }
1970       // fall through
1971       case 5:
1972       case 6:
1973       case 7:
1974       case 8:
1975         for (int i = 0; i < size; ++i) {
1976           config.localeVariant[i] = (byte) Character.toLowerCase(start.charAt(i));
1977         }
1978         break;
1979       default:
1980         state.parserState = State.IGNORE_THE_REST;
1981     }
1982 
1983     return state;
1984   }
1985 
setBcp47Locale(final String in)1986   public void setBcp47Locale(final String in) {
1987     clearLocale();
1988 
1989     int start = 0;
1990     LocaleParserState state = new LocaleParserState();
1991     int separator;
1992     while ((separator = in.indexOf('-', start)) > 0) {
1993       final int size = separator - start;
1994       state = assignLocaleComponent(this, in.substring(start), size, state);
1995       if (state.parserState == State.IGNORE_THE_REST) {
1996 
1997         System.err.println(String.format("Invalid BCP-47 locale string: %s", in));
1998         break;
1999       }
2000 
2001       start = (separator + 1);
2002     }
2003 
2004     if (state.parserState != State.IGNORE_THE_REST) {
2005       final int size = in.length() - start;
2006       assignLocaleComponent(this, in.substring(start), size, state);
2007     }
2008 
2009     localeScriptWasComputed = (localeScript[0] == '\0');
2010     if (localeScriptWasComputed) {
2011       computeScript();
2012     }
2013   }
2014 
clearLocale()2015   void clearLocale() {
2016     //    locale = 0;
2017     clear(language);
2018     clear(country);
2019 
2020     localeScriptWasComputed = false;
2021     clear(localeScript);
2022     clear(localeVariant);
2023   }
2024 
computeScript()2025   void computeScript() {
2026     localeDataComputeScript(localeScript, language, country);
2027   }
2028 
clear(byte[] bytes)2029   private void clear(byte[] bytes) {
2030     for (int i = 0; i < bytes.length; i++) {
2031       bytes[i] = 0;
2032     }
2033   }
2034 
2035   /**
2036    * union { struct { // Mobile country code (from SIM). 0 means "any". uint16_t mcc; // Mobile
2037    * network code (from SIM). 0 means "any". uint16_t mnc; }; uint32_t imsi; };
2038    */
imsi()2039   private int imsi() {
2040     return ((mcc & 0xffff) << 16) | (mnc & 0xffff);
2041   }
2042 
2043   /** union { struct { uint16_t screenWidth; uint16_t screenHeight; }; uint32_t screenSize; }; */
screenSize()2044   private int screenSize() {
2045     return ((screenWidth & 0xffff) << 16) | (screenHeight & 0xffff);
2046   }
2047 
2048   /**
2049    * union { struct { uint8_t screenLayout; uint8_t uiMode; uint16_t smallestScreenWidthDp; };
2050    * uint32_t screenConfig; };
2051    */
screenConfig()2052   private int screenConfig() {
2053     return ((screenLayout & 0xff) << 24)
2054         | ((uiMode * 0xff) << 16)
2055         | (smallestScreenWidthDp & 0xffff);
2056   }
2057 
2058   /**
2059    * union { struct { uint16_t screenWidthDp; uint16_t screenHeightDp; }; uint32_t screenSizeDp; };
2060    */
screenSizeDp()2061   private int screenSizeDp() {
2062     // screenWidthDp and screenHeightDp are really shorts...
2063     return (screenWidthDp & 0xffff) << 16 | (screenHeightDp & 0xffff);
2064   }
2065 
2066   /**
2067    * union { struct { uint8_t orientation; uint8_t touchscreen; uint16_t density; }; uint32_t
2068    * screenType; };
2069    */
screenType()2070   private int screenType() {
2071     return ((orientation & 0xff) << 24) | ((touchscreen & 0xff) << 16) | (density & 0xffff);
2072   }
2073 
2074   /**
2075    * union { struct { uint8_t keyboard; uint8_t navigation; uint8_t inputFlags; uint8_t inputPad0;
2076    * }; uint32_t input; };
2077    */
input()2078   private int input() {
2079     // TODO is Pad Zeros?
2080     return ((keyboard & 0xff) << 24) | ((navigation & 0xff) << 16) | ((inputFlags & 0xff) << 8);
2081   }
2082 
2083   /**
2084    * union { struct { uint16_t sdkVersion; // For now minorVersion must always be 0!!! Its meaning
2085    * // is currently undefined. uint16_t minorVersion; }; uint32_t version; };
2086    */
version()2087   private int version() {
2088     return ((sdkVersion & 0xffff) << 16) | (minorVersion & 0xffff);
2089   }
2090 
2091   /**
2092    * union { struct { // This field can take three different forms: // - \0\0 means "any". // // -
2093    * Two 7 bit ascii values interpreted as ISO-639-1 language // codes ('fr', 'en' etc. etc.). The
2094    * high bit for both bytes is // zero. // // - A single 16 bit little endian packed value
2095    * representing an // ISO-639-2 3 letter language code. This will be of the form: // // {1, t, t,
2096    * t, t, t, s, s, s, s, s, f, f, f, f, f} // // bit[0, 4] = first letter of the language code //
2097    * bit[5, 9] = second letter of the language code // bit[10, 14] = third letter of the language
2098    * code. // bit[15] = 1 always // // For backwards compatibility, languages that have unambiguous
2099    * // two letter codes are represented in that format. // // The layout is always bigendian
2100    * irrespective of the runtime // architecture. char language[2];
2101    *
2102    * <p>// This field can take three different forms: // - \0\0 means "any". // // - Two 7 bit ascii
2103    * values interpreted as 2 letter country // codes ('US', 'GB' etc.). The high bit for both bytes
2104    * is zero. // // - An UN M.49 3 digit country code. For simplicity, these are packed // in the
2105    * same manner as the language codes, though we should need // only 10 bits to represent them,
2106    * instead of the 15. // // The layout is always bigendian irrespective of the runtime //
2107    * architecture. char country[2]; }; uint32_t locale; };
2108    */
locale()2109   int locale() {
2110     return ((language[0] & 0xff) << 24)
2111         | ((language[1] & 0xff) << 16)
2112         | ((country[0] & 0xff) << 8)
2113         | (country[1] & 0xff);
2114   }
2115 
isLocaleBetterThan(ResTable_config o, ResTable_config requested)2116   private boolean isLocaleBetterThan(ResTable_config o, ResTable_config requested) {
2117     if (requested.locale() == 0) {
2118       // The request doesn't have a locale, so no resource is better
2119       // than the other.
2120       return false;
2121     }
2122 
2123     if (locale() == 0 && o.locale() == 0) {
2124       // The locale part of both resources is empty, so none is better
2125       // than the other.
2126       return false;
2127     }
2128 
2129     // Non-matching locales have been filtered out, so both resources
2130     // match the requested locale.
2131     //
2132     // Because of the locale-related checks in match() and the checks, we know
2133     // that:
2134     // 1) The resource languages are either empty or match the request;
2135     // and
2136     // 2) If the request's script is known, the resource scripts are either
2137     //    unknown or match the request.
2138 
2139     if (!langsAreEquivalent(language, o.language)) {
2140       // The languages of the two resources are not equivalent. If we are
2141       // here, we can only assume that the two resources matched the request
2142       // because one doesn't have a language and the other has a matching
2143       // language.
2144       //
2145       // We consider the one that has the language specified a better match.
2146       //
2147       // The exception is that we consider no-language resources a better match
2148       // for US English and similar locales than locales that are a descendant
2149       // of Internatinal English (en-001), since no-language resources are
2150       // where the US English resource have traditionally lived for most apps.
2151       if (areIdentical(requested.language, kEnglish)) {
2152         if (areIdentical(requested.country, kUnitedStates)) {
2153           // For US English itself, we consider a no-locale resource a
2154           // better match if the other resource has a country other than
2155           // US specified.
2156           if (language[0] != '\0') {
2157             return country[0] == '\0' || areIdentical(country, kUnitedStates);
2158           } else {
2159             return !(o.country[0] == '\0' || areIdentical(o.country, kUnitedStates));
2160           }
2161         } else if (localeDataIsCloseToUsEnglish(requested.country)) {
2162           if (language[0] != '\0') {
2163             return localeDataIsCloseToUsEnglish(country);
2164           } else {
2165             return !localeDataIsCloseToUsEnglish(o.country);
2166           }
2167         }
2168       }
2169       return (language[0] != '\0');
2170     }
2171 
2172     // If we are here, both the resources have an equivalent non-empty language
2173     // to the request.
2174     //
2175     // Because the languages are equivalent, computeScript() always returns a
2176     // non-empty script for languages it knows about, and we have passed the
2177     // script checks in match(), the scripts are either all unknown or are all
2178     // the same. So we can't gain anything by checking the scripts. We need to
2179     // check the country and variant.
2180 
2181     // See if any of the regions is better than the other.
2182     final int region_comparison =
2183         localeDataCompareRegions(
2184             country, o.country, requested.language, str(requested.localeScript), requested.country);
2185     if (region_comparison != 0) {
2186       return (region_comparison > 0);
2187     }
2188 
2189     // The regions are the same. Try the variant.
2190     final boolean localeMatches = Arrays.equals(localeVariant, requested.localeVariant);
2191     final boolean otherMatches = Arrays.equals(o.localeVariant, requested.localeVariant);
2192     if (localeMatches != otherMatches) {
2193       return localeMatches;
2194     }
2195 
2196     // The variants are the same, try numbering system.
2197     boolean localeNumsysMatches =
2198         arrayCompare(localeNumberingSystem, requested.localeNumberingSystem) == 0;
2199     boolean otherNumsysMatches =
2200         arrayCompare(o.localeNumberingSystem, requested.localeNumberingSystem) == 0;
2201 
2202     if (localeNumsysMatches != otherNumsysMatches) {
2203       return localeNumsysMatches;
2204     }
2205 
2206     // Finally, the languages, although equivalent, may still be different
2207     // (like for Tagalog and Filipino). Identical is better than just
2208     // equivalent.
2209     if (areIdentical(language, requested.language)
2210         && !areIdentical(o.language, requested.language)) {
2211       return true;
2212     }
2213 
2214     return false;
2215   }
2216 
str(byte[] country)2217   private String str(byte[] country) {
2218     return new String(country, UTF_8);
2219   }
2220 
langsAreEquivalent(final byte[] lang1, final byte[] lang2)2221   private boolean langsAreEquivalent(final byte[] lang1, final byte[] lang2) {
2222     return areIdentical(lang1, lang2)
2223         || (areIdentical(lang1, kTagalog) && areIdentical(lang2, kFilipino))
2224         || (areIdentical(lang1, kFilipino) && areIdentical(lang2, kTagalog));
2225   }
2226 
2227   // Checks if two language or country codes are identical
areIdentical(final byte[] code1, final byte[] code2)2228   private boolean areIdentical(final byte[] code1, final byte[] code2) {
2229     return code1[0] == code2[0] && code1[1] == code2[1];
2230   }
2231 
isLocaleMoreSpecificThan(ResTable_config o)2232   int isLocaleMoreSpecificThan(ResTable_config o) {
2233     if (isTruthy(locale()) || isTruthy(o.locale())) {
2234       if (language[0] != o.language[0]) {
2235         if (!isTruthy(language[0])) return -1;
2236         if (!isTruthy(o.language[0])) return 1;
2237       }
2238       if (country[0] != o.country[0]) {
2239         if (!isTruthy(country[0])) return -1;
2240         if (!isTruthy(o.country[0])) return 1;
2241       }
2242     }
2243     return getImportanceScoreOfLocale() - o.getImportanceScoreOfLocale();
2244   }
2245 
isMoreSpecificThan(ResTable_config o)2246   private boolean isMoreSpecificThan(ResTable_config o) {
2247     // The order of the following tests defines the importance of one
2248     // configuration parameter over another.  Those tests first are more
2249     // important, trumping any values in those following them.
2250     if (isTruthy(imsi()) || isTruthy(o.imsi())) {
2251       if (mcc != o.mcc) {
2252         if (!isTruthy(mcc)) return false;
2253         if (!isTruthy(o.mcc)) return true;
2254       }
2255       if (mnc != o.mnc) {
2256         if (!isTruthy(mnc)) return false;
2257         if (!isTruthy(o.mnc)) return true;
2258       }
2259     }
2260     if (isTruthy(locale()) || isTruthy(o.locale())) {
2261       int diff = isLocaleMoreSpecificThan(o);
2262       if (diff < 0) {
2263         return false;
2264       }
2265       if (diff > 0) {
2266         return true;
2267       }
2268     }
2269     if (isTruthy(screenLayout) || isTruthy(o.screenLayout)) {
2270       if (((screenLayout ^ o.screenLayout) & MASK_LAYOUTDIR) != 0) {
2271         if (!isTruthy((screenLayout & MASK_LAYOUTDIR))) return false;
2272         if (!isTruthy((o.screenLayout & MASK_LAYOUTDIR))) return true;
2273       }
2274     }
2275     if (isTruthy(smallestScreenWidthDp) || isTruthy(o.smallestScreenWidthDp)) {
2276       if (smallestScreenWidthDp != o.smallestScreenWidthDp) {
2277         if (!isTruthy(smallestScreenWidthDp)) return false;
2278         if (!isTruthy(o.smallestScreenWidthDp)) return true;
2279       }
2280     }
2281     if (isTruthy(screenSizeDp()) || isTruthy(o.screenSizeDp())) {
2282       if (screenWidthDp != o.screenWidthDp) {
2283         if (!isTruthy(screenWidthDp)) return false;
2284         if (!isTruthy(o.screenWidthDp)) return true;
2285       }
2286       if (screenHeightDp != o.screenHeightDp) {
2287         if (!isTruthy(screenHeightDp)) return false;
2288         if (!isTruthy(o.screenHeightDp)) return true;
2289       }
2290     }
2291     if (isTruthy(screenLayout) || isTruthy(o.screenLayout)) {
2292       if (((screenLayout ^ o.screenLayout) & MASK_SCREENSIZE) != 0) {
2293         if (!isTruthy((screenLayout & MASK_SCREENSIZE))) return false;
2294         if (!isTruthy((o.screenLayout & MASK_SCREENSIZE))) return true;
2295       }
2296       if (((screenLayout ^ o.screenLayout) & MASK_SCREENLONG) != 0) {
2297         if (!isTruthy((screenLayout & MASK_SCREENLONG))) return false;
2298         if (!isTruthy((o.screenLayout & MASK_SCREENLONG))) return true;
2299       }
2300     }
2301     if (isTruthy(screenLayout2) || isTruthy(o.screenLayout2)) {
2302       if (((screenLayout2 ^ o.screenLayout2) & MASK_SCREENROUND) != 0) {
2303         if (!isTruthy((screenLayout2 & MASK_SCREENROUND))) return false;
2304         if (!isTruthy((o.screenLayout2 & MASK_SCREENROUND))) return true;
2305       }
2306     }
2307 
2308     if (isTruthy(colorMode) || isTruthy(o.colorMode)) {
2309       if (((colorMode ^ o.colorMode) & MASK_HDR) != 0) {
2310         if (!isTruthy((colorMode & MASK_HDR))) return false;
2311         if (!isTruthy((o.colorMode & MASK_HDR))) return true;
2312       }
2313       if (((colorMode ^ o.colorMode) & MASK_WIDE_COLOR_GAMUT) != 0) {
2314         if (!isTruthy((colorMode & MASK_WIDE_COLOR_GAMUT))) return false;
2315         if (!isTruthy((o.colorMode & MASK_WIDE_COLOR_GAMUT))) return true;
2316       }
2317     }
2318 
2319     if (orientation != o.orientation) {
2320       if (!isTruthy(orientation)) return false;
2321       if (!isTruthy(o.orientation)) return true;
2322     }
2323     if (isTruthy(uiMode) || isTruthy(o.uiMode)) {
2324       if (((uiMode ^ o.uiMode) & MASK_UI_MODE_TYPE) != 0) {
2325         if (!isTruthy((uiMode & MASK_UI_MODE_TYPE))) return false;
2326         if (!isTruthy((o.uiMode & MASK_UI_MODE_TYPE))) return true;
2327       }
2328       if (((uiMode ^ o.uiMode) & MASK_UI_MODE_NIGHT) != 0) {
2329         if (!isTruthy((uiMode & MASK_UI_MODE_NIGHT))) return false;
2330         if (!isTruthy((o.uiMode & MASK_UI_MODE_NIGHT))) return true;
2331       }
2332     }
2333     // density is never 'more specific'
2334     // as the default just equals 160
2335     if (touchscreen != o.touchscreen) {
2336       if (!isTruthy(touchscreen)) return false;
2337       if (!isTruthy(o.touchscreen)) return true;
2338     }
2339     if (isTruthy(input()) || isTruthy(o.input())) {
2340       if (((inputFlags ^ o.inputFlags) & MASK_KEYSHIDDEN) != 0) {
2341         if (!isTruthy((inputFlags & MASK_KEYSHIDDEN))) return false;
2342         if (!isTruthy((o.inputFlags & MASK_KEYSHIDDEN))) return true;
2343       }
2344       if (((inputFlags ^ o.inputFlags) & MASK_NAVHIDDEN) != 0) {
2345         if (!isTruthy((inputFlags & MASK_NAVHIDDEN))) return false;
2346         if (!isTruthy((o.inputFlags & MASK_NAVHIDDEN))) return true;
2347       }
2348       if (keyboard != o.keyboard) {
2349         if (!isTruthy(keyboard)) return false;
2350         if (!isTruthy(o.keyboard)) return true;
2351       }
2352       if (navigation != o.navigation) {
2353         if (!isTruthy(navigation)) return false;
2354         if (!isTruthy(o.navigation)) return true;
2355       }
2356     }
2357     if (isTruthy(screenSize()) || isTruthy(o.screenSize())) {
2358       if (screenWidth != o.screenWidth) {
2359         if (!isTruthy(screenWidth)) return false;
2360         if (!isTruthy(o.screenWidth)) return true;
2361       }
2362       if (screenHeight != o.screenHeight) {
2363         if (!isTruthy(screenHeight)) return false;
2364         if (!isTruthy(o.screenHeight)) return true;
2365       }
2366     }
2367     if (isTruthy(version()) || isTruthy(o.version())) {
2368       if (sdkVersion != o.sdkVersion) {
2369         if (!isTruthy(sdkVersion)) return false;
2370         if (!isTruthy(o.sdkVersion)) return true;
2371       }
2372       if (minorVersion != o.minorVersion) {
2373         if (!isTruthy(minorVersion)) return false;
2374         if (!isTruthy(o.minorVersion)) return true;
2375       }
2376     }
2377     return false;
2378   }
2379 }
2380