因为工作需要,再加上个人爱好,经过分析整理出短彩应用中从发送至收到附件为音频的彩信的下载,预览,播放整个流程,给大家一起分享。

第一步,添加附件:ComposeMessageActivity类下,addAttachement();

private void addAttachment(int type, boolean replace) {
         // Calculate the size of the current slide if we're doing a replace so the
         // slide size can optionally be used in computing how much room is left for an attachment.
         int currentSlideSize = 0;
         SlideshowModel slideShow = mWorkingMessage.getSlideshow();
         if (replace && slideShow != null) {
             WorkingMessage.removeThumbnailsFromCache(slideShow);
             SlideModel slide = slideShow.get(0);
             currentSlideSize = slide.getSlideSize();
         }
         switch (type) {
             case AttachmentTypeSelectorAdapter.ADD_IMAGE:
                 MessageUtils.selectImage(this, REQUEST_CODE_ATTACH_IMAGE);
                 break;

             case AttachmentTypeSelectorAdapter.TAKE_PICTURE: {
                 MessageUtils.capturePicture(this, REQUEST_CODE_TAKE_PICTURE);
                 break;
             }

             case AttachmentTypeSelectorAdapter.ADD_VIDEO:
                 MessageUtils.selectVideo(this, REQUEST_CODE_ATTACH_VIDEO);
                 break;

             case AttachmentTypeSelectorAdapter.RECORD_VIDEO: {
                 long sizeLimit = computeAttachmentSizeLimit(slideShow, currentSlideSize);
                 if (sizeLimit > 0) {
                     MessageUtils.recordVideo(this, REQUEST_CODE_TAKE_VIDEO, sizeLimit);
                 } else {
                     Toast.makeText(this,
                             getString(R.string.message_too_big_for_video),
                             Toast.LENGTH_SHORT).show();
                 }
             }
             break;

             case AttachmentTypeSelectorAdapter.ADD_SOUND:
                 MessageUtils.selectAudio(this, REQUEST_CODE_ATTACH_SOUND);
                 break;

             case AttachmentTypeSelectorAdapter.RECORD_SOUND:
                 long sizeLimit = computeAttachmentSizeLimit(slideShow, currentSlideSize);
                 MessageUtils.recordSound(this, REQUEST_CODE_RECORD_SOUND, sizeLimit);
                 break;

             case AttachmentTypeSelectorAdapter.ADD_SLIDESHOW:
                 editSlideshow();
                 break;

             case AttachmentTypeSelectorAdapter.ADD_CONTACT_AS_TEXT:
                 pickContacts(MultiPickContactsActivity.MODE_INFO,
                         replace ? REQUEST_CODE_ATTACH_REPLACE_CONTACT_INFO
                                 : REQUEST_CODE_ATTACH_ADD_CONTACT_INFO);
                 break;

             case AttachmentTypeSelectorAdapter.ADD_CONTACT_AS_VCARD:
                 pickContacts(MultiPickContactsActivity.MODE_VCARD,
                         REQUEST_CODE_ATTACH_ADD_CONTACT_VCARD);
                 break;

             default:
                 break;
         }
     }
第二步,选择音频类型:MessageUtils类中的selectAudio()方法;
public static void selectAudio(final Activity activity, final int requestCode) {
         // Compare other phone's behavior, we are not only display the
         // RingtonePick to add, we could have other choices like external audio
         // and system audio. Allow the user to select a particular kind of data
         // and return it.
         String[] items = new String[2];
         items[SELECT_SYSTEM] = activity.getString(R.string.system_audio_item);
         items[SELECT_EXTERNAL] = activity.getString(R.string.external_audio_item);
         ArrayAdapter<String> adapter = new ArrayAdapter<String>(activity,
                 android.R.layout.simple_list_item_1, android.R.id.text1, items);
         AlertDialog.Builder builder = new AlertDialog.Builder(activity);
         AlertDialog dialog = builder.setTitle(activity.getString(R.string.select_audio))
                 .setAdapter(adapter, new OnClickListener() {
                     @Override
                     public void onClick(DialogInterface dialog, int which) {
                         Intent audioIntent = null;
                         switch (which) {
                             case SELECT_SYSTEM:
                                 audioIntent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);//android.intent.action.RINGTONE_PICKER
                                 //add by zhihui.wang for SWBUG00028878 at 2014-5-5.
                                 audioIntent.addCategory("android.intent.category.SIMRINGTONE");
                                 audioIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, false);
                                 audioIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, false);
                                 audioIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_INCLUDE_DRM, false);
                                 audioIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE,
                                         activity.getString(R.string.select_audio));
                                 audioIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, false);
                                 break;
                             case SELECT_EXTERNAL:
                                 audioIntent = new Intent();
                                 audioIntent.setAction(Intent.ACTION_PICK);
                                 audioIntent.setData(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI);
                                 break;
                         }
                         activity.startActivityForResult(audioIntent, requestCode);
                     }
                 })
                 .create();
         dialog.show();
     }在上述代码中弹出Choose audio对话框,选项分别为System audio和External audio,我们这里选择System audio选项,这里在Intent中封装了彩铃设置为没有默认选项(EXTRA_RINGTONE_SHOW_DEFAULT),非静音状态(EXTRA_RINGTONE_SHOW_SILENT),非数字版权管理(EXTRA_RINGTONE_INCLUDE_DRM),标题(EXTRA_RINGTONE_TITLE)-Choose audio
第三步,选择音频文件:RingtonePickerActivity类,该类继承了AlertActivity类,选择音频文件,OK;
 public void onClick(DialogInterface dialog, int which) {
         boolean positiveResult = which == DialogInterface.BUTTON_POSITIVE;

         // Should't response the "OK" and "Cancel" button's click event at the
         // same time.
         if (mIsHasClick || (mCursor == null)) {
             return;
         }
         mIsHasClick = true;

         // Stop playing the previous ringtone
         mRingtoneManager.stopPreviousRingtone();

         if (positiveResult) {
             Intent resultIntent = new Intent();
             Uri uri = null;

             if (mClickedPos == mDefaultRingtonePos) {
                 // Set it to the default Uri that they originally gave us
                 uri = mUriForDefaultItem;
             } else if (mClickedPos == mSilentPos) {
                 // A null Uri is for the 'Silent' item
                 uri = null;
             } else {
                 uri = mRingtoneManager.getRingtoneUri(getRingtoneManagerPosition(mClickedPos));
             }

             resultIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, uri);//所选音频文件的地址
             setResult(RESULT_OK, resultIntent);//RESULT_OK=-1
         } else {
             setResult(RESULT_CANCELED);
         }

         getWindow().getDecorView().post(new Runnable() {
             public void run() {
                 mCursor.deactivate();
             }
         });

         finish();
     }选择音频,添加附件成功后,返回ComposeMessage,编辑彩信界面,这里我们继续输入文本内容:这里介绍一下与编辑彩信文本内容的控件为ComposeMessageActivity类的mTextEditor;
第四步,处理附件:选择附件后,处理添加的附件;<TAG 1-1>
  @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         if (LogTag.VERBOSE) {
             log("onActivityResult: requestCode=" + requestCode + ", resultCode=" + resultCode +
                     ", data=" + data);
         }
         mWaitingForSubActivity = false;          // We're back!
         mShouldLoadDraft = false;
         if (mWorkingMessage.isFakeMmsForDraft()) {
             // We no longer have to fake the fact we're an Mms. At this point we are or we aren't,
             // based on attachments and other Mms attrs.
             mWorkingMessage.removeFakeMmsForDraft();
         }

         if (requestCode == REQUEST_CODE_PICK) {
             mWorkingMessage.asyncDeleteDraftSmsMessage(mConversation);
         }

         if (requestCode == REQUEST_CODE_ADD_CONTACT) {
             // The user might have added a new contact. When we tell contacts to add a contact
             // and tap "Done", we're not returned to Messaging. If we back out to return to
             // messaging after adding a contact, the resultCode is RESULT_CANCELED. Therefore,
             // assume a contact was added and get the contact and force our cached contact to
             // get reloaded with the new info (such as contact name). After the
             // contact is reloaded, the function onUpdate() in this file will get called
             // and it will update the title bar, etc.
             if (mAddContactIntent != null) {
                 String address =
                     mAddContactIntent.getStringExtra(ContactsContract.Intents.Insert.EMAIL);
                 if (address == null) {
                     address =
                         mAddContactIntent.getStringExtra(ContactsContract.Intents.Insert.PHONE);
                 }
                 if (address != null) {
                     Contact contact = Contact.get(address, false);
                     if (contact != null) {
                         contact.reload();
                     }
                 }
             }
         }

         if (requestCode == AttachmentEditor.MSG_PLAY_AUDIO
                 || requestCode == AttachmentEditor.MSG_PLAY_SLIDESHOW
                 || requestCode == AttachmentEditor.MSG_PLAY_VIDEO) {
             // When the audio has finished to play, we put the
             // mIsAudioPlayerActivityRunning to false.
             mIsAudioPlayerActivityRunning = false;
         }

         if (resultCode != RESULT_OK){
             if (LogTag.VERBOSE) log("bail due to resultCode=" + resultCode);
             return;
         }

         switch (requestCode) {
             case REQUEST_CODE_CREATE_SLIDESHOW:
                 if (data != null) {
                     mAttachFileUri = data.getData();
                     mIsSendMultiple = false;
                     WorkingMessage newMessage = WorkingMessage.load(this, mAttachFileUri);
                     if (newMessage != null) {
                         // Here we should keep the subject from the old mWorkingMessage.
                         setNewMessageSubject(newMessage);
                         mWorkingMessage = newMessage;
                         mWorkingMessage.setConversation(mConversation);
                         updateThreadIdIfRunning();
                         updateMmsSizeIndicator();
                         drawTopPanel(false);
                         drawBottomPanel();
                         updateSendButtonState();
                         updateAttachButtonState();
                     }
                 }
                 break;

             case REQUEST_CODE_TAKE_PICTURE: {
                 // create a file based uri and pass to addImage(). We want to read the JPEG
                 // data directly from file (using UriImage) instead of decoding it into a Bitmap,
                 // which takes up too much memory and could easily lead to OOM.
                 File file = new File(TempFileProvider.getScrapPath(this));
                 mAttachFileUri = Uri.fromFile(file);

                 // Remove the old captured picture's thumbnail from the cache
                 if(MmsApp.getApplication().getThumbnailManager() != null) {
                    MmsApp.getApplication().getThumbnailManager().removeThumbnail(mAttachFileUri);

                    addImageAsync(mAttachFileUri, false);
                 }

                 break;
             }

             case REQUEST_CODE_ATTACH_IMAGE: {
                 if (data != null) {
                     mAttachFileUri = data.getData();
                     addImageAsync(mAttachFileUri, false);
                 }
                 break;
             }

             case REQUEST_CODE_TAKE_VIDEO:
                 mAttachFileUri = TempFileProvider.renameScrapFile(".3gp", null, this);
                 // Remove the old captured video's thumbnail from the cache
                 MmsApp.getApplication().getThumbnailManager().removeThumbnail(mAttachFileUri);

                 addVideoAsync(mAttachFileUri, false);      // can handle null videoUri
                 break;

             case REQUEST_CODE_ATTACH_VIDEO:
                 if (data != null) {
                     mAttachFileUri = data.getData();
                     addVideoAsync(mAttachFileUri, false);
                 }
                 break;

            case REQUEST_CODE_ATTACH_SOUND: {//104
                 // Attempt to add the audio to the  attachment.
                 Uri uri = (Uri) data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
                 if (uri == null) {
                     uri = data.getData();
                 } else if (Settings.System.DEFAULT_RINGTONE_URI.equals(uri)) {
                     break;
                 }
                 mAttachFileUri = uri;
                 addAudio(mAttachFileUri, false);
                 break;
             }

             case REQUEST_CODE_RECORD_SOUND:
                 if (data != null) {
                     mAttachFileUri = data.getData();
                     addAudio(mAttachFileUri,false);
                 }
                 break;

             case REQUEST_CODE_ECM_EXIT_DIALOG:
                 boolean outOfEmergencyMode = data.getBooleanExtra(EXIT_ECM_RESULT, false);
                 if (outOfEmergencyMode) {
                     sendMessage(false);
                 }
                 break;

             case REQUEST_CODE_PICK:
                 if (data != null) {
                     processPickResult(data);
                 }
                 break;

             case REQUEST_CODE_ATTACH_REPLACE_CONTACT_INFO:
                 // Caused by user choose to replace the attachment, so we need remove
                 // the attachment and then add the contact info to text.
                 if (data != null) {
                     mWorkingMessage.removeAttachment(true);
                     mAttachFileUri = null;
                 }
             case REQUEST_CODE_ATTACH_ADD_CONTACT_INFO:
                 if (data != null) {
                     String newText = mWorkingMessage.getText() +
                         data.getStringExtra(MultiPickContactsActivity.EXTRA_INFO);
                     mWorkingMessage.setText(newText);
                 }
                 break;

             case REQUEST_CODE_ATTACH_ADD_CONTACT_VCARD:
                 if (data != null) {
                     // In a case that a draft message has an attachment whose type is slideshow,
                     // then reopen it and replace the attachment through attach icon, we have to
                     // remove the old attachement silently first.
                     if (mWorkingMessage != null) {
                         mWorkingMessage.removeAttachment(false);
                         mAttachFileUri = null;
                     }
                     String extraVCard = data.getStringExtra(MultiPickContactsActivity.EXTRA_VCARD);
                     if (extraVCard != null) {
                         Uri vcard = Uri.parse(extraVCard);
                         addVcard(vcard);
                     }
                 }
                 break;

             default:
                 if (LogTag.VERBOSE) log("bail due to unknown requestCode=" + requestCode);
                 break;
         }
     }上述代码调用了addAudio;<TAG 1-2>
    private void addAudio(Uri uri, boolean append) {
         if (uri != null) {
            int result = mWorkingMessage.setAttachment(WorkingMessage.AUDIO, uri, append);
             handleAddAttachmentError(result, R.string.type_audio);
         }
     }上述代码<TAG 1-2-1>中WorkingMessage类(信息发送的第一站,它会先处理一下信息的相关内容,比如刷新收信人(Sync Recipients)以保证都是合法收信人,把附件(Slideshow)转成可发送的彩信附件Pdu(SendReq),makeSendReq。然后针对,不同的信息类型(短信,彩信)调用不同的处理类来处理。处理的流程也比较类似,都是先把消息放到一个队列中,然后启动相应的Service来处理。Service会维护信息队列,然后处理每个信息。短信是由Frameworks中的SmsManager发送出去,而彩信是通过Http协议发送。)中调用了setAttachment()方法;<TAG 1-3>
 public int setAttachment(int type, Uri dataUri, boolean append) {
         if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
             LogTag.debug("setAttachment type=%d uri %s", type, dataUri);
         }
         int result = OK;
         SlideshowEditor slideShowEditor = new SlideshowEditor(mActivity, mSlideshow);//创建一个幻灯片编辑器实例

         // Special case for deleting a slideshow. When ComposeMessageActivity gets told to
         // remove an attachment (search for AttachmentEditor.MSG_REMOVE_ATTACHMENT), it calls
         // this function setAttachment with a type of TEXT and a null uri. Basically, it's turning
         // the working message from an MMS back to a simple SMS. The various attachment types
         // use slide[0] as a special case. The call to ensureSlideshow below makes sure there's
         // a slide zero. In the case of an already attached slideshow, ensureSlideshow will do
         // nothing and the slideshow will remain such that if a user adds a slideshow again, they'll
         // see their old slideshow they previously deleted. Here we really delete the slideshow.
         if (type == TEXT && mAttachmentType == SLIDESHOW && mSlideshow != null && dataUri == null
                 && !append) {
             slideShowEditor.removeAllSlides();
         }

         // Make sure mSlideshow is set up and has a slide.
         ensureSlideshow();      // mSlideshow can be null before this call, won't be afterwards//确认创建一个幻灯片实例,如果没有这里会进行处理
         slideShowEditor.setSlideshow(mSlideshow);

         // Change the attachment
         result = append ? appendMedia(type, dataUri, slideShowEditor)
                 : changeMedia(type, dataUri, slideShowEditor);

         // If we were successful, update mAttachmentType and notify
         // the listener than there was a change.
         if (result == OK) {
             mAttachmentType = type;
         }
         correctAttachmentState();   // this can remove the slideshow if there are no attachments

         if (mSlideshow != null && type == IMAGE) {
             // Prime the image's cache; helps A LOT when the image is coming from the network
             // (e.g. Picasa album). See b/5445690.
             int numSlides = mSlideshow.size();
             if (numSlides > 0) {
                 ImageModel imgModel = mSlideshow.get(numSlides - 1).getImage();
                 if (imgModel != null) {
                     cancelThumbnailLoading();
                     imgModel.loadThumbnailBitmap(null);
                 }
             }
         }

         mStatusListener.onAttachmentChanged();  // have to call whether succeeded or failed,
                                                 // because a replace that fails, removes the slide

         if (!append && mAttachmentType == TEXT && type == TEXT) {
             int[] params = SmsMessage.calculateLength(getText(), false);
             /* SmsMessage.calculateLength returns an int[4] with:
              *   int[0] being the number of SMS's required,
              *   int[1] the number of code units used,
              *   int[2] is the number of code units remaining until the next message.
              *   int[3] is the encoding type that should be used for the message.
              */
             int smsSegmentCount = params[0];

             if (!MmsConfig.getMultipartSmsEnabled()) {
                 // The provider doesn't support multi-part sms's so as soon as the user types
                 // an sms longer than one segment, we have to turn the message into an mms.
                 setLengthRequiresMms(smsSegmentCount > 1, false);
             } else {
                 int threshold = MmsConfig.getSmsToMmsTextThreshold();
                 setLengthRequiresMms(threshold > 0 && smsSegmentCount > threshold, false);
             }
         } else {
             // Set HAS_ATTACHMENT if we need it.
             updateState(HAS_ATTACHMENT, hasAttachment(), true);
         }
         return result;
     }
上述代码<TAG 1-3>由于这里主要研究的附件主要是音频附件,因此我们直接调用了updateState()方法;    private void updateState(int state, boolean on, boolean notify) {
         if (!sMmsEnabled) {
             // If Mms isn't enabled, the rest of the Messaging UI should not be using any
             // feature that would cause us to to turn on any Mms flag and show the
             // "Converting to multimedia..." message.
             return;
         }
         int oldState = mMmsState;
         if (on) {
             mMmsState |= state;
         } else {
             mMmsState &= ~state;
         }

         // If we are clearing the last bit that is not FORCE_MMS,
         // expire the FORCE_MMS bit.
         if (mMmsState == FORCE_MMS && ((oldState & ~FORCE_MMS) > 0)) {
             mMmsState = 0;
         }

         // Notify the listener if we are moving from SMS to MMS
         // or vice versa.//这里判断状态,并弹出短信切换至彩信对话框或彩信切换之短信对话框
         if (notify) {
             if (oldState == 0 && mMmsState != 0) {
                 mStatusListener.onProtocolChanged(true);
             } else if (oldState != 0 && mMmsState == 0) {
                 mStatusListener.onProtocolChanged(false);
             }
         }

         if (oldState != mMmsState) {
             if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) LogTag.debug("updateState: %s%s = %s",
                     on ? "+" : "-",
                     stateString(state), stateString(mMmsState));
         }
     }