闪信接收分析
flash sms现在虽然用的很少了,但是手机的代码中默认还是支持闪信这种类型的,在某邮箱里还是支持下发闪信的
由于我们设备一直不用短信这个功能,突然一天跳出一个页面:
一开始直接懵逼,怎么会跳出一个短信页面呢,查看记录的日志跟踪到该信息为闪信即零级短信(由于收到一个诈骗电话,设备直接挂断了,运营商发下来一个闪信,导致跳出了这个页面),在没有同意权限的情况下,就会跳出这个授权的页面.如果同意授权了,再收到闪信,就会是下面这个样子:
出现这个问题了,那就找原因吧:
- 第一步:
在脑海里回顾运行模型,拉通整个流程,缩小范围想可能是哪里的问题。
得了,这个不是咱们写的,没得搞,进入第二步
- 第二步:
查看日志、查看各种快照。
由于咱们有记录日志,那么可以先查看日志,根据大概时间一行一行查看,看到了如下日志
I/ActivityManager( 1623): START u0 {flg=0x18000000 cmp=com.android.mms/.ui.ClassZeroActivity (has extras)} from uid 10021 on display 0
#省略部分
I/ActivityManager( 1623): START u0 {cmp=com.android.mms/.ui.PermissionGuardActivity (has extras)} from uid 10021 on display 0
难道这就是起的这个页面吗?当然如果有条件,可以用命令查看一下当前顶部焦点页面是哪个也能确认到就是这个PermissionGuardActivity页面,接下来就是从代码的角度(源码)里去分析这个页面的弹出流程。
由于我们不需要零级短信,最后的处理呢,自然是修改源码,过滤掉零级短信的显示了,接下来看下闪信接收流程。源码为7.1.1原生代码
Mms的类图与时序图
类图:
时序图:
涉及对象:
- PrivilegedCbReceiver
- PrivilegedCbReceiver的父类SmsReceiver
- SmsReceiverService
- SmsReceiverService的内部类ServiceHandler
- ClassZeroActivity
- MessageUtils
- PermissionGuardActivity
查看时序图大致就能知道跳出闪信页面的流程了,下面贴下代码
流程代码分析
新信息从RIL到达framework后,是从哪里发送给MMs应用处理的- - 我目前还没理清具体是从哪里发出来的,看了GsmSMSDispatcher->GsmInboundSmsHandler->StateMachine,但也没看出来是怎样子发送出来的,有更了解的可以科普科普。
不了解具体是从哪里发出来,不影响对这个问题的分析,接下来看MMs里的接收
- 发送到了MMS应用内的PrivilegedCbReceiver
public class PrivilegedCbReceiver extends SmsReceiver {
@Override
public void onReceive(Context context, Intent intent) {
//接收到消息后,调用父类的onReceiveWithPrivilege方法
onReceiveWithPrivilege(context, intent, true);
}
}
- SmsReceiver的onReceiveWithPrivilege
这里会启动SmsReceiverService服务,利用intent将消息传过去
protected void onReceiveWithPrivilege(Context context, Intent intent, boolean privileged) {
if (!MessageUtils.hasBasicPermissions()) {
Log.d("Mms", "SmsReceiver do not have basic permissions");
return;
}
String action = intent.getAction();
LogTag.debugD("onReceiveWithPrivilege:intent="+intent+"|privileged="+privileged);
// If 'privileged' is false, it means that the intent was delivered to the base
// no-permissions receiver class. If we get an SMS_RECEIVED message that way, it
// means someone has tried to spoof the message by delivering it outside the normal
// permission-checked route, so we just ignore it.
if (!privileged && (Intents.SMS_DELIVER_ACTION.equals(action) ||
"android.cellbroadcastreceiver.CB_AREA_INFO_RECEIVED".equals(action))) {
return;
}
intent.setClass(context, SmsReceiverService.class);
intent.putExtra("result", getResultCode());
//起SmsReceiverService服务,在beginStartingService方法内会唤醒屏幕锁
beginStartingService(context, intent);
}
- SmsReceiverService
在SmsReceiverService内部,主要是经过它的onStartCommand,将消息传递到mServiceHandler内,ServiceHandler是SmsReceiverService的一个内部类继承Handler
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (!MmsConfig.isSmsEnabled(this)) {
LogTag.debugD("SmsReceiverService: is not the default sms app");
// NOTE: We MUST not call stopSelf() directly, since we need to
// make sure the wake lock acquired by AlertReceiver is released.
SmsReceiver.finishStartingService(SmsReceiverService.this, startId);
return Service.START_NOT_STICKY;
}
// Temporarily removed for this duplicate message track down.
int resultCode = intent != null ? intent.getIntExtra("result", 0) : 0;
if (resultCode != 0) {
LogTag.debugD("onStart: #" + startId + " resultCode: " + resultCode +
" = " + translateResultCode(resultCode));
}
//将消息传递到mServiceHandler内处理
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
return Service.START_NOT_STICKY;
}
- ServiceHandler
- handleMessage : 消息队列方式,处理接收到的消息
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
int serviceId = msg.arg1;
Intent intent = (Intent)msg.obj;
LogTag.debugD("handleMessage serviceId: " + serviceId + " intent: " + intent);
LogTag.debugD("handleMessage action: " + action + " error: " + error);
~~~省去部分代码
if (CB_AREA_INFO_RECEIVED_ACTION.equals(action)) {
//调用消息接收处理
handleSmsReceived(intent, error);
}
~~~省去部分代码
}
}
- handleSmsReceived : 处理接收到的短消息
private void handleSmsReceived(Intent intent, int error) {
SmsMessage[] msgs = Intents.getMessagesFromIntent(intent);
String format = intent.getStringExtra("format");
// Because all sub id have been changed to phone id in Mms,
// so also change it here.
SmsMessage sms4log = msgs[0];
LogTag.debugD("handleSmsReceived" + (sms4log.isReplace() ? "(replace)" : "") +
", address: " + sms4log.getOriginatingAddress() +
", body: " + sms4log.getMessageBody());
//获取存储本地,在config.xml里 <bool name="config_savelocation">false</bool>,默认是false
int saveLoc = MessageUtils.getSmsPreferStoreLocation(this,
SubscriptionManager.getPhoneId(msgs[0].getSubId()));
if (getResources().getBoolean(R.bool.config_savelocation)
&& saveLoc == MessageUtils.PREFER_SMS_STORE_CARD) {
LogTag.debugD("PREFER SMS STORE CARD");
for (int i = 0; i < msgs.length; i++) {
SmsMessage sms = msgs[i];
boolean saveSuccess = saveMessageToIcc(sms);
if (saveSuccess) {
int subId = TelephonyManager.getDefault().isMultiSimEnabled()
? sms.getSubId() : (int)MessageUtils.SUB_INVALID;
int phoneId = SubscriptionManager.getPhoneId(subId);
String address = MessageUtils.convertIdp(this,
sms.getDisplayOriginatingAddress(), phoneId);
MessagingNotification.blockingUpdateNewIccMessageIndicator(
this, address, sms.getDisplayMessageBody(),
subId, sms.getTimestampMillis());
getContentResolver().notifyChange(MessageUtils.getIccUriBySubscription(
phoneId), null);
} else {
LogTag.debugD("SaveMessageToIcc fail");
mToastHandler.post(new Runnable() {
public void run() {
Toast.makeText(getApplicationContext(),
getString(R.string.pref_sim_card_full_save_to_phone),
Toast.LENGTH_LONG).show();
}
});
// 存储消息
saveMessageToPhone(msgs, error, format);
break;
}
}
} else {
// 走到这里存储消息
saveMessageToPhone(msgs, error, format);
}
}
- saveMessageToPhone : 存入消息到手机
private void saveMessageToPhone(SmsMessage[] msgs, int error, String format){
setSavingMessage(true);
//插入消息
Uri messageUri = insertMessage(this, msgs, error, format);
MessageUtils.checkIsPhoneMessageFull(this);
if (messageUri != null) {
long threadId = MessagingNotification.getSmsThreadId(this, messageUri);
// Called off of the UI thread so ok to block.
LogTag.debugD("saveMessageToPhone messageUri: " + messageUri + " threadId: " + threadId);
MessagingNotification.blockingUpdateNewMessageIndicator(this, threadId, false);
MessageUtils.updateThreadAttachTypeByThreadId(this, threadId);
} else {
LogTag.debugD("saveMessageToPhone messageUri is null !");
}
setSavingMessage(false);
}
- insertMessage : 插入消息,会判断消息类型,闪信的话是不存储的,只弹框
private Uri insertMessage(Context context, SmsMessage[] msgs, int error, String format) {
// Build the helper classes to parse the messages.
SmsMessage sms = msgs[0];
//闪信真面目,判断到是闪信
if (sms.getMessageClass() == SmsMessage.MessageClass.CLASS_0) {
//显示闪信的弹窗
displayClassZeroMessage(context, sms, format);
return null;
} else if (sms.isReplace()) {
return replaceMessage(context, msgs, error);
} else {
return storeMessage(context, msgs, error);
}
}
- displayClassZeroMessage : 显示闪信页面
private void displayClassZeroMessage(Context context, SmsMessage sms, String format) {
int subId = sms.getSubId();
//起一个页面来显示闪信
Intent smsDialogIntent = new Intent(context, ClassZeroActivity.class)
.putExtra("pdu", sms.getPdu())
.putExtra("format", format)
.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
context.startActivity(smsDialogIntent);
}
- ClassZeroActivity :显示闪信页面
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
//上来先校验权限
if (MessageUtils.checkPermissionsIfNeeded(this)) {
return;
}
}
- MessageUtils :工具类
- checkPermissionsIfNeeded :校验权限
public static boolean checkPermissionsIfNeeded(Activity activity) {
if (hasBasicPermissions()) {
MmsApp.getApplication().initPermissionRelated();
if (hasPermissions(sSMSExtendPermissions)) {
return false;
}
}
launchPermissionCheckActivity(activity, sSMSExtendPermissions);//如果没权限,开启权限认证的页面
activity.finish();
return true;
}
- launchPermissionCheckActivity :开启权限申请页面
public static void launchPermissionCheckActivity(Activity activity,String [] permissions) {
final Intent intent = new Intent(activity, PermissionGuardActivity.class);
intent.putExtra(PermissionGuardActivity.ORIGINAL_INTENT, activity.getIntent());
intent.putExtra(PermissionGuardActivity.EXT_PERMISSIONS, permissions);
activity.startActivity(intent);
}
自此,所以跳出了一个权限申请页面。如果同意了权限后,再来闪信,就会直接弹dialog方式显示出闪信的信息内容,最后的处理就是直接屏蔽掉前面提到的displayClassZeroMessage方法即可。
借此机会记录和分享给大家,在出现这个问题前都不知道这个Flash sms。如果大家遇到了,希望给大家的处理能带来一点思路