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
This commit is contained in:
@@ -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();
|
||||
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user