需求及分析
客户需求:
使用hierarchyviewer工具可以发现这个界面对应的activity是DialtactsActivity.
通过搜索拨号盘的source id(dialpad_view)找到dialpad_fragment.xml,从而找到DialpadFragment.java。
最后在Dialpad_view.xml里面自定义了一个EditText类来容纳拨号的内容:
<view class="com.android.phone.common.dialpad.DigitsEditText"
xmlns:ex="http://schemas.android.com/apk/res-auto"
android:id="@+id/digits"
public class DigitsEditText extends ResizingTextEditText
所以,主要的函数逻辑在DialpadFragment.java里面。
线索
在DialpadFragment里面全局搜索mDigits。在初始化函数onCreateView里面,对mDigits注册了对输入内容变化的监听器。
mDigits.addTextChangedListener(this);
PhoneNumberFormatter.setPhoneNumberFormattingTextWatcher(getActivity(), mDigits);
在PhoneNumberFormatter中会启动一个TextWatcherLoadAsyncTask来另起一个线程来进行数字的分割。
//PhoneNumberFormatter.java
public static final void setPhoneNumberFormattingTextWatcher(Context context, TextView textView) {
new TextWatcherLoadAsyncTask(GeoUtil.getCurrentCountryIso(context), textView).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
}
注意,使用国家ISO码以及mDigits这个TextView来初始化这个对象。这样在该对象中就可以直接操作TextView的内容了。而在这个AsyncTask中会实例化一个PhoneNumberFormattingTextWatcher对象来对变化的数字进行格式化。
最后的数字格式处理是在这个TextWatcher的afterTextChanged中进行处理。
//PhoneNumberFormattingTextWatcher.java
public synchronized void afterTextChanged(Editable s) {
String formatted = reformat(s, Selection.getSelectionEnd(s));
if (formatted != null) {
int rememberedPos = mFormatter.getRememberedPosition();
s.replace(0, s.length(), formatted, 0, formatted.length());
}
}
针对通过Intent,启动拨号界面显示的号码,我们在DialpadFragment中可以找到fillDigitsIfNecessary。这个函数中,该函数接收通过intent调用发送过来的号码,并且调用下面的setFormattedDigits来格式化这个号码,并且显示在mDigits这个TextView中。
/** DialpadFragment.java * Sets formatted digits to digits field. */
private void setFormattedDigits(String data, String normalizedNumber) {
// strip the non-dialable numbers out of the data string.
String dialString = PhoneNumberUtils.extractNetworkPortion(data);
dialString =
PhoneNumberUtils.formatNumber(dialString, normalizedNumber, mCurrentCountryIso);
if (!TextUtils.isEmpty(dialString)) {
Editable digits = mDigits.getText();
digits.replace(0, digits.length(), dialString);
// for some reason this isn't getting called in the digits.replace call above..
// but in any case, this will make sure the background drawable looks right
afterTextChanged(digits);
}
}
这两个的共同点是都使用了CountryIso这个参数。所以数字是按照不同的国家,显示不同的样式的。
这两个分支中CountryIso都是通过下面的代码获取的:
mCurrentCountryIso = GeoUtil.getCurrentCountryIso(getActivity());
//packages/apps/contactcommon/src/com/android/contacts/common/location/CountryDetector.java
public String getCurrentCountryIso() {
String result = null;
if (isNetworkCountryCodeAvailable()) {
result = getNetworkBasedCountryIso();
Log.w(TAG, "getNetworkBasedCountryIso." + result);
}
if (TextUtils.isEmpty(result)) {
result = getLocationBasedCountryIso();
Log.w(TAG, "getLocationBasedCountryIso." + result);
}
if (TextUtils.isEmpty(result)) {
result = getSimBasedCountryIso();
Log.w(TAG, "getSimBasedCountryIso." + result);
}
if (TextUtils.isEmpty(result)) {
result = getLocaleBasedCountryIso();
Log.w(TAG, "getLocaleBasedCountryIso." + result);
}
if (TextUtils.isEmpty(result)) {
result = DEFAULT_COUNTRY_ISO;
}
Log.w(TAG, "DEFAULT_COUNTRY_ISO." + result);
//for debug
result = "BR";
return result.toUpperCase(Locale.US);
}
这个值是由很多条件判断来决定的。先判断当前连接上的网络所属的国家。如果为空,判断当前的物理位置所在的国家。如果依然为空,获取Sim卡所属的国家。依然为空,判断当前选择的语言Locale所属的国家。否则就默认使用DEFAULT_COUNTRY_ISO = “US”作为国家码。
注意,上面的Log打印语句都是添加用来验证国家码是否会影响分割方法的。
修改之后输出结果为:
W/CountryDetector( 3948): getLocationBasedCountryIso.null
W/CountryDetector( 3948): getSimBasedCountryIso.
W/CountryDetector( 3948): getLocaleBasedCountryIso.US
W/CountryDetector( 3948): DEFAULT_COUNTRY_ISO.US
说明Location和Sim都是为null,而LocaleBaseCountryIso则为US。
getLocaleBasedCountryIso()这个函数还有点意思,是通过属性中的值类判定用户属于哪个locale的:
//Locale.java
public static Locale getDefaultLocaleFromSystemProperties() {
final String languageTag = System.getProperty("user.locale", "");
final Locale defaultLocale;
if (!languageTag.isEmpty()) {
defaultLocale = Locale.forLanguageTag(languageTag);
} else {
String language = System.getProperty("user.language", "en");
String region = System.getProperty("user.region", "US");
String variant = System.getProperty("user.variant", "");
defaultLocale = new Locale(language, region, variant);
}
return defaultLocale;
}
然后通过defaultLocale.getCountry()来获取国家。第二个,也就是region实际上就是country的值。
查一下“String region = System.getProperty(“user.region”, “US”);”这个值是怎么获取的(其实也就是systemProperties.getProperty(name, defaultValue))。
在liccore/luni/src/main/java/java/lang/System.java里面有如下语句:
static {
in = new BufferedInputStream(new FileInputStream(FileDescriptor.in));
unchangeableSystemProperties = initUnchangeableSystemProperties();
systemProperties = createSystemProperties();//会将unchangeableSystemProperties的内容添加到systemProperties里面
addLegacyLocaleSystemProperties();
}
在addLegacyLocaleSystemProperties里面,会根据user.locale的值来设定一些值,如下:
private static void addLegacyLocaleSystemProperties() {
final String locale = getProperty("user.locale", "");
if (!locale.isEmpty()) {
Locale l = Locale.forLanguageTag(locale);
setUnchangeableSystemProperty("user.language", l.getLanguage());
setUnchangeableSystemProperty("user.region", l.getCountry());
setUnchangeableSystemProperty("user.variant", l.getVariant());
} else {
// If "user.locale" isn't set we fall back to our old defaults of
// language="en" and region="US" (if unset) and don't attempt to set it.
// The Locale class will fall back to using user.language and user.region if unset.
final String language = getProperty("user.language", "");
final String region = getProperty("user.region", "");
if (language.isEmpty()) { setUnchangeableSystemProperty("user.language", "en");
}
if (region.isEmpty()) {
setUnchangeableSystemProperty("user.region", "US");
}}}
其实就是根据user.locale的值来设定user.language和user.region的值。
总结
通过上面的分析,我们知道拨号界面数字的分割是按照当前网络所属的国家来显示。如果针对特定国家的项目,可以通过设定user.locale来改变拨号界面数字的分割方式,以适应相关的客户需求。这个分割方式都是google提供的原生方式,推荐不要修改。当然如果客户有特殊需求,那么就需要修改PhoneNumberFormattingTextWatcher里面afterTextChanged函数中的reformat方法,来满足客户需求。