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