記一次Android Flash Sms引起的問題

閃信接收分析

flash sms現在雖然用的很少了,但是手機的代碼中默認還是支持閃信這種類型的,在某郵箱裏還是支持下發閃信的

由於我們設備一直不用短信這個功能,突然一天跳出一個頁面:

flash sms 權限提示

一開始直接懵逼,怎麼會跳出一個短信頁面呢,查看記錄的日誌跟蹤到該信息爲閃信即零級短信(由於收到一個詐騙電話,設備直接掛斷了,運營商發下來一個閃信,導致跳出了這個頁面),在沒有同意權限的情況下,就會跳出這個授權的頁面.如果同意授權了,再收到閃信,就會是下面這個樣子:

接收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的類圖與時序圖

類圖:

flash sms 類圖

時序圖:

flash sms 時序圖

涉及對象:

  1. PrivilegedCbReceiver
  2. PrivilegedCbReceiver的父類SmsReceiver
  3. SmsReceiverService
  4. SmsReceiverService的內部類ServiceHandler
  5. ClassZeroActivity
  6. MessageUtils
  7. 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
  1. 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);
            }
            ~~~省去部分代碼
        }
    }
  1. 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);
        }
    }
  1. 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);
    }
  1. 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);
        }
    }
  1. 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 :工具類
  1. checkPermissionsIfNeeded :校驗權限
public static boolean checkPermissionsIfNeeded(Activity activity) {
        if (hasBasicPermissions()) {
            MmsApp.getApplication().initPermissionRelated();
            if (hasPermissions(sSMSExtendPermissions)) {
                return false;
            }
        }
        launchPermissionCheckActivity(activity, sSMSExtendPermissions);//如果沒權限,開啓權限認證的頁面
        activity.finish();
        return true;
    }
  1. 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。如果大家遇到了,希望給大家的處理能帶來一點思路

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章