Fields Summary |
---|
private static final String | EXTRA_ACCOUNT |
private static final String | EXTRA_FOLDER |
private static final String | EXTRA_MESSAGE |
private static final String | EXTRA_FOLDER_UIDS |
private static final String | EXTRA_NEXT |
private static final String[] | METHODS_WITH_PRESENCE_PROJECTION |
private static final int | METHODS_STATUS_COLUMN |
private static final Pattern | IMG_TAG_START_REGEX |
private android.widget.TextView | mSubjectView |
private android.widget.TextView | mFromView |
private android.widget.TextView | mDateView |
private android.widget.TextView | mTimeView |
private android.widget.TextView | mToView |
private android.widget.TextView | mCcView |
private android.view.View | mCcContainerView |
private android.webkit.WebView | mMessageContentView |
private android.widget.LinearLayout | mAttachments |
private android.widget.ImageView | mAttachmentIcon |
private android.view.View | mShowPicturesSection |
private android.widget.ImageView | mSenderPresenceView |
private com.android.email.Account | mAccount |
private String | mFolder |
private String | mMessageUid |
private ArrayList | mFolderUids |
private com.android.email.mail.Message | mMessage |
private String | mNextMessageUid |
private String | mPreviousMessageUid |
private DateFormat | mDateFormat |
private DateFormat | mTimeFormat |
private Listener | mListener |
private MessageViewHandler | mHandler |
Methods Summary |
---|
public static void | actionView(android.content.Context context, com.android.email.Account account, java.lang.String folder, java.lang.String messageUid, java.util.ArrayList folderUids)
actionView(context, account, folder, messageUid, folderUids, null);
|
public static void | actionView(android.content.Context context, com.android.email.Account account, java.lang.String folder, java.lang.String messageUid, java.util.ArrayList folderUids, android.os.Bundle extras)
Intent i = new Intent(context, MessageView.class);
i.putExtra(EXTRA_ACCOUNT, account);
i.putExtra(EXTRA_FOLDER, folder);
i.putExtra(EXTRA_MESSAGE, messageUid);
i.putExtra(EXTRA_FOLDER_UIDS, folderUids);
if (extras != null) {
i.putExtras(extras);
}
context.startActivity(i);
|
private java.io.File | createUniqueFile(java.io.File directory, java.lang.String filename)Creates a unique file in the given directory by appending a hyphen
and a number to the given filename.
File file = new File(directory, filename);
if (!file.exists()) {
return file;
}
// Get the extension of the file, if any.
int index = filename.lastIndexOf('.");
String format;
if (index != -1) {
String name = filename.substring(0, index);
String extension = filename.substring(index);
format = name + "-%d" + extension;
}
else {
format = filename + "-%d";
}
for (int i = 2; i < Integer.MAX_VALUE; i++) {
file = new File(directory, String.format(format, i));
if (!file.exists()) {
return file;
}
}
return null;
|
private void | findSurroundingMessagesUid()
for (int i = 0, count = mFolderUids.size(); i < count; i++) {
String messageUid = mFolderUids.get(i);
if (messageUid.equals(mMessageUid)) {
if (i != 0) {
mPreviousMessageUid = mFolderUids.get(i - 1);
}
if (i != count - 1) {
mNextMessageUid = mFolderUids.get(i + 1);
}
break;
}
}
|
public static java.lang.String | formatSize(float size)
long kb = 1024;
long mb = (kb * 1024);
long gb = (mb * 1024);
if (size < kb) {
return String.format("%d bytes", (int) size);
}
else if (size < mb) {
return String.format("%.1f kB", size / kb);
}
else if (size < gb) {
return String.format("%.1f MB", size / mb);
}
else {
return String.format("%.1f GB", size / gb);
}
|
private android.graphics.Bitmap | getPreviewIcon(com.android.email.activity.MessageView$Attachment attachment)
try {
return BitmapFactory.decodeStream(
getContentResolver().openInputStream(
AttachmentProvider.getAttachmentThumbnailUri(mAccount,
attachment.part.getAttachmentId(),
62,
62)));
}
catch (Exception e) {
/*
* We don't care what happened, we just return null for the preview icon.
*/
return null;
}
|
boolean | handleMenuItem(int menuItemId)This is the core functionality of onOptionsItemSelected() but broken out and exposed
for testing purposes (because it's annoying to mock a MenuItem).
switch (menuItemId) {
case R.id.delete:
onDelete();
break;
case R.id.reply:
onReply();
break;
case R.id.reply_all:
onReplyAll();
break;
case R.id.forward:
onForward();
break;
case R.id.mark_as_unread:
onMarkAsUnread();
break;
default:
return false;
}
return true;
|
public void | onClick(android.view.View view)
switch (view.getId()) {
case R.id.from:
case R.id.presence:
onClickSender();
break;
case R.id.reply:
onReply();
break;
case R.id.reply_all:
onReplyAll();
break;
case R.id.delete:
onDelete();
break;
case R.id.next:
onNext();
break;
case R.id.previous:
onPrevious();
break;
case R.id.download:
onDownloadAttachment((Attachment) view.getTag());
break;
case R.id.view:
onViewAttachment((Attachment) view.getTag());
break;
case R.id.show_pictures:
onShowPictures();
break;
}
|
private void | onClickSender()
if (mMessage != null) {
try {
Address senderEmail = mMessage.getFrom()[0];
Uri contactUri = Uri.fromParts("mailto", senderEmail.getAddress(), null);
Intent contactIntent = new Intent(Contacts.Intents.SHOW_OR_CREATE_CONTACT);
contactIntent.setData(contactUri);
// Pass along full E-mail string for possible create dialog
contactIntent.putExtra(Contacts.Intents.EXTRA_CREATE_DESCRIPTION,
senderEmail.toString());
// Only provide personal name hint if we have one
String senderPersonal = senderEmail.getPersonal();
if (senderPersonal != null) {
contactIntent.putExtra(Intents.Insert.NAME, senderPersonal);
}
startActivity(contactIntent);
} catch (MessagingException me) {
if (Config.LOGV) {
Log.v(Email.LOG_TAG, "loadMessageForViewHeadersAvailable", me);
}
}
}
|
public void | onCreate(android.os.Bundle icicle)
super.onCreate(icicle);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.message_view);
mSubjectView = (TextView) findViewById(R.id.subject);
mFromView = (TextView) findViewById(R.id.from);
mToView = (TextView) findViewById(R.id.to);
mCcView = (TextView) findViewById(R.id.cc);
mCcContainerView = findViewById(R.id.cc_container);
mDateView = (TextView) findViewById(R.id.date);
mTimeView = (TextView) findViewById(R.id.time);
mMessageContentView = (WebView) findViewById(R.id.message_content);
mAttachments = (LinearLayout) findViewById(R.id.attachments);
mAttachmentIcon = (ImageView) findViewById(R.id.attachment);
mShowPicturesSection = findViewById(R.id.show_pictures_section);
mSenderPresenceView = (ImageView) findViewById(R.id.presence);
mMessageContentView.setVerticalScrollBarEnabled(false);
mAttachments.setVisibility(View.GONE);
mAttachmentIcon.setVisibility(View.GONE);
mFromView.setOnClickListener(this);
mSenderPresenceView.setOnClickListener(this);
findViewById(R.id.reply).setOnClickListener(this);
findViewById(R.id.reply_all).setOnClickListener(this);
findViewById(R.id.delete).setOnClickListener(this);
findViewById(R.id.show_pictures).setOnClickListener(this);
mMessageContentView.getSettings().setBlockNetworkImage(true);
mMessageContentView.getSettings().setSupportZoom(false);
setTitle("");
mDateFormat = android.text.format.DateFormat.getDateFormat(this); // short format
mTimeFormat = android.text.format.DateFormat.getTimeFormat(this); // 12/24 date format
Intent intent = getIntent();
mAccount = (Account) intent.getSerializableExtra(EXTRA_ACCOUNT);
mFolder = intent.getStringExtra(EXTRA_FOLDER);
mMessageUid = intent.getStringExtra(EXTRA_MESSAGE);
mFolderUids = intent.getStringArrayListExtra(EXTRA_FOLDER_UIDS);
View next = findViewById(R.id.next);
View previous = findViewById(R.id.previous);
/*
* Next and Previous Message are not shown in landscape mode, so
* we need to check before we use them.
*/
if (next != null && previous != null) {
next.setOnClickListener(this);
previous.setOnClickListener(this);
findSurroundingMessagesUid();
previous.setVisibility(mPreviousMessageUid != null ? View.VISIBLE : View.GONE);
next.setVisibility(mNextMessageUid != null ? View.VISIBLE : View.GONE);
boolean goNext = intent.getBooleanExtra(EXTRA_NEXT, false);
if (goNext) {
next.requestFocus();
}
}
MessagingController.getInstance(getApplication()).addListener(mListener);
new Thread() {
@Override
public void run() {
// TODO this is a spot that should be eventually handled by a MessagingController
// thread pool. We want it in a thread but it can't be blocked by the normal
// synchronization stuff in MC.
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
MessagingController.getInstance(getApplication()).loadMessageForView(
mAccount,
mFolder,
mMessageUid,
mListener);
}
}.start();
|
public boolean | onCreateOptionsMenu(android.view.Menu menu)
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.message_view_option, menu);
return true;
|
private void | onDelete()
if (mMessage != null) {
MessagingController.getInstance(getApplication()).deleteMessage(
mAccount,
mFolder,
mMessage,
null);
Toast.makeText(this, R.string.message_deleted_toast, Toast.LENGTH_SHORT).show();
// Remove this message's Uid locally
mFolderUids.remove(mMessage.getUid());
// Check if we have previous/next messages available before choosing
// which one to display
findSurroundingMessagesUid();
if (mPreviousMessageUid != null) {
onPrevious();
} else if (mNextMessageUid != null) {
onNext();
} else {
finish();
}
}
|
public void | onDestroy()We override onDestroy to make sure that the WebView gets explicitly destroyed.
Otherwise it can leak native references.
super.onDestroy();
mMessageContentView.destroy();
mMessageContentView = null;
|
private void | onDownloadAttachment(com.android.email.activity.MessageView$Attachment attachment)
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
/*
* Abort early if there's no place to save the attachment. We don't want to spend
* the time downloading it and then abort.
*/
Toast.makeText(this,
getString(R.string.message_view_status_attachment_not_saved),
Toast.LENGTH_SHORT).show();
return;
}
MessagingController.getInstance(getApplication()).loadAttachment(
mAccount,
mMessage,
attachment.part,
new Object[] { true, attachment },
mListener);
|
private void | onForward()
if (mMessage != null) {
MessageCompose.actionForward(this, mAccount, mMessage);
finish();
}
|
private void | onMarkAsUnread()
if (mMessage != null) {
MessagingController.getInstance(getApplication()).markMessageRead(
mAccount,
mFolder,
mMessage.getUid(),
false);
}
|
private void | onNext()
Bundle extras = new Bundle(1);
extras.putBoolean(EXTRA_NEXT, true);
MessageView.actionView(this, mAccount, mFolder, mNextMessageUid, mFolderUids, extras);
finish();
|
public boolean | onOptionsItemSelected(android.view.MenuItem item)
boolean handled = handleMenuItem(item.getItemId());
if (!handled) {
handled = super.onOptionsItemSelected(item);
}
return handled;
|
public void | onPause()
super.onPause();
MessagingController.getInstance(getApplication()).removeListener(mListener);
|
private void | onPrevious()
MessageView.actionView(this, mAccount, mFolder, mPreviousMessageUid, mFolderUids);
finish();
|
private void | onReply()
if (mMessage != null) {
MessageCompose.actionReply(this, mAccount, mMessage, false);
finish();
}
|
private void | onReplyAll()
if (mMessage != null) {
MessageCompose.actionReply(this, mAccount, mMessage, true);
finish();
}
|
public void | onResume()
super.onResume();
MessagingController.getInstance(getApplication()).addListener(mListener);
if (mMessage != null) {
startPresenceCheck();
}
|
private void | onShowPictures()
if (mMessage != null) {
mMessageContentView.getSettings().setBlockNetworkImage(false);
mShowPicturesSection.setVisibility(View.GONE);
}
|
private void | onViewAttachment(com.android.email.activity.MessageView$Attachment attachment)
MessagingController.getInstance(getApplication()).loadAttachment(
mAccount,
mMessage,
attachment.part,
new Object[] { false, attachment },
mListener);
|
private void | renderAttachments(com.android.email.mail.Part part, int depth)
String contentType = MimeUtility.unfoldAndDecode(part.getContentType());
String name = MimeUtility.getHeaderParameter(contentType, "name");
if (name != null) {
/*
* We're guaranteed size because LocalStore.fetch puts it there.
*/
String contentDisposition = MimeUtility.unfoldAndDecode(part.getDisposition());
int size = Integer.parseInt(MimeUtility.getHeaderParameter(contentDisposition, "size"));
Attachment attachment = new Attachment();
attachment.size = size;
attachment.contentType = part.getMimeType();
attachment.name = name;
attachment.part = (LocalAttachmentBodyPart) part;
LayoutInflater inflater = getLayoutInflater();
View view = inflater.inflate(R.layout.message_view_attachment, null);
TextView attachmentName = (TextView)view.findViewById(R.id.attachment_name);
TextView attachmentInfo = (TextView)view.findViewById(R.id.attachment_info);
ImageView attachmentIcon = (ImageView)view.findViewById(R.id.attachment_icon);
Button attachmentView = (Button)view.findViewById(R.id.view);
Button attachmentDownload = (Button)view.findViewById(R.id.download);
if ((!MimeUtility.mimeTypeMatches(attachment.contentType,
Email.ACCEPTABLE_ATTACHMENT_VIEW_TYPES))
|| (MimeUtility.mimeTypeMatches(attachment.contentType,
Email.UNACCEPTABLE_ATTACHMENT_VIEW_TYPES))) {
attachmentView.setVisibility(View.GONE);
}
if ((!MimeUtility.mimeTypeMatches(attachment.contentType,
Email.ACCEPTABLE_ATTACHMENT_DOWNLOAD_TYPES))
|| (MimeUtility.mimeTypeMatches(attachment.contentType,
Email.UNACCEPTABLE_ATTACHMENT_DOWNLOAD_TYPES))) {
attachmentDownload.setVisibility(View.GONE);
}
if (attachment.size > Email.MAX_ATTACHMENT_DOWNLOAD_SIZE) {
attachmentView.setVisibility(View.GONE);
attachmentDownload.setVisibility(View.GONE);
}
attachment.viewButton = attachmentView;
attachment.downloadButton = attachmentDownload;
attachment.iconView = attachmentIcon;
view.setTag(attachment);
attachmentView.setOnClickListener(this);
attachmentView.setTag(attachment);
attachmentDownload.setOnClickListener(this);
attachmentDownload.setTag(attachment);
attachmentName.setText(name);
attachmentInfo.setText(formatSize(size));
Bitmap previewIcon = getPreviewIcon(attachment);
if (previewIcon != null) {
attachmentIcon.setImageBitmap(previewIcon);
}
mHandler.addAttachment(view);
}
if (part.getBody() instanceof Multipart) {
Multipart mp = (Multipart)part.getBody();
for (int i = 0; i < mp.getCount(); i++) {
renderAttachments(mp.getBodyPart(i), depth + 1);
}
}
|
java.lang.String | resolveInlineImage(java.lang.String text, com.android.email.mail.Part part, int depth)Resolve content-id reference in src attribute of img tag to AttachmentProvider's
content uri. This method calls itself recursively at most the number of
LocalAttachmentPart that mime type is image and has content id.
The attribute src="cid:content_id" is resolved as src="content://...".
This method is package scope for testing purpose.
// avoid too deep recursive call.
if (depth >= 10) {
return text;
}
String contentType = MimeUtility.unfoldAndDecode(part.getContentType());
String contentId = part.getContentId();
if (contentType.startsWith("image/") &&
contentId != null &&
part instanceof LocalAttachmentBodyPart) {
LocalAttachmentBodyPart attachment = (LocalAttachmentBodyPart)part;
Uri contentUri = AttachmentProvider.getAttachmentUri(
mAccount,
attachment.getAttachmentId());
if (contentUri != null) {
// Regexp which matches ' src="cid:contentId"'.
String contentIdRe = "\\s+(?i)src=\"cid(?-i):\\Q" + contentId + "\\E\"";
// Replace all occurrences of src attribute with ' src="content://contentUri"'.
text = text.replaceAll(contentIdRe, " src=\"" + contentUri + "\"");
}
}
if (part.getBody() instanceof Multipart) {
Multipart mp = (Multipart)part.getBody();
for (int i = 0; i < mp.getCount(); i++) {
text = resolveInlineImage(text, mp.getBodyPart(i), depth + 1);
}
}
return text;
|
private void | startPresenceCheck()Launch a thread (because of cross-process DB lookup) to check presence of the sender of the
message. When that thread completes, update the UI.
This must only be called when mMessage is null (it will hide presence indications) or when
mMessage has already seen its headers loaded.
Note: This is just a polling operation. A more advanced solution would be to keep the
cursor open and respond to presence status updates (in the form of content change
notifications). However, because presence changes fairly slowly compared to the duration
of viewing a single message, a simple poll at message load (and onResume) should be
sufficient.
String email = null;
try {
if (mMessage != null) {
Address sender = mMessage.getFrom()[0];
email = sender.getAddress();
}
} catch (MessagingException me) { }
if (email == null) {
mHandler.setSenderPresence(0);
return;
}
final String senderEmail = email;
new Thread() {
@Override
public void run() {
Cursor methodsCursor = getContentResolver().query(
Uri.withAppendedPath(Contacts.ContactMethods.CONTENT_URI, "with_presence"),
METHODS_WITH_PRESENCE_PROJECTION,
Contacts.ContactMethods.DATA + "=?",
new String[]{ senderEmail },
null);
int presenceIcon = 0;
if (methodsCursor != null) {
if (methodsCursor.moveToFirst() &&
!methodsCursor.isNull(METHODS_STATUS_COLUMN)) {
presenceIcon = Presence.getPresenceIconResourceId(
methodsCursor.getInt(METHODS_STATUS_COLUMN));
}
methodsCursor.close();
}
mHandler.setSenderPresence(presenceIcon);
}
}.start();
|
private void | updateSenderPresence(int presenceIconId)Update the actual UI. Must be called from main thread (or handler)
if (presenceIconId == 0) {
// This is a placeholder used for "unknown" presence, including signed off,
// no presence relationship.
presenceIconId = R.drawable.presence_inactive;
}
mSenderPresenceView.setImageResource(presenceIconId);
|