当前位置: 代码迷 >> Android >> 深度分析:Android4.3下MMS发送到附件替音频文件(音频为系统内置音频)的彩信给自己,添加音频-发送彩信-接收彩信-下载音频附件-预览-播放(三,接收彩信<2,下载彩信>)
  详细解决方案

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

热度:660   发布时间:2016-04-28 05:16:19.0
深度分析:Android4.3下MMS发送到附件为音频文件(音频为系统内置音频)的彩信给自己,添加音频-发送彩信-接收彩信-下载音频附件-预览-播放(三,接收彩信<2,下载彩信>)

彩信的接收简介

                主要是由应用程序负责从彩信服务中心(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服务;






  相关解决方案