Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 378c6775 authored by Narayan Kamath's avatar Narayan Kamath
Browse files

AssetManager support for 3 letter lang/country codes.

- 3 letter codes are packed into the existing 32 bit locale
  field in ResTable_config
- We introduce new fields for script / variant information.

Note that we define a "match" between two ResTable_config
structures to be purely on the basis of their language &
country (disregarding the script and the variant). However,
configs with scripts and variants are considered to be more
specific than those without.

Change-Id: I3c2e909ebbebdbbd0ab72a639d531b2b9fdbd9b9
parent dd99d87a
Loading
Loading
Loading
Loading
+84 −4
Original line number Diff line number Diff line
@@ -808,6 +808,19 @@ struct ResTable_package
    uint32_t lastPublicKey;
};

// The most specific locale can consist of:
//
// - a 3 char language code
// - a 3 char region code prefixed by a 'r'
// - a 4 char script code prefixed by a 's'
// - a 8 char variant code prefixed by a 'v'
//
// each separated by a single char separator, which sums up to a total of 24
// chars, (25 include the string terminator) rounded up to 28 to be 4 byte
// aligned.
#define RESTABLE_MAX_LOCALE_LEN 28


/**
 * Describes a particular resource configuration.
 */
@@ -828,10 +841,42 @@ struct ResTable_config
    
    union {
        struct {
            // \0\0 means "any".  Otherwise, en, fr, etc.
            // This field can take three different forms:
            // - \0\0 means "any".
            //
            // - Two 7 bit ascii values interpreted as ISO-639-1 language
            //   codes ('fr', 'en' etc. etc.). The high bit for both bytes is
            //   zero.
            //
            // - A single 16 bit little endian packed value representing an
            //   ISO-639-2 3 letter language code. This will be of the form:
            //
            //   {1, t, t, t, t, t, s, s, s, s, s, f, f, f, f, f}
            //
            //   bit[0, 4] = first letter of the language code
            //   bit[5, 9] = second letter of the language code
            //   bit[10, 14] = third letter of the language code.
            //   bit[15] = 1 always
            //
            // For backwards compatibility, languages that have unambiguous
            // two letter codes are represented in that format.
            //
            // The layout is always bigendian irrespective of the runtime
            // architecture.
            char language[2];
            
            // \0\0 means "any".  Otherwise, US, CA, etc.
            // This field can take three different forms:
            // - \0\0 means "any".
            //
            // - Two 7 bit ascii values interpreted as 2 letter region
            //   codes ('US', 'GB' etc.). The high bit for both bytes is zero.
            //
            // - An UN M.49 3 digit region code. For simplicity, these are packed
            //   in the same manner as the language codes, though we should need
            //   only 10 bits to represent them, instead of the 15.
            //
            // The layout is always bigendian irrespective of the runtime
            // architecture.
            char country[2];
        };
        uint32_t locale;
@@ -1006,6 +1051,15 @@ struct ResTable_config
        uint32_t screenSizeDp;
    };

    // The ISO-15924 short name for the script corresponding to this
    // configuration. (eg. Hant, Latn, etc.). Interpreted in conjunction with
    // the locale field
    char localeScript[4];

    // A single BCP-47 variant subtag. Will vary in length between 5 and 8
    // chars. Interpreted in conjunction with the locale field.
    char localeVariant[8];

    void copyFromDeviceNoSwap(const ResTable_config& o);
    
    void copyFromDtoH(const ResTable_config& o);
@@ -1063,7 +1117,33 @@ struct ResTable_config
    // settings is the requested settings
    bool match(const ResTable_config& settings) const;

    void getLocale(char str[6]) const;
    // Get the string representation of the locale component of this
    // Config. This will contain the language along with the prefixed script,
    // region and variant of this config, separated by underscores.
    //
    // 'r' is the region prefix, 's' is the script prefix and 'v' is the
    // variant prefix.
    //
    // Example: en_rUS, en_sLatn_rUS, en_vPOSIX.
    void getLocale(char str[RESTABLE_MAX_LOCALE_LEN]) const;
    // Get the 2 or 3 letter language code of this configuration. Trailing
    // bytes are set to '\0'.
    size_t unpackLanguage(char language[4]) const;
    // Get the 2 or 3 letter language code of this configuration. Trailing
    // bytes are set to '\0'.
    size_t unpackRegion(char region[4]) const;

    // Sets the language code of this configuration from |language|. If |language|
    // is a 2 letter code, the trailing byte is expected to be '\0'.
    void packLanguage(const char language[3]);
    // Sets the region code of this configuration from |region|. If |region|
    // is a 2 letter code, the trailing byte is expected to be '\0'.
    void packRegion(const char region[3]);

    // Returns a positive integer if this config is more specific than |o|
    // with respect to their locales, a negative integer if |o| is more specific
    // and 0 if they're equally specific.
    int isLocaleMoreSpecificThan(const ResTable_config &o) const;

    String8 toString() const;
};
+195 −44
Original line number Diff line number Diff line
@@ -66,11 +66,6 @@ namespace android {
// size measured in sizeof(uint32_t)
#define IDMAP_HEADER_SIZE (ResTable::IDMAP_HEADER_SIZE_BYTES / sizeof(uint32_t))

static void printToLogFunc(int32_t cookie, const char* txt)
{
    ALOGV("[cookie=%d] %s", cookie, txt);
}

// Standard C isspace() is only required to look at the low byte of its input, so
// produces incorrect results for UTF-16 characters.  For safety's sake, assume that
// any high-byte UTF-16 code point is not whitespace.
@@ -1539,6 +1534,71 @@ void ResTable_config::copyFromDeviceNoSwap(const ResTable_config& o) {
    }
}

/* static */ size_t unpackLanguageOrRegion(const char in[2], const char base,
        char out[4]) {
  if (in[0] & 0x80) {
      // The high bit is "1", which means this is a packed three letter
      // language code.

      // The smallest 5 bits of the second char are the first alphabet.
      const uint8_t first = in[1] & 0x1f;
      // The last three bits of the second char and the first two bits
      // of the first char are the second alphabet.
      const uint8_t second = ((in[1] & 0xe0) >> 5) + ((in[0] & 0x03) << 3);
      // Bits 3 to 7 (inclusive) of the first char are the third alphabet.
      const uint8_t third = (in[0] & 0x7c) >> 2;

      out[0] = first + base;
      out[1] = second + base;
      out[2] = third + base;
      out[3] = 0;

      return 3;
  }

  if (in[0]) {
      memcpy(out, in, 2);
      memset(out + 2, 0, 2);
      return 2;
  }

  memset(out, 0, 4);
  return 0;
}

/* static */ void packLanguageOrRegion(const char in[3], const char base,
        char out[2]) {
  if (in[2] == 0) {
      out[0] = in[0];
      out[1] = in[1];
  } else {
      uint8_t first = (in[0] - base) & 0x00ef;
      uint8_t second = (in[1] - base) & 0x00ef;
      uint8_t third = (in[2] - base) & 0x00ef;

      out[0] = (0x80 | (third << 2) | (second >> 3));
      out[1] = ((second << 5) | first);
  }
}


void ResTable_config::packLanguage(const char language[3]) {
    packLanguageOrRegion(language, 'a', this->language);
}

void ResTable_config::packRegion(const char region[3]) {
    packLanguageOrRegion(region, '0', this->country);
}

size_t ResTable_config::unpackLanguage(char language[4]) const {
    return unpackLanguageOrRegion(this->language, 'a', language);
}

size_t ResTable_config::unpackRegion(char region[4]) const {
    return unpackLanguageOrRegion(this->country, '0', region);
}


void ResTable_config::copyFromDtoH(const ResTable_config& o) {
    copyFromDeviceNoSwap(o);
    size = sizeof(ResTable_config);
@@ -1568,10 +1628,30 @@ void ResTable_config::swapHtoD() {
    screenHeightDp = htods(screenHeightDp);
}

/* static */ inline int compareLocales(const ResTable_config &l, const ResTable_config &r) {
    if (l.locale != r.locale) {
        // NOTE: This is the old behaviour with respect to comparison orders.
        // The diff value here doesn't make much sense (given our bit packing scheme)
        // but it's stable, and that's all we need.
        return l.locale - r.locale;
    }

    // The language & region are equal, so compare the scripts and variants.
    int script = memcmp(l.localeScript, r.localeScript, sizeof(l.localeScript));
    if (script) {
        return script;
    }

    // The language, region and script are equal, so compare variants.
    //
    // This should happen very infrequently (if at all.)
    return memcmp(l.localeVariant, r.localeVariant, sizeof(l.localeVariant));
}

int ResTable_config::compare(const ResTable_config& o) const {
    int32_t diff = (int32_t)(imsi - o.imsi);
    if (diff != 0) return diff;
    diff = (int32_t)(locale - o.locale);
    diff = compareLocales(*this, o);
    if (diff != 0) return diff;
    diff = (int32_t)(screenType - o.screenType);
    if (diff != 0) return diff;
@@ -1598,18 +1678,15 @@ int ResTable_config::compareLogical(const ResTable_config& o) const {
    if (mnc != o.mnc) {
        return mnc < o.mnc ? -1 : 1;
    }
    if (language[0] != o.language[0]) {
        return language[0] < o.language[0] ? -1 : 1;
    }
    if (language[1] != o.language[1]) {
        return language[1] < o.language[1] ? -1 : 1;
    }
    if (country[0] != o.country[0]) {
        return country[0] < o.country[0] ? -1 : 1;

    int diff = compareLocales(*this, o);
    if (diff < 0) {
        return -1;
    }
    if (country[1] != o.country[1]) {
        return country[1] < o.country[1] ? -1 : 1;
    if (diff > 0) {
        return 1;
    }

    if ((screenLayout & MASK_LAYOUTDIR) != (o.screenLayout & MASK_LAYOUTDIR)) {
        return (screenLayout & MASK_LAYOUTDIR) < (o.screenLayout & MASK_LAYOUTDIR) ? -1 : 1;
    }
@@ -1656,7 +1733,6 @@ int ResTable_config::diff(const ResTable_config& o) const {
    int diffs = 0;
    if (mcc != o.mcc) diffs |= CONFIG_MCC;
    if (mnc != o.mnc) diffs |= CONFIG_MNC;
    if (locale != o.locale) diffs |= CONFIG_LOCALE;
    if (orientation != o.orientation) diffs |= CONFIG_ORIENTATION;
    if (density != o.density) diffs |= CONFIG_DENSITY;
    if (touchscreen != o.touchscreen) diffs |= CONFIG_TOUCHSCREEN;
@@ -1671,9 +1747,44 @@ int ResTable_config::diff(const ResTable_config& o) const {
    if (uiMode != o.uiMode) diffs |= CONFIG_UI_MODE;
    if (smallestScreenWidthDp != o.smallestScreenWidthDp) diffs |= CONFIG_SMALLEST_SCREEN_SIZE;
    if (screenSizeDp != o.screenSizeDp) diffs |= CONFIG_SCREEN_SIZE;

    const int diff = compareLocales(*this, o);
    if (diff) diffs |= CONFIG_LOCALE;

    return diffs;
}

int ResTable_config::isLocaleMoreSpecificThan(const ResTable_config& o) const {
    if (locale || o.locale) {
        if (language[0] != o.language[0]) {
            if (!language[0]) return -1;
            if (!o.language[0]) return 1;
        }

        if (country[0] != o.country[0]) {
            if (!country[0]) return -1;
            if (!o.country[0]) return 1;
        }
    }

    // There isn't a well specified "importance" order between variants and
    // scripts. We can't easily tell whether, say "en-Latn-US" is more or less
    // specific than "en-US-POSIX".
    //
    // We therefore arbitrarily decide to give priority to variants over
    // scripts since it seems more useful to do so. We will consider
    // "en-US-POSIX" to be more specific than "en-Latn-US".

    const int score = ((localeScript[0] != 0) ? 1 : 0) +
        ((localeVariant[0] != 0) ? 2 : 0);

    const int oScore = ((o.localeScript[0] != 0) ? 1 : 0) +
        ((o.localeVariant[0] != 0) ? 2 : 0);

    return score - oScore;

}

bool ResTable_config::isMoreSpecificThan(const ResTable_config& o) const {
    // The order of the following tests defines the importance of one
    // configuration parameter over another.  Those tests first are more
@@ -1691,14 +1802,13 @@ bool ResTable_config::isMoreSpecificThan(const ResTable_config& o) const {
    }

    if (locale || o.locale) {
        if (language[0] != o.language[0]) {
            if (!language[0]) return false;
            if (!o.language[0]) return true;
        const int diff = isLocaleMoreSpecificThan(o);
        if (diff < 0) {
            return false;
        }

        if (country[0] != o.country[0]) {
            if (!country[0]) return false;
            if (!o.country[0]) return true;
        if (diff > 0) {
            return true;
        }
    }

@@ -1834,6 +1944,18 @@ bool ResTable_config::isBetterThan(const ResTable_config& o,
            }
        }

        if (localeScript[0] || o.localeScript[0]) {
            if (localeScript[0] != o.localeScript[0] && requested->localeScript[0]) {
                return localeScript[0];
            }
        }

        if (localeVariant[0] || o.localeVariant[0]) {
            if (localeVariant[0] != o.localeVariant[0] && requested->localeVariant[0]) {
                return localeVariant[0];
            }
        }

        if (screenLayout || o.screenLayout) {
            if (((screenLayout^o.screenLayout) & MASK_LAYOUTDIR) != 0
                    && (requested->screenLayout & MASK_LAYOUTDIR)) {
@@ -2054,17 +2176,23 @@ bool ResTable_config::match(const ResTable_config& settings) const {
        }
    }
    if (locale != 0) {
        // Don't consider the script & variants when deciding matches.
        //
        // If we two configs differ only in their script or language, they
        // can be weeded out in the isMoreSpecificThan test.
        if (language[0] != 0
            && (language[0] != settings.language[0]
                || language[1] != settings.language[1])) {
            return false;
        }

        if (country[0] != 0
            && (country[0] != settings.country[0]
                || country[1] != settings.country[1])) {
            return false;
        }
    }

    if (screenConfig != 0) {
        const int layoutDir = screenLayout&MASK_LAYOUTDIR;
        const int setLayoutDir = settings.screenLayout&MASK_LAYOUTDIR;
@@ -2166,16 +2294,42 @@ bool ResTable_config::match(const ResTable_config& settings) const {
    return true;
}

void ResTable_config::getLocale(char str[6]) const {
    memset(str, 0, 6);
void ResTable_config::getLocale(char str[RESTABLE_MAX_LOCALE_LEN]) const {
    memset(str, 0, RESTABLE_MAX_LOCALE_LEN);

    // This represents the "any" locale value, which has traditionally been
    // represented by the empty string.
    if (!language[0] && !country[0]) {
        return;
    }

    size_t charsWritten = 0;
    if (language[0]) {
        str[0] = language[0];
        str[1] = language[1];
        unpackLanguage(str);
    }

    if (country[0]) {
            str[2] = '_';
            str[3] = country[0];
            str[4] = country[1];
        if (charsWritten) {
            str[charsWritten++] = '_';
            str[charsWritten++] = 'r';
        }
        charsWritten += unpackRegion(str + charsWritten);
    }

    if (localeScript[0]) {
        if (charsWritten) {
            str[charsWritten++] = '_';
            str[charsWritten++] = '_s';
        }
        memcpy(str + charsWritten, localeScript, sizeof(localeScript));
    }

    if (localeVariant[0]) {
        if (charsWritten) {
            str[charsWritten++] = '_';
            str[charsWritten++] = 'v';
        }
        memcpy(str + charsWritten, localeVariant, sizeof(localeVariant));
    }
}

@@ -2190,14 +2344,10 @@ String8 ResTable_config::toString() const {
        if (res.size() > 0) res.append("-");
        res.appendFormat("%dmnc", dtohs(mnc));
    }
    if (language[0] != 0) {
        if (res.size() > 0) res.append("-");
        res.append(language, 2);
    }
    if (country[0] != 0) {
        if (res.size() > 0) res.append("-");
        res.append(country, 2);
    }
    char localeStr[RESTABLE_MAX_LOCALE_LEN];
    getLocale(localeStr);
    res.append(localeStr);

    if ((screenLayout&MASK_LAYOUTDIR) != 0) {
        if (res.size() > 0) res.append("-");
        switch (screenLayout&ResTable_config::MASK_LAYOUTDIR) {
@@ -4976,8 +5126,9 @@ void ResTable::getLocales(Vector<String8>* locales) const
    getConfigurations(&configs);
    ALOGV("called getConfigurations size=%d", (int)configs.size());
    const size_t I = configs.size();

    char locale[RESTABLE_MAX_LOCALE_LEN];
    for (size_t i=0; i<I; i++) {
        char locale[6];
        configs[i].getLocale(locale);
        const size_t J = locales->size();
        size_t j;
@@ -5601,9 +5752,9 @@ void ResTable::print(bool inclValues) const
        printf("mError=0x%x (%s)\n", mError, strerror(mError));
    }
#if 0
    printf("mParams=%c%c-%c%c,\n",
            mParams.language[0], mParams.language[1],
            mParams.country[0], mParams.country[1]);
    char localeStr[RESTABLE_MAX_LOCALE_LEN];
    mParams.getLocale(localeStr);
    printf("mParams=%s,\n" localeStr);
#endif
    size_t pgCount = mPackageGroups.size();
    printf("Package Groups (%d)\n", (int)pgCount);
@@ -5732,7 +5883,7 @@ void ResTable::print(bool inclValues) const
                            continue;
                        }
                        
                        uint16_t esize = dtohs(ent->size);
                        uintptr_t esize = dtohs(ent->size);
                        if ((esize&0x3) != 0) {
                            printf("NON-INTEGER ResTable_entry SIZE: %p\n", (void*)esize);
                            continue;
+2 −1
Original line number Diff line number Diff line
@@ -6,7 +6,8 @@ include $(CLEAR_VARS)
test_src_files := \
    BackupData_test.cpp \
    ObbFile_test.cpp \
    ZipUtils_test.cpp
    ZipUtils_test.cpp \
    ResourceTypes_test.cpp

shared_libraries := \
    libandroidfw \
+150 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <androidfw/ResourceTypes.h>
#include <utils/Log.h>
#include <utils/String8.h>

#include <gtest/gtest.h>
namespace android {

TEST(ResourceTypesTest, ResourceConfig_packAndUnpack2LetterLanguage) {
     ResTable_config config;
     config.packLanguage("en");

     EXPECT_EQ('e', config.language[0]);
     EXPECT_EQ('n', config.language[1]);

     char out[4] = { 1, 1, 1, 1};
     config.unpackLanguage(out);
     EXPECT_EQ('e', out[0]);
     EXPECT_EQ('n', out[1]);
     EXPECT_EQ(0, out[2]);
     EXPECT_EQ(0, out[3]);

     memset(out, 1, sizeof(out));
     config.locale = 0;
     config.unpackLanguage(out);
     EXPECT_EQ(0, out[0]);
     EXPECT_EQ(0, out[1]);
     EXPECT_EQ(0, out[2]);
     EXPECT_EQ(0, out[3]);
}

TEST(ResourceTypesTest, ResourceConfig_packAndUnpack2LetterRegion) {
     ResTable_config config;
     config.packRegion("US");

     EXPECT_EQ('U', config.country[0]);
     EXPECT_EQ('S', config.country[1]);

     char out[4] = { 1, 1, 1, 1};
     config.unpackRegion(out);
     EXPECT_EQ('U', out[0]);
     EXPECT_EQ('S', out[1]);
     EXPECT_EQ(0, out[2]);
     EXPECT_EQ(0, out[3]);
}

TEST(ResourceTypesTest, ResourceConfig_packAndUnpack3LetterLanguage) {
     ResTable_config config;
     config.packLanguage("eng");

     // 1-00110-01 101-00100
     EXPECT_EQ(0x99, config.language[0]);
     EXPECT_EQ(0xa4, config.language[1]);

     char out[4] = { 1, 1, 1, 1};
     config.unpackLanguage(out);
     EXPECT_EQ('e', out[0]);
     EXPECT_EQ('n', out[1]);
     EXPECT_EQ('g', out[2]);
     EXPECT_EQ(0, out[3]);
}

TEST(ResourceTypesTest, ResourceConfig_packAndUnpack3LetterRegion) {
     ResTable_config config;
     config.packRegion("419");

     char out[4] = { 1, 1, 1, 1};
     config.unpackRegion(out);

     EXPECT_EQ('4', out[0]);
     EXPECT_EQ('1', out[1]);
     EXPECT_EQ('9', out[2]);
}

/* static */ void fillIn(const char* lang, const char* country,
        const char* script, const char* variant, ResTable_config* out) {
     memset(out, 0, sizeof(ResTable_config));
     if (lang != NULL) {
         out->packLanguage(lang);
     }

     if (country != NULL) {
         out->packRegion(country);
     }

     if (script != NULL) {
         memcpy(out->localeScript, script, 4);
     }

     if (variant != NULL) {
         memcpy(out->localeVariant, variant, strlen(variant));
     }
}

TEST(ResourceTypesTest, IsMoreSpecificThan) {
    ResTable_config l;
    ResTable_config r;

    fillIn("en", NULL, NULL, NULL, &l);
    fillIn(NULL, NULL, NULL, NULL, &r);

    EXPECT_TRUE(l.isMoreSpecificThan(r));
    EXPECT_FALSE(r.isMoreSpecificThan(l));

    fillIn("eng", NULL, NULL, NULL, &l);
    EXPECT_TRUE(l.isMoreSpecificThan(r));
    EXPECT_FALSE(r.isMoreSpecificThan(l));

    fillIn("eng", "419", NULL, NULL, &r);
    EXPECT_FALSE(l.isMoreSpecificThan(r));
    EXPECT_TRUE(r.isMoreSpecificThan(l));

    fillIn("en", NULL, NULL, NULL, &l);
    fillIn("en", "US", NULL, NULL, &r);
    EXPECT_FALSE(l.isMoreSpecificThan(r));
    EXPECT_TRUE(r.isMoreSpecificThan(l));

    fillIn("en", "US", NULL, NULL, &l);
    fillIn("en", "US", "Latn", NULL, &r);
    EXPECT_FALSE(l.isMoreSpecificThan(r));
    EXPECT_TRUE(r.isMoreSpecificThan(l));

    fillIn("en", "US", NULL, NULL, &l);
    fillIn("en", "US", NULL, "POSIX", &r);
    EXPECT_FALSE(l.isMoreSpecificThan(r));
    EXPECT_TRUE(r.isMoreSpecificThan(l));

    fillIn("en", "US", "Latn", NULL, &l);
    fillIn("en", "US", NULL, "POSIX", &r);
    EXPECT_FALSE(l.isMoreSpecificThan(r));
    EXPECT_TRUE(r.isMoreSpecificThan(l));
}


}  // namespace android.