閃信接收分析
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。如果大家遇到了,希望給大家的處理能帶來一點思路