Android_SMS
陆弘光
2023-12-01
1 Android_SMS 源代码接受短信流程
2 短息发送流程
3 PDU 编解码详解
1 Android_SMS 源代码接受短信流程
短信来了之后 framework 会发送广播 “android.provider.Telephony.SMS_RECEIVED”
PrivilegedSmsReceiver
此时,PrivilegedSmsReceiver 会接受到该广播,调用父类 SmsReceiver 的 onReceiveWithPrivilege()方法
此方法内获取一个 wake lock 然后启动 SmsReceiverService 服务
SmsReceiverService
启动该服务后,会调用 onStartCommand 方法,该方法以之前传来的 Intent 为 Message 的 Obj 发送一条
Message
在 handleMessage 方法里面通过 Intent 判断后执行相应的操作,如
handleSmsSent,handleSmsReceived,
handleBootCompleted,handleServiceStateChanged
接受到短信时当然执行 handleSmsReceived 方法
该方法内通过 Intents.getMessagesFromIntent(intent)方法从 Intent 里面取出 Message[]
然后通过 insertMessage(this, msgs)方法插入短信 insertMessage 里通过调用 storeMessage 方法
storeMessage 方法执行 values.put(Inbox.BODY, sms.getDisplayMessageBody())方法就可以将
短信以 ContentValues 的形式插入数据库。
insertMessage 方法如果插入成功将会返回插入短信的 Uri,如果此 Uri 不为 Null,说明已经插入数据库,
于是
执行 MessagingNotification.updateNewMessageIndicator(this, true);
该方法则会根据短信的状态,发出提示音或震动,也可以根据设置 notification
自此,一条新信息就成功接受了。
短信的所有提示都是通过 Notification 来提示的,所以当从设置里面把 Notification 的震动关闭,设为静音
的话,
无所短信设置里面怎么设置提示方式都没用。因为短信提示就是一个普通的 Notification
2 短息发送流程
1、上层开发调用的接口函数:SmsManager.getDefault().sendTextMessage()
函数实现:
public void sendTextMessage(
String destinationAddress, String scAddress, String text,
PendingIntent sentIntent, PendingIntent deliveryIntent) {
if (TextUtils.isEmpty(destinationAddress)) {
throw new IllegalArgumentException("Invalid destinationAddress");
}
if (TextUtils.isEmpty(text)) {
throw new IllegalArgumentException("Invalid message body");
}
try {
ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));// 在 Binder 注册服
务,通过 iccISms 调用底层接口
if (iccISms != null) {
iccISms.sendText(destinationAddress, scAddress, text, sentIntent, deliveryIntent);// 调用底层
函数
}
} catch (RemoteException ex) {
// ignore it
}
}
2、Isms 中处理 sendText
到 ISms.java 中查找此函数发现有两个,尴尬,为什么会有两个?
有一个是:iccISms.sendText
另一个是:Proxy:sendText
明白了,一看就知道我们要的是第一个。但是当我们查看第一个函数的时候发现没有实现。。。为什么呢?
这时候就要说到一个机制:Binder,这里就是它在捣鬼。这里给我们提供的接口就是 Binder 客户端的接口,
具体实现是在 Binder 的服务。
3、寻找 Binder 服务中哪里实现的 sendText
sendText 是在 SMSDispatcher.java 文件中,但是他的实现是在 GsmSMSDispatcher.java 或者
CdmaSMSDispatcher.java 文件中。
protected void sendText(String destAddr, String scAddr, String text,
PendingIntent sentIntent, PendingIntent deliveryIntent)
{
SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
scAddr, destAddr, text, (deliveryIntent != null));
sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent);
}
4、sendRawPdu 这个函数又返回到 SMSDispatcher.java 文件中,
protected void sendRawPdu(byte[] smsc, byte[] pdu, PendingIntent sentIntent,
PendingIntent deliveryIntent)
{
if (pdu == null)
{
if (sentIntent != null)
{
try
{
sentIntent.send(RESULT_ERROR_NULL_PDU);
} catch (CanceledException ex) {}
}
return;
}
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("smsc", smsc);
map.put("pdu", pdu);
SmsTracker tracker = new SmsTracker(map, sentIntent,
deliveryIntent);
int ss = mPhone.getServiceState().getState();
if (ss != ServiceState.STATE_IN_SERVICE) {
handleNotInService(ss, tracker);
} else {
String appName = getAppNameByIntent(sentIntent);
if (mCounter.check(appName, SINGLE_PART_SMS)) {
sendSms(tracker);
} else {
sendMessage(obtainMessage(EVENT_POST_ALERT, tracker));
}
}
}
5、sendSms 看到这个函数感觉离真正干活的地方不远了。此函数又跑到 GsmSMSDispatcher.java 或者
CdmaSMSDispatcher.java 文件中了,说明两个网的实现方式还有有一定差异的。
protected void sendSms(SmsTracker tracker) {
HashMap map = tracker.mData;
byte smsc[] = (byte[]) map.get("smsc");
byte pdu[] = (byte[]) map.get("pdu");
Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker);
mCm.sendSMS(IccUtils.bytesToHexString(smsc),
IccUtils.bytesToHexString(pdu), reply);
}
有必要说一下 mCm 这个成员变量,比变量声明为:CommandsInterface,(怎么跑出来这么个东西?)这
个是 Ril 层的接口层,里边提供了 Ril 实现的功能。
看到这里我们就可以跨过 Ril 机制了,直接找我们想要的功能实现。CommandsInterface.java 只是提供接口,
具体实现在 Ril.java 中。
6、sendSMS 此函数在 Ril.java 中,看看怎么实现的。(这是什么东西,没看明白)
public void
sendSMS (String smscPDU, String pdu, Message result) {
RILRequest rr
= RILRequest.obtain(RIL_REQUEST_SEND_SMS, result);
rr.mp.writeInt(2);
rr.mp.writeString(smscPDU);
rr.mp.writeString(pdu);
if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
send(rr);
}
Ril.java 中的实现方式基本都是一个样子,就是 send(rr),这其实是 Ril 的机制,Ril.java 其实是 Rild 的一
个代理,他负责给 Rild 发消息,通知他去做具体的操作。
7、看看谁处理了 RIL_REQUEST_SEND_SMS 这个消息就知道谁在干活了。
onRequest
此函数就是处理消息的函数。对应 case RIL_REQUEST_SEND_SMS 会调用相应的函数
requestSendSMS(data, datalen, t);
8、再往下看就是如何将请求编辑成 AT 命令发送给 cp 层了,这样就是 Ril 的真正意义。没必要再写了。
参考下列标准:
TS 23.040 着重介绍短信发送中对字符集的控制部分
TS 23.038 SMS 编码解码相关
TS 24.011 SMS 同 SMSC 交互
3
PDU 编解码详解
简单介绍
SMS 是由 Etsi 所制定的一个规范(GSM 03.40 和 GSM 03.38)。当使用 7bits 编码的时候 ,它可以发送最多 160 个
字符,8bit 编码(最多 140 个字符)通常无法直接通过手机显示;通常被用来作为数据消息。16bit 信息(最多 70
个字符)被用来显示 Unicode(UCS2)文本信息,可以被大多数的手机所显示。一个以 class 0 开头的 16bit 的文本信
息将在某些手机上作为 Flash SMS 显示(闪烁的 SMS 和警告 SMS)。
有两种方式来发送和接收 SMS 信息:使用文本模式或者使用 PDU(protocol description unit) 模式。文本模式(可能
某些手机不支持)实际上也是一种 PDU 编码的一种表现形式。在显示 SMS 信息,可能使用不同的字符集和不同的
编码方式。
接收
PDU 串不仅仅包含了消息,而且还有很多发送者的元信息,他的 SMS 服务中心,时间标志等等。这些都是以 8 位
字节的 16 进制数,或者半 8 位字节的十进制数。以下的字符书我从 Nokia 6110 收到的信息,当从 www.mtn.co.za 发
送的串是"hellohello"的时候。
07 917238010010F5 040BC87238880900F100009930925161958003C16010
这个八位串包含了三个部分:第一个 8 位表示 SMSC 信息的长度("07"),SMSC 的信息 ("917238010010F5"),和
SMS_DELIVER 部分(GSM 03.40 中指定)。
8 位描述
07 SMSC 信息的长度。(在这个例子里是 7 个八位)
91 SMSC 的地址类型 (91 意味着国际格式的电话号码)
72 38 01 00 10 F5 服务中心号码(半八位的十进制数)电话号码是一个奇数(11),因此加入 F 来保证 8 位。这个服
务中心的号码是"+27381000015"
04 SMS_DELIVER 的第一个 8 位。
0B 地址长度。发送号码的长度(0B hex = 11 dec)
C8 发送号码的地址类型
72 38 88 09 00 F1 发送号码(半八位的十进制数),有一个 F 结尾。
00 TPPID.协议标识
00 TPDCS 编码方式
99 30 92 51 61 95 80 TPSCTS.时间邮戳(半 8 位)
0A TPUDL.用户数据长度,信息的长度。TPDCS 域表明是 7bit 格式的数据。因此长度在这里是一个 10 个 7
bits。如果 TPDCS 被设置成 8bit 或者 Unicode,那么长度就应该是 9 个八位长度。
E8329BFD4697D9EC37 TPUD. 7bit 编码的信息。
所有的 8 位都是 16 进制编码,除了服务中心号码,发送号码和时间邮戳;他们都是十进制的半 8 位编码。在 PDU
串的结尾部分包含了一些 16 进制的 8bits 数据,但他们实际 7bits 数据。
十进制的半 8 位只需要将高位和地位交换就可以得到实际的数值。例如:"72 38 88 09 00 F1" 到 "27 83 88 90 00
1F"。因为电话号码是一个奇数,没有办法组成 8 位编码,所以使用 F 来补齐。在解析时间邮戳的时候("99 03 29
15 16 59 08"),前 6 位代表日期,后 6 位代表时间,最后 2 位是时区。
7Bit 编码
"hellohello"包含了 10 个字符,他们必须一个个将用 7bits 来代表。
h e l l o h e l l o
104 101 108 108 111 104 101 108 108 111
1101000 1100101 1101100 1101100 1101111 1101000 1100101 1101100 1101100 1101111
1101000
110010 1
11011 00
1101 100
110 1111
11 01000
1 100101
1101100
1101100
110111 1
首先将字符转换为 7 位的二进制,然后,将后面字符的位调用到前面,补齐前面的差别。例如:h 翻译成
1101000,e 翻译成 1100101,显然 h 的二进制编码不足八位,那么就将 e 的最后一位补足到 h 的前面。那么就成了
11101000(E8)。剩余地编码看下表:
1 1101000
00 110010
100 11011
1111 1101
01000 110
100101 11
1101100 1
1 1101100
110111
E8 32 9B FD 46 97 D9
EC 37
那么就变成了 9 个八进制数 E8 32 9B FD 46 97 D9 EC 37。