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);
        }
    }



        
createNewMesage()
/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();
    }
}    

   
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);
        }        
}

    
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: [&lt;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)
/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);
}

Posted by jazzlife
,