彩信的接收简介:

主要是由应用程序负责从彩信服务中心(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. SpringBoot+Vue+OpenOffice实现文档管理(文档上传、下载、在线预览)

    场景 SpringBoot集成OpenOffice实现doc文档转html: SpringBoot集成OpenOffice实现doc文档转html_BADAO_LIUMANG_QIZHI的博客-CSD ...

  2. 为什么有的视频下载了一点就能播放,有的视频不下载完就播放不了

    作者:Happy Ennding 链接:https://www.zhihu.com/question/54673095/answer/140528753 来源:知乎 著作权归作者所有,转载请联系作者获 ...

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

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

  4. java 看书浏览器官_JAVA读取文件流,设置浏览器下载或直接预览操作

    最近项目需要在浏览器中通过url预览图片.但发现浏览器始终默认下载,而不是预览.研究了一下,发现了问题: // 设置response的header,注意这句,如果开启,默认浏览器会进行下载操作,如果注 ...

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

    这篇文章主要介绍了java实现文件上传.下载.图片预览,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 多文件保存到本地: @ResponseBody ...

  6. java 读取浏览器_JAVA读取文件流,设置浏览器下载或直接预览操作

    最近项目需要在浏览器中通过URL预览图片.但发现浏览器始终默认下载,而不是预览.研究了一下,发现了问题: // 设置response的Header,注意这句,如果开启,默认浏览器会进行下载操作,如果注 ...

  7. java用浏览器下载文件_JAVA读取文件流,设置浏览器下载或直接预览操作

    最近项目需要在浏览器中通过url预览图片.但发现浏览器始终默认下载,而不是预览.研究了一下,发现了问题: // 设置response的header,注意这句,如果开启,默认浏览器会进行下载操作,如果注 ...

  8. 利用微软的Office Online在线预览播放Office文档,无工具栏

    利用微软的Office Online在线预览播放Office文档 通过微软的在线预览URL链接可以直接打开在线PPT文档如下 https://view.officeapps.live.com/op/v ...

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

    将vue文档下载到本地预览 由于vue文档在服务器在国外,因此访问速度较慢,为了方便文档查看,可以将文档下载到本地预览 步骤 到vue的GitHub仓库下载文档源码 下载node.js和git安装到本 ...

  10. java浏览器预览文件_JAVA读取文件流,设置浏览器下载或直接预览操作

    最近项目需要在浏览器中通过URL预览图片.但发现浏览器始终默认下载,而不是预览.研究了一下,发现了问题: // 设置response的Header,注意这句,如果开启,默认浏览器会进行下载操作,如果注 ...

最新文章

  1. Win7和Win10安装VC6.0注意事项
  2. android studio 2.1 ndk,Android studio 2.1编辑器(CLint)无法找到使用原生(ndk)插件的模块的标题...
  3. 没被同事卷死,被司机卷死了...
  4. LeetCode Search a 2D Matrix II
  5. javascript中 this 指向问题
  6. 小功能隐藏着大学问---windows的ACL带来的挑战
  7. FIO测试磁盘的iops
  8. LeetCode 1291. 顺次数(模拟)
  9. C++实现的Miller-Rabin素性测试程序
  10. Blender相关的一些链接(持续更新)
  11. postgresql14编译安装参考手册(centos)
  12. 农夫山泉赴港上市,迷之操作暗藏“算计”
  13. HTML——HTML中的特殊符号
  14. STM32F103C8T6在Arduino框架下驱动SH1106 1.3“ IIC OLED显示
  15. Windows挂载Linux网络共享文件夹
  16. android 手机 otg,对于安卓智能手机的OTG功能,你了解多少
  17. php图片文字水印透明度,php图片水印 可以设置透明度
  18. 手撕python_手撕编译器(一)——编译原理简介
  19. 2016c语言模拟试卷A,2016C语言模拟试卷(读程序写结果).doc
  20. 计算机基础知识系列·进制转换的简易方法

热门文章

  1. 视频播放器的新选择——Mplayer WW编译版
  2. 如何使用c语言获取麦克风信息,[C#] 如何获取麦克风采集的音频信息 和 如何根据波形播放声音。...
  3. 主流七款web服务器软件点评,7款主流WEB服务器软件.pdf
  4. 关于电脑双屏后,微信截屏会黑屏,且截屏范围不全的问题解决方法
  5. kienct 散斑测量原理
  6. 别上“绵羊墙”多丢脸啊!
  7. 基于Android的校园社交平台,1万1 字基于Android平台的校园社交App的设计与实现 ).docx...
  8. 大龄处男是怪胎还是珍品
  9. XML简介,XML和HTML的区别,XML用处,XML规则,XML约束,XML语法,XML解析,DOM
  10. OJ1051: 平方根的和(C语言)(关于循环体求和和求中间量先后问题)