APP languages detection
Parse the contents of LocaleConfig's XML to obtain the app-specific supported locales
Bug: 203015582
Bug: 193787310
Test: atest LocaleConfigTest
Local test APK
Change-Id: Ie9314e4c09f881f834bf7ea3cc0780d1545e6ce7
This commit is contained in:
@@ -971,6 +971,7 @@ package android {
|
||||
field public static final int listSeparatorTextViewStyle = 16843272; // 0x1010208
|
||||
field public static final int listViewStyle = 16842868; // 0x1010074
|
||||
field public static final int listViewWhiteStyle = 16842869; // 0x1010075
|
||||
field public static final int localeConfig;
|
||||
field public static final int lockTaskMode = 16844013; // 0x10104ed
|
||||
field public static final int logo = 16843454; // 0x10102be
|
||||
field public static final int logoDescription = 16844009; // 0x10104e9
|
||||
@@ -5685,6 +5686,17 @@ package android.app {
|
||||
method @Deprecated public android.view.Window startActivity(String, android.content.Intent);
|
||||
}
|
||||
|
||||
public class LocaleConfig {
|
||||
ctor public LocaleConfig(@NonNull android.content.Context);
|
||||
method public int getStatus();
|
||||
method @Nullable public android.os.LocaleList getSupportedLocales();
|
||||
field public static final int STATUS_NOT_SPECIFIED = 1; // 0x1
|
||||
field public static final int STATUS_PARSING_FAILED = 2; // 0x2
|
||||
field public static final int STATUS_SUCCESS = 0; // 0x0
|
||||
field public static final String TAG_LOCALE = "locale";
|
||||
field public static final String TAG_LOCALE_CONFIG = "locale-config";
|
||||
}
|
||||
|
||||
public class LocaleManager {
|
||||
method @NonNull public android.os.LocaleList getApplicationLocales();
|
||||
method public void setApplicationLocales(@NonNull android.os.LocaleList);
|
||||
|
||||
166
core/java/android/app/LocaleConfig.java
Normal file
166
core/java/android/app/LocaleConfig.java
Normal file
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.
|
||||
*/
|
||||
|
||||
package android.app;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.content.res.XmlResourceParser;
|
||||
import android.os.LocaleList;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Slog;
|
||||
import android.util.Xml;
|
||||
|
||||
import com.android.internal.util.XmlUtils;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* The LocaleConfig of an application.
|
||||
* Defined in an XML resource file with an {@code <locale-config>} element and
|
||||
* referenced in the manifest via {@code android:localeConfig} on
|
||||
* {@code <application>}.
|
||||
*
|
||||
* For more information, see TODO(b/214154050): add link to guide
|
||||
*
|
||||
* @attr ref android.R.styleable#LocaleConfig_Locale_name
|
||||
* @attr ref android.R.styleable#AndroidManifestApplication_localeConfig
|
||||
*/
|
||||
public class LocaleConfig {
|
||||
|
||||
private static final String TAG = "LocaleConfig";
|
||||
public static final String TAG_LOCALE_CONFIG = "locale-config";
|
||||
public static final String TAG_LOCALE = "locale";
|
||||
private LocaleList mLocales;
|
||||
private int mStatus;
|
||||
|
||||
/**
|
||||
* succeeded reading the LocaleConfig structure stored in an XML file.
|
||||
*/
|
||||
public static final int STATUS_SUCCESS = 0;
|
||||
/**
|
||||
* No android:localeConfig tag on <application>.
|
||||
*/
|
||||
public static final int STATUS_NOT_SPECIFIED = 1;
|
||||
/**
|
||||
* Malformed input in the XML file where the LocaleConfig was stored.
|
||||
*/
|
||||
public static final int STATUS_PARSING_FAILED = 2;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(prefix = { "STATUS_" }, value = {
|
||||
STATUS_SUCCESS,
|
||||
STATUS_NOT_SPECIFIED,
|
||||
STATUS_PARSING_FAILED
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Status{}
|
||||
|
||||
/**
|
||||
* Returns the LocaleConfig for the provided application context
|
||||
*
|
||||
* @param context the context of the application
|
||||
*
|
||||
* @see Context#createPackageContext(String, int).
|
||||
*/
|
||||
public LocaleConfig(@NonNull Context context) {
|
||||
int resId = 0;
|
||||
Resources res = context.getResources();
|
||||
try {
|
||||
//Get the resource id
|
||||
resId = new ApplicationInfo(context.getApplicationInfo()).getLocaleConfigRes();
|
||||
//Get the parser to read XML data
|
||||
XmlResourceParser parser = res.getXml(resId);
|
||||
parseLocaleConfig(parser, res);
|
||||
} catch (Resources.NotFoundException e) {
|
||||
Slog.w(TAG, "The resource file pointed to by the given resource ID isn't found.", e);
|
||||
mStatus = STATUS_NOT_SPECIFIED;
|
||||
} catch (XmlPullParserException | IOException e) {
|
||||
Slog.w(TAG, "Failed to parse XML configuration from "
|
||||
+ res.getResourceEntryName(resId), e);
|
||||
mStatus = STATUS_PARSING_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the XML content and get the locales supported by the application
|
||||
*/
|
||||
private void parseLocaleConfig(XmlResourceParser parser, Resources res)
|
||||
throws IOException, XmlPullParserException {
|
||||
XmlUtils.beginDocument(parser, TAG_LOCALE_CONFIG);
|
||||
int outerDepth = parser.getDepth();
|
||||
AttributeSet attrs = Xml.asAttributeSet(parser);
|
||||
Set<String> localeNames = new HashSet<String>();
|
||||
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
|
||||
if (TAG_LOCALE.equals(parser.getName())) {
|
||||
final TypedArray attributes = res.obtainAttributes(
|
||||
attrs, com.android.internal.R.styleable.LocaleConfig_Locale);
|
||||
String nameAttr = attributes.getString(
|
||||
com.android.internal.R.styleable.LocaleConfig_Locale_name);
|
||||
localeNames.add(nameAttr);
|
||||
attributes.recycle();
|
||||
} else {
|
||||
XmlUtils.skipCurrentTag(parser);
|
||||
}
|
||||
}
|
||||
mStatus = STATUS_SUCCESS;
|
||||
mLocales = LocaleList.forLanguageTags(String.join(",", localeNames));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the locales supported by the specified application.
|
||||
*
|
||||
* <p><b>Note:</b> The locale format should follow the
|
||||
* <a href="https://www.rfc-editor.org/rfc/bcp/bcp47.txt">IETF BCP47 regular expression</a>
|
||||
*
|
||||
* @return the {@link LocaleList}
|
||||
*/
|
||||
public @Nullable LocaleList getSupportedLocales() {
|
||||
return mLocales;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the status of reading the resource file where the LocaleConfig was stored.
|
||||
*
|
||||
* <p>Distinguish "the application didn't provide the resource file" from "the application
|
||||
* provided malformed input" if {@link #getSupportedLocales()} returns {@code null}.
|
||||
*
|
||||
* @return {@code STATUS_SUCCESS} if the LocaleConfig structure existed in an XML file was
|
||||
* successfully read, or {@code STATUS_NOT_SPECIFIED} if no android:localeConfig tag on
|
||||
* <application> pointing to an XML file that stores the LocaleConfig, or
|
||||
* {@code STATUS_PARSING_FAILED} if the application provided malformed input for the
|
||||
* LocaleConfig structure.
|
||||
*
|
||||
* @see #STATUS_SUCCESS
|
||||
* @see #STATUS_NOT_SPECIFIED
|
||||
* @see #STATUS_PARSING_FAILED
|
||||
*
|
||||
*/
|
||||
public @Status int getStatus() {
|
||||
return mStatus;
|
||||
}
|
||||
}
|
||||
@@ -1543,6 +1543,11 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
|
||||
@Nullable
|
||||
private ArrayMap<String, String> mAppClassNamesByProcess;
|
||||
|
||||
/**
|
||||
* Resource file providing the application's locales configuration.
|
||||
*/
|
||||
private int localeConfigRes;
|
||||
|
||||
public void dump(Printer pw, String prefix) {
|
||||
dump(pw, prefix, DUMP_FLAG_ALL);
|
||||
}
|
||||
@@ -1660,6 +1665,10 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
|
||||
pw.println(prefix + "requestRawExternalStorageAccess="
|
||||
+ requestRawExternalStorageAccess);
|
||||
}
|
||||
if (localeConfigRes != 0) {
|
||||
pw.println(prefix + "localeConfigRes=0x"
|
||||
+ Integer.toHexString(localeConfigRes));
|
||||
}
|
||||
}
|
||||
pw.println(prefix + "createTimestamp=" + createTimestamp);
|
||||
super.dumpBack(pw, prefix);
|
||||
@@ -1891,6 +1900,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
|
||||
memtagMode = orig.memtagMode;
|
||||
nativeHeapZeroInitialized = orig.nativeHeapZeroInitialized;
|
||||
requestRawExternalStorageAccess = orig.requestRawExternalStorageAccess;
|
||||
localeConfigRes = orig.localeConfigRes;
|
||||
createTimestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
@@ -1993,6 +2003,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
|
||||
dest.writeString(mAppClassNamesByProcess.valueAt(i));
|
||||
}
|
||||
}
|
||||
dest.writeInt(localeConfigRes);
|
||||
}
|
||||
|
||||
public static final @android.annotation.NonNull Parcelable.Creator<ApplicationInfo> CREATOR
|
||||
@@ -2088,6 +2099,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
|
||||
mAppClassNamesByProcess.put(source.readString(), source.readString());
|
||||
}
|
||||
}
|
||||
localeConfigRes = source.readInt();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2631,4 +2643,16 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
|
||||
}
|
||||
return className;
|
||||
}
|
||||
|
||||
/** @hide */ public void setLocaleConfigRes(int value) { localeConfigRes = value; }
|
||||
|
||||
/**
|
||||
* Return the resource id pointing to the resource file that provides the application's locales
|
||||
* configuration.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public int getLocaleConfigRes() {
|
||||
return localeConfigRes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,6 +375,8 @@ public interface ParsingPackage extends ParsingPackageRead {
|
||||
ParsingPackage setResetEnabledSettingsOnAppDataCleared(
|
||||
boolean resetEnabledSettingsOnAppDataCleared);
|
||||
|
||||
ParsingPackage setLocaleConfigRes(int localeConfigRes);
|
||||
|
||||
// TODO(b/135203078): This class no longer has access to ParsedPackage, find a replacement
|
||||
// for moving to the next step
|
||||
@CallSuper
|
||||
|
||||
@@ -559,6 +559,8 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden,
|
||||
private UUID mStorageUuid;
|
||||
private long mLongVersionCode;
|
||||
|
||||
private int mLocaleConfigRes;
|
||||
|
||||
@VisibleForTesting
|
||||
public ParsingPackageImpl(@NonNull String packageName, @NonNull String baseApkPath,
|
||||
@NonNull String path, @Nullable TypedArray manifestArray) {
|
||||
@@ -1136,6 +1138,7 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden,
|
||||
appInfo.setSplitResourcePaths(splitCodePaths);
|
||||
appInfo.setVersionCode(mLongVersionCode);
|
||||
appInfo.setAppClassNamesByProcess(buildAppClassNamesByProcess());
|
||||
appInfo.setLocaleConfigRes(mLocaleConfigRes);
|
||||
|
||||
return appInfo;
|
||||
}
|
||||
@@ -1314,6 +1317,7 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden,
|
||||
dest.writeInt(this.memtagMode);
|
||||
dest.writeInt(this.nativeHeapZeroInitialized);
|
||||
sForBoolean.parcel(this.requestRawExternalStorageAccess, dest, flags);
|
||||
dest.writeInt(this.mLocaleConfigRes);
|
||||
}
|
||||
|
||||
public ParsingPackageImpl(Parcel in) {
|
||||
@@ -1461,6 +1465,7 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden,
|
||||
this.memtagMode = in.readInt();
|
||||
this.nativeHeapZeroInitialized = in.readInt();
|
||||
this.requestRawExternalStorageAccess = sForBoolean.unparcel(in);
|
||||
this.mLocaleConfigRes = in.readInt();
|
||||
assignDerivedFields();
|
||||
}
|
||||
|
||||
@@ -2279,6 +2284,11 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden,
|
||||
return nativeHeapZeroInitialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLocaleConfigRes() {
|
||||
return mLocaleConfigRes;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Boolean hasRequestRawExternalStorageAccess() {
|
||||
@@ -2936,4 +2946,10 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden,
|
||||
resetEnabledSettingsOnAppDataCleared);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParsingPackageImpl setLocaleConfigRes(int value) {
|
||||
mLocaleConfigRes = value;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -343,4 +343,11 @@ public interface ParsingPackageRead extends PkgWithoutStateAppInfo, PkgWithoutSt
|
||||
* @see R.styleable#AndroidManifestApplication_resetEnabledSettingsOnAppDataCleared
|
||||
*/
|
||||
boolean isResetEnabledSettingsOnAppDataCleared();
|
||||
|
||||
/**
|
||||
* The resource ID used to provide the application's locales configuration.
|
||||
*
|
||||
* @see R.styleable#AndroidManifestApplication_localeConfig
|
||||
*/
|
||||
int getLocaleConfigRes();
|
||||
}
|
||||
|
||||
@@ -2331,6 +2331,7 @@ public class ParsingPackageUtils {
|
||||
.setTheme(resId(R.styleable.AndroidManifestApplication_theme, sa))
|
||||
.setDataExtractionRules(
|
||||
resId(R.styleable.AndroidManifestApplication_dataExtractionRules, sa))
|
||||
.setLocaleConfigRes(resId(R.styleable.AndroidManifestApplication_localeConfig, sa))
|
||||
// Strings
|
||||
.setClassLoaderName(string(R.styleable.AndroidManifestApplication_classLoader, sa))
|
||||
.setRequiredAccountType(string(R.styleable.AndroidManifestApplication_requiredAccountType, sa))
|
||||
|
||||
@@ -9645,4 +9645,11 @@
|
||||
<attr name="iconfactoryBadgeSize" format="dimension"/>
|
||||
<!-- Perceptual luminance of a color, in accessibility friendly color space. From 0 to 100. -->
|
||||
<attr name="lStar" format="float"/>
|
||||
</resources>
|
||||
|
||||
<!-- The attributes of the {@code <locale>} tag within {@code <locale-config>}. -->
|
||||
<declare-styleable name="LocaleConfig_Locale">
|
||||
<!-- The <a href="https://www.rfc-editor.org/rfc/bcp/bcp47.txt">IETF BCP47 language tag</a>
|
||||
of the supported locale. {@link android.app.LocaleConfig} -->
|
||||
<attr name="name" />
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
|
||||
@@ -1519,6 +1519,9 @@
|
||||
<!-- An XML resource with the application's Network Security Config. -->
|
||||
<attr name="networkSecurityConfig" format="reference" />
|
||||
|
||||
<!-- An XML resource with the application's {@link android.app.LocaleConfig} -->
|
||||
<attr name="localeConfig" format="reference" />
|
||||
|
||||
<!-- When an application is partitioned into splits, this is the name of the
|
||||
split that contains the defined component. -->
|
||||
<attr name="splitName" format="string" />
|
||||
@@ -1801,6 +1804,7 @@
|
||||
<attr name="maxAspectRatio" />
|
||||
<attr name="minAspectRatio" />
|
||||
<attr name="networkSecurityConfig" />
|
||||
<attr name="localeConfig" />
|
||||
<!-- Declare the category of this app. Categories are used to cluster multiple apps
|
||||
together into meaningful groups, such as when summarizing battery, network, or
|
||||
disk usage. Apps should only define this value when they fit well into one of
|
||||
|
||||
@@ -3252,6 +3252,7 @@
|
||||
<public name="supportsStylusHandwriting" />
|
||||
<!-- @hide @SystemApi -->
|
||||
<public name="gameSessionService" />
|
||||
<public name="localeConfig" />
|
||||
</staging-public-group>
|
||||
|
||||
<staging-public-group type="id" first-id="0x01de0000">
|
||||
|
||||
@@ -130,6 +130,7 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag
|
||||
AndroidPackage::getLabelRes,
|
||||
AndroidPackage::getLargestWidthLimitDp,
|
||||
AndroidPackage::getLogo,
|
||||
AndroidPackage::getLocaleConfigRes,
|
||||
AndroidPackage::getManageSpaceActivityName,
|
||||
AndroidPackage::getMemtagMode,
|
||||
AndroidPackage::getMinSdkVersion,
|
||||
|
||||
Reference in New Issue
Block a user