Skip to content

Commit bc672c4

Browse files
committed
Converting phone number from hint selector to e164 optimistically
Change-Id: Icd1e115e51c37c1b599166422be6072fc0fd4f4c
1 parent 505415c commit bc672c4

File tree

4 files changed

+207
-42
lines changed

4 files changed

+207
-42
lines changed

auth/src/main/java/com/firebase/ui/auth/ui/phone/CountryListSpinner.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,8 @@ private void init() {
6767
dialogPopup = new DialogPopup(countryListAdapter);
6868
textFormat = "%1$s +%2$d";
6969
selectedCountryName = "";
70-
final Locale defaultLocale = Locale.getDefault();
71-
final int defaultCountryCode =
72-
PhoneNumberUtils.getCountryCode(defaultLocale.getCountry());
73-
74-
setSpinnerText(defaultCountryCode, defaultLocale);
70+
final CountryInfo countryInfo = PhoneNumberUtils.getCurrentCountryInfo(getContext());
71+
setSpinnerText(countryInfo.countryCode, countryInfo.locale);
7572
}
7673

7774
private void setSpinnerText(int countryCode, Locale locale) {

auth/src/main/java/com/firebase/ui/auth/ui/phone/PhoneNumberUtils.java

Lines changed: 82 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,12 @@
1717
*/
1818
package com.firebase.ui.auth.ui.phone;
1919

20+
import android.content.Context;
2021
import android.os.Build;
22+
import android.support.annotation.NonNull;
2123
import android.support.annotation.Nullable;
24+
import android.telephony.TelephonyManager;
25+
import android.text.TextUtils;
2226

2327
import java.util.ArrayList;
2428
import java.util.Collections;
@@ -29,9 +33,11 @@
2933
import java.util.concurrent.ConcurrentHashMap;
3034

3135
final class PhoneNumberUtils {
32-
private final static String DEFAULT_COUNTRY_CODE = "1";
3336
private final static int DEFAULT_COUNTRY_CODE_INT = 1;
34-
private final static String DEFAULT_COUNTRY_ISO = "US";
37+
private final static String DEFAULT_COUNTRY_CODE = String.valueOf(DEFAULT_COUNTRY_CODE_INT);
38+
private final static Locale DEFAULT_LOCALE = Locale.US;
39+
private final static CountryInfo DEFAULT_COUNTRY =
40+
new CountryInfo(DEFAULT_LOCALE, DEFAULT_COUNTRY_CODE_INT);
3541

3642
private final static int MAX_COUNTRIES = 291;
3743
private final static int MAX_COUNTRY_CODES = 286;
@@ -54,27 +60,75 @@ final class PhoneNumberUtils {
5460
static void load() {
5561
}
5662

57-
63+
/**
64+
* This method works as follow:
65+
* <ol><li>When the android version is LOLLIPOP or greater, the reliable
66+
* {{@link android.telephony.PhoneNumberUtils#formatNumberToE164}} is used to format.</li>
67+
* <li>For lower versions, we construct a value with the input phone number stripped of
68+
* non numeric characters and prefix it with a "+" and country code</li>
69+
* </ol>
70+
71+
* @param phoneNumber that may or may not itself have country code
72+
* @param countryInfo must have locale with ISO 3166 2-letter code for country
73+
* @return
74+
*/
5875
@Nullable
59-
static String normalize(String phone) {
76+
static String formatPhoneNumber(@NonNull String phoneNumber, @NonNull CountryInfo countryInfo) {
77+
6078
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
61-
return android.telephony.PhoneNumberUtils.isGlobalPhoneNumber(phone) ? android
62-
.telephony.PhoneNumberUtils.normalizeNumber(phone) : null;
79+
return android.telephony.PhoneNumberUtils
80+
.formatNumberToE164(phoneNumber,countryInfo.locale.getCountry());
6381
}
64-
return android.telephony.PhoneNumberUtils.isGlobalPhoneNumber(phone) ? phone : null;
82+
return phoneNumber.startsWith("+")
83+
? phoneNumber
84+
: ("+" + String.valueOf(countryInfo.countryCode)
85+
+ phoneNumber.replaceAll("[^\\d.]", ""));
86+
}
87+
88+
89+
/**
90+
* This method uses the country returned by {@link #getCurrentCountryInfo(Context)} to
91+
* format the phone number. Internall invokes {@link #formatPhoneNumber(String, CountryInfo)}
92+
* @param phoneNumber that may or may not itself have country code
93+
* @return
94+
*/
95+
96+
@Nullable
97+
static String formatPhoneNumberUsingCurrentCountry(
98+
@NonNull String phoneNumber, Context context) {
99+
final CountryInfo currentCountry = PhoneNumberUtils.getCurrentCountryInfo(context);
100+
return formatPhoneNumber(phoneNumber, currentCountry);
101+
}
102+
103+
@NonNull
104+
static CountryInfo getCurrentCountryInfo(@NonNull Context context) {
105+
Locale locale = getSimBasedLocale(context);
106+
107+
if (locale == null) {
108+
locale = getOSLocale();
109+
}
110+
111+
if (locale == null) {
112+
return DEFAULT_COUNTRY;
113+
}
114+
115+
Integer countryCode = PhoneNumberUtils.getCountryCode(locale.getCountry());
116+
117+
return countryCode == null ? DEFAULT_COUNTRY : new CountryInfo(locale, countryCode);
65118
}
66119

67120
/**
68121
* This method should not be called on UI thread. Potentially creates a country code by iso
69-
* * map which can take long in some devices
70-
* TODO(ashwinraghav)
122+
* map which can take long in some devices
123+
* @param providedPhoneNumber works best when formatted as e164
71124
*
72125
* @return an instance of the PhoneNumber using the SIM information
73126
*/
74127

75-
protected static PhoneNumber getPhoneNumber(String providedPhoneNumber) {
128+
protected static PhoneNumber getPhoneNumber(@NonNull String providedPhoneNumber) {
76129
String countryCode = DEFAULT_COUNTRY_CODE;
77-
String countryIso = DEFAULT_COUNTRY_ISO;
130+
String countryIso = DEFAULT_LOCALE.getCountry();
131+
78132
String phoneNumber = providedPhoneNumber;
79133
if (providedPhoneNumber.startsWith("+")) {
80134
countryCode = countryCodeForPhoneNumber(providedPhoneNumber);
@@ -89,7 +143,7 @@ private static String countryIsoForCountryCode(String countryCode) {
89143
if (countries != null) {
90144
return countries.get(0);
91145
}
92-
return DEFAULT_COUNTRY_ISO;
146+
return DEFAULT_LOCALE.getCountry();
93147
}
94148

95149
/**
@@ -1272,16 +1326,25 @@ private synchronized static Map<String, Integer> createCountryCodeByIsoMap() {
12721326
return countryCodeByIso;
12731327
}
12741328

1275-
public static int getCountryCode(String countryIso) {
1276-
if(countryIso == null){
1277-
countryIso = DEFAULT_COUNTRY_ISO;
1278-
}
1279-
Integer countryCode = CountryCodeByIsoMap.get(countryIso.toUpperCase(Locale.getDefault()));
1280-
1281-
return countryCode == null ? DEFAULT_COUNTRY_CODE_INT : countryCode;
1329+
@Nullable
1330+
public static Integer getCountryCode(String countryIso) {
1331+
return countryIso == null
1332+
? null
1333+
: CountryCodeByIsoMap.get(countryIso.toUpperCase(Locale.getDefault()));
12821334
}
12831335

12841336
private static String stripCountryCode(String phoneNumber, String countryCode) {
12851337
return phoneNumber.replaceFirst("^\\+?" + countryCode, "");
12861338
}
1339+
1340+
private static Locale getSimBasedLocale(@NonNull Context context) {
1341+
final TelephonyManager tm =
1342+
(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
1343+
final String countryIso = tm != null ? tm.getSimCountryIso() : null;
1344+
return TextUtils.isEmpty(countryIso) ? null : new Locale("", countryIso);
1345+
}
1346+
1347+
private static Locale getOSLocale() {
1348+
return Locale.getDefault();
1349+
}
12871350
}

auth/src/main/java/com/firebase/ui/auth/ui/phone/VerifyPhoneNumberFragment.java

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
106106
}
107107

108108
// Check for phone
109+
// It is assumed that the phone number that are being wired in via Credential Selector
110+
// are e164 since we store it.
109111
String phone = getArguments().getString(ExtraConstants.EXTRA_PHONE);
110112
if (!TextUtils.isEmpty(phone)) {
111113
// Use phone passed in
@@ -125,11 +127,22 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) {
125127
if (data != null) {
126128
Credential cred = data.getParcelableExtra(Credential.EXTRA_KEY);
127129
if (cred != null) {
128-
// Hint provided by telephony only sometimes contains country codes
129-
// This is gracefully handled by the PhoneNumberUtils
130-
PhoneNumber phoneNumber = PhoneNumberUtils.getPhoneNumber(cred.getId());
131-
setPhoneNumber(phoneNumber);
132-
setCountryCode(phoneNumber);
130+
// Hint selector does not always return phone numbers in e164 format.
131+
// To accommodate either case, we normalize to e164 with best effort
132+
final String unformattedPhone = cred.getId();
133+
final String formattedPhone =
134+
PhoneNumberUtils
135+
.formatPhoneNumberUsingCurrentCountry(unformattedPhone,
136+
getContext());
137+
if(formattedPhone == null) {
138+
Log.e(TAG, "Unable to normalize phone number from hint selector:"
139+
+ unformattedPhone);
140+
return;
141+
}
142+
final PhoneNumber phoneNumberObj =
143+
PhoneNumberUtils.getPhoneNumber(formattedPhone);
144+
setPhoneNumber(phoneNumberObj);
145+
setCountryCode(phoneNumberObj);
133146
}
134147
}
135148
}
@@ -147,16 +160,14 @@ public void onClick(View v) {
147160

148161
@Nullable
149162
String getPseudoValidPhoneNumber() {
150-
final int countryCode = ((CountryInfo) countryListSpinner.getTag()).countryCode;
163+
final CountryInfo countryInfo = (CountryInfo) countryListSpinner.getTag();
151164
final String everythingElse = mPhoneEditText.getText().toString();
152165

153166
if (TextUtils.isEmpty(everythingElse)) {
154167
return null;
155168
}
156169

157-
String ret = "+" + String.valueOf(countryCode) + everythingElse.replaceAll("[^\\d.]", "");
158-
159-
return PhoneNumberUtils.normalize(ret);
170+
return PhoneNumberUtils.formatPhoneNumber(everythingElse, countryInfo);
160171
}
161172

162173
private void setUpCountrySpinner() {

auth/src/test/java/com/firebase/ui/auth/ui/phone/PhoneNumberUtilsTest.java

Lines changed: 103 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,34 +18,42 @@
1818

1919
package com.firebase.ui.auth.ui.phone;
2020

21+
import android.content.Context;
22+
import android.telephony.TelephonyManager;
23+
2124
import com.firebase.ui.auth.BuildConfig;
2225
import com.firebase.ui.auth.testhelpers.CustomRobolectricGradleTestRunner;
2326

2427
import org.junit.Test;
2528
import org.junit.runner.RunWith;
29+
import org.robolectric.Robolectric;
30+
import org.robolectric.RuntimeEnvironment;
2631
import org.robolectric.annotation.Config;
2732

2833
import java.util.Locale;
2934

35+
import static com.firebase.ui.auth.ui.phone.PhoneNumberUtils.*;
3036
import static com.firebase.ui.auth.ui.phone.PhoneTestConstants.RAW_PHONE;
3137
import static org.junit.Assert.assertEquals;
38+
import static org.junit.Assert.assertNull;
39+
import static org.mockito.Mockito.mock;
40+
import static org.mockito.Mockito.when;
41+
import static org.robolectric.Shadows.shadowOf;
3242

3343
@RunWith(CustomRobolectricGradleTestRunner.class)
3444
@Config(constants = BuildConfig.class, sdk = 21)
3545
public class PhoneNumberUtilsTest {
36-
private static final String INVENTED_ISO = "random";
37-
3846
@Test
3947
public void testGetPhoneNumber() throws Exception {
40-
final PhoneNumber number = PhoneNumberUtils.getPhoneNumber(RAW_PHONE);
48+
final PhoneNumber number = getPhoneNumber(RAW_PHONE);
4149
assertEquals(PhoneTestConstants.PHONE_NO_COUNTRY_CODE, number.getPhoneNumber());
4250
assertEquals(PhoneTestConstants.US_COUNTRY_CODE, number.getCountryCode());
4351
assertEquals(PhoneTestConstants.US_ISO2, number.getCountryIso());
4452
}
4553

4654
@Test
4755
public void testGetPhoneNumber_withLongestCountryCode() throws Exception {
48-
final PhoneNumber phoneNumber = PhoneNumberUtils.getPhoneNumber(PhoneTestConstants
56+
final PhoneNumber phoneNumber = getPhoneNumber(PhoneTestConstants
4957
.YE_RAW_PHONE);
5058
assertEquals(PhoneTestConstants.PHONE_NO_COUNTRY_CODE, phoneNumber.getPhoneNumber());
5159
assertEquals(PhoneTestConstants.YE_COUNTRY_CODE, phoneNumber.getCountryCode());
@@ -54,15 +62,15 @@ public void testGetPhoneNumber_withLongestCountryCode() throws Exception {
5462

5563
@Test
5664
public void testGetPhoneNumber_withPhoneWithoutPlusSign() throws Exception {
57-
final PhoneNumber phoneNumber = PhoneNumberUtils.getPhoneNumber(PhoneTestConstants.PHONE);
65+
final PhoneNumber phoneNumber = getPhoneNumber(PhoneTestConstants.PHONE);
5866
assertEquals(PhoneTestConstants.PHONE, phoneNumber.getPhoneNumber());
5967
assertEquals(PhoneTestConstants.US_COUNTRY_CODE, phoneNumber.getCountryCode());
6068
assertEquals(PhoneTestConstants.US_ISO2, phoneNumber.getCountryIso());
6169
}
6270

6371
@Test
6472
public void testGetPhoneNumber_noCountryCode() throws Exception {
65-
final PhoneNumber number = PhoneNumberUtils.getPhoneNumber("0" + PhoneTestConstants
73+
final PhoneNumber number = getPhoneNumber("0" + PhoneTestConstants
6674
.PHONE_NO_COUNTRY_CODE);
6775
assertEquals("0" + PhoneTestConstants.PHONE_NO_COUNTRY_CODE, number.getPhoneNumber());
6876
assertEquals(PhoneTestConstants.US_COUNTRY_CODE, number.getCountryCode());
@@ -71,8 +79,94 @@ public void testGetPhoneNumber_noCountryCode() throws Exception {
7179

7280
@Test
7381
public void testGetCountryCode() throws Exception {
74-
assertEquals(86, PhoneNumberUtils.getCountryCode(Locale.CHINA.getCountry()));
75-
assertEquals(1, PhoneNumberUtils.getCountryCode(null));
76-
assertEquals(1, PhoneNumberUtils.getCountryCode(new Locale("", "DJJZ").getCountry()));
82+
assertEquals(new Integer(86), getCountryCode(Locale.CHINA.getCountry()));
83+
assertEquals(null, getCountryCode(null));
84+
assertEquals(null, getCountryCode(new Locale("", "DJJZ").getCountry()));
85+
}
86+
87+
@Test
88+
@Config(constants = BuildConfig.class, sdk = 21)
89+
public void testFormatNumberToE164_aboveApi21() {
90+
String validPhoneNumber = "+919994947354";
91+
CountryInfo indiaCountryInfo = new CountryInfo(new Locale("", "IN"), 91);
92+
//no leading plus
93+
assertEquals(validPhoneNumber, formatPhoneNumber("9994947354", indiaCountryInfo));
94+
//no country code
95+
assertEquals(validPhoneNumber, formatPhoneNumber("919994947354", indiaCountryInfo));
96+
//fully formatted
97+
assertEquals(validPhoneNumber, formatPhoneNumber("+919994947354", indiaCountryInfo));
98+
//with hyphens
99+
assertEquals(validPhoneNumber, formatPhoneNumber("+91-(999)-(49)-(47354)", indiaCountryInfo));
100+
//with spaces leading plus
101+
assertEquals(validPhoneNumber, formatPhoneNumber("+91 99949 47354", indiaCountryInfo));
102+
// space formatting
103+
assertEquals(validPhoneNumber, formatPhoneNumber("91 99949 47354", indiaCountryInfo));
104+
// parantheses and hyphens
105+
assertEquals(validPhoneNumber, formatPhoneNumber("(99949) 47-354", indiaCountryInfo));
106+
// mismatched country
107+
assertEquals(validPhoneNumber, formatPhoneNumber("+919994947354",
108+
new CountryInfo(
109+
new Locale("", "US"), 1)));
110+
// incorrect country with well formatted number
111+
assertNull(formatPhoneNumber("999474735", indiaCountryInfo));
112+
113+
// incorrect country with unformattednumber
114+
assertNull(validPhoneNumber, formatPhoneNumber("919994947354",
115+
new CountryInfo(
116+
new Locale("", "US"), 1)));
117+
//incorrect country, incorrect phone number
118+
assertNull(formatPhoneNumber("+914349873457", new CountryInfo(
119+
new Locale("", "US"), 1)));
120+
}
121+
122+
@Test
123+
@Config(constants = BuildConfig.class, sdk = 16)
124+
public void testFormatNumberToE164_belowApi21() {
125+
String validPhoneNumber = "+919994947354";
126+
CountryInfo indiaCountryInfo = new CountryInfo(new Locale("", "IN"), 91);
127+
// no leading plus
128+
assertEquals(validPhoneNumber, formatPhoneNumber("9994947354", indiaCountryInfo));
129+
// fully formatted
130+
assertEquals(validPhoneNumber, formatPhoneNumber("+919994947354", indiaCountryInfo));
131+
// parantheses and hyphens
132+
assertEquals(validPhoneNumber, formatPhoneNumber("(99949) 47-354", indiaCountryInfo));
133+
134+
// The following cases would fail for lower api versions.
135+
// Leaving tests in place to formally identify cases
136+
137+
// no leading +
138+
// assertEquals(validPhoneNumber, formatPhoneNumber("919994947354", indiaCountryInfo));
139+
140+
// with hyphens
141+
// assertEquals(validPhoneNumber, formatPhoneNumber("+91-(999)-(49)-(47354)",
142+
// indiaCountryInfo));
143+
144+
// with spaces leading plus
145+
// assertEquals(validPhoneNumber, formatPhoneNumber("+91 99949 47354", indiaCountryInfo));
146+
147+
// space formatting
148+
// assertEquals(validPhoneNumber, formatPhoneNumber("91 99949 47354", indiaCountryInfo));
149+
150+
// invalid phone number
151+
// assertNull(formatPhoneNumber("999474735", indiaCountryInfo));
152+
}
153+
154+
@Test
155+
public void testGetCurrentCountryInfo_fromSim() {
156+
Context context = mock(Context.class);
157+
TelephonyManager telephonyManager = mock(TelephonyManager.class);
158+
159+
when(context.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(telephonyManager);
160+
when(telephonyManager.getSimCountryIso()).thenReturn("IN");
161+
assertEquals(new CountryInfo(new Locale("", "IN"), 91), getCurrentCountryInfo(context));
162+
}
163+
164+
@Test
165+
public void testGetCurrentCountryInfo_noTelephonyReturnsDefaultLocale() {
166+
Context context = mock(Context.class);
167+
assertEquals(new CountryInfo(
168+
Locale.getDefault(),
169+
PhoneNumberUtils.getCountryCode(Locale.getDefault().getCountry())),
170+
getCurrentCountryInfo(context));
77171
}
78172
}

0 commit comments

Comments
 (0)