概述
Android的通话设置代码5.0之前在packages/apps/Phone目录下,5.0之后在packages/services/Telephony/目录下,一直是在com.android.phone进程中。
Telephony目录下代码主要可以分为通话设置(呼叫等待、呼叫转移等)和网络设置(开启数据网络、数据漫游,网络选择等)两个部分,通话设置原生的入口在拨号盘的菜单中,网络设置在设置程序的菜单中。还有双卡设置和apn设置,也是运行在com.android.phone进程中,不过代码在设置Settings中。
通话设置
主界面
packages/services/Telephony/src/com/android/phone/CallFeaturesSetting.java
public class CallFeaturesSetting extends PreferenceActivity
继承PreferenceActivity,主界面。
private void initUi() {
addPreferencesFromResource(R.xml.call_feature_setting);
...
}
android的设置UI都是列表风格,基本操作是点击。所以看xml配置基本可以看个差不多:
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:phone="http://schemas.android.com/apk/res/com.android.phone"
android:title="@string/call_settings">
<PreferenceScreen
android:key="phone_account_settings_preference_screen"
android:title="@string/phone_accounts">
<intent
android:targetPackage="com.android.phone"
android:targetClass="com.android.phone.settings.PhoneAccountSettingsActivity"/> <!--sip设置相关-->
</PreferenceScreen>
<PreferenceScreen
android:key="button_voicemail_category_key" <!--语音邮箱-->
android:title="@string/voicemail" />
<PreferenceScreen
android:key="button_fdn_key" <!--固定拨号-->
android:title="@string/fdn"
android:persistent="false" />
<PreferenceScreen
android:key="@string/wifi_calling_settings_key" <!--WiFi通话-->
android:title="@string/wifi_calling">
<intent android:action="android.intent.action.MAIN"
android:targetPackage="com.android.settings"
android:targetClass="com.android.settings.Settings$WifiCallingSettingsActivity"/>
</PreferenceScreen>
<CheckBoxPreference
android:key="button_enable_video_calling" <!--是否开启视频通话-->
android:title="@string/enable_video_calling_title"
android:persistent="true"
android:defaultValue="true" />
<CheckBoxPreference
android:key="button_auto_retry_key"
android:title="@string/auto_retry_mode_title" <!--是否开启自动重拨-->
android:persistent="false"
android:summary="@string/auto_retry_mode_summary"/>
<!-- M: Add for [HAC] -->
<CheckBoxPreference
android:key="button_hac_key"
android:persistent="true" <!--是否启用助听器兼容模式-->
android:summary="@string/hac_mode_summary"
android:title="@string/hac_mode_title" />
<!-- M: Add for [DualMic] -->
<CheckBoxPreference
android:key="button_dual_mic_key"
android:title="@string/dual_mic_title" <!--是否启用双麦克降噪-->
android:defaultValue="true"
android:summary="@string/dual_mic_summary"/>
<!-- M: Add for [MagiConference] -->
<CheckBoxPreference
android:key="button_magi_conference_key"
android:title="@string/magi_conference_title" <!--是否启用多人通话语音增强-->
android:defaultValue="true"
android:summary="@string/magi_conference_summary"/>
<!-- M: Add for [ANC] -->
<CheckBoxPreference
android:key="button_anc_key"
android:title="@string/anc_title" <!--是否启用主动式消噪-->
android:defaultValue="true"
android:summary="@string/anc_on"/>
<!-- M: Add for [IpProfix] -->
<PreferenceScreen
android:key="button_ip_prefix_key" <!--IP拨号设置-->
android:title="@string/ip_prefix_setting"
android:summary="@string/ip_prefix_setting_sum"
android:persistent="false">
<intent android:action="android.intent.action.MAIN"
android:targetPackage="com.android.phone"
android:targetClass="com.mediatek.settings.IpPrefixPreference"/>
</PreferenceScreen>
<PreferenceScreen
android:key="button_gsm_more_expand_key"
android:title="@string/labelGSMMore"
android:summary="@string/sum_gsm_call_settings" <!--gsm通话设置-->
android:persistent="false" />
<PreferenceScreen
android:key="button_cdma_more_expand_key" <!--cdma通话设置-->
android:title="@string/labelCDMAMore"
android:summary="@string/sum_cdma_call_settings"
android:persistent="false" />
</PreferenceScreen>
从我手里拿的插着电信卡的mtk机器来看,除了IP设置、cdma通话设置和双麦克降噪,其它都没有。确实其它的要不用的少,要不在国内就没有这项业务。
双卡处理
@Override
protected void onCreate(Bundle icicle) {
...
mSubscriptionInfoHelper = new SubscriptionInfoHelper(this, getIntent());
mSubscriptionInfoHelper.setActionBarTitle(
getActionBar(), getResources(), R.string.call_settings_with_label);
mPhone = mSubscriptionInfoHelper.getPhone();
...
}
构造函数中的mSubscriptionInfoHelper 类就是确定mPhone对象是哪个。
packages/services/Telephony/src/com/android/phone/SubscriptionInfoHelper.java
private static int mSubId = NO_SUB_ID;
private static String mSubLabel;
/**
* Instantiates the helper, by extracting the subscription id and label from the intent.
*/
public SubscriptionInfoHelper(Context context, Intent intent) {
mContext = context;
if (intent != null) {
mSubId = intent.getIntExtra(SUB_ID_EXTRA, NO_SUB_ID);
mSubLabel = intent.getStringExtra(SUB_LABEL_EXTRA);
/// M: Add for special case
initForOtherCase(intent);
}
log("SubscriptionInfoHelper: subid = " + mSubId
+ "; mSubLabel = " + mSubLabel);
}
mSubId是关键,由intent获取该值,然后依据它就能确定phone对象了。依据subid的不同,就进入不同卡的设置。
gsm通话设置
代码中先加载布局文件,然后调用GsmUmtsCallOptions的静态方法设置点击事件
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:phone="http://schemas.android.com/apk/res/com.android.phone"
android:title="@string/labelGSMMore">
<PreferenceScreen
android:key="call_forwarding_key"
android:title="@string/labelCF"
android:persistent="false" />
<!-- M: Add for CallBarring -->
<PreferenceScreen
android:key="button_cb_expand_key"
android:title="@string/lable_call_barring"
android:persistent="false"
android:summary="@string/call_barring_sum">
</PreferenceScreen>
<PreferenceScreen
android:key="additional_gsm_call_settings_key"
android:title="@string/additional_gsm_call_settings"
android:persistent="false" />
</PreferenceScreen>
packages/services/Telephony/src/com/android/phone/GsmUmtsCallOptions.java
public static void init(PreferenceScreen prefScreen, SubscriptionInfoHelper subInfoHelper) {
Preference callForwardingPref = prefScreen.findPreference(CALL_FORWARDING_KEY);
callForwardingPref.setIntent(subInfoHelper.getIntent(GsmUmtsCallForwardOptions.class));
Preference additionalGsmSettingsPref =
prefScreen.findPreference(ADDITIONAL_GSM_SETTINGS_KEY);
additionalGsmSettingsPref.setIntent(
subInfoHelper.getIntent(GsmUmtsAdditionalCallOptions.class));
}
添加了呼叫转移设置和GsmUmtsAdditionalCallOptions,还有呼叫限制(这个国内手机一般没有,不做分析)
packages/services/Telephony/src/com/android/phone/GsmUmtsAdditionalCallOptions.java
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
addPreferencesFromResource(R.xml.gsm_umts_additional_options);
...
}
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:phone="http://schemas.android.com/apk/res/com.android.phone"
android:title="@string/additional_gsm_call_settings">
<com.android.phone.CLIRListPreference <!--主叫隐藏设置-->
android:key="button_clir_key"
android:title="@string/labelCallerId"
android:persistent="false"
android:defaultValue="DEFAULT"
android:entries="@array/clir_display_values"
android:entryValues="@array/clir_values"
android:dialogTitle="@string/labelCallerId"
android:summary="@string/sum_loading_settings"
android:enabled="false"/>
<com.android.phone.CallWaitingCheckBoxPreference <!--呼叫等待开关-->
android:key="button_cw_key"
android:title="@string/labelCW"
android:persistent="false"
android:summaryOn="@string/sum_cw_enabled"
android:summaryOff="@string/sum_cw_disabled"
android:enabled="false"/>
</PreferenceScreen>
添加了两个设置项如上,其中主叫隐藏选项一般手机也是没有的,隐藏掉了。那么gsm的设置也就是呼叫转移和呼叫等待了。
gsm呼叫转移设置流程
packages/services/Telephony/src/com/android/phone/GsmUmtsCallForwardOptions.java
呼叫转移布局中加载了四个CallForwardEditPreference,分别代表始终转接、占线时转接、无人接听时转接和无法接通时转接。
packages/services/Telephony/src/com/android/phone/CallForwardEditPreference.java
void init(TimeConsumingPreferenceListener listener, boolean skipReading, Phone phone) {
...
mPhone.getCallForwardingOption(reason,
mHandler.obtainMessage(MyHandler.MESSAGE_GET_CF,
// unused in this case
CommandsInterface.CF_ACTION_DISABLE,
MyHandler.MESSAGE_GET_CF, null));
...
}
获取当前呼叫转移设置的代码如上,
case MESSAGE_GET_CF:
handleGetCFResponse(msg);
在MyHandler的消息处理中获取结果,handleGetCFResponse获取到结果并更新UI
@Override
protected void onDialogClosed(boolean positiveResult) {
super.onDialogClosed(positiveResult);
...
mPhone.setCallForwardingOption(action,
reason,
number,
time,
mHandler.obtainMessage(MyHandler.MESSAGE_SET_CF,
action,
MyHandler.MESSAGE_SET_CF));
...
}
设置呼叫转移是在onDialogClosed方法中,设置的结果返回同获取,都在MyHandler的消息处理中
gsm呼叫等待设置流程
packages/services/Telephony/src/com/android/phone/CallWaitingCheckBoxPreference.java
@Override
protected void onClick() {
...
mPhone.setCallWaiting(isChecked(),
mHandler.obtainMessage(MyHandler.MESSAGE_SET_CALL_WAITING));
...
}
设置在点击事件中,如上所示。初始化和设置结果同呼叫转移,都是handler处理。
这个也基本是大多数通话设置的代码流程:调用Phone接口中的方法与ril通信,返回结果由Handler处理。
cdma通话设置
private void initUi() {
...
if (!carrierConfig.getBoolean(
CarrierConfigManager.KEY_VOICE_PRIVACY_DISABLE_UI_BOOL)) {
addPreferencesFromResource(R.xml.cdma_call_privacy);
/// M: for ALPS02087723, get the right cdma phone instance @{
CdmaVoicePrivacyCheckBoxPreference ccp =
(CdmaVoicePrivacyCheckBoxPreference)findPreference(BUTTON_CP_KEY);
if (ccp != null) {
ccp.setPhone(mPhone);
}
/// @}
}
/// M: For C2K project to group GSM and C2K Call Settings @{
log("CallFeartueSetting call isCdmaSupport");
log("isCdmaSupport = " + TelephonyUtils.isCdmaSupport());
if (TelephonyUtils.isCdmaSupport()) {
log("CallFeartueSetting call showCallOption");
//mCallFeaturesSettingExt.showCallOption(prefSet);
addPreferencesFromResource(R.xml.mtk_cdma_call_options);
}
...
}
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:phone="http://schemas.android.com/apk/res/com.android.phone"
android:title="@string/labelCDMAMore">
<PreferenceScreen
android:key="button_cf_expand_key" <!--呼叫转移-->
android:title="@string/labelCF"
android:persistent="false"
>
</PreferenceScreen>
<Preference
android:key="button_cw_key" <!--呼叫等待-->
android:title="@string/labelCW"
android:persistent="false"/>
</PreferenceScreen>
代码中可见cdma有呼叫转移、呼叫等待和语音隐私权。语音隐私见
This is a feature of CDMA (standardized in IS-95) and is called Voice Privacy.
See an Analysis of IS-95 CDMA Voice Privacy by M.Zhang, et al. from 2000, free download here
Citation (the real paper begins at p.10 in the PDF:
Abstract. The voice privacy of IS-95 CDMA cellular system is analyzed in this paper. By exploiting information redundancy on the downlink traffic channel, it is shown that an eavesdropper can recover the voice privacy mask after eavesdropping the transmission on the downlink traf- fic channel for about one second. Thus, IS-95 CDMA voice privacy is vulnerable under ciphertext-only attacks.
That cryptanalysis is now 12 years old and already then was the scheme considered broken. I guess it's easy to suggest to just leave the setting disabled.
2000年的东西,可惜这个加密算法已经被破了,和stk一样成历史了。
cdma呼叫转移点击后会跳转到packages/services/Telephony/src/com/mediatek/settings/cdma/CdmaCallForwardOptions.java,看下设置流程:
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
if (preference == mButtonCFU) {
showDialog(DIALOG_CFU);
...
}
点击后弹dialog
@Override
protected Dialog onCreateDialog(final int id) {
...
if (radioGroup.getCheckedRadioButtonId() == R.id.enable) {
/**
* Get the CF type according to id.
* the CF_HEADERS list is "enable, disable" format
* so we can use the id to locate the SystemProperties
* we need to use.
*/
int cfType = id * 2;
cf = CF_HEADERS[cfType] + mEditNumber.getText();
} else {
int cfType = id * 2 + 1;
cf = CF_HEADERS[cfType];
}
dialog.dismiss();
setCallForward(cf);
...
}
CF_HEADERS是从系统属性中获取,因为不同国家的不一样。
private static final String[] CF_HEADERS = {
SystemProperties.get("ro.cdma.cfu.enable"), SystemProperties.get("ro.cdma.cfu.disable"),
SystemProperties.get("ro.cdma.cfb.enable"), SystemProperties.get("ro.cdma.cfb.disable"),
SystemProperties.get("ro.cdma.cfnr.enable"), SystemProperties.get("ro.cdma.cfnr.disable"),
SystemProperties.get("ro.cdma.cfdf.enable"), SystemProperties.get("ro.cdma.cfdf.disable"),
SystemProperties.get("ro.cdma.cfall.disable")
};
private void setCallForward(String cf) {
if (mSubId == -1 || cf == null || cf.isEmpty()) {
Log.d(LOG_TAG, "setCallForward null return");
return;
}
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:" + cf));
int phoneId = SubscriptionManager.getPhoneId(mSubId);
PhoneAccountHandle phoneAccountHandle = PhoneUtils.makePstnPhoneAccountHandle(phoneId);
intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
startActivity(intent);
}
最终是拨号,这就是cdma设置的独特之处,设置呼叫等待和呼叫转移都是拨出特殊号码完成。所以cdma无法获取到设置信息的,只能是用户打电话确认,设置的结果也是以听到的语音提示为准。呼叫等待流程类似,不再分析。
TimeConsumingPreferenceActivity
packages/services/Telephony/src/com/android/phone/TimeConsumingPreferenceActivity.java
通话设置中很多动作是耗时的,所以一般UI上会弹出一个进度框显示“正在读取”或者“正在设置”。这个使用的地方比较多,所以在代码中有一个基类来统一处理这个问题,那就是TimeConsumingPreferenceActivity。这个类有完整的一套显示进度框和针对动作进行中、动作完毕和动作结果出错的代码。下面只介绍下子类中使用的入口方法:
@Override
public void onStarted(Preference preference, boolean reading) { //对话框显示开始
if (DBG) dumpState();
if (DBG) Log.d(LOG_TAG, "onStarted, preference=" + preference.getKey()
+ ", reading=" + reading);
mBusyList.add(preference.getKey());
if (mIsForeground) { //只有在前台才显示
if (reading) { //依据是读取还是设置显示不同的提示
showDialog(BUSY_READING_DIALOG);
} else {
showDialog(BUSY_SAVING_DIALOG);
}
}
}
@Override
public void onFinished(Preference preference, boolean reading) { //关闭dialog
if (DBG) dumpState();
if (DBG) Log.d(LOG_TAG, "onFinished, preference=" + preference.getKey()
+ ", reading=" + reading);
mBusyList.remove(preference.getKey());
if (mBusyList.isEmpty()) {
if (reading) {
dismissDialogSafely(BUSY_READING_DIALOG);
} else {
dismissDialogSafely(BUSY_SAVING_DIALOG);
}
}
preference.setEnabled(true);
}
网络设置
主界面
packages/services/Telephony/src/com/android/phone/MobileNetworkSettings.java,onCreate中
addPreferencesFromResource(R.xml.network_setting);
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
android:title="@string/mobile_networks">
<PreferenceScreen
android:key="cdma_lte_data_service_key" <!--设置数据服务-->
android:title="@string/cdma_lte_data_service">
</PreferenceScreen>
<SwitchPreference
android:key="button_roaming_key" <!--数据漫游开关-->
android:title="@string/roaming"
android:persistent="false"
android:summaryOn="@string/roaming_enable"
android:summaryOff="@string/roaming_disable"/>
<ListPreference
android:key="preferred_network_mode_key" <!--网络模式-->
android:title="@string/preferred_network_mode_title"
android:summary="@string/preferred_network_mode_summary"
android:entries="@array/preferred_network_mode_choices"
android:entryValues="@array/preferred_network_mode_values"
android:dialogTitle="@string/preferred_network_mode_dialogtitle" />
<ListPreference
android:key="enabled_networks_key" <!--切换2/3/4G-->
android:title="@string/preferred_network_mode_title"
android:summary="@string/preferred_network_mode_summary"
android:entries="@array/enabled_networks_choices"
android:entryValues="@array/enabled_networks_values"
android:dialogTitle="@string/preferred_network_mode_dialogtitle" />
<SwitchPreference
android:key="enhanced_4g_lte" <!--使用增强型LTE模式-->
android:title="@string/enhanced_4g_lte_mode_title"
android:persistent="false"
android:summary="@string/enhanced_4g_lte_mode_summary"/>
</PreferenceScreen>
布局文件如上,其中preferred_network_mode_key和enabled_networks_key就最终调用的Phone接口来说是一样的,都是setPreferredNetworkType。不一样的就是上层的ui显示和设置都简化了,2/3/4G三个选项明显比十几个英文网络选项好理解多了。
mCdmaOptions = new CdmaOptions(this, prefSet, mPhone);
mGsmUmtsOptions = new GsmUmtsOptions(this, prefSet, phoneSubId);
和通话设置类似,这也有各自的网络设置
gsm网络设置
packages/services/Telephony/src/com/android/phone/GsmUmtsOptions.java
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res/com.android.phone">
<PreferenceScreen <!--APN设置-->
android:key="button_apn_key"
android:title="@string/apn_settings"
android:persistent="false">
<!-- The launching Intent will be defined thru code as we need to pass some Extra -->
</PreferenceScreen>
<PreferenceScreen <!--运营商网络选择-->
android:key="button_carrier_sel_key"
android:title="@string/networks"
android:summary="@string/sum_carrier_select"
android:persistent="false">
</PreferenceScreen>
<PreferenceScreen <!--运营商网络选择,提供了自定义接口,不过默认是不显示的-->
android:key="carrier_settings_key"
android:title="@string/carrier_settings_title">
<intent android:action="android.intent.action.MAIN"
android:targetPackage="@string/carrier_settings"
android:targetClass="@string/carrier_settings_menu" />
</PreferenceScreen>
</PreferenceScreen>
有两个一个,是apn设置,另外一个是运营商网络选择
cdma网络设置
packages/services/Telephony/src/com/android/phone/CdmaOptions.java
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res/com.android.phone">
<com.android.phone.CdmaSystemSelectListPreference <!--cdma漫游模式-->
android:key="cdma_system_select_key"
android:title="@string/cdma_system_select_title"
android:summary="@string/cdma_system_select_summary"
android:entries="@array/cdma_system_select_choices"
android:entryValues="@array/cdma_system_select_values"
android:dialogTitle="@string/cdma_system_select_dialogtitle" />
<com.android.phone.CdmaSubscriptionListPreference <!--CDMA 订阅-->
android:key="cdma_subscription_key"
android:title="@string/cdma_subscription_title"
android:summary="@string/cdma_subscription_summary"
android:entries="@array/cdma_subscription_choices"
android:entryValues="@array/cdma_subscription_values"
android:dialogTitle="@string/cdma_subscription_dialogtitle" />
<PreferenceScreen <!--apns设置-->
android:key="button_apn_key_cdma"
android:title="@string/apn_settings"
android:persistent="false">
<!-- The launching Intent will be defined thru code as we need to pass some Extra -->
</PreferenceScreen>
<PreferenceScreen <!--激活设备,国内都是插sim卡,所以这个没有使用-->
android:key="cdma_activate_device_key"
android:title="@string/cdma_activate_device">
<intent android:action="com.android.phone.PERFORM_VOICELESS_CDMA_PROVISIONING">
<extra android:name="autoStart" android:value="true" />
</intent>
</PreferenceScreen>
<PreferenceScreen <!--同gsm的网络选择,没有使用-->
android:key="carrier_settings_key"
android:title="@string/carrier_settings_title">
<intent android:action="android.intent.action.MAIN"
android:targetPackage="@string/carrier_settings"
android:targetClass="@string/carrier_settings_menu" />
</PreferenceScreen>
</PreferenceScreen>
cdma订阅见
RUIM与NV[关于CDMA订阅在RUIM与NV之间切换的问题],国内基本都是插卡机器,所以除了cdma漫游和apn之外其它都没使用(本人手机上cdma漫游也没有找到)。
运营商网络选择
packages/services/Telephony/src/com/android/phone/NetworkSetting.java
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
android:key="list_networks_key"
android:title="@string/label_available">
<Preference
android:key="button_srch_netwrks_key"
android:title="@string/search_networks"
android:summary="@string/sum_search_networks"
android:persistent="false"/>
<Preference
android:key="button_auto_select_key"
android:title="@string/select_automatically"
android:summary="@string/sum_select_automatically"
android:persistent="false"/>
</PreferenceScreen>
从布局文件看,有两个项目:搜索网络和自动选择。手动网络点击后会开始搜索网络:
private void loadNetworksList() {
...
mNetworkQueryService.startNetworkQuery(mCallback, mPhoneId);
...
}
mNetworkQueryService是个Service,在OnCreate中绑定该服务
packages/services/Telephony/src/com/android/phone/NetworkQueryService.java
public void startNetworkQuery(INetworkQueryServiceCallback cb, int phoneId) {
...
phone.getAvailableNetworks(
mHandler.obtainMessage(EVENT_NETWORK_SCAN_COMPLETED));
...
}
该服务调用Phone的接口,搜索完成后会回调
private void networksListLoaded(List<OperatorInfo> result, int status)
networksListLoaded会更新UI,显示所有搜索到的网络。点击显示的网络可以在该网络上注册,但不是随便一个都能注册,例如联通卡肯定注册不到移动的网络上。
自动选择调用Phone接口setNetworkSelectionModeAutomatic方法,点击后手机自动选择一个网络注册。
网络选择gsm有,cdma没有。这个又和cdma的特性有关,cdma搜索一遍网络大约十五分钟,对用户来说是不可忍受的,所以干脆去掉了。cdma只会注册到搜索到的第一个网络上。
双卡设置
packages/apps/Settings/src/com/android/settings/sim/SimSettings.java
<PreferenceCategory
android:key="sim_activities" <!--插入的SIM卡列表-->
android:title="@string/sim_pref_divider" >
<Preference android:key="sim_cellular_data" <!--默认上网卡-->
android:title="@string/cellular_data_title" />
<Preference android:key="sim_calls" <!--默认拨号卡-->
android:title="@string/calls_title" />
<Preference android:key="sim_sms" <!--默认短信卡-->
android:title="@string/sms_messages_title" />
</PreferenceCategory>
布局文件比较简单,后三项是设置默认卡,第一项是显示当前插入的sim卡列表。
默认卡设置流程
public boolean onPreferenceTreeClick(final PreferenceScreen preferenceScreen,
final Preference preference) {
...
} else if (findPreference(KEY_CELLULAR_DATA) == preference) {
intent.putExtra(SimDialogActivity.DIALOG_TYPE_KEY, SimDialogActivity.DATA_PICK);
context.startActivity(intent);
...
}
以设置网络默认卡为例,发送了到了packages/apps/Settings/src/com/android/settings/sim/SimDialogActivity.java去处理,插入双卡的时候显示双卡供用户选择。
public Dialog createDialog(final Context context, final int id) {
...
final DialogInterface.OnClickListener selectionListener =
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int value) {
final SubscriptionInfo sir;
switch (id) {
case DATA_PICK:
...
setDefaultDataSubId(context, targetSub);
...
}
创建的dialog点击事件中调用setDefaultDataSubId。
private void setDefaultDataSubId(final Context context, final int subId) {
final SubscriptionManager subscriptionManager = SubscriptionManager.from(context);
...
subscriptionManager.setDefaultDataSubId(subId);
...
}
调用frameworks/base/telephony/java/android/telephony/SubscriptionManager.java完成功能。
public void setDefaultDataSubId(int subId) {
...
try {
ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
if (iSub != null) {
iSub.setDefaultDataSubId(subId);
}
} catch (RemoteException ex) {
// ignore it
}
}
isub实现端在frameworks/opt/telephony/src/java/com/android/internal/telephony/SubscriptionController.java
public void setDefaultDataSubId(int subId) {
...
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION, subId);
...
}
最终设置了SettingProvider中的一个值,设置拨号和短信默认卡流程类似。
可用sim卡列表
加载sim卡列表的方法是SimSettings中的updateSubscriptions
private void updateSubscriptions() {
mSubInfoList = mSubscriptionManager.getActiveSubscriptionInfoList();
for (int i = 0; i < mNumSlots; ++i) {
Preference pref = mSimCards.findPreference("sim" + i);
if (pref instanceof SimPreference) {
mSimCards.removePreference(pref);
}
}
mAvailableSubInfos.clear();
mSelectableSubInfos.clear();
for (int i = 0; i < mNumSlots; ++i) {
final SubscriptionInfo sir = mSubscriptionManager
.getActiveSubscriptionInfoForSimSlotIndex(i);
SimPreference simPreference = new SimPreference(mContext, sir, i);
simPreference.setOrder(i-mNumSlots);
/// M: for [SIM Radio On/Off]
simPreference
.bindRadioPowerState(sir == null ? SubscriptionManager.INVALID_SUBSCRIPTION_ID
: sir.getSubscriptionId());
mSimCards.addPreference(simPreference);
mAvailableSubInfos.add(sir);
if (sir != null) {
mSelectableSubInfos.add(sir);
}
}
updateAllOptions();
}
每一个sim卡控件是SimPreference,bindRadioPowerState方法是获取当前sim卡是否被用户禁用
private class SimPreference extends RadioPowerPreference{
SimPreference继承自packages/apps/Settings/src/com/mediatek/settings/sim/RadioPowerPreference.java,该类专门处理卡开启或禁止,更新switch开关的UI状态并实现了switch开关事件。
@Override
public boolean onPreferenceTreeClick(final PreferenceScreen preferenceScreen,
final Preference preference) {
...
if (preference instanceof SimPreference) {
Intent newIntent = new Intent(context, SimPreferenceDialog.class);
newIntent.putExtra(EXTRA_SLOT_ID, ((SimPreference)preference).getSlotId());
startActivity(newIntent);
...
}
点击事件中跳转到packages/apps/Settings/src/com/android/settings/sim/SimPreferenceDialog.java,
public void onCreate(Bundle bundle) {
...
createEditDialog(bundle);
...
}
onCreate中就调用createEditDialog
显示用于编辑sim卡信息的对话框,其实就是修改数据库中siminfo表中的相关数据,simInfo详细可见
点击打开链接。
APN设置
百度百科:APN指一种网络接入技术,是通过手机上网时必须配置的一个参数,它决定了手机通过哪种接入方式来访问网络。对于手机用户来说,可以访问的外部网络类型有很多,例如:Internet、WAP网站、集团企业内部网络、行业内部专用网络。而不同的接入点所能访问的范围以及接入的方式是不同的,网络侧如何知道手机激活以后要访问哪个网络从而分配哪个网段的IP呢,这就要靠APN来区分了,即APN决定了用户的手机通过哪种接入方式来访问什么样的网络。
apn设置UI
packages/apps/Settings/src/com/android/settings/ApnSettings.java
private void fillList() {
final TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
final String mccmnc = mSubscriptionInfo == null ? ""
: tm.getSimOperator(mSubscriptionInfo.getSubscriptionId());
Log.d(TAG, "mccmnc = " + mccmnc);
String where = "numeric=\""
+ mccmnc
+ "\" AND NOT (type='ia' AND (apn=\"\" OR apn IS NULL))";
...
String order = mApnExt.getApnSortOrder(Telephony.Carriers.DEFAULT_SORT_ORDER);
Log.d(TAG, "fillList sort: " + order);
Cursor cursor = getContentResolver().query(
Telephony.Carriers.CONTENT_URI,
new String[] { "_id", "name", "apn", "type", "mvno_type", "mvno_match_data",
"sourcetype" }, where, null, order);
if (cursor != null) {
...
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
String name = cursor.getString(NAME_INDEX);
String apn = cursor.getString(APN_INDEX);
String key = cursor.getString(ID_INDEX);
String type = cursor.getString(TYPES_INDEX);
String mvnoType = cursor.getString(MVNO_TYPE_INDEX);
String mvnoMatchData = cursor.getString(MVNO_MATCH_DATA_INDEX);
...
ApnPreference pref = new ApnPreference(getActivity());
pref.setKey(key);
pref.setTitle(name);
pref.setSummary(apn);
pref.setPersistent(false);
pref.setOnPreferenceChangeListener(this);
...
addApnToList(pref, mnoApnList, mvnoApnList, r, mvnoType, mvnoMatchData);
cursor.moveToNext();
}
cursor.close();
...
}
}
apn设置主界面就是一个apn列表,填充列表的方法fillList如上,基本结构很简单,就是查询数据库,然后依据cursor生成apn列表。列表中的每一项是packages/apps/Settings/src/com/android/settings/ApnPreference.java,每一项的点击后会跳转到packages/apps/Settings/src/com/android/settings/ApnEditor.java。整个apn设置的操作逻辑实际上就是apn数据库的增删查改,具体代码就不再分析。
apn数据库
packages/providers/TelephonyProvider/src/com/android/providers/telephony/TelephonyProvider.java
private void createCarriersTable(SQLiteDatabase db, String tableName) {
// Set up the database schema
if (DBG) log("dbh.createCarriersTable start");
String columns = "(_id INTEGER PRIMARY KEY,"
+ "name TEXT DEFAULT '',"
+ "numeric TEXT DEFAULT '',"
+ "mcc TEXT DEFAULT '',"
+ "mnc TEXT DEFAULT '',"
+ "apn TEXT DEFAULT '',"
+ "user TEXT DEFAULT '',"
+ "server TEXT DEFAULT '',"
+ "password TEXT DEFAULT '',"
+ "proxy TEXT DEFAULT '',"
+ "port TEXT DEFAULT '',"
+ "mmsproxy TEXT DEFAULT '',"
+ "mmsport TEXT DEFAULT '',"
+ "mmsc TEXT DEFAULT '',"
+ "authtype INTEGER DEFAULT -1,"
+ "type TEXT DEFAULT '',"
+ "current INTEGER DEFAULT 0,"
+ "sourcetype INTEGER DEFAULT 0,"
+ "csdnum TEXT DEFAULT '',"
+ "protocol TEXT DEFAULT IP,"
+ "roaming_protocol TEXT DEFAULT IP,";
...
db.execSQL("CREATE TABLE " + tableName + columns);
db.execSQL("DROP TABLE IF EXISTS " + CARRIERS_DM_TABLE);
db.execSQL("CREATE TABLE " + CARRIERS_DM_TABLE + columns);
if (DBG) log("dbh.createCarriersTable:-");
}
建表方法如上,其中表名是carriers。
private void initDatabase(SQLiteDatabase db) {
if (VDBG) log("dbh.initDatabase:+ db=" + db);
// Read internal APNS data
Resources r = mContext.getResources();
XmlResourceParser parser = r.getXml(com.android.internal.R.xml.apns);
...
}
初始化的方法是见上,就是读取xml填充数据库。Android默认的有frameworks/base/core/res/res/xml/apns.xml,不过这个是个空的文件。其它还能读取的xml有:
private static final String PARTNER_APNS_PATH = "etc/apns-conf.xml";
private static final String OEM_APNS_PATH = "telephony/apns-conf.xml";
private static final String OTA_UPDATED_APNS_PATH = "misc/apns-conf.xml";
private static final String OLD_APNS_PATH = "etc/old-apns-conf.xml";
例如/device/mediatek/common/apns-conf.xml
<apn carrier="CTNET"
mcc="454"
mnc="04"
apn="ctnet"
authtype="3"
user="ctnet@mycdma.cn"
password="vnet.mobi"
type="default,dun"
mvno_type="spn"
mvno_match_data="中国电信"
protocol="IPV4V6"
roaming_protocol="IPV4V6"
/>
xml中的每一项都是一个apn配置,对应数据库中的每一个row。