2010-07-16 22:10:00 +00:00
|
|
|
/*
|
|
|
|
* 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.Controller;
|
|
|
|
import com.android.email.ControllerResultUiThreadWrapper;
|
|
|
|
import com.android.email.Email;
|
2010-11-02 21:57:22 +00:00
|
|
|
import com.android.email.Preferences;
|
2010-07-16 22:10:00 +00:00
|
|
|
import com.android.email.R;
|
2010-10-02 01:04:12 +00:00
|
|
|
import com.android.email.Throttle;
|
2010-07-16 22:10:00 +00:00
|
|
|
import com.android.email.Utility;
|
|
|
|
import com.android.email.mail.Address;
|
|
|
|
import com.android.email.mail.MessagingException;
|
|
|
|
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;
|
2010-09-02 01:40:10 +00:00
|
|
|
import com.android.email.provider.EmailContent.Mailbox;
|
2010-07-16 22:10:00 +00:00
|
|
|
import com.android.email.provider.EmailContent.Message;
|
2010-08-10 00:48:53 +00:00
|
|
|
import com.android.email.service.AttachmentDownloadService;
|
2010-07-16 22:10:00 +00:00
|
|
|
|
|
|
|
import org.apache.commons.io.IOUtils;
|
|
|
|
|
2010-11-22 22:04:04 +00:00
|
|
|
import android.app.Activity;
|
2010-07-16 22:10:00 +00:00
|
|
|
import android.app.Fragment;
|
2010-09-07 20:30:59 +00:00
|
|
|
import android.app.LoaderManager.LoaderCallbacks;
|
2010-07-16 22:10:00 +00:00
|
|
|
import android.content.ActivityNotFoundException;
|
2010-10-02 01:04:12 +00:00
|
|
|
import android.content.ContentResolver;
|
|
|
|
import android.content.ContentUris;
|
2010-07-16 22:10:00 +00:00
|
|
|
import android.content.Context;
|
|
|
|
import android.content.Intent;
|
2010-09-07 20:30:59 +00:00
|
|
|
import android.content.Loader;
|
2010-10-02 01:04:12 +00:00
|
|
|
import android.database.ContentObserver;
|
2010-07-16 22:10:00 +00:00
|
|
|
import android.graphics.Bitmap;
|
|
|
|
import android.graphics.BitmapFactory;
|
|
|
|
import android.net.Uri;
|
|
|
|
import android.os.AsyncTask;
|
|
|
|
import android.os.Bundle;
|
|
|
|
import android.os.Environment;
|
|
|
|
import android.os.Handler;
|
|
|
|
import android.provider.ContactsContract;
|
|
|
|
import android.provider.ContactsContract.QuickContact;
|
|
|
|
import android.text.TextUtils;
|
|
|
|
import android.util.Log;
|
|
|
|
import android.util.Patterns;
|
|
|
|
import android.view.LayoutInflater;
|
|
|
|
import android.view.View;
|
|
|
|
import android.view.ViewGroup;
|
2010-11-02 21:57:22 +00:00
|
|
|
import android.webkit.WebSettings;
|
2010-07-16 22:10:00 +00:00
|
|
|
import android.webkit.WebView;
|
|
|
|
import android.webkit.WebViewClient;
|
|
|
|
import android.widget.Button;
|
|
|
|
import android.widget.ImageView;
|
|
|
|
import android.widget.LinearLayout;
|
2010-08-10 00:48:53 +00:00
|
|
|
import android.widget.ProgressBar;
|
2010-07-16 22:10:00 +00:00
|
|
|
import android.widget.TextView;
|
|
|
|
|
|
|
|
import java.io.File;
|
|
|
|
import java.io.FileOutputStream;
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.io.InputStream;
|
|
|
|
import java.io.OutputStream;
|
|
|
|
import java.util.Date;
|
|
|
|
import java.util.regex.Matcher;
|
|
|
|
import java.util.regex.Pattern;
|
|
|
|
|
2010-10-04 23:00:02 +00:00
|
|
|
// TODO Better handling of config changes.
|
|
|
|
// - Restore "Show pictures" state, scroll position and current tab
|
|
|
|
// - Retain the content; don't kick 3 async tasks every time
|
2010-07-16 22:10:00 +00:00
|
|
|
|
2010-07-30 00:06:00 +00:00
|
|
|
/**
|
2010-07-30 22:41:40 +00:00
|
|
|
* Base class for {@link MessageViewFragment} and {@link MessageFileViewFragment}.
|
2010-07-30 00:06:00 +00:00
|
|
|
*
|
|
|
|
* See {@link MessageViewBase} for the class relation diagram.
|
|
|
|
*/
|
|
|
|
public abstract class MessageViewFragmentBase extends Fragment implements View.OnClickListener {
|
2010-09-07 20:30:59 +00:00
|
|
|
private static final int PHOTO_LOADER_ID = 1;
|
2010-07-16 22:10:00 +00:00
|
|
|
private Context mContext;
|
|
|
|
|
|
|
|
// Regex that matches start of img tag. '<(?i)img\s+'.
|
|
|
|
private static final Pattern IMG_TAG_START_REGEX = Pattern.compile("<(?i)img\\s+");
|
|
|
|
// Regex that matches Web URL protocol part as case insensitive.
|
|
|
|
private static final Pattern WEB_URL_PROTOCOL = Pattern.compile("(?i)http|https://");
|
|
|
|
|
|
|
|
private static int PREVIEW_ICON_WIDTH = 62;
|
|
|
|
private static int PREVIEW_ICON_HEIGHT = 62;
|
|
|
|
|
|
|
|
private TextView mSubjectView;
|
2010-11-12 01:38:21 +00:00
|
|
|
private TextView mFromNameView;
|
|
|
|
private TextView mFromAddressView;
|
2010-11-17 01:28:59 +00:00
|
|
|
private TextView mDateTimeView;
|
2010-07-16 22:10:00 +00:00
|
|
|
private TextView mToView;
|
|
|
|
private TextView mCcView;
|
|
|
|
private View mCcContainerView;
|
2010-11-12 01:38:21 +00:00
|
|
|
private TextView mBccView;
|
|
|
|
private View mBccContainerView;
|
2010-07-16 22:10:00 +00:00
|
|
|
private WebView mMessageContentView;
|
|
|
|
private LinearLayout mAttachments;
|
2010-10-04 23:00:02 +00:00
|
|
|
private View mTabSection;
|
2010-11-12 01:38:21 +00:00
|
|
|
private ImageView mFromBadge;
|
2010-07-16 22:10:00 +00:00
|
|
|
private ImageView mSenderPresenceView;
|
2010-12-07 19:02:24 +00:00
|
|
|
private View mMainView;
|
|
|
|
private View mLoadingProgress;
|
2010-10-04 23:00:02 +00:00
|
|
|
|
|
|
|
private TextView mMessageTab;
|
|
|
|
private TextView mAttachmentTab;
|
|
|
|
private TextView mInviteTab;
|
|
|
|
// It is not really a tab, but looks like one of them.
|
|
|
|
private TextView mShowPicturesTab;
|
|
|
|
|
|
|
|
private View mAttachmentsScroll;
|
|
|
|
private View mInviteScroll;
|
2010-07-16 22:10:00 +00:00
|
|
|
|
|
|
|
private long mAccountId = -1;
|
|
|
|
private long mMessageId = -1;
|
|
|
|
private Message mMessage;
|
|
|
|
|
|
|
|
private LoadMessageTask mLoadMessageTask;
|
2010-10-02 01:04:12 +00:00
|
|
|
private ReloadMessageTask mReloadMessageTask;
|
2010-07-16 22:10:00 +00:00
|
|
|
private LoadBodyTask mLoadBodyTask;
|
|
|
|
private LoadAttachmentsTask mLoadAttachmentsTask;
|
|
|
|
|
|
|
|
private java.text.DateFormat mDateFormat;
|
|
|
|
private java.text.DateFormat mTimeFormat;
|
|
|
|
|
|
|
|
private Controller mController;
|
|
|
|
private ControllerResultUiThreadWrapper<ControllerResults> mControllerCallback;
|
|
|
|
|
|
|
|
// contains the HTML body. Is used by LoadAttachmentTask to display inline images.
|
|
|
|
// is null most of the time, is used transiently to pass info to LoadAttachementTask
|
|
|
|
private String mHtmlTextRaw;
|
|
|
|
|
|
|
|
// contains the HTML content as set in WebView.
|
|
|
|
private String mHtmlTextWebView;
|
|
|
|
|
2010-11-19 23:03:37 +00:00
|
|
|
private boolean mResumed;
|
|
|
|
private boolean mLoadWhenResumed;
|
2010-07-21 21:29:49 +00:00
|
|
|
|
2010-07-30 00:06:00 +00:00
|
|
|
private boolean mIsMessageLoadedForTest;
|
|
|
|
|
2010-10-02 01:04:12 +00:00
|
|
|
private MessageObserver mMessageObserver;
|
|
|
|
|
2010-09-07 20:30:59 +00:00
|
|
|
private static final int CONTACT_STATUS_STATE_UNLOADED = 0;
|
|
|
|
private static final int CONTACT_STATUS_STATE_UNLOADED_TRIGGERED = 1;
|
|
|
|
private static final int CONTACT_STATUS_STATE_LOADED = 2;
|
|
|
|
|
|
|
|
private int mContactStatusState;
|
|
|
|
private Uri mQuickContactLookupUri;
|
|
|
|
|
2010-10-04 23:00:02 +00:00
|
|
|
/** Flag for {@link #mTabFlags}: Message has attachment(s) */
|
|
|
|
protected static final int TAB_FLAGS_HAS_ATTACHMENT = 1;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Flag for {@link #mTabFlags}: Message contains invite. This flag is only set by
|
|
|
|
* {@link MessageViewFragment}.
|
|
|
|
*/
|
|
|
|
protected static final int TAB_FLAGS_HAS_INVITE = 2;
|
|
|
|
|
|
|
|
/** Flag for {@link #mTabFlags}: Message contains pictures */
|
|
|
|
protected static final int TAB_FLAGS_HAS_PICTURES = 4;
|
|
|
|
|
|
|
|
/** Flag for {@link #mTabFlags}: "Show pictures" has already been pressed */
|
|
|
|
protected static final int TAB_FLAGS_PICTURE_LOADED = 8;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Flags to control the tabs.
|
|
|
|
* @see #updateTabFlags(int)
|
|
|
|
*/
|
|
|
|
private int mTabFlags;
|
|
|
|
|
|
|
|
/** # of attachments in the current message */
|
|
|
|
private int mAttachmentCount;
|
|
|
|
|
|
|
|
// Use (random) large values, to avoid confusion with TAB_FLAGS_*
|
|
|
|
protected static final int TAB_MESSAGE = 101;
|
|
|
|
protected static final int TAB_INVITE = 102;
|
|
|
|
protected static final int TAB_ATTACHMENT = 103;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Currently visible tab. Any of {@link #TAB_MESSAGE}, {@link #TAB_INVITE} or
|
|
|
|
* {@link #TAB_ATTACHMENT}.
|
|
|
|
*
|
|
|
|
* Note we don't retain this value through configuration changes, as restoring the current tab
|
|
|
|
* would be clumsy with the current implementation where we load Message/Body/Attachments
|
|
|
|
* separately. (e.g. # of attachments can't be obtained quickly enough to update the UI
|
|
|
|
* after screen rotation.)
|
|
|
|
*/
|
|
|
|
private int mCurrentTab;
|
|
|
|
|
2010-07-16 22:10:00 +00:00
|
|
|
/**
|
|
|
|
* Encapsulates known information about a single attachment.
|
|
|
|
*/
|
|
|
|
private static class AttachmentInfo {
|
|
|
|
public String name;
|
|
|
|
public String contentType;
|
|
|
|
public long size;
|
|
|
|
public long attachmentId;
|
|
|
|
public Button viewButton;
|
2010-08-10 00:48:53 +00:00
|
|
|
public Button saveButton;
|
|
|
|
public Button loadButton;
|
|
|
|
public Button cancelButton;
|
2010-07-16 22:10:00 +00:00
|
|
|
public ImageView iconView;
|
2010-08-10 00:48:53 +00:00
|
|
|
public ProgressBar progressView;
|
2010-07-16 22:10:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public interface Callback {
|
2010-09-02 01:40:10 +00:00
|
|
|
/** Called when the fragment is about to show up, or show a different message. */
|
|
|
|
public void onMessageViewShown(int mailboxType);
|
|
|
|
|
|
|
|
/** Called when the fragment is about to be destroyed. */
|
|
|
|
public void onMessageViewGone();
|
|
|
|
|
2010-07-16 22:10:00 +00:00
|
|
|
/**
|
|
|
|
* Called when a link in a message is clicked.
|
|
|
|
*
|
|
|
|
* @param url link url that's clicked.
|
|
|
|
* @return true if handled, false otherwise.
|
|
|
|
*/
|
|
|
|
public boolean onUrlInMessageClicked(String url);
|
|
|
|
|
2010-10-02 01:04:12 +00:00
|
|
|
/**
|
|
|
|
* Called when the message specified doesn't exist, or is deleted/moved.
|
|
|
|
*/
|
2010-07-16 22:10:00 +00:00
|
|
|
public void onMessageNotExists();
|
|
|
|
|
|
|
|
/** Called when it starts loading a message. */
|
|
|
|
public void onLoadMessageStarted();
|
|
|
|
|
|
|
|
/** Called when it successfully finishes loading a message. */
|
|
|
|
public void onLoadMessageFinished();
|
|
|
|
|
|
|
|
/** Called when an error occurred during loading a message. */
|
2010-11-29 19:42:13 +00:00
|
|
|
public void onLoadMessageError(String errorMessage);
|
2010-07-16 22:10:00 +00:00
|
|
|
}
|
|
|
|
|
2010-07-30 00:06:00 +00:00
|
|
|
public static class EmptyCallback implements Callback {
|
2010-07-16 22:10:00 +00:00
|
|
|
public static final Callback INSTANCE = new EmptyCallback();
|
2010-09-02 01:40:10 +00:00
|
|
|
@Override public void onMessageViewShown(int mailboxType) {}
|
|
|
|
@Override public void onMessageViewGone() {}
|
2010-11-29 19:42:13 +00:00
|
|
|
@Override public void onLoadMessageError(String errorMessage) {}
|
2010-09-02 01:40:10 +00:00
|
|
|
@Override public void onLoadMessageFinished() {}
|
|
|
|
@Override public void onLoadMessageStarted() {}
|
|
|
|
@Override public void onMessageNotExists() {}
|
2010-07-16 22:10:00 +00:00
|
|
|
@Override
|
|
|
|
public boolean onUrlInMessageClicked(String url) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private Callback mCallback = EmptyCallback.INSTANCE;
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onCreate(Bundle savedInstanceState) {
|
Refactoring MessageListXL
I always thought our Activities are way too fat, meaning we've put too many
things into activities without any structure.
The major problems with this are:
- They have too many fields, which are not final and not even orthogonal.
This makes them very hard to understand/maintain. Changing one tiny bit
can always cause unanticipated side-effects.
- Very hard, or almost impossible to test.
I really think we should break them into independent and self-contained
subcomponents which can be tested separately.
Introducing MessageListXLStateManager, which manages the current account,
mailbox and message, and show/hide/update fragments accordingly
for MessageListXL.
With this class, MessageListXL will be able to switch accounts/mailboxes/
messages by just calling the methods such as selectAccount(), without
worrying about when to show/hide what fragment and how to initialize them.
(In other words, MessageListXLStateManager encapsulates the two-pane screen
transition. It's not intended to be reused for the phone UI.)
I didn't make it a nested class in MessageListXL, because nested classes can't
have real private members (private member are accessible from outer classes and
even brother classes!!), and I wanted it to be really self-contained anyway.
Change-Id: I1c121e99e30f12cc118e1c35abc9b30f49939a4a
2010-07-22 22:01:31 +00:00
|
|
|
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
|
|
|
|
Log.d(Email.LOG_TAG, "MessageViewFragment onCreate");
|
|
|
|
}
|
2010-07-16 22:10:00 +00:00
|
|
|
super.onCreate(savedInstanceState);
|
|
|
|
|
|
|
|
mContext = getActivity().getApplicationContext();
|
|
|
|
|
|
|
|
mControllerCallback = new ControllerResultUiThreadWrapper<ControllerResults>(
|
|
|
|
new Handler(), new ControllerResults());
|
|
|
|
|
|
|
|
mDateFormat = android.text.format.DateFormat.getDateFormat(mContext); // short format
|
|
|
|
mTimeFormat = android.text.format.DateFormat.getTimeFormat(mContext); // 12/24 date format
|
|
|
|
|
2010-08-24 01:48:25 +00:00
|
|
|
mController = Controller.getInstance(mContext);
|
2010-10-02 01:04:12 +00:00
|
|
|
mMessageObserver = new MessageObserver(new Handler(), mContext);
|
2010-07-16 22:10:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public View onCreateView(
|
|
|
|
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
Refactoring MessageListXL
I always thought our Activities are way too fat, meaning we've put too many
things into activities without any structure.
The major problems with this are:
- They have too many fields, which are not final and not even orthogonal.
This makes them very hard to understand/maintain. Changing one tiny bit
can always cause unanticipated side-effects.
- Very hard, or almost impossible to test.
I really think we should break them into independent and self-contained
subcomponents which can be tested separately.
Introducing MessageListXLStateManager, which manages the current account,
mailbox and message, and show/hide/update fragments accordingly
for MessageListXL.
With this class, MessageListXL will be able to switch accounts/mailboxes/
messages by just calling the methods such as selectAccount(), without
worrying about when to show/hide what fragment and how to initialize them.
(In other words, MessageListXLStateManager encapsulates the two-pane screen
transition. It's not intended to be reused for the phone UI.)
I didn't make it a nested class in MessageListXL, because nested classes can't
have real private members (private member are accessible from outer classes and
even brother classes!!), and I wanted it to be really self-contained anyway.
Change-Id: I1c121e99e30f12cc118e1c35abc9b30f49939a4a
2010-07-22 22:01:31 +00:00
|
|
|
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
|
|
|
|
Log.d(Email.LOG_TAG, "MessageViewFragment onCreateView");
|
|
|
|
}
|
2010-07-22 00:23:07 +00:00
|
|
|
final View view = inflater.inflate(R.layout.message_view_fragment, container, false);
|
2010-07-16 22:10:00 +00:00
|
|
|
|
|
|
|
mSubjectView = (TextView) view.findViewById(R.id.subject);
|
2010-11-12 01:38:21 +00:00
|
|
|
mFromNameView = (TextView) view.findViewById(R.id.from_name);
|
|
|
|
mFromAddressView = (TextView) view.findViewById(R.id.from_address);
|
2010-07-16 22:10:00 +00:00
|
|
|
mToView = (TextView) view.findViewById(R.id.to);
|
|
|
|
mCcView = (TextView) view.findViewById(R.id.cc);
|
|
|
|
mCcContainerView = view.findViewById(R.id.cc_container);
|
2010-11-12 01:38:21 +00:00
|
|
|
mBccView = (TextView) view.findViewById(R.id.bcc);
|
|
|
|
mBccContainerView = view.findViewById(R.id.bcc_container);
|
2010-11-17 01:28:59 +00:00
|
|
|
mDateTimeView = (TextView) view.findViewById(R.id.datetime);
|
2010-07-16 22:10:00 +00:00
|
|
|
mMessageContentView = (WebView) view.findViewById(R.id.message_content);
|
|
|
|
mAttachments = (LinearLayout) view.findViewById(R.id.attachments);
|
2010-10-04 23:00:02 +00:00
|
|
|
mTabSection = view.findViewById(R.id.message_tabs_section);
|
2010-11-12 01:38:21 +00:00
|
|
|
mFromBadge = (ImageView) view.findViewById(R.id.badge);
|
2010-07-16 22:10:00 +00:00
|
|
|
mSenderPresenceView = (ImageView) view.findViewById(R.id.presence);
|
2010-12-07 19:02:24 +00:00
|
|
|
mMainView = view.findViewById(R.id.main_panel);
|
|
|
|
mLoadingProgress = view.findViewById(R.id.loading_progress);
|
2010-07-16 22:10:00 +00:00
|
|
|
|
2010-11-12 01:38:21 +00:00
|
|
|
mFromNameView.setOnClickListener(this);
|
|
|
|
mFromAddressView.setOnClickListener(this);
|
2010-09-07 20:30:59 +00:00
|
|
|
mFromBadge.setOnClickListener(this);
|
2010-07-16 22:10:00 +00:00
|
|
|
mSenderPresenceView.setOnClickListener(this);
|
2010-10-04 23:00:02 +00:00
|
|
|
|
|
|
|
mMessageTab = (TextView) view.findViewById(R.id.show_message);
|
|
|
|
mAttachmentTab = (TextView) view.findViewById(R.id.show_attachments);
|
|
|
|
mShowPicturesTab = (TextView) view.findViewById(R.id.show_pictures);
|
|
|
|
// Invite is only used in MessageViewFragment, but visibility is controlled here.
|
|
|
|
mInviteTab = (TextView) view.findViewById(R.id.show_invite);
|
|
|
|
|
|
|
|
mMessageTab.setOnClickListener(this);
|
|
|
|
mAttachmentTab.setOnClickListener(this);
|
|
|
|
mShowPicturesTab.setOnClickListener(this);
|
|
|
|
mInviteTab.setOnClickListener(this);
|
|
|
|
|
|
|
|
mAttachmentsScroll = view.findViewById(R.id.attachments_scroll);
|
|
|
|
mInviteScroll = view.findViewById(R.id.invite_scroll);
|
2010-07-16 22:10:00 +00:00
|
|
|
|
2010-11-03 22:31:08 +00:00
|
|
|
WebSettings webSettings = mMessageContentView.getSettings();
|
|
|
|
webSettings.setBlockNetworkLoads(true);
|
|
|
|
webSettings.setSupportZoom(true);
|
|
|
|
webSettings.setBuiltInZoomControls(true);
|
2010-07-16 22:10:00 +00:00
|
|
|
mMessageContentView.setWebViewClient(new CustomWebViewClient());
|
|
|
|
return view;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onActivityCreated(Bundle savedInstanceState) {
|
Refactoring MessageListXL
I always thought our Activities are way too fat, meaning we've put too many
things into activities without any structure.
The major problems with this are:
- They have too many fields, which are not final and not even orthogonal.
This makes them very hard to understand/maintain. Changing one tiny bit
can always cause unanticipated side-effects.
- Very hard, or almost impossible to test.
I really think we should break them into independent and self-contained
subcomponents which can be tested separately.
Introducing MessageListXLStateManager, which manages the current account,
mailbox and message, and show/hide/update fragments accordingly
for MessageListXL.
With this class, MessageListXL will be able to switch accounts/mailboxes/
messages by just calling the methods such as selectAccount(), without
worrying about when to show/hide what fragment and how to initialize them.
(In other words, MessageListXLStateManager encapsulates the two-pane screen
transition. It's not intended to be reused for the phone UI.)
I didn't make it a nested class in MessageListXL, because nested classes can't
have real private members (private member are accessible from outer classes and
even brother classes!!), and I wanted it to be really self-contained anyway.
Change-Id: I1c121e99e30f12cc118e1c35abc9b30f49939a4a
2010-07-22 22:01:31 +00:00
|
|
|
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
|
|
|
|
Log.d(Email.LOG_TAG, "MessageViewFragment onActivityCreated");
|
|
|
|
}
|
2010-07-16 22:10:00 +00:00
|
|
|
super.onActivityCreated(savedInstanceState);
|
|
|
|
mController.addResultCallback(mControllerCallback);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onStart() {
|
Refactoring MessageListXL
I always thought our Activities are way too fat, meaning we've put too many
things into activities without any structure.
The major problems with this are:
- They have too many fields, which are not final and not even orthogonal.
This makes them very hard to understand/maintain. Changing one tiny bit
can always cause unanticipated side-effects.
- Very hard, or almost impossible to test.
I really think we should break them into independent and self-contained
subcomponents which can be tested separately.
Introducing MessageListXLStateManager, which manages the current account,
mailbox and message, and show/hide/update fragments accordingly
for MessageListXL.
With this class, MessageListXL will be able to switch accounts/mailboxes/
messages by just calling the methods such as selectAccount(), without
worrying about when to show/hide what fragment and how to initialize them.
(In other words, MessageListXLStateManager encapsulates the two-pane screen
transition. It's not intended to be reused for the phone UI.)
I didn't make it a nested class in MessageListXL, because nested classes can't
have real private members (private member are accessible from outer classes and
even brother classes!!), and I wanted it to be really self-contained anyway.
Change-Id: I1c121e99e30f12cc118e1c35abc9b30f49939a4a
2010-07-22 22:01:31 +00:00
|
|
|
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
|
|
|
|
Log.d(Email.LOG_TAG, "MessageViewFragment onStart");
|
|
|
|
}
|
2010-07-16 22:10:00 +00:00
|
|
|
super.onStart();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onResume() {
|
Refactoring MessageListXL
I always thought our Activities are way too fat, meaning we've put too many
things into activities without any structure.
The major problems with this are:
- They have too many fields, which are not final and not even orthogonal.
This makes them very hard to understand/maintain. Changing one tiny bit
can always cause unanticipated side-effects.
- Very hard, or almost impossible to test.
I really think we should break them into independent and self-contained
subcomponents which can be tested separately.
Introducing MessageListXLStateManager, which manages the current account,
mailbox and message, and show/hide/update fragments accordingly
for MessageListXL.
With this class, MessageListXL will be able to switch accounts/mailboxes/
messages by just calling the methods such as selectAccount(), without
worrying about when to show/hide what fragment and how to initialize them.
(In other words, MessageListXLStateManager encapsulates the two-pane screen
transition. It's not intended to be reused for the phone UI.)
I didn't make it a nested class in MessageListXL, because nested classes can't
have real private members (private member are accessible from outer classes and
even brother classes!!), and I wanted it to be really self-contained anyway.
Change-Id: I1c121e99e30f12cc118e1c35abc9b30f49939a4a
2010-07-22 22:01:31 +00:00
|
|
|
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
|
|
|
|
Log.d(Email.LOG_TAG, "MessageViewFragment onResume");
|
|
|
|
}
|
2010-07-16 22:10:00 +00:00
|
|
|
super.onResume();
|
2010-11-02 21:57:22 +00:00
|
|
|
|
2010-11-19 23:03:37 +00:00
|
|
|
mResumed = true;
|
|
|
|
if (isMessageSpecified()) {
|
|
|
|
if (mLoadWhenResumed) {
|
|
|
|
loadMessageIfResumed();
|
|
|
|
} else {
|
|
|
|
// This means, the user comes back from other (full-screen) activities.
|
|
|
|
// In this case we've already loaded the content, so don't load it again,
|
|
|
|
// which results in resetting all view state, including WebView zoom/pan
|
|
|
|
// and the current tab.
|
|
|
|
}
|
2010-11-02 21:57:22 +00:00
|
|
|
}
|
2010-07-16 22:10:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onPause() {
|
Refactoring MessageListXL
I always thought our Activities are way too fat, meaning we've put too many
things into activities without any structure.
The major problems with this are:
- They have too many fields, which are not final and not even orthogonal.
This makes them very hard to understand/maintain. Changing one tiny bit
can always cause unanticipated side-effects.
- Very hard, or almost impossible to test.
I really think we should break them into independent and self-contained
subcomponents which can be tested separately.
Introducing MessageListXLStateManager, which manages the current account,
mailbox and message, and show/hide/update fragments accordingly
for MessageListXL.
With this class, MessageListXL will be able to switch accounts/mailboxes/
messages by just calling the methods such as selectAccount(), without
worrying about when to show/hide what fragment and how to initialize them.
(In other words, MessageListXLStateManager encapsulates the two-pane screen
transition. It's not intended to be reused for the phone UI.)
I didn't make it a nested class in MessageListXL, because nested classes can't
have real private members (private member are accessible from outer classes and
even brother classes!!), and I wanted it to be really self-contained anyway.
Change-Id: I1c121e99e30f12cc118e1c35abc9b30f49939a4a
2010-07-22 22:01:31 +00:00
|
|
|
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
|
|
|
|
Log.d(Email.LOG_TAG, "MessageViewFragment onPause");
|
|
|
|
}
|
2010-11-19 23:03:37 +00:00
|
|
|
mResumed = false;
|
2010-07-16 22:10:00 +00:00
|
|
|
super.onPause();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onStop() {
|
Refactoring MessageListXL
I always thought our Activities are way too fat, meaning we've put too many
things into activities without any structure.
The major problems with this are:
- They have too many fields, which are not final and not even orthogonal.
This makes them very hard to understand/maintain. Changing one tiny bit
can always cause unanticipated side-effects.
- Very hard, or almost impossible to test.
I really think we should break them into independent and self-contained
subcomponents which can be tested separately.
Introducing MessageListXLStateManager, which manages the current account,
mailbox and message, and show/hide/update fragments accordingly
for MessageListXL.
With this class, MessageListXL will be able to switch accounts/mailboxes/
messages by just calling the methods such as selectAccount(), without
worrying about when to show/hide what fragment and how to initialize them.
(In other words, MessageListXLStateManager encapsulates the two-pane screen
transition. It's not intended to be reused for the phone UI.)
I didn't make it a nested class in MessageListXL, because nested classes can't
have real private members (private member are accessible from outer classes and
even brother classes!!), and I wanted it to be really self-contained anyway.
Change-Id: I1c121e99e30f12cc118e1c35abc9b30f49939a4a
2010-07-22 22:01:31 +00:00
|
|
|
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
|
|
|
|
Log.d(Email.LOG_TAG, "MessageViewFragment onStop");
|
|
|
|
}
|
2010-07-16 22:10:00 +00:00
|
|
|
super.onStop();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onDestroy() {
|
Refactoring MessageListXL
I always thought our Activities are way too fat, meaning we've put too many
things into activities without any structure.
The major problems with this are:
- They have too many fields, which are not final and not even orthogonal.
This makes them very hard to understand/maintain. Changing one tiny bit
can always cause unanticipated side-effects.
- Very hard, or almost impossible to test.
I really think we should break them into independent and self-contained
subcomponents which can be tested separately.
Introducing MessageListXLStateManager, which manages the current account,
mailbox and message, and show/hide/update fragments accordingly
for MessageListXL.
With this class, MessageListXL will be able to switch accounts/mailboxes/
messages by just calling the methods such as selectAccount(), without
worrying about when to show/hide what fragment and how to initialize them.
(In other words, MessageListXLStateManager encapsulates the two-pane screen
transition. It's not intended to be reused for the phone UI.)
I didn't make it a nested class in MessageListXL, because nested classes can't
have real private members (private member are accessible from outer classes and
even brother classes!!), and I wanted it to be really self-contained anyway.
Change-Id: I1c121e99e30f12cc118e1c35abc9b30f49939a4a
2010-07-22 22:01:31 +00:00
|
|
|
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
|
|
|
|
Log.d(Email.LOG_TAG, "MessageViewFragment onDestroy");
|
|
|
|
}
|
2010-09-02 01:40:10 +00:00
|
|
|
mCallback.onMessageViewGone();
|
2010-07-16 22:10:00 +00:00
|
|
|
mController.removeResultCallback(mControllerCallback);
|
2010-11-22 22:04:04 +00:00
|
|
|
clearContent();
|
2010-07-16 22:10:00 +00:00
|
|
|
mMessageContentView.destroy();
|
|
|
|
mMessageContentView = null;
|
|
|
|
super.onDestroy();
|
|
|
|
}
|
|
|
|
|
2010-07-23 01:35:33 +00:00
|
|
|
@Override
|
|
|
|
public void onSaveInstanceState(Bundle outState) {
|
Refactoring MessageListXL
I always thought our Activities are way too fat, meaning we've put too many
things into activities without any structure.
The major problems with this are:
- They have too many fields, which are not final and not even orthogonal.
This makes them very hard to understand/maintain. Changing one tiny bit
can always cause unanticipated side-effects.
- Very hard, or almost impossible to test.
I really think we should break them into independent and self-contained
subcomponents which can be tested separately.
Introducing MessageListXLStateManager, which manages the current account,
mailbox and message, and show/hide/update fragments accordingly
for MessageListXL.
With this class, MessageListXL will be able to switch accounts/mailboxes/
messages by just calling the methods such as selectAccount(), without
worrying about when to show/hide what fragment and how to initialize them.
(In other words, MessageListXLStateManager encapsulates the two-pane screen
transition. It's not intended to be reused for the phone UI.)
I didn't make it a nested class in MessageListXL, because nested classes can't
have real private members (private member are accessible from outer classes and
even brother classes!!), and I wanted it to be really self-contained anyway.
Change-Id: I1c121e99e30f12cc118e1c35abc9b30f49939a4a
2010-07-22 22:01:31 +00:00
|
|
|
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
|
|
|
|
Log.d(Email.LOG_TAG, "MessageViewFragment onSaveInstanceState");
|
|
|
|
}
|
2010-07-23 01:35:33 +00:00
|
|
|
super.onSaveInstanceState(outState);
|
|
|
|
}
|
|
|
|
|
2010-07-16 22:10:00 +00:00
|
|
|
public void setCallback(Callback callback) {
|
|
|
|
mCallback = (callback == null) ? EmptyCallback.INSTANCE : callback;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void cancelAllTasks() {
|
2010-10-02 01:04:12 +00:00
|
|
|
mMessageObserver.unregister();
|
2010-07-16 22:10:00 +00:00
|
|
|
Utility.cancelTaskInterrupt(mLoadMessageTask);
|
|
|
|
mLoadMessageTask = null;
|
2010-10-02 01:04:12 +00:00
|
|
|
Utility.cancelTaskInterrupt(mReloadMessageTask);
|
|
|
|
mReloadMessageTask = null;
|
2010-07-16 22:10:00 +00:00
|
|
|
Utility.cancelTaskInterrupt(mLoadBodyTask);
|
|
|
|
mLoadBodyTask = null;
|
|
|
|
Utility.cancelTaskInterrupt(mLoadAttachmentsTask);
|
|
|
|
mLoadAttachmentsTask = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2010-07-30 00:06:00 +00:00
|
|
|
* Subclass returns true if which message to open is already specified by the activity.
|
2010-07-16 22:10:00 +00:00
|
|
|
*/
|
2010-07-30 00:06:00 +00:00
|
|
|
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;
|
2010-07-16 22:10:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2010-07-30 00:06:00 +00:00
|
|
|
* Returns the account id of the current message, or -1 if unknown (message not open yet, or
|
|
|
|
* viewing an EML message).
|
2010-07-16 22:10:00 +00:00
|
|
|
*/
|
|
|
|
public long getAccountId() {
|
|
|
|
return mAccountId;
|
|
|
|
}
|
|
|
|
|
2010-09-30 01:44:05 +00:00
|
|
|
/**
|
|
|
|
* Clear all the content -- should be called when the fragment is hidden.
|
|
|
|
*/
|
|
|
|
public void clearContent() {
|
|
|
|
cancelAllTasks();
|
|
|
|
resetView();
|
|
|
|
}
|
|
|
|
|
2010-11-19 23:03:37 +00:00
|
|
|
protected final void loadMessageIfResumed() {
|
|
|
|
if (!mResumed) {
|
|
|
|
mLoadWhenResumed = true;
|
2010-07-30 00:06:00 +00:00
|
|
|
return;
|
2010-07-21 21:29:49 +00:00
|
|
|
}
|
2010-11-19 23:03:37 +00:00
|
|
|
mLoadWhenResumed = false;
|
2010-07-16 22:10:00 +00:00
|
|
|
cancelAllTasks();
|
2010-09-07 20:30:59 +00:00
|
|
|
resetView();
|
2010-09-17 18:42:05 +00:00
|
|
|
mLoadMessageTask = new LoadMessageTask(true);
|
|
|
|
mLoadMessageTask.execute();
|
2010-09-07 20:30:59 +00:00
|
|
|
}
|
|
|
|
|
2010-12-07 19:02:24 +00:00
|
|
|
/**
|
|
|
|
* Show/hide the content. We hide all the content (except for the bottom buttons) when loading,
|
|
|
|
* to avoid flicker.
|
|
|
|
*/
|
2010-12-22 00:32:41 +00:00
|
|
|
private void showContent(boolean showContent, boolean showProgressWhenHidden) {
|
2010-12-07 19:02:24 +00:00
|
|
|
if (mLoadingProgress == null) {
|
|
|
|
// Phone UI doesn't have it yet.
|
|
|
|
// TODO Add loading_progress and main_panel to the phone layout too.
|
|
|
|
} else {
|
2010-12-22 00:32:41 +00:00
|
|
|
makeVisible(mMainView, showContent);
|
|
|
|
makeVisible(mLoadingProgress, !showContent && showProgressWhenHidden);
|
2010-12-07 19:02:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-09-07 20:30:59 +00:00
|
|
|
protected void resetView() {
|
2010-12-22 00:32:41 +00:00
|
|
|
showContent(false, false);
|
2010-10-04 23:00:02 +00:00
|
|
|
setCurrentTab(TAB_MESSAGE);
|
|
|
|
updateTabFlags(0);
|
2010-07-16 22:10:00 +00:00
|
|
|
if (mMessageContentView != null) {
|
|
|
|
mMessageContentView.getSettings().setBlockNetworkLoads(true);
|
|
|
|
mMessageContentView.scrollTo(0, 0);
|
2010-12-22 00:32:41 +00:00
|
|
|
mMessageContentView.clearView();
|
2010-11-19 23:03:37 +00:00
|
|
|
|
|
|
|
// Dynamic configuration of WebView
|
|
|
|
WebSettings.TextSize textZoom;
|
|
|
|
switch (Preferences.getPreferences(mContext).getTextZoom()) {
|
|
|
|
case Preferences.TEXT_ZOOM_TINY: textZoom = WebSettings.TextSize.SMALLEST; break;
|
|
|
|
case Preferences.TEXT_ZOOM_SMALL: textZoom = WebSettings.TextSize.SMALLER; break;
|
|
|
|
case Preferences.TEXT_ZOOM_NORMAL: textZoom = WebSettings.TextSize.NORMAL; break;
|
|
|
|
case Preferences.TEXT_ZOOM_LARGE: textZoom = WebSettings.TextSize.LARGER; break;
|
|
|
|
case Preferences.TEXT_ZOOM_HUGE: textZoom = WebSettings.TextSize.LARGEST; break;
|
|
|
|
default: textZoom = WebSettings.TextSize.NORMAL; break;
|
|
|
|
}
|
2010-12-22 00:32:41 +00:00
|
|
|
final WebSettings settings = mMessageContentView.getSettings();
|
|
|
|
settings.setTextSize(textZoom);
|
|
|
|
settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
|
2010-07-16 22:10:00 +00:00
|
|
|
}
|
2010-10-04 23:00:02 +00:00
|
|
|
mAttachmentsScroll.scrollTo(0, 0);
|
|
|
|
mInviteScroll.scrollTo(0, 0);
|
2010-07-16 22:10:00 +00:00
|
|
|
mAttachments.removeAllViews();
|
|
|
|
mAttachments.setVisibility(View.GONE);
|
2010-09-07 20:30:59 +00:00
|
|
|
initContactStatusViews();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void initContactStatusViews() {
|
|
|
|
mContactStatusState = CONTACT_STATUS_STATE_UNLOADED;
|
|
|
|
mQuickContactLookupUri = null;
|
|
|
|
mSenderPresenceView.setImageResource(ContactStatusLoader.PRESENCE_UNKNOWN_RESOURCE_ID);
|
2010-11-12 01:38:21 +00:00
|
|
|
showDefaultQuickContactBadgeImage();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void showDefaultQuickContactBadgeImage() {
|
2010-12-15 23:26:30 +00:00
|
|
|
mFromBadge.setImageResource(R.drawable.ic_contact_picture);
|
2010-07-16 22:10:00 +00:00
|
|
|
}
|
|
|
|
|
2010-10-04 23:00:02 +00:00
|
|
|
protected final void addTabFlags(int tabFlags) {
|
|
|
|
updateTabFlags(mTabFlags | tabFlags);
|
|
|
|
}
|
|
|
|
|
|
|
|
private final void clearTabFlags(int tabFlags) {
|
|
|
|
updateTabFlags(mTabFlags & ~tabFlags);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void setAttachmentCount(int count) {
|
|
|
|
mAttachmentCount = count;
|
|
|
|
if (mAttachmentCount > 0) {
|
|
|
|
addTabFlags(TAB_FLAGS_HAS_ATTACHMENT);
|
|
|
|
} else {
|
|
|
|
clearTabFlags(TAB_FLAGS_HAS_ATTACHMENT);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void makeVisible(View v, boolean visible) {
|
|
|
|
v.setVisibility(visible ? View.VISIBLE : View.GONE);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update the visual of the tabs. (visibility, text, etc)
|
|
|
|
*/
|
|
|
|
private void updateTabFlags(int tabFlags) {
|
|
|
|
mTabFlags = tabFlags;
|
|
|
|
mTabSection.setVisibility(tabFlags == 0 ? View.GONE : View.VISIBLE);
|
|
|
|
if (tabFlags == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
boolean messageTabVisible = (tabFlags & (TAB_FLAGS_HAS_INVITE | TAB_FLAGS_HAS_ATTACHMENT))
|
|
|
|
!= 0;
|
|
|
|
makeVisible(mMessageTab, messageTabVisible);
|
|
|
|
makeVisible(mInviteTab, (tabFlags & TAB_FLAGS_HAS_INVITE) != 0);
|
|
|
|
makeVisible(mAttachmentTab, (tabFlags & TAB_FLAGS_HAS_ATTACHMENT) != 0);
|
|
|
|
makeVisible(mShowPicturesTab, (tabFlags & TAB_FLAGS_HAS_PICTURES) != 0);
|
|
|
|
mShowPicturesTab.setEnabled((tabFlags & TAB_FLAGS_PICTURE_LOADED) == 0);
|
|
|
|
|
|
|
|
mAttachmentTab.setText(mContext.getResources().getQuantityString(
|
|
|
|
R.plurals.message_view_show_attachments_action,
|
|
|
|
mAttachmentCount, mAttachmentCount));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the current tab.
|
|
|
|
*
|
|
|
|
* @param tab any of {@link #TAB_MESSAGE}, {@link #TAB_ATTACHMENT} or {@link #TAB_INVITE}.
|
|
|
|
*/
|
|
|
|
private void setCurrentTab(int tab) {
|
|
|
|
mCurrentTab = tab;
|
|
|
|
makeVisible(mMessageContentView, tab == TAB_MESSAGE);
|
2010-12-07 00:49:19 +00:00
|
|
|
mMessageTab.setSelected(tab == TAB_MESSAGE);
|
|
|
|
|
2010-10-04 23:00:02 +00:00
|
|
|
makeVisible(mAttachmentsScroll, tab == TAB_ATTACHMENT);
|
2010-12-07 00:49:19 +00:00
|
|
|
mAttachmentTab.setSelected(tab == TAB_ATTACHMENT);
|
2010-10-04 23:00:02 +00:00
|
|
|
|
2010-12-07 00:49:19 +00:00
|
|
|
makeVisible(mInviteScroll, tab == TAB_INVITE);
|
|
|
|
mInviteTab.setSelected(tab == TAB_INVITE);
|
2010-10-04 23:00:02 +00:00
|
|
|
}
|
|
|
|
|
2010-07-16 22:10:00 +00:00
|
|
|
/**
|
|
|
|
* Handle clicks on sender, which shows {@link QuickContact} or prompts to add
|
|
|
|
* the sender as a contact.
|
|
|
|
*/
|
|
|
|
private void onClickSender() {
|
|
|
|
final Address senderEmail = Address.unpackFirst(mMessage.mFrom);
|
|
|
|
if (senderEmail == null) return;
|
|
|
|
|
2010-09-07 20:30:59 +00:00
|
|
|
if (mContactStatusState == CONTACT_STATUS_STATE_UNLOADED) {
|
|
|
|
// Status not loaded yet.
|
|
|
|
mContactStatusState = CONTACT_STATUS_STATE_UNLOADED_TRIGGERED;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (mContactStatusState == CONTACT_STATUS_STATE_UNLOADED_TRIGGERED) {
|
|
|
|
return; // Already clicked, and waiting for the data.
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mQuickContactLookupUri != null) {
|
|
|
|
QuickContact.showQuickContact(mContext, mFromBadge, mQuickContactLookupUri,
|
|
|
|
QuickContact.MODE_LARGE, null);
|
2010-07-16 22:10:00 +00:00
|
|
|
} else {
|
|
|
|
// No matching contact, ask user to create one
|
2010-09-07 20:30:59 +00:00
|
|
|
final Uri mailUri = Uri.fromParts("mailto", senderEmail.getAddress(), null);
|
2010-07-16 22:10:00 +00:00
|
|
|
final Intent intent = new Intent(ContactsContract.Intents.SHOW_OR_CREATE_CONTACT,
|
|
|
|
mailUri);
|
|
|
|
|
|
|
|
// Pass along full E-mail string for possible create dialog
|
|
|
|
intent.putExtra(ContactsContract.Intents.EXTRA_CREATE_DESCRIPTION,
|
|
|
|
senderEmail.toString());
|
|
|
|
|
|
|
|
// Only provide personal name hint if we have one
|
|
|
|
final String senderPersonal = senderEmail.getPersonal();
|
|
|
|
if (!TextUtils.isEmpty(senderPersonal)) {
|
|
|
|
intent.putExtra(ContactsContract.Intents.Insert.NAME, senderPersonal);
|
|
|
|
}
|
|
|
|
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
|
|
|
|
|
|
|
|
startActivity(intent);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-09-07 20:30:59 +00:00
|
|
|
private static class ContactStatusLoaderCallbacks
|
|
|
|
implements LoaderCallbacks<ContactStatusLoader.Result> {
|
|
|
|
private static final String BUNDLE_EMAIL_ADDRESS = "email";
|
|
|
|
private final MessageViewFragmentBase mFragment;
|
|
|
|
|
|
|
|
public ContactStatusLoaderCallbacks(MessageViewFragmentBase fragment) {
|
|
|
|
mFragment = fragment;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static Bundle createArguments(String emailAddress) {
|
|
|
|
Bundle b = new Bundle();
|
|
|
|
b.putString(BUNDLE_EMAIL_ADDRESS, emailAddress);
|
|
|
|
return b;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Loader<ContactStatusLoader.Result> onCreateLoader(int id, Bundle args) {
|
|
|
|
return new ContactStatusLoader(mFragment.mContext,
|
|
|
|
args.getString(BUNDLE_EMAIL_ADDRESS));
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onLoadFinished(Loader<ContactStatusLoader.Result> loader,
|
|
|
|
ContactStatusLoader.Result result) {
|
|
|
|
boolean triggered =
|
|
|
|
(mFragment.mContactStatusState == CONTACT_STATUS_STATE_UNLOADED_TRIGGERED);
|
|
|
|
mFragment.mContactStatusState = CONTACT_STATUS_STATE_LOADED;
|
|
|
|
mFragment.mQuickContactLookupUri = result.mLookupUri;
|
|
|
|
mFragment.mSenderPresenceView.setImageResource(result.mPresenceResId);
|
|
|
|
if (result.mPhoto != null) { // photo will be null if unknown.
|
|
|
|
mFragment.mFromBadge.setImageBitmap(result.mPhoto);
|
|
|
|
}
|
|
|
|
if (triggered) {
|
|
|
|
mFragment.onClickSender();
|
|
|
|
}
|
|
|
|
}
|
2010-12-16 08:44:50 +00:00
|
|
|
|
2010-12-17 21:31:25 +00:00
|
|
|
@Override
|
2010-12-16 08:44:50 +00:00
|
|
|
public void onLoaderReset(Loader<ContactStatusLoader.Result> loader) {
|
|
|
|
}
|
2010-09-07 20:30:59 +00:00
|
|
|
}
|
|
|
|
|
2010-08-10 00:48:53 +00:00
|
|
|
private void onSaveAttachment(AttachmentInfo info) {
|
|
|
|
if (!Utility.isExternalStorageMounted()) {
|
2010-07-16 22:10:00 +00:00
|
|
|
/*
|
|
|
|
* Abort early if there's no place to save the attachment. We don't want to spend
|
|
|
|
* the time downloading it and then abort.
|
|
|
|
*/
|
|
|
|
Utility.showToast(getActivity(), R.string.message_view_status_attachment_not_saved);
|
|
|
|
return;
|
|
|
|
}
|
2010-08-10 00:48:53 +00:00
|
|
|
Attachment attachment = Attachment.restoreAttachmentWithId(mContext, info.attachmentId);
|
|
|
|
Uri attachmentUri = AttachmentProvider.getAttachmentUri(mAccountId, attachment.mId);
|
2010-07-16 22:10:00 +00:00
|
|
|
|
2010-08-10 00:48:53 +00:00
|
|
|
try {
|
|
|
|
File file = Utility.createUniqueFile(Environment.getExternalStorageDirectory(),
|
|
|
|
attachment.mFileName);
|
|
|
|
Uri contentUri = AttachmentProvider.resolveAttachmentIdToContentUri(
|
|
|
|
mContext.getContentResolver(), attachmentUri);
|
|
|
|
InputStream in = mContext.getContentResolver().openInputStream(contentUri);
|
|
|
|
OutputStream out = new FileOutputStream(file);
|
|
|
|
IOUtils.copy(in, out);
|
|
|
|
out.flush();
|
|
|
|
out.close();
|
|
|
|
in.close();
|
|
|
|
|
|
|
|
Utility.showToast(getActivity(), String.format(
|
|
|
|
mContext.getString(R.string.message_view_status_attachment_saved),
|
|
|
|
file.getName()));
|
|
|
|
MediaOpener.scanAndOpen(getActivity(), file);
|
|
|
|
} catch (IOException ioe) {
|
|
|
|
Utility.showToast(getActivity(), R.string.message_view_status_attachment_not_saved);
|
|
|
|
}
|
2010-07-16 22:10:00 +00:00
|
|
|
}
|
|
|
|
|
2010-08-10 00:48:53 +00:00
|
|
|
private void onViewAttachment(AttachmentInfo info) {
|
|
|
|
Uri attachmentUri = AttachmentProvider.getAttachmentUri(mAccountId, info.attachmentId);
|
|
|
|
Uri contentUri = AttachmentProvider.resolveAttachmentIdToContentUri(
|
|
|
|
mContext.getContentResolver(), attachmentUri);
|
|
|
|
try {
|
|
|
|
Intent intent = new Intent(Intent.ACTION_VIEW);
|
|
|
|
intent.setData(contentUri);
|
|
|
|
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
|
|
|
|
| Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
|
|
|
|
startActivity(intent);
|
|
|
|
} catch (ActivityNotFoundException e) {
|
|
|
|
Utility.showToast(getActivity(), R.string.message_view_display_attachment_toast);
|
|
|
|
// TODO: Add a proper warning message (and lots of upstream cleanup to prevent
|
|
|
|
// it from happening) in the next release.
|
|
|
|
}
|
|
|
|
}
|
2010-07-16 22:10:00 +00:00
|
|
|
|
2010-10-23 19:14:00 +00:00
|
|
|
private void onLoadAttachment(final AttachmentInfo attachment) {
|
2010-08-10 00:48:53 +00:00
|
|
|
attachment.loadButton.setVisibility(View.GONE);
|
2010-10-23 19:14:00 +00:00
|
|
|
// If there's nothing in the download queue, we'll probably start right away so wait a
|
|
|
|
// second before showing the cancel button
|
|
|
|
if (AttachmentDownloadService.getQueueSize() == 0) {
|
|
|
|
// Set to invisible; if the button is still in this state one second from now, we'll
|
|
|
|
// assume the download won't start right away, and we make the cancel button visible
|
|
|
|
attachment.cancelButton.setVisibility(View.INVISIBLE);
|
|
|
|
// Create the timed task that will change the button state
|
|
|
|
new AsyncTask<Void, Void, Void>() {
|
|
|
|
@Override
|
|
|
|
protected Void doInBackground(Void... params) {
|
|
|
|
try {
|
|
|
|
Thread.sleep(1000L);
|
|
|
|
} catch (InterruptedException e) { }
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
@Override
|
|
|
|
protected void onPostExecute(Void result) {
|
|
|
|
if (attachment.cancelButton.getVisibility() == View.INVISIBLE) {
|
|
|
|
attachment.cancelButton.setVisibility(View.VISIBLE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}.execute();
|
|
|
|
} else {
|
|
|
|
attachment.cancelButton.setVisibility(View.VISIBLE);
|
|
|
|
}
|
2010-08-10 00:48:53 +00:00
|
|
|
ProgressBar bar = attachment.progressView;
|
|
|
|
bar.setVisibility(View.VISIBLE);
|
|
|
|
bar.setIndeterminate(true);
|
2010-09-02 17:50:25 +00:00
|
|
|
mController.loadAttachment(attachment.attachmentId, mMessageId, mAccountId);
|
2010-07-16 22:10:00 +00:00
|
|
|
}
|
|
|
|
|
2010-08-10 00:48:53 +00:00
|
|
|
private void onCancelAttachment(AttachmentInfo attachment) {
|
|
|
|
// Don't change button states if we couldn't cancel the download
|
|
|
|
if (AttachmentDownloadService.cancelQueuedAttachment(attachment.attachmentId)) {
|
|
|
|
attachment.loadButton.setVisibility(View.VISIBLE);
|
|
|
|
attachment.cancelButton.setVisibility(View.GONE);
|
|
|
|
ProgressBar bar = attachment.progressView;
|
|
|
|
bar.setVisibility(View.GONE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-07-16 22:10:00 +00:00
|
|
|
/**
|
2010-08-10 00:48:53 +00:00
|
|
|
* Called by ControllerResults. Show the "View" and "Save" buttons; hide "Load"
|
2010-07-16 22:10:00 +00:00
|
|
|
*
|
|
|
|
* @param attachmentId the attachment that was just downloaded
|
|
|
|
*/
|
|
|
|
private void doFinishLoadAttachment(long attachmentId) {
|
2010-08-10 00:48:53 +00:00
|
|
|
AttachmentInfo info = findAttachmentInfo(attachmentId);
|
|
|
|
if (info != null) {
|
|
|
|
info.loadButton.setVisibility(View.INVISIBLE);
|
|
|
|
info.loadButton.setVisibility(View.GONE);
|
2010-09-11 22:34:28 +00:00
|
|
|
if (!TextUtils.isEmpty(info.name)) {
|
|
|
|
info.saveButton.setVisibility(View.VISIBLE);
|
|
|
|
}
|
2010-08-10 00:48:53 +00:00
|
|
|
info.viewButton.setVisibility(View.VISIBLE);
|
2010-07-16 22:10:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void onShowPicturesInHtml() {
|
2010-07-30 00:06:00 +00:00
|
|
|
if (mMessageContentView != null) {
|
|
|
|
mMessageContentView.getSettings().setBlockNetworkLoads(false);
|
|
|
|
if (mHtmlTextWebView != null) {
|
|
|
|
mMessageContentView.loadDataWithBaseURL("email://", mHtmlTextWebView,
|
|
|
|
"text/html", "utf-8", null);
|
2010-07-16 22:10:00 +00:00
|
|
|
}
|
2010-10-04 23:00:02 +00:00
|
|
|
addTabFlags(TAB_FLAGS_PICTURE_LOADED);
|
2010-07-16 22:10:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onClick(View view) {
|
2010-07-30 00:06:00 +00:00
|
|
|
if (!isMessageOpen()) {
|
|
|
|
return; // Ignore.
|
|
|
|
}
|
2010-07-16 22:10:00 +00:00
|
|
|
switch (view.getId()) {
|
2010-11-12 01:38:21 +00:00
|
|
|
case R.id.from_name:
|
|
|
|
case R.id.from_address:
|
2010-09-07 20:30:59 +00:00
|
|
|
case R.id.badge:
|
2010-07-16 22:10:00 +00:00
|
|
|
case R.id.presence:
|
|
|
|
onClickSender();
|
|
|
|
break;
|
2010-08-10 00:48:53 +00:00
|
|
|
case R.id.load:
|
|
|
|
onLoadAttachment((AttachmentInfo) view.getTag());
|
|
|
|
break;
|
|
|
|
case R.id.save:
|
|
|
|
onSaveAttachment((AttachmentInfo) view.getTag());
|
2010-07-16 22:10:00 +00:00
|
|
|
break;
|
|
|
|
case R.id.view:
|
|
|
|
onViewAttachment((AttachmentInfo) view.getTag());
|
|
|
|
break;
|
2010-08-10 00:48:53 +00:00
|
|
|
case R.id.cancel:
|
|
|
|
onCancelAttachment((AttachmentInfo) view.getTag());
|
|
|
|
break;
|
2010-10-04 23:00:02 +00:00
|
|
|
case R.id.show_message:
|
|
|
|
setCurrentTab(TAB_MESSAGE);
|
|
|
|
break;
|
|
|
|
case R.id.show_invite:
|
|
|
|
setCurrentTab(TAB_INVITE);
|
|
|
|
break;
|
|
|
|
case R.id.show_attachments:
|
|
|
|
setCurrentTab(TAB_ATTACHMENT);
|
|
|
|
break;
|
2010-07-16 22:10:00 +00:00
|
|
|
case R.id.show_pictures:
|
|
|
|
onShowPicturesInHtml();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2010-09-07 20:30:59 +00:00
|
|
|
* Start loading contact photo and presence.
|
2010-07-16 22:10:00 +00:00
|
|
|
*/
|
2010-09-07 20:30:59 +00:00
|
|
|
private void queryContactStatus() {
|
|
|
|
initContactStatusViews(); // Initialize the state, just in case.
|
|
|
|
|
|
|
|
// Find the sender email address, and start presence check.
|
2010-07-16 22:10:00 +00:00
|
|
|
if (mMessage != null) {
|
|
|
|
Address sender = Address.unpackFirst(mMessage.mFrom);
|
|
|
|
if (sender != null) {
|
|
|
|
String email = sender.getAddress();
|
|
|
|
if (email != null) {
|
2010-09-07 20:30:59 +00:00
|
|
|
getLoaderManager().restartLoader(PHOTO_LOADER_ID,
|
|
|
|
ContactStatusLoaderCallbacks.createArguments(email),
|
|
|
|
new ContactStatusLoaderCallbacks(this));
|
2010-07-16 22:10:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-07-30 00:06:00 +00:00
|
|
|
/**
|
2010-10-12 21:47:29 +00:00
|
|
|
* Called by {@link LoadMessageTask} and {@link ReloadMessageTask} to load a message in a
|
|
|
|
* subclass specific way.
|
|
|
|
*
|
|
|
|
* NOTE This method is called on a worker thread! Implementations must properly synchronize
|
|
|
|
* when accessing members. This method may be called after or even at the same time as
|
|
|
|
* {@link #clearContent()}.
|
2010-11-22 22:04:04 +00:00
|
|
|
*
|
|
|
|
* @param activity the parent activity. Subclass use it as a context, and to show a toast.
|
2010-07-30 00:06:00 +00:00
|
|
|
*/
|
2010-11-22 22:04:04 +00:00
|
|
|
protected abstract Message openMessageSync(Activity activity);
|
2010-07-30 00:06:00 +00:00
|
|
|
|
2010-07-16 22:10:00 +00:00
|
|
|
/**
|
|
|
|
* Async task for loading a single message outside of the UI thread
|
|
|
|
*/
|
|
|
|
private class LoadMessageTask extends AsyncTask<Void, Void, Message> {
|
|
|
|
|
|
|
|
private final boolean mOkToFetch;
|
2010-09-02 01:40:10 +00:00
|
|
|
private int mMailboxType;
|
2010-07-16 22:10:00 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Special constructor to cache some local info
|
|
|
|
*/
|
2010-07-30 00:06:00 +00:00
|
|
|
public LoadMessageTask(boolean okToFetch) {
|
2010-07-16 22:10:00 +00:00
|
|
|
mOkToFetch = okToFetch;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected Message doInBackground(Void... params) {
|
2010-11-22 22:04:04 +00:00
|
|
|
Activity activity = getActivity();
|
|
|
|
Message message = null;
|
|
|
|
if (activity != null) {
|
|
|
|
message = openMessageSync(activity);
|
|
|
|
}
|
2010-09-02 01:40:10 +00:00
|
|
|
if (message != null) {
|
|
|
|
mMailboxType = Mailbox.getMailboxType(mContext, message.mMailboxKey);
|
|
|
|
if (mMailboxType == -1) {
|
|
|
|
message = null; // mailbox removed??
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return message;
|
2010-07-16 22:10:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void onPostExecute(Message message) {
|
|
|
|
if (isCancelled()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (message == null) {
|
2010-12-22 00:32:41 +00:00
|
|
|
resetView();
|
2010-07-16 22:10:00 +00:00
|
|
|
mCallback.onMessageNotExists();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
mMessageId = message.mId;
|
|
|
|
|
|
|
|
reloadUiFromMessage(message, mOkToFetch);
|
2010-09-07 20:30:59 +00:00
|
|
|
queryContactStatus();
|
Let MessageViewFragment own bottom buttons.
Create a custom view containing the bottons below MVF
(delete, move, reply, etc) and let MVF own this.
These buttons used to be owned by the XL activity itself, because
the UI for these commands will most likely be totally different
from the tablet UI, so the fragment having them looked wrong.
However, this made it harder to make changes suggested by the latest
mock, such as "put reply/forward in the message header".
I think the buttons are semantically part of the message view anyway,
so the fragment owning UI for these commands is probably the way to go.
(And let's worry about the phone UI later.)
Reason for the use of a custom view is that it will make it easier
to make non-trivial UI changes, e.g. "combine reply, reply-all and
forward and make it dropdown."
Also removed obsolete TODOs from MessageListXL.
Change-Id: Ibf93f4c70fe07bdbbe33d2adb6bbd2b96812830d
2010-09-16 18:16:46 +00:00
|
|
|
onMessageShown(mMessageId, mMailboxType);
|
2010-07-16 22:10:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-10-02 01:04:12 +00:00
|
|
|
/**
|
|
|
|
* Kicked by {@link MessageObserver}. Reload the message and update the views.
|
|
|
|
*/
|
|
|
|
private class ReloadMessageTask extends AsyncTask<Void, Void, Message> {
|
|
|
|
@Override
|
|
|
|
protected Message doInBackground(Void... params) {
|
2010-10-07 00:40:07 +00:00
|
|
|
if (!isMessageSpecified()) { // just in case
|
|
|
|
return null;
|
|
|
|
}
|
2010-11-22 22:04:04 +00:00
|
|
|
Activity activity = getActivity();
|
|
|
|
if (activity == null) {
|
|
|
|
return null;
|
|
|
|
} else {
|
|
|
|
return openMessageSync(activity);
|
|
|
|
}
|
2010-10-02 01:04:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void onPostExecute(Message message) {
|
|
|
|
if (isCancelled()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (message == null || message.mMailboxKey != mMessage.mMailboxKey) {
|
|
|
|
// Message deleted or moved.
|
|
|
|
mCallback.onMessageNotExists();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
mMessage = message;
|
|
|
|
updateHeaderView(mMessage);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Let MessageViewFragment own bottom buttons.
Create a custom view containing the bottons below MVF
(delete, move, reply, etc) and let MVF own this.
These buttons used to be owned by the XL activity itself, because
the UI for these commands will most likely be totally different
from the tablet UI, so the fragment having them looked wrong.
However, this made it harder to make changes suggested by the latest
mock, such as "put reply/forward in the message header".
I think the buttons are semantically part of the message view anyway,
so the fragment owning UI for these commands is probably the way to go.
(And let's worry about the phone UI later.)
Reason for the use of a custom view is that it will make it easier
to make non-trivial UI changes, e.g. "combine reply, reply-all and
forward and make it dropdown."
Also removed obsolete TODOs from MessageListXL.
Change-Id: Ibf93f4c70fe07bdbbe33d2adb6bbd2b96812830d
2010-09-16 18:16:46 +00:00
|
|
|
/**
|
|
|
|
* Called when a message is shown to the user.
|
|
|
|
*/
|
|
|
|
protected void onMessageShown(long messageId, int mailboxType) {
|
|
|
|
mCallback.onMessageViewShown(mailboxType);
|
|
|
|
}
|
|
|
|
|
2010-07-30 00:06:00 +00:00
|
|
|
/**
|
|
|
|
* Called when the message body is loaded.
|
|
|
|
*/
|
|
|
|
protected void onPostLoadBody() {
|
|
|
|
}
|
|
|
|
|
2010-07-16 22:10:00 +00:00
|
|
|
/**
|
|
|
|
* Async task for loading a single message body outside of the UI thread
|
|
|
|
*/
|
|
|
|
private class LoadBodyTask extends AsyncTask<Void, Void, String[]> {
|
|
|
|
|
|
|
|
private long mId;
|
|
|
|
private boolean mErrorLoadingMessageBody;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Special constructor to cache some local info
|
|
|
|
*/
|
|
|
|
public LoadBodyTask(long messageId) {
|
|
|
|
mId = messageId;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected String[] doInBackground(Void... params) {
|
|
|
|
try {
|
|
|
|
String text = null;
|
|
|
|
String html = Body.restoreBodyHtmlWithMessageId(mContext, mId);
|
|
|
|
if (html == null) {
|
|
|
|
text = Body.restoreBodyTextWithMessageId(mContext, mId);
|
|
|
|
}
|
|
|
|
return new String[] { text, html };
|
|
|
|
} catch (RuntimeException re) {
|
|
|
|
// This catches SQLiteException as well as other RTE's we've seen from the
|
|
|
|
// database calls, such as IllegalStateException
|
|
|
|
Log.d(Email.LOG_TAG, "Exception while loading message body: " + re.toString());
|
|
|
|
mErrorLoadingMessageBody = true;
|
2010-08-13 22:42:12 +00:00
|
|
|
return null;
|
2010-07-16 22:10:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void onPostExecute(String[] results) {
|
2010-08-13 22:42:12 +00:00
|
|
|
if (results == null || isCancelled()) {
|
2010-07-16 22:10:00 +00:00
|
|
|
if (mErrorLoadingMessageBody) {
|
|
|
|
Utility.showToast(getActivity(), R.string.error_loading_message_body);
|
|
|
|
}
|
2010-12-22 00:32:41 +00:00
|
|
|
resetView();
|
2010-07-16 22:10:00 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
reloadUiFromBody(results[0], results[1]); // text, html
|
2010-07-30 00:06:00 +00:00
|
|
|
onPostLoadBody();
|
2010-07-16 22:10:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Async task for loading attachments
|
|
|
|
*
|
|
|
|
* Note: This really should only be called when the message load is complete - or, we should
|
|
|
|
* leave open a listener so the attachments can fill in as they are discovered. In either case,
|
|
|
|
* this implementation is incomplete, as it will fail to refresh properly if the message is
|
|
|
|
* partially loaded at this time.
|
|
|
|
*/
|
|
|
|
private class LoadAttachmentsTask extends AsyncTask<Long, Void, Attachment[]> {
|
|
|
|
@Override
|
|
|
|
protected Attachment[] doInBackground(Long... messageIds) {
|
|
|
|
return Attachment.restoreAttachmentsWithMessageId(mContext, messageIds[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void onPostExecute(Attachment[] attachments) {
|
2010-12-22 00:32:41 +00:00
|
|
|
try {
|
|
|
|
if (isCancelled() || attachments == null) {
|
|
|
|
return;
|
2010-07-16 22:10:00 +00:00
|
|
|
}
|
2010-12-22 00:32:41 +00:00
|
|
|
boolean htmlChanged = false;
|
|
|
|
int numDisplayedAttachments = 0;
|
|
|
|
for (Attachment attachment : attachments) {
|
|
|
|
if (mHtmlTextRaw != null && attachment.mContentId != null
|
|
|
|
&& attachment.mContentUri != null) {
|
|
|
|
// for html body, replace CID for inline images
|
|
|
|
// Regexp which matches ' src="cid:contentId"'.
|
|
|
|
String contentIdRe =
|
|
|
|
"\\s+(?i)src=\"cid(?-i):\\Q" + attachment.mContentId + "\\E\"";
|
|
|
|
String srcContentUri = " src=\"" + attachment.mContentUri + "\"";
|
|
|
|
mHtmlTextRaw = mHtmlTextRaw.replaceAll(contentIdRe, srcContentUri);
|
|
|
|
htmlChanged = true;
|
|
|
|
} else {
|
|
|
|
addAttachment(attachment);
|
|
|
|
numDisplayedAttachments++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
setAttachmentCount(numDisplayedAttachments);
|
|
|
|
mHtmlTextWebView = mHtmlTextRaw;
|
|
|
|
mHtmlTextRaw = null;
|
|
|
|
if (htmlChanged && mMessageContentView != null) {
|
|
|
|
mMessageContentView.loadDataWithBaseURL("email://", mHtmlTextWebView,
|
|
|
|
"text/html", "utf-8", null);
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
showContent(true, false);
|
2010-07-16 22:10:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private Bitmap getPreviewIcon(AttachmentInfo attachment) {
|
|
|
|
try {
|
|
|
|
return BitmapFactory.decodeStream(
|
|
|
|
mContext.getContentResolver().openInputStream(
|
|
|
|
AttachmentProvider.getAttachmentThumbnailUri(
|
|
|
|
mAccountId, attachment.attachmentId,
|
|
|
|
PREVIEW_ICON_WIDTH,
|
|
|
|
PREVIEW_ICON_HEIGHT)));
|
|
|
|
} catch (Exception e) {
|
2010-09-21 23:49:13 +00:00
|
|
|
Log.d(Email.LOG_TAG, "Attachment preview failed with exception " + e.getMessage());
|
2010-07-16 22:10:00 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void updateAttachmentThumbnail(long attachmentId) {
|
|
|
|
for (int i = 0, count = mAttachments.getChildCount(); i < count; i++) {
|
|
|
|
AttachmentInfo attachment = (AttachmentInfo) mAttachments.getChildAt(i).getTag();
|
|
|
|
if (attachment.attachmentId == attachmentId) {
|
|
|
|
Bitmap previewIcon = getPreviewIcon(attachment);
|
|
|
|
if (previewIcon != null) {
|
|
|
|
attachment.iconView.setImageBitmap(previewIcon);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Copy data from a cursor-refreshed attachment into the UI. Called from UI thread.
|
|
|
|
*
|
|
|
|
* @param attachment A single attachment loaded from the provider
|
|
|
|
*/
|
|
|
|
private void addAttachment(Attachment attachment) {
|
|
|
|
AttachmentInfo attachmentInfo = new AttachmentInfo();
|
|
|
|
attachmentInfo.size = attachment.mSize;
|
|
|
|
attachmentInfo.contentType =
|
|
|
|
AttachmentProvider.inferMimeType(attachment.mFileName, attachment.mMimeType);
|
|
|
|
attachmentInfo.name = attachment.mFileName;
|
|
|
|
attachmentInfo.attachmentId = attachment.mId;
|
|
|
|
|
|
|
|
LayoutInflater inflater = getActivity().getLayoutInflater();
|
|
|
|
View view = inflater.inflate(R.layout.message_view_attachment, null);
|
|
|
|
|
|
|
|
TextView attachmentName = (TextView)view.findViewById(R.id.attachment_name);
|
|
|
|
TextView attachmentInfoView = (TextView)view.findViewById(R.id.attachment_info);
|
|
|
|
ImageView attachmentIcon = (ImageView)view.findViewById(R.id.attachment_icon);
|
|
|
|
Button attachmentView = (Button)view.findViewById(R.id.view);
|
2010-08-10 00:48:53 +00:00
|
|
|
Button attachmentSave = (Button)view.findViewById(R.id.save);
|
|
|
|
Button attachmentLoad = (Button)view.findViewById(R.id.load);
|
|
|
|
Button attachmentCancel = (Button)view.findViewById(R.id.cancel);
|
|
|
|
ProgressBar attachmentProgress = (ProgressBar)view.findViewById(R.id.progress);
|
2010-07-16 22:10:00 +00:00
|
|
|
|
2010-08-10 00:48:53 +00:00
|
|
|
// TODO: Remove this test (acceptable types = everything; unacceptable = nothing)
|
2010-07-16 22:10:00 +00:00
|
|
|
if ((!MimeUtility.mimeTypeMatches(attachmentInfo.contentType,
|
|
|
|
Email.ACCEPTABLE_ATTACHMENT_VIEW_TYPES))
|
|
|
|
|| (MimeUtility.mimeTypeMatches(attachmentInfo.contentType,
|
|
|
|
Email.UNACCEPTABLE_ATTACHMENT_VIEW_TYPES))) {
|
|
|
|
attachmentView.setVisibility(View.GONE);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (attachmentInfo.size > Email.MAX_ATTACHMENT_DOWNLOAD_SIZE) {
|
|
|
|
attachmentView.setVisibility(View.GONE);
|
2010-08-10 00:48:53 +00:00
|
|
|
attachmentSave.setVisibility(View.GONE);
|
2010-07-16 22:10:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
attachmentInfo.viewButton = attachmentView;
|
2010-08-10 00:48:53 +00:00
|
|
|
attachmentInfo.saveButton = attachmentSave;
|
|
|
|
attachmentInfo.loadButton = attachmentLoad;
|
|
|
|
attachmentInfo.cancelButton = attachmentCancel;
|
2010-07-16 22:10:00 +00:00
|
|
|
attachmentInfo.iconView = attachmentIcon;
|
2010-08-10 00:48:53 +00:00
|
|
|
attachmentInfo.progressView = attachmentProgress;
|
|
|
|
|
|
|
|
// If the attachment is loaded, show 100% progress
|
|
|
|
// Note that for POP3 messages, the user will only see "Open" and "Save" since the entire
|
|
|
|
// message is loaded before being shown.
|
2010-08-25 00:17:54 +00:00
|
|
|
if (Utility.attachmentExists(mContext, attachment)) {
|
2010-08-10 00:48:53 +00:00
|
|
|
// Hide "Load", show "View" and "Save"
|
|
|
|
attachmentProgress.setVisibility(View.VISIBLE);
|
|
|
|
attachmentProgress.setProgress(100);
|
|
|
|
attachmentSave.setVisibility(View.VISIBLE);
|
|
|
|
attachmentView.setVisibility(View.VISIBLE);
|
|
|
|
attachmentLoad.setVisibility(View.INVISIBLE);
|
|
|
|
attachmentCancel.setVisibility(View.GONE);
|
2010-12-21 20:37:40 +00:00
|
|
|
|
|
|
|
Bitmap previewIcon = getPreviewIcon(attachmentInfo);
|
|
|
|
if (previewIcon != null) {
|
|
|
|
attachmentIcon.setImageBitmap(previewIcon);
|
|
|
|
}
|
2010-08-10 00:48:53 +00:00
|
|
|
} else {
|
|
|
|
// Show "Load"; hide "View" and "Save"
|
|
|
|
attachmentSave.setVisibility(View.INVISIBLE);
|
|
|
|
attachmentView.setVisibility(View.INVISIBLE);
|
|
|
|
// If the attachment is queued, show the indeterminate progress bar. From this point,.
|
|
|
|
// any progress changes will cause this to be replaced by the normal progress bar
|
|
|
|
if (AttachmentDownloadService.isAttachmentQueued(attachment.mId)){
|
|
|
|
attachmentProgress.setVisibility(View.VISIBLE);
|
|
|
|
attachmentProgress.setIndeterminate(true);
|
|
|
|
attachmentLoad.setVisibility(View.GONE);
|
|
|
|
attachmentCancel.setVisibility(View.VISIBLE);
|
|
|
|
} else {
|
|
|
|
attachmentLoad.setVisibility(View.VISIBLE);
|
|
|
|
attachmentCancel.setVisibility(View.GONE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Don't enable the "save" button if we've got no place to save the file
|
|
|
|
if (!Utility.isExternalStorageMounted()) {
|
|
|
|
attachmentSave.setEnabled(false);
|
|
|
|
}
|
2010-07-16 22:10:00 +00:00
|
|
|
|
|
|
|
view.setTag(attachmentInfo);
|
|
|
|
attachmentView.setOnClickListener(this);
|
|
|
|
attachmentView.setTag(attachmentInfo);
|
2010-08-10 00:48:53 +00:00
|
|
|
attachmentSave.setOnClickListener(this);
|
|
|
|
attachmentSave.setTag(attachmentInfo);
|
|
|
|
attachmentLoad.setOnClickListener(this);
|
|
|
|
attachmentLoad.setTag(attachmentInfo);
|
|
|
|
attachmentCancel.setOnClickListener(this);
|
|
|
|
attachmentCancel.setTag(attachmentInfo);
|
2010-07-16 22:10:00 +00:00
|
|
|
|
|
|
|
attachmentName.setText(attachmentInfo.name);
|
|
|
|
attachmentInfoView.setText(Utility.formatSize(mContext, attachmentInfo.size));
|
|
|
|
|
|
|
|
mAttachments.addView(view);
|
|
|
|
mAttachments.setVisibility(View.VISIBLE);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2010-10-02 01:04:12 +00:00
|
|
|
* Reload the UI from a provider cursor. {@link LoadMessageTask#onPostExecute} calls it.
|
|
|
|
*
|
|
|
|
* Update the header views, and start loading the body.
|
2010-07-16 22:10:00 +00:00
|
|
|
*
|
|
|
|
* @param message A copy of the message loaded from the database
|
|
|
|
* @param okToFetch If true, and message is not fully loaded, it's OK to fetch from
|
|
|
|
* the network. Use false to prevent looping here.
|
|
|
|
*/
|
2010-07-30 00:06:00 +00:00
|
|
|
protected void reloadUiFromMessage(Message message, boolean okToFetch) {
|
2010-07-16 22:10:00 +00:00
|
|
|
mMessage = message;
|
|
|
|
mAccountId = message.mAccountKey;
|
|
|
|
|
2010-10-02 01:04:12 +00:00
|
|
|
mMessageObserver.register(ContentUris.withAppendedId(Message.CONTENT_URI, mMessage.mId));
|
|
|
|
|
|
|
|
updateHeaderView(mMessage);
|
2010-07-16 22:10:00 +00:00
|
|
|
|
|
|
|
// Handle partially-loaded email, as follows:
|
|
|
|
// 1. Check value of message.mFlagLoaded
|
|
|
|
// 2. If != LOADED, ask controller to load it
|
|
|
|
// 3. Controller callback (after loaded) should trigger LoadBodyTask & LoadAttachmentsTask
|
|
|
|
// 4. Else start the loader tasks right away (message already loaded)
|
|
|
|
if (okToFetch && message.mFlagLoaded != Message.FLAG_LOADED_COMPLETE) {
|
|
|
|
mControllerCallback.getWrappee().setWaitForLoadMessageId(message.mId);
|
|
|
|
mController.loadMessageForView(message.mId);
|
|
|
|
} else {
|
|
|
|
mControllerCallback.getWrappee().setWaitForLoadMessageId(-1);
|
|
|
|
// Ask for body
|
|
|
|
mLoadBodyTask = new LoadBodyTask(message.mId);
|
|
|
|
mLoadBodyTask.execute();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-10-02 01:04:12 +00:00
|
|
|
protected void updateHeaderView(Message message) {
|
|
|
|
mSubjectView.setText(message.mSubject);
|
2010-11-12 01:38:21 +00:00
|
|
|
final Address from = Address.unpackFirst(message.mFrom);
|
2010-11-23 00:55:00 +00:00
|
|
|
|
|
|
|
// Set sender address/display name
|
|
|
|
// Note we set " " for empty field, so TextView's won't get squashed.
|
|
|
|
// Otherwise their height will be 0, which breaks the layout.
|
2010-11-12 01:38:21 +00:00
|
|
|
if (from != null) {
|
|
|
|
final String fromFriendly = from.toFriendly();
|
|
|
|
final String fromAddress = from.getAddress();
|
|
|
|
mFromNameView.setText(fromFriendly);
|
2010-11-23 00:55:00 +00:00
|
|
|
mFromAddressView.setText(fromFriendly.equals(fromAddress) ? " " : fromAddress);
|
2010-11-12 01:38:21 +00:00
|
|
|
} else {
|
2010-11-23 00:55:00 +00:00
|
|
|
mFromNameView.setText(" ");
|
|
|
|
mFromAddressView.setText(" ");
|
2010-11-12 01:38:21 +00:00
|
|
|
}
|
2010-10-02 01:04:12 +00:00
|
|
|
Date date = new Date(message.mTimeStamp);
|
2010-11-17 01:28:59 +00:00
|
|
|
// STOPSHIP Use the same format as MessageListItem uses
|
|
|
|
mDateTimeView.setText(mTimeFormat.format(date));
|
2010-10-02 01:04:12 +00:00
|
|
|
mToView.setText(Address.toFriendly(Address.unpack(message.mTo)));
|
|
|
|
String friendlyCc = Address.toFriendly(Address.unpack(message.mCc));
|
|
|
|
mCcView.setText(friendlyCc);
|
|
|
|
mCcContainerView.setVisibility((friendlyCc != null) ? View.VISIBLE : View.GONE);
|
2010-11-12 01:38:21 +00:00
|
|
|
String friendlyBcc = Address.toFriendly(Address.unpack(message.mBcc));
|
|
|
|
mBccView.setText(friendlyBcc);
|
|
|
|
mBccContainerView.setVisibility((friendlyBcc != null) ? View.VISIBLE : View.GONE);
|
2010-10-02 01:04:12 +00:00
|
|
|
}
|
|
|
|
|
2010-07-16 22:10:00 +00:00
|
|
|
/**
|
|
|
|
* Reload the body from the provider cursor. This must only be called from the UI thread.
|
|
|
|
*
|
|
|
|
* @param bodyText text part
|
|
|
|
* @param bodyHtml html part
|
|
|
|
*
|
|
|
|
* TODO deal with html vs text and many other issues <- WHAT DOES IT MEAN??
|
|
|
|
*/
|
|
|
|
private void reloadUiFromBody(String bodyText, String bodyHtml) {
|
|
|
|
String text = null;
|
|
|
|
mHtmlTextRaw = null;
|
|
|
|
boolean hasImages = false;
|
|
|
|
|
|
|
|
if (bodyHtml == null) {
|
|
|
|
text = bodyText;
|
|
|
|
/*
|
|
|
|
* Convert the plain text to HTML
|
|
|
|
*/
|
|
|
|
StringBuffer sb = new StringBuffer("<html><body>");
|
|
|
|
if (text != null) {
|
|
|
|
// Escape any inadvertent HTML in the text message
|
|
|
|
text = EmailHtmlUtil.escapeCharacterToDisplay(text);
|
|
|
|
// Find any embedded URL's and linkify
|
|
|
|
Matcher m = Patterns.WEB_URL.matcher(text);
|
|
|
|
while (m.find()) {
|
|
|
|
int start = m.start();
|
|
|
|
/*
|
|
|
|
* WEB_URL_PATTERN may match domain part of email address. To detect
|
|
|
|
* this false match, the character just before the matched string
|
|
|
|
* should not be '@'.
|
|
|
|
*/
|
|
|
|
if (start == 0 || text.charAt(start - 1) != '@') {
|
|
|
|
String url = m.group();
|
|
|
|
Matcher proto = WEB_URL_PROTOCOL.matcher(url);
|
|
|
|
String link;
|
|
|
|
if (proto.find()) {
|
|
|
|
// This is work around to force URL protocol part be lower case,
|
|
|
|
// because WebView could follow only lower case protocol link.
|
|
|
|
link = proto.group().toLowerCase() + url.substring(proto.end());
|
|
|
|
} else {
|
|
|
|
// Patterns.WEB_URL matches URL without protocol part,
|
|
|
|
// so added default protocol to link.
|
|
|
|
link = "http://" + url;
|
|
|
|
}
|
|
|
|
String href = String.format("<a href=\"%s\">%s</a>", link, url);
|
|
|
|
m.appendReplacement(sb, href);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
m.appendReplacement(sb, "$0");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
m.appendTail(sb);
|
|
|
|
}
|
|
|
|
sb.append("</body></html>");
|
|
|
|
text = sb.toString();
|
|
|
|
} else {
|
|
|
|
text = bodyHtml;
|
|
|
|
mHtmlTextRaw = bodyHtml;
|
|
|
|
hasImages = IMG_TAG_START_REGEX.matcher(text).find();
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO this is not really accurate.
|
|
|
|
// - Images aren't the only network resources. (e.g. CSS)
|
|
|
|
// - If images are attached to the email and small enough, we download them at once,
|
|
|
|
// and won't need network access when they're shown.
|
2010-10-04 23:00:02 +00:00
|
|
|
if (hasImages) {
|
|
|
|
addTabFlags(TAB_FLAGS_HAS_PICTURES);
|
|
|
|
}
|
2010-07-16 22:10:00 +00:00
|
|
|
if (mMessageContentView != null) {
|
|
|
|
mMessageContentView.loadDataWithBaseURL("email://", text, "text/html", "utf-8", null);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ask for attachments after body
|
|
|
|
mLoadAttachmentsTask = new LoadAttachmentsTask();
|
|
|
|
mLoadAttachmentsTask.execute(mMessage.mId);
|
2010-07-30 00:06:00 +00:00
|
|
|
|
|
|
|
mIsMessageLoadedForTest = true;
|
2010-07-16 22:10:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Overrides for WebView behaviors.
|
|
|
|
*/
|
|
|
|
private class CustomWebViewClient extends WebViewClient {
|
|
|
|
@Override
|
|
|
|
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
|
|
|
return mCallback.onUrlInMessageClicked(url);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-08-10 00:48:53 +00:00
|
|
|
private View findAttachmentView(long attachmentId) {
|
|
|
|
for (int i = 0, count = mAttachments.getChildCount(); i < count; i++) {
|
|
|
|
View view = mAttachments.getChildAt(i);
|
|
|
|
AttachmentInfo attachment = (AttachmentInfo) view.getTag();
|
|
|
|
if (attachment.attachmentId == attachmentId) {
|
|
|
|
return view;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
private AttachmentInfo findAttachmentInfo(long attachmentId) {
|
|
|
|
View view = findAttachmentView(attachmentId);
|
|
|
|
if (view != null) {
|
|
|
|
return (AttachmentInfo)view.getTag();
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2010-07-16 22:10:00 +00:00
|
|
|
/**
|
|
|
|
* Controller results listener. We wrap it with {@link ControllerResultUiThreadWrapper},
|
|
|
|
* so all methods are called on the UI thread.
|
|
|
|
*/
|
|
|
|
private class ControllerResults extends Controller.Result {
|
|
|
|
private long mWaitForLoadMessageId;
|
|
|
|
|
|
|
|
public void setWaitForLoadMessageId(long messageId) {
|
|
|
|
mWaitForLoadMessageId = messageId;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2010-12-10 21:36:18 +00:00
|
|
|
public void loadMessageForViewCallback(MessagingException result, long accountId,
|
|
|
|
long messageId, int progress) {
|
2010-07-16 22:10:00 +00:00
|
|
|
if (messageId != mWaitForLoadMessageId) {
|
|
|
|
// We are not waiting for this message to load, so exit quickly
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (result == null) {
|
|
|
|
switch (progress) {
|
|
|
|
case 0:
|
|
|
|
mCallback.onLoadMessageStarted();
|
2010-12-22 00:32:41 +00:00
|
|
|
// Loading from network -- show the progress icon.
|
|
|
|
showContent(false, true);
|
2010-07-16 22:10:00 +00:00
|
|
|
break;
|
|
|
|
case 100:
|
|
|
|
mWaitForLoadMessageId = -1;
|
|
|
|
mCallback.onLoadMessageFinished();
|
|
|
|
// reload UI and reload everything else too
|
|
|
|
// pass false to LoadMessageTask to prevent looping here
|
|
|
|
cancelAllTasks();
|
2010-07-30 00:06:00 +00:00
|
|
|
mLoadMessageTask = new LoadMessageTask(false);
|
2010-07-16 22:10:00 +00:00
|
|
|
mLoadMessageTask.execute();
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
// do nothing - we don't have a progress bar at this time
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
mWaitForLoadMessageId = -1;
|
2010-11-29 19:42:13 +00:00
|
|
|
String error = mContext.getString(R.string.status_network_error);
|
|
|
|
mCallback.onLoadMessageError(error);
|
2010-12-22 00:32:41 +00:00
|
|
|
resetView();
|
2010-07-16 22:10:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2010-12-10 21:36:18 +00:00
|
|
|
public void loadAttachmentCallback(MessagingException result, long accountId,
|
|
|
|
long messageId, long attachmentId, int progress) {
|
2010-07-16 22:10:00 +00:00
|
|
|
if (messageId == mMessageId) {
|
|
|
|
if (result == null) {
|
2010-08-10 00:48:53 +00:00
|
|
|
showAttachmentProgress(attachmentId, progress);
|
2010-07-16 22:10:00 +00:00
|
|
|
switch (progress) {
|
|
|
|
case 100:
|
|
|
|
updateAttachmentThumbnail(attachmentId);
|
|
|
|
doFinishLoadAttachment(attachmentId);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
// do nothing - we don't have a progress bar at this time
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else {
|
2010-08-10 00:48:53 +00:00
|
|
|
AttachmentInfo attachment = findAttachmentInfo(attachmentId);
|
|
|
|
attachment.cancelButton.setVisibility(View.GONE);
|
|
|
|
attachment.loadButton.setVisibility(View.VISIBLE);
|
|
|
|
attachment.progressView.setVisibility(View.INVISIBLE);
|
2010-11-29 19:42:13 +00:00
|
|
|
|
|
|
|
final String error;
|
2010-08-10 00:48:53 +00:00
|
|
|
if (result.getCause() instanceof IOException) {
|
2010-11-29 19:42:13 +00:00
|
|
|
error = mContext.getString(R.string.status_network_error);
|
2010-08-10 00:48:53 +00:00
|
|
|
} else {
|
2010-11-29 19:42:13 +00:00
|
|
|
error = mContext.getString(
|
|
|
|
R.string.message_view_load_attachment_failed_toast,
|
|
|
|
attachment.name);
|
2010-08-10 00:48:53 +00:00
|
|
|
}
|
2010-11-29 19:42:13 +00:00
|
|
|
mCallback.onLoadMessageError(error);
|
2010-07-16 22:10:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-08-10 00:48:53 +00:00
|
|
|
private void showAttachmentProgress(long attachmentId, int progress) {
|
|
|
|
AttachmentInfo attachment = findAttachmentInfo(attachmentId);
|
|
|
|
if (attachment != null) {
|
|
|
|
ProgressBar bar = attachment.progressView;
|
|
|
|
if (progress == 0) {
|
|
|
|
// When the download starts, we can get rid of the indeterminate bar
|
|
|
|
bar.setVisibility(View.VISIBLE);
|
|
|
|
bar.setIndeterminate(false);
|
|
|
|
// And we're not implementing stop of in-progress downloads
|
|
|
|
attachment.cancelButton.setVisibility(View.GONE);
|
|
|
|
}
|
|
|
|
bar.setProgress(progress);
|
2010-07-16 22:10:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2010-07-30 00:06:00 +00:00
|
|
|
|
2010-10-02 01:04:12 +00:00
|
|
|
/**
|
|
|
|
* Class to detect update on the current message (e.g. toggle star). When it gets content
|
|
|
|
* change notifications, it kicks {@link ReloadMessageTask}.
|
|
|
|
*
|
|
|
|
* TODO Use the new Throttle class.
|
|
|
|
*/
|
|
|
|
private class MessageObserver extends ContentObserver implements Runnable {
|
|
|
|
private final Throttle mThrottle;
|
|
|
|
private final ContentResolver mContentResolver;
|
|
|
|
|
|
|
|
private boolean mRegistered;
|
|
|
|
|
|
|
|
public MessageObserver(Handler handler, Context context) {
|
|
|
|
super(handler);
|
|
|
|
mContentResolver = context.getContentResolver();
|
|
|
|
mThrottle = new Throttle("MessageObserver", this, handler);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void unregister() {
|
|
|
|
if (!mRegistered) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
mThrottle.cancelScheduledCallback();
|
|
|
|
mContentResolver.unregisterContentObserver(this);
|
|
|
|
mRegistered = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void register(Uri notifyUri) {
|
|
|
|
unregister();
|
|
|
|
mContentResolver.registerContentObserver(notifyUri, true, this);
|
|
|
|
mRegistered = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean deliverSelfNotifications() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onChange(boolean selfChange) {
|
|
|
|
mThrottle.onEvent();
|
|
|
|
}
|
|
|
|
|
2010-10-07 00:40:07 +00:00
|
|
|
/**
|
|
|
|
* This method is delay-called by {@link Throttle} on the UI thread. Need to make
|
|
|
|
* sure if the fragment is still valid. (i.e. don't reload if clearContent() has been
|
|
|
|
* called.)
|
|
|
|
*/
|
2010-10-02 01:04:12 +00:00
|
|
|
@Override
|
|
|
|
public void run() {
|
2010-10-07 00:40:07 +00:00
|
|
|
if (!isMessageSpecified()) {
|
|
|
|
return;
|
|
|
|
}
|
2010-10-02 01:04:12 +00:00
|
|
|
Utility.cancelTaskInterrupt(mReloadMessageTask);
|
|
|
|
mReloadMessageTask = new ReloadMessageTask();
|
|
|
|
mReloadMessageTask.execute();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-07-30 00:06:00 +00:00
|
|
|
public boolean isMessageLoadedForTest() {
|
|
|
|
return mIsMessageLoadedForTest;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void clearIsMessageLoadedForTest() {
|
|
|
|
mIsMessageLoadedForTest = true;
|
|
|
|
}
|
2010-07-16 22:10:00 +00:00
|
|
|
}
|