1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "ConfigDescription.h"
18#include "Locale.h"
19#include "SdkConstants.h"
20#include "StringPiece.h"
21#include "Util.h"
22
23#include <androidfw/ResourceTypes.h>
24#include <string>
25#include <vector>
26
27namespace aapt {
28
29using android::ResTable_config;
30
31static const char* kWildcardName = "any";
32
33static bool parseMcc(const char* name, ResTable_config* out) {
34    if (strcmp(name, kWildcardName) == 0) {
35        if (out) out->mcc = 0;
36        return true;
37    }
38    const char* c = name;
39    if (tolower(*c) != 'm') return false;
40    c++;
41    if (tolower(*c) != 'c') return false;
42    c++;
43    if (tolower(*c) != 'c') return false;
44    c++;
45
46    const char* val = c;
47
48    while (*c >= '0' && *c <= '9') {
49        c++;
50    }
51    if (*c != 0) return false;
52    if (c-val != 3) return false;
53
54    int d = atoi(val);
55    if (d != 0) {
56        if (out) out->mcc = d;
57        return true;
58    }
59
60    return false;
61}
62
63static bool parseMnc(const char* name, ResTable_config* out) {
64    if (strcmp(name, kWildcardName) == 0) {
65        if (out) out->mcc = 0;
66        return true;
67    }
68    const char* c = name;
69    if (tolower(*c) != 'm') return false;
70    c++;
71    if (tolower(*c) != 'n') return false;
72    c++;
73    if (tolower(*c) != 'c') return false;
74    c++;
75
76    const char* val = c;
77
78    while (*c >= '0' && *c <= '9') {
79        c++;
80    }
81    if (*c != 0) return false;
82    if (c-val == 0 || c-val > 3) return false;
83
84    if (out) {
85        out->mnc = atoi(val);
86        if (out->mnc == 0) {
87            out->mnc = ACONFIGURATION_MNC_ZERO;
88        }
89    }
90
91    return true;
92}
93
94static bool parseLayoutDirection(const char* name, ResTable_config* out) {
95    if (strcmp(name, kWildcardName) == 0) {
96        if (out) out->screenLayout =
97                (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR)
98                | ResTable_config::LAYOUTDIR_ANY;
99        return true;
100    } else if (strcmp(name, "ldltr") == 0) {
101        if (out) out->screenLayout =
102                (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR)
103                | ResTable_config::LAYOUTDIR_LTR;
104        return true;
105    } else if (strcmp(name, "ldrtl") == 0) {
106        if (out) out->screenLayout =
107                (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR)
108                | ResTable_config::LAYOUTDIR_RTL;
109        return true;
110    }
111
112    return false;
113}
114
115static bool parseScreenLayoutSize(const char* name, ResTable_config* out) {
116    if (strcmp(name, kWildcardName) == 0) {
117        if (out) out->screenLayout =
118                (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
119                | ResTable_config::SCREENSIZE_ANY;
120        return true;
121    } else if (strcmp(name, "small") == 0) {
122        if (out) out->screenLayout =
123                (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
124                | ResTable_config::SCREENSIZE_SMALL;
125        return true;
126    } else if (strcmp(name, "normal") == 0) {
127        if (out) out->screenLayout =
128                (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
129                | ResTable_config::SCREENSIZE_NORMAL;
130        return true;
131    } else if (strcmp(name, "large") == 0) {
132        if (out) out->screenLayout =
133                (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
134                | ResTable_config::SCREENSIZE_LARGE;
135        return true;
136    } else if (strcmp(name, "xlarge") == 0) {
137        if (out) out->screenLayout =
138                (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
139                | ResTable_config::SCREENSIZE_XLARGE;
140        return true;
141    }
142
143    return false;
144}
145
146static bool parseScreenLayoutLong(const char* name, ResTable_config* out) {
147    if (strcmp(name, kWildcardName) == 0) {
148        if (out) out->screenLayout =
149                (out->screenLayout&~ResTable_config::MASK_SCREENLONG)
150                | ResTable_config::SCREENLONG_ANY;
151        return true;
152    } else if (strcmp(name, "long") == 0) {
153        if (out) out->screenLayout =
154                (out->screenLayout&~ResTable_config::MASK_SCREENLONG)
155                | ResTable_config::SCREENLONG_YES;
156        return true;
157    } else if (strcmp(name, "notlong") == 0) {
158        if (out) out->screenLayout =
159                (out->screenLayout&~ResTable_config::MASK_SCREENLONG)
160                | ResTable_config::SCREENLONG_NO;
161        return true;
162    }
163
164    return false;
165}
166
167static bool parseOrientation(const char* name, ResTable_config* out) {
168    if (strcmp(name, kWildcardName) == 0) {
169        if (out) out->orientation = out->ORIENTATION_ANY;
170        return true;
171    } else if (strcmp(name, "port") == 0) {
172        if (out) out->orientation = out->ORIENTATION_PORT;
173        return true;
174    } else if (strcmp(name, "land") == 0) {
175        if (out) out->orientation = out->ORIENTATION_LAND;
176        return true;
177    } else if (strcmp(name, "square") == 0) {
178        if (out) out->orientation = out->ORIENTATION_SQUARE;
179        return true;
180    }
181
182    return false;
183}
184
185static bool parseUiModeType(const char* name, ResTable_config* out) {
186    if (strcmp(name, kWildcardName) == 0) {
187        if (out) out->uiMode =
188                (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
189                | ResTable_config::UI_MODE_TYPE_ANY;
190        return true;
191    } else if (strcmp(name, "desk") == 0) {
192      if (out) out->uiMode =
193              (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
194              | ResTable_config::UI_MODE_TYPE_DESK;
195        return true;
196    } else if (strcmp(name, "car") == 0) {
197      if (out) out->uiMode =
198              (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
199              | ResTable_config::UI_MODE_TYPE_CAR;
200        return true;
201    } else if (strcmp(name, "television") == 0) {
202      if (out) out->uiMode =
203              (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
204              | ResTable_config::UI_MODE_TYPE_TELEVISION;
205        return true;
206    } else if (strcmp(name, "appliance") == 0) {
207      if (out) out->uiMode =
208              (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
209              | ResTable_config::UI_MODE_TYPE_APPLIANCE;
210        return true;
211    } else if (strcmp(name, "watch") == 0) {
212      if (out) out->uiMode =
213              (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
214              | ResTable_config::UI_MODE_TYPE_WATCH;
215        return true;
216    }
217
218    return false;
219}
220
221static bool parseUiModeNight(const char* name, ResTable_config* out) {
222    if (strcmp(name, kWildcardName) == 0) {
223        if (out) out->uiMode =
224                (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT)
225                | ResTable_config::UI_MODE_NIGHT_ANY;
226        return true;
227    } else if (strcmp(name, "night") == 0) {
228        if (out) out->uiMode =
229                (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT)
230                | ResTable_config::UI_MODE_NIGHT_YES;
231        return true;
232    } else if (strcmp(name, "notnight") == 0) {
233      if (out) out->uiMode =
234              (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT)
235              | ResTable_config::UI_MODE_NIGHT_NO;
236        return true;
237    }
238
239    return false;
240}
241
242static bool parseDensity(const char* name, ResTable_config* out) {
243    if (strcmp(name, kWildcardName) == 0) {
244        if (out) out->density = ResTable_config::DENSITY_DEFAULT;
245        return true;
246    }
247
248    if (strcmp(name, "anydpi") == 0) {
249        if (out) out->density = ResTable_config::DENSITY_ANY;
250        return true;
251    }
252
253    if (strcmp(name, "nodpi") == 0) {
254        if (out) out->density = ResTable_config::DENSITY_NONE;
255        return true;
256    }
257
258    if (strcmp(name, "ldpi") == 0) {
259        if (out) out->density = ResTable_config::DENSITY_LOW;
260        return true;
261    }
262
263    if (strcmp(name, "mdpi") == 0) {
264        if (out) out->density = ResTable_config::DENSITY_MEDIUM;
265        return true;
266    }
267
268    if (strcmp(name, "tvdpi") == 0) {
269        if (out) out->density = ResTable_config::DENSITY_TV;
270        return true;
271    }
272
273    if (strcmp(name, "hdpi") == 0) {
274        if (out) out->density = ResTable_config::DENSITY_HIGH;
275        return true;
276    }
277
278    if (strcmp(name, "xhdpi") == 0) {
279        if (out) out->density = ResTable_config::DENSITY_XHIGH;
280        return true;
281    }
282
283    if (strcmp(name, "xxhdpi") == 0) {
284        if (out) out->density = ResTable_config::DENSITY_XXHIGH;
285        return true;
286    }
287
288    if (strcmp(name, "xxxhdpi") == 0) {
289        if (out) out->density = ResTable_config::DENSITY_XXXHIGH;
290        return true;
291    }
292
293    char* c = (char*)name;
294    while (*c >= '0' && *c <= '9') {
295        c++;
296    }
297
298    // check that we have 'dpi' after the last digit.
299    if (toupper(c[0]) != 'D' ||
300            toupper(c[1]) != 'P' ||
301            toupper(c[2]) != 'I' ||
302            c[3] != 0) {
303        return false;
304    }
305
306    // temporarily replace the first letter with \0 to
307    // use atoi.
308    char tmp = c[0];
309    c[0] = '\0';
310
311    int d = atoi(name);
312    c[0] = tmp;
313
314    if (d != 0) {
315        if (out) out->density = d;
316        return true;
317    }
318
319    return false;
320}
321
322static bool parseTouchscreen(const char* name, ResTable_config* out) {
323    if (strcmp(name, kWildcardName) == 0) {
324        if (out) out->touchscreen = out->TOUCHSCREEN_ANY;
325        return true;
326    } else if (strcmp(name, "notouch") == 0) {
327        if (out) out->touchscreen = out->TOUCHSCREEN_NOTOUCH;
328        return true;
329    } else if (strcmp(name, "stylus") == 0) {
330        if (out) out->touchscreen = out->TOUCHSCREEN_STYLUS;
331        return true;
332    } else if (strcmp(name, "finger") == 0) {
333        if (out) out->touchscreen = out->TOUCHSCREEN_FINGER;
334        return true;
335    }
336
337    return false;
338}
339
340static bool parseKeysHidden(const char* name, ResTable_config* out) {
341    uint8_t mask = 0;
342    uint8_t value = 0;
343    if (strcmp(name, kWildcardName) == 0) {
344        mask = ResTable_config::MASK_KEYSHIDDEN;
345        value = ResTable_config::KEYSHIDDEN_ANY;
346    } else if (strcmp(name, "keysexposed") == 0) {
347        mask = ResTable_config::MASK_KEYSHIDDEN;
348        value = ResTable_config::KEYSHIDDEN_NO;
349    } else if (strcmp(name, "keyshidden") == 0) {
350        mask = ResTable_config::MASK_KEYSHIDDEN;
351        value = ResTable_config::KEYSHIDDEN_YES;
352    } else if (strcmp(name, "keyssoft") == 0) {
353        mask = ResTable_config::MASK_KEYSHIDDEN;
354        value = ResTable_config::KEYSHIDDEN_SOFT;
355    }
356
357    if (mask != 0) {
358        if (out) out->inputFlags = (out->inputFlags&~mask) | value;
359        return true;
360    }
361
362    return false;
363}
364
365static bool parseKeyboard(const char* name, ResTable_config* out) {
366    if (strcmp(name, kWildcardName) == 0) {
367        if (out) out->keyboard = out->KEYBOARD_ANY;
368        return true;
369    } else if (strcmp(name, "nokeys") == 0) {
370        if (out) out->keyboard = out->KEYBOARD_NOKEYS;
371        return true;
372    } else if (strcmp(name, "qwerty") == 0) {
373        if (out) out->keyboard = out->KEYBOARD_QWERTY;
374        return true;
375    } else if (strcmp(name, "12key") == 0) {
376        if (out) out->keyboard = out->KEYBOARD_12KEY;
377        return true;
378    }
379
380    return false;
381}
382
383static bool parseNavHidden(const char* name, ResTable_config* out) {
384    uint8_t mask = 0;
385    uint8_t value = 0;
386    if (strcmp(name, kWildcardName) == 0) {
387        mask = ResTable_config::MASK_NAVHIDDEN;
388        value = ResTable_config::NAVHIDDEN_ANY;
389    } else if (strcmp(name, "navexposed") == 0) {
390        mask = ResTable_config::MASK_NAVHIDDEN;
391        value = ResTable_config::NAVHIDDEN_NO;
392    } else if (strcmp(name, "navhidden") == 0) {
393        mask = ResTable_config::MASK_NAVHIDDEN;
394        value = ResTable_config::NAVHIDDEN_YES;
395    }
396
397    if (mask != 0) {
398        if (out) out->inputFlags = (out->inputFlags&~mask) | value;
399        return true;
400    }
401
402    return false;
403}
404
405static bool parseNavigation(const char* name, ResTable_config* out) {
406    if (strcmp(name, kWildcardName) == 0) {
407        if (out) out->navigation = out->NAVIGATION_ANY;
408        return true;
409    } else if (strcmp(name, "nonav") == 0) {
410        if (out) out->navigation = out->NAVIGATION_NONAV;
411        return true;
412    } else if (strcmp(name, "dpad") == 0) {
413        if (out) out->navigation = out->NAVIGATION_DPAD;
414        return true;
415    } else if (strcmp(name, "trackball") == 0) {
416        if (out) out->navigation = out->NAVIGATION_TRACKBALL;
417        return true;
418    } else if (strcmp(name, "wheel") == 0) {
419        if (out) out->navigation = out->NAVIGATION_WHEEL;
420        return true;
421    }
422
423    return false;
424}
425
426static bool parseScreenSize(const char* name, ResTable_config* out) {
427    if (strcmp(name, kWildcardName) == 0) {
428        if (out) {
429            out->screenWidth = out->SCREENWIDTH_ANY;
430            out->screenHeight = out->SCREENHEIGHT_ANY;
431        }
432        return true;
433    }
434
435    const char* x = name;
436    while (*x >= '0' && *x <= '9') x++;
437    if (x == name || *x != 'x') return false;
438    std::string xName(name, x-name);
439    x++;
440
441    const char* y = x;
442    while (*y >= '0' && *y <= '9') y++;
443    if (y == name || *y != 0) return false;
444    std::string yName(x, y-x);
445
446    uint16_t w = (uint16_t)atoi(xName.c_str());
447    uint16_t h = (uint16_t)atoi(yName.c_str());
448    if (w < h) {
449        return false;
450    }
451
452    if (out) {
453        out->screenWidth = w;
454        out->screenHeight = h;
455    }
456
457    return true;
458}
459
460static bool parseSmallestScreenWidthDp(const char* name, ResTable_config* out) {
461    if (strcmp(name, kWildcardName) == 0) {
462        if (out) {
463            out->smallestScreenWidthDp = out->SCREENWIDTH_ANY;
464        }
465        return true;
466    }
467
468    if (*name != 's') return false;
469    name++;
470    if (*name != 'w') return false;
471    name++;
472    const char* x = name;
473    while (*x >= '0' && *x <= '9') x++;
474    if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false;
475    std::string xName(name, x-name);
476
477    if (out) {
478        out->smallestScreenWidthDp = (uint16_t)atoi(xName.c_str());
479    }
480
481    return true;
482}
483
484static bool parseScreenWidthDp(const char* name, ResTable_config* out) {
485    if (strcmp(name, kWildcardName) == 0) {
486        if (out) {
487            out->screenWidthDp = out->SCREENWIDTH_ANY;
488        }
489        return true;
490    }
491
492    if (*name != 'w') return false;
493    name++;
494    const char* x = name;
495    while (*x >= '0' && *x <= '9') x++;
496    if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false;
497    std::string xName(name, x-name);
498
499    if (out) {
500        out->screenWidthDp = (uint16_t)atoi(xName.c_str());
501    }
502
503    return true;
504}
505
506static bool parseScreenHeightDp(const char* name, ResTable_config* out) {
507    if (strcmp(name, kWildcardName) == 0) {
508        if (out) {
509            out->screenHeightDp = out->SCREENWIDTH_ANY;
510        }
511        return true;
512    }
513
514    if (*name != 'h') return false;
515    name++;
516    const char* x = name;
517    while (*x >= '0' && *x <= '9') x++;
518    if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false;
519    std::string xName(name, x-name);
520
521    if (out) {
522        out->screenHeightDp = (uint16_t)atoi(xName.c_str());
523    }
524
525    return true;
526}
527
528static bool parseVersion(const char* name, ResTable_config* out) {
529    if (strcmp(name, kWildcardName) == 0) {
530        if (out) {
531            out->sdkVersion = out->SDKVERSION_ANY;
532            out->minorVersion = out->MINORVERSION_ANY;
533        }
534        return true;
535    }
536
537    if (*name != 'v') {
538        return false;
539    }
540
541    name++;
542    const char* s = name;
543    while (*s >= '0' && *s <= '9') s++;
544    if (s == name || *s != 0) return false;
545    std::string sdkName(name, s-name);
546
547    if (out) {
548        out->sdkVersion = (uint16_t)atoi(sdkName.c_str());
549        out->minorVersion = 0;
550    }
551
552    return true;
553}
554
555bool ConfigDescription::parse(const StringPiece& str, ConfigDescription* out) {
556    std::vector<std::string> parts = util::splitAndLowercase(str, '-');
557
558    ConfigDescription config;
559    ssize_t partsConsumed = 0;
560    LocaleValue locale;
561
562    const auto partsEnd = parts.end();
563    auto partIter = parts.begin();
564
565    if (str.size() == 0) {
566        goto success;
567    }
568
569    if (parseMcc(partIter->c_str(), &config)) {
570        ++partIter;
571        if (partIter == partsEnd) {
572            goto success;
573        }
574    }
575
576    if (parseMnc(partIter->c_str(), &config)) {
577        ++partIter;
578        if (partIter == partsEnd) {
579            goto success;
580        }
581    }
582
583    // Locale spans a few '-' separators, so we let it
584    // control the index.
585    partsConsumed = locale.initFromParts(partIter, partsEnd);
586    if (partsConsumed < 0) {
587        return false;
588    } else {
589        locale.writeTo(&config);
590        partIter += partsConsumed;
591        if (partIter == partsEnd) {
592            goto success;
593        }
594    }
595
596    if (parseLayoutDirection(partIter->c_str(), &config)) {
597        ++partIter;
598        if (partIter == partsEnd) {
599            goto success;
600        }
601    }
602
603    if (parseSmallestScreenWidthDp(partIter->c_str(), &config)) {
604        ++partIter;
605        if (partIter == partsEnd) {
606            goto success;
607        }
608    }
609
610    if (parseScreenWidthDp(partIter->c_str(), &config)) {
611        ++partIter;
612        if (partIter == partsEnd) {
613            goto success;
614        }
615    }
616
617    if (parseScreenHeightDp(partIter->c_str(), &config)) {
618        ++partIter;
619        if (partIter == partsEnd) {
620            goto success;
621        }
622    }
623
624    if (parseScreenLayoutSize(partIter->c_str(), &config)) {
625        ++partIter;
626        if (partIter == partsEnd) {
627            goto success;
628        }
629    }
630
631    if (parseScreenLayoutLong(partIter->c_str(), &config)) {
632        ++partIter;
633        if (partIter == partsEnd) {
634            goto success;
635        }
636    }
637
638    if (parseOrientation(partIter->c_str(), &config)) {
639        ++partIter;
640        if (partIter == partsEnd) {
641            goto success;
642        }
643    }
644
645    if (parseUiModeType(partIter->c_str(), &config)) {
646        ++partIter;
647        if (partIter == partsEnd) {
648            goto success;
649        }
650    }
651
652    if (parseUiModeNight(partIter->c_str(), &config)) {
653        ++partIter;
654        if (partIter == partsEnd) {
655            goto success;
656        }
657    }
658
659    if (parseDensity(partIter->c_str(), &config)) {
660        ++partIter;
661        if (partIter == partsEnd) {
662            goto success;
663        }
664    }
665
666    if (parseTouchscreen(partIter->c_str(), &config)) {
667        ++partIter;
668        if (partIter == partsEnd) {
669            goto success;
670        }
671    }
672
673    if (parseKeysHidden(partIter->c_str(), &config)) {
674        ++partIter;
675        if (partIter == partsEnd) {
676            goto success;
677        }
678    }
679
680    if (parseKeyboard(partIter->c_str(), &config)) {
681        ++partIter;
682        if (partIter == partsEnd) {
683            goto success;
684        }
685    }
686
687    if (parseNavHidden(partIter->c_str(), &config)) {
688        ++partIter;
689        if (partIter == partsEnd) {
690            goto success;
691        }
692    }
693
694    if (parseNavigation(partIter->c_str(), &config)) {
695        ++partIter;
696        if (partIter == partsEnd) {
697            goto success;
698        }
699    }
700
701    if (parseScreenSize(partIter->c_str(), &config)) {
702        ++partIter;
703        if (partIter == partsEnd) {
704            goto success;
705        }
706    }
707
708    if (parseVersion(partIter->c_str(), &config)) {
709        ++partIter;
710        if (partIter == partsEnd) {
711            goto success;
712        }
713    }
714
715    // Unrecognized.
716    return false;
717
718success:
719    if (out != NULL) {
720        applyVersionForCompatibility(&config);
721        *out = config;
722    }
723    return true;
724}
725
726void ConfigDescription::applyVersionForCompatibility(ConfigDescription* config) {
727    uint16_t minSdk = 0;
728    if (config->density == ResTable_config::DENSITY_ANY) {
729        minSdk = SDK_LOLLIPOP;
730    } else if (config->smallestScreenWidthDp != ResTable_config::SCREENWIDTH_ANY
731            || config->screenWidthDp != ResTable_config::SCREENWIDTH_ANY
732            || config->screenHeightDp != ResTable_config::SCREENHEIGHT_ANY) {
733        minSdk = SDK_HONEYCOMB_MR2;
734    } else if ((config->uiMode & ResTable_config::MASK_UI_MODE_TYPE)
735                != ResTable_config::UI_MODE_TYPE_ANY
736            ||  (config->uiMode & ResTable_config::MASK_UI_MODE_NIGHT)
737                != ResTable_config::UI_MODE_NIGHT_ANY) {
738        minSdk = SDK_FROYO;
739    } else if ((config->screenLayout & ResTable_config::MASK_SCREENSIZE)
740                != ResTable_config::SCREENSIZE_ANY
741            ||  (config->screenLayout & ResTable_config::MASK_SCREENLONG)
742                != ResTable_config::SCREENLONG_ANY
743            || config->density != ResTable_config::DENSITY_DEFAULT) {
744        minSdk = SDK_DONUT;
745    }
746
747    if (minSdk > config->sdkVersion) {
748        config->sdkVersion = minSdk;
749    }
750}
751
752} // namespace aapt
753