Application Layer Analysis of SMS sending Process
1. involved classes
com.android.mms.ui.ComposeMessageActivitycom.android.mms.data.WorkingMessagecom.android.mms.transaction.MessageSendercom.android.mms.transaction.SmsMessageSendercom.android.mms.transaction.SmsSingleRecipientSendercom.android.mms.transaction.SmsReceiverServicecom.android.mms.transaction.SmsReceiver
2. Time Sequence Diagram
Note: From the UI interface to the call of the middle layer smsmanger method to send text messages, the approximate time sequence is like this. The reference code is Android 2.3.
3. Process Analysis
3.1 composemessageactivity work
This class is the UI for editing text messages and interacting with users, as shown in
After editing, you can click the send button to send the text message content. Clicking sendbutton triggers the listener corresponding to the button. Because composemessageactivity implements the onclicklistener interface, therefore, The onclick method is finally called.
1) onclick Analysis
This method does two things:
The first is to call the ispreparedforsending method to determine whether the current text message is to be sent, based on whether the recipient of the text message exceeds the allowed upper limit, whether there are recipients, and whether the text message contains content, attachments, themes, and so on, users are not allowed to send a text message without any content.
Second, the above check starts the sending process by calling the confirmsendmessageifneeded method. Of course, this method will not be successfully sent if it is called. This method will also perform a series of checks until it meets the requirements.
2) confirmsendmessageifneeded Analysis
Shows the logical call of this method:
3) The sendmessage method analysis shows that we have to go to sendmessage. Let's take a look at what work this method has done. By viewing the code, we can find that the core task is to send the text message to workingmessage and mworkingmessage. Send (mdebugrecipients). Other tasks are only for some auxiliary operations. Conclusion: The work of sending text messages till now is handed over to workingmessage, so the main task of composemessageactivity is to process dual-card.
3.2 simple analysis of workingmessage
1) Send () Analysis
This method performs five tasks: first, check whether the receiver list is empty, so I will not perform specific analysis here. The second is to convert the text message content from 8 bytes to 7 bytes. The third is to determine whether or not the current message is sent. Currently, the text message is sent, so the MMS sending process will not be followed. 4. Add the text message signature to the text message content. 5. Call the presendsmsworker () method. 2) presendsmsworker: reset the interface, clear all the components on the interface, call the sendsmsworker method, and delete the draft. 3) The work done by sendsmsworker () calls the sendmessage () method of smsmessagesender 3.3 smsmessagesender Analysis 1) sendmessage ()
This method will call the queuemessage () method to throw the task to process the sent task.
2) queuemessage () has two responsibilities: one is to save the messages to be sent to the database;
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext); boolean requestDeliveryReport = prefs.getBoolean( MessagingPreferenceActivity.SMS_DELIVERY_REPORT_MODE, DEFAULT_DELIVERY_REPORT_MODE); for (int i = 0; i < mNumberOfDests; i++) { try { log("updating Database with sub = " + mSubscription); Sms.addMessageToUri(mContext.getContentResolver(), Uri.parse("content://sms/queued"), mDests[i], mMessageText, null, mTimestamp, true /* read */, requestDeliveryReport, mThreadId, mSubscription); } catch (SQLiteException e) { SqliteWrapper.checkSQLiteException(mContext, e); } }
Second, transfer the task to another person, except that it is broadcast;
// Notify the SmsReceiverService to send the message out Intent intent = new Intent(SmsReceiverService.ACTION_SEND_MESSAGE, null, mContext, SmsReceiver.class);intent.putExtra(SUBSCRIPTION, mSubscription); mContext.sendBroadcast(intent);
Summary: this class has done a very important task: Saving the text message to be sent to the database, and then sending a broadcast notification smsreceiver;
3.4 smsreceiver to smsreceiverservice
In fact, the smsreceiver guy is not an officer either. He just handed it over to the smsreceiverservice immediately after getting it. "I don't care about this. I am a courier.", the role of smsreceiver is like this. You can refer to the time sequence diagram for calling methods;
3.5 smsreceiverservice Analysis
After talking about it for a long time, I finally got to work. Since it is a service, of course it will go through its own declared periodic function, first oncrate. This method has been initialized in recent years, and then onstartcommand (), this method does not do anything. It only sends messages to servicehandler. It seems that people have made great efforts.
1) servicehandler processes send requests
@Override public void handleMessage(Message msg) { int serviceId = msg.arg1; Intent intent = (Intent)msg.obj; if (intent != null) { String action = intent.getAction(); int error = intent.getIntExtra("errorCode", 0); if (SMS_RECEIVED_ACTION.equals(action)) { handleSmsReceived(intent, error); } else if (SMS_CB_RECEIVED_ACTION.equals(action)) { handleCbSmsReceived(intent, error); } else if (ACTION_BOOT_COMPLETED.equals(action)) { handleBootCompleted(); } else if (TelephonyIntents.ACTION_SERVICE_STATE_CHANGED.equals(action)) { handleServiceStateChanged(intent); } else if (ACTION_SEND_MESSAGE.endsWith(action)) { handleSendMessage(intent); } } // 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, serviceId); } }
After receiving the message, the handlesendmessage method is used;
2) handlesendmessage () Analysis: 1. Determine whether the dual-card can be used. If the current card is obtained and sendfirstqueuedmessage (INT sub) is called. 2. If the dual-card is not used, directly call the sendfirstqueuedmessage () method. Note: two different methods are called. You can see their parameters, but actually sendfirstqueuedmessage () the function without parameters is implemented by calling sendfirstqueuedmessage (INT sub). It is equivalent to calling sendfirstqueuedmessage (INT sub). 3) sendfirstqueuedmessage (INT sub) in brief, it first extracts text messages from the database, and then calls the sendmessage () method of smssinglerecipientsender to send the messages. Conclusion: you can find that the message was not sent after half a day. 3.6 smssinglerecipientsender
Sendmessage () Description:
The first is to split the text message content. The second is to save the text message to the outbox database table. The third is to separate the separated text messages and send the fourth is to send the sendmultiparttextmessage () of the smsmanger class called, transfers the specific operation to the intermediate layer. The Code is as follows:
if (mMessageText == null) { // Don't try to send an empty message, and destination should be just // one. throw new MmsException("Null message body or have multiple destinations."); } SmsManager smsManager = SmsManager.getDefault(); ArrayList<String> messages = null; if ((MmsConfig.getEmailGateway() != null) && (Mms.isEmailAddress(mDest) || MessageUtils.isAlias(mDest))) { String msgText; msgText = mDest + " " + mMessageText; mDest = MmsConfig.getEmailGateway(); messages = smsManager.divideMessage(msgText); } else { messages = smsManager.divideMessage(mMessageText); // remove spaces from destination number (e.g. "801 555 1212" -> "8015551212") mDest = mDest.replaceAll(" ", ""); } int messageCount = messages.size(); if (messageCount == 0) { // Don't try to send an empty message. throw new MmsException("SmsMessageSender.sendMessage: divideMessage returned " + "empty messages. Original message is \"" + mMessageText + "\""); } boolean moved = Sms.moveMessageToFolder(mContext, mUri, Sms.MESSAGE_TYPE_OUTBOX, 0); if (!moved) { throw new MmsException("SmsMessageSender.sendMessage: couldn't move message " + "to outbox: " + mUri); } ArrayList<PendingIntent> deliveryIntents = new ArrayList<PendingIntent>(messageCount); ArrayList<PendingIntent> sentIntents = new ArrayList<PendingIntent>(messageCount); for (int i = 0; i < messageCount; i++) { if (mRequestDeliveryReport) { // TODO: Fix: It should not be necessary to // specify the class in this intent. Doing that // unnecessarily limits customizability. deliveryIntents.add(PendingIntent.getBroadcast( mContext, 0, new Intent( MessageStatusReceiver.MESSAGE_STATUS_RECEIVED_ACTION, mUri, mContext, MessageStatusReceiver.class), 0)); } Intent intent = new Intent(SmsReceiverService.MESSAGE_SENT_ACTION, mUri, mContext, SmsReceiver.class); int requestCode = 0; if (i == messageCount -1) { // Changing the requestCode so that a different pending intent // is created for the last fragment with // EXTRA_MESSAGE_SENT_SEND_NEXT set to true. requestCode = 1; intent.putExtra(SmsReceiverService.EXTRA_MESSAGE_SENT_SEND_NEXT, true); intent.putExtra(SUBSCRIPTION, mSubscription); } sentIntents.add(PendingIntent.getBroadcast(mContext, requestCode, intent, 0)); } try { smsManager.sendMultipartTextMessage(mDest, mServiceCenter, messages, sentIntents, deliveryIntents, mSubscription); } catch (Exception ex) { throw new MmsException("SmsMessageSender.sendMessage: caught " + ex + " from SmsManager.sendTextMessage()"); } if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { log("sendMessage: address=" + mDest + ", threadId=" + mThreadId + ", uri=" + mUri + ", msgs.count=" + messageCount); }
4. Summary
This part mainly analyzes the process of sending short messages, starting from clicking the button on the UI to executing the sending operation on the middle layer. Of course, there are still many unknown points here, I am also trying to improve it.