com.almarsoft.GroundhogReader.MessageActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.almarsoft.GroundhogReader.MessageActivity.java

Source

/*
Groundhog Usenet Reader
Copyright (C) 2008-2010  Juan Jose Alvarez Martinez
    
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
    
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
    
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

package com.almarsoft.GroundhogReader;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Vector;

import org.apache.commons.net.nntp.Article;
import org.apache.commons.net.nntp.NNTPNoSuchMessageException;
import org.apache.james.mime4j.message.Header;
import org.apache.james.mime4j.message.Message;
import org.apache.james.mime4j.message.TextBody;
import org.apache.james.mime4j.parser.Field;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;

import com.almarsoft.GroundhogReader.lib.DBUtils;
import com.almarsoft.GroundhogReader.lib.FSUtils;
import com.almarsoft.GroundhogReader.lib.GroundhogApplication;
import com.almarsoft.GroundhogReader.lib.MessageTextProcessor;
import com.almarsoft.GroundhogReader.lib.ServerAuthException;
import com.almarsoft.GroundhogReader.lib.ServerManager;
import com.almarsoft.GroundhogReader.lib.UsenetConstants;
import com.almarsoft.GroundhogReader.lib.UsenetReaderException;

public class MessageActivity extends Activity {
    /** Activity showing one message */

    private static final int FINISHED_GET_OK = 1;
    private static final int FETCH_FINISHED_ERROR = 2;
    private static final int FETCH_FINISHED_NOMESSAGE = 3;
    private static final int FETCH_FINISHED_NODISK = 4;

    private int mMsgIndexInArray;
    private long[] mArticleNumbersArray;
    private String mGroup;

    // Loaded from the thread, read by the UI updater:
    private String mBodyText;
    private String mOriginalText;
    private String mSubjectText;
    private String mAuthorText;
    private String mLastSubject;
    private String mCharset = null;
    private Header mHeader;
    private Message mMessage;
    private Vector<HashMap<String, String>> mMimePartsVector;
    private boolean mIsFavorite = false;
    private boolean mShowFullHeaders = false;

    private LinearLayout mMainLayout;
    //private LinearLayout mLayoutAuthor;
    private LinearLayout mLayoutSubject;
    private LinearLayout mLayoutDate;
    private TextView mAuthor;
    private ImageView mHeart;
    private TextView mSubject;
    private TextView mDate;
    private WebView mContent;
    private WebSettings mWebSettings;
    private ImageButton mButton_Prev;
    private ImageButton mButton_Next;
    private ImageButton mButton_GoGroup;
    private ScrollView mScroll;
    private Context mContext;
    private AlertDialog mConfigAlert;

    private SharedPreferences mPrefs;
    final Handler mHandler = new Handler();
    private ServerManager mServerManager;
    private boolean mOfflineMode;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mContext = getApplicationContext();
        setContentView(R.layout.message);

        // Config checker alert dialog
        mConfigAlert = new AlertDialog.Builder(this).create();
        mConfigAlert.setButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dlg, int sumthin) {
                startActivity(new Intent(MessageActivity.this, OptionsActivity.class));
            }
        });

        mPrefs = PreferenceManager.getDefaultSharedPreferences(this);

        mOfflineMode = mPrefs.getBoolean("offlineMode", true);

        Bundle extras = getIntent().getExtras();
        mMsgIndexInArray = extras.getInt("msgIndexInArray");
        mArticleNumbersArray = extras.getLongArray("articleNumbers");
        mGroup = extras.getString("group");

        mMainLayout = (LinearLayout) this.findViewById(R.id.main_message_layout);
        //mLayoutAuthor  = (LinearLayout) this.findViewById(R.id.layout_author);
        mLayoutSubject = (LinearLayout) this.findViewById(R.id.layout_subject);
        mLayoutDate = (LinearLayout) this.findViewById(R.id.layout_date);

        mAuthor = (TextView) this.findViewById(R.id.text_author);
        mHeart = (ImageView) this.findViewById(R.id.img_love);
        //mHeart.setVisibility(ImageView.INVISIBLE);
        mDate = (TextView) this.findViewById(R.id.text_date);
        mSubject = (TextView) this.findViewById(R.id.text_subject);
        mSubjectText = null;
        mLastSubject = null;

        mContent = (WebView) this.findViewById(R.id.text_content);
        mWebSettings = mContent.getSettings();
        mWebSettings.setDefaultTextEncodingName("utf-8");
        mWebSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);
        mWebSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
        mWebSettings.setJavaScriptEnabled(false);
        mWebSettings.setRenderPriority(WebSettings.RenderPriority.HIGH);
        mWebSettings.setSupportZoom(false);
        this.setWebViewSizeFromPrefs(0);

        mScroll = (ScrollView) this.findViewById(R.id.textAreaScroller);

        // Conectar los botones next y prev (sumar/restar 1 a mMsgIndexInArray y
        // llamar a loadMessage();
        mButton_Prev = (ImageButton) this.findViewById(R.id.btn_prev);
        mButton_Prev.setOnClickListener(new OnClickListener() {

            public void onClick(View arg0) {
                if (mMsgIndexInArray > 0) {
                    mMsgIndexInArray--;
                    loadMessage();
                } else {
                    Toast.makeText(MessageActivity.this, getString(R.string.at_first_message), Toast.LENGTH_SHORT)
                            .show();
                }

            }
        });

        mButton_Next = (ImageButton) this.findViewById(R.id.btn_next);
        mButton_Next.setOnClickListener(new OnClickListener() {
            public void onClick(View arg0) {
                if (mMsgIndexInArray + 1 < mArticleNumbersArray.length) {
                    mMsgIndexInArray++;
                    loadMessage();
                } else {
                    Toast.makeText(MessageActivity.this, getString(R.string.no_more_messages), Toast.LENGTH_SHORT)
                            .show();
                }

            }
        });

        mButton_GoGroup = (ImageButton) this.findViewById(R.id.btn_gogroup);
        mButton_GoGroup.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                /*
                 Intent intent_MsgList = new Intent(MessageActivity.this, MessageListActivity.class);
                intent_MsgList.putExtra("selectedGroup", mGroup);
                MessageActivity.this.startActivity(intent_MsgList);
                */
                MessageActivity.this.finish();
            }
        });

        mServerManager = new ServerManager(mContext);

        loadMessage();

        mButton_Prev.setFocusable(false);
        mButton_Next.setFocusable(false);
        mContent.requestFocus();
    }

    private WebViewClient mWebViewClient = new WebViewClient() {
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {

            // Workaround for an Android bug, sometimes if the url doesn't contain a server address it doesn't works
            if (url.startsWith("attachment://fake.com/")) {
                MessageActivity.this.attachClicked(url.replace("attachment://fake.com/", ""));
                return true;
            }

            else {
                Intent intent = new Intent();
                intent.setAction(android.content.Intent.ACTION_VIEW);
                intent.addCategory("android.intent.category.BROWSABLE");
                Uri myUri = Uri.parse(url);
                intent.setData(myUri);
                startActivity(intent);
                return true;
            }
        }
    };

    protected void attachClicked(final String attachcode) {

        HashMap<String, String> tmpattachPart = null;
        String tmpmd5 = null;

        for (HashMap<String, String> part : mMimePartsVector) {
            tmpmd5 = part.get("md5");
            if (tmpmd5.equals(attachcode)) {
                tmpattachPart = part;
                break;
            }
        }

        final String md5 = tmpmd5;
        final HashMap<String, String> attachPart = tmpattachPart;

        if (attachPart != null && md5 != null) {

            new AlertDialog.Builder(this).setTitle(getString(R.string.attachment))
                    .setMessage(getString(R.string.open_save_attach_question))
                    .setPositiveButton(getString(R.string.open), new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dlg, int sumthin) {
                            Intent intent = new Intent();
                            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                            intent.setAction(android.content.Intent.ACTION_VIEW);
                            File attFile = new File(UsenetConstants.EXTERNALSTORAGE + "/" + UsenetConstants.APPNAME
                                    + "/" + UsenetConstants.ATTACHMENTSDIR + "/" + mGroup, md5);
                            Uri attachUri = Uri.fromFile(attFile);
                            intent.setDataAndType(attachUri, attachPart.get("type"));
                            startActivity(intent);
                        }
                    }).setNegativeButton(getString(R.string.save), new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dlg, int sumthin) {
                            try {
                                String finalPath = FSUtils.saveAttachment(md5, mGroup, attachPart.get("name"));
                                Toast.makeText(MessageActivity.this, getString(R.string.saved_to) + finalPath,
                                        Toast.LENGTH_LONG).show();
                            } catch (IOException e) {
                                e.printStackTrace();
                                Toast.makeText(MessageActivity.this,
                                        getString(R.string.could_not_save_colon) + e.toString(), Toast.LENGTH_LONG)
                                        .show();
                            }
                        }
                    }).show();
        }
    }

    private void setWebViewSizeFromPrefs(int increase) {

        int textSize = mPrefs.getInt("webViewTextSize", UsenetConstants.TEXTSIZE_NORMAL);

        if (increase > 0) {
            if (textSize < UsenetConstants.TEXTSIZE_LARGEST) {
                textSize++;
            }
        } else if (increase < 0) {
            if (textSize > UsenetConstants.TEXTSIZE_SMALLEST)
                textSize--;

        }

        Editor editor = mPrefs.edit();
        editor.putInt("webViewTextSize", textSize);
        editor.commit();

        if (mContent != null) {
            switch (textSize) {
            case UsenetConstants.TEXTSIZE_SMALLEST:
                mWebSettings.setTextSize(WebSettings.TextSize.SMALLEST);
                break;

            case UsenetConstants.TEXTSIZE_SMALLER:
                mWebSettings.setTextSize(WebSettings.TextSize.SMALLER);
                break;

            case UsenetConstants.TEXTSIZE_NORMAL:
                mWebSettings.setTextSize(WebSettings.TextSize.NORMAL);
                break;

            case UsenetConstants.TEXTSIZE_LARGER:
                mWebSettings.setTextSize(WebSettings.TextSize.LARGER);
                break;

            case UsenetConstants.TEXTSIZE_LARGEST:
                mWebSettings.setTextSize(WebSettings.TextSize.LARGEST);
            }
        }
    }

    @Override
    protected void onPause() {
        super.onPause();

        Log.d(UsenetConstants.APPNAME, "MessageActivity onPause");

        if (mServerManager != null)
            mServerManager.stop();
        mServerManager = null;

        if (mContent != null)
            mContent.clearCache(true);
    }

    // Try to detect server hostname changes in the settings (if true, go to the 
    // (grouplist activity which will handle better the change)
    @Override
    protected void onResume() {
        super.onResume();

        Log.d(UsenetConstants.APPNAME, "MessageActivity onResume");

        // =============================================
        // Detect empty-values errors in the settings
        // =============================================
        GroundhogApplication grapp = (GroundhogApplication) getApplication();

        if (grapp.checkEmptyConfigValues(this, mPrefs)) {
            mConfigAlert.setTitle(grapp.getConfigValidation_errorTitle());
            mConfigAlert.setMessage(grapp.getConfigValidation_errorText());
            if (mConfigAlert.isShowing())
                mConfigAlert.hide();
            mConfigAlert.show();
        } else {
            if (mConfigAlert.isShowing())
                mConfigAlert.hide();
        }

        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);

        if (prefs.getBoolean("hostChanged", false)) {
            // The host  has changed in the prefs, go to the GroupList
            this.startActivity(new Intent(MessageActivity.this, GroupListActivity.class));
        }

        if (mServerManager == null)
            mServerManager = new ServerManager(mContext);
    }

    @Override
    protected void onActivityResult(int intentCode, int resultCode, Intent data) {
        super.onActivityResult(intentCode, resultCode, data);

        if (intentCode == UsenetConstants.COMPOSEMESSAGEINTENT) {

            if (resultCode == RESULT_OK) {

                if (mOfflineMode && !mPrefs.getBoolean("postDirectlyInOfflineMode", false))
                    Toast.makeText(mContext, getString(R.string.stored_outbox_send_next_sync), Toast.LENGTH_SHORT)
                            .show();
                else
                    Toast.makeText(mContext, getString(R.string.message_sent), Toast.LENGTH_SHORT).show();
            } else if (resultCode == RESULT_CANCELED)
                Toast.makeText(mContext, getString(R.string.message_discarded), Toast.LENGTH_SHORT).show();

        } else if (intentCode == UsenetConstants.BANNEDACTIVITYINTENT) {

            if (resultCode == RESULT_OK)
                Toast.makeText(mContext, getString(R.string.reload_tosee_unbanned_authors), Toast.LENGTH_LONG)
                        .show();
            else if (resultCode == RESULT_CANCELED)
                Toast.makeText(mContext, getString(R.string.nothing_to_unban), Toast.LENGTH_SHORT).show();

        } else if (intentCode == UsenetConstants.QUOTINGINTENT) {

            String composeText;
            if (resultCode == RESULT_OK)
                composeText = data.getStringExtra("quotedMessage");
            else
                composeText = mOriginalText;

            Intent intent_Post = new Intent(MessageActivity.this, ComposeActivity.class);
            intent_Post.putExtra("isNew", false);
            intent_Post.putExtra("From", mAuthorText);
            intent_Post.putExtra("Newsgroups", mHeader.getField("Newsgroups").getBody().trim());
            intent_Post.putExtra("Date", mHeader.getField("Date").getBody().trim());
            intent_Post.putExtra("Message-ID", mHeader.getField("Message-ID").getBody().trim());
            if (mHeader.getField("References") != null)
                intent_Post.putExtra("References", mHeader.getField("References").getBody().trim());
            if (mSubjectText != null)
                intent_Post.putExtra("Subject", mSubjectText);
            intent_Post.putExtra("bodytext", composeText);
            if (data != null)
                intent_Post.putExtra("multipleFollowup", data.getStringExtra("multipleFollowup"));
            intent_Post.putExtra("group", mGroup);
            startActivityForResult(intent_Post, UsenetConstants.COMPOSEMESSAGEINTENT);
        }
    }

    // ================================================
    // Menu setting
    // ================================================

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        new MenuInflater(mContext).inflate(R.menu.messagemenu, menu);

        return (super.onCreateOptionsMenu(menu));

    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        int textSize = mPrefs.getInt("webViewTextSize", UsenetConstants.TEXTSIZE_NORMAL);

        MenuItem bigtext = menu.findItem(R.id.message_menu_bigtext);
        MenuItem smalltext = menu.findItem(R.id.message_menu_smalltext);

        if (textSize == UsenetConstants.TEXTSIZE_LARGEST)
            bigtext.setEnabled(false);
        else
            bigtext.setEnabled(true);

        if (textSize == UsenetConstants.TEXTSIZE_SMALLEST)
            smalltext.setEnabled(false);
        else
            smalltext.setEnabled(true);
        return (super.onPrepareOptionsMenu(menu));

    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {

        case R.id.message_menu_reply:

            if (mHeader != null) {
                String multipleFollowup = mPrefs.getString("multipleFollowup", "ASK");
                String groups = mHeader.getField("Newsgroups").getBody().trim();
                String[] groupsArray = null;

                // If is configured to ask for multiple followup and there are in fact multiple, show the dialog
                if (groups != null) {
                    groupsArray = groups.split(",");
                    if (groupsArray.length > 1) {
                        if (multipleFollowup.equalsIgnoreCase("ASK")) {
                            showFollowupDialog(groupsArray);
                            return true;
                        }
                    }
                }

                startPostingOrQuotingActivity(multipleFollowup);
            } else {
                Toast.makeText(this, getString(R.string.cant_reply_no_header_data), Toast.LENGTH_SHORT).show();
            }

            return true;

        case R.id.message_menu_settings:
            startActivity(new Intent(MessageActivity.this, OptionsActivity.class));
            return true;

        case R.id.message_menu_markunread:
            DBUtils.markAsUnRead(mArticleNumbersArray[mMsgIndexInArray], mContext);
            Toast.makeText(this, getString(R.string.message_marked_unread), Toast.LENGTH_SHORT).show();
            return true;

        case R.id.message_menu_forward:
            forwardMessage();
            return true;

        case R.id.message_menu_favorite:
            toggleFavoriteAuthor();
            return true;

        case R.id.message_menu_ban:
            if (mHeader != null) {
                DBUtils.banUser(mHeader.getField("From").getBody().trim(), mContext);
                Toast.makeText(this, getString(R.string.author_banned_reload_tohide), Toast.LENGTH_LONG).show();
            } else
                Toast.makeText(this, getString(R.string.cant_ban_no_header_data), Toast.LENGTH_SHORT).show();

            return true;

        case R.id.message_menu_manageban:
            Intent intent_bannedthreads = new Intent(MessageActivity.this, BannedActivity.class);
            intent_bannedthreads.putExtra("typeban", UsenetConstants.BANNEDTROLLS);
            startActivityForResult(intent_bannedthreads, UsenetConstants.BANNEDACTIVITYINTENT);
            return true;

        case R.id.message_menu_bigtext:
            setWebViewSizeFromPrefs(1);
            return true;

        case R.id.message_menu_smalltext:
            setWebViewSizeFromPrefs(-1);
            return true;

        case R.id.message_menu_fullheaders:
            toggleFullHeaders();
            return true;
        }
        return false;
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        //ignore orientation change because it would cause the message body to be reloaded
        super.onConfigurationChanged(newConfig);
    }

    private void showFollowupDialog(String[] groups) {
        StringBuilder buf = new StringBuilder(groups.length);

        for (String g : groups)
            buf.append(g + "\n");

        new AlertDialog.Builder(this).setTitle("Multiple followup")
                .setMessage(getString(R.string.followup_multigroup_question) + buf.toString())
                .setPositiveButton(getString(R.string.followup_all_groups), new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dlg, int sumthin) {
                        startPostingOrQuotingActivity("ALL");
                    }
                }).setNegativeButton(getString(R.string.followup_current_group),
                        new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dlg, int sumthin) {
                                startPostingOrQuotingActivity("CURRENT");
                            }
                        })
                .show();
    }

    private void startPostingOrQuotingActivity(String multipleFollowup) {

        if (mHeader == null) {
            Toast.makeText(this, getString(R.string.cant_reply_no_header_data), Toast.LENGTH_SHORT);
            return;
        }

        // Check that the user has set "name" and "email on preferences
        String name = mPrefs.getString("name", null);
        String email = mPrefs.getString("email", null);

        if (name == null || name.trim().length() == 0 || email == null || email.trim().length() == 0) {
            new AlertDialog.Builder(this).setTitle(getString(R.string.user_info_unset))
                    .setMessage(getString(R.string.must_fill_name_email_goto_settings))
                    .setPositiveButton(getString(R.string.yes), new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dlg, int sumthin) {
                            startActivity(new Intent(MessageActivity.this, OptionsActivity.class));
                        }
                    }).setNegativeButton(getString(R.string.no), null).show();
            return;
        }

        boolean useQuoter = mPrefs.getBoolean("useQuotingView", true);
        if (useQuoter) {

            Intent intent_Quote = new Intent(MessageActivity.this, QuotingActivity.class);
            intent_Quote.putExtra("origText", mOriginalText);
            intent_Quote.putExtra("multipleFollowup", multipleFollowup);
            startActivityForResult(intent_Quote, UsenetConstants.QUOTINGINTENT);

        } else {

            Intent intent_Post = new Intent(MessageActivity.this, ComposeActivity.class);
            intent_Post.putExtra("isNew", false);
            intent_Post.putExtra("From", mAuthorText);
            intent_Post.putExtra("bodytext", mOriginalText);
            intent_Post.putExtra("multipleFollowup", multipleFollowup);
            intent_Post.putExtra("group", mGroup);
            intent_Post.putExtra("Newsgroups", mHeader.getField("Newsgroups").getBody().trim());
            intent_Post.putExtra("Date", mHeader.getField("Date").getBody().trim());
            intent_Post.putExtra("Message-ID", mHeader.getField("Message-ID").getBody().trim());
            if (mHeader.getField("References") != null)
                intent_Post.putExtra("References", mHeader.getField("References").getBody().trim());
            if (mSubjectText != null)
                intent_Post.putExtra("Subject", mSubjectText);

            startActivityForResult(intent_Post, UsenetConstants.COMPOSEMESSAGEINTENT);
        }
    }

    private void toggleFavoriteAuthor() {

        if (mHeader == null) {
            Toast.makeText(this, getString(R.string.cant_make_favorite_no_header), Toast.LENGTH_SHORT);
            return;
        }

        DBUtils.setAuthorFavorite(mIsFavorite, !mIsFavorite, mHeader.getField("From").getBody().trim(), mContext);
        mIsFavorite = !mIsFavorite;

        if (mIsFavorite) {
            mHeart.setImageDrawable(getResources().getDrawable(R.drawable.love));
        } else {
            mHeart.setImageDrawable(getResources().getDrawable(R.drawable.nullimage));
        }
    }

    // ===============================================================
    // Forward a message by email using the configured email program
    // ===============================================================
    private void forwardMessage() {
        String forwardMsg = "\n\n\nForwarded message originally written by "
                + mHeader.getField("From").getBody().trim() + " on the newsgroup [" + mGroup + "]: \n\n"
                + mOriginalText;

        Intent i = new Intent(Intent.ACTION_SEND);
        i.putExtra(Intent.EXTRA_TEXT, forwardMsg);
        i.putExtra(Intent.EXTRA_SUBJECT, "FWD: " + mSubjectText);
        i.setType("message/rfc822");
        startActivity(Intent.createChooser(i, "Title:"));
    }

    // Create the progress dialog and call the AsyncTask
    private void loadMessage() {
        new LoadMessageTask().execute(mArticleNumbersArray[mMsgIndexInArray]);
    }

    private void toggleFullHeaders() {

        mShowFullHeaders = !mShowFullHeaders;
        loadMessage();
    }

    // ====================================================
    // ====================================================
    // AsyncTask to load the message and display it
    // ====================================================
    // ====================================================

    private class LoadMessageTask extends AsyncTask<Long, String, Integer> {

        private String mErrorMsg;
        private ProgressDialog mProgress = null;

        @Override
        protected void onPreExecute() {
            MessageActivity mi = MessageActivity.this;
            mProgress = ProgressDialog.show(mi, MessageActivity.this.getString(R.string.message),
                    mi.getString(R.string.requesting_message));
        }

        @SuppressWarnings("unchecked")
        protected Integer doInBackground(Long... serverMsgNumbers) {

            mErrorMsg = "";

            try {
                publishProgress(getString(R.string.fetching_body));

                long serverMsgNumber = serverMsgNumbers[0];
                Hashtable<String, Object> articleData = DBUtils.getHeaderRecordCatchedData(mGroup, serverMsgNumber,
                        MessageActivity.this);
                boolean isCatched = (Boolean) articleData.get("catched");

                if (!isCatched)
                    mServerManager.selectNewsGroup(mGroup, mOfflineMode);
                else // This wont connect (since the message is catched), so the loading will be faster even in online mode
                    mServerManager.selectNewsGroupWithoutConnect(mGroup);

                // ===========================================================================================
                // Get or load the header, and from the header, the from, subject, date,
                // and Content-Transfer-Encoding
                // ===========================================================================================

                mHeader = mServerManager.getHeader((Integer) articleData.get("id"),
                        (String) articleData.get("server_article_id"), false, isCatched);

                if (mHeader == null)
                    throw new UsenetReaderException(getString(R.string.could_not_fetch_header));

                // ===========================================================================================
                // Extract the charset from the Content-Type header or if it's MULTIPART/MIME, the boundary
                // between parts
                // ===========================================================================================

                String[] tmpContentArr = null;
                String[] contentTypeParts = null;
                String tmpFirstToken;

                mCharset = mPrefs.getString("readDefaultCharset", "ISO8859-15");

                Field tmpField = mHeader.getField("Content-Type");
                if (tmpField != null) {
                    tmpContentArr = tmpField.getBody().trim().split(";");

                    for (String cont : tmpContentArr) {
                        contentTypeParts = cont.split("=", 2);
                        tmpFirstToken = contentTypeParts[0].trim();

                        if (contentTypeParts.length > 1 && tmpFirstToken.equalsIgnoreCase("charset"))
                            mCharset = contentTypeParts[1].replace("\"", "").trim();
                    }
                }

                // ===============================================================================
                // Get or load the body, extract the mime text/plain part if it is a Mime message
                // and decode if it is QuotedPrintable
                // ===============================================================================
                mMessage = mServerManager.getMessage(mHeader, (Integer) articleData.get("id"),
                        (String) articleData.get("server_article_id"), false, isCatched, mCharset, getCacheDir());

                Vector<Object> body_attachs = MessageTextProcessor.extractBodySaveAttachments(mGroup, mMessage);
                TextBody textBody = (TextBody) body_attachs.get(0);
                if (textBody == null)
                    throw new UsenetReaderException("Unkown error parsing the message :(");

                if (mHeader.getField("MIME-Version") != null)
                    mMimePartsVector = (Vector<HashMap<String, String>>) body_attachs.get(1);

                if (mSubjectText != null)
                    mLastSubject = Article.simplifySubject(mSubjectText);

                mSubjectText = MessageTextProcessor.decodeSubject(mHeader.getField("Subject"), mCharset, mMessage);

                // Check for uuencoded attachments
                BufferedReader bodyReader = new BufferedReader(textBody.getReader());
                Vector<HashMap<String, String>> uuattachData = MessageTextProcessor
                        .saveUUEncodedAttachments(bodyReader, mGroup);

                if (uuattachData != null) {
                    mBodyText = uuattachData.get(0).get("body");
                    uuattachData.removeElementAt(0);

                    if (uuattachData.size() > 0) {
                        if (mMimePartsVector == null || mMimePartsVector.size() == 0)
                            mMimePartsVector = uuattachData;
                        else {
                            // Join the two vectors
                            for (HashMap<String, String> attach : uuattachData)
                                mMimePartsVector.add(attach);
                        }
                    }
                }

                if (mMimePartsVector != null && mMimePartsVector.size() > 0)
                    DBUtils.updateHeaderRecordAttachments((Integer) articleData.get("id"), mMimePartsVector,
                            mContext);

                return FINISHED_GET_OK;

            } catch (NNTPNoSuchMessageException e) {
                mErrorMsg = getString(R.string.error);
                e.printStackTrace();
                return FETCH_FINISHED_NOMESSAGE;
            } catch (FileNotFoundException e) {
                mErrorMsg = getString(R.string.error);
                e.printStackTrace();
                return FETCH_FINISHED_NODISK;
            } catch (IOException e) {
                mErrorMsg = getString(R.string.error);
                e.printStackTrace();
                return FETCH_FINISHED_ERROR;
            } catch (ServerAuthException e) {
                mErrorMsg = getString(R.string.error);
                e.printStackTrace();
                return FETCH_FINISHED_ERROR;
            } catch (UsenetReaderException e) {
                mErrorMsg = getString(R.string.error);
                e.printStackTrace();
                return FETCH_FINISHED_ERROR;
            } catch (OutOfMemoryError e) {
                mErrorMsg = "Memory error, message with too huge attachments? : " + getString(R.string.error);
                e.printStackTrace();
                return FETCH_FINISHED_ERROR;
            }
        }

        @Override
        protected void onProgressUpdate(String... message) {
            mProgress.setMessage(message[0]);
        }

        protected void onPostExecute(Integer result) {

            if (mProgress != null)
                mProgress.dismiss();

            switch (result) {

            case FINISHED_GET_OK:
                // Show or hide the heart marking favorite authors
                mIsFavorite = DBUtils.isAuthorFavorite(mHeader.getField("From").getBody().trim(), mContext);

                if (mIsFavorite)
                    mHeart.setImageDrawable(getResources().getDrawable(R.drawable.love));
                else
                    mHeart.setImageDrawable(getResources().getDrawable(R.drawable.nullimage));

                mHeart.invalidate();
                //mLayoutAuthor.invalidate();
                mMainLayout.invalidate();

                // Save a copy of the body for the reply so we don't break netiquette rules with
                // the justification applied in sanitizeLinebreaks
                mOriginalText = mBodyText;

                // Justify the text removing artificial '\n' chars so it looks square and nice on the phone screen
                // XXX: Optimizacion: aqui se puede utilizar de forma intermedia un StringBuffer (sanitize
                // lo devuelve y se le pasa a prepareHTML)

                boolean justify = mPrefs.getBoolean("justifyText", false);
                if (justify) {
                    mBodyText = MessageTextProcessor.sanitizeLineBreaks(mBodyText);
                }

                mBodyText = MessageTextProcessor.getHtmlHeader(mCharset)
                        + MessageTextProcessor.getAttachmentsHtml(mMimePartsVector)
                        + MessageTextProcessor.prepareHTML(mBodyText, justify);

                // Show the nice, short, headers or the ugly full headers if the user selected that
                if (!mShowFullHeaders) {
                    //mLayoutAuthor.setVisibility(View.VISIBLE);
                    mLayoutDate.setVisibility(View.VISIBLE);
                    mLayoutSubject.setVisibility(View.VISIBLE);

                    mAuthorText = MessageTextProcessor.decodeFrom(mHeader.getField("From"), mCharset, mMessage);
                    mAuthor.setText(mAuthorText);
                    mDate.setText(mHeader.getField("Date").getBody().trim());
                    mSubject.setText(mSubjectText);

                } else {
                    //mLayoutAuthor.setVisibility(View.INVISIBLE);
                    mLayoutDate.setVisibility(View.INVISIBLE);
                    mLayoutSubject.setVisibility(View.INVISIBLE);
                    mBodyText = MessageTextProcessor.htmlizeFullHeaders(mMessage) + mBodyText;
                }

                mContent.loadDataWithBaseURL("x-data://base", mBodyText, "text/html", mCharset, null);
                mBodyText = null;
                mContent.requestFocus();

                DBUtils.markAsRead(mHeader.getField("Message-ID").getBody().trim(), mContext);

                // Go to the start of the message
                mScroll.scrollTo(0, 0);

                String simplifiedSubject = Article.simplifySubject(mSubjectText);

                if (mLastSubject != null && (!mLastSubject.equalsIgnoreCase(simplifiedSubject))) {
                    Toast.makeText(mContext, getString(R.string.new_subject) + simplifiedSubject,
                            Toast.LENGTH_SHORT).show();
                }

                // Intercept "attachment://" url clicks
                mContent.setWebViewClient(mWebViewClient);
                break;

            case FETCH_FINISHED_ERROR:
                if (mProgress != null)
                    mProgress.dismiss();

                mContent.loadData(getString(R.string.error_loading_kept_unread), "text/html", "UTF-8");

                new AlertDialog.Builder(MessageActivity.this).setTitle(getString(R.string.error))
                        .setMessage(getString(R.string.error_loading_kept_unread_long) + ":" + mErrorMsg)
                        .setNeutralButton(getString(R.string.close), null).show();
                break;

            case FETCH_FINISHED_NODISK:
                mContent.loadData(getString(R.string.error_saving_kept_unread), "text/html", "UTF-8");

                new AlertDialog.Builder(MessageActivity.this).setTitle(getString(R.string.error))
                        .setMessage(getString(R.string.error_saving_kept_unread_long) + ":" + mErrorMsg)
                        .setNeutralButton(getString(R.string.close), null).show();
                break;

            case FETCH_FINISHED_NOMESSAGE:
                mContent.loadData(getString(R.string.server_doesnt_have_message_long), "text/html", "UTF-8");

                new AlertDialog.Builder(MessageActivity.this).setTitle(getString(R.string.error))
                        .setMessage(getString(R.string.server_doesnt_have_message) + ":" + mErrorMsg)
                        .setNeutralButton(getString(R.string.close), null).show();

                DBUtils.markAsRead(mArticleNumbersArray[mMsgIndexInArray], mContext);
                break;
            }
        }
    }
}