Merge "Regenerate date for CLDR-18367" into main am: 0eabc9f94e Original change: https://android-review.googlesource.com/c/platform/external/icu/+/3556503 Change-Id: I340ff836f4b10c649d1ba4b0eaea17355e01bf2f Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Android.bp b/Android.bp index 93f2e36..bc47e01 100644 --- a/Android.bp +++ b/Android.bp
@@ -87,7 +87,8 @@ aconfig_declarations { name: "icu_aconfig_flags", package: "com.android.icu", - container: "system", + container: "com.android.i18n", + exportable: true, srcs: [ "icu.aconfig", ],
diff --git a/android_icu4j/Android.bp b/android_icu4j/Android.bp index fd6edc4..9cd4d77 100644 --- a/android_icu4j/Android.bp +++ b/android_icu4j/Android.bp
@@ -97,18 +97,18 @@ name: "core-repackaged-icu4j", installable: false, srcs: [":android_icu4j_repackaged_src_files"], + // The resource files are generated in the downstream branch master-icu-dev. + java_resource_dirs: ["resources"], libs: [ "unsupportedappusage", // Need framework-api-annotations-lib for @FlaggedApi. // Put framework-api-annotations into libs to avoid exposing the definition of framework's // annotations from ICU (wrong place) instead of framework (correct place). "framework-annotations-lib", - // We only need the aconfig library for @FlaggedApi. We can move it to static_libs - // if we need it for flagging features in the future. + ], + static_libs: [ "icu_aconfig_flags_lib", ], - // The resource files are generated in the downstream branch master-icu-dev. - java_resource_dirs: ["resources"], sdk_version: "none", system_modules: "art-module-public-api-stubs-system-modules", @@ -154,6 +154,9 @@ installable: true, hostdex: false, + libs: [ + "framework-annotations-lib", + ], static_libs: ["core-repackaged-icu4j"], // It is important that core-icu4j is restricted to only use stable APIs from the ART module @@ -442,6 +445,10 @@ "--show-single-annotation libcore.api.CorePlatformApi ", ], + libs: [ + "icu_aconfig_flags_lib", + ], + // Don't copy any output files to the dist. no_dist: true, @@ -543,6 +550,17 @@ "src/main/tests/**/*.java", "testing/src/**/*.java", ], + libs: [ + "icu_aconfig_flags_lib", + "telephony-common", + "telephony_flags_core_java_lib", + ], + static_libs: [ + "flag-junit", + "junit", + "platform-parametric-runner-lib", + "platform-test-annotations", + ], } // Equivalent to android-icu4j-tests, excluding the tests under testing/.
diff --git a/android_icu4j/api/legacy_platform/current.txt b/android_icu4j/api/legacy_platform/current.txt index ca3fafb..6b178a6 100644 --- a/android_icu4j/api/legacy_platform/current.txt +++ b/android_icu4j/api/legacy_platform/current.txt
@@ -70,6 +70,12 @@ method public static com.android.i18n.timezone.DebugInfo getDebugInfo(); } + @FlaggedApi("com.android.icu.telephony_lookup_mcc_extension") public final class MobileCountries { + method @NonNull public java.util.Set<java.lang.String> getCountryIsoCodes(); + method @NonNull public String getDefaultCountryIsoCode(); + method @NonNull public String getMcc(); + } + public final class TelephonyLookup { method public static com.android.i18n.timezone.TelephonyLookup createInstance(String) throws java.io.IOException; method public static com.android.i18n.timezone.TelephonyLookup getInstance(); @@ -84,6 +90,7 @@ } public final class TelephonyNetworkFinder { + method @FlaggedApi("com.android.icu.telephony_lookup_mcc_extension") @Nullable public com.android.i18n.timezone.MobileCountries findCountriesByMcc(@NonNull String); method public com.android.i18n.timezone.TelephonyNetwork findNetworkByMccMnc(String, String); }
diff --git a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/MobileCountries.java b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/MobileCountries.java new file mode 100644 index 0000000..a760cf3 --- /dev/null +++ b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/MobileCountries.java
@@ -0,0 +1,117 @@ +/* + * Copyright (C) 2024 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 com.android.i18n.timezone; + +import static java.util.stream.Collectors.toUnmodifiableSet; + +import android.annotation.NonNull; + +import com.android.icu.Flags; + +import java.util.Objects; +import java.util.Set; + +/** + * Information about the countries where an MCC operates. + * + * @hide + */ +@libcore.api.CorePlatformApi +@android.annotation.FlaggedApi(Flags.FLAG_TELEPHONY_LOOKUP_MCC_EXTENSION) +public final class MobileCountries { + + private final String mcc; + private final String defaultCountryIsoCode; + private final Set<String> countryIsoCodes; + + public static MobileCountries create(String mcc, Set<String> countryIsoCodes, + String defaultCountryIsoCode) { + Objects.requireNonNull(mcc); + Objects.requireNonNull(countryIsoCodes); + Objects.requireNonNull(defaultCountryIsoCode); + + Set<String> normalizedCountryIsos = countryIsoCodes.stream() + .map(XmlUtils::normalizeCountryIso) + .collect(toUnmodifiableSet()); + return new MobileCountries(Objects.requireNonNull(mcc), normalizedCountryIsos, + XmlUtils.normalizeCountryIso(defaultCountryIsoCode)); + } + + private MobileCountries(String mcc, Set<String> countryIsoCodes, + String defaultCountryIsoCode) { + if (!countryIsoCodes.contains(Objects.requireNonNull(defaultCountryIsoCode))) { + throw new IllegalArgumentException( + "The default country ISO code was not found in the set of country ISO codes"); + } + this.mcc = Objects.requireNonNull(mcc); + this.countryIsoCodes = Objects.requireNonNull(countryIsoCodes); + this.defaultCountryIsoCode = Objects.requireNonNull(defaultCountryIsoCode); + } + + /** + * Returns the MCC of the network. + */ + @libcore.api.CorePlatformApi + @NonNull + public String getMcc() { + return mcc; + } + + /** + * Returns the countries in which this MCC is used as an ISO 3166 alpha-2 (lower case). + */ + @libcore.api.CorePlatformApi + @NonNull + public Set<String> getCountryIsoCodes() { + return countryIsoCodes; + } + + /** + * Returns the default country for this MCC as an ISO 3166 alpha-2 (lower case). + */ + @libcore.api.CorePlatformApi + @NonNull + public String getDefaultCountryIsoCode() { + return defaultCountryIsoCode; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o instanceof MobileCountries that) { + return mcc.equals(that.mcc) && countryIsoCodes.equals(that.countryIsoCodes) + && defaultCountryIsoCode.equals(that.defaultCountryIsoCode); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(mcc, countryIsoCodes, defaultCountryIsoCode); + } + + @Override + public String toString() { + return "MobileCountries{" + + "mcc=" + mcc + + ", countryIsoCodes='" + countryIsoCodes + + ", defaultCountryIsoCode='" + defaultCountryIsoCode + '\'' + + '}'; + } +}
diff --git a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/TelephonyLookup.java b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/TelephonyLookup.java index 826ee67..f910524 100644 --- a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/TelephonyLookup.java +++ b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/TelephonyLookup.java
@@ -17,6 +17,7 @@ package com.android.i18n.timezone; import static com.android.i18n.timezone.XmlUtils.checkOnEndTag; +import static com.android.i18n.timezone.XmlUtils.consumeText; import static com.android.i18n.timezone.XmlUtils.consumeUntilEndTag; import static com.android.i18n.timezone.XmlUtils.findNextStartTagOrEndTagNoRecurse; import static com.android.i18n.timezone.XmlUtils.findNextStartTagOrThrowNoRecurse; @@ -25,6 +26,7 @@ import com.android.i18n.timezone.TelephonyNetwork.MccMnc; import com.android.i18n.timezone.XmlUtils.ReaderSupplier; import com.android.i18n.util.Log; +import com.android.icu.Flags; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -65,6 +67,13 @@ // This is the ISO 3166 alpha-2 code (in lower case). private static final String COUNTRY_ISO_CODE_ATTRIBUTE = "country"; + // <mobile_countries> + private static final String MOBILE_COUNTRIES_ELEMENT = "mobile_countries"; + + // <mobile_country mcc="123" [default="gu"]> + private static final String MOBILE_COUNTRY_ELEMENT = "mobile_country"; + private static final String DEFAULT_ATTRIBUTE = "default"; + private static TelephonyLookup instance; private final ReaderSupplier xmlSource; @@ -112,7 +121,12 @@ Log.e("No valid file found in set: " + Arrays.toString(telephonyLookupFilePaths) + " Printing exceptions and falling back to empty map.", lastException); - return createInstanceForTests("<telephony_lookup><networks /></telephony_lookup>"); + return createInstanceFromString(""" + <telephony_lookup> + <networks/> + <mobile_countries/> + </telephony_lookup> + """); } /** @@ -128,7 +142,7 @@ /** Used to create an instance using an in-memory XML String instead of a file. */ // VisibleForTesting - public static TelephonyLookup createInstanceForTests(String xml) { + public static TelephonyLookup createInstanceFromString(String xml) { return new TelephonyLookup(ReaderSupplier.forString(xml)); } @@ -182,23 +196,40 @@ * <network mcc="123" mnc="456" country="ab"/> * <network mcc="123" mnc="567" country="cd"/> * </networks> + * <mobile_countries> + * <mobile_country mcc="310"/> + * <country>us</country> + * </mobile_country> + * <mobile_country mcc="340" default="gp"> + * <country>gp</country> + * <country>gf</country> + * </mobile_country> + * </mobile_countries> * </telephony_lookup> */ findNextStartTagOrThrowNoRecurse(parser, TELEPHONY_LOOKUP_ELEMENT); - // There is only one expected sub-element <telephony_lookup> in the format currently, - // skip over anything before it. + /* Start parsing <networks> */ findNextStartTagOrThrowNoRecurse(parser, NETWORKS_ELEMENT); - processNetworks(parser, processor); - - // Make sure we are on the </networks> tag. checkOnEndTag(parser, NETWORKS_ELEMENT); + /* End parsing </networks> */ // Advance to the next event. parser.next(); + if (Flags.telephonyLookupMccExtension()) { + /* Start parsing <mobile_countries> */ + findNextStartTagOrThrowNoRecurse(parser, MOBILE_COUNTRIES_ELEMENT); + processMobileCountries(parser, processor); + checkOnEndTag(parser, MOBILE_COUNTRIES_ELEMENT); + /* End parsing </mobile_countries> */ + + // Advance to the next event. + parser.next(); + } + // Skip anything until </telephony_lookup>, and make sure the file is not truncated and // we can find the end. consumeUntilEndTag(parser, TELEPHONY_LOOKUP_ELEMENT); @@ -210,7 +241,6 @@ private static void processNetworks(XmlPullParser parser, TelephonyNetworkProcessor processor) throws IOException, XmlPullParserException { - // Skip over any unexpected elements and process <network> elements. while (findNextStartTagOrEndTagNoRecurse(parser, NETWORK_ELEMENT)) { String mcc = parser.getAttributeValue( @@ -244,19 +274,44 @@ } } - /** - * Processes <network> data. - */ - private interface TelephonyNetworkProcessor { + private static void processMobileCountries(XmlPullParser parser, + TelephonyNetworkProcessor processor) throws IOException, XmlPullParserException { + // Skip over any unexpected elements and process <mobile_country> elements. + while (findNextStartTagOrEndTagNoRecurse(parser, MOBILE_COUNTRY_ELEMENT)) { + String mcc = parser.getAttributeValue( + null /* namespace */, MOBILE_COUNTRY_CODE_ATTRIBUTE); + String defaultCountryIsoCode = parser.getAttributeValue( + null /* namespace */, DEFAULT_ATTRIBUTE); + Set<String> countryIsoCodes = new HashSet<>(); - boolean CONTINUE = true; - boolean HALT = false; + if (mcc == null) { + throw new XmlPullParserException( + "Unable to find mcc: " + parser.getPositionDescription()); + } - /** - * Process network data. Problems with the data are reported as an exception. - */ - void processNetwork(String mcc, String mnc, String countryIso, String debugInfo) - throws XmlPullParserException; + while (findNextStartTagOrEndTagNoRecurse(parser, COUNTRY_ISO_CODE_ATTRIBUTE)) { + String countryIsoCode = consumeText(parser); + if (countryIsoCode == null) { + throw new XmlPullParserException( + "Unable to find country for mcc=" + mcc + ": " + + parser.getPositionDescription()); + } + if (defaultCountryIsoCode == null) { + defaultCountryIsoCode = countryIsoCode; + } + countryIsoCodes.add(countryIsoCode); + + // Skip anything until </country>. + consumeUntilEndTag(parser, COUNTRY_ISO_CODE_ATTRIBUTE); + } + + String debugInfo = parser.getPositionDescription(); + processor.processMobileCountries(mcc, countryIsoCodes, defaultCountryIsoCode, + debugInfo); + + // Skip anything until </mobile_country>. + consumeUntilEndTag(parser, MOBILE_COUNTRY_ELEMENT); + } } /** @@ -267,6 +322,7 @@ private static class TelephonyNetworkValidator implements TelephonyNetworkProcessor { private final Set<MccMnc> knownMccMncs = new HashSet<>(); + private final Set<String> knownMccs = new HashSet<>(); @Override public void processNetwork(String mcc, String mnc, String countryIso, String debugInfo) @@ -294,6 +350,43 @@ knownMccMncs.add(mccMnc); } + @Override + public void processMobileCountries(String mcc, Set<String> countryIsoCodes, + String defaultCountryIso, String debugInfo) throws XmlPullParserException { + if (mcc == null || mcc.length() != 3 || !isAsciiNumeric(mcc)) { + throw new XmlPullParserException( + "MCC is not valid: mcc=" + mcc + " at " + debugInfo); + } + + if (countryIsoCodes.isEmpty()) { + throw new XmlPullParserException( + "No country found for mcc=" + mcc + " at " + debugInfo); + } + + if (!normalizeCountryIso(defaultCountryIso).equals(defaultCountryIso)) { + throw new XmlPullParserException("Default country code: " + defaultCountryIso + + " is not normalized at " + debugInfo); + } + + for (String countryIso : countryIsoCodes) { + if (!normalizeCountryIso(countryIso).equals(countryIso)) { + throw new XmlPullParserException("Country code: " + countryIso + + " is not normalized at " + debugInfo); + } + } + + if (!countryIsoCodes.contains(defaultCountryIso)) { + throw new XmlPullParserException( + "Default country not in country list for mcc=" + mcc + " at " + debugInfo); + } + + if (knownMccs.contains(mcc)) { + throw new XmlPullParserException("Second entry for MCC: " + mcc + + " at " + debugInfo); + } + knownMccs.add(mcc); + } + private static boolean isAsciiNumeric(String string) { for (int i = 0; i < string.length(); i++) { char character = string.charAt(i); @@ -311,16 +404,40 @@ */ private static class TelephonyNetworksExtractor implements TelephonyNetworkProcessor { private List<TelephonyNetwork> networksList = new ArrayList<>(10 /* default */); + private List<MobileCountries> mobileCountries = new ArrayList<>(); @Override - public void processNetwork(String mcc, String mnc, String countryIso, String debugInfo) - throws XmlPullParserException { + public void processNetwork(String mcc, String mnc, String countryIso, String debugInfo) { TelephonyNetwork network = TelephonyNetwork.create(mcc, mnc, countryIso); networksList.add(network); } - TelephonyNetworkFinder getTelephonyNetworkFinder() { - return TelephonyNetworkFinder.create(networksList); + @Override + public void processMobileCountries(String mcc, Set<String> countryIsos, + String defaultCountryIso, String debugInfo) { + mobileCountries.add(MobileCountries.create(mcc, countryIsos, defaultCountryIso)); } + + TelephonyNetworkFinder getTelephonyNetworkFinder() { + return TelephonyNetworkFinder.create(networksList, mobileCountries); + } + } + + /** + * Processes <network> data. + */ + private interface TelephonyNetworkProcessor { + + boolean CONTINUE = true; + boolean HALT = false; + + /** + * Process network data. Problems with the data are reported as an exception. + */ + void processNetwork(String mcc, String mnc, String countryIso, String debugInfo) + throws XmlPullParserException; + + void processMobileCountries(String mcc, Set<String> countryIsos, String defaultCountryIso, + String debugInfo) throws XmlPullParserException; } }
diff --git a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/TelephonyNetworkFinder.java b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/TelephonyNetworkFinder.java index c6e5a89..0003efa 100644 --- a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/TelephonyNetworkFinder.java +++ b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/TelephonyNetworkFinder.java
@@ -18,8 +18,12 @@ import static com.android.i18n.timezone.XmlUtils.normalizeCountryIso; +import android.annotation.NonNull; +import android.annotation.Nullable; + import com.android.i18n.timezone.TelephonyNetwork.MccMnc; import com.android.i18n.util.Log; +import com.android.icu.Flags; import java.util.ArrayList; import java.util.Collections; @@ -40,13 +44,16 @@ private final Map<MccMnc, TelephonyNetwork> networksMap; private final List<TelephonyNetwork> networksList; + private final Map<String, MobileCountries> countriesByMcc; - public static TelephonyNetworkFinder create(List<TelephonyNetwork> networksList) { + public static TelephonyNetworkFinder create(List<TelephonyNetwork> networksList, + List<MobileCountries> mobileCountriesList) { Set<String> validCountryIsoCodes = new HashSet<>(); for (String validCountryIsoCode : Locale.getISOCountries()) { validCountryIsoCodes.add(normalizeCountryIso(validCountryIsoCode)); } + /* Generate network map */ Map<MccMnc, TelephonyNetwork> networksMap = new HashMap<>(); for (TelephonyNetwork network : networksList) { if (!validCountryIsoCodes.contains(network.getCountryIsoCode())) { @@ -61,15 +68,46 @@ + ". New entry=" + network + " replacing previous entry."); } } + /* ************************* */ + + /* Generate countries map */ + Map<String, MobileCountries> countriesByMcc = new HashMap<>(); + for (MobileCountries mobileCountries : mobileCountriesList) { + for (String countryIsoCode : mobileCountries.getCountryIsoCodes()) { + if (!validCountryIsoCodes.contains(countryIsoCode)) { + Log.w("Unrecognized country code: " + countryIsoCode + + " for telephony MCC=" + mobileCountries.getMcc()); + } + } + + if (!validCountryIsoCodes.contains(mobileCountries.getDefaultCountryIsoCode())) { + Log.w("Unrecognized default country code: " + + mobileCountries.getDefaultCountryIsoCode() + + " for telephony MCC=" + mobileCountries.getMcc()); + } + + if (countriesByMcc.containsKey(mobileCountries.getMcc())) { + Log.w("Duplicate MCC detected for " + mobileCountries.getMcc() + + ". New entry=" + mobileCountries + + " replacing previous entry."); + } + + countriesByMcc.put(mobileCountries.getMcc(), mobileCountries); + } + /* ************************* */ + return new TelephonyNetworkFinder( Collections.unmodifiableList(new ArrayList<>(networksList)), - networksMap); + networksMap, + countriesByMcc); } private TelephonyNetworkFinder(List<TelephonyNetwork> networksList, - Map<MccMnc, TelephonyNetwork> networksMap) { + Map<MccMnc, TelephonyNetwork> networksMap, + Map<String, MobileCountries> countriesByMcc) { this.networksList = networksList; this.networksMap = networksMap; + this.countriesByMcc = countriesByMcc; } /** @@ -82,8 +120,24 @@ return networksMap.get(new MccMnc(mcc, mnc)); } + /** + * Returns the countries where a given MCC is in use. It is expected for this method to return + * {@code null} if the MCC is not found. + */ + @libcore.api.CorePlatformApi + @android.annotation.FlaggedApi(Flags.FLAG_TELEPHONY_LOOKUP_MCC_EXTENSION) + @Nullable + public MobileCountries findCountriesByMcc(@NonNull String mcc) { + return countriesByMcc.get(mcc); + } + // @VisibleForTesting - public List<TelephonyNetwork> getAll() { + public List<TelephonyNetwork> getAllNetworks() { return networksList; } + + // @VisibleForTesting + public List<MobileCountries> getAllMobileCountries() { + return countriesByMcc.values().stream().toList(); + } }
diff --git a/android_icu4j/testing/src/com/android/i18n/test/timezone/MobileCountriesTest.java b/android_icu4j/testing/src/com/android/i18n/test/timezone/MobileCountriesTest.java new file mode 100644 index 0000000..795194e --- /dev/null +++ b/android_icu4j/testing/src/com/android/i18n/test/timezone/MobileCountriesTest.java
@@ -0,0 +1,68 @@ +/* + * Copyright (C) 2025 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 com.android.i18n.test.timezone; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import android.icu.testsharding.MainTestShard; +import android.platform.test.annotations.UsesFlags; + +import com.android.i18n.timezone.MobileCountries; + +import org.junit.Test; + +import java.util.Set; + +@MainTestShard +@UsesFlags(com.android.icu.Flags.class) +public class MobileCountriesTest { + + @Test + public void createMobileCountries_multipleRegions_expectNormalised() { + MobileCountries mobileCountries = MobileCountries.create("340", Set.of("GP", "GF"), "gp"); + + assertEquals("340", mobileCountries.getMcc()); + assertEquals(Set.of("gp", "gf"), mobileCountries.getCountryIsoCodes()); + assertEquals("gp", mobileCountries.getDefaultCountryIsoCode()); + } + + @Test + public void createMobileCountries_singleRegions_expectNormalised() { + MobileCountries mobileCountries = MobileCountries.create("222", Set.of("gr"), "GR"); + + assertEquals("222", mobileCountries.getMcc()); + assertEquals(Set.of("gr"), mobileCountries.getCountryIsoCodes()); + assertEquals("gr", mobileCountries.getDefaultCountryIsoCode()); + } + + @Test + public void createMobileCountries_defaultCountryNotInSet_shouldThrow() { + assertThrows(IllegalArgumentException.class, + () -> MobileCountries.create("340", Set.of("gr"), "gp")); + } + + @Test + public void nullArguments_shouldThrow() { + assertThrows(NullPointerException.class, + () -> MobileCountries.create(null, Set.of("gr"), "GR")); + assertThrows(NullPointerException.class, + () -> MobileCountries.create("222", null, "GR")); + assertThrows(NullPointerException.class, + () -> MobileCountries.create("222", Set.of("gr"), null)); + } +} \ No newline at end of file
diff --git a/android_icu4j/testing/src/com/android/i18n/test/timezone/TelephonyLookupTest.java b/android_icu4j/testing/src/com/android/i18n/test/timezone/TelephonyLookupTest.java index 08d5db9..abf299a 100644 --- a/android_icu4j/testing/src/com/android/i18n/test/timezone/TelephonyLookupTest.java +++ b/android_icu4j/testing/src/com/android/i18n/test/timezone/TelephonyLookupTest.java
@@ -18,18 +18,29 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; +import android.icu.testsharding.MainTestShard; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.annotations.UsesFlags; +import android.platform.test.flag.junit.FlagsParameterization; +import android.platform.test.flag.junit.SetFlagsRule; + +import com.android.i18n.timezone.MobileCountries; import com.android.i18n.timezone.TelephonyLookup; import com.android.i18n.timezone.TelephonyNetwork; import com.android.i18n.timezone.TelephonyNetworkFinder; +import com.android.icu.Flags; +import com.android.internal.telephony.MccTable; import org.junit.After; import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; - -import android.icu.testsharding.MainTestShard; +import org.junit.runner.RunWith; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -41,12 +52,33 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.Set; +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + +@RunWith(ParameterizedAndroidJunit4.class) @MainTestShard +@UsesFlags(com.android.icu.Flags.class) public class TelephonyLookupTest { + @ClassRule + public static final SetFlagsRule.ClassRule mSetFlagsClassRule = new SetFlagsRule.ClassRule(); + + @Parameters(name = "{0}") + public static List<FlagsParameterization> getParams() { + return FlagsParameterization.allCombinationsOf( + Flags.FLAG_TELEPHONY_LOOKUP_MCC_EXTENSION); + } + + @Rule + public final SetFlagsRule mSetFlagsRule; private Path testDir; + public TelephonyLookupTest(FlagsParameterization flags) { + mSetFlagsRule = mSetFlagsClassRule.createSetFlagsRule(flags); + } + @Before public void setUp() throws Exception { testDir = Files.createTempDirectory("TelephonyLookupTest"); @@ -55,7 +87,7 @@ @After public void tearDown() throws Exception { // Delete the testDir and all contents. - Files.walkFileTree(testDir, new SimpleFileVisitor<Path>() { + Files.walkFileTree(testDir, new SimpleFileVisitor<>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { @@ -74,21 +106,40 @@ @Test public void createInstanceWithFallback() throws Exception { - String validXml1 = "<telephony_lookup>\n" - + " <networks>\n" - + " <network mcc=\"123\" mnc=\"456\" country=\"gb\"/>\n" - + " </networks>\n" - + "</telephony_lookup>\n"; + String validXml1 = """ + <telephony_lookup> + <networks> + <network mcc="123" mnc="456" country="gb"/> + </networks> + <mobile_countries> + <mobile_country mcc="202"> + <country>gr</country> + </mobile_country> + </mobile_countries> + </telephony_lookup> + """; TelephonyNetwork expectedTelephonyNetwork1 = TelephonyNetwork.create("123", "456", "gb"); + MobileCountries expectedMobileCountries1 = + MobileCountries.create("202", Set.of("gr"), "gr"); - String validXml2 = "<telephony_lookup>\n" - + " <networks>\n" - + " <network mcc=\"234\" mnc=\"567\" country=\"fr\"/>\n" - + " </networks>\n" - + "</telephony_lookup>\n"; + String validXml2 = """ + <telephony_lookup> + <networks> + <network mcc="234" mnc="567" country="fr"/> + </networks> + <mobile_countries> + <mobile_country mcc="505" default="au"> + <country>au</country> + <country>nf</country> + </mobile_country> + </mobile_countries> + </telephony_lookup> + """; TelephonyNetwork expectedTelephonyNetwork2 = TelephonyNetwork.create("234", "567", "fr"); + MobileCountries expectedMobileCountries2 = + MobileCountries.create("505", Set.of("au", "nf"), "au"); String invalidXml = "<foo></foo>\n"; checkValidateThrowsParserException(invalidXml); @@ -101,17 +152,38 @@ TelephonyLookup file1ThenFile2 = TelephonyLookup.createInstanceWithFallback(validFile1, validFile2); assertEquals(list(expectedTelephonyNetwork1), - file1ThenFile2.getTelephonyNetworkFinder().getAll()); + file1ThenFile2.getTelephonyNetworkFinder().getAllNetworks()); + + if (Flags.telephonyLookupMccExtension()) { + assertEquals(list(expectedMobileCountries1), + file1ThenFile2.getTelephonyNetworkFinder().getAllMobileCountries()); + } else { + assertEmpty(file1ThenFile2.getTelephonyNetworkFinder().getAllMobileCountries()); + } TelephonyLookup missingFileThenFile1 = TelephonyLookup.createInstanceWithFallback(missingFile, validFile1); assertEquals(list(expectedTelephonyNetwork1), - missingFileThenFile1.getTelephonyNetworkFinder().getAll()); + missingFileThenFile1.getTelephonyNetworkFinder().getAllNetworks()); + + if (Flags.telephonyLookupMccExtension()) { + assertEquals(list(expectedMobileCountries1), + missingFileThenFile1.getTelephonyNetworkFinder().getAllMobileCountries()); + } else { + assertEmpty(missingFileThenFile1.getTelephonyNetworkFinder().getAllMobileCountries()); + } TelephonyLookup file2ThenFile1 = TelephonyLookup.createInstanceWithFallback(validFile2, validFile1); assertEquals(list(expectedTelephonyNetwork2), - file2ThenFile1.getTelephonyNetworkFinder().getAll()); + file2ThenFile1.getTelephonyNetworkFinder().getAllNetworks()); + + if (Flags.telephonyLookupMccExtension()) { + assertEquals(list(expectedMobileCountries2), + file2ThenFile1.getTelephonyNetworkFinder().getAllMobileCountries()); + } else { + assertEmpty(file2ThenFile1.getTelephonyNetworkFinder().getAllMobileCountries()); + } // We assume the file has been validated so an invalid file is not checked ahead of time. // We will find out when we look something up. @@ -122,44 +194,110 @@ // This is not a normal case: It would imply a device shipped without a file anywhere! TelephonyLookup missingFiles = TelephonyLookup.createInstanceWithFallback(missingFile, missingFile); - assertEmpty(missingFiles.getTelephonyNetworkFinder().getAll()); + assertEmpty(missingFiles.getTelephonyNetworkFinder().getAllNetworks()); + assertEmpty(missingFiles.getTelephonyNetworkFinder().getAllMobileCountries()); } @Test - public void xmlParsing_emptyFile() throws Exception { + public void xmlParsing_emptyFile() { checkValidateThrowsParserException(""); } @Test - public void xmlParsing_unexpectedRootElement() throws Exception { + public void xmlParsing_unexpectedRootElement() { checkValidateThrowsParserException("<foo></foo>\n"); } @Test - public void xmlParsing_missingNetworks() throws Exception { - checkValidateThrowsParserException("<telephony_lookup></telephony_lookup>\n"); + public void xmlParsing_missingNetworks() { + checkValidateThrowsParserException(""" + <telephony_lookup> + <mobile_countries> + <mobile_country mcc="505" default="au"> + <country>au</country> + <country>nf</country> + </mobile_country> + </mobile_countries> + </telephony_lookup> + """); } @Test - public void xmlParsing_emptyNetworksOk() throws Exception { + public void xmlParsing_missingMobileCountries() { + assumeTrue(Flags.telephonyLookupMccExtension()); + checkValidateThrowsParserException(""" + <telephony_lookup> + <networks> + <network mcc="234" mnc="567" country="fr"/> + </networks> + </telephony_lookup> + """); + } + + @Test + public void xmlParsing_emptyNetworkOk() throws Exception { { TelephonyLookup telephonyLookup = - validate("<telephony_lookup>\n" - + " <networks>\n" - + " </networks>\n" - + "</telephony_lookup>\n"); + validate(""" + <telephony_lookup> + <networks></networks> + <mobile_countries> + <mobile_country mcc="505" default="au"> + <country>au</country> + </mobile_country> + </mobile_countries> + </telephony_lookup> + """); TelephonyNetworkFinder telephonyNetworkFinder = telephonyLookup .getTelephonyNetworkFinder(); - assertEquals(list(), telephonyNetworkFinder.getAll()); + assertEquals(list(), telephonyNetworkFinder.getAllNetworks()); } { TelephonyLookup telephonyLookup = - validate("<telephony_lookup>\n" - + " <networks/>\n" - + "</telephony_lookup>\n"); + validate(""" + <telephony_lookup> + <networks/> + <mobile_countries> + <mobile_country mcc="505" default="au"> + <country>au</country> + </mobile_country> + </mobile_countries> + </telephony_lookup> + """); TelephonyNetworkFinder telephonyNetworkFinder = telephonyLookup .getTelephonyNetworkFinder(); - assertEquals(list(), telephonyNetworkFinder.getAll()); + assertEquals(list(), telephonyNetworkFinder.getAllNetworks()); + } + } + + @Test + public void xmlParsing_emptyMobileCountries() throws Exception { + assumeTrue(Flags.telephonyLookupMccExtension()); + + { + TelephonyLookup telephonyLookup = + validate(""" + <telephony_lookup> + <networks/> + <mobile_countries> + </mobile_countries> + </telephony_lookup> + """); + TelephonyNetworkFinder telephonyNetworkFinder = telephonyLookup + .getTelephonyNetworkFinder(); + assertEmpty(telephonyNetworkFinder.getAllMobileCountries()); + } + { + TelephonyLookup telephonyLookup = + validate(""" + <telephony_lookup> + <networks/> + <mobile_countries/> + </telephony_lookup> + """); + TelephonyNetworkFinder telephonyNetworkFinder = telephonyLookup + .getTelephonyNetworkFinder(); + assertEmpty(telephonyNetworkFinder.getAllMobileCountries()); } } @@ -167,14 +305,33 @@ public void xmlParsing_unexpectedComments() throws Exception { TelephonyNetwork expectedTelephonyNetwork = TelephonyNetwork.create("123", "456", "gb"); + MobileCountries expectedMobileCountries = + MobileCountries.create("202", Set.of("gr"), "gr"); - TelephonyLookup telephonyLookup = validate("<telephony_lookup>\n" - + " <networks>\n" - + " <!-- This is a comment -->" - + " <network mcc=\"123\" mnc=\"456\" country=\"gb\"/>\n" - + " </networks>\n" - + "</telephony_lookup>\n"); - assertEquals(list(expectedTelephonyNetwork), telephonyLookup.getTelephonyNetworkFinder().getAll()); + TelephonyLookup telephonyLookup = validate(""" + <telephony_lookup> + <networks> + <!-- This is a comment --> + <network mcc="123" mnc="456" country="gb"/> + </networks> + <!-- This is a comment --> + <mobile_countries> + <!-- This is a comment --> + <mobile_country mcc="202"> + <!-- This is a comment --> + <country>gr</country> + </mobile_country> + </mobile_countries> + <!-- This is a comment --> + </telephony_lookup> + """); + assertEquals(list(expectedTelephonyNetwork), + telephonyLookup.getTelephonyNetworkFinder().getAllNetworks()); + + if (Flags.telephonyLookupMccExtension()) { + assertEquals(list(expectedMobileCountries), + telephonyLookup.getTelephonyNetworkFinder().getAllMobileCountries()); + } } @Test @@ -182,6 +339,9 @@ TelephonyNetwork expectedTelephonyNetwork = TelephonyNetwork.create("123", "456", "gb"); List<TelephonyNetwork> expectedNetworks = list(expectedTelephonyNetwork); + MobileCountries expectedMobileCountries = + MobileCountries.create("202", Set.of("gr"), "gr"); + List<MobileCountries> expectedMobileCountriesList = list(expectedMobileCountries); String unexpectedElement = "<unexpected-element>\n<a /></unexpected-element>\n"; @@ -195,28 +355,52 @@ + " " + unexpectedElement + " </networks>\n" + " " + unexpectedElement - + "</telephony_lookup>\n"); - assertEquals(expectedNetworks, telephonyLookup.getTelephonyNetworkFinder().getAll()); - - telephonyLookup = validate("<telephony_lookup>\n" - + " <networks>\n" - + " <network mcc=\"123\" mnc=\"456\" country=\"gb\">\n" + + " <mobile_countries>\n" + + " " + unexpectedElement + + " <mobile_country mcc=\"202\">\n" + " " + unexpectedElement - + " </network>\n" - + " </networks>\n" + + " <country>gr</country>\n" + + " " + unexpectedElement + + " </mobile_country>\n" + + " " + unexpectedElement + + " </mobile_countries>\n" + + " " + unexpectedElement + "</telephony_lookup>\n"); - assertEquals(expectedNetworks, telephonyLookup.getTelephonyNetworkFinder().getAll()); + assertEquals(expectedNetworks, + telephonyLookup.getTelephonyNetworkFinder().getAllNetworks()); + + if (Flags.telephonyLookupMccExtension()) { + assertEquals(expectedMobileCountriesList, + telephonyLookup.getTelephonyNetworkFinder().getAllMobileCountries()); + } expectedNetworks = list(expectedTelephonyNetwork, TelephonyNetwork.create("234", "567", "fr")); + expectedMobileCountriesList = list(expectedMobileCountries, + MobileCountries.create("204", Set.of("nl"), "nl")); telephonyLookup = validate("<telephony_lookup>\n" + " <networks>\n" + " <network mcc=\"123\" mnc=\"456\" country=\"gb\"/>\n" + " " + unexpectedElement + " <network mcc=\"234\" mnc=\"567\" country=\"fr\"/>\n" + " </networks>\n" + + " <mobile_countries>\n" + + " <mobile_country mcc=\"202\">\n" + + " <country>gr</country>\n" + + " </mobile_country>\n" + + " " + unexpectedElement + + " <mobile_country mcc=\"204\">\n" + + " <country>nl</country>\n" + + " </mobile_country>\n" + + " </mobile_countries>\n" + "</telephony_lookup>\n"); - assertEquals(expectedNetworks, telephonyLookup.getTelephonyNetworkFinder().getAll()); + assertEquals(expectedNetworks, + telephonyLookup.getTelephonyNetworkFinder().getAllNetworks()); + + if (Flags.telephonyLookupMccExtension()) { + assertEquals(expectedMobileCountriesList, + telephonyLookup.getTelephonyNetworkFinder().getAllMobileCountries()); + } } @Test @@ -224,6 +408,9 @@ TelephonyNetwork expectedTelephonyNetwork = TelephonyNetwork.create("123", "456", "gb"); List<TelephonyNetwork> expectedNetworks = list(expectedTelephonyNetwork); + MobileCountries expectedMobileCountries = + MobileCountries.create("202", Set.of("gr"), "gr"); + List<MobileCountries> expectedMobileCountriesList = list(expectedMobileCountries); String unexpectedText = "unexpected-text"; TelephonyLookup telephonyLookup = validate("<telephony_lookup>\n" @@ -234,116 +421,314 @@ + " " + unexpectedText + " </networks>\n" + " " + unexpectedText + + " <mobile_countries>\n" + + " " + unexpectedText + + " <mobile_country mcc=\"202\">\n" + + " " + unexpectedText + + " <country>gr</country>\n" + + " " + unexpectedText + + " </mobile_country>\n" + + " " + unexpectedText + + " </mobile_countries>\n" + + " " + unexpectedText + "</telephony_lookup>\n"); - assertEquals(expectedNetworks, telephonyLookup.getTelephonyNetworkFinder().getAll()); + assertEquals(expectedNetworks, + telephonyLookup.getTelephonyNetworkFinder().getAllNetworks()); - telephonyLookup = validate("<telephony_lookup>\n" - + " <networks>\n" - + " <network mcc=\"123\" mnc=\"456\" country=\"gb\">\n" - + " " + unexpectedText - + " </network>\n" - + " </networks>\n" - + "</telephony_lookup>\n"); - assertEquals(expectedNetworks, telephonyLookup.getTelephonyNetworkFinder().getAll()); + if (Flags.telephonyLookupMccExtension()) { + assertEquals(expectedMobileCountriesList, + telephonyLookup.getTelephonyNetworkFinder().getAllMobileCountries()); + } else { + assertEmpty(telephonyLookup.getTelephonyNetworkFinder().getAllMobileCountries()); + } } @Test - public void xmlParsing_truncatedInput() throws Exception { + public void xmlParsing_truncatedInput() { checkValidateThrowsParserException("<telephony_lookup>\n"); - checkValidateThrowsParserException("<telephony_lookup>\n" - + " <networks>\n"); + checkValidateThrowsParserException(""" + <telephony_lookup> + <networks> + """); - checkValidateThrowsParserException("<telephony_lookup>\n" - + " <networks>\n" - + " <network mcc=\"123\" mnc=\"456\" country=\"gb\"/>\n"); + checkValidateThrowsParserException(""" + <telephony_lookup> + <networks> + <network mcc="123" mnc="456" country="gb"/> + """); - checkValidateThrowsParserException("<telephony_lookup>\n" - + " <networks>\n" - + " <network mcc=\"123\" mnc=\"456\" country=\"gb\"/>\n" - + " </networks>\n"); + checkValidateThrowsParserException(""" + <telephony_lookup> + <networks> + <network mcc="123" mnc="456" country="gb"/> + </networks> + """); + + checkValidateThrowsParserException(""" + <telephony_lookup> + <networks> + <network mcc="123" mnc="456" country="gb"/> + </networks> + <mobile_countries> + """); + + checkValidateThrowsParserException(""" + <telephony_lookup> + <networks> + <network mcc="123" mnc="456" country="gb"/> + </networks> + <mobile_countries> + <mobile_country mcc="202"> + """); + + checkValidateThrowsParserException(""" + <telephony_lookup> + <networks> + <network mcc="123" mnc="456" country="gb"/> + </networks> + <mobile_countries> + <mobile_country mcc="202"> + <country>gr</country> + """); + + checkValidateThrowsParserException(""" + <telephony_lookup> + <networks> + <network mcc="123" mnc="456" country="gb"/> + </networks> + <mobile_countries> + <mobile_country mcc="202"> + <country>gr</country> + </mobile_country> + </mobile_countries> + """); } @Test public void validateDuplicateMccMnc() { - checkValidateThrowsParserException("<telephony_lookup>\n" - + " <networks>\n" - + " <network mcc=\"123\" mnc=\"456\" countryCode=\"gb\"/>\n" - + " <network mcc=\"123\" mnc=\"456\" countryCode=\"fr\"/>\n" - + " </networks>\n" - + "</telephony_lookup>\n"); + checkValidateThrowsParserException(""" + <telephony_lookup> + <networks> + <network mcc="123" mnc="456" countryCode="gb"/> + <network mcc="123" mnc="456" countryCode="fr"/> + </networks> + <mobile_countries> + <mobile_country mcc="202"> + <country>gr</country> + </mobile_country> + </mobile_countries> + </telephony_lookup> + """); + } + + @Test + public void validateDuplicateMcc() { + assumeTrue(Flags.telephonyLookupMccExtension()); + checkValidateThrowsParserException(""" + <telephony_lookup> + <networks> + <network mcc="123" mnc="456" countryCode="gb"/> + </networks> + <mobile_countries> + <mobile_country mcc="202"> + <country>gr</country> + </mobile_country> + <mobile_country mcc="202"> + <country>nl</country> + </mobile_country> + </mobile_countries> + </telephony_lookup> + """); } @Test public void validateCountryCodeLowerCase() { - checkValidateThrowsParserException("<telephony_lookup>\n" - + " <networks>\n" - + " <network mcc=\"123\" mnc=\"456\" countryCode=\"GB\"/>\n" - + " </networks>\n" - + "</telephony_lookup>\n"); + checkValidateThrowsParserException(""" + <telephony_lookup> + <networks> + <network mcc="123" mnc="456" countryCode="GB"/> + </networks> + <mobile_countries> + <mobile_country mcc="202"> + <country>gr</country> + </mobile_country> + </mobile_countries> + </telephony_lookup> + """); + + if (Flags.telephonyLookupMccExtension()) { + checkValidateThrowsParserException(""" + <telephony_lookup> + <networks> + <network mcc="123" mnc="456" countryCode="gb"/> + </networks> + <mobile_countries> + <mobile_country mcc="202"> + <country>GR</country> + </mobile_country> + </mobile_countries> + </telephony_lookup> + """); + } } - @Test - public void getTelephonyNetworkFinder() throws Exception { - TelephonyLookup telephonyLookup = TelephonyLookup.createInstanceForTests( - "<telephony_lookup>\n" - + " <networks>\n" - + " <network mcc=\"123\" mnc=\"456\" country=\"gb\"/>\n" - + " <network mcc=\"234\" mnc=\"567\" country=\"fr\"/>\n" - + " </networks>\n" - + "</telephony_lookup>\n"); + public void getTelephonyNetworkFinder() { + TelephonyLookup telephonyLookup = TelephonyLookup.createInstanceFromString(""" + <telephony_lookup> + <networks> + <network mcc="123" mnc="456" country="gb"/> + <network mcc="234" mnc="567" country="fr"/> + </networks> + <mobile_countries> + <mobile_country mcc="202"> + <country>gr</country> + </mobile_country> + <mobile_country mcc="505" default="au"> + <country>au</country> + <country>nf</country> + </mobile_country> + </mobile_countries> + </telephony_lookup> + """); TelephonyNetworkFinder telephonyNetworkFinder = telephonyLookup.getTelephonyNetworkFinder(); TelephonyNetwork expectedNetwork1 = TelephonyNetwork.create("123", "456", "gb"); TelephonyNetwork expectedNetwork2 = TelephonyNetwork.create("234", "567", "fr"); - assertEquals(list(expectedNetwork1, expectedNetwork2), telephonyNetworkFinder.getAll()); + MobileCountries expectedMobileCountries1 = + MobileCountries.create("202", Set.of("gr"), "gr"); + MobileCountries expectedMobileCountries2 = + MobileCountries.create("505", Set.of("au", "nf"), "au"); + + assertEquals(list(expectedNetwork1, expectedNetwork2), + telephonyNetworkFinder.getAllNetworks()); + + if (Flags.telephonyLookupMccExtension()) { + assertEquals(list(expectedMobileCountries1, expectedMobileCountries2), + telephonyNetworkFinder.getAllMobileCountries()); + } else { + assertEmpty(telephonyNetworkFinder.getAllMobileCountries()); + } + assertEquals(expectedNetwork1, telephonyNetworkFinder.findNetworkByMccMnc("123", "456")); assertEquals(expectedNetwork2, telephonyNetworkFinder.findNetworkByMccMnc("234", "567")); assertNull(telephonyNetworkFinder.findNetworkByMccMnc("999", "999")); - } - @Test - public void xmlParsing_missingMccAttribute() throws Exception { - checkValidateThrowsParserException("<telephony_lookup>\n" - + " <networks>\n" - + " <network mnc=\"456\" country=\"gb\"/>\n" - + " </networks>\n" - + "</telephony_lookup>\n"); - } - - @Test - public void xmlParsing_missingMncAttribute() throws Exception { - TelephonyLookup telephonyLookup = TelephonyLookup.createInstanceForTests( - "<telephony_lookup>\n" - + " <networks>\n" - + " <network mcc=\"123\" country=\"gb\"/>\n" - + " </networks>\n" - + "</telephony_lookup>\n"); - assertNull(telephonyLookup.getTelephonyNetworkFinder()); - } - - @Test - public void xmlParsing_missingCountryCodeAttribute() throws Exception { - TelephonyLookup telephonyLookup = TelephonyLookup.createInstanceForTests( - "<telephony_lookup>\n" - + " <networks>\n" - + " <network mcc=\"123\" mnc=\"456\"/>\n" - + " </networks>\n" - + "</telephony_lookup>\n"); - assertNull(telephonyLookup.getTelephonyNetworkFinder()); - } - - private static void checkValidateThrowsParserException(String xml) { - try { - validate(xml); - fail(); - } catch (IOException expected) { + if (Flags.telephonyLookupMccExtension()) { + assertEquals(expectedMobileCountries1, + telephonyNetworkFinder.findCountriesByMcc("202")); + assertEquals(expectedMobileCountries2, + telephonyNetworkFinder.findCountriesByMcc("505")); + assertNull(telephonyNetworkFinder.findCountriesByMcc("999")); } } + @Test + public void xmlParsing_networks_missingMccAttribute() { + checkValidateThrowsParserException(""" + <telephony_lookup> + <networks> + <network mnc="456" country="gb"/> + </networks> + <mobile_countries> + <mobile_country mcc="202"> + <country>gr</country> + </mobile_country> + </mobile_countries> + </telephony_lookup> + """); + } + + @Test + public void xmlParsing_mobileCountries_missingMccAttribute() { + assumeTrue(Flags.telephonyLookupMccExtension()); + checkValidateThrowsParserException(""" + <telephony_lookup> + <networks> + <network mcc="123" mnc="456" country="gb"/> + </networks> + <mobile_countries> + <mobile_country> + <country>gr</country> + </mobile_country> + </mobile_countries> + </telephony_lookup> + """); + } + + @Test + public void xmlParsing_networks_missingMncAttribute() { + TelephonyLookup telephonyLookup = TelephonyLookup.createInstanceFromString(""" + <telephony_lookup> + <networks> + <network mcc="123" country="gb"/> + </networks> + <mobile_countries> + <mobile_country mcc="202"> + <country>gr</country> + </mobile_country> + </mobile_countries> + </telephony_lookup> + """); + assertNull(telephonyLookup.getTelephonyNetworkFinder()); + } + + @Test + public void xmlParsing_network_missingCountryCodeAttribute() { + TelephonyLookup telephonyLookup = TelephonyLookup.createInstanceFromString(""" + <telephony_lookup> + <networks> + <network mcc="123" mnc="456"/> + </networks> + <mobile_countries> + <mobile_country mcc="202"> + <country>gr</country> + </mobile_country> + </mobile_countries> + </telephony_lookup> + """); + assertNull(telephonyLookup.getTelephonyNetworkFinder()); + } + + @Test + public void xmlParsing_mobileCountry_missingCountryCode() { + assumeTrue(Flags.telephonyLookupMccExtension()); + TelephonyLookup telephonyLookup = TelephonyLookup.createInstanceFromString(""" + <telephony_lookup> + <networks> + <network mcc="123" mnc="456" country="gb"/> + </networks> + <mobile_countries> + <mobile_country mcc="202"> + <country/> + </mobile_country> + </mobile_countries> + </telephony_lookup> + """); + assertNull(telephonyLookup.getTelephonyNetworkFinder()); + } + + @EnableFlags(Flags.FLAG_TELEPHONY_LOOKUP_MCC_EXTENSION) + @Test + public void telephonyFinder_shouldBeIdenticalToTelephonyMccTable() { + TelephonyNetworkFinder telephonyNetworkFinder = + TelephonyLookup.getInstance().getTelephonyNetworkFinder(); + + telephonyNetworkFinder.getAllMobileCountries().forEach(countries -> { + String telephonyCountry = MccTable.geoCountryCodeForMccMnc( + new MccTable.MccMnc(countries.getMcc(), null)); + + assertEquals(telephonyCountry, countries.getDefaultCountryIsoCode()); + }); + } + + private static void checkValidateThrowsParserException(String xml) { + assertThrows(IOException.class, () -> validate(xml)); + } + private static TelephonyLookup validate(String xml) throws IOException { - TelephonyLookup telephonyLookup = TelephonyLookup.createInstanceForTests(xml); + TelephonyLookup telephonyLookup = TelephonyLookup.createInstanceFromString(xml); telephonyLookup.validate(); return telephonyLookup; }
diff --git a/android_icu4j/testing/src/com/android/i18n/test/timezone/TelephonyNetworkFinderTest.java b/android_icu4j/testing/src/com/android/i18n/test/timezone/TelephonyNetworkFinderTest.java index 6b638cf..14b3b64 100644 --- a/android_icu4j/testing/src/com/android/i18n/test/timezone/TelephonyNetworkFinderTest.java +++ b/android_icu4j/testing/src/com/android/i18n/test/timezone/TelephonyNetworkFinderTest.java
@@ -17,35 +17,91 @@ package com.android.i18n.test.timezone; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; - -import com.android.i18n.timezone.TelephonyNetwork; -import com.android.i18n.timezone.TelephonyNetworkFinder; - -import org.junit.Test; +import static org.junit.Assert.assertThrows; import android.icu.testsharding.MainTestShard; +import android.platform.test.annotations.UsesFlags; +import android.platform.test.flag.junit.FlagsParameterization; +import android.platform.test.flag.junit.SetFlagsRule; -import java.util.Arrays; +import com.android.i18n.timezone.MobileCountries; +import com.android.i18n.timezone.TelephonyNetwork; +import com.android.i18n.timezone.TelephonyNetworkFinder; +import com.android.icu.Flags; + +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + import java.util.List; +import java.util.Set; +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + +@RunWith(ParameterizedAndroidJunit4.class) @MainTestShard +@UsesFlags(com.android.icu.Flags.class) public class TelephonyNetworkFinderTest { + @ClassRule + public static final SetFlagsRule.ClassRule mSetFlagsClassRule = new SetFlagsRule.ClassRule(); + + @Parameters(name = "{0}") + public static List<FlagsParameterization> getParams() { + return FlagsParameterization.allCombinationsOf( + Flags.FLAG_TELEPHONY_LOOKUP_MCC_EXTENSION); + } + + @Rule + public final SetFlagsRule mSetFlagsRule; + + private TelephonyNetworkFinder finder; + + public TelephonyNetworkFinderTest(FlagsParameterization flags) { + mSetFlagsRule = mSetFlagsClassRule.createSetFlagsRule(flags); + } + + @Before + public void setup() { + List<TelephonyNetwork> networkList = List.of( + TelephonyNetwork.create("123", "456", "gb"), + TelephonyNetwork.create("234", "567", "gb")); + + List<MobileCountries> mobileCountriesList = List.of( + MobileCountries.create("234", Set.of("uk"), "uk"), + MobileCountries.create("310", Set.of("uk", "us"), "us")); + + finder = TelephonyNetworkFinder.create(networkList, mobileCountriesList); + } @Test - public void testCreateAndLookups() { + public void telephonyFinder_shouldFindNetworks() { TelephonyNetwork network = TelephonyNetwork.create("123", "456", "gb"); - List<TelephonyNetwork> networkList = list(network); - TelephonyNetworkFinder finder = TelephonyNetworkFinder.create(networkList); + assertEquals(network, finder.findNetworkByMccMnc("123", "456")); assertNull(finder.findNetworkByMccMnc("XXX", "XXX")); assertNull(finder.findNetworkByMccMnc("123", "XXX")); assertNull(finder.findNetworkByMccMnc("456", "123")); assertNull(finder.findNetworkByMccMnc("111", "222")); - assertEquals(networkList, finder.getAll()); + assertEquals(2, finder.getAllNetworks().size()); } - private static <X> List<X> list(X... values) { - return Arrays.asList(values); + @Test + public void telephonyFinder_shouldFindCountries() { + MobileCountries countries = MobileCountries.create("310", Set.of("uk", "us"), "us"); + + assertEquals(countries, finder.findCountriesByMcc("310")); + assertNotNull(finder.findCountriesByMcc("234")); + assertEquals(2, finder.getAllMobileCountries().size()); + } + + @Test + public void telephonyFinder_countryIsoCodes_cannotBeTamperedWith() { + MobileCountries found = finder.findCountriesByMcc("310"); + assertThrows(UnsupportedOperationException.class, () -> found.getCountryIsoCodes().add("gb")); } }
diff --git a/icu.aconfig b/icu.aconfig index 9f97578..27aa57b 100644 --- a/icu.aconfig +++ b/icu.aconfig
@@ -13,7 +13,7 @@ # limitations under the License. package: "com.android.icu" -container: "system" +container: "com.android.i18n" flag { namespace: "core_libraries" @@ -34,3 +34,13 @@ # ICU is used before SettingsProvider starts is_fixed_read_only: true } + +flag { + namespace: "core_libraries" + name: "telephony_lookup_mcc_extension" + is_exported: true + description: "This flag controls whether to parse the new MCC to countries mapping from telephony lookup" + bug: "376493245" + # ICU is used before SettingsProvider starts + is_fixed_read_only: true +}