diff options
author | Roozbeh Pournader <roozbeh@google.com> | 2015-12-18 14:22:14 -0800 |
---|---|---|
committer | Roozbeh Pournader <roozbeh@google.com> | 2016-01-04 13:53:54 -0800 |
commit | 1c686f2ce6cbfa3fdb598f452aa31d38f3eb2320 (patch) | |
tree | 231683457601efdf2050ebc9ae863b99c3f056ed | |
parent | c1f9e7c4875e791e108c79382508aab8d171f25f (diff) |
Avoid matching system locales in locale negotiation
Also:
1. Add AssetManager method for finding non-system locales: This is
used in per-app locale negotiation. (Normally,
AssetManager#getLocales() returns both system and non-system
locales.)
2. Match pseudolocales correctly in locale negotiation.
Bug: 25800576
Bug: 26236938
Change-Id: I116caf3a91c290deb4ad68b291c65b7035b18dd4
-rw-r--r-- | core/java/android/content/res/AssetManager.java | 12 | ||||
-rw-r--r-- | core/java/android/content/res/Resources.java | 16 | ||||
-rw-r--r-- | core/java/android/util/LocaleList.java | 41 | ||||
-rw-r--r-- | core/jni/android_util_AssetManager.cpp | 16 | ||||
-rw-r--r-- | include/androidfw/AssetManager.h | 17 | ||||
-rw-r--r-- | include/androidfw/ResourceTypes.h | 15 | ||||
-rw-r--r-- | libs/androidfw/AssetManager.cpp | 15 | ||||
-rw-r--r-- | libs/androidfw/ResourceTypes.cpp | 50 |
8 files changed, 141 insertions, 41 deletions
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index 7669053cc803..ee6aec2910aa 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -698,6 +698,18 @@ public final class AssetManager implements AutoCloseable { */ public native final String[] getLocales(); + /** + * Same as getLocales(), except that locales that are only provided by the system (i.e. those + * present in framework-res.apk or its overlays) will not be listed. + * + * For example, if the "system" assets support English, French, and German, and the additional + * assets support Cherokee and French, getLocales() would return + * [Cherokee, English, French, German], while getNonSystemLocales() would return + * [Cherokee, French]. + * {@hide} + */ + public native final String[] getNonSystemLocales(); + /** {@hide} */ public native final Configuration[] getSizeConfigurations(); diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 60c6e825e1be..c460746ba514 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -1976,7 +1976,21 @@ public class Resources { if (setLocalesToDefault || mResolvedLocale == null || (configChanges & Configuration.NATIVE_CONFIG_LOCALE) != 0) { - mResolvedLocale = locales.getFirstMatch(mAssets.getLocales()); + if (locales.size() == 1) { + // This is an optimization to avoid the JNI call(s) when the result of + // getFirstMatch() does not depend on the supported locales. + mResolvedLocale = locales.getPrimary(); + } else { + String[] supportedLocales = mAssets.getNonSystemLocales(); + if (LocaleList.isPseudoLocalesOnly(supportedLocales)) { + // We fallback to all locales (including system locales) if there was no + // locale specifically supported by the assets. This is to properly support + // apps that only rely on the shared system assets and don't need assets of + // their own. + supportedLocales = mAssets.getLocales(); + } + mResolvedLocale = locales.getFirstMatch(supportedLocales); + } } mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc, adjustLanguageTag(mResolvedLocale.toLanguageTag()), diff --git a/core/java/android/util/LocaleList.java b/core/java/android/util/LocaleList.java index 1becfb4a0e23..65a436b89818 100644 --- a/core/java/android/util/LocaleList.java +++ b/core/java/android/util/LocaleList.java @@ -219,6 +219,20 @@ public final class LocaleList implements Parcelable { } } + private static final String STRING_EN_XA = "en-XA"; + private static final String STRING_AR_XB = "ar-XB"; + private static final Locale LOCALE_EN_XA = new Locale("en", "XA"); + private static final Locale LOCALE_AR_XB = new Locale("ar", "XB"); + private static final int NUM_PSEUDO_LOCALES = 2; + + private static boolean isPseudoLocale(String locale) { + return STRING_EN_XA.equals(locale) || STRING_AR_XB.equals(locale); + } + + private static boolean isPseudoLocale(Locale locale) { + return LOCALE_EN_XA.equals(locale) || LOCALE_AR_XB.equals(locale); + } + private static int matchScore(Locale supported, Locale desired) { if (supported.equals(desired)) { return 1; // return early so we don't do unnecessary computation @@ -226,6 +240,11 @@ public final class LocaleList implements Parcelable { if (!supported.getLanguage().equals(desired.getLanguage())) { return 0; } + if (isPseudoLocale(supported) || isPseudoLocale(desired)) { + // The locales are not the same, but the languages are the same, and one of the locales + // is a pseudo-locale. So this is not a match. + return 0; + } // There is no match if the two locales use different scripts. This will most imporantly // take care of traditional vs simplified Chinese. final String supportedScr = getLikelyScript(supported); @@ -247,7 +266,6 @@ public final class LocaleList implements Parcelable { if (mList.length == 0) { // empty locale list return null; } - // TODO: Figure out what to if en-XA or ar-XB are in the locale list int bestIndex = Integer.MAX_VALUE; for (String tag : supportedLocales) { final Locale supportedLocale = Locale.forLanguageTag(tag); @@ -271,6 +289,27 @@ public final class LocaleList implements Parcelable { } } + /** + * Returns true if the array of locale tags only contains empty locales and pseudolocales. + * Assumes that there is no repetition in the input. + * {@hide} + */ + public static boolean isPseudoLocalesOnly(String[] supportedLocales) { + if (supportedLocales.length > NUM_PSEUDO_LOCALES + 1) { + // This is for optimization. Since there's no repetition in the input, if we have more + // than the number of pseudo-locales plus one for the empty string, it's guaranteed + // that we have some meaninful locale in the list, so the list is not "practically + // empty". + return false; + } + for (String locale : supportedLocales) { + if (!locale.isEmpty() && !isPseudoLocale(locale)) { + return false; + } + } + return true; + } + private final static Object sLock = new Object(); @GuardedBy("sLock") diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp index 90606a35b5a2..3473d9dab732 100644 --- a/core/jni/android_util_AssetManager.cpp +++ b/core/jni/android_util_AssetManager.cpp @@ -578,7 +578,7 @@ static jboolean android_content_AssetManager_isUpToDate(JNIEnv* env, jobject cla return am->isUpToDate() ? JNI_TRUE : JNI_FALSE; } -static jobjectArray android_content_AssetManager_getLocales(JNIEnv* env, jobject clazz) +static jobjectArray getLocales(JNIEnv* env, jobject clazz, bool includeSystemLocales) { Vector<String8> locales; @@ -587,7 +587,7 @@ static jobjectArray android_content_AssetManager_getLocales(JNIEnv* env, jobject return NULL; } - am->getLocales(&locales); + am->getLocales(&locales, includeSystemLocales); const int N = locales.size(); @@ -608,6 +608,16 @@ static jobjectArray android_content_AssetManager_getLocales(JNIEnv* env, jobject return result; } +static jobjectArray android_content_AssetManager_getLocales(JNIEnv* env, jobject clazz) +{ + return getLocales(env, clazz, true /* include system locales */); +} + +static jobjectArray android_content_AssetManager_getNonSystemLocales(JNIEnv* env, jobject clazz) +{ + return getLocales(env, clazz, false /* don't include system locales */); +} + static jobject constructConfigurationObject(JNIEnv* env, const ResTable_config& config) { jobject result = env->NewObject(gConfigurationOffsets.classObject, gConfigurationOffsets.constructor); @@ -2154,6 +2164,8 @@ static const JNINativeMethod gAssetManagerMethods[] = { // Resources. { "getLocales", "()[Ljava/lang/String;", (void*) android_content_AssetManager_getLocales }, + { "getNonSystemLocales", "()[Ljava/lang/String;", + (void*) android_content_AssetManager_getNonSystemLocales }, { "getSizeConfigurations", "()[Landroid/content/res/Configuration;", (void*) android_content_AssetManager_getSizeConfigurations }, { "setConfiguration", "!(IILjava/lang/String;IIIIIIIIIIIIII)V", diff --git a/include/androidfw/AssetManager.h b/include/androidfw/AssetManager.h index 3d4e47d84c65..914ac3d52421 100644 --- a/include/androidfw/AssetManager.h +++ b/include/androidfw/AssetManager.h @@ -100,16 +100,17 @@ public: * then on success, *cookie is set to the value corresponding to the * newly-added asset source. */ - bool addAssetPath(const String8& path, int32_t* cookie, bool appAsLib=false); + bool addAssetPath(const String8& path, int32_t* cookie, + bool appAsLib=false, bool isSystemAsset=false); bool addOverlayPath(const String8& path, int32_t* cookie); - /* + /* * Convenience for adding the standard system assets. Uses the * ANDROID_ROOT environment variable to find them. */ bool addDefaultAssets(); - /* + /* * Iterate over the asset paths in this manager. (Previously * added via addAssetPath() and addDefaultAssets().) On first call, * 'cookie' must be 0, resulting in the first cookie being returned. @@ -118,7 +119,7 @@ public: */ int32_t nextAssetPath(const int32_t cookie) const; - /* + /* * Return an asset path in the manager. 'which' must be between 0 and * countAssetPaths(). */ @@ -221,11 +222,11 @@ public: * the current data. */ bool isUpToDate(); - + /** * Get the known locales for this asset manager object. */ - void getLocales(Vector<String8>* locales) const; + void getLocales(Vector<String8>* locales, bool includeSystemLocales=true) const; /** * Generate idmap data to translate resources IDs between a package and a @@ -237,11 +238,13 @@ public: private: struct asset_path { - asset_path() : path(""), type(kFileTypeRegular), idmap(""), isSystemOverlay(false) {} + asset_path() : path(""), type(kFileTypeRegular), idmap(""), + isSystemOverlay(false), isSystemAsset(false) {} String8 path; FileType type; String8 idmap; bool isSystemOverlay; + bool isSystemAsset; }; Asset* openInPathLocked(const char* fileName, AccessMode mode, diff --git a/include/androidfw/ResourceTypes.h b/include/androidfw/ResourceTypes.h index 49b6333e8a4d..428a2b831e1a 100644 --- a/include/androidfw/ResourceTypes.h +++ b/include/androidfw/ResourceTypes.h @@ -1552,9 +1552,9 @@ public: status_t add(Asset* asset, const int32_t cookie=-1, bool copyData=false); status_t add(Asset* asset, Asset* idmapAsset, const int32_t cookie=-1, bool copyData=false, - bool appAsLib=false); + bool appAsLib=false, bool isSystemAsset=false); - status_t add(ResTable* src); + status_t add(ResTable* src, bool isSystemAsset=false); status_t addEmpty(const int32_t cookie); status_t getError() const; @@ -1822,9 +1822,9 @@ public: // Return the configurations (ResTable_config) that we know about void getConfigurations(Vector<ResTable_config>* configs, bool ignoreMipmap=false, - bool ignoreAndroidPackage=false) const; + bool ignoreAndroidPackage=false, bool includeSystemConfigs=true) const; - void getLocales(Vector<String8>* locales) const; + void getLocales(Vector<String8>* locales, bool includeSystemLocales=true) const; // Generate an idmap. // @@ -1860,7 +1860,7 @@ private: typedef Vector<Type*> TypeList; status_t addInternal(const void* data, size_t size, const void* idmapData, size_t idmapDataSize, - bool appAsLib, const int32_t cookie, bool copyData); + bool appAsLib, const int32_t cookie, bool copyData, bool isSystemAsset=false); ssize_t getResourcePackageIndex(uint32_t resID) const; @@ -1873,10 +1873,11 @@ private: size_t nameLen, uint32_t* outTypeSpecFlags) const; status_t parsePackage( - const ResTable_package* const pkg, const Header* const header, bool appAsLib); + const ResTable_package* const pkg, const Header* const header, + bool appAsLib, bool isSystemAsset); void print_value(const Package* pkg, const Res_value& value) const; - + mutable Mutex mLock; status_t mError; diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp index 8a03b94492d8..6913f43a87c3 100644 --- a/libs/androidfw/AssetManager.cpp +++ b/libs/androidfw/AssetManager.cpp @@ -176,7 +176,8 @@ AssetManager::~AssetManager(void) delete[] mVendor; } -bool AssetManager::addAssetPath(const String8& path, int32_t* cookie, bool appAsLib) +bool AssetManager::addAssetPath( + const String8& path, int32_t* cookie, bool appAsLib, bool isSystemAsset) { AutoMutex _l(mLock); @@ -222,6 +223,7 @@ bool AssetManager::addAssetPath(const String8& path, int32_t* cookie, bool appAs } delete manifestAsset; + ap.isSystemAsset = isSystemAsset; mAssetPaths.add(ap); // new paths are always added at the end @@ -233,6 +235,7 @@ bool AssetManager::addAssetPath(const String8& path, int32_t* cookie, bool appAs // Load overlays, if any asset_path oap; for (size_t idx = 0; mZipSet.getOverlay(ap.path, idx, &oap); idx++) { + oap.isSystemAsset = isSystemAsset; mAssetPaths.add(oap); } #endif @@ -340,7 +343,7 @@ bool AssetManager::addDefaultAssets() String8 path(root); path.appendPath(kSystemAssets); - return addAssetPath(path, NULL); + return addAssetPath(path, NULL, false /* appAsLib */, true /* isSystemAsset */); } int32_t AssetManager::nextAssetPath(const int32_t cookie) const @@ -682,10 +685,10 @@ bool AssetManager::appendPathToResTable(const asset_path& ap, bool appAsLib) con ALOGV("Installing resource asset %p in to table %p\n", ass, mResources); if (sharedRes != NULL) { ALOGV("Copying existing resources for %s", ap.path.string()); - mResources->add(sharedRes); + mResources->add(sharedRes, ap.isSystemAsset); } else { ALOGV("Parsing resources for %s", ap.path.string()); - mResources->add(ass, idmap, nextEntryIdx + 1, !shared, appAsLib); + mResources->add(ass, idmap, nextEntryIdx + 1, !shared, appAsLib, ap.isSystemAsset); } onlyEmptyResources = false; @@ -831,11 +834,11 @@ bool AssetManager::isUpToDate() return mZipSet.isUpToDate(); } -void AssetManager::getLocales(Vector<String8>* locales) const +void AssetManager::getLocales(Vector<String8>* locales, bool includeSystemLocales) const { ResTable* res = mResources; if (res != NULL) { - res->getLocales(locales); + res->getLocales(locales, includeSystemLocales); } const size_t numLocales = locales->size(); diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index 21b543eefa01..44f92c7bf3d6 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -3080,13 +3080,16 @@ struct ResTable::Package // table that defined the package); the ones after are skins on top of it. struct ResTable::PackageGroup { - PackageGroup(ResTable* _owner, const String16& _name, uint32_t _id, bool appAsLib) + PackageGroup( + ResTable* _owner, const String16& _name, uint32_t _id, + bool appAsLib, bool _isSystemAsset) : owner(_owner) , name(_name) , id(_id) , largestTypeId(0) , bags(NULL) , dynamicRefTable(static_cast<uint8_t>(_id), appAsLib) + , isSystemAsset(_isSystemAsset) { } ~PackageGroup() { @@ -3178,6 +3181,10 @@ struct ResTable::PackageGroup // by having these tables in a per-package scope rather than // per-package-group. DynamicRefTable dynamicRefTable; + + // If the package group comes from a system asset. Used in + // determining non-system locales. + const bool isSystemAsset; }; struct ResTable::bag_set @@ -3572,8 +3579,9 @@ status_t ResTable::add(Asset* asset, const int32_t cookie, bool copyData) { copyData); } -status_t ResTable::add(Asset* asset, Asset* idmapAsset, const int32_t cookie, bool copyData, - bool appAsLib) { +status_t ResTable::add( + Asset* asset, Asset* idmapAsset, const int32_t cookie, bool copyData, + bool appAsLib, bool isSystemAsset) { const void* data = asset->getBuffer(true); if (data == NULL) { ALOGW("Unable to get buffer of resource asset file"); @@ -3592,20 +3600,21 @@ status_t ResTable::add(Asset* asset, Asset* idmapAsset, const int32_t cookie, bo } return addInternal(data, static_cast<size_t>(asset->getLength()), - idmapData, idmapSize, appAsLib, cookie, copyData); + idmapData, idmapSize, appAsLib, cookie, copyData, isSystemAsset); } -status_t ResTable::add(ResTable* src) +status_t ResTable::add(ResTable* src, bool isSystemAsset) { mError = src->mError; - for (size_t i=0; i<src->mHeaders.size(); i++) { + for (size_t i=0; i < src->mHeaders.size(); i++) { mHeaders.add(src->mHeaders[i]); } - for (size_t i=0; i<src->mPackageGroups.size(); i++) { + for (size_t i=0; i < src->mPackageGroups.size(); i++) { PackageGroup* srcPg = src->mPackageGroups[i]; - PackageGroup* pg = new PackageGroup(this, srcPg->name, srcPg->id, false); + PackageGroup* pg = new PackageGroup(this, srcPg->name, srcPg->id, + false /* appAsLib */, isSystemAsset || srcPg->isSystemAsset); for (size_t j=0; j<srcPg->packages.size(); j++) { pg->packages.add(srcPg->packages[j]); } @@ -3646,7 +3655,7 @@ status_t ResTable::addEmpty(const int32_t cookie) { } status_t ResTable::addInternal(const void* data, size_t dataSize, const void* idmapData, size_t idmapDataSize, - bool appAsLib, const int32_t cookie, bool copyData) + bool appAsLib, const int32_t cookie, bool copyData, bool isSystemAsset) { if (!data) { return NO_ERROR; @@ -3749,7 +3758,8 @@ status_t ResTable::addInternal(const void* data, size_t dataSize, const void* id return (mError=BAD_TYPE); } - if (parsePackage((ResTable_package*)chunk, header, appAsLib) != NO_ERROR) { + if (parsePackage( + (ResTable_package*)chunk, header, appAsLib, isSystemAsset) != NO_ERROR) { return mError; } curPackage++; @@ -5663,7 +5673,7 @@ const DynamicRefTable* ResTable::getDynamicRefTableForCookie(int32_t cookie) con } void ResTable::getConfigurations(Vector<ResTable_config>* configs, bool ignoreMipmap, - bool ignoreAndroidPackage) const { + bool ignoreAndroidPackage, bool includeSystemConfigs) const { const size_t packageCount = mPackageGroups.size(); String16 android("android"); for (size_t i = 0; i < packageCount; i++) { @@ -5671,6 +5681,9 @@ void ResTable::getConfigurations(Vector<ResTable_config>* configs, bool ignoreMi if (ignoreAndroidPackage && android == packageGroup->name) { continue; } + if (!includeSystemConfigs && packageGroup->isSystemAsset) { + continue; + } const size_t typeCount = packageGroup->types.size(); for (size_t j = 0; j < typeCount; j++) { const TypeList& typeList = packageGroup->types[j]; @@ -5707,11 +5720,14 @@ void ResTable::getConfigurations(Vector<ResTable_config>* configs, bool ignoreMi } } -void ResTable::getLocales(Vector<String8>* locales) const +void ResTable::getLocales(Vector<String8>* locales, bool includeSystemLocales) const { Vector<ResTable_config> configs; ALOGV("calling getConfigurations"); - getConfigurations(&configs); + getConfigurations(&configs, + false /* ignoreMipmap */, + false /* ignoreAndroidPackage */, + includeSystemLocales /* includeSystemConfigs */); ALOGV("called getConfigurations size=%d", (int)configs.size()); const size_t I = configs.size(); @@ -5937,7 +5953,7 @@ status_t ResTable::getEntry( } status_t ResTable::parsePackage(const ResTable_package* const pkg, - const Header* const header, bool appAsLib) + const Header* const header, bool appAsLib, bool isSystemAsset) { const uint8_t* base = (const uint8_t*)pkg; status_t err = validate_chunk(&pkg->header, sizeof(*pkg) - sizeof(pkg->typeIdOffset), @@ -5985,8 +6001,8 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, if (id >= 256) { LOG_ALWAYS_FATAL("Package id out of range"); return NO_ERROR; - } else if (id == 0 || appAsLib) { - // This is a library so assign an ID + } else if (id == 0 || appAsLib || isSystemAsset) { + // This is a library or a system asset, so assign an ID id = mNextPackageId++; } @@ -6018,7 +6034,7 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, char16_t tmpName[sizeof(pkg->name)/sizeof(pkg->name[0])]; strcpy16_dtoh(tmpName, pkg->name, sizeof(pkg->name)/sizeof(pkg->name[0])); - group = new PackageGroup(this, String16(tmpName), id, appAsLib); + group = new PackageGroup(this, String16(tmpName), id, appAsLib, isSystemAsset); if (group == NULL) { delete package; return (mError=NO_MEMORY); |