彩信的接收简介

主要是由应用程序负责从彩信服务中心(MMSC Multimedia Messaging Service Center)下载彩信信息。大致的流程是Frameworks会先发出一条短信,告知应用程序有一个彩信,短信中含有一些信息比如过期日期,发送者手机号码,彩信的URL等,然后应用程序自行通过HTTP取回URL所指的彩信内容。具体的流程为:

Telephony Frameworks会先发出一个Intent:android.provider.Telephony.Intents.WAP_PUSH_RECEIVED_ACTION=”android.provider.Telephony.WAP_PUSH_RECEIVED”告知上层应用有一个彩信来了。这个Intent中会含有一个”data”byte数组(通过byte[] data = intent.getByteArrayExtra(“data”)来获取),这个Byte数组是关于这个彩信的一些信息的描述,它是一个NotificationInd,里面含有彩信的一些信息,比如发送者手机号码,彩信的ContentLocation(URL)。之后是由应用程序来决定如何做下一步的处理。

在Mms中是由transaction.PushReceiver.java来接收WAP_PUSH_RECEIVED_ACTION,接收到彩信通知Intent后,它会做一些预处理,把data字段取出来,用Pdu的工具解析成为GenericPdu,然后转化为NotificationInd,并把它写入数据库,然后会启动TransactionService来做进一步的NOTIFICATION_TRANSACTION处理,同时把这个NotificationInd的Uri也传过去。

TransactionService被唤起,在其onStartCommand中会处理一下把PushReceiver所传来的Intent放入自己的MessageQueue中,然后在Handler.handleMessage()中处理TRANSACTION_REQUEST时处理NOTIFICATION_TRANSACTION。先是加载默认的一些彩信相关的配置信息,主要是MMSC,Proxy和Port,这些都是与运营商相关的信息,可以通过APN的设置来更改。TransactionService用PushReciver传来的NotificationInd的Uri和加载的配置信息TransactionSettings构建一个NotificationTransaction对象。之后,TransactionService检查其内的二个队列,或是加入Pending队列,或是直接处理(加入到正在处理队列),处理也是直接调用NotificationTransaction.process()。

NotificationTransaction的process()方法是继承自父类Transaction的方法,它只是简单的开启一个新的线程,然后返回,这样就可以让Service去处理其他的Transaction Request了。

在线程中,首先从DownloadManager和TelephonyManager中加载一些配置信息,是否彩信设置为自动获取(auto retrieve),以及Telephony是否设置为数据延迟(DATA_SUSPEND),然后会采取不同的措施,再从NotificationInd中取出彩信的过期日期。如果配置为不取数据(更确切的说,是不现在取数据),那么就先给DownloadManager的状态标记为STATE_UNSTARTED,再给MMSC发送一个Notify
Response Indication,之后结束处理,函数返回,彩信的通知处理流程到此为止。用户可以通过操作UI,用其他方法手动下载彩信,这个会在后面详细讨论。

如果设置为自动获取或者数据传输是畅通的,那么就把DownloadManager状态标记为START_DOWNLOADING并开始下载彩信数据。彩信的获取是通过HTTP到彩信的ContentLocation(URL)取得数据。先是调用父类方法getPdu(),传入彩信的URL,最终调用HttpUtils的httpConnection方法发送HTTP GET请求,MMSC会把彩信数据返回,作为getPdu()的返回值返回。拿到的是一个byte数组,需要用Pdu的工具解析成为GenericPdu,然后用PduPersister把其写入数据库,再把彩信的大小更新到数据库,到这里一个彩信的接收就算完成了。剩下的就是,因为已经获得了彩信的数据,所以要把先前的通知信息(NotificationInd)删除掉,然后更新一下相关的状态,给MMSC返回Notify
Response Indication,结束处理。

如前所述,如果彩信配置设置为不自动获取,那么UI刷新了后就会显示彩信通知:到期日期,彩信大小等,并提供一个”Download”按扭。用户可以点击按扭来下载彩信内容,点击按扭后,会启动TransactionService,把彩信通知的Uri,和RETRIEVE_TRANSACTION request打包进一个Intent传给TransactionService。TransactionService,像处理其他的Transaction一样,都是放进自己的MessageQueue,然后加载默认的TransactionSettings,构建RetrieveTransaction对象,然后处理调用RetrieveTransaction.process()。

RetrieveTransaction也是继承自Transaction,其process()也是创建一个线程,然后返回。在线程中,首先它用Pdu工具根据Uri从数据库中加载出彩信通知(NotificationInd),从NotificationInd中取得彩信的过期日期,检查过期日期,如果彩信已经过期,那么给MMSC发送Notify Response Indication。把DownloadManager状态标记为开始下载,然后如果彩信已过期,标记Transaction状态为Failed,然后返回,结束处理流程。如果一切正常,会用getPdu()从彩信的ContentLocation(URL)上面获取彩信内容,它会用HttpUtils.httpConnection()通过HTTP来获取,返回一个byte数组。用Pdu工具解析byte数组,得到GenericPdu,检查一下是否是新信息,是否是重复的信息,如果重复,标记状为失败,然后返回,结束处理。如果是新信息,先把GenericPdu用PduPersister写入数据库中,更新信息大小和ContentLocation(URL)到数据库中,到这里一个彩信其实已经全部获取完了。接下来就是发送收到确认信息给MMSC,标记处理状态为成功,结束处理。这时UI应该监听到数据库变化,并刷新,新信息应该会显示给用户。

在分析代码之前,也是首先与大家分享一下在网络上很流行的两张顺序图,本人也受到了很大的启发。

android的彩信接收应用层部分从PushReceiver类开始。当onReceive被调用后,让屏幕亮5秒( wl.acquire(5000);),然后创建一个ReceivePushTask并使用它的execute方法。ReceivePushTask(内部类)是一个AsyncTask,实现了doInBackground()方法。根据消息类型做出相应的处理。

调用PushReceiver.java类中的doInBackground()方法,部分代码如下:《TAG 2-1》

case MESSAGE_TYPE_NOTIFICATION_IND: {

                        NotificationInd nInd = (NotificationInd) pdu;



                        if (MmsConfig.getTransIdEnabled()) {

                            byte [] contentLocation = nInd.getContentLocation();

                            if ('=' == contentLocation[contentLocation.length - 1]) {

                                byte [] transactionId = nInd.getTransactionId();

                                byte [] contentLocationWithId = new byte [contentLocation.length

                                                                          + transactionId.length];

                                System.arraycopy(contentLocation, 0, contentLocationWithId,

                                        0, contentLocation.length);

                                System.arraycopy(transactionId, 0, contentLocationWithId,

                                        contentLocation.length, transactionId.length);

                                nInd.setContentLocation(contentLocationWithId);

                            }

                        }



                        if (!isDuplicateNotification(mContext, nInd)) {

                            int subId = intent.getIntExtra(MSimConstants.SUBSCRIPTION_KEY, 0);

                            ContentValues values = new ContentValues(1);

                            values.put(Mms.SUB_ID, subId);



                            Uri uri = p.persist(pdu, Inbox.CONTENT_URI,

                                    true,

                                    MessagingPreferenceActivity.getIsGroupMmsEnabled(mContext),

                                    null);



                            SqliteWrapper.update(mContext, cr, uri, values, null, null);

                            if (MessageUtils.isMobileDataDisabled(mContext) &&

                                    !MessageUtils.CAN_SETUP_MMS_DATA) {

                                MessagingNotification.nonBlockingUpdateNewMessageIndicator(mContext,

                                        MessagingNotification.THREAD_ALL, false);

                            }

                            // Start service to finish the notification transaction.

                            Intent svc = new Intent(mContext, TransactionService.class);

                            svc.putExtra(TransactionBundle.URI, uri.toString());

                            svc.putExtra(TransactionBundle.TRANSACTION_TYPE,

                                    Transaction.NOTIFICATION_TRANSACTION);

                            svc.putExtra(Mms.SUB_ID, subId); //destination sub id

                            svc.putExtra(MultiSimUtility.ORIGIN_SUB_ID,

                                    MultiSimUtility.getCurrentDataSubscription(mContext));



                            if (MSimTelephonyManager.getDefault().isMultiSimEnabled()) {

                                boolean isSilent = true; //default, silent enabled.

                                if ("prompt".equals(

                                    SystemProperties.get(

                                        TelephonyProperties.PROPERTY_MMS_TRANSACTION))) {

                                    isSilent = false;

                                }



                                if (isSilent) {

                                    Log.d(TAG, "MMS silent transaction");

                                    Intent silentIntent = new Intent(mContext,

                                            com.android.mms.ui.SelectMmsSubscription.class);

                                    silentIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

                                    silentIntent.putExtras(svc); //copy all extras

                                    mContext.startService(silentIntent);



                                } else {

                                    Log.d(TAG, "MMS prompt transaction");

                                    triggerPendingOperation(svc, subId);

                                }

                            } else {

                                mContext.startService(svc);

                            }





                        } else if (LOCAL_LOGV) {

                            Log.v(TAG, "Skip downloading duplicate message: "

                                    + new String(nInd.getContentLocation()));

                        }

                        break;

                    }

doInBackground中将其中的数据转成GenericPdu,并根据其消息类型做出不同的操作。如果是发送报告或已读报告,将其存入数据库。如果是彩信通知,若已存在,则不处理。否则将其存入数据库。启动TransactionService进行处理。TransactionService中的处理主要是调用mServiceHandler,大体过程与发送彩信时相同,只是此处创建的是NotificationTransaction。如果不支持自动下载或数据传输没打开,仅通知mmsc。否则,下载相应彩信,删除彩信通知,通知mmsc,删除超过容量限制的彩信,通知TransactionService处理其余待发送的彩信。

我们接着进入TransactionService.java类中进行分析,启动服务后,在onStartCommand()方法中接收Intent并进行封装并放入自己的MessageQueue中,在Handler的handleMessgae中进行处理:

@Override

    public int onStartCommand(Intent intent, int flags, int startId) {

        if (intent != null) {

            Log.d(TAG, "onStartCommand(): E");

            incRefCount();



            Message msg = mServiceHandler.obtainMessage(EVENT_NEW_INTENT);

            msg.arg1 = startId;

            msg.obj = intent;

            mServiceHandler.sendMessage(msg);

        }

        return Service.START_NOT_STICKY;

    }

首先调用handleMessage()方法;

@Override

        public void handleMessage(Message msg) {

            Log.d(TAG, "Handling incoming message: " + msg + " = " + decodeMessage(msg));



            Transaction transaction = null;



            switch (msg.what) {

                case EVENT_NEW_INTENT:

                    onNewIntent((Intent)msg.obj, msg.arg1);

                    break;



接着调用onNewIntent()方法;

public void onNewIntent(Intent intent, int serviceId) {



        mConnMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);

        /*AddBy:yabin.huang BugID:SWBUG00029243 Date:20140515*/

        if (mConnMgr == null) {

            endMmsConnectivity();

            decRefCount();

            return ;

        }

        NetworkInfo ni = mConnMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_MMS);

        boolean noNetwork = ni == null || !ni.isAvailable();



        Log.d(TAG, "onNewIntent: serviceId: " + serviceId + ": " + intent.getExtras() +

                " intent=" + intent);

        Log.d(TAG, "    networkAvailable=" + !noNetwork);



        Bundle extras = intent.getExtras();

        String action = intent.getAction();//这里的action的值为null

        if ((ACTION_ONALARM.equals(action) || ACTION_ENABLE_AUTO_RETRIEVE.equals(action) ||

                (extras == null)) || ((extras != null) && !extras.containsKey("uri")

                && !extras.containsKey(CANCEL_URI))) {



            //We hit here when either the Retrymanager triggered us or there is

            //send operation in which case uri is not set. For rest of the

            //cases(MT MMS) we hit "else" case.



            // Scan database to find all pending operations.

            Cursor cursor = PduPersister.getPduPersister(this).getPendingMessages(

                    System.currentTimeMillis());

            Log.d(TAG, "Cursor= "+DatabaseUtils.dumpCursorToString(cursor));

            if (cursor != null) {

                try {

                    int count = cursor.getCount();



                    //if more than 1 records are present in DB.

                    if (count > 1) {

                        incRefCountN(count-1);

                        Log.d(TAG, "onNewIntent() multiple pending items mRef=" + mRef);

                    }



                    Log.d(TAG, "onNewIntent: cursor.count=" + count + " action=" + action);



                    if (count == 0) {

                        Log.d(TAG, "onNewIntent: no pending messages. Stopping service.");

                        RetryScheduler.setRetryAlarm(this);

                        cleanUpIfIdle(serviceId);

                        decRefCount();

                        return;

                    }



                    int columnIndexOfMsgId = cursor.getColumnIndexOrThrow(PendingMessages.MSG_ID);

                    int columnIndexOfMsgType = cursor.getColumnIndexOrThrow(

                            PendingMessages.MSG_TYPE);



                    while (cursor.moveToNext()) {

                        int msgType = cursor.getInt(columnIndexOfMsgType);

                        int transactionType = getTransactionType(msgType);

                        Log.d(TAG, "onNewIntent: msgType=" + msgType + " transactionType=" +

                                    transactionType);

                        if (noNetwork) {

                            onNetworkUnavailable(serviceId, transactionType);

                            Log.d(TAG, "No network during MO or retry operation");

                            decRefCountN(count);

                            Log.d(TAG, "Reverted mRef to =" + mRef);

                            return;

                        }

                        switch (transactionType) {

                            case -1:

                                decRefCount();

                                break;

                            case Transaction.RETRIEVE_TRANSACTION:

                                // If it's a transiently failed transaction,

                                // we should retry it in spite of current

                                // downloading mode. If the user just turned on the auto-retrieve

                                // option, we also retry those messages that don't have any errors.

                                int failureType = cursor.getInt(

                                        cursor.getColumnIndexOrThrow(

                                                PendingMessages.ERROR_TYPE));

                                DownloadManager downloadManager = DownloadManager.getInstance();

                                boolean autoDownload = downloadManager.isAuto();

                                boolean isMobileDataEnabled = mConnMgr.getMobileDataEnabled();

                                if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {

                                    Log.v(TAG, "onNewIntent: failureType=" + failureType +

                                            " action=" + action + " isTransientFailure:" +

                                            isTransientFailure(failureType) + " autoDownload=" +

                                            autoDownload);

                                }

                                if (!autoDownload || MessageUtils.isMmsMemoryFull()

                                        || !isMobileDataEnabled) {

                                    // If autodownload is turned off, don't process the

                                    // transaction.

                                    Log.d(TAG, "onNewIntent: skipping - autodownload off");

                                    decRefCount();

                                    break;

                                }

                                // Logic is twisty. If there's no failure or the failure

                                // is a non-permanent failure, we want to process the transaction.

                                // Otherwise, break out and skip processing this transaction.

                                if (!(failureType == MmsSms.NO_ERROR ||

                                        isTransientFailure(failureType))) {

                                    if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {

                                        Log.v(TAG, "onNewIntent: skipping - permanent error");

                                    }

                                    decRefCount();

                                    break;

                                }

                                if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {

                                    Log.v(TAG, "onNewIntent: falling through and processing");

                                }

                               // fall-through

                            default:

                                Uri uri = ContentUris.withAppendedId(

                                        Mms.CONTENT_URI,

                                        cursor.getLong(columnIndexOfMsgId));



                                String txnId = getTxnIdFromDb(uri);

                                int subId = getSubIdFromDb(uri);

                                Log.d(TAG, "SubId from DB= "+subId);



                                if(subId != MultiSimUtility.getCurrentDataSubscription

                                        (getApplicationContext())) {

                                    Log.d(TAG, "This MMS transaction can not be done"+

                                         "on current sub. Ignore it. uri="+uri);

                                    decRefCount();

                                    break;

                                }



                                int destSub = intent.getIntExtra(Mms.SUB_ID, -1);

                                int originSub = intent.getIntExtra(

                                        MultiSimUtility.ORIGIN_SUB_ID, -1);



                                Log.d(TAG, "Destination Sub = "+destSub);

                                Log.d(TAG, "Origin Sub = "+originSub);



                                addUnique(txnId, destSub, originSub);



                                TransactionBundle args = new TransactionBundle(

                                        transactionType, uri.toString());

                                if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {

                                    Log.v(TAG, "onNewIntent: launchTransaction uri=" + uri);

                                }

                                // FIXME: We use the same serviceId for all MMs.

                                launchTransaction(serviceId, args, false);

                                break;

                        }

                    }

                } finally {

                    cursor.close();

                }

            } else {

                Log.d(TAG, "onNewIntent: no pending messages. Stopping service.");

                RetryScheduler.setRetryAlarm(this);

                cleanUpIfIdle(serviceId);

                decRefCount();

            }

        } else if ((extras != null) && extras.containsKey(CANCEL_URI)) {

            String uriStr = intent.getStringExtra(CANCEL_URI);

            Uri mCancelUri = Uri.parse(uriStr);

            for (Transaction transaction : mProcessing) {

                transaction.cancelTransaction(mCancelUri);

            }

            for (Transaction transaction : mPending) {

                transaction.cancelTransaction(mCancelUri);

            }

        } else {

            if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE) || DEBUG) {

                Log.v(TAG, "onNewIntent: launch transaction...");

            }

            String uriStr = intent.getStringExtra("uri");

            int destSub = intent.getIntExtra(Mms.SUB_ID, -1);

            int originSub = intent.getIntExtra(MultiSimUtility.ORIGIN_SUB_ID, -1);



            Uri uri = Uri.parse(uriStr);

            int subId = getSubIdFromDb(uri);

            String txnId = getTxnIdFromDb(uri);



            if (txnId == null) {

                Log.d(TAG, "Transaction already over.");

                decRefCount();

                return;

            }



            Log.d(TAG, "SubId from DB= "+subId);

            Log.d(TAG, "Destination Sub = "+destSub);

            Log.d(TAG, "Origin Sub = "+originSub);



            if (noNetwork) {

                synchronized (mRef) {

                    Log.e(TAG, "No network during MT operation");

                    decRefCount();

                }

                return;

            }



            addUnique(txnId, destSub, originSub);



            // For launching NotificationTransaction and test purpose.

            TransactionBundle args = new TransactionBundle(intent.getExtras());

            launchTransaction(serviceId, args, noNetwork);


        }

    }



这里调用launchTransaction()方法;

private void launchTransaction(int serviceId, TransactionBundle txnBundle, boolean noNetwork) {

        if (noNetwork) {

            Log.w(TAG, "launchTransaction: no network error!");

            onNetworkUnavailable(serviceId, txnBundle.getTransactionType());

            return;

        }

        Message msg = mServiceHandler.obtainMessage(EVENT_TRANSACTION_REQUEST);

        msg.arg1 = serviceId;

        msg.obj = txnBundle;



        if (Log.isLoggable(LogTag.TRANSACTION, Log.DEBUG)) {

            Log.v(TAG, "launchTransaction: sending message " + msg);

        }

        mServiceHandler.sendMessage(msg);

    }



调用mServiceHandler,根据业务类型创建一个NotificationTransaction对象,如下代码:《TAG 2-2》

                case EVENT_TRANSACTION_REQUEST:

                    int serviceId = msg.arg1;

                    try {

                        TransactionBundle args = (TransactionBundle) msg.obj;

                        TransactionSettings transactionSettings;



                        if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {

                            Log.v(TAG, "EVENT_TRANSACTION_REQUEST MmscUrl=" +

                                    args.getMmscUrl() + " proxy port: " + args.getProxyAddress());

                        }



                        // Set the connection settings for this transaction.

                        // If these have not been set in args, load the default settings.

                        String mmsc = args.getMmscUrl();

                        if (mmsc != null) {

                            transactionSettings = new TransactionSettings(

                                    mmsc, args.getProxyAddress(), args.getProxyPort());

                        } else {

                            transactionSettings = new TransactionSettings(

                                                    TransactionService.this, null);

                        }



                        int transactionType = args.getTransactionType();



                        if (Log.isLoggable(LogTag.TRANSACTION, Log.DEBUG)) {

                            Log.v(TAG, "handle EVENT_TRANSACTION_REQUEST: transactionType=" +

                                    transactionType + " " + decodeTransactionType(transactionType));

                            if (transactionSettings != null) {

                                Log.v(TAG, "mmsc=" + transactionSettings.getMmscUrl()

                                    + ", address=" + transactionSettings.getProxyAddress()

                                    + ", port=" + transactionSettings.getProxyPort());

                            }

                        }



                        // Create appropriate transaction

                        switch (transactionType) {

                            case Transaction.NOTIFICATION_TRANSACTION:

                                String uri = args.getUri();

                                if (uri != null) {

                                    transaction = new NotificationTransaction(

                                            TransactionService.this, serviceId,

                                            transactionSettings, uri);

                                } else {

                                    // Now it's only used for test purpose.

                                    byte[] pushData = args.getPushData();

                                    PduParser parser = new PduParser(pushData);

                                    GenericPdu ind = parser.parse();



                                    int type = PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND;

                                    if ((ind != null) && (ind.getMessageType() == type)) {

                                        transaction = new NotificationTransaction(

                                                TransactionService.this, serviceId,

                                                transactionSettings, (NotificationInd) ind);

                                    } else {

                                        Log.e(TAG, "Invalid PUSH data.");

                                        transaction = null;

                                        return;

                                    }

                                }

                                break;

                            case Transaction.RETRIEVE_TRANSACTION://这里当用户在界面中点击下载,会调用MessageListItem.java方法中的startDownloadAttachment()方法,随后会走这里。

                                transaction = new RetrieveTransaction(

                                        TransactionService.this, serviceId,

                                        transactionSettings, args.getUri());

                                break;


                            case Transaction.SEND_TRANSACTION:

                                transaction = new SendTransaction(

                                        TransactionService.this, serviceId,

                                        transactionSettings, args.getUri());

                                break;

                            case Transaction.READREC_TRANSACTION:

                                transaction = new ReadRecTransaction(

                                        TransactionService.this, serviceId,

                                        transactionSettings, args.getUri());

                                break;

                            default:

                                Log.w(TAG, "Invalid transaction type: " + serviceId);

                                transaction = null;

                                return;

                        }



                        if (!processTransaction(transaction)) {

                            transaction = null;

                            return;

                        }



                        if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE) || DEBUG) {

                            Log.v(TAG, "Started processing of incoming message: " + msg);

                        }

                    } catch (Exception ex) {

                        Log.w(TAG, "Exception occurred while handling message: " + msg, ex);



                        if (transaction != null) {

                            try {

                                transaction.detach(TransactionService.this);

                                if (mProcessing.contains(transaction)) {

                                    synchronized (mProcessing) {

                                        mProcessing.remove(transaction);

                                    }

                                }

                            } catch (Throwable t) {

                                Log.e(TAG, "Unexpected Throwable.", t);

                            } finally {

                                // Set transaction to null to allow stopping the

                                // transaction service.

                                transaction = null;

                            }

                        }

                    } finally {

                        if (transaction == null) {

                            if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {

                                Log.v(TAG, "Transaction was null. Stopping self: " + serviceId);

                            }



                            launchRetryAttempt++;

                            if (launchRetryAttempt <= maxLaunchRetryAttempts) {

                                Log.d(TAG, "launchTransaction retry attempt - "

                                        + launchRetryAttempt);

                                TransactionBundle args = (TransactionBundle) msg.obj;

                                sleep(5*1000);

                                launchTransaction(serviceId, args, false);

                            } else {

                                Log.e(TAG, "Multiple launchTransaction retries failed");

                                launchRetryAttempt = 0;

                                decRefCount();



                            }

                        }

                    }

                    return;

  这里接着调用processTransaction()方法;

*/

        private boolean processTransaction(Transaction transaction) throws IOException {

            // Check if transaction already processing

            synchronized (mProcessing) {

                for (Transaction t : mPending) {

                    if (t.isEquivalent(transaction)) {

                        Log.d(TAG, "Transaction already pending: " +

                                    transaction.getServiceId());

                        decRefCount();

                        return true;

                    }

                }

                for (Transaction t : mProcessing) {

                    if (t.isEquivalent(transaction)) {

                        Log.d(TAG, "Duplicated transaction: " + transaction.getServiceId());

                        decRefCount();

                        return true;

                    }

                }



                /*

                * Make sure that the network connectivity necessary

                * for MMS traffic is enabled. If it is not, we need

                * to defer processing the transaction until

                * connectivity is established.

                */

                Log.d(TAG, "processTransaction: call beginMmsConnectivity...");



                int connectivityResult = beginMmsConnectivity();

                if (connectivityResult == PhoneConstants.APN_REQUEST_STARTED) {

                    mPending.add(transaction);

                    if (Log.isLoggable(LogTag.TRANSACTION, Log.DEBUG)) {

                        Log.v(TAG, "processTransaction: connResult=APN_REQUEST_STARTED, " +

                                "defer transaction pending MMS connectivity");

                    }

                    return true;

                }



                Log.d(TAG, "Adding transaction to 'mProcessing' list: " + transaction);

                mProcessing.add(transaction);

            }



            // Set a timer to keep renewing our "lease" on the MMS connection

            sendMessageDelayed(obtainMessage(EVENT_CONTINUE_MMS_CONNECTIVITY),

                               APN_EXTENSION_WAIT);



            if (Log.isLoggable(LogTag.TRANSACTION, Log.DEBUG) || DEBUG) {

                Log.v(TAG, "processTransaction: starting transaction " + transaction);

            }



            // Attach to transaction and process it

            transaction.attach(TransactionService.this);

            transaction.process();

            return true;

        }

    }

创建调用NotificationTransaction.java类中的Process()方法。

@Override

    public void process() {

        new Thread(this, "NotificationTransaction").start();

    }

调用NotificationTransaction.java类中的run()方法,获得彩信数据(!这里需要注意的是,这里所指的下载是指的自动下载,而如果是点击下载按钮进行下载则调用的是RetrieveTransaction.java中的run()方法,或者一定时间内没有自动下载,也没有去点击下载彩信的按钮,也会走RetrieveTransaction.java中的run()方法)《TAG:2-3》

    public void run() {

        DownloadManager downloadManager = DownloadManager.getInstance();

        boolean autoDownload = allowAutoDownload();

        boolean isMemoryFull = MessageUtils.isMmsMemoryFull();

        boolean isTooLarge = isMmsSizeTooLarge(mNotificationInd);

        boolean isMobileDataDisabled= MessageUtils.isMobileDataDisabled(mContext);

        try {

            if (LOCAL_LOGV) {

                Log.v(TAG, "Notification transaction launched: " + this);

            }



            // By default, we set status to STATUS_DEFERRED because we

            // should response MMSC with STATUS_DEFERRED when we cannot

            // download a MM immediately.

            int status = STATUS_DEFERRED;

            // Don't try to download when data is suspended, as it will fail, so defer download

            if (!autoDownload || isMobileDataDisabled) {

                downloadManager.markState(mUri, DownloadManager.STATE_UNSTARTED);

                sendNotifyRespInd(status);

                return;

            }



            if (isMemoryFull || isTooLarge) {

                downloadManager.markState(mUri, DownloadManager.STATE_TRANSIENT_FAILURE);

                sendNotifyRespInd(status);

                return;

            }



            downloadManager.markState(mUri, DownloadManager.STATE_DOWNLOADING);



            if (LOCAL_LOGV) {

                Log.v(TAG, "Content-Location: " + mContentLocation);

            }



            byte[] retrieveConfData = null;

            // We should catch exceptions here to response MMSC

            // with STATUS_DEFERRED.

            try {

                retrieveConfData = getPdu(mContentLocation);

            } catch (IOException e) {

                mTransactionState.setState(FAILED);

            }



            if (retrieveConfData != null) {

                if (Log.isLoggable(LogTag.TRANSACTION, Log.DEBUG)) {

                    Log.v(TAG, "NotificationTransaction: retrieve data=" +

                            HexDump.dumpHexString(retrieveConfData));

                }

                GenericPdu pdu = new PduParser(retrieveConfData).parse();

                if ((pdu == null) || (pdu.getMessageType() != MESSAGE_TYPE_RETRIEVE_CONF)) {

                    Log.e(TAG, "Invalid M-RETRIEVE.CONF PDU. " +

                            (pdu != null ? "message type: " + pdu.getMessageType() : "null pdu"));

                    mTransactionState.setState(FAILED);

                    status = STATUS_UNRECOGNIZED;

                } else {

                    // Save the received PDU (must be a M-RETRIEVE.CONF).

                    PduPersister p = PduPersister.getPduPersister(mContext);

                    Uri uri = p.persist(pdu, Inbox.CONTENT_URI, true,

                            MessagingPreferenceActivity.getIsGroupMmsEnabled(mContext), null);



                    // Use local time instead of PDU time

                    ContentValues values = new ContentValues(2);

                    values.put(Mms.DATE, System.currentTimeMillis() / 1000L);

                    Cursor c = mContext.getContentResolver().query(mUri,

                            null, null, null, null);

                    if (c != null) {

                        try {

                            if (c.moveToFirst()) {

                                int subId = c.getInt(c.getColumnIndex(Mms.SUB_ID));

                                values.put(Mms.SUB_ID, subId);

                            }

                        } catch (Exception ex) {

                            Log.e(TAG, "Exception:" + ex);

                        } finally {

                            c.close();

                        }

                    }

                    SqliteWrapper.update(mContext, mContext.getContentResolver(),

                            uri, values, null, null);



                    // We have successfully downloaded the new MM. Delete the

                    // M-NotifyResp.ind from Inbox.

                    SqliteWrapper.delete(mContext, mContext.getContentResolver(),

                                         mUri, null, null);

                    Log.v(TAG, "NotificationTransaction received new mms message: " + uri);

                    // Delete obsolete threads

                    SqliteWrapper.delete(mContext, mContext.getContentResolver(),

                            Threads.OBSOLETE_THREADS_URI, null, null);



                    // Notify observers with newly received MM.

                    mUri = uri;

                    status = STATUS_RETRIEVED;

                }

            }



            if (LOCAL_LOGV) {

                Log.v(TAG, "status=0x" + Integer.toHexString(status));

            }



            // Check the status and update the result state of this Transaction.

            switch (status) {

                case STATUS_RETRIEVED:

                    mTransactionState.setState(SUCCESS);

                    break;

                case STATUS_DEFERRED:

                    // STATUS_DEFERRED, may be a failed immediate retrieval.

                    if (mTransactionState.getState() == INITIALIZED) {

                        mTransactionState.setState(SUCCESS);

                    }

                    break;

            }



            sendNotifyRespInd(status);



            // Make sure this thread isn't over the limits in message count.

            Recycler.getMmsRecycler().deleteOldMessagesInSameThreadAsMessage(mContext, mUri);

            MmsWidgetProvider.notifyDatasetChanged(mContext);

        } catch (Throwable t) {

            Log.e(TAG, Log.getStackTraceString(t));

        } finally {

            mTransactionState.setContentUri(mUri);

            if (!autoDownload || isMemoryFull || isTooLarge || isMobileDataDisabled) {

                // Always mark the transaction successful for deferred

                // download since any error here doesn't make sense.

                mTransactionState.setState(SUCCESS);

            }

            if (mTransactionState.getState() != SUCCESS) {

                mTransactionState.setState(FAILED);

                Log.e(TAG, "NotificationTransaction failed.");

            }

            notifyObservers();

        }

    }

上述代码《TAG:2-3》中,调用Transaction.java类中的getPdu()方法下载彩信数据:

protected byte[] getPdu(String url) throws IOException {

        ensureRouteToHost(url, mTransactionSettings);

        return HttpUtils.httpConnection(

                mContext, SendingProgressTokenManager.NO_TOKEN,

                url, null, HttpUtils.HTTP_GET_METHOD,

                mTransactionSettings.isProxySet(),

                mTransactionSettings.getProxyAddress(),

                mTransactionSettings.getProxyPort());

    }

上述代码《TAG:2-3》中,调用PduParser.java类中的parse()方法解析彩信数据,PduParser类是用于把PDU字节流解析成为Android可识别的GenericPdu:《TAG:2-4》

 public PduParser(byte[] pduDataStream) {

        mPduDataStream = new ByteArrayInputStream(pduDataStream);

    }



    /**

     * Parse the pdu.

     *

     * @return the pdu structure if parsing successfully.

     *         null if parsing error happened or mandatory fields are not set.

     */

    public GenericPdu parse(){

          if (mPduDataStream == null) {

            return null;

        }



        /* parse headers */

        mHeaders = parseHeaders(mPduDataStream);

        if (null == mHeaders) {

            // Parse headers failed.

            return null;

        }



        /* get the message type */

        int messageType = mHeaders.getOctet(PduHeaders.MESSAGE_TYPE);



        /* check mandatory header fields */

        if (false == checkMandatoryHeader(mHeaders)) {

            log("check mandatory headers failed!");

            return null;

        }



        if ((PduHeaders.MESSAGE_TYPE_SEND_REQ == messageType) ||

                (PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF == messageType)) {

            /* need to parse the parts */

            mBody = parseParts(mPduDataStream);

            if (null == mBody) {

                // Parse parts failed.

                return null;

            }

        }



        switch (messageType) {

            case PduHeaders.MESSAGE_TYPE_SEND_REQ:

                if (LOCAL_LOGV) {

                    Log.v(LOG_TAG, "parse: MESSAGE_TYPE_SEND_REQ");

                }

                SendReq sendReq = new SendReq(mHeaders, mBody);

                return sendReq;

            case PduHeaders.MESSAGE_TYPE_SEND_CONF:

                if (LOCAL_LOGV) {

                    Log.v(LOG_TAG, "parse: MESSAGE_TYPE_SEND_CONF");

                }

                SendConf sendConf = new SendConf(mHeaders);

                return sendConf;

            case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:

                if (LOCAL_LOGV) {

                    Log.v(LOG_TAG, "parse: MESSAGE_TYPE_NOTIFICATION_IND");

                }

                NotificationInd notificationInd =

                    new NotificationInd(mHeaders);

                return notificationInd;

            case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:

                if (LOCAL_LOGV) {

                    Log.v(LOG_TAG, "parse: MESSAGE_TYPE_NOTIFYRESP_IND");

                }

                NotifyRespInd notifyRespInd =

                    new NotifyRespInd(mHeaders);

                return notifyRespInd;

            case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:

                if (LOCAL_LOGV) {

                    Log.v(LOG_TAG, "parse: MESSAGE_TYPE_RETRIEVE_CONF");

                }

                RetrieveConf retrieveConf =

                    new RetrieveConf(mHeaders, mBody);



                byte[] contentType = retrieveConf.getContentType();

                if (null == contentType) {

                    return null;

                }

                String ctTypeStr = new String(contentType);

                if (ctTypeStr.equals(ContentType.MULTIPART_MIXED)

                        || ctTypeStr.equals(ContentType.MULTIPART_RELATED)

                        || ctTypeStr.equals(ContentType.MULTIPART_ALTERNATIVE)) {

                    // The MMS content type must be "application/vnd.wap.multipart.mixed"

                    // or "application/vnd.wap.multipart.related"

                    // or "application/vnd.wap.multipart.alternative"

                    return retrieveConf;

                } else if (ctTypeStr.equals(ContentType.MULTIPART_ALTERNATIVE)) {

                    // "application/vnd.wap.multipart.alternative"

                    // should take only the first part.

                    PduPart firstPart = mBody.getPart(0);

                    mBody.removeAll();

                    mBody.addPart(0, firstPart);

                    return retrieveConf;

                }

                return null;

            case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:

                if (LOCAL_LOGV) {

                    Log.v(LOG_TAG, "parse: MESSAGE_TYPE_DELIVERY_IND");

                }

                DeliveryInd deliveryInd =

                    new DeliveryInd(mHeaders);

                return deliveryInd;

            case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:

                if (LOCAL_LOGV) {

                    Log.v(LOG_TAG, "parse: MESSAGE_TYPE_ACKNOWLEDGE_IND");

                }

                AcknowledgeInd acknowledgeInd =

                    new AcknowledgeInd(mHeaders);

                return acknowledgeInd;

            case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND:

                if (LOCAL_LOGV) {

                    Log.v(LOG_TAG, "parse: MESSAGE_TYPE_READ_ORIG_IND");

                }

                ReadOrigInd readOrigInd =

                    new ReadOrigInd(mHeaders);

                return readOrigInd;

            case PduHeaders.MESSAGE_TYPE_READ_REC_IND:

                if (LOCAL_LOGV) {

                    Log.v(LOG_TAG, "parse: MESSAGE_TYPE_READ_REC_IND");

                }

                ReadRecInd readRecInd =

                    new ReadRecInd(mHeaders);

                return readRecInd;

            default:

                log("Parser doesn't support this message type in this version!");

            return null;

        }

    }

上述代码中《TAG2-4》中调用parseParts()方法解析pdupart:

protected static PduBody parseParts(ByteArrayInputStream pduDataStream) {

        if (pduDataStream == null) {

            return null;

        }



        int count = parseUnsignedInt(pduDataStream); // get the number of parts

        PduBody body = new PduBody();



        for (int i = 0 ; i < count ; i++) {

            int headerLength = parseUnsignedInt(pduDataStream);

            int dataLength = parseUnsignedInt(pduDataStream);

            PduPart part = new PduPart();

            int startPos = pduDataStream.available();

            if (startPos <= 0) {

                // Invalid part.

                return null;

            }



            /* parse part's content-type */

            HashMap<Integer, Object> map = new HashMap<Integer, Object>();

            byte[] contentType = parseContentType(pduDataStream, map);

            if (null != contentType) {

                part.setContentType(contentType);

            } else {

                part.setContentType((PduContentTypes.contentTypes[0]).getBytes()); //"*/*"

            }



            /* get name parameter */

            byte[] name = (byte[]) map.get(PduPart.P_NAME);

            if (null != name) {

                part.setName(name);

            }



            /* get charset parameter */

            Integer charset = (Integer) map.get(PduPart.P_CHARSET);

            if (null != charset) {

                part.setCharset(charset);

            }



            /* parse part's headers */

            int endPos = pduDataStream.available();

            int partHeaderLen = headerLength - (startPos - endPos);

            if (partHeaderLen > 0) {

                if (false == parsePartHeaders(pduDataStream, part, partHeaderLen)) {

                    // Parse part header faild.

                    return null;

                }

            } else if (partHeaderLen < 0) {

                // Invalid length of content-type.

                return null;

            }



            /* FIXME: check content-id, name, filename and content location,

             * if not set anyone of them, generate a default content-location

             */

            if ((null == part.getContentLocation())

                    && (null == part.getName())

                    && (null == part.getFilename())

                    && (null == part.getContentId())) {

                part.setContentLocation(Long.toOctalString(

                        System.currentTimeMillis()).getBytes());

            }



            /* get part's data */

            if (dataLength > 0) {

                byte[] partData = new byte[dataLength];

                String partContentType = new String(part.getContentType());

                pduDataStream.read(partData, 0, dataLength);

                if (partContentType.equalsIgnoreCase(ContentType.MULTIPART_ALTERNATIVE)) {

                    // parse "multipart/vnd.wap.multipart.alternative".

                    PduBody childBody = parseParts(new ByteArrayInputStream(partData));

                    // take the first part of children.

                    part = childBody.getPart(0);

                } else {

                    // Check Content-Transfer-Encoding.

                    byte[] partDataEncoding = part.getContentTransferEncoding();

                    if (null != partDataEncoding) {

                        String encoding = new String(partDataEncoding);

                        if (encoding.equalsIgnoreCase(PduPart.P_BASE64)) {

                            // Decode "base64" into "binary".

                            partData = Base64.decodeBase64(partData);

                        } else if (encoding.equalsIgnoreCase(PduPart.P_QUOTED_PRINTABLE)) {

                            // Decode "quoted-printable" into "binary".

                            partData = QuotedPrintable.decodeQuotedPrintable(partData);

                        } else {

                            // "binary" is the default encoding.

                        }

                    }

                    if (null == partData) {

                        log("Decode part data error!");

                        return null;

                    }

                    part.setData(partData);

                }

            }



            /* add this part to body */

            if (THE_FIRST_PART == checkPartPosition(part)) {

                /* this is the first part */

                body.addPart(0, part);

            } else {

                /* add the part to the end */

                body.addPart(part);

            }

        }



        return body;

    }

上述代码《TAG:2-3》中,调用PduPersister.java类中的persist()方法;《TAG:2-5》PduPersister类用于管理PDU存储,为什么会要把PDU的存储也封装成PduPersister呢?因为PDU的存储方式 是放在标准的SQLiteDatabase中,通过TelephonyProvider,而SQLiteDatabase中存储不能以直接的PDU的字节流来存储,必须要把PDU拆解成为可读的字段,因此在存储PDU和从存储加载PDU的过程
中涉及到PDU数据上面的处理,因此封装出来,更方便使用。其中persist(GenericPdu, Uri)方法把一个GenericPdu保存到Uri所指定的数据库中,返回指向新生成数据的Uri;load(Uri)方法从数据库把Uri所指的数据加载出来成一个GenericPdu对象;move(Uri, Uri)方法把Pdu从一个地方移到另一个地方,比如从草稿箱移动到发件箱,当MMS已发送时。

public Uri persist(GenericPdu pdu, Uri uri, boolean createThreadId, boolean groupMmsEnabled,

            HashMap<Uri, InputStream> preOpenedFiles)

            throws MmsException {

        if (uri == null) {

            throw new MmsException("Uri may not be null.");

        }

        long msgId = -1;

        try {

            msgId = ContentUris.parseId(uri);

        } catch (NumberFormatException e) {

            // the uri ends with "inbox" or something else like that

        }

        boolean existingUri = msgId != -1;



        if (!existingUri && MESSAGE_BOX_MAP.get(uri) == null) {

            throw new MmsException(

                    "Bad destination, must be one of "

                    + "content://mms/inbox, content://mms/sent, "

                    + "content://mms/drafts, content://mms/outbox, "

                    + "content://mms/temp.");

        }

        synchronized(PDU_CACHE_INSTANCE) {

            // If the cache item is getting updated, wait until it's done updating before

            // purging it.

            if (PDU_CACHE_INSTANCE.isUpdating(uri)) {

                if (LOCAL_LOGV) {

                    Log.v(TAG, "persist: " + uri + " blocked by isUpdating()");

                }

                try {

                    PDU_CACHE_INSTANCE.wait();

                } catch (InterruptedException e) {

                    Log.e(TAG, "persist1: ", e);

                }

            }

        }

        PDU_CACHE_INSTANCE.purge(uri);



        PduHeaders header = pdu.getPduHeaders();

        PduBody body = null;

        ContentValues values = new ContentValues();

        Set<Entry<Integer, String>> set;



        set = ENCODED_STRING_COLUMN_NAME_MAP.entrySet();

        for (Entry<Integer, String> e : set) {

            int field = e.getKey();

            EncodedStringValue encodedString = header.getEncodedStringValue(field);

            if (encodedString != null) {

                String charsetColumn = CHARSET_COLUMN_NAME_MAP.get(field);

                values.put(e.getValue(), toIsoString(encodedString.getTextString()));

                values.put(charsetColumn, encodedString.getCharacterSet());

            }

        }



        set = TEXT_STRING_COLUMN_NAME_MAP.entrySet();

        for (Entry<Integer, String> e : set){

            byte[] text = header.getTextString(e.getKey());

            if (text != null) {

                values.put(e.getValue(), toIsoString(text));

            }

        }



        set = OCTET_COLUMN_NAME_MAP.entrySet();

        for (Entry<Integer, String> e : set){

            int b = header.getOctet(e.getKey());

            if (b != 0) {

                values.put(e.getValue(), b);

            }

        }



        set = LONG_COLUMN_NAME_MAP.entrySet();

        for (Entry<Integer, String> e : set){

            long l = header.getLongInteger(e.getKey());

            if (l != -1L) {

                values.put(e.getValue(), l);

            }

        }



        HashMap<Integer, EncodedStringValue[]> addressMap =

                new HashMap<Integer, EncodedStringValue[]>(ADDRESS_FIELDS.length);

        // Save address information.

        for (int addrType : ADDRESS_FIELDS) {

            EncodedStringValue[] array = null;

            if (addrType == PduHeaders.FROM) {

                EncodedStringValue v = header.getEncodedStringValue(addrType);

                if (v != null) {

                    array = new EncodedStringValue[1];

                    array[0] = v;

                }

            } else {

                array = header.getEncodedStringValues(addrType);

            }

            addressMap.put(addrType, array);

        }



        HashSet<String> recipients = new HashSet<String>();

        int msgType = pdu.getMessageType();

        // Here we only allocate thread ID for M-Notification.ind,

        // M-Retrieve.conf and M-Send.req.

        // Some of other PDU types may be allocated a thread ID outside

        // this scope.

        if ((msgType == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND)

                || (msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF)

                || (msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ)) {

            switch (msgType) {

                case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:

                case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:

                    loadRecipients(PduHeaders.FROM, recipients, addressMap, false);



                    // For received messages when group MMS is enabled, we want to associate this

                    // message with the thread composed of all the recipients -- all but our own

                    // number, that is. This includes the person who sent the

                    // message or the FROM field (above) in addition to the other people the message

                    // was addressed to or the TO field. Our own number is in that TO field and

                    // we have to ignore it in loadRecipients.

                    if (groupMmsEnabled) {

                        loadRecipients(PduHeaders.TO, recipients, addressMap, true);

                    }

                    break;

                case PduHeaders.MESSAGE_TYPE_SEND_REQ:

                    loadRecipients(PduHeaders.TO, recipients, addressMap, false);

                    break;

            }

            long threadId = 0;

            if (createThreadId && !recipients.isEmpty()) {

                // Given all the recipients associated with this message, find (or create) the

                // correct thread.

                threadId = Threads.getOrCreateThreadId(mContext, recipients);

            }

            values.put(Mms.THREAD_ID, threadId);

        }



        // Save parts first to avoid inconsistent message is loaded

        // while saving the parts.

        long dummyId = System.currentTimeMillis(); // Dummy ID of the msg.



        // Figure out if this PDU is a text-only message

        boolean textOnly = true;



        // Get body if the PDU is a RetrieveConf or SendReq.

        if (pdu instanceof MultimediaMessagePdu) {

            body = ((MultimediaMessagePdu) pdu).getBody();

            // Start saving parts if necessary.

            if (body != null) {

                int partsNum = body.getPartsNum();

                if (partsNum > 2) {

                    // For a text-only message there will be two parts: 1-the SMIL, 2-the text.

                    // Down a few lines below we're checking to make sure we've only got SMIL or

                    // text. We also have to check then we don't have more than two parts.

                    // Otherwise, a slideshow with two text slides would be marked as textOnly.

                    textOnly = false;

                }

                for (int i = 0; i < partsNum; i++) {

                    PduPart part = body.getPart(i);

                    persistPart(part, dummyId, preOpenedFiles);



                    // If we've got anything besides text/plain or SMIL part, then we've got

                    // an mms message with some other type of attachment.

                    String contentType = getPartContentType(part);

                    if (contentType != null && !ContentType.APP_SMIL.equals(contentType)

                            && !ContentType.TEXT_PLAIN.equals(contentType)) {

                        textOnly = false;

                    }

                }

            }

        }

        // Record whether this mms message is a simple plain text or not. This is a hint for the

        // UI.

        values.put(Mms.TEXT_ONLY, textOnly ? 1 : 0);



        Uri res = null;

        if (existingUri) {

            res = uri;

            SqliteWrapper.update(mContext, mContentResolver, res, values, null, null);

        } else {

            res = SqliteWrapper.insert(mContext, mContentResolver, uri, values);

            if (res == null) {

                throw new MmsException("persist() failed: return null.");

            }

            // Get the real ID of the PDU and update all parts which were

            // saved with the dummy ID.

            msgId = ContentUris.parseId(res);

        }



        values = new ContentValues(1);

        values.put(Part.MSG_ID, msgId);

        SqliteWrapper.update(mContext, mContentResolver,

                             Uri.parse("content://mms/" + dummyId + "/part"),

                             values, null, null);

        // We should return the longest URI of the persisted PDU, for

        // example, if input URI is "content://mms/inbox" and the _ID of

        // persisted PDU is '8', we should return "content://mms/inbox/8"

        // instead of "content://mms/8".

        // FIXME: Should the MmsProvider be responsible for this???

        if (!existingUri) {

            res = Uri.parse(uri + "/" + msgId);

        }



        // Save address information.

        for (int addrType : ADDRESS_FIELDS) {

            EncodedStringValue[] array = addressMap.get(addrType);

            if (array != null) {

                persistAddress(msgId, addrType, array);

            }

        }



        return res;

    }

上述代码《TAG:2-5》中,调用PduPersister.java类中的()方法对彩信进行持久化,该方法中调用SqliteWrapper类的insert方法将数据存入数据库mmssms的part表中:《TAG:2-6》

 public Uri persistPart(PduPart part, long msgId, HashMap<Uri, InputStream> preOpenedFiles)

            throws MmsException {

        Uri uri = Uri.parse("content://mms/" + msgId + "/part");

        ContentValues values = new ContentValues(8);



        int charset = part.getCharset();

        if (charset != 0 ) {

            values.put(Part.CHARSET, charset);

        }



        String contentType = getPartContentType(part);

        if (contentType != null) {

            // There is no "image/jpg" in Android (and it's an invalid mimetype).

            // Change it to "image/jpeg"

            if (ContentType.IMAGE_JPG.equals(contentType)) {

                contentType = ContentType.IMAGE_JPEG;

            }



            values.put(Part.CONTENT_TYPE, contentType);

            // To ensure the SMIL part is always the first part.

            if (ContentType.APP_SMIL.equals(contentType)) {

                values.put(Part.SEQ, -1);

            }

        } else {

            throw new MmsException("MIME type of the part must be set.");

        }



        if (part.getFilename() != null) {

            String fileName = new String(part.getFilename());

            values.put(Part.FILENAME, fileName);

        }



        if (part.getName() != null) {

            String name = new String(part.getName());

            values.put(Part.NAME, name);

        }



        String value = null;

        int encode=-1;

        if (part.getContentDisposition() != null) {

            value = toIsoString(part.getContentDisposition());

            values.put(Part.CONTENT_DISPOSITION,value);

        }



        if (part.getContentId() != null) {

            byte[] byte_cid=part.getContentId();

            encode=detectEncoding(byte_cid);

            try{

                switch(encode){

            case GB2312:

                             value=new String(byte_cid,"GB2312");

            break;

            case ASCII:

                             

                             value=new String(byte_cid,"ASCII");

                     break;

            case UTF8:

                value=new String(byte_cid,"UTF-8");

            break;

            case UNICODE:

                value=new String(byte_cid,"Unicode");

            break;

            default:

                            value = toIsoString(byte_cid);

                     break;

                }

         Log.d("bill","getContentId---"+value);

            values.put(Part.CONTENT_ID, value);}catch(Exception e){}

        }



        if (part.getContentLocation() != null) {

            byte[] byte_cl=part.getContentLocation();

            encode=detectEncoding(byte_cl);

            try{

                switch(encode){

            case GB2312:

                             value=new String(byte_cl,"GB2312");

            break;

            case ASCII:

                             value=new String(byte_cl,"ASCII");

                     break;

            case UTF8:

                value=new String(byte_cl,"UTF-8");

            break;

            case UNICODE:

                value=new String(byte_cl,"Unicode");

             break;

            default:

                            value = toIsoString(byte_cl);

                     break;

                }

            Log.d("bill","getContentLocation---"+value);

            values.put(Part.CONTENT_LOCATION,value);}catch(Exception e){}

        }



        Uri res = SqliteWrapper.insert(mContext, mContentResolver, uri, values);

        if (res == null) {

            throw new MmsException("Failed to persist part, return null.");

        }



        persistData(part, res, contentType, preOpenedFiles);

        // After successfully store the data, we should update

        // the dataUri of the part.

        part.setDataUri(res);



        return res;

    }

我们接着分析,持久会存储之后会调用<TAG 2-1>中的PushReceiver.java类中的doInBackground()方法来启动TransactionService服务;

深度分析:Android4.3下MMS发送到附件为音频文件(音频为系统内置音频)的彩信给自己,添加音频-发送彩信-接收彩信-下载音频附件-预览-播放(三,接收彩信<2,下载彩信>)的更多相关文章

  1. Atitit.web预览播放视频的总结

    Atitit.web预览播放视频的总结 1. 浏览器类型的兼容性(chrome,ff,ie) 1 2. 操作系统的兼容性 1 3. 视频格式的内部视频格式跟播放器插件的兼容性.. 2 4. 指定播放器 ...

  2. 乐橙平台大华监控Android端实时预览播放

    一.初始化 首先我们需要到乐橙开放平台下载android对应的开发包,将sdk中提供的jar和so文件添加到项目中: 二.获取监控列表 监控列表我们是通过从自家后台服务器中获取的,这个自己根据需要调整 ...

  3. IIS6/IIS7环境下实现支持mp4视频随意拖动、预览播放、边下载边播放

    前几天,一客户需要在IIS环境下实现MP4视频可以随意拖动观看,边下载边播放.一看这要求,IIS本身是无法实现,想着应该需要用插件,于是GG一番,还真找到这样的插件,此组件为H264-Streamin ...

  4. 深度分析:Android中Mms设置页面更改短信中心号码流程

    相关控件初始化方法:showSmscPref private void showSmscPref() {         int count = MSimTelephonyManager.getDef ...

  5. java文件上传、下载、图片预览

    多文件保存到本地: @ResponseBody    @RequestMapping(value = "/uploadApp",produces = { "applica ...

  6. 将vue文档下载到本地预览

    1下载:https://github.com/vuejs/cn.vuejs.org   到本地 2. npm install npm start # 开发服务器地址为 http://localhost ...

  7. S3C2416裸机开发系列19_Fatfs播放录像wav音频文件

    S3C2416裸机开发系列19 Fatfs播放录像wav音频文件 国际象棋男孩    1048272975 多媒体资源,一般都是以文件的形式存储在固化存储器中.Fatfs所支持的fat32为windo ...

  8. Python实例---爬取下载喜马拉雅音频文件

    PyCharm下python爬虫准备 打开pycharm 点击设置 点击项目解释器,再点击右边+号 搜索相关库并添加,例如:requests 喜马拉雅全网递归下载 打开谷歌/火狐浏览器,按F12打开开 ...

  9. 使用java的 htpUrlConnection post请求 下载pdf文件,然后输出到页面进行预览和下载

    使用java的 htpUrlConnection post请求 下载pdf文件,然后输出到页面进行预览和下载 2018年06月07日 10:42:26 守望dfdfdf 阅读数:235 标签: jav ...

随机推荐

  1. Python实现自平衡二叉树AVL

    # -*- coding: utf-8 -*- from enum import Enum #参考http://blog.csdn.net/niteip/article/details/1184069 ...

  2. 常见文档一览表 -httpclient

    httpclient http://www.yeetrack.com/?p=779 虫师『性能测试』文章大汇总 https://www.cnblogs.com/fnng/archive/2012/08 ...

  3. Jmeter 抓app包 抓到一半不好用了

    错误描述: java.net.ConnectException: Connection refused (Connection refused) at java.net.PlainSocketImpl ...

  4. Java 集合、Iterator迭代器、泛型等

    01集合使用的回顾 A:集合使用的回顾 a.ArrayList集合存储5个int类型元素 public static void main(String[] args) { ArrayList<I ...

  5. 《剑指offer》第三_一题(找出数组中重复的数字,可改变数组)

    // 面试题3(一):找出数组中重复的数字 // 题目:在一个长度为n的数组里的所有数字都在0到n-1的范围内.数组中某些数字是重复的,但不知道有几个数字重复了, // 也不知道每个数字重复了几次.请 ...

  6. 雷林鹏分享:Ruby 哈希(Hash)

    Ruby 哈希(Hash) 哈希(Hash)是类似 "employee" => "salary" 这样的键值对的集合.哈希的索引是通过任何对象类型的任意键 ...

  7. English trip -- VC(情景课)4 B Parts of the body 身体部位

    xu言: ... Words eye  读音同 I     眼睛 nose 鼻子 ear   耳朵 tooth  牙齿 mouth  嘴 hair 头发 eyebrow  眉毛 cheek  脸颊 n ...

  8. JDK1.5 新特性

    1:自动装箱与拆箱 自动装箱:每当需要一种类型的对象时,这种基本类型就自动地封装到与它相同类型的包装中. 自动拆箱:每当需要一个值时,被装箱对象中的值就被自动地提取出来,没必要再去调用intValue ...

  9. Android 列表使用(ListView GridView Gallery图片计时滚动)

    ListView 作用: 1.将数据填充到布局. 2.处理用户的选择点击等操作. 根据列表的适配器类型,列表分为三种,ArrayAdapter,SimpleAdapter和SimpleCursorAd ...

  10. Confluence 6 设置公共访问备注

    你不能为匿名用户赋予空间管理员或者限制权限. 你可以让用户自行注册你的 Confluence 站点,同时也可以选择其他的用户注册选项,比如邀请用户注册等.请查看:Add and Invite User ...