说明
文章还没写完,最后一步IRradio.iccIOForApp方法的具体调用暂时未查找到,如有大佬能补充下,感激不尽
参考文献
- subid和slotid_firedancer0089的专栏-CSDN博客
- Android:关于ContentProvider的知识都在这里了!_专注分享 Android开发 干货-CSDN博客_contentprovider
- ADN加载流程_Android开发 - UCloud云社区
- Android:全面&详细解析android 10 RSSI信号格显示与刷新其二,RIL篇,重点类与文件:RIL.java,ril_service.cpp,radioResponse.java_GentelmanTsao的博客-CSDN博客
前言
最近收到个bug,名字过长,导出SIM失败,了解了下SIM卡存储以及Android设备联系人 导入到SIM流程
SIM存储限制
(SIM标准中文应该是只有4个)
- SIM卡记录名字的最大长度是14,中文可以输入6个,(同样的中文可以输入11个,如 天天天天天天天天天天天,但是只要有一个不一样的中文就只能有6个)
- 号码长度过长
手机号码导入SIM卡流程记录(MTK)
1. 应用层调用
Mtk SimContacts 应用代码
SimContactDaoImpl.java
class SimContactDaoImpl extends SimContactDao{
...
public static final Uri ICC_CONTENT_URI = Uri.parse("content://icc/adn");
public static String NUMBER = "number";
public static String EMAILS = "emails";
public static String ANRS = "anrs";
private static final String STR_TAG = "tag";
@Override
public Uri createNewSimContact(SimContact simContact, int subscriptionId) {
// 代码1
Uri uri = ICC_CONTENT_URI.buildUpon()
.appendPath("subId")
.appendPath(String.valueOf(subscriptionId))
.build();
ContentValues values = new ContentValues();
values.put(STR_TAG, TextUtils.isEmpty(simContact.getName()) ? "" : simContact.getName());
if (!TextUtils.isEmpty(simContact.getPhone())) {
values.put(NUMBER, PhoneNumberUtils.stripSeparators(simContact.getPhone()));
}
values.put(EMAILS, simContact.getEmailsString());
values.put(ANRS, simContact.getAnrsString().
replaceAll("[^0123456789PWN\\,\\;\\*\\#\\+\\:]", ""));
return mResolver.insert(uri,values);//调用
}
...
}
- 从以上代码可以看出,对SIM卡的操作是发出一个Uri,通过
ContentProvider进行进程通信,修改sim信息,其传入了 STR_TAG(名称),NUMBER、EMAILS、ANRS等字段,且 uri中传入了subId。
注: subId 是卡在 数据库对应的主键值,可以通过subId查询对应卡槽。
IccProvider
ICC_CONTENT_URI对应的ContentProvider为IccProvider(path:/frameworks/opt/telephony/src/java/com/android/internal/telephony/IccProvider.java)
IccProvider#insert
public Uri insert(Uri url, ContentValues initialValues) {
Uri resultUri;
int efType;
String pin2 = null;
int subId;
int match = URL_MATCHER.match(url);
switch (match) {
...
case ADN_SUB:
efType = IccConstants.EF_ADN;
subId = getRequestSubId(url);
break;
...
}
String tag = initialValues.getAsString("tag");
String number = initialValues.getAsString("number");
// TODO(): Read email instead of sending null.
// 代码1 调用addIccRecordToEf 将数据添加到sim卡
boolean success = addIccRecordToEf(efType, tag, number, null, pin2, subId);
if (!success) {
return null;
}
StringBuilder buf = new StringBuilder("content://icc/");
switch (match) {
...
case ADN_SUB:
buf.append("adn/subId/");
break;
...
}
// TODO: we need to find out the rowId for the newly added record
buf.append(0);
resultUri = Uri.parse(buf.toString());
getContext().getContentResolver().notifyChange(url, null);
/*
// notify interested parties that an insertion happened
getContext().getContentResolver().notifyInsert(
resultUri, rowID, null);
*/
return resultUri;
}
- 代码1处调用
addIccRecordToEf方法将数据添加到sim卡
IccProvider#addIccRecordToEf
private boolean
addIccRecordToEf(int efType, String name, String number, String[] emails,
String pin2, int subId) {
...
boolean success = false;
...
try {
//代码1
IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface(
ServiceManager.getService("simphonebook"));
if (iccIpb != null) {
success = iccIpb.updateAdnRecordsInEfBySearchForSubscriber(subId, efType,
"", "", name, number, pin2);//
}
} catch (RemoteException ex) {
// ignore it
} catch (SecurityException ex) {
if (DBG) log(ex.toString());
}
if (DBG) log("addIccRecordToEf: " + success);
return success;
}
- 从上面代码中可以看出
addIccRecordToEf方法通过binder调用simphonebook服务的updateAdnRecordsInEfBySearchForSubscriber方法来添加sim信息。simphonebook服务的实现类是UiccPhoneBookController.java
simphonebook 服务
UiccPhoneBookController.java代码如下
public class UiccPhoneBookController extends IIccPhoneBook.Stub{
...
@Override
public boolean
updateAdnRecordsInEfBySearch (int efid, String oldTag, String oldPhoneNumber,
String newTag, String newPhoneNumber, String pin2) throws android.os.RemoteException {
return updateAdnRecordsInEfBySearchForSubscriber(getDefaultSubscription(), efid, oldTag,
oldPhoneNumber, newTag, newPhoneNumber, pin2);
}
@Override
public boolean
updateAdnRecordsInEfBySearchForSubscriber(int subId, int efid, String oldTag,
String oldPhoneNumber, String newTag, String newPhoneNumber,
String pin2) throws android.os.RemoteException {
IccPhoneBookInterfaceManager iccPbkIntMgr =
getIccPhoneBookInterfaceManager(subId);
if (iccPbkIntMgr != null) {
return iccPbkIntMgr.updateAdnRecordsInEfBySearch(efid, oldTag,
oldPhoneNumber, newTag, newPhoneNumber, pin2);
} else {
Rlog.e(TAG,"updateAdnRecordsInEfBySearch iccPbkIntMgr is" +
" null for Subscription:"+subId);
return false;
}
}
/**
* get phone book interface manager object based on subscription.
**/
private IccPhoneBookInterfaceManager
getIccPhoneBookInterfaceManager(int subId) {
// 代码1
int phoneId = SubscriptionController.getInstance().getPhoneId(subId);
try {
//代码2
return mPhone[phoneId].getIccPhoneBookInterfaceManager();
} catch (NullPointerException e) {
Rlog.e(TAG, "Exception is :"+e.toString()+" For subscription :"+subId );
e.printStackTrace(); //To print stack trace
return null;
} catch (ArrayIndexOutOfBoundsException e) {
Rlog.e(TAG, "Exception is :"+e.toString()+" For subscription :"+subId );
e.printStackTrace();
return null;
}
}
}
- 代码1处通过subId获取对应 phoneId(此处代码就不分析了)
- 代码2处获取了phoneId后,根据phoneId 获取
Phone对象(mPhone是一个Phone数组),调用getIccPhoneBookInterfaceManager方法获得IccPhoneBookInterfaceManager对象。 - 代码3 调用
IccPhoneBookInterfaceManager#updateAdnRecordsInEfBySearch方法更新sim卡存储联系人信息。
/frameworks/opt/telephony/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java
IccPhoneBookInterfaceManager.java#updateAdnRecordsInEfBySearch代码如下:
public boolean
updateAdnRecordsInEfBySearch (int efid,
String oldTag, String oldPhoneNumber,
String newTag, String newPhoneNumber, String pin2) {
//代码1 检查权限
if (mPhone.getContext().checkCallingOrSelfPermission(
android.Manifest.permission.WRITE_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException(
"Requires android.permission.WRITE_CONTACTS permission");
}
...
efid = updateEfForIccType(efid);//不太清楚作用
synchronized(mLock) {
checkThread();
mSuccess = false;
AtomicBoolean status = new AtomicBoolean(false);
Message response = mBaseHandler.obtainMessage(EVENT_UPDATE_DONE, status);
AdnRecord oldAdn = new AdnRecord(oldTag, oldPhoneNumber);
AdnRecord newAdn = new AdnRecord(newTag, newPhoneNumber);
if (mAdnCache != null) {
//代码2
mAdnCache.updateAdnBySearch(efid, oldAdn, newAdn, pin2, response);
//代码3
waitForResult(status);
} else {
loge("Failure while trying to update by search due to uninitialised adncache");
}
}
return mSuccess;
}
private int updateEfForIccType(int efid) {
// Check if we are trying to read ADN records
if (efid == IccConstants.EF_ADN) {
if (mPhone.getCurrentUiccAppType() == AppType.APPTYPE_USIM) {
return IccConstants.EF_PBR;
}
}
return efid;
}
protected void waitForResult(AtomicBoolean status) {
while (!status.get()) {
try {
mLock.wait();
} catch (InterruptedException e) {
logd("interrupted while trying to update by search");
}
}
}
- 代码2 处调用
AdnRecordCache.java#updateAdnBySearch方法,写入值 - 代码3 处调用
waitForResult()方法阻塞线程,等待status被置为true.
private IccFileHandler mFh;//文件操作Handler
/**
* Replace oldAdn with newAdn in ADN-like record in EF
*
* The ADN-like records must be read through requestLoadAllAdnLike() before
*
* @param efid must be one of EF_ADN, EF_FDN, and EF_SDN
* @param oldAdn is the adn to be replaced
* If oldAdn.isEmpty() is ture, it insert the newAdn
* @param newAdn is the adn to be stored
* If newAdn.isEmpty() is true, it delete the oldAdn
* @param pin2 is required to update EF_FDN, otherwise must be null
* @param response message to be posted when done
* response.exception hold the exception in error
*/
public void updateAdnBySearch(int efid, AdnRecord oldAdn, AdnRecord newAdn,
String pin2, Message response) {
int extensionEF;
extensionEF = extensionEfForEf(efid);
if (extensionEF < 0) {
sendErrorResponse(response, "EF is not known ADN-like EF:0x" +
Integer.toHexString(efid).toUpperCase());
return;
}
ArrayList<AdnRecord> oldAdnList;
if (efid == EF_PBR) {
oldAdnList = mUsimPhoneBookManager.loadEfFilesFromUsim();
} else {
oldAdnList = getRecordsIfLoaded(efid);
}
if (oldAdnList == null) {
sendErrorResponse(response, "Adn list not exist for EF:0x" +
Integer.toHexString(efid).toUpperCase());
return;
}
int index = -1;
int count = 1;
//代码1 遍历查询要被替代的联系人信息位置(增加联系人时,传入的是"")
for (Iterator<AdnRecord> it = oldAdnList.iterator(); it.hasNext(); ) {
if (oldAdn.isEqual(it.next())) {
index = count;
break;
}
count++;
}
if (index == -1) {
sendErrorResponse(response, "Adn record don't exist for " + oldAdn);
return;
}
if (efid == EF_PBR) {
AdnRecord foundAdn = oldAdnList.get(index-1);
efid = foundAdn.mEfid;
extensionEF = foundAdn.mExtRecord;
index = foundAdn.mRecordNumber;
newAdn.mEfid = efid;
newAdn.mExtRecord = extensionEF;
newAdn.mRecordNumber = index;
}
Message pendingResponse = mUserWriteResponse.get(efid);
if (pendingResponse != null) {
sendErrorResponse(response, "Have pending update for EF:0x" +
Integer.toHexString(efid).toUpperCase());
return;
}
mUserWriteResponse.put(efid, response);
//代码2
new AdnRecordLoader(mFh).updateEF(newAdn, efid, extensionEF,
index, pin2,
obtainMessage(EVENT_UPDATE_ADN_DONE, efid, index, newAdn));
}
public ArrayList<AdnRecord> getRecordsIfLoaded(int efid) {
return mAdnLikeFiles.get(efid);
}
- 代码1 代码1 遍历查询要被替代的联系人信息位置,由
IccProvider#addIccRecordToEf处代码可以看出传入的number和phone都是"". - 代码2 处调用
AdnRecordLoader.java#updateEF方法更新联系人信息
private IccFileHandler mFh;
AdnRecordLoader(IccFileHandler fh) {
// The telephony unit-test cases may create AdnRecords
// in secondary threads
super(Looper.getMainLooper());
mFh = fh;
}
/**
* Write adn to a EF SIM record
* It will get the record size of EF record and compose hex adn array
* then write the hex array to EF record
*
* @param adn is set with alphaTag and phone number
* @param ef EF fileid
* @param extensionEF extension EF fileid
* @param recordNumber 1-based record index
* @param pin2 for CHV2 operations, must be null if pin2 is not needed
* @param response will be sent to its handler when completed
*/
public void
updateEF(AdnRecord adn, int ef, int extensionEF, int recordNumber,
String pin2, Message response) {
mEf = ef;
mExtensionEF = extensionEF;
mRecordNumber = recordNumber;
mUserResponse = response;
mPin2 = pin2;
//代码1
mFh.getEFLinearRecordSize( ef, getEFPath(ef),
obtainMessage(EVENT_EF_LINEAR_RECORD_SIZE_DONE, adn));
}
...
- 代码1处调用
IccFileHandler.java#java方法,传入 根据efid 值获取对着应操作sim卡的文件路径。(getEFPath方法在IccFileHandle.java 中是个abstract方法,对于sim来说,一般其实现为SIMFileHandler.java)IccFileHandler.java#getEFLinearRecordSize
protected final CommandsInterface mCi;
/**
* get record size for a linear fixed EF
*
* @param fileid EF id
* @param path Path of the EF on the card
* @param onLoaded ((AsnyncResult)(onLoaded.obj)).result is the recordSize[]
* int[0] is the record length int[1] is the total length of the EF
* file int[3] is the number of records in the EF file So int[0] *
* int[3] = int[1]
*/
public void getEFLinearRecordSize(int fileid, String path, Message onLoaded) {
String efPath = (path == null) ? getEFPath(fileid) : path;
Message response
= obtainMessage(EVENT_GET_EF_LINEAR_RECORD_SIZE_DONE,
new LoadLinearFixedContext(fileid, efPath, onLoaded));
//代码1
mCi.iccIOForApp(COMMAND_GET_RESPONSE, fileid, efPath,
0, 0, GET_RESPONSE_EF_SIZE_BYTES, null, null, mAid, response);
}
- 代码1处调用
iccIOForApp方法
/frameworks/opt/telephony/src/java/com/android/internal/telephony/CommandsInterface.java
其实现类是/frameworks/opt/telephony/src/java/com/android/internal/telephony/RIL.java
RIL.java代码
static final String[] HIDL_SERVICE_NAME = {"slot1", "slot2", "slot3"};
@Override
public void iccIOForApp(int command, int fileId, String path, int p1, int p2, int p3,
String data, String pin2, String aid, Message result) {
//代码1
IRadio radioProxy = getRadioProxy(result);
if (radioProxy != null) {
RILRequest rr = obtainRequest(RIL_REQUEST_SIM_IO, result,
mRILDefaultWorkSource);
if (RILJ_LOGD) {
if (Build.IS_DEBUGGABLE) {
riljLog(rr.serialString() + "> iccIO: "
+ requestToString(rr.mRequest) + " command = 0x"
+ Integer.toHexString(command) + " fileId = 0x"
+ Integer.toHexString(fileId) + " path = " + path + " p1 = "
+ p1 + " p2 = " + p2 + " p3 = " + " data = " + data
+ " aid = " + aid);
} else {
riljLog(rr.serialString() + "> iccIO: " + requestToString(rr.mRequest));
}
}
IccIo iccIo = new IccIo();
iccIo.command = command;
iccIo.fileId = fileId;
iccIo.path = convertNullToEmptyString(path);
iccIo.p1 = p1;
iccIo.p2 = p2;
iccIo.p3 = p3;
iccIo.data = convertNullToEmptyString(data);
iccIo.pin2 = convertNullToEmptyString(pin2);
iccIo.aid = convertNullToEmptyString(aid);
try {
//代码4
radioProxy.iccIOForApp(rr.mSerial, iccIo);
} catch (RemoteException | RuntimeException e) {
handleRadioProxyExceptionForRR(rr, "iccIOForApp", e);
}
}
}
/** Returns a {@link IRadio} instance or null if the service is not available. */
@VisibleForTesting
public IRadio getRadioProxy(Message result) {
//代码2 判断此时是否有移动网络
if (!mIsMobileNetworkSupported) {
if (RILJ_LOGV) riljLog("getRadioProxy: Not calling getService(): wifi-only");
if (result != null) {
AsyncResult.forMessage(result, null,
CommandException.fromRilErrno(RADIO_NOT_AVAILABLE));
result.sendToTarget();
}
return null;
}
if (mRadioProxy != null) {
return mRadioProxy;
}
try {
//代码3 根据phoneId获取对应卡槽,默认为卡槽1
mRadioProxy = IRadio.getService(HIDL_SERVICE_NAME[mPhoneId == null ? 0 : mPhoneId],
true);
if (mRadioProxy != null) {
mRadioProxy.linkToDeath(mRadioProxyDeathRecipient,
mRadioProxyCookie.incrementAndGet());
mRadioProxy.setResponseFunctions(mRadioResponse, mRadioIndication);
} else {
riljLoge("getRadioProxy: mRadioProxy == null");
}
} catch (RemoteException | RuntimeException e) {
mRadioProxy = null;
riljLoge("RadioProxy getService/setResponseFunctions: " + e);
}
if (mRadioProxy == null) {
// getService() is a blocking call, so this should never happen
riljLoge("getRadioProxy: mRadioProxy == null");
if (result != null) {
AsyncResult.forMessage(result, null,
CommandException.fromRilErrno(RADIO_NOT_AVAILABLE));
result.sendToTarget();
}
}
return mRadioProxy;
}
- 代码1 获取Radio服务
- 代码2 判断是否支持移动网络
- 代码3 根据phoneId获取卡槽,再根据卡槽获取对应卡槽的服务类,
IRadio.getService获取的实现类是/hardware/ril/libril/ril_service.cpp?
...