<h2 align = "center">carrier_config相關(guān)研究</h2>
概述: 在PhoneGlobals創(chuàng)建時(shí),會(huì)同時(shí)創(chuàng)建carrier_config 服務(wù),實(shí)現(xiàn)carrier_config 的類是CarrierConfigLoader。CarrierConfigLoader的主要目的是,綁定特定運(yùn)營商的App,從而獲取特定運(yùn)營的配置信息。
1. 相關(guān)類和aidl
- frameworks/base/telephony/java/com/android/internal/telephony/ICarrierConfigLoader.aidl
- packages/services/Telephony/src/com/android/phone/CarrierConfigLoader.java
- frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java
2. 初始化
private CarrierConfigLoader(Context context) {
mContext = context;
// 監(jiān)聽開機(jī)
IntentFilter bootFilter = new IntentFilter();
bootFilter.addAction(Intent.ACTION_BOOT_COMPLETED);
context.registerReceiver(mBootReceiver, bootFilter);
// Register for package updates. Update app or uninstall app update will have all 3 intents,
// in the order or removed, added, replaced, all with extra_replace set to true.
// 監(jiān)聽安裝或者卸載
IntentFilter pkgFilter = new IntentFilter();
pkgFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
pkgFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
pkgFilter.addDataScheme("package");
context.registerReceiverAsUser(mPackageReceiver, UserHandle.ALL, pkgFilter, null, null);
int numPhones = TelephonyManager.from(context).getPhoneCount();
// 運(yùn)營商默認(rèn)配置
mConfigFromDefaultApp = new PersistableBundle[numPhones];
// 運(yùn)營商App設(shè)置
mConfigFromCarrierApp = new PersistableBundle[numPhones];
mServiceConnection = new CarrierServiceConnection[numPhones];
// Make this service available through ServiceManager.
// 讓外部可以通過ServiceManager訪問carrier_config
ServiceManager.addService(Context.CARRIER_CONFIG_SERVICE, this);
log("CarrierConfigLoader has started");
mHandler.sendEmptyMessage(EVENT_CHECK_SYSTEM_UPDATE);
}
在構(gòu)造函數(shù)初始化完成后,發(fā)送了EVENT_CHECK_SYSTEM_UPDATE事件,Handler對(duì)于該事件的處理如下:
case EVENT_CHECK_SYSTEM_UPDATE:
SharedPreferences sharedPrefs =
PreferenceManager.getDefaultSharedPreferences(mContext);
final String lastFingerprint = sharedPrefs.getString(KEY_FINGERPRINT, null);
// 如果系統(tǒng)的構(gòu)建信息發(fā)生了變化,即系統(tǒng)更新了,那么就需要清除緩存。
if (!Build.FINGERPRINT.equals(lastFingerprint)) {
log("Build fingerprint changed. old: "
+ lastFingerprint + " new: " + Build.FINGERPRINT);
// 清除緩存
clearCachedConfigForPackage(null);
// 更新系統(tǒng)構(gòu)建信息
sharedPrefs.edit().putString(KEY_FINGERPRINT, Build.FINGERPRINT).apply();
}
break;
清除緩存的代碼如下,運(yùn)營商的信息都是通過xml文件保存下來的,packageName為null時(shí)會(huì)清除所有的xml文件。
private boolean clearCachedConfigForPackage(final String packageName) {
File dir = mContext.getFilesDir();
File[] packageFiles = dir.listFiles(new FilenameFilter() {
public boolean accept(File dir, String filename) {
if (packageName != null) {
return filename.startsWith("carrierconfig-" + packageName + "-");
} else {
// 因?yàn)閜ackageName==null,所以會(huì)清除掉所有以carrierconfig-開頭的文件。
return filename.startsWith("carrierconfig-");
}
}
});
if (packageFiles == null || packageFiles.length < 1) return false;
for (File f : packageFiles) {
log("deleting " + f.getName());
f.delete();
}
return true;
}
3. 開機(jī)更新運(yùn)營商信息
接收到開機(jī)廣播后mHandler接收到EVENT_SYSTEM_UNLOCKED事件,對(duì)該事件的處理如下:
case EVENT_SYSTEM_UNLOCKED:
for (int i = 0; i < TelephonyManager.from(mContext).getPhoneCount(); ++i) {
// 如果是單卡,phoneId為0, 雙卡:phoneId 為0,1
// 更新每個(gè)卡的運(yùn)營商信息
updateConfigForPhoneId(i);
}
break;
更新內(nèi)存中的運(yùn)營商信息,如果特定運(yùn)營商app不存在,那么清空對(duì)應(yīng)的緩存:
private void updateConfigForPhoneId(int phoneId) {
// Clear in-memory cache for carrier app config, so when carrier app gets uninstalled, no
// stale config is left.
if (mConfigFromCarrierApp[phoneId] != null &&
getCarrierPackageForPhoneId(phoneId) == null) {
mConfigFromCarrierApp[phoneId] = null;
}
// 重新獲取運(yùn)營商信息
mHandler.sendMessage(mHandler.obtainMessage(EVENT_FETCH_DEFAULT, phoneId, -1));
}
首先從本地文件中讀取配置信息,如果本地不能存在,那么嘗試綁定android.service.carrier.CarrierService,綁定成功后,從該服務(wù)的接口中讀取信息。
case EVENT_FETCH_DEFAULT:
iccid = getIccIdForPhoneId(phoneId);
// 從文件中讀取默認(rèn)的配置
config = restoreConfigFromXml(DEFAULT_CARRIER_CONFIG_PACKAGE, iccid);
if (config != null) {
log("Loaded config from XML. package=" + DEFAULT_CARRIER_CONFIG_PACKAGE
+ " phoneId=" + phoneId);
// xml文件存在并加載成功,發(fā)送加載默認(rèn)配置成功事件
mConfigFromDefaultApp[phoneId] = config;
Message newMsg = obtainMessage(EVENT_LOADED_FROM_DEFAULT, phoneId, -1);
newMsg.getData().putBoolean("loaded_from_xml", true);
mHandler.sendMessage(newMsg);
} else {
// xml文件不存在,綁定android.service.carrier.CarrierService,該服務(wù)的實(shí)現(xiàn)不具體分析。
if (bindToConfigPackage(DEFAULT_CARRIER_CONFIG_PACKAGE,
phoneId, EVENT_CONNECTED_TO_DEFAULT)) {
sendMessageDelayed(obtainMessage(EVENT_BIND_DEFAULT_TIMEOUT, phoneId, -1),
BIND_TIMEOUT_MILLIS);
} else {
// Send bcast if bind fails
// 告訴監(jiān)聽者,運(yùn)營商信息已經(jīng)發(fā)生了變化,這個(gè)時(shí)候,特定運(yùn)營商信息為空。
broadcastConfigChangedIntent(phoneId);
}
}
break;
上面EVENT_BIND_DEFAULT_TIMEOUT事件比較簡單, 首先unBind服務(wù),再通過broadcastConfigChangedIntent通知監(jiān)聽者。
所以應(yīng)該是在綁定服務(wù)成功后,就獲取運(yùn)營商信息,BIND_TIMEOUT_MILLIS是綁定服務(wù)的超時(shí)時(shí)間。下面看bindToConfigPackage:
/** Binds to the default or carrier config app. */
private boolean bindToConfigPackage(String pkgName, int phoneId, int eventId) {
log("Binding to " + pkgName + " for phone " + phoneId);
Intent carrierService = new Intent(CarrierService.CARRIER_SERVICE_INTERFACE);
carrierService.setPackage(pkgName);
// 很顯然綁定成功后的邏輯在CarrierServiceConnection類里面。
mServiceConnection[phoneId] = new CarrierServiceConnection(phoneId, eventId);
try {
return mContext.bindService(carrierService, mServiceConnection[phoneId],
Context.BIND_AUTO_CREATE);
} catch (SecurityException ex) {
return false;
}
}
綁定服務(wù)成功后給mHandler發(fā)送了EVENT_CONNECTED_TO_DEFAULT事件:
case EVENT_CONNECTED_TO_DEFAULT:
// 移除超時(shí)事件。
removeMessages(EVENT_BIND_DEFAULT_TIMEOUT);
carrierId = getCarrierIdForPhoneId(phoneId);
conn = (CarrierServiceConnection) msg.obj;
// If new service connection has been created, unbind.
if (mServiceConnection[phoneId] != conn || conn.service == null) {
mContext.unbindService(conn);
break;
}
try {
ICarrierService carrierService = ICarrierService.Stub
.asInterface(conn.service);
// 從運(yùn)營商服務(wù)中獲取到配置。
config = carrierService.getCarrierConfig(carrierId);
iccid = getIccIdForPhoneId(phoneId);
// 保存配置到xml文件
saveConfigToXml(DEFAULT_CARRIER_CONFIG_PACKAGE, iccid, config);
// 更新內(nèi)存中的配置
mConfigFromDefaultApp[phoneId] = config;
sendMessage(obtainMessage(EVENT_LOADED_FROM_DEFAULT, phoneId, -1));
} catch (Exception ex) {
// The bound app could throw exceptions that binder will pass to us.
loge("Failed to get carrier config: " + ex.toString());
} finally {
mContext.unbindService(mServiceConnection[phoneId]);
}
break;
無論是從本地xml文件中獲取到運(yùn)營商配置,還是從服務(wù)中獲取到運(yùn)營商配置,最終都會(huì)回到mHandler的EVENT_LOADED_FROM_DEFAULT事件:
case EVENT_LOADED_FROM_DEFAULT:
// If we attempted to bind to the app, but the service connection is null, then
// config was cleared while we were waiting and we should not continue.
// 我們是從服務(wù)中獲取到的配置,但是服務(wù)卻斷開了,當(dāng)然不能繼續(xù)了。
if (!msg.getData().getBoolean("loaded_from_xml", false)
&& mServiceConnection[phoneId] == null) {
break;
}
carrierPackageName = getCarrierPackageForPhoneId(phoneId);
// 特定的運(yùn)營商app存在,那么需要從特定的運(yùn)營商中再次獲取配置。
if (carrierPackageName != null) {
log("Found carrier config app: " + carrierPackageName);
sendMessage(obtainMessage(EVENT_FETCH_CARRIER, phoneId));
} else {
broadcastConfigChangedIntent(phoneId);
}
break;
獲取特定運(yùn)營商配置信息和獲取默認(rèn)配置信息的邏輯是一樣的,代碼就不具體列舉了:
- 檢查本地緩存文件,緩存存在>>通知變更
- 緩存不存在,那么從特定運(yùn)營商的服務(wù)中獲取對(duì)應(yīng)的配置信息,然后緩存到本地,再通知變更。
4. 安裝卸載更新運(yùn)營商信息
理解了上面的更新一個(gè)SIM卡的運(yùn)營商信息的流程,下面就比較簡單了。安裝卸載會(huì)給mHandler發(fā)送EVENT_PACKAGE_CHANGED事件:
case EVENT_PACKAGE_CHANGED:
carrierPackageName = (String) msg.obj;
// Only update if there are cached config removed to avoid updating config
// for unrelated packages.
// 清除掉被卸載的運(yùn)營商app對(duì)應(yīng)的保存在本地的xml文件。
if (clearCachedConfigForPackage(carrierPackageName)) {
int numPhones = TelephonyManager.from(mContext).getPhoneCount();
for (int i = 0; i < numPhones; ++i) {
updateConfigForPhoneId(i);
}
}
break;
上面的邏輯和開機(jī)更新運(yùn)營商信息差不多,再結(jié)合前面的分析,我們知道大體的流程如下:
- 默認(rèn)運(yùn)營商配置更新
1.1 檢查本地緩存文件, 緩存存在,不需要更新。
- 特定運(yùn)營商配置更新
2.1 檢查本地緩存文件,緩存不存在,需要從服務(wù)重新獲取配置信息。同時(shí)發(fā)現(xiàn)特定運(yùn)營商app不存在,清除特定運(yùn)營商配置緩存。
2.2 檢查特定運(yùn)營商app,不存在,通知更新。
5. 主動(dòng)更新
上面所說的都是carrier_config在接收到外部某個(gè)事件后,進(jìn)行更新的邏輯。
carrier_config同樣支持主動(dòng)更新。它為主動(dòng)更新提供了2個(gè)接口:
- notifyConfigChangedForSubId(int subId) 主要是提供給 telephony services 更新它當(dāng)前的運(yùn)營商配置用的。
- updateConfigForPhoneId(int phoneId, String simState) 根據(jù)提供的sim狀態(tài),決定清除該卡的運(yùn)營商配置還是加載該卡的運(yùn)營商配置。
6. 獲取運(yùn)營商配置的接口
僅僅是從內(nèi)存緩存中讀取配置信息,首先是讀取內(nèi)存預(yù)設(shè)好的配置,如果默認(rèn)運(yùn)營商配置存在,那么默認(rèn)配置會(huì)覆蓋預(yù)設(shè)配置,然后,如果特定運(yùn)營商配置存在,那么特定配置覆蓋默認(rèn)配置。
PersistableBundle getConfigForSubId(int subId) {
try {
mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE, null);
// SKIP checking run-time READ_PHONE_STATE since using PRIVILEGED
} catch (SecurityException e) {
mContext.enforceCallingOrSelfPermission(READ_PHONE_STATE, null);
}
int phoneId = SubscriptionManager.getPhoneId(subId);
// 優(yōu)先級(jí)是 預(yù)設(shè)配置 < 默認(rèn)配置 < 特定配置
PersistableBundle retConfig = CarrierConfigManager.getDefaultConfig();
if (SubscriptionManager.isValidPhoneId(phoneId)) {
PersistableBundle config = mConfigFromDefaultApp[phoneId];
if (config != null)
retConfig.putAll(config);
config = mConfigFromCarrierApp[phoneId];
if (config != null)
retConfig.putAll(config);
}
return retConfig;
}
7. 服務(wù)的調(diào)用
1) 因?yàn)榉?wù)加入到了ServiceManager里面,所以對(duì)于系統(tǒng)應(yīng)用,可以通過ServiceManager.getService的方法獲取到該服務(wù)的接口。
IBinder carrierConfig = ServiceManager.getService(Context.CARRIER_CONFIG_SERVICE);
ICarrierConfigLoader loader = ICarrierConfigLoader.Stub.asInterface(carrierConfig);
try {
PersistableBundle defaultConfig = loader.getConfigForSubId(SubscriptionManager.getDefaultSubId());
} catch (RemoteException e) {
e.printStackTrace();
}
2) 因?yàn)樵摲?wù)也注冊(cè)到系統(tǒng)服務(wù)里面了,所以第三方應(yīng)用可以獲取該服務(wù)接口。
CarrierConfigManager ccm = (CarrierConfigManager) context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
PersistableBundle defaultConfig = ccm.getConfigForSubId(SubscriptionManager.getDefaultSubId());
總結(jié)
最后需要說明的是,carrier_config 服務(wù)自身有一套很完善的更新邏輯,調(diào)用者一般情況下不需要主動(dòng)去觸發(fā)更新。因?yàn)門elephony已經(jīng)管理好了。