因为工作需要,再加上个人爱好,经过分析整理出短彩应用中从发送至收到附件为音频的彩信的下载,预览,播放整个流程,给大家一起分享。
第一步,添加附件: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));
}
}