• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 package android.app;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.content.Context;
23 import android.content.pm.ApplicationInfo;
24 import android.content.res.Resources;
25 import android.content.res.TypedArray;
26 import android.content.res.XmlResourceParser;
27 import android.os.LocaleList;
28 import android.util.AttributeSet;
29 import android.util.Slog;
30 import android.util.Xml;
31 
32 import com.android.internal.util.XmlUtils;
33 
34 import org.xmlpull.v1.XmlPullParserException;
35 
36 import java.io.IOException;
37 import java.lang.annotation.Retention;
38 import java.lang.annotation.RetentionPolicy;
39 import java.util.HashSet;
40 import java.util.Set;
41 
42 /**
43  * The LocaleConfig of an application.
44  * Defined in an XML resource file with an {@code <locale-config>} element and
45  * referenced in the manifest via {@code android:localeConfig} on
46  * {@code <application>}.
47  *
48  * <p>For more information, see
49  * <a href="https://developer.android.com/about/versions/13/features/app-languages#use-localeconfig">
50  * the section on per-app language preferences</a>.
51  *
52  * @attr ref android.R.styleable#LocaleConfig_Locale_name
53  * @attr ref android.R.styleable#AndroidManifestApplication_localeConfig
54  */
55 public class LocaleConfig {
56 
57     private static final String TAG = "LocaleConfig";
58     public static final String TAG_LOCALE_CONFIG = "locale-config";
59     public static final String TAG_LOCALE = "locale";
60     private LocaleList mLocales;
61     private int mStatus;
62 
63     /**
64      * succeeded reading the LocaleConfig structure stored in an XML file.
65      */
66     public static final int STATUS_SUCCESS = 0;
67     /**
68      * No android:localeConfig tag on <application>.
69      */
70     public static final int STATUS_NOT_SPECIFIED = 1;
71     /**
72      * Malformed input in the XML file where the LocaleConfig was stored.
73      */
74     public static final int STATUS_PARSING_FAILED = 2;
75 
76     /** @hide */
77     @IntDef(prefix = { "STATUS_" }, value = {
78             STATUS_SUCCESS,
79             STATUS_NOT_SPECIFIED,
80             STATUS_PARSING_FAILED
81     })
82     @Retention(RetentionPolicy.SOURCE)
83     public @interface Status{}
84 
85     /**
86      * Returns the LocaleConfig for the provided application context
87      *
88      * @param context the context of the application
89      *
90      * @see Context#createPackageContext(String, int).
91      */
LocaleConfig(@onNull Context context)92     public LocaleConfig(@NonNull Context context) {
93         int resId = 0;
94         Resources res = context.getResources();
95         try {
96             //Get the resource id
97             resId = new ApplicationInfo(context.getApplicationInfo()).getLocaleConfigRes();
98             //Get the parser to read XML data
99             XmlResourceParser parser = res.getXml(resId);
100             parseLocaleConfig(parser, res);
101         } catch (Resources.NotFoundException e) {
102             Slog.w(TAG, "The resource file pointed to by the given resource ID isn't found.");
103             mStatus = STATUS_NOT_SPECIFIED;
104         } catch (XmlPullParserException | IOException e) {
105             Slog.w(TAG, "Failed to parse XML configuration from "
106                     + res.getResourceEntryName(resId), e);
107             mStatus = STATUS_PARSING_FAILED;
108         }
109     }
110 
111     /**
112      * Parse the XML content and get the locales supported by the application
113      */
parseLocaleConfig(XmlResourceParser parser, Resources res)114     private void parseLocaleConfig(XmlResourceParser parser, Resources res)
115             throws IOException, XmlPullParserException {
116         XmlUtils.beginDocument(parser, TAG_LOCALE_CONFIG);
117         int outerDepth = parser.getDepth();
118         AttributeSet attrs = Xml.asAttributeSet(parser);
119         Set<String> localeNames = new HashSet<String>();
120         while (XmlUtils.nextElementWithin(parser, outerDepth)) {
121             if (TAG_LOCALE.equals(parser.getName())) {
122                 final TypedArray attributes = res.obtainAttributes(
123                         attrs, com.android.internal.R.styleable.LocaleConfig_Locale);
124                 String nameAttr = attributes.getString(
125                         com.android.internal.R.styleable.LocaleConfig_Locale_name);
126                 localeNames.add(nameAttr);
127                 attributes.recycle();
128             } else {
129                 XmlUtils.skipCurrentTag(parser);
130             }
131         }
132         mStatus = STATUS_SUCCESS;
133         mLocales = LocaleList.forLanguageTags(String.join(",", localeNames));
134     }
135 
136     /**
137      * Returns the locales supported by the specified application.
138      *
139      * <p><b>Note:</b> The locale format should follow the
140      * <a href="https://www.rfc-editor.org/rfc/bcp/bcp47.txt">IETF BCP47 regular expression</a>
141      *
142      * @return the {@link LocaleList}
143      */
getSupportedLocales()144     public @Nullable LocaleList getSupportedLocales() {
145         return mLocales;
146     }
147 
148     /**
149      * Get the status of reading the resource file where the LocaleConfig was stored.
150      *
151      * <p>Distinguish "the application didn't provide the resource file" from "the application
152      * provided malformed input" if {@link #getSupportedLocales()} returns {@code null}.
153      *
154      * @return {@code STATUS_SUCCESS} if the LocaleConfig structure existed in an XML file was
155      * successfully read, or {@code STATUS_NOT_SPECIFIED} if no android:localeConfig tag on
156      * <application> pointing to an XML file that stores the LocaleConfig, or
157      * {@code STATUS_PARSING_FAILED} if the application provided malformed input for the
158      * LocaleConfig structure.
159      *
160      * @see #STATUS_SUCCESS
161      * @see #STATUS_NOT_SPECIFIED
162      * @see #STATUS_PARSING_FAILED
163      *
164      */
getStatus()165     public @Status int getStatus() {
166         return mStatus;
167     }
168 }
169