Android SMS call analysis
< android SDK1.5 cupcake emulator >
solicited Message
emulator HOME의 Messaging 아이콘 클릭
ConversationList.java
/open_src/Packages/Apps/Mms/Src/Com/Android/Mms/Ui/ConversationList.java |
onCreate();
View, Listener 초기화 작업
onResume(); DraftCache.getInstance().addOnDraftChangedListener(this); getContentResolver().delete(Threads.OBSOLETE_THREADS_URI, null, null);
DraftCache.getInstance().refresh();
startAsyncQuery();
ContactInfoCache.getInstance().invalidateCache(); |
New message 클릭
/open_src/Packages/Apps/Mms/Src/Com/Android/Mms/Ui/ConversationList.java |
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
if (LOCAL_LOGV) {
Log.v(TAG, "onListItemClick: position=" + position + ", id=" + id);
} if (position == 0) {
createNewMessage();
} else if (v instanceof ConversationHeaderView) {
ConversationHeaderView headerView = (ConversationHeaderView) v;
ConversationHeader ch = headerView.getConversationHeader(); // TODO: The 'from' view of the ConversationHeader was
// repurposed to be the cached display value, rather than
// the old raw value, which openThread() wanted. But it
// turns out openThread() doesn't need it:
// ComposeMessageActivity will load it. That's not ideal,
// though, as it's an SQLite query. So fix this later to
// save some latency on starting ComposeMessageActivity.
String somethingDelimitedAddresses = null;
openThread(ch.getThreadId(), somethingDelimitedAddresses);
}
} |
/open_src/Packages/Apps/Mms/Src/Com/Android/Mms/Ui/ConversationList.java |
private void createNewMessage() {
Intent intent = new Intent(this, ComposeMessageActivity.class);
startActivity(intent);
} |
ComposeMessageActivity.java
/open_src/Packages/Apps/Mms/Src/Com/Android/Mms/Ui/ComposeMessageActivity.java |
onCreate() {
initResourceRefs(); // Initialize members for UI elements.
initActivityState(savedInstanceState, getIntent()); // Read parameters or previously saved
state of this activity.
mRecipientList = RecipientList.from(mExternalAddress, this); // Parse the recipient list.
initMessageList(); // Set up the message history ListAdapter
markAsRead(mThreadId); // Mark the current thread as read.
updateSendButtonState();
// 이 밖에도 MMS 모드 체크, 전송 실패한 메시지 정보를 pop-up 등등의 초기화 작업을 함.
} onStart() {
updateWindowTitle();
initFocus();
registerReceiver(mHttpProgressReceiver, mHttpProgressFilter); // Register a
// BroadcastReceiver to listen on HTTP I/O process.
startMsgListQuery();
startQueryForContactInfo();
updateSendFailedNotification();
} onResume() {
startPresencePollingRequest(); // enqueueMessage(Message msg, long when) 에서
// MessageQueue를 확인한다. 주기적 반복
} |
Send 버튼 클릭
/open_src/Packages/Apps/Mms/Src/Com/Android/Mms/Ui/ComposeMessageActivity.java |
/*
onCreate() {
...
initResourceRefs() {
...
mSendButton = (Button) findViewById(R.id.send_button);
mSendButton.setOnClickListener(this);
}
updateSendButtonState() {
...
mSendButton.setEnabled(enable);
mSendButton.setFocusable(enable);
}
}
*/ public void onClick(View v) {
if ((v == mSendButton) && isPreparedForSending()) {
// if문의 두번째 메소드는 hasRecipient() && (hasAttachment() || hasText()) 를 확인한다.
// 즉, 메시지(받는사람, 내용, 첨부)를 작성했는지를 확인
confirmSendMessageIfNeeded();
}
} |
/open_src/Packages/Apps/Mms/Src/Com/Android/Mms/Ui/ComposeMessageActivity.java |
//받는사람이 유효한지 확인한 후, 메시지 전송 private void confirmSendMessageIfNeeded() {
if (mRecipientList.hasInvalidRecipient()) {
if (mRecipientList.hasValidRecipient()) {
String title = getResourcesString(R.string.has_invalid_recipient,
mRecipientList.getInvalidRecipientString());
new AlertDialog.Builder(this)
.setIcon(android.R.drawable.ic_dialog_alert)
.setTitle(title)
.setMessage(R.string.invalid_recipient_message)
.setPositiveButton(R.string.try_to_send,
new SendIgnoreInvalidRecipientListener())
.setNegativeButton(R.string.no, new CancelSendingListener())
.show();
} else {
new AlertDialog.Builder(this)
.setIcon(android.R.drawable.ic_dialog_alert)
.setTitle(R.string.cannot_send_message)
.setMessage(R.string.cannot_send_message_reason)
.setPositiveButton(R.string.yes, new CancelSendingListener())
.show();
}
} else {
sendMessage();
}
} |
sendMessage()
/open_src/Packages/Apps/Mms/Src/Com/Android/Mms/Ui/ComposeMessageActivity.java |
private void sendMessage() {
// Need this for both SMS and MMS.
final String[] dests = mRecipientList.getToNumbers();
// removeSubjectIfEmpty will convert a message that is solely an MMS
// message because it has an empty subject back into an SMS message.
// It doesn't notify the user of the conversion.
removeSubjectIfEmpty();
if (requiresMms()) {
// Make local copies of the bits we need for sending a message,
// because we will be doing it off of the main thread, which will
// immediately continue on to resetting some of this state.
final Uri mmsUri = mMessageUri;
final PduPersister persister = mPersister;
final SlideshowModel slideshow = mSlideshow;
final SendReq sendReq = new SendReq();
fillMessageHeaders(sendReq);
// Make sure the text in slide 0 is no longer holding onto a reference to the text
// in the message text box.
slideshow.prepareForSend(); // Do the dirty work of sending the message off of the main UI thread.
new Thread(new Runnable() {
public void run() {
sendMmsWorker(dests, mmsUri, persister, slideshow, sendReq);
}
}).start();
} else {
// Same rules apply as above.
final String msgText = mMsgText.toString();
new Thread(new Runnable() {
public void run() {
sendSmsWorker(dests, msgText);
}
}).start();
}
if (mExitOnSent) {
// If we are supposed to exit after a message is sent,
// clear out the text and URIs to inhibit saving of any
// drafts and call finish().
mMsgText = "";
mMessageUri = null;
finish();
} else {
// Otherwise, reset the UI to be ready for the next message.
resetMessage();
}
} |
sendSmsWorker()
/open_src/Packages/Apps/Mms/Src/Com/Android/Mms/Ui/ComposeMessageActivity.java |
private void sendSmsWorker(String[] dests, String msgText) {
// Make sure we are still using the correct thread ID for our
// recipient set.
long threadId = getOrCreateThreadId(dests); MessageSender sender = new SmsMessageSender(this, dests, msgText, threadId);
try {
sender.sendMessage(threadId);
setThreadId(threadId);
startMsgListQuery();
} catch (Exception e) {
Log.e(TAG, "Failed to send SMS message, threadId=" + threadId, e);
}
} |
--- APPLICATION FRAMEWORK ↓ -------------------------------------------------------------
sender.sendMessage(threadId)
/open_src/Packages/Apps/Mms/Src/Com/Android/Mms/Transaction/SmsMessageSender.java |
/*
위의 sender 인스턴스 생성시 SmsMessageSender(..) 생성자 실행
public SmsMessageSender(Context context, String[] dests, String msgText,
long threadId) {
mContext = context;
mMessageText = msgText;
mNumberOfDests = dests.length;
mDests = new String[mNumberOfDests];
System.arraycopy(dests, 0, mDests, 0, mNumberOfDests);
mTimestamp = System.currentTimeMillis();
mThreadId = threadId > 0 ? threadId
: Threads.getOrCreateThreadId(context,
new HashSet<String>(Arrays.asList(dests)));
mServiceCenter = getOutgoingServiceCenter(mThreadId);
}
*/ public boolean sendMessage(long token) throws MmsException {
if ((mMessageText == null) || (mNumberOfDests == 0)) {
// Don't try to send an empty message.
throw new MmsException("Null message body or dest.");
} SmsManager smsManager = SmsManager.getDefault(); //Get the default instance
// of the SmsManager for (int i = 0; i < mNumberOfDests; i++) {
ArrayList<String> messages = smsManager.divideMessage(mMessageText);
// Divide a text message into several
// messages, none bigger than
// the maximum SMS message size.
// SmsManager.java
int messageCount = messages.size();
ArrayList<PendingIntent> deliveryIntents =
new ArrayList<PendingIntent>(messageCount);
ArrayList<PendingIntent> sentIntents =
new ArrayList<PendingIntent>(messageCount);
SharedPreferences prefs =
PreferenceManager.getDefaultSharedPreferences(mContext);
boolean requestDeliveryReport = prefs.getBoolean(
MessagingPreferenceActivity.SMS_DELIVERY_REPORT_MODE,
DEFAULT_DELIVERY_REPORT_MODE);
Uri uri = null;
try {
uri = Sms.Outbox.addMessage(mContext.getContentResolver(), mDests[i],
mMessageText, null, mTimestamp, requestDeliveryReport, mThreadId);
} catch (SQLiteException e) {
SqliteWrapper.checkSQLiteException(mContext, e);
} for (int j = 0; j < messageCount; j++) {
if (requestDeliveryReport) {
// 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,
uri,
mContext,
MessageStatusReceiver.class),
0));
}
sentIntents.add(PendingIntent.getBroadcast(
mContext, 0,
new Intent(SmsReceiverService.MESSAGE_SENT_ACTION,
uri,
mContext,
SmsReceiver.class),
0));
}
smsManager.sendMultipartTextMessage(
mDests[i], mServiceCenter, messages, sentIntents,
deliveryIntents);
}
return false;
} |
smsManager.sendMultipartTextmessage(...)
/open_src/Frameworks/Base/Telephony/Gsm/SmsManager.java |
/**
* Send a multi-part text based SMS. The callee should have already
* divided the message into correctly sized parts by calling
* <code>divideMessage</code>.
*/ public void sendMultipartTextMessage(
String destinationAddress, String scAddress, ArrayList<String> parts,
ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents) {
if (TextUtils.isEmpty(destinationAddress)) {
throw new IllegalArgumentException("Invalid destinationAddress");
}
if (parts == null || parts.size() < 1) {
throw new IllegalArgumentException("Invalid message body");
}
// divided된 메시지 parts에 따라 전송 방법이 2 가지로 나뉜다.
if (parts.size() > 1) {
try {
ISms simISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
if (simISms != null) {
simISms.sendMultipartText(destinationAddress, scAddress, parts,
sentIntents, deliveryIntents);
}
} catch (RemoteException ex) {
// ignore it
}
} else {
PendingIntent sentIntent = null;
PendingIntent deliveryIntent = null;
if (sentIntents != null && sentIntents.size() > 0) {
sentIntent = sentIntents.get(0);
}
if (deliveryIntents != null && deliveryIntents.size() > 0) {
deliveryIntent = deliveryIntents.get(0);
}
sendTextMessage(destinationAddress, scAddress, parts.get(0),
sentIntent, deliveryIntent);
}
} |
simISms.sendMultipartText(...)
/open_src/frameworks/base/telephony/java/com/android/internal/telephony/gsm/ SimSmsInterfaceManager.java |
/**
* Send a multi-part text based SMS
*/
private GSMPhone mPhone; public void sendMultipartText(String destinationAddress, String scAddress, List<String> parts,
List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents) {
Context context = mPhone.getContext(); context.enforceCallingPermission(
"android.permission.SEND_SMS",
"Sending SMS message");
if (DBG) log("sendMultipartText");
mPhone.mSMS.sendMultipartText(destinationAddress, scAddress,
(ArrayList<String>) parts,
(ArrayList<PendingIntent>) sentIntents, (ArrayList<PendingIntent>)
deliveryIntents);
} |
mphone.mSMS.sendMultipartText(...)
/open_src/frameworks/base/telephony/java/com/android/internal/telephony/gsm/ SMSDispatcher.java |
/**
* Send a multi-part text based SMS
*/ void sendMultipartText(String destinationAddress, String scAddress, ArrayList<String> parts,
ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents) { PendingIntent sentIntent = null;
int ss = mPhone.getServiceState().getState(); //Get current servcie state of phone
if (ss == ServiceState.STATE_IN_SERVICE) {
// Only check SMS sending limit while in service
if (sentIntents != null && sentIntents.size() > 0) {
sentIntent = sentIntents.get(0);
}
String appName = getAppNameByIntent(sentIntent);
if ( !mCounter.check(appName, parts.size())) {
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("destination", destinationAddress);
map.put("scaddress", scAddress);
map.put("parts", parts);
map.put("sentIntents", sentIntents);
map.put("deliveryIntents", deliveryIntents);
SmsTracker multipartParameter = new SmsTracker(map, null, null); sendMessage(obtainMessage(EVENT_POST_ALERT, multipartParameter));
return;
}
}
sendMultipartTextWithPermit(destinationAddress,
scAddress, parts, sentIntents, deliveryIntents);
} |
sendMultipartTextWithPermit(...)
/open_src/frameworks/base/telephony/java/com/android/internal/telephony/gsm/ SMSDispatcher.java |
/**
* Send a multi-part text based SMS which already passed SMS control check.
*
* It is the working function for sendMultipartText().
*/ private void sendMultipartTextWithPermit(String destinationAddress,
String scAddress, ArrayList<String> parts,
ArrayList<PendingIntent> sentIntents,
ArrayList<PendingIntent> deliveryIntents) {
PendingIntent sentIntent = null;
PendingIntent deliveryIntent = null;
// check if in service
int ss = mPhone.getServiceState().getState();
if (ss != ServiceState.STATE_IN_SERVICE) {
for (int i = 0, count = parts.size(); i < count; i++) {
if (sentIntents != null && sentIntents.size() > i) {
sentIntent = sentIntents.get(i);
}
SmsTracker tracker = new SmsTracker(null, sentIntent, null);
handleNotInService(ss, tracker);
}
return;
} int ref = ++sConcatenatedRef & 0xff; for (int i = 0, count = parts.size(); i < count; i++) {
// build SmsHeader
byte[] data = new byte[3];
data[0] = (byte) ref; // reference #, unique per message
data[1] = (byte) count; // total part count
data[2] = (byte) (i + 1); // 1-based sequence
SmsHeader header = new SmsHeader();
header.add(new SmsHeader.Element(
SmsHeader.CONCATENATED_8_BIT_REFERENCE, data));
if (sentIntents != null && sentIntents.size() > i) {
sentIntent = sentIntents.get(i);
}
if (deliveryIntents != null && deliveryIntents.size() > i) {
deliveryIntent = deliveryIntents.get(i);
} /** * Get an SMS-SUBMIT PDU for a destination address and a message
*/ SmsMessage.SubmitPdu pdus = // ↓ Encoding
SmsMessage.getSubmitPdu(scAddress, destinationAddress,
parts.get(i), deliveryIntent != null, header.toByteArray()); HashMap<String, Object> map = new HashMap<String, Object>();
map.put("smsc", pdus.encodedScAddress);
map.put("pdu", pdus.encodedMessage); SmsTracker tracker = new SmsTracker(map, sentIntent, deliveryIntent);
sendSms(tracker);
}
} |
/open_src/frameworks/base/telephony/java/com/android/internal/telephony/gsm/ SMSDispatcher.java |
/**
* Send the message along to the radio.
*/ private final CommandsInterface mCm; private 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(SimUtils.bytesToHexString(smsc), SimUtils.bytesToHexString(pdu),
reply); // sendSMS,, interface CommandsInterface} |
--- RIL ↓ -----------------------------------------------------------------------------------
mCm.sendSMS(...)
/open_src/frameworks/base/telephony/java/com/android/internal/telephony/gsm/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));
// RILJ 로그는 여기서 프린트 된다. "[...] > SEND_SMS"
send(rr);
} |
send(rr)
/open_src/frameworks/base/telephony/java/com/android/internal/telephony/gsm/RIL.java |
private void
send(RILRequest rr)
{
Message msg; msg = mSender.obtainMessage(EVENT_SEND, rr); // 메시지큐에서 메시지 가져오기 acquireWakeLock(); // Holds a PARTIAL_WAKE_LOCK whenever
(a) There is outstanding RIL request sent to RIL deamon and no
replied
(b) There is a request waiting to be sent out. There is a
WAKE_LOCK_TIMEOUT to release the lock, though
it shouldn't happen often. msg.sendToTarget(); // Sends this Message to the Handler specified by getTarget().
// Handler adroid.os.Message.getTarget()
// Retrieve the a handler implementation that will receive this
message. The object must implement
Handler.handleMessage().
Each Handler has its own name-space for message codes,
so you do not need to worry about yours conflicting with other
handlers.
} |
Unsolicited Message
Vendor RIL 에서 New Message가 왔음을 알린다.
processUnsolicited (Parcel P)
/open_src/frameworks/base/telephony/java/com/android/internal/telephony/gsm/RIL.java |
private void
processUnsolicited (Parcel p)
{
int response;
Object ret; /** * Read an integer value from the parcel at the current dataPosition().
*/
response = p.readInt(); try {switch(response) { /*
cat libs/telephony/ril_unsol_commands.h \
| egrep "^ *{RIL_" \
| sed -re 's/\{([^,]+),[^,]+,([^}]+).+/case \1: \2(rr, p); break;/'
*/ ...
/**
* Read a string value from the parcel at the current dataPosition().
*/ ↑ p.readString()
case RIL_UNSOL_RESPONSE_NEW_SMS: ret = responseString(p); break;
case RIL_UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT: ret = responseString(p);
break;
case RIL_UNSOL_RESPONSE_NEW_SMS_ON_SIM: ret = responseInts(p); break;
...
default:
throw new RuntimeException("Unrecognized unsol response: " + response);
//break; (implied)
}} catch (Throwable tr) {
Log.e(LOG_TAG, "Exception processing unsol response: " + response +
"Exception:" + tr.toString());
return;
} switch(response) {
case RIL_UNSOL_RESPONSE_NEW_SMS: {
if (RILJ_LOGD) unsljLog(response);
// ↑ D/RILJ ( 610): [UNSL]< UNSOL_RESPONSE_NEW_SMS // 로그 프린트 // FIXME this should move up a layer
String a[] = new String[2]; a[1] = (String)ret; SmsMessage sms; sms = SmsMessage.newFromCMT(a);
if (mSMSRegistrant != null) {
mSMSRegistrant
.notifyRegistrant(new AsyncResult(null, sms, null));
}
break;
}
}
} |
SmsMessage.newFromCMT(a)
/open_src/frameworks/base/telephony/java/com/android/internal/telephony/gsm/RIL.java |
/**
* TS 27.005 3.4.1 lines[0] and lines[1] are the two lines read from the
* +CMT unsolicited response (PDU mode, of course)
* +CMT: [<alpha>],<length><CR><LF><pdu>
*
* Only public for debugging
*
* {@hide}
*/
/* package */
public static SmsMessage newFromCMT(String[] lines) {
try {
SmsMessage msg = new SmsMessage();
msg.parsePdu(SimUtils.hexStringToBytes(lines[1]));
return msg;
} catch (RuntimeException ex) {
Log.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
return null;
}
} |
msg.parsePdu(...)
/open_src/Frameworks/Base/Telephony/Gsm/SmsMessage.java |
/**
* TS 27.005 3.1, <pdu> definition "In the case of SMS: 3GPP TS 24.011 [6]
* SC address followed by 3GPP TS 23.040 [3] TPDU in hexadecimal format:
* ME/TA converts each octet of TP data unit into two IRA character long
* hexad number (e.g. octet with integer value 42 is presented to TE as two
* characters 2A (IRA 50 and 65))" ...in the case of cell broadcast,
* something else...
*/ int mti; // TP-Message-Type-Indicator // 9.2.3 String scAddress; /* The address of the SMSC. May be null */private void parsePdu(byte[] pdu) {
mPdu = pdu;
// Log.d(LOG_TAG, "raw sms mesage:");
// Log.d(LOG_TAG, s); PduParser p = new PduParser(pdu); scAddress = p.getSCAddress();
↑ /* int android.telephony.gsm.SmsMessage.PduParser.getByte() 에 의해 SCAddress길이를 * 알수 있고, 이 integer값으로 if문을 통해 * PhoneNumberUtils.calledPartyBCDToString(pdu, cur, len) 메소드가 실행되고, * String 타입의 리턴값을 받는다. */
if (scAddress != null) {
if (Config.LOGD) Log.d(LOG_TAG, "SMS SC address: " + scAddress);
} // TODO(mkf) support reply path, user data header indicator // TP-Message-Type-Indicator
// 9.2.3
int firstByte = p.getByte(); /* return pdu[cur++] & 0xff; */ ↑ /* 위의 PduParser 인스턴스 p 생성시 생성자에 의해 PduParser 클래스의 integer 형 변수
* cur, mUserDataSeptetPadding 는 0 으로 셋팅된다.
*/ mti = firstByte & 0x3;
switch (mti) {
// TP-Message-Type-Indicator
// 9.2.3
case 0:
parseSmsDeliver(p, firstByte);
break;
case 2:
parseSmsStatusReport(p, firstByte);
break;
default:
// TODO(mkf) the rest of these
throw new RuntimeException("Unsupported message type");
}
} |
parseSmsDeliver(p, firstByte)
/open_src/Frameworks/Base/Telephony/Gsm/SmsMessage.java |
boolean replyPathPresent = false; // TP-Reply-Path
// e.g. 23.040 9.2.2.1
SmsAddress originatingAddress; /* The address of the sender */
int protocolIdentifier; /* TP-Protocol-Identifier (TP-PID) */
int dataCodingScheme; // TP-Data-Coding-Scheme
// see TS 23.038
private void parseSmsDeliver(PduParser p, int firstByte) {
replyPathPresent = (firstByte & 0x80) == 0x80; originatingAddress = p.getAddress(); if (originatingAddress != null) {
if (Config.LOGV) Log.v(LOG_TAG, "SMS originating address: "
+ originatingAddress.address);
} // TP-Protocol-Identifier (TP-PID)
// TS 23.040 9.2.3.9
protocolIdentifier = p.getByte(); // TP-Data-Coding-Scheme
// see TS 23.038
dataCodingScheme = p.getByte(); if (Config.LOGV) {
Log.v(LOG_TAG, "SMS TP-PID:" + protocolIdentifier
+ " data coding scheme: " + dataCodingScheme);
} scTimeMillis = p.getSCTimestampMillis();
↑
// Parses an SC timestamp and returns a currentTimeMillis()-style
// timestamp if (Config.LOGD) Log.d(LOG_TAG, "SMS SC timestamp: " + scTimeMillis); ↑// D/GSM ( 610): SMS SC timestamp: 1265783527000 // 로그 프린트 boolean hasUserDataHeader = (firstByte & 0x40) == 0x40; parseUserData(p, hasUserDataHeader); //Parses the User Data of an SMS.
} |
parseUserData(p, hasUserDataHeader)
/open_src/Frameworks/Base/Telephony/Gsm/SmsMessage.java |
private void parseUserData(PduParser p, boolean hasUserDataHeader) {
boolean hasMessageClass = false;
boolean userDataCompressed = false; int encodingType = ENCODING_UNKNOWN; // Look up the data encoding scheme
if ((dataCodingScheme & 0x80) == 0) {
// Bits 7..4 == 0xxx
automaticDeletion = (0 != (dataCodingScheme & 0x40));
userDataCompressed = (0 != (dataCodingScheme & 0x20));
hasMessageClass = (0 != (dataCodingScheme & 0x10)); if (userDataCompressed) {
Log.w(LOG_TAG, "4 - Unsupported SMS data coding scheme "
+ "(compression) " + (dataCodingScheme & 0xff));
} else {
switch ((dataCodingScheme >> 2) & 0x3) {
case 0: // GSM 7 bit default alphabet
encodingType = ENCODING_7BIT;
break; case 2: // UCS 2 (16bit)
encodingType = ENCODING_16BIT;
break; case 1: // 8 bit data
case 3: // reserved
Log.w(LOG_TAG, "1 - Unsupported SMS data coding scheme "
+ (dataCodingScheme & 0xff));
encodingType = ENCODING_8BIT;
break;
}
}
} else if ((dataCodingScheme & 0xf0) == 0xf0) {
automaticDeletion = false;
hasMessageClass = true;
userDataCompressed = false; if (0 == (dataCodingScheme & 0x04)) {
// GSM 7 bit default alphabet
encodingType = ENCODING_7BIT;
} else {
// 8 bit data
encodingType = ENCODING_8BIT;
}
} else if ((dataCodingScheme & 0xF0) == 0xC0
|| (dataCodingScheme & 0xF0) == 0xD0
|| (dataCodingScheme & 0xF0) == 0xE0) {
// 3GPP TS 23.038 V7.0.0 (2006-03) section 4 // 0xC0 == 7 bit, don't store
// 0xD0 == 7 bit, store
// 0xE0 == UCS-2, store if ((dataCodingScheme & 0xF0) == 0xE0) {
encodingType = ENCODING_16BIT;
} else {
encodingType = ENCODING_7BIT;
} userDataCompressed = false;
boolean active = ((dataCodingScheme & 0x08) == 0x08); // bit 0x04 reserved if ((dataCodingScheme & 0x03) == 0x00) {
isMwi = true;
mwiSense = active;
mwiDontStore = ((dataCodingScheme & 0xF0) == 0xC0);
} else {
isMwi = false; Log.w(LOG_TAG, "MWI for fax, email, or other "
+ (dataCodingScheme & 0xff));
}
} else {
Log.w(LOG_TAG, "3 - Unsupported SMS data coding scheme "
+ (dataCodingScheme & 0xff));
} // set both the user data and the user data header.
int count = p.constructUserData(hasUserDataHeader,
encodingType == ENCODING_7BIT);
this.userData = p.getUserData();
this.userDataHeader = p.getUserDataHeader(); switch (encodingType) {
case ENCODING_UNKNOWN:
case ENCODING_8BIT:
messageBody = null;
break; case ENCODING_7BIT:
messageBody = p.getUserDataGSM7Bit(count);
break; ↑ // Convert a GSM alphabet 7 bit packed string (SMS string) into a java.lang.String. See TS 23.038 6.1.2.1 for SMS Character Packing case ENCODING_16BIT:
messageBody = p.getUserDataUCS2(count);
break;
} if (Config.LOGV) Log.v(LOG_TAG, "SMS message body (raw): '" + messageBody + "'"); if (messageBody != null) {
parseMessageBody();
} if (!hasMessageClass) {
messageClass = MessageClass.UNKNOWN;
} else {
switch (dataCodingScheme & 0x3) {
case 0:
messageClass = MessageClass.CLASS_0;
break;
case 1:
messageClass = MessageClass.CLASS_1;
break;
case 2:
messageClass = MessageClass.CLASS_2;
break;
case 3:
messageClass = MessageClass.CLASS_3;
break;
}
}
} |
parseMessageBody()
/open_src/Frameworks/Base/Telephony/Gsm/SmsMessage.java |
private void parseMessageBody() { // ↓ return address.length() <= 4;
if (originatingAddress.couldBeEmailGateway()) {
extractEmailAddressFromMessageBody();
}
} |
extractEmailAddressFromMessageBody();
/open_src/Frameworks/Base/Telephony/Gsm/SmsMessage.java |
/**
* Try to parse this message as an email gateway message -> Neither
* of the standard ways are currently supported: There are two ways
* specified in TS 23.040 Section 3.8 (not supported via this mechanism) -
* SMS message "may have its TP-PID set for internet electronic mail - MT
* SMS format: [<from-address><space>]<message> - "Depending on the
* nature of the gateway, the destination/origination address is either
* derived from the content of the SMS TP-OA or TP-DA field, or the
* TP-OA/TP-DA field contains a generic gateway address and the to/from
* address is added at the beginning as shown above." - multiple addreses
* separated by commas, no spaces - subject field delimited by '()' or '##'
* and '#' Section 9.2.3.24.11
*/
private void extractEmailAddressFromMessageBody() { /*
* a little guesswork here. I haven't found doc for this.
* the format could be either
*
* 1. [x@y][ ]/[subject][ ]/[body]
* -or-
* 2. [x@y][ ]/[body]
*/
int slash = 0, slash2 = 0, atSymbol = 0; try {
slash = messageBody.indexOf(" /");
if (slash == -1) {
return;
} atSymbol = messageBody.indexOf('@');
if (atSymbol == -1 || atSymbol > slash) {
return;
} emailFrom = messageBody.substring(0, slash); slash2 = messageBody.indexOf(" /", slash + 2); if (slash2 == -1) {
pseudoSubject = null;
emailBody = messageBody.substring(slash + 2);
} else {
pseudoSubject = messageBody.substring(slash + 2, slash2);
emailBody = messageBody.substring(slash2 + 2);
} isEmail = true;
} catch (Exception ex) {
Log.w(LOG_TAG,
"extractEmailAddressFromMessageBody: exception slash="
+ slash + ", atSymbol=" + atSymbol + ", slash2="
+ slash2, ex);
}
} |
여기까지 parse 작업이 완료되면,
SmsMessage.newFromCMT(a) {} 부분의 return msg 부분이 실행되어processUnsolicited (Parcel P) {} 부분의 밑부분의 if문이 실행된다.
/open_src/frameworks/base/telephony/java/com/android/internal/telephony/gsm/RIL.java |
private void processUnsolicited (Parcel p)
{
...
if (mSMSRegistrant != null) {
mSMSRegistrant.notifyRegistrant(new AsyncResult(null, sms, null)); /* mSMSRegistrant 는 Registrant의 인스턴스, 정의되어있는 곳은 RIL이 상속하고 있는 * BaseCommands 이다. 인스턴스화된 곳을 찾아 올라가면 * BaseCommands의 메소드인 setOnNewSMS()에서 생성됨을 알수 있고, setOnNewSMS()를 * 호출한 곳을 역으로 올라가면, * setOnNewSMS() ⇠ SMSDispatcher ⇠ GSMPhone * ⇠ PhoneFactory.makeDefaultPhones() ⇠ PhoneApp.onCreate() * PhoneApp 는 system의 server thread가 생성하였다. */ ...
} |
mSMSRegistrant.notifyRegistrant(new AsyncResult(null, sms, null));
/open_src/frameworks/base/Core/Java/Os/Registrant.java |
/**
* This makes a copy of @param ar
*/ public void notifyRegistrant(AsyncResult ar)
{
internalNotifyRegistrant (ar.result, ar.exception);
} |
internalNotifyRegistrant(ar.result, ar.exception);
/open_src/frameworks/base/Core/Java/Os/Registrant.java |
/*package*/ void internalNotifyRegistrant (Object result, Throwable exception)
{
Handler h = getHandler(); if (h == null) {
clear();
} else { // ↓ Return a new Message instance from the global pool.
Message msg = Message.obtain(); msg.what = what;
msg.obj = new AsyncResult(userObj, result, exception);
h.sendMessage(msg);
}
} |
h.sendMesage(msg)
/open_src/frameworks/base/Core/Java/Android/Os/Handler.java |
/**
* Pushes a message onto the end of the message queue after all pending messages
* before the current time. It will be received in {@link #handleMessage},
* in the thread attached to this handler.
* / public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
} |
sendMessageDelayed(msg, 0)
/open_src/frameworks/base/Core/Java/Android/Os/Handler.java |
/**
* Enqueue a message into the message queue after all pending messages
* before (current time + delayMillis). You will receive it in
* {@link #handleMessage}, in the thread attached to this handler.
* / public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
} |
sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
/open_src/frameworks/base/Core/Java/Android/Os/Handler.java |
/**
* Enqueue a message into the message queue after all pending messages
* before the absolute time (in milliseconds) <var>uptimeMillis</var>.
* <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
* You will receive it in {@link #handleMessage}, in the thread attached
* to this handler.
*/ public boolean sendMessageAtTime(Message msg, long uptimeMillis)
{
boolean sent = false;
MessageQueue queue = mQueue;
if (queue != null) {
msg.target = this;
sent = queue.enqueueMessage(msg, uptimeMillis);
/* 메시지 전송 delay를 설정해주고, 메시지 큐에 넣는다.
* 메시지큐에 메시지가 들어오면 시스템에서 handleMassage()를 실행한다.
* handleMessage()는 Handler를 상속한 SMSDiapatcher에서 오버라이딩된
* handleMessage()가 실행된다.
* delay설정은 시스템의 스케줄링에 도움을 주기 위해서 이다.
}
else {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
}
return sent;
} |
handelMessage(msg)
/open_src/frameworks/Base/Telephony/Java/Com/Android/Internal/Telephony/Gsm/ SMSDispatcher.java |
/**
* Handles events coming from the phone stack. Overridden from handler.
*
* @param msg the message to handle
*/ @Override
public void handleMessage(Message msg) {
AsyncResult ar; switch (msg.what) {
case EVENT_NEW_SMS:
// A new SMS has been received by the device
if (Config.LOGD) {
Log.d(TAG, "New SMS Message Received");
// ↑ D/GSM ( 610): New SMS Message Received } // 로그 프린트 SmsMessage sms; ar = (AsyncResult) msg.obj; // FIXME unit test leaves cm == null. this should change
if (mCm != null) {
// FIXME only acknowledge on store
mCm.acknowledgeLastIncomingSMS(true, null);
} if (ar.exception != null) {
Log.e(TAG, "Exception processing incoming SMS. Exception:" + ar.exception);
return;
} sms = (SmsMessage) ar.result;
dispatchMessage(sms); break; case EVENT_SEND_SMS_COMPLETE:
// An outbound SMS has been sucessfully transferred, or failed.
handleSendComplete((AsyncResult) msg.obj);
break; case EVENT_SEND_RETRY:
sendSms((SmsTracker) msg.obj);
break; case EVENT_NEW_SMS_STATUS_REPORT:
handleStatusReport((AsyncResult)msg.obj);
break; case EVENT_SIM_FULL:
handleSimFull();
break; case EVENT_POST_ALERT:
handleReachSentLimit((SmsTracker)(msg.obj));
break; case EVENT_ALERT_TIMEOUT:
((AlertDialog)(msg.obj)).dismiss();
msg.obj = null;
mSTracker = null;
break; case EVENT_SEND_CONFIRMED_SMS:
if (mSTracker!=null) {
if (isMultipartTracker(mSTracker)) {
sendMultipartSms(mSTracker);
} else {
sendSms(mSTracker);
}
mSTracker = null;
}
break;
}
} |
dispatchMessage(msg)
/open_src/frameworks/Base/Telephony/Java/Com/Android/Internal/Telephony/Gsm/ SMSDispatcher.java |
/**
* Dispatches an incoming SMS messages.
*
* @param sms the incoming message from the phone
*/
/* package */ void dispatchMessage(SmsMessage sms) {
// If sms is null, means there was a parsing error.
// TODO: Should NAK this.
if (sms == null) {
return;
} boolean handled = false; // Special case the message waiting indicator messages
if (sms.isMWISetMessage()) {
mPhone.updateMessageWaitingIndicator(true); if (sms.isMwiDontStore()) {
handled = true;
} if (Config.LOGD) {
Log.d(TAG,
"Received voice mail indicator set SMS shouldStore="
+ !handled);
}
} else if (sms.isMWIClearMessage()) {
mPhone.updateMessageWaitingIndicator(false); if (sms.isMwiDontStore()) {
handled = true;
} if (Config.LOGD) {
Log.d(TAG,
"Received voice mail indicator clear SMS shouldStore="
+ !handled);
}
} if (handled) {
return;
} // Parse the headers to see if this is partial, or port addressed
int referenceNumber = -1;
int count = 0;
int sequence = 0;
int destPort = -1; SmsHeader header = sms.getUserDataHeader();
if (header != null) {
for (SmsHeader.Element element : header.getElements()) {
try {
switch (element.getID()) {
case SmsHeader.CONCATENATED_8_BIT_REFERENCE: {
byte[] data = element.getData();
referenceNumber = data[0] & 0xff;
count = data[1] & 0xff;
sequence = data[2] & 0xff;
// Per TS 23.040, 9.2.3.24.1: If the count is zero, sequence
// is zero, or sequence > count, ignore the entire element
if (count == 0 || sequence == 0 || sequence > count) {
referenceNumber = -1;
}
break;
}
case SmsHeader.CONCATENATED_16_BIT_REFERENCE: {
byte[] data = element.getData();
referenceNumber = (data[0] & 0xff) * 256 + (data[1] & 0xff);
count = data[2] & 0xff;
sequence = data[3] & 0xff;
// Per TS 23.040, 9.2.3.24.8: If the count is zero, sequence
// is zero, or sequence > count, ignore the entire element
if (count == 0 || sequence == 0 || sequence > count) {
referenceNumber = -1;
}
break;
}
case SmsHeader.APPLICATION_PORT_ADDRESSING_16_BIT: {
byte[] data = element.getData();
destPort = (data[0] & 0xff) << 8;
destPort |= (data[1] & 0xff);
break;
}
}
} catch (ArrayIndexOutOfBoundsException e) {
Log.e(TAG, "Bad element in header", e);
return; // TODO: NACK the message or something, don't just discard.
}
}
} if (referenceNumber == -1) {
// notify everyone of the message if it isn't partial
byte[][] pdus = new byte[1][];
pdus[0] = sms.getPdu(); if (destPort != -1) {
if (destPort == SmsHeader.PORT_WAP_PUSH) {
mWapPush.dispatchWapPdu(sms.getUserData());
}
// The message was sent to a port, so concoct a URI for it
dispatchPortAddressedPdus(pdus, destPort);
} else {
// It's a normal message, dispatch it
dispatchPdus(pdus);
}
} else {
// Process the message part
processMessagePart(sms, referenceNumber, sequence, count, destPort);
}
} |
dispatchPdus(pdus)
/open_src/frameworks/Base/Telephony/Java/Com/Android/Internal/Telephony/Gsm/ SMSDispatcher.java |
/**
* Dispatches standard PDUs to interested applications
*
* @param pdus The raw PDUs making up the message
*/ private void dispatchPdus(byte[][] pdus) {
Intent intent = new Intent(Intents.SMS_RECEIVED_ACTION);
intent.putExtra("pdus", pdus);
sendBroadcast(intent, "android.permission.RECEIVE_SMS");
} |
sendBroadcast(intent, "android.permission.RECEIVE_SMS");
/open_src/frameworks/Base/Telephony/Java/Com/Android/Internal/Telephony/Gsm/ SMSDispatcher.java |
private void sendBroadcast(Intent intent, String permission) {
// Hold a wake lock for WAKE_LOCK_TIMEOUT seconds, enough to give any
// receivers time to take their own wake locks.
mWakeLock.acquire(WAKE_LOCK_TIMEOUT);
/* 메소드를 타고 들어가면, delay 주고, 핸들러의 메시지큐에 메시지를 넣는다.
* acquire ⇠ mHandler.postDelayed() ⇠ sendMessageDelayed()
* ⇠ sendMessageAtTime ⇠ enqueueMessage()
*/
// 메시지큐에 메시지가 왔음으로 handleMessage()가 실행된다.
mContext.sendBroadcast(intent, permission);
// BroadcastReceiver 실행된다.
} |
handleMessage(Message msg)
/open_src/Packages/Apps/Src/Com/Android/Mms/Transactions/SmsReceiverService.java |
/**
* Handle incoming transaction requests.
* The incoming requests are initiated by the MMSC Server or by the
* MMS Client itself.
*/
@Override
public void handleMessage(Message msg) {
if (Log.isLoggable(MmsApp.LOG_TAG, Log.VERBOSE)) {
Log.v(TAG, "Handling incoming message: " + msg);
}
int serviceId = msg.arg1;
Intent intent = (Intent)msg.obj; String action = intent.getAction(); if (MESSAGE_SENT_ACTION.equals(intent.getAction())) {
handleSmsSent(intent);
} else if (SMS_RECEIVED_ACTION.equals(action)) {
handleSmsReceived(intent);
} else if (ACTION_BOOT_COMPLETED.equals(action)) {
handleBootCompleted();
} else if (TelephonyIntents.ACTION_SERVICE_STATE_CHANGED.equals(action)) {
handleServiceStateChanged(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);
} |
handleSmsReceived(intent)
MessagingNotification.updateNewMessageIndicator(this, true)
/open_src/Packages/Apps/Src/Com/Android/Mms/Transactions/SmsReceiverService.java |
private void handleSmsReceived(Intent intent) {
SmsMessage[] msgs = Intents.getMessagesFromIntent(intent);
Uri messageUri = insertMessage(this, msgs); if (messageUri != null) {
MessagingNotification.updateNewMessageIndicator(this, true);
}
} |
MessagingNotification.updateNewMessageIndicator(this, true)
Open_src/Package/Apps/Mms/Src/Com/Android/Mms/Transaction/MessagingNotification.java |
/**
* Checks to see if there are any unread messages or delivery
* reports. Shows the most recent notification if there is one.
*
* @param context the context to use
* @param isNew if notify a new message comes, it should be true, otherwise, false.
*/
public static void updateNewMessageIndicator(Context context, boolean isNew) {
SortedSet<MmsSmsNotificationInfo> accumulator =
new TreeSet<MmsSmsNotificationInfo>(INFO_COMPARATOR);
Set<Long> threads = new HashSet<Long>(4);
int count = 0;
count += accumulateNotificationInfo(
accumulator, getMmsNewMessageNotificationInfo(context, threads));
count += accumulateNotificationInfo(
accumulator, getSmsNewMessageNotificationInfo(context, threads)); cancelNotification(context, NOTIFICATION_ID);
if (!accumulator.isEmpty()) {
accumulator.first().deliver(context, isNew, count, threads.size());
}
} |
accumulator.first().deliver(context, isNew, count, threads.size())
Open_src/Package/Apps/Mms/Src/Com/Android/Mms/Transaction/MessagingNotification.java |
public void deliver(Context context, boolean isNew, int count, int uniqueThreads) {
updateNotification(
context, mClickIntent, mDescription, mIconResourceId,
isNew, mTicker, mTimeMillis, mTitle, count, uniqueThreads);
} |
updateNotification(...)
Open_src/Package/Apps/Mms/Src/Com/Android/Mms/Transaction/MessagingNotification.java |
private static void updateNotification(
Context context,
Intent clickIntent,
String description,
int iconRes,
boolean isNew,
CharSequence ticker,
long timeMillis,
String title,
int messageCount,
int uniqueThreadCount) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); if (!sp.getBoolean(
MessagingPreferenceActivity.NOTIFICATION_ENABLED, true)) {
return;
} Notification notification = new Notification(iconRes, ticker, timeMillis); // If we have more than one unique thread, change the title (which would
// normally be the contact who sent the message) to a generic one that
// makes sense for multiple senders, and change the Intent to take the
// user to the conversation list instead of the specific thread.
if (uniqueThreadCount > 1) {
title = context.getString(R.string.notification_multiple_title);
clickIntent = getAppIntent();
clickIntent.setAction(Intent.ACTION_MAIN);
clickIntent.setType("vnd.android-dir/mms-sms");
}
// If there is more than one message, change the description (which
// would normally be a snippet of the individual message text) to
// a string indicating how many unread messages there are.
if (messageCount > 1) {
description = context.getString(R.string.notification_multiple,
Integer.toString(messageCount));
} // Make a startActivity() PendingIntent for the notification.
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, clickIntent,
PendingIntent.FLAG_UPDATE_CURRENT); // Update the notification.
notification.setLatestEventInfo(context, title, description, pendingIntent); if (isNew) {
boolean vibrate =
sp.getBoolean(MessagingPreferenceActivity.NOTIFICATION_VIBRATE, true);
if (vibrate) {
notification.defaults |= Notification.DEFAULT_VIBRATE;
} String ringtoneStr = sp
.getString(MessagingPreferenceActivity.NOTIFICATION_RINGTONE, null);
notification.sound = TextUtils.isEmpty(ringtoneStr) ? null : Uri.parse(ringtoneStr);
} notification.flags |= Notification.FLAG_SHOW_LIGHTS;
notification.ledARGB = 0xff00ff00;
notification.ledOnMS = 500;
notification.ledOffMS = 2000; NotificationManager nm = (NotificationManager)
context.getSystemService(Context.NOTIFICATION_SERVICE); nm.notify(NOTIFICATION_ID, notification);
} |
'old > sms&mms' 카테고리의 다른 글
Android's Radio Interface Layer(RIL) (0) | 2010.07.08 |
---|---|
Complete End to End Call Flow of Short Message Service(SMS) (0) | 2010.07.08 |
기본 안드로이드 서비스 구조 (0) | 2010.05.12 |
SMS framework 소스 참고 (0) | 2010.05.10 |
framework_source 참고 (0) | 2010.05.10 |