Split up MessageViewFragment like we did to MessageView.

Break MessageViewFragment up into two fragments, MessageViewFragment, which
is used to show regular messages, and MessageFileViewFragment, which shows
EML messages.  (And their base class, MessageViewFragmentBase.)

MessageViewFragmentBase's javadoc has a class diagram.

MessageViewFragment is actually named MessageViewFragment2 at this point
so that GIT correctly finds out the rename from MessageViewFragment to
MessageViewFragmentBase.  I'll rename it back in a following CL.

Also added very basic unit tests for MessageView and MessageFileView.
At this point, they just make sure the activities really open and show
messages without exceptions.

I feel like the current naming schema for the activities/fragments is
kinda confusing.  Let me know if you come up with better names.

Change-Id: Iff948f4b68cfdb7c1e68f225927b0ce58d34766b
This commit is contained in:
Makoto Onuki 2010-07-29 17:06:00 -07:00
parent b8f31d5490
commit 206d4e842d
15 changed files with 824 additions and 315 deletions

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white">
<fragment
android:id="@+id/message_file_view_fragment"
android:name="com.android.email.activity.MessageFileViewFragment"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
/>
</LinearLayout>

View File

@ -22,7 +22,7 @@
android:background="@android:color/white">
<fragment
android:id="@+id/message_view_fragment"
android:name="com.android.email.activity.MessageViewFragment"
android:name="com.android.email.activity.MessageViewFragment2"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"

View File

@ -16,12 +16,13 @@
package com.android.email.activity;
import com.android.email.Email;
import com.android.email.R;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.util.Log;
/**
* Activity to show file-based messages. (i.e. *.eml files, and possibly *.msg files).
@ -33,7 +34,7 @@ import android.view.View;
* <li>No navigating around (no older/newer buttons)
* </ul>
*
* TODO Test it!
* See {@link MessageViewBase} for the class relation diagram.
*/
public class MessageFileView extends MessageViewBase {
/**
@ -41,56 +42,48 @@ public class MessageFileView extends MessageViewBase {
*/
private Uri mFileEmailUri;
private MessageFileViewFragment mFragment;
@Override
protected int getLayoutId() {
return R.layout.message_file_view;
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
mFragment = (MessageFileViewFragment) findFragmentById(R.id.message_file_view_fragment);
mFragment.setCallback(this);
Intent intent = getIntent();
mFileEmailUri = intent.getData();
if (mFileEmailUri == null) {
Log.w(Email.LOG_TAG, "Insufficient intent parameter. Closing...");
finish();
return;
}
// TODO set title here: "Viewing XXX.eml".
// Hide all bottom buttons.
findViewById(R.id.button_panel).setVisibility(View.GONE);
findViewById(R.id.favorite).setVisibility(View.GONE);
}
@Override
public void onResume() {
super.onResume();
// Note: We don't have to close it even if an account has been deleted, unlike MessageView.
// Note: We don't have to close the activity even if an account has been deleted,
// unlike MessageView.
getFragment().openMessage(mFileEmailUri);
}
/** @return always -1, as there's no account associated with EML files. */
/** @return always -1, as no accounts are associated with EML files. */
@Override
protected long getAccountId() {
return -1;
}
// Note EML files can have ICS (calendar invitation) files, but we don't treat them as
// invitations at this point. (Only exchange provider sets the FLAG_INCOMING_MEETING_INVITE
// flag.) At any rate, it'd be weird to respond to an invitation in an EML that might not
// be addressed to you...
// TODO Remove these callbacks below, when breaking down the fragment.
// MessageViewFragment for email files shouldn't have these callbacks.
// Note the return type is a subclass of that of the super class method.
@Override
public void onRespondedToInvite(int response) {
// EML files shouldn't have calender response buttons.
throw new RuntimeException();
}
@Override
public void onCalendarLinkClicked(long epochEventStartTime) {
// EML files shouldn't have the "View in calender" button.
throw new RuntimeException();
}
@Override
public void onMessageSetUnread() {
// EML files shouldn't have the "mark unread" button.
throw new RuntimeException();
protected MessageFileViewFragment getFragment() {
return mFragment;
}
}

View File

@ -0,0 +1,105 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email.activity;
import com.android.email.R;
import com.android.email.Utility;
import com.android.email.provider.EmailContent.Message;
import android.app.Activity;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.security.InvalidParameterException;
/**
* A {@link MessageViewFragmentBase} subclass for file based messages. (aka EML files)
*
* See {@link MessageViewBase} for the class relation diagram.
*/
public class MessageFileViewFragment extends MessageViewFragmentBase {
private Uri mFileEmailUri;
/**
* Loads the layout.
*
* This class uses the same layout as {@link MessageViewFragment2}, but hides the star.
*/
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
view.findViewById(R.id.favorite).setVisibility(View.GONE);
return view;
}
@Override
public void onDestroy() {
super.onDestroy();
// If we're leaving a non-attachment message, delete any/all attachment messages
// TODO We shouldn't remove ALL attachement messages here. Remove only the current one.
getController().deleteAttachmentMessages();
}
/** Called by activities with a URI to an EML file. */
public void openMessage(Uri fileEmailUri) {
if (fileEmailUri == null) {
throw new InvalidParameterException();
}
mFileEmailUri = fileEmailUri;
openMessageIfStarted();
}
@Override
protected boolean isMessageSpecified() {
return mFileEmailUri != null;
}
@Override
protected Message openMessageSync() {
final Activity activity = getActivity();
// Put up a toast; this can take a little while...
Utility.showToast(activity, R.string.message_view_parse_message_toast);
Message msg = getController().loadMessageFromUri(mFileEmailUri);
if (msg == null) {
// Indicate that the attachment couldn't be loaded
Utility.showToast(activity, R.string.message_view_display_attachment_toast);
return null;
}
return msg;
}
/**
* {@inheritDoc}
*
* Does exactly same as the super class method, but does an extra sanity check.
*/
@Override
protected void reloadUiFromMessage(Message message, boolean okToFetch) {
// EML file should never be partially loaded.
if (message.mFlagLoaded != Message.FLAG_LOADED_COMPLETE) {
throw new IllegalStateException();
}
super.reloadUiFromMessage(message, okToFetch);
}
}

View File

@ -285,7 +285,7 @@ MessageListXLFragmentManager.TargetActivity {
}
}
private class MessageViewFragmentCallback implements MessageViewFragment.Callback {
private class MessageViewFragmentCallback implements MessageViewFragment2.Callback {
@Override
public boolean onUrlInMessageClicked(String url) {
return false;

View File

@ -70,11 +70,11 @@ class MessageListXLFragmentManager {
private MailboxListFragment mMailboxListFragment;
private MessageListFragment mMessageListFragment;
private MessageViewFragment mMessageViewFragment;
private MessageViewFragment2 mMessageViewFragment;
private MailboxListFragment.Callback mMailboxListFragmentCallback;
private MessageListFragment.Callback mMessageListFragmentCallback;
private MessageViewFragment.Callback mMessageViewFragmentCallback;
private MessageViewFragment2.Callback mMessageViewFragmentCallback;
/**
* The interface that {@link MessageListXL} implements. We don't call its methods directly,
@ -114,7 +114,7 @@ class MessageListXLFragmentManager {
/** Set callback for fragment. */
public void setMessageViewFragmentCallback(
MessageViewFragment.Callback messageViewFragmentCallback) {
MessageViewFragment2.Callback messageViewFragmentCallback) {
mMessageViewFragmentCallback = messageViewFragmentCallback;
}
@ -202,8 +202,8 @@ class MessageListXLFragmentManager {
updateMailboxListFragment((MailboxListFragment) fragment);
} else if (fragment instanceof MessageListFragment) {
updateMessageListFragment((MessageListFragment) fragment);
} else if (fragment instanceof MessageViewFragment) {
updateMessageViewFragment((MessageViewFragment) fragment);
} else if (fragment instanceof MessageViewFragment2) {
updateMessageViewFragment((MessageViewFragment2) fragment);
}
}
@ -354,7 +354,7 @@ class MessageListXLFragmentManager {
// Update fragments.
if (mMessageViewFragment == null) {
MessageViewFragment f = new MessageViewFragment();
MessageViewFragment2 f = new MessageViewFragment2();
// TODO We want to support message view -> [back] -> message list, but the back behavior
// with addToBackStack() is not too clear. We do it manually for now.
@ -368,7 +368,7 @@ class MessageListXLFragmentManager {
}
}
private void updateMessageViewFragment(MessageViewFragment fragment) {
private void updateMessageViewFragment(MessageViewFragment2 fragment) {
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "updateMessageViewFragment messageId=" + mMessageId);
}

View File

@ -24,6 +24,7 @@ import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
@ -33,13 +34,13 @@ import android.view.View;
*
* This activity shows regular email messages, which are not file-based. (i.e. not *.eml or *.msg)
*
* TODO Test it!
* See {@link MessageViewBase} for the class relation diagram.
*/
public class MessageView extends MessageViewBase implements View.OnClickListener,
MessageOrderManager.Callback {
MessageOrderManager.Callback, MessageViewFragment2.Callback {
private static final String EXTRA_MESSAGE_ID = "com.android.email.MessageView_message_id";
private static final String EXTRA_MAILBOX_ID = "com.android.email.MessageView_mailbox_id";
/* package */ static final String EXTRA_DISABLE_REPLY =
private static final String EXTRA_DISABLE_REPLY =
"com.android.email.MessageView_disable_reply";
// for saveInstanceState()
@ -50,6 +51,8 @@ public class MessageView extends MessageViewBase implements View.OnClickListener
private MessageOrderManager mOrderManager;
private MessageViewFragment2 mFragment;
private View mMoveToNewer;
private View mMoveToOlder;
@ -79,10 +82,18 @@ public class MessageView extends MessageViewBase implements View.OnClickListener
actionView(context, messageId, mailboxId, false);
}
@Override
protected int getLayoutId() {
return R.layout.message_view;
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
mFragment = (MessageViewFragment2) findFragmentById(R.id.message_view_fragment);
mFragment.setCallback(this);
mMoveToNewer = findViewById(R.id.moveToNewer);
mMoveToOlder = findViewById(R.id.moveToOlder);
mMoveToNewer.setOnClickListener(this);
@ -102,6 +113,12 @@ public class MessageView extends MessageViewBase implements View.OnClickListener
Intent intent = getIntent();
mMessageId = intent.getLongExtra(EXTRA_MESSAGE_ID, -1);
mMailboxId = intent.getLongExtra(EXTRA_MAILBOX_ID, -1);
if (mMessageId == -1 || mMailboxId == -1) {
Log.w(Email.LOG_TAG, "Insufficient intent parameter. Closing...");
finish();
return;
}
mDisableReplyAndForward = intent.getBooleanExtra(EXTRA_DISABLE_REPLY, false);
if (mDisableReplyAndForward) {
findViewById(R.id.reply).setEnabled(false);
@ -140,6 +157,12 @@ public class MessageView extends MessageViewBase implements View.OnClickListener
super.onPause();
}
// Note the return type is a subclass of that of the super class method.
@Override
protected MessageViewFragment2 getFragment() {
return mFragment;
}
@Override
protected long getAccountId() {
return getFragment().getAccountId();

View File

@ -29,19 +29,37 @@ import android.provider.Browser;
/**
* Base class for {@link MessageView} and {@link MessageFileView}.
*
* Class relation diagram:
* <pre>
* (activities) (fragments)
* MessageViewBase MessageViewFragmentBase
* | | (with nested interface Callback)
* | |
* |-- MessageFileView -- owns --> |-- MessageFileViewFragment : For EML files.
* | | (with nested interface Callback, which implements
* | | MessageViewFragmentBase.Callback)
* | |
* |-- MessageView -- owns --> |-- MessageViewFragment : For regular messages
*
* MessageView is basically same as MessageFileView, but has more operations, such as "delete",
* "forward", "reply", etc.
*
* Similarly, MessageViewFragment has more operations than MessageFileViewFragment does, such as
* "mark unread", "respond to invite", etc. Also its Callback interface has more method than
* MessageViewFragmentBase.Callback does, for the extra operations.
* </pre>
*/
public abstract class MessageViewBase extends Activity implements MessageViewFragment.Callback {
public abstract class MessageViewBase extends Activity implements MessageViewFragmentBase.Callback {
private ProgressDialog mFetchAttachmentProgressDialog;
private MessageViewFragment mMessageViewFragment;
private Controller mController;
protected abstract int getLayoutId();
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.message_view);
mMessageViewFragment = (MessageViewFragment) findFragmentById(R.id.message_view_fragment);
mMessageViewFragment.setCallback(this);
setContentView(getLayoutId());
// TODO Turn it into a "managed" dialog?
// Managed dialogs survive activity re-creation. (e.g. orientation change)
@ -71,9 +89,7 @@ public abstract class MessageViewBase extends Activity implements MessageViewFra
return mController;
}
protected MessageViewFragment getFragment() {
return mMessageViewFragment;
}
protected abstract MessageViewFragmentBase getFragment();
/**
* @return the account id for the current message, or -1 if there's no account associated with.
@ -159,13 +175,4 @@ public abstract class MessageViewBase extends Activity implements MessageViewFra
public void onMessageNotExists() { // Probably meessage deleted.
finish();
}
@Override
public abstract void onRespondedToInvite(int response);
@Override
public abstract void onCalendarLinkClicked(long epochEventStartTime);
@Override
public abstract void onMessageSetUnread();
}

View File

@ -0,0 +1,273 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email.activity;
import com.android.email.Email;
import com.android.email.R;
import com.android.email.Utility;
import com.android.email.mail.MeetingInfo;
import com.android.email.mail.PackedString;
import com.android.email.provider.EmailContent.Message;
import com.android.email.service.EmailServiceConstants;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import java.security.InvalidParameterException;
/**
* A {@link MessageViewFragmentBase} subclass for regular email messages. (regular as in "not eml
* files").
*
* See {@link MessageViewBase} for the class relation diagram.
*
* TODO Rename it to MessageViewFragment. "2" was added so that GIT correctly detects the rename
* from MessageViewFragment to MessageViewFragmentBase.
*/
public class MessageViewFragment2 extends MessageViewFragmentBase {
private ImageView mFavoriteIcon;
private View mInviteSection;
// calendar meeting invite answers
private TextView mMeetingYes;
private TextView mMeetingMaybe;
private TextView mMeetingNo;
private int mPreviousMeetingResponse = -1;
private Drawable mFavoriteIconOn;
private Drawable mFavoriteIconOff;
private long mMessageIdToOpen = -1;
/**
* This class has more call backs than {@link MessageViewFragmentBase}.
*
* - EML files can't be "mark unread".
* - EML files can't have the invite buttons or the view in calender link.
* Note EML files can have ICS (calendar invitation) files, but we don't treat them as
* invites. (Only exchange provider sets the FLAG_INCOMING_MEETING_INVITE
* flag.)
* It'd be weird to respond to an invitation in an EML that might not be addressed to you...
*/
public interface Callback extends MessageViewFragmentBase.Callback {
/** Called when the "view in calendar" link is clicked. */
public void onCalendarLinkClicked(long epochEventStartTime);
/**
* Called when a calender response button is clicked.
*
* @param response one of {@link EmailServiceConstants#MEETING_REQUEST_ACCEPTED},
* {@link EmailServiceConstants#MEETING_REQUEST_DECLINED}, or
* {@link EmailServiceConstants#MEETING_REQUEST_TENTATIVE}.
*/
public void onRespondedToInvite(int response);
/** Called when the current message is set unread. */
public void onMessageSetUnread();
}
public static final class EmptyCallback extends MessageViewFragmentBase.EmptyCallback
implements Callback {
public static final Callback INSTANCE = new EmptyCallback();
@Override
public void onCalendarLinkClicked(long epochEventStartTime) {
}
@Override
public void onMessageSetUnread() {
}
@Override
public void onRespondedToInvite(int response) {
}
}
private Callback mCallback = EmptyCallback.INSTANCE;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Resources res = getActivity().getResources();
mFavoriteIconOn = res.getDrawable(R.drawable.btn_star_big_buttonless_on);
mFavoriteIconOff = res.getDrawable(R.drawable.btn_star_big_buttonless_off);
}
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View view = super.onCreateView(inflater, container, savedInstanceState);
mFavoriteIcon = (ImageView) view.findViewById(R.id.favorite);
mInviteSection = view.findViewById(R.id.invite_section);
mFavoriteIcon.setOnClickListener(this);
mMeetingYes = (TextView) view.findViewById(R.id.accept);
mMeetingMaybe = (TextView) view.findViewById(R.id.maybe);
mMeetingNo = (TextView) view.findViewById(R.id.decline);
mMeetingYes.setOnClickListener(this);
mMeetingMaybe.setOnClickListener(this);
mMeetingNo.setOnClickListener(this);
view.findViewById(R.id.invite_link).setOnClickListener(this);
return view;
}
public void setCallback(Callback callback) {
mCallback = (callback == null) ? EmptyCallback.INSTANCE : callback;
super.setCallback(mCallback);
}
/** Called by activities to set an id of a message to open. */
public void openMessage(long messageId) {
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "MessageViewFragment openMessage");
}
if (messageId == -1) {
throw new InvalidParameterException();
}
mMessageIdToOpen = messageId;
openMessageIfStarted();
}
@Override
protected boolean isMessageSpecified() {
return mMessageIdToOpen != -1;
}
@Override
protected Message openMessageSync() {
return Message.restoreMessageWithId(getActivity(), mMessageIdToOpen);
}
/**
* Toggle favorite status and write back to provider
*/
private void onClickFavorite() {
Message message = getMessage();
// Update UI
boolean newFavorite = ! message.mFlagFavorite;
mFavoriteIcon.setImageDrawable(newFavorite ? mFavoriteIconOn : mFavoriteIconOff);
// Update provider
message.mFlagFavorite = newFavorite;
getController().setMessageFavorite(message.mId, newFavorite);
}
/**
* Set message read/unread.
*/
public void onMarkMessageAsRead(boolean isRead) {
Message message = getMessage();
if (message.mFlagRead != isRead) {
message.mFlagRead = isRead;
getController().setMessageRead(message.mId, isRead);
if (!isRead) { // Became unread. We need to close the message.
mCallback.onMessageSetUnread();
}
}
}
/**
* Send a service message indicating that a meeting invite button has been clicked.
*/
private void onRespondToInvite(int response, int toastResId) {
Message message = getMessage();
// do not send twice in a row the same response
if (mPreviousMeetingResponse != response) {
getController().sendMeetingResponse(message.mId, response);
mPreviousMeetingResponse = response;
}
Utility.showToast(getActivity(), toastResId);
mCallback.onRespondedToInvite(response);
}
private void onInviteLinkClicked() {
Message message = getMessage();
String startTime = new PackedString(message.mMeetingInfo).get(MeetingInfo.MEETING_DTSTART);
if (startTime != null) {
long epochTimeMillis = Utility.parseEmailDateTimeToMillis(startTime);
mCallback.onCalendarLinkClicked(epochTimeMillis);
} else {
Email.log("meetingInfo without DTSTART " + message.mMeetingInfo);
}
}
@Override
public void onClick(View view) {
if (!isMessageOpen()) {
return; // Ignore.
}
switch (view.getId()) {
case R.id.favorite:
onClickFavorite();
return;
case R.id.accept:
onRespondToInvite(EmailServiceConstants.MEETING_REQUEST_ACCEPTED,
R.string.message_view_invite_toast_yes);
return;
case R.id.maybe:
onRespondToInvite(EmailServiceConstants.MEETING_REQUEST_TENTATIVE,
R.string.message_view_invite_toast_maybe);
return;
case R.id.decline:
onRespondToInvite(EmailServiceConstants.MEETING_REQUEST_DECLINED,
R.string.message_view_invite_toast_no);
return;
case R.id.invite_link:
onInviteLinkClicked();
return;
}
super.onClick(view);
}
/**
* {@inheritDoc}
*
* Mark the current as unread.
*/
@Override
protected void onPostLoadBody() {
onMarkMessageAsRead(true);
}
/**
* {@inheritDoc}
*
* - Update the favorite star icon.
* - Show the invite section if necessary.
*/
@Override
protected void reloadUiFromMessage(Message message, boolean okToFetch) {
super.reloadUiFromMessage(message, okToFetch);
mFavoriteIcon.setImageDrawable(message.mFlagFavorite ? mFavoriteIconOn : mFavoriteIconOff);
// Show the message invite section if we're an incoming meeting invitation only
mInviteSection.setVisibility((message.mFlags & Message.FLAG_INCOMING_MEETING_INVITE) != 0 ?
View.VISIBLE : View.GONE);
}
}

View File

@ -22,29 +22,23 @@ import com.android.email.Email;
import com.android.email.R;
import com.android.email.Utility;
import com.android.email.mail.Address;
import com.android.email.mail.MeetingInfo;
import com.android.email.mail.MessagingException;
import com.android.email.mail.PackedString;
import com.android.email.mail.internet.EmailHtmlUtil;
import com.android.email.mail.internet.MimeUtility;
import com.android.email.provider.AttachmentProvider;
import com.android.email.provider.EmailContent.Attachment;
import com.android.email.provider.EmailContent.Body;
import com.android.email.provider.EmailContent.Message;
import com.android.email.service.EmailServiceConstants;
import org.apache.commons.io.IOUtils;
import android.app.Activity;
import android.app.Fragment;
import android.content.ActivityNotFoundException;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
@ -77,7 +71,12 @@ import java.util.regex.Pattern;
// TODO Restore "Show pictures" state and scroll position on rotation.
public class MessageViewFragment extends Fragment implements View.OnClickListener {
/**
* Base class for {@link MessageViewFragment2} and {@link MessageFileViewFragment}.
*
* See {@link MessageViewBase} for the class relation diagram.
*/
public abstract class MessageViewFragmentBase extends Fragment implements View.OnClickListener {
private Context mContext;
// Regex that matches start of img tag. '<(?i)img\s+'.
@ -98,30 +97,10 @@ public class MessageViewFragment extends Fragment implements View.OnClickListene
private WebView mMessageContentView;
private LinearLayout mAttachments;
private ImageView mAttachmentIcon;
private ImageView mFavoriteIcon;
private View mShowPicturesSection;
private View mInviteSection;
private ImageView mSenderPresenceView;
private View mScrollView;
// calendar meeting invite answers
private TextView mMeetingYes;
private TextView mMeetingMaybe;
private TextView mMeetingNo;
private int mPreviousMeetingResponse = -1;
/**
* If set, URI to the email (i.e. *.eml files, and possibly *.msg files) file that's being
* viewed.
*
* Use {@link #isViewingEmailFile()} to see if the activity is created for opening an EML file.
*
* TODO: We probably should split it into two different MessageViews, one for regular messages
* and the other for for EML files (these two will share the same base MessageView class) to
* eliminate the bunch of 'if {@link #isViewingEmailFile()}'s.
* Do this after making it into a fragment.
*/
private Uri mFileEmailUri;
private long mAccountId = -1;
private long mMessageId = -1;
private Message mMessage;
@ -138,9 +117,6 @@ public class MessageViewFragment extends Fragment implements View.OnClickListene
private java.text.DateFormat mDateFormat;
private java.text.DateFormat mTimeFormat;
private Drawable mFavoriteIconOn;
private Drawable mFavoriteIconOff;
private Controller mController;
private ControllerResultUiThreadWrapper<ControllerResults> mControllerCallback;
@ -153,6 +129,8 @@ public class MessageViewFragment extends Fragment implements View.OnClickListene
private boolean mStarted;
private boolean mIsMessageLoadedForTest;
/**
* Encapsulates known information about a single attachment.
*/
@ -178,9 +156,6 @@ public class MessageViewFragment extends Fragment implements View.OnClickListene
/** Called when the message specified doesn't exist. */
public void onMessageNotExists();
/** Called when the "view in calendar" link is clicked. */
public void onCalendarLinkClicked(long epochEventStartTime);
/** Called when it starts loading a message. */
public void onLoadMessageStarted();
@ -190,18 +165,6 @@ public class MessageViewFragment extends Fragment implements View.OnClickListene
/** Called when an error occurred during loading a message. */
public void onLoadMessageError();
/** Called when the current message is set unread. */
public void onMessageSetUnread();
/**
* Called when a calender response button is clicked.
*
* @param response one of {@link EmailServiceConstants#MEETING_REQUEST_ACCEPTED},
* {@link EmailServiceConstants#MEETING_REQUEST_DECLINED}, or
* {@link EmailServiceConstants#MEETING_REQUEST_TENTATIVE}.
*/
public void onRespondedToInvite(int response);
/** Called when it starts loading an attachment. */
public void onFetchAttachmentStarted(String attachmentName);
@ -212,13 +175,9 @@ public class MessageViewFragment extends Fragment implements View.OnClickListene
public void onFetchAttachmentError();
}
private static final class EmptyCallback implements Callback {
public static class EmptyCallback implements Callback {
public static final Callback INSTANCE = new EmptyCallback();
@Override
public void onCalendarLinkClicked(long epochEventStartTime) {
}
@Override
public void onFetchAttachmentError() {
}
@ -247,14 +206,6 @@ public class MessageViewFragment extends Fragment implements View.OnClickListene
public void onMessageNotExists() {
}
@Override
public void onMessageSetUnread() {
}
@Override
public void onRespondedToInvite(int response) {
}
@Override
public boolean onUrlInMessageClicked(String url) {
return false;
@ -279,10 +230,6 @@ public class MessageViewFragment extends Fragment implements View.OnClickListene
mDateFormat = android.text.format.DateFormat.getDateFormat(mContext); // short format
mTimeFormat = android.text.format.DateFormat.getTimeFormat(mContext); // 12/24 date format
final Resources res = mContext.getResources();
mFavoriteIconOn = res.getDrawable(R.drawable.btn_star_big_buttonless_on);
mFavoriteIconOff = res.getDrawable(R.drawable.btn_star_big_buttonless_off);
mController = Controller.getInstance(mContext);
}
@ -304,26 +251,14 @@ public class MessageViewFragment extends Fragment implements View.OnClickListene
mMessageContentView = (WebView) view.findViewById(R.id.message_content);
mAttachments = (LinearLayout) view.findViewById(R.id.attachments);
mAttachmentIcon = (ImageView) view.findViewById(R.id.attachment);
mFavoriteIcon = (ImageView) view.findViewById(R.id.favorite);
mShowPicturesSection = view.findViewById(R.id.show_pictures_section);
mInviteSection = view.findViewById(R.id.invite_section);
mSenderPresenceView = (ImageView) view.findViewById(R.id.presence);
mScrollView = view.findViewById(R.id.scrollview);
mFromView.setOnClickListener(this);
mSenderPresenceView.setOnClickListener(this);
mFavoriteIcon.setOnClickListener(this);
view.findViewById(R.id.show_pictures).setOnClickListener(this);
mMeetingYes = (TextView) view.findViewById(R.id.accept);
mMeetingMaybe = (TextView) view.findViewById(R.id.maybe);
mMeetingNo = (TextView) view.findViewById(R.id.decline);
mMeetingYes.setOnClickListener(this);
mMeetingMaybe.setOnClickListener(this);
mMeetingNo.setOnClickListener(this);
view.findViewById(R.id.invite_link).setOnClickListener(this);
mMessageContentView.setVerticalScrollBarEnabled(false);
mMessageContentView.getSettings().setBlockNetworkLoads(true);
mMessageContentView.getSettings().setSupportZoom(false);
@ -347,8 +282,8 @@ public class MessageViewFragment extends Fragment implements View.OnClickListene
}
super.onStart();
mStarted = true;
if (mMessageId != -1 || mFileEmailUri != null) {
openMessageInternal();
if (isMessageSpecified()) {
openMessageIfStarted();
}
}
@ -386,15 +321,6 @@ public class MessageViewFragment extends Fragment implements View.OnClickListene
cancelAllTasks();
mMessageContentView.destroy();
mMessageContentView = null;
// If we're leaving a non-attachment message, delete any/all attachment messages
// TODO It's probably wronn. We can show an EML in other app's stack, in which case
// we can task-switch between the main app and the activity showing an EML.
// We probably have to keep track of the number of EMLs currently open in a static field.
if (!isViewingEmailFile()) {
mController.deleteAttachmentMessages();
}
super.onDestroy();
}
@ -423,44 +349,38 @@ public class MessageViewFragment extends Fragment implements View.OnClickListene
}
/**
* @return true if viewing an email file. (i.e. *.eml files)
* Subclass returns true if which message to open is already specified by the activity.
*/
private boolean isViewingEmailFile() {
return mFileEmailUri != null;
protected abstract boolean isMessageSpecified();
protected final Controller getController() {
return mController;
}
protected final Callback getCallback() {
return mCallback;
}
protected final Message getMessage() {
return mMessage;
}
protected final boolean isMessageOpen() {
return mMessage != null;
}
/**
* Returns the account id of the current message, or -1 if unknown.
* Probably doesn't make sense if {@link #isViewingEmailFile()}.
* Returns the account id of the current message, or -1 if unknown (message not open yet, or
* viewing an EML message).
*/
public long getAccountId() {
return mAccountId;
}
/** Called by activities to set an id of a message to open. */
public void openMessage(long messageId) {
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "MessageViewFragment openMessage");
protected void openMessageIfStarted() {
if (!mStarted) {
return;
}
mFileEmailUri = null;
mMessageId = messageId;
mAccountId = -1;
if (mStarted) {
openMessageInternal();
}
}
/** Called by activities to a URI to an EML file to open. */
public void openMessage(Uri fileEmailUri) {
mFileEmailUri = fileEmailUri;
mMessageId = -1;
mAccountId = -1;
if (mStarted) {
openMessageInternal();
}
}
private void openMessageInternal() {
cancelAllTasks();
if (mMessageContentView != null) {
mMessageContentView.getSettings().setBlockNetworkLoads(true);
@ -471,7 +391,7 @@ public class MessageViewFragment extends Fragment implements View.OnClickListene
mAttachments.removeAllViews();
mAttachments.setVisibility(View.GONE);
mAttachmentIcon.setVisibility(View.GONE);
mLoadMessageTask = new LoadMessageTask(mFileEmailUri, mMessageId, true);
mLoadMessageTask = new LoadMessageTask(true);
mLoadMessageTask.execute();
}
@ -482,9 +402,6 @@ public class MessageViewFragment extends Fragment implements View.OnClickListene
* TODO Move DB lookup to a worker thread.
*/
private void onClickSender() {
// Bail early if message or sender not present
if (mMessage == null) return;
final Address senderEmail = Address.unpackFirst(mMessage.mFrom);
if (senderEmail == null) return;
@ -520,56 +437,6 @@ public class MessageViewFragment extends Fragment implements View.OnClickListene
}
}
/**
* Toggle favorite status and write back to provider
*/
private void onClickFavorite() {
if (isViewingEmailFile()) {
return;
}
if (mMessage != null) {
// Update UI
boolean newFavorite = ! mMessage.mFlagFavorite;
mFavoriteIcon.setImageDrawable(newFavorite ? mFavoriteIconOn : mFavoriteIconOff);
// Update provider
mMessage.mFlagFavorite = newFavorite;
mController.setMessageFavorite(mMessageId, newFavorite);
}
}
/**
* Set message read/unread.
*/
public void onMarkMessageAsRead(boolean isRead) {
if (isViewingEmailFile()) {
return;
}
if (mMessage != null && mMessage.mFlagRead != isRead) {
mMessage.mFlagRead = isRead;
mController.setMessageRead(mMessageId, isRead);
if (!isRead) { // Became unread. We need to close the message.
mCallback.onMessageSetUnread();
}
}
}
/**
* Send a service message indicating that a meeting invite button has been clicked.
*/
private void onRespondToInvite(int response, int toastResId) {
if (isViewingEmailFile()) {
return;
}
// do not send twice in a row the same response
if (mPreviousMeetingResponse != response) {
mController.sendMeetingResponse(mMessageId, response);
mPreviousMeetingResponse = response;
}
Utility.showToast(getActivity(), toastResId);
mCallback.onRespondedToInvite(response);
}
private void onDownloadAttachment(AttachmentInfo attachment) {
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
/*
@ -646,41 +513,26 @@ public class MessageViewFragment extends Fragment implements View.OnClickListene
}
private void onShowPicturesInHtml() {
if (mMessage != null) {
if (mMessageContentView != null) {
mMessageContentView.getSettings().setBlockNetworkLoads(false);
if (mHtmlTextWebView != null) {
mMessageContentView.loadDataWithBaseURL("email://", mHtmlTextWebView,
"text/html", "utf-8", null);
}
if (mMessageContentView != null) {
mMessageContentView.getSettings().setBlockNetworkLoads(false);
if (mHtmlTextWebView != null) {
mMessageContentView.loadDataWithBaseURL("email://", mHtmlTextWebView,
"text/html", "utf-8", null);
}
mShowPicturesSection.setVisibility(View.GONE);
}
}
private void onInviteLinkClicked() {
if (isViewingEmailFile()) {
return;
}
String startTime = new PackedString(mMessage.mMeetingInfo).get(MeetingInfo.MEETING_DTSTART);
if (startTime != null) {
long epochTimeMillis = Utility.parseEmailDateTimeToMillis(startTime);
mCallback.onCalendarLinkClicked(epochTimeMillis);
} else {
Email.log("meetingInfo without DTSTART " + mMessage.mMeetingInfo);
}
mShowPicturesSection.setVisibility(View.GONE);
}
@Override
public void onClick(View view) {
if (!isMessageOpen()) {
return; // Ignore.
}
switch (view.getId()) {
case R.id.from:
case R.id.presence:
onClickSender();
break;
case R.id.favorite:
onClickFavorite();
break;
case R.id.download:
onDownloadAttachment((AttachmentInfo) view.getTag());
break;
@ -690,21 +542,6 @@ public class MessageViewFragment extends Fragment implements View.OnClickListene
case R.id.show_pictures:
onShowPicturesInHtml();
break;
case R.id.accept:
onRespondToInvite(EmailServiceConstants.MEETING_REQUEST_ACCEPTED,
R.string.message_view_invite_toast_yes);
break;
case R.id.maybe:
onRespondToInvite(EmailServiceConstants.MEETING_REQUEST_TENTATIVE,
R.string.message_view_invite_toast_maybe);
break;
case R.id.decline:
onRespondToInvite(EmailServiceConstants.MEETING_REQUEST_DECLINED,
R.string.message_view_invite_toast_no);
break;
case R.id.invite_link:
onInviteLinkClicked();
break;
}
}
@ -737,48 +574,29 @@ public class MessageViewFragment extends Fragment implements View.OnClickListene
}
}
/**
* Called on a worker thread by {@link LoadMessageTask} to load a message in a subclass specific
* way.
*/
protected abstract Message openMessageSync();
/**
* Async task for loading a single message outside of the UI thread
*/
private class LoadMessageTask extends AsyncTask<Void, Void, Message> {
private final long mId;
private final boolean mOkToFetch;
private final Uri mFileEmailUri;
/**
* Special constructor to cache some local info
*/
public LoadMessageTask(Uri fileEmailUri, long messageId, boolean okToFetch) {
mFileEmailUri = fileEmailUri;
mId = messageId;
public LoadMessageTask(boolean okToFetch) {
mOkToFetch = okToFetch;
}
/**
* There will either be a Uri in the Intent (i.e. whose content is the Message to be
* loaded), or mId will be holding the id of the Message as stored in the provider.
* If we're loading via Uri, the Controller does the actual message parsing and storage,
* and we setup the message id and mailbox id based on the result; forward and reply are
* disabled for messages loaded via Uri
*/
@Override
protected Message doInBackground(Void... params) {
// If we have a URI, then we were opened via an intent filter (e.g. an attachment that
// has a mime type that we can handle (e.g. message/rfc822).
if (mFileEmailUri != null) {
final Activity activity = getActivity();
// Put up a toast; this can take a little while...
Utility.showToast(activity, R.string.message_view_parse_message_toast);
Message msg = mController.loadMessageFromUri(mFileEmailUri);
if (msg == null) {
// Indicate that the attachment couldn't be loaded
Utility.showToast(activity, R.string.message_view_display_attachment_toast);
return null;
}
return msg;
}
return Message.restoreMessageWithId(mContext, mId);
return openMessageSync();
}
@Override
@ -807,6 +625,12 @@ public class MessageViewFragment extends Fragment implements View.OnClickListene
}
}
/**
* Called when the message body is loaded.
*/
protected void onPostLoadBody() {
}
/**
* Async task for loading a single message body outside of the UI thread
*/
@ -849,7 +673,7 @@ public class MessageViewFragment extends Fragment implements View.OnClickListene
return;
}
reloadUiFromBody(results[0], results[1]); // text, html
onMarkMessageAsRead(true);
onPostLoadBody();
}
}
@ -994,7 +818,7 @@ public class MessageViewFragment extends Fragment implements View.OnClickListene
*
* TODO: trigger presence check
*/
private void reloadUiFromMessage(Message message, boolean okToFetch) {
protected void reloadUiFromMessage(Message message, boolean okToFetch) {
mMessage = message;
mAccountId = message.mAccountKey;
@ -1008,10 +832,6 @@ public class MessageViewFragment extends Fragment implements View.OnClickListene
mCcView.setText(friendlyCc);
mCcContainerView.setVisibility((friendlyCc != null) ? View.VISIBLE : View.GONE);
mAttachmentIcon.setVisibility(message.mAttachments != null ? View.VISIBLE : View.GONE);
mFavoriteIcon.setImageDrawable(message.mFlagFavorite ? mFavoriteIconOn : mFavoriteIconOff);
// Show the message invite section if we're an incoming meeting invitation only
mInviteSection.setVisibility((message.mFlags & Message.FLAG_INCOMING_MEETING_INVITE) != 0 ?
View.VISIBLE : View.GONE);
// Handle partially-loaded email, as follows:
// 1. Check value of message.mFlagLoaded
@ -1102,6 +922,8 @@ public class MessageViewFragment extends Fragment implements View.OnClickListene
// Ask for attachments after body
mLoadAttachmentsTask = new LoadAttachmentsTask();
mLoadAttachmentsTask.execute(mMessage.mId);
mIsMessageLoadedForTest = true;
}
/**
@ -1144,7 +966,7 @@ public class MessageViewFragment extends Fragment implements View.OnClickListene
// reload UI and reload everything else too
// pass false to LoadMessageTask to prevent looping here
cancelAllTasks();
mLoadMessageTask = new LoadMessageTask(mFileEmailUri, mMessageId, false);
mLoadMessageTask = new LoadMessageTask(false);
mLoadMessageTask.execute();
break;
default:
@ -1211,4 +1033,12 @@ public class MessageViewFragment extends Fragment implements View.OnClickListene
}
}
}
public boolean isMessageLoadedForTest() {
return mIsMessageLoadedForTest;
}
public void clearIsMessageLoadedForTest() {
mIsMessageLoadedForTest = true;
}
}

View File

@ -38,11 +38,11 @@ import java.util.Locale;
/**
* Tests of the Controller class that depend on the underlying provider.
*
*
* NOTE: It would probably make sense to rewrite this using a MockProvider, instead of the
* ProviderTestCase (which is a real provider running on a temp database). This would be more of
* a true "unit test".
*
*
* You can run this entire test case with:
* runtest -c com.android.email.ControllerProviderOpsTests email
*/
@ -119,12 +119,12 @@ public class ControllerProviderOpsTests extends ProviderTestCase2<EmailProvider>
Controller ct = new TestController(mProviderContext, mContext);
ct.createMailbox(accountId, Mailbox.TYPE_DRAFTS);
long boxId = Mailbox.findMailboxOfType(mProviderContext, accountId, Mailbox.TYPE_DRAFTS);
// check that the drafts mailbox exists
assertTrue("mailbox exists", boxId != Mailbox.NO_MAILBOX);
}
/**
/**
* Test of Controller.findOrCreateMailboxOfType().
* Checks:
* - finds correctly the ID of existing mailbox
@ -154,7 +154,7 @@ public class ControllerProviderOpsTests extends ProviderTestCase2<EmailProvider>
// check it doesn't create twice when existing
long boxId3 = ct.findOrCreateMailboxOfType(accountId, Mailbox.TYPE_DRAFTS);
assertEquals("don't create if exists", boxId3, boxId2);
// check invalid aruments
assertEquals(Mailbox.NO_MAILBOX, ct.findOrCreateMailboxOfType(-1, Mailbox.TYPE_DRAFTS));
assertEquals(Mailbox.NO_MAILBOX, ct.findOrCreateMailboxOfType(accountId, -1));
@ -313,7 +313,7 @@ public class ControllerProviderOpsTests extends ProviderTestCase2<EmailProvider>
Message.MAILBOX_KEY + "=?", new String[] {Long.toString(box.mId)}));
}
public void testLoadMessageFromUri() throws IOException, MessagingException {
public void testLoadMessageFromUri() throws Exception {
// Create a simple message
Message msg = new Message();
String text = "This is some text";
@ -323,16 +323,12 @@ public class ControllerProviderOpsTests extends ProviderTestCase2<EmailProvider>
// Save this away
msg.save(mProviderContext);
// Write out the message in rfc822 format
File outputFile = File.createTempFile("message", "tmp", mContext.getFilesDir());
assertNotNull(outputFile);
FileOutputStream outputStream = new FileOutputStream(outputFile);
Rfc822Output.writeTo(mProviderContext, msg.mId, outputStream, false, false);
outputStream.close();
Uri fileUri = ProviderTestUtils.createTempEmlFile(mProviderContext, msg,
mContext.getFilesDir());
// Load the message via Controller and a Uri
Controller ct = new TestController(mProviderContext, mContext);
Message loadedMsg = ct.loadMessageFromUri(Uri.fromFile(outputFile));
Message loadedMsg = ct.loadMessageFromUri(fileUri);
// Check server id, mailbox key, account key, and from
assertNotNull(loadedMsg);

View File

@ -96,6 +96,13 @@ public class TestUtils extends TestCase /* It tests itself */ {
}, Exception.class);
}
/**
* Wait until a {@code Condition} is met.
*/
public static void waitUntil(Condition condition, int timeoutSeconds) {
waitUntil("", condition, timeoutSeconds);
}
/**
* Wait until a {@code Condition} is met.
*/

View File

@ -0,0 +1,111 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email.activity;
import com.android.email.DBTestHelper;
import com.android.email.TestUtils;
import com.android.email.provider.EmailContent.Message;
import com.android.email.provider.EmailProvider;
import com.android.email.provider.ProviderTestUtils;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.test.ActivityInstrumentationTestCase2;
/**
* Test case for {@link MessageTestView}.
*
* TODO Add more tests. Any good way to test fragment??
*/
public class MessageFileViewTest extends ActivityInstrumentationTestCase2<MessageFileView> {
private static int TIMEOUT = 10; // in seconds
private Context mProviderContext;
public MessageFileViewTest() {
super(MessageFileView.class);
}
@Override
protected void setUp() throws Exception {
super.setUp();
mProviderContext = DBTestHelper.ProviderContextSetupHelper.getProviderContext(
getInstrumentation().getTargetContext(), EmailProvider.class);
}
private void setUpIntent(Uri uri) {
final Intent i = new Intent(getInstrumentation().getTargetContext(), MessageView.class);
i.setData(uri);
setActivityIntent(i);
}
/**
* Open the activity without setting an Intent.
*
* Expected: Activity will close itself.
*/
public void testCreateWithoutParamter() throws Throwable {
// No intent parameters specified. The activity will close itself.
final MessageFileView activity = getActivity();
TestUtils.waitUntil("", new TestUtils.Condition() {
@Override
public boolean isMet() {
return activity.isFinishing();
}
}, TIMEOUT);
}
private Uri createEmlFile() throws Exception {
// Create a simple message
Message msg = new Message();
String text = "This is some text";
msg.mText = text;
String sender = "sender@host.com";
msg.mFrom = sender;
// Save this away
msg.save(mProviderContext);
return ProviderTestUtils.createTempEmlFile(mProviderContext, msg,
getInstrumentation().getContext().getFilesDir());
}
/**
* Set up an EML file, and open it in the activity.
*
* Expected: Message opens.
*/
public void testOpenMessage() throws Exception {
setUpIntent(createEmlFile());
final MessageFileView activity = getActivity();
TestUtils.waitUntil(new TestUtils.Condition() {
@Override
public boolean isMet() {
MessageFileViewFragment f = activity.getFragment();
return f != null && f.isMessageLoadedForTest();
}
}, TIMEOUT);
// TODO Check UI elements, once our UI is settled.
}
}

View File

@ -0,0 +1,113 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email.activity;
import com.android.email.DBTestHelper;
import com.android.email.TestUtils;
import com.android.email.provider.EmailContent.Account;
import com.android.email.provider.EmailContent.Mailbox;
import com.android.email.provider.EmailContent.Message;
import com.android.email.provider.EmailProvider;
import com.android.email.provider.ProviderTestUtils;
import android.content.Context;
import android.content.Intent;
import android.test.ActivityInstrumentationTestCase2;
/**
* Test case for {@link MessageView}.
*
* TODO Add more tests. Any good way to test fragment??
*/
public class MessageViewTest extends ActivityInstrumentationTestCase2<MessageView> {
private static final String EXTRA_MESSAGE_ID = "com.android.email.MessageView_message_id";
private static final String EXTRA_DISABLE_REPLY =
"com.android.email.MessageView_disable_reply";
private static final String EXTRA_MAILBOX_ID = "com.android.email.MessageView_mailbox_id";
private static int TIMEOUT = 10; // in seconds
private Context mProviderContext;
public MessageViewTest() {
super(MessageView.class);
}
@Override
protected void setUp() throws Exception {
super.setUp();
mProviderContext = DBTestHelper.ProviderContextSetupHelper.getProviderContext(
getInstrumentation().getTargetContext(), EmailProvider.class);
}
private void setUpIntent(long messageId, long mailboxId, boolean disableReply) {
final Intent i = new Intent(getInstrumentation().getTargetContext(), MessageView.class);
i.putExtra(EXTRA_MESSAGE_ID, messageId);
i.putExtra(EXTRA_MAILBOX_ID, mailboxId);
if (disableReply) {
i.putExtra(EXTRA_DISABLE_REPLY, true);
}
setActivityIntent(i);
}
/**
* Open the activity without setting an Intent.
*
* Expected: Activity will close itself.
*/
public void testCreateWithoutParamter() throws Throwable {
// No intent parameters specified. The activity will close itself.
final MessageView activity = getActivity();
TestUtils.waitUntil("", new TestUtils.Condition() {
@Override
public boolean isMet() {
return activity.isFinishing();
}
}, TIMEOUT);
}
/**
* Set up account/mailbox/message, and open the activity.
*
* Expected: Message opens.
*/
public void testOpenMessage() throws Exception {
final Context c = mProviderContext;
final Account acct1 = ProviderTestUtils.setupAccount("test1", true, c);
final Account acct2 = ProviderTestUtils.setupAccount("test2", true, c);
final Mailbox acct2inbox = ProviderTestUtils.setupMailbox("inbox", acct2.mId, true, c);
final Message msg1 = ProviderTestUtils.setupMessage("message1", acct2.mId, acct2inbox.mId,
true, true, c);
final Message msg2 = ProviderTestUtils.setupMessage("message2", acct2.mId, acct2inbox.mId,
true, true, c);
setUpIntent(msg2.mId, msg2.mMailboxKey, false);
final MessageView activity = getActivity();
TestUtils.waitUntil(new TestUtils.Condition() {
@Override
public boolean isMet() {
MessageViewFragment2 f = activity.getFragment();
return f != null && f.isMessageLoadedForTest();
}
}, TIMEOUT);
// TODO Check UI elements, once our UI is settled.
}
}

View File

@ -17,6 +17,7 @@
package com.android.email.provider;
import com.android.email.Utility;
import com.android.email.mail.transport.Rfc822Output;
import com.android.email.provider.EmailContent.Account;
import com.android.email.provider.EmailContent.Attachment;
import com.android.email.provider.EmailContent.HostAuth;
@ -24,8 +25,13 @@ import com.android.email.provider.EmailContent.Mailbox;
import com.android.email.provider.EmailContent.Message;
import android.content.Context;
import android.net.Uri;
import android.test.MoreAsserts;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import junit.framework.Assert;
public class ProviderTestUtils extends Assert {
@ -370,4 +376,19 @@ public class ProviderTestUtils extends Assert {
MoreAsserts.assertEquals(caller + " mContentBytes",
expect.mContentBytes, actual.mContentBytes);
}
/**
* Create a temporary EML file based on {@code msg} in the directory {@code directory}.
*/
public static Uri createTempEmlFile(Context context, Message msg, File directory)
throws Exception {
// Write out the message in rfc822 format
File outputFile = File.createTempFile("message", "tmp", directory);
assertNotNull(outputFile);
FileOutputStream outputStream = new FileOutputStream(outputFile);
Rfc822Output.writeTo(context, msg.mId, outputStream, false, false);
outputStream.close();
return Uri.fromFile(outputFile);
}
}