在framework层下的SmsManager类中,封装好了一个copyMessageToIcc方法,只要正确地调用它便可以将短信存储到SIM卡中。
SmsManager smsManager = SmsManager.getDefault(); //用来获取一个SmsManager对象
现在我们来看一下copyMessageToIcc(byte[] smsc, byte[] pdu, int status)这个方法的三个参数:
1) byte[] smsc : 短信服务中心的地址,个人认为在复制到SIM卡过程中可以为空。
2) byte[] pdu : 中文翻译是协议数据单元,这个参数最为重要,一会我们会做详细地解释说明。
3) int status : 短信存储在Icc卡上的状态,有4种状态,1是已读,3是未读,5是已发送,7是未发送。
其实要想将将短信正确地存储到SIM卡上,pdu这个参数尤为重要,下面我们就来分析一下!
首先先看一下复制短信到SIM卡时log显示的正确pdu:

00 00 0d91683155724572f9 00 0b 11117091914323 0a4e0b73ed4e864e48ff1f



原本上面的pdu是完整连续,为了方便解释说明,特意加了空格区分开来。
00 SC Address 短信服务中心地址,通常我们发送短信时会发送一个pdu到短信服务中心,然后短信服务中心会对pdu进行一些处理再发送到目的手机,这其中就包括增加了这个SC Address和后面会介绍的时间戳。
00 PDUType pdu的第一个八位位组,即一个八位的二进制数转变成十六进制而来,每一位代表什么意思呢?由高到低依次代表RP(应答路径)、UDHI(用户数据头标识)、SRR(状态请求报告)、VPF(有效期格式,2位)、RD(拒绝副本)、MTI(信息指示类型,2位),在这里我们其实只要全部将其设置为0就好,于是便显示为00。
0d91683155724572f9 这一段代表了目的手机的号码,0d代表后面的地址长度(二进制下为13),那么这十三个数是怎么算的呢?其实手机号为13552754279,而91代表短消息中心地址的类型(81&h表示国内,91&h表示国际)。所以0d表示的就是后面683155724572f9的长度(f9中的f是用来凑偶数位的)。
00 PID 协议标识,通常设为00就好。
0b DCS 数据编码方案,含有中文字符的话一般默认都为0b,即00001011转化而来,具体每一位的含义暂时不做详细解释。
11117091914323 时间戳,代表的时间为11年11月07日19时19分34秒,后面的23表示时区(什么这么表示尚在研究中,可以写死,感觉意义不大)。
0a4e0b73ed4e864e48ff1f 短信的具体内容,其中0a表示信息的长度。

好了,关于pdu我们分析完了,现在要做的就是如何获取这个pdu并传进copyMessageToIcc方法中。观察源码会发现在framework/base/telephony/java/com/android/internal/telephony/gsm下的SmsMessage类中有一个getSubmitPdu方法,返回一个SubmitPdu对象,该对象有encodedScAddress和encodedMessage两个byte[]数组类型的属性,而且在多个地方被用到,和copyMessageToIcc中的参数十分相似,会不会就是我们要找的呢?经调用后发现,得到的pdu并不正确,但是没有关系,我们可以将其涉及到的方法重写,拼出我们想要的pdu!我改写的代码如下:

private SubmitPdu getSubmitPdu(String scAddress, String destinationAddress, String message, 

 boolean statusReportRequested, byte[] header, int encoding, long date) { 


 // Perform null parameter checks. 

 if (message == null || destinationAddress == null) { 

 return null; 

 } 


 SubmitPdu ret = new SubmitPdu(); 

 // MTI = SMS-SUBMIT, UDHI = header != null 

 //在这个方法中获得了数据编码方案(DCS,就是例子中的0b)前的所有字节。 

 ByteArrayOutputStream bo = getSubmitPduHead(scAddress, destinationAddress, 

 statusReportRequested, ret); 

 // User Data (and length) 

 byte[] userData; 

 if (encoding == ENCODING_UNKNOWN) { 

 // First, try encoding it with the GSM alphabet 

 encoding = ENCODING_7BIT; 

 } 

 try { 

 if (encoding == ENCODING_7BIT) { 

 userData = GsmAlphabet.stringToGsm7BitPackedWithHeader(message, header); 

 } else { // assume UCS-2 

 try { 

 userData = encodeUCS2(message, header); 

 } catch (UnsupportedEncodingException uex) { 

 Log.e("GSM", "Implausible UnsupportedEncodingException ", uex); 

 return null; 

 } 

 } 

 } catch (EncodeException ex) { 

 // Encoding to the 7-bit alphabet failed. Let's see if we can 

 // send it as a UCS-2 encoded message 

 try { 

 userData = encodeUCS2(message, header); 

 encoding = ENCODING_16BIT; 

 } catch (UnsupportedEncodingException uex) { 

 Log.e("GSM", "Implausible UnsupportedEncodingException ", uex); 

 return null; 

 } 

 } 


 if (encoding == ENCODING_7BIT) { 

 if ((0xff & userData[0]) > MAX_USER_DATA_SEPTETS) { 

 // Message too long 

 return null; 

 } 

 bo.write(0x00); 

 } else { // assume UCS-2 

 if ((0xff & userData[0]) > MAX_USER_DATA_BYTES) { 

 // Message too long 

 return null; 

 } 

 // TP-Data-Coding-Scheme 

 // Class 3, UCS-2 encoding, uncompressed 

 bo.write(0x0b); //DCS,数据编码方案 

 } 

 //获得所需的时间戳,否则会显示乱码 

 byte[] timeStamp = getTimeStamp(date); 

 for (int i=0; i<timeStamp.length; i++){ 

 bo.write(timeStamp[i]); 

 } 

 bo.write(0x23); 

 //写入短信信息,含有中文时会自动捕获异常并改变其编码格式 

 bo.write(userData, 0, userData.length); 

 ret.encodedMessage = bo.toByteArray(); 

 return ret; 

 } 


 private static ByteArrayOutputStream getSubmitPduHead( 

 String scAddress, String destinationAddress, 

 boolean statusReportRequested, SubmitPdu ret) { 

 ByteArrayOutputStream bo = new ByteArrayOutputStream( 

 MAX_USER_DATA_BYTES + 40); 

 // SMSC address with length octet, or 0 

 if (scAddress == null) { 

 ret.encodedScAddress = null; 

 } else { 

 ret.encodedScAddress = PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength( 

 scAddress); 

 } 

 // TP-Message-Type-Indicator (and friends) 

 if (statusReportRequested) { 

 // Set TP-Status-Report-Request bit. 

 mtiByte |= 0x20; 

 if (Config.LOGD) Log.d("GSM", "SMS status report requested"); 

 } 


 //bo.write(0); 

 //bo.write(mtiByte); 


 if(null != ret.encodedScAddress) { 

 for (int i=0,len=ret.encodedScAddress.length;i<len;i++){ 

 bo.write(ret.encodedScAddress[i]); 

 } 

 } 


 // space for TP-Message-Reference 

 bo.write(0); 


 byte[] daBytes; 


 daBytes = PhoneNumberUtils.networkPortionToCalledPartyBCD(destinationAddress); 


 // destination address length in BCD digits, ignoring TON byte and pad 

 // TODO Should be better. 

 bo.write((daBytes.length - 1) * 2 

 - ((daBytes[daBytes.length - 1] & 0xf0) == 0xf0 ? 1 : 0)); 


 // destination address 

 bo.write(daBytes, 0, daBytes.length); 


 // TP-Protocol-Identifier 

 bo.write(0); 

 return bo; 

 } 


 private byte[] encodeUCS2(String message, byte[] header) 

 throws UnsupportedEncodingException { 

 byte[] userData, textPart; 

 textPart = message.getBytes("utf-16be"); 


 if (header != null) { 

 // Need 1 byte for UDHL 

 userData = new byte[header.length + textPart.length + 1]; 


 userData[0] = (byte) header.length; 

 System.arraycopy(header, 0, userData, 1, header.length); 

 System.arraycopy(textPart, 0, userData, header.length + 1, textPart.length); 

 } else { 

 userData = textPart; 

 } 

 byte[] ret = new byte[userData.length + 1]; 

 ret[0] = (byte) (userData.length & 0xff); 

 System.arraycopy(userData, 0, ret, 1, userData.length); 

 return ret; 

 } 


 private byte[] getTimeStamp(long time){ 

 byte[] timeStamp=null; 

 StringBuilder builder = new StringBuilder(); 

 SimpleDateFormat sdf=new SimpleDateFormat("yy-MM-dd-hh-mm-ss"); 

 String[] array = sdf.format(time).split("-"); 

 for(int i=0;i<array.length;i++){ 

 int p = Integer.parseInt(array[i]); 

 int q = p/10 + 10*(p%10); 

 builder = q<10 ? builder.append("0"+q) : builder.append(q); 

 } 

 timeStamp = IccUtils.hexStringToBytes(builder.toString()); 


 return timeStamp; 

 }



调用上述方法后,就会得到我们想要的pdu了,但是会发现还是无法成功地将信息写入SIM卡上,究竟是为什么呢!分析log后才知道,原来pdu还少了一些东西!在将pdu传给底层的modem时,还要在其前面添加“00”,表示点对点发送,这就要更改hardware/ril/reference-ril/reference-sc8800s.c文件里的requestWriteSmsToSim了,具体代码如下:

RIL_SMS_WriteArgs *p_args; 

 char *cmd; 

 char *pdu; 

 int length; 

 int err; 

 ATResponse *p_response = NULL; 


 length = strlen(p_args->pdu)/2; 

 asprintf(&cmd, "AT+CMGW=%d,%d", length, p_args->status); 

 asprintf(&pdu, "00%s", p_args->pdu); 


 err = at_send_command_sms(cmd, pdu, "+CMGW:", &p_response); 

 free(cmd); // add by lhhuang@ingenic.cn 

 free(pdu);



编译so后push到手机中,重启一下就大功告成了!