com.almarsoft.GroundhogReader.MessageListActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.almarsoft.GroundhogReader.MessageListActivity.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.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Stack;
import java.util.Vector;

import org.apache.commons.net.nntp.Article;
import org.apache.commons.net.nntp.Threader;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
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.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.PowerManager;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.View.OnClickListener;
import android.view.ViewGroup.LayoutParams;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener;

import com.almarsoft.GroundhogReader.lib.DBHelper;
import com.almarsoft.GroundhogReader.lib.DBUtils;
import com.almarsoft.GroundhogReader.lib.GroundhogApplication;
import com.almarsoft.GroundhogReader.lib.HeaderItemClass;
import com.almarsoft.GroundhogReader.lib.MessageTextProcessor;
import com.almarsoft.GroundhogReader.lib.MiniHeader;
import com.almarsoft.GroundhogReader.lib.ServerAuthException;
import com.almarsoft.GroundhogReader.lib.ServerManager;
import com.almarsoft.GroundhogReader.lib.UsenetConstants;

public class MessageListActivity extends Activity {

    //private static final int NOT_FINISHED = 0;
    private static final int DBGETTER_FINISHED_OK = 1;
    private static final int FINISHED_INTERRUPTED = 2;

    private static final int MENU_ITEM_MARKTHREADREAD = 1;
    private static final int MENU_ITEM_MARKTHREADUNREAD = 2;
    private static final int MENU_ITEM_STARTHREAD = 3;
    private static final int MENU_ITEM_BANTHREAD = 4;
    private static final int MENU_ITEM_BANUSER = 5;

    private static final int ID_DIALOG_CATCHUP = 1;

    // packagevisibility because it used by inner class (see dev guide)
    String mGroup;
    private int mGroupID;

    private int mNumUnread;
    private ArrayList<HeaderItemClass> mHeaderItemsList = null;
    private long[] mNumbersArray;

    // packagevisibility because it used by inner class (see dev guide)
    HashSet<String> mFavoritesSet;
    HashSet<String> mMyPostsSet;
    HashSet<String> mReadSet;

    // packagevisibility because it used by inner class (see dev guide)
    SharedPreferences mPrefs;
    private PowerManager.WakeLock mWakeLock = null;

    private ServerManager mServerManager;
    private GroupMessagesDownloadDialog mDownloader = null;
    private boolean mOfflineMode;
    private LoadFromDBAndThreadTask mLoadDBTask = null;

    private ListView mMsgList;
    private TextView mTitleBar;
    private ImageButton mGoGroups;
    private AlertDialog mConfigAlert;

    @Override
    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.messagelist);
        Context context = getApplicationContext();

        // 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(MessageListActivity.this, OptionsActivity.class));
            }
        });

        mNumUnread = 0; // Loaded in OnResume || threadMessagesFromDB()
        mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
        mOfflineMode = mPrefs.getBoolean("offlineMode", true);

        // Get the selected group from the GroupListActivity-passed bundle
        mTitleBar = (TextView) this.findViewById(R.id.topbar_text);
        mGroup = getIntent().getExtras().getString("selectedGroup");
        mGroupID = DBUtils.getGroupIdFromName(mGroup, context);

        mGoGroups = (ImageButton) this.findViewById(R.id.btn_gogroups);
        mGoGroups.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                MessageListActivity.this
                        .startActivity(new Intent(MessageListActivity.this, GroupListActivity.class));
            }
        });

        mServerManager = new ServerManager(context);
        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        mWakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE,
                "GroundhogThreading");

        mMsgList = (ListView) this.findViewById(R.id.list_msgs);
        mMsgList.setOnItemClickListener(mListItemClickListener);
        registerForContextMenu(mMsgList);
        Drawable dw = getResources().getDrawable(R.drawable.greyline2);
        mMsgList.setDivider(dw);

        // Show the progress dialog, get messages from server, write to DB
        // and call the loading of message from DB and threading when it ends
        mWakeLock.acquire();
        threadMessagesFromDB();
    }

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

        if (mWakeLock.isHeld())
            mWakeLock.release();
        Log.d(UsenetConstants.APPNAME, "ListActivity onPause");

        if (mDownloader != null)
            mDownloader.interrupt();

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

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

        if (mWakeLock.isHeld())
            mWakeLock.release();
        Log.d(UsenetConstants.APPNAME, "MessageList onStop");

        if (mLoadDBTask != null && mLoadDBTask.getStatus() != AsyncTask.Status.FINISHED)
            mLoadDBTask.cancel(true);
    }

    // =======================================================================
    // Detect server changes, redraw the items read from the MessageAcitivty 
    // (using the Next and Prev buttons) and get all the favorites on a set
    // =======================================================================

    protected void onResume() {
        super.onResume();

        Log.d(UsenetConstants.APPNAME, "ListActivity 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();
        }

        // ==================================================================================
        // Detect server hostname or charset changes in the settings (if true, go to the
        // (grouplist activity which will handle better the change, cleanup the
        // headers, etc)
        // ==================================================================================

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

        boolean mustThread = false;
        if (mPrefs.getBoolean("readCharsetChanged", false)) {
            mustThread = true;
            Editor editor = mPrefs.edit();
            editor.putBoolean("readCharsetChanged", false);
            editor.commit();
        }

        // ====================================================================================
        // Get all the favorites and load them to a set
        mFavoritesSet = DBUtils.getFavoriteAuthors(getApplicationContext());

        // Now get the unread items. Yes, the select to load mHeaderItemsList is done with
        // "WHERE read=0" but the MessageActivity can also set some messages as read using the
        // Next and Prev buttons
        mReadSet = DBUtils.getReadMessagesSet(mGroup, getApplicationContext());

        if (mHeaderItemsList != null) {
            mNumUnread = mHeaderItemsList.size() - mReadSet.size();
            mTitleBar.setText(mGroup + ":" + mNumUnread);
        }

        mMsgList.invalidateViews();

        if (mServerManager == null)
            mServerManager = new ServerManager(getApplicationContext());

        if (mustThread) {
            mWakeLock.acquire();
            threadMessagesFromDB();
        }
    }

    @Override
    protected Dialog onCreateDialog(int id) {

        ProgressDialog dialog = null;
        if (id == ID_DIALOG_CATCHUP) {
            dialog = new ProgressDialog(this);
            dialog.setMessage(getString(R.string.catchingup_server));
            dialog.setIndeterminate(true);
            dialog.setCancelable(false);
            return dialog;

        }

        return super.onCreateDialog(id);
    }

    private void checkNoUnread() {
        if (mNumUnread == 0)
            Toast.makeText(this, getString(R.string.no_unread_use_sync), Toast.LENGTH_LONG).show();
    }

    // ==================================================================================================
    // Header Clicked Listener (start the MessageActivity and pass the server
    // number)
    // ==================================================================================================

    OnItemClickListener mListItemClickListener = new OnItemClickListener() {

        public void onItemClick(AdapterView<?> parent, View v, int position, long id) {

            Intent intent_MsgDetail = new Intent(MessageListActivity.this, MessageActivity.class);
            intent_MsgDetail.putExtra("articleNumbers", mNumbersArray);
            intent_MsgDetail.putExtra("msgIndexInArray", position);
            intent_MsgDetail.putExtra("group", mGroup);
            startActivity(intent_MsgDetail);
        }
    };

    // =======================================
    // Options menu shown with the "Menu" key
    // =======================================
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.messagelist_menu_newpost:
            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(MessageListActivity.this, OptionsActivity.class));
                            }
                        }).setNegativeButton(getString(R.string.no), null).show();
            } else {
                Intent intent_Compose = new Intent(MessageListActivity.this, ComposeActivity.class);
                intent_Compose.putExtra("isNew", true);
                intent_Compose.putExtra("group", mGroup);
                startActivityForResult(intent_Compose, UsenetConstants.COMPOSEMESSAGEINTENT);
            }
            return true;

        case R.id.messagelist_menu_getnew:
            getNewMessagesFromServer(false);
            return true;

        case R.id.messagelist_menu_get_latest:
            getNewMessagesFromServer(true);
            return true;

        case R.id.messagelist_menu_refresh:
            threadMessagesFromDB();
            return true;

        case R.id.messagelist_menu_markread:
            markAllRead();
            return true;

        case R.id.messagelist_menu_charset:
            startActivity(new Intent(MessageListActivity.this, ReadCharsetActivity.class));
            return true;

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

        case R.id.messagelist_menu_showread:
            Editor editor = mPrefs.edit();
            item.setChecked(!item.isChecked());
            editor.putBoolean("showRead", item.isChecked());
            editor.commit();
            threadMessagesFromDB();
            return true;

        case R.id.messagelist_menu_managebanneds:
            Intent intent_bannedthreads = new Intent(MessageListActivity.this, BannedActivity.class);
            intent_bannedthreads.putExtra("typeban", UsenetConstants.BANNEDTHREADS);
            intent_bannedthreads.putExtra("group", mGroup);
            startActivityForResult(intent_bannedthreads, UsenetConstants.BANNEDACTIVITYINTENT);
            return true;

        case R.id.messagelist_menu_managebannedusers:
            Intent intent_bannedusers = new Intent(MessageListActivity.this, BannedActivity.class);
            intent_bannedusers.putExtra("typeban", UsenetConstants.BANNEDTROLLS);
            intent_bannedusers.putExtra("group", mGroup);
            startActivityForResult(intent_bannedusers, UsenetConstants.BANNEDACTIVITYINTENT);
            return true;

        case R.id.messagelist_menu_catchup:
            catchupGroup();
            return true;
        }
        return false;
    }

    private void catchupGroup() {
        AsyncTask<String, Void, Void> catchupTask = new AsyncTask<String, Void, Void>() {

            @Override
            protected Void doInBackground(String... groupArr) {

                try {
                    mServerManager.catchupGroup(mGroup);
                } catch (IOException e) {
                    Log.w("Groundhog", "Problem catching up with the server");
                    e.printStackTrace();
                } catch (ServerAuthException e) {
                    Log.w("Groundhog", "Problem catching up with the server");
                    e.printStackTrace();
                }
                return null;
            }

            protected void onPostExecute(Void arg0) {
                Toast.makeText(MessageListActivity.this, R.string.catchup_done, Toast.LENGTH_SHORT);
                dismissDialog(ID_DIALOG_CATCHUP);
            }
        };

        showDialog(ID_DIALOG_CATCHUP);
        catchupTask.execute(mGroup);
    }

    // Call groupMessagesDownloader to download messages from this group, 
    // and pass him the callback pointing to threadMessagesFromDB so when it 
    // finishes the messagelist get reloaded
    @SuppressWarnings("unchecked")
    private void getNewMessagesFromServer(boolean getlatest) {
        Vector<String> groupVector = new Vector<String>(1);
        groupVector.add(mGroup);
        Class[] noargs = new Class[0];
        Method callback;
        try {
            callback = this.getClass().getMethod("threadMessagesFromDB", noargs);

            mServerManager.setFetchLatest(getlatest);
            mDownloader = new GroupMessagesDownloadDialog(mServerManager, this);
            mDownloader.synchronize(mOfflineMode, groupVector, callback, callback, this);

        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

    // =============================
    // Context menu for header items
    // =============================
    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
        new MenuInflater(getApplicationContext()).inflate(R.menu.messagelist_item_menu, menu);
        menu.setHeaderTitle(getString(R.string.article_menu));
        super.onCreateContextMenu(menu, v, menuInfo);
    }

    @Override
    public boolean onContextItemSelected(MenuItem item) {

        AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
        int order = item.getOrder();
        HeaderItemClass header = mHeaderItemsList.get(info.position);

        // "Mark thread as read"
        if (order == MENU_ITEM_MARKTHREADREAD)
            markThreadAsReadOrUnread(header, true);

        // "Mark thread as unread"
        if (order == MENU_ITEM_MARKTHREADUNREAD)
            markThreadAsReadOrUnread(header, false);

        if (order == MENU_ITEM_STARTHREAD) {

            ArrayList<HeaderItemClass> itemsProxy = mHeaderItemsList;
            int itemsSize = itemsProxy.size();
            String starred_thread_subject = header.getArticle().simplifiedSubject();

            for (int i = 0; i < itemsSize; i++) {
                if (itemsProxy.get(i).getArticle().simplifiedSubject() == starred_thread_subject) {
                    itemStarClicked(i);
                }
            }
        }

        if (order == MENU_ITEM_BANTHREAD) {
            banThread(header);
            Toast.makeText(this, getString(R.string.thread_ignore), Toast.LENGTH_LONG).show();
        }

        if (order == MENU_ITEM_BANUSER) {
            banUser(header);
            Toast.makeText(this, getString(R.string.author_banned_reload_tohide), Toast.LENGTH_LONG).show();
        }

        return true;
    }

    private void banUser(HeaderItemClass header) {
        String from = header.getArticle().getFrom();
        markUserMessagesAsRead(from);
        DBUtils.banUser(from, getApplicationContext());
        mTitleBar.setText(mGroup + ":" + mNumUnread);
    }

    private void banThread(HeaderItemClass header) {
        markThreadAsReadOrUnread(header, true);
        DBUtils.banThread(mGroup, header.getArticle().simplifiedSubject(), getApplicationContext());
        mTitleBar.setText(mGroup + ":" + mNumUnread);
    }

    // setread == true: marks as read, else: marks as unread
    private void markThreadAsReadOrUnread(HeaderItemClass header, boolean setread) {

        // Proxy stuff
        String thread_subject = header.getArticle().simplifiedSubject();
        String msgId;
        Article article;
        ArrayList<HeaderItemClass> proxyHeaderItems = mHeaderItemsList;
        HashSet<String> proxyReadSet = mReadSet;
        int headerItemsSize = proxyHeaderItems.size();
        int proxyNumUnread = mNumUnread;
        // End proxy stuff

        for (int i = 0; i < headerItemsSize; i++) {
            article = proxyHeaderItems.get(i).getArticle();

            if (thread_subject.equalsIgnoreCase(article.simplifiedSubject())) {
                msgId = article.getArticleId();
                if (setread) {
                    DBUtils.markAsRead(msgId, getApplicationContext());
                    if (!proxyReadSet.contains(msgId))
                        proxyNumUnread--;
                } else {
                    DBUtils.markAsUnread(msgId, getApplicationContext());
                    if (proxyReadSet.contains(msgId))
                        proxyNumUnread++;
                }
            }
        }
        mNumUnread = proxyNumUnread;

        mTitleBar.setText(mGroup + ":" + mNumUnread);

        mReadSet = DBUtils.getReadMessagesSet(mGroup, getApplicationContext());
        DBUtils.updateUnreadInGroupsTable(mNumUnread, mGroupID, getApplicationContext());
        mMsgList.invalidateViews();
    }

    // mark all the messages from a user as read
    private void markUserMessagesAsRead(String from) {

        // Proxy stuff
        String msgId;
        Article article;
        ArrayList<HeaderItemClass> proxyHeaderItems = mHeaderItemsList;
        HashSet<String> proxyReadSet = mReadSet;
        int headerItemsSize = proxyHeaderItems.size();
        int proxyNumUnread = mNumUnread;
        // End proxy stuff

        for (int i = 0; i < headerItemsSize; i++) {
            article = proxyHeaderItems.get(i).getArticle();

            if (from.equalsIgnoreCase(article.getFrom())) {
                msgId = article.getArticleId();
                DBUtils.markAsRead(msgId, getApplicationContext());
                if (!proxyReadSet.contains(msgId))
                    proxyNumUnread--;
            }
        }
        mNumUnread = proxyNumUnread;

        mTitleBar.setText(mGroup + ":" + mNumUnread);

        mReadSet = DBUtils.getReadMessagesSet(mGroup, getApplicationContext());
        DBUtils.updateUnreadInGroupsTable(mNumUnread, mGroupID, getApplicationContext());
        mMsgList.invalidateViews();
    }

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

        if (requestCode == UsenetConstants.COMPOSEMESSAGEINTENT) {

            if (resultCode == RESULT_OK) { // Message Send activity returns here from
                // "new post" (instead of reply)
                if (mOfflineMode && !mPrefs.getBoolean("postDirectlyInOfflineMode", false))
                    Toast.makeText(getApplicationContext(), getString(R.string.stored_outbox_send_next_sync),
                            Toast.LENGTH_SHORT).show();

                else
                    Toast.makeText(getApplicationContext(), getString(R.string.message_sent), Toast.LENGTH_SHORT)
                            .show();
            }

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

            if (resultCode == RESULT_OK) {
                Toast.makeText(getApplicationContext(), getString(R.string.future_unignored_willbe_fetched),
                        Toast.LENGTH_LONG).show();
            }

            else if (resultCode == RESULT_CANCELED) {
                Toast.makeText(getApplicationContext(), getString(R.string.nothing_to_unban), Toast.LENGTH_SHORT)
                        .show();
            }
        }

    }

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

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

        return (super.onCreateOptionsMenu(menu));
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        MenuItem showRead = menu.findItem(R.id.messagelist_menu_showread);
        boolean showReadValue = mPrefs.getBoolean("showRead", false);
        showRead.setChecked(showReadValue);

        if (showReadValue)
            showRead.setTitle(R.string.msglist_group_hideread);
        else
            showRead.setTitle(R.string.msglist_group_showread);

        return (super.onPrepareOptionsMenu(menu));
    }

    // ==========================================================================
    // Mark all the messages from the group as read (called from the menu
    // option)
    // ==========================================================================
    private void markAllRead() {

        String msg = getResources().getString(R.string.mark_read_question);
        msg = java.text.MessageFormat.format(msg, mGroup);

        new AlertDialog.Builder(this).setTitle(getString(R.string.mark_all_read)).setMessage(msg)
                .setPositiveButton(getString(R.string.yes), new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dlg, int sumthin) {
                        if (mHeaderItemsList != null) {

                            // Mark as read in the DB for this group
                            DBUtils.setGroupAllRead(mGroup, getApplicationContext());

                            // Delete all items from the list and
                            // refresh
                            mHeaderItemsList = new ArrayList<HeaderItemClass>();
                            mMsgList.invalidateViews();
                            threadMessagesFromDB();
                        }
                    }
                }).setNegativeButton(getString(R.string.no), new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dlg, int sumthin) {
                        return;
                    }
                }).show();
    }

    // Create a thread with big stack so the recursive functions in nntp.Threader.java doesn't crash the stack
    class BigStackThreader {

        public BigStackThreader(final Article[] articles) throws InterruptedException {
            Runnable r = new Runnable() {
                public void run() {
                    Threader threader = new Threader();
                    Article root = (Article) threader.thread(articles);
                    fillListNonRecursive(root, 0, null);
                    fillNumbersArray();
                }
            };

            Thread t = new Thread(new ThreadGroup("tgroup"), r, "threader_thread_", 1024 * 1024);
            t.start();
            t.join();
        }
    }

    private class LoadFromDBAndThreadTask extends AsyncTask<Void, Integer, Integer> {

        private ProgressDialog mProgress = null;

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

        @Override
        protected Integer doInBackground(Void... arg0) {

            MessageListActivity act = MessageListActivity.this;
            String charset = mPrefs.getString("readDefaultCharset", "ISO8859-15");
            DBHelper dbhelper = new DBHelper(getApplicationContext());
            SQLiteDatabase db = dbhelper.getReadableDatabase();

            // Get the msgIds of all my posts to check for replies on fillListNonRecursive; load them on a set

            if (act.mPrefs.getBoolean("markReplies", true)) {
                act.mMyPostsSet = DBUtils.getGroupSentMessagesSet(act.mGroup, getApplicationContext());
            }

            // This is not moved to a single function in DBUtils because this way we can update realistically the 
            // progressDialog

            String query = null;
            //SQLiteStatement getGroupMessages = null;

            if (mPrefs.getBoolean("showRead", false)) {
                query = "SELECT server_article_id, server_article_number, date, from_header, subject_header, reference_list, clean_subject"
                        + " FROM headers " + " WHERE subscribed_group_id=" + mGroupID;

            } else {
                /*
                getGroupMessages = db.compileStatement("SELECT server_article_id, server_article_number, date, from_header, subject_header, reference_list, clean_subject"
                      + " FROM headers "
                      + " WHERE subscribed_group_id=? AND read=0");
                getGroupMessages.bindLong(1, mGroupID);
                */

                query = "SELECT server_article_id, server_article_number, date, from_header, subject_header, reference_list, clean_subject"
                        + " FROM headers " + " WHERE subscribed_group_id=" + mGroupID + " AND read=0";

            }

            //ResultSet res = getGroupMessages.executeQuery();
            Cursor cur = db.rawQuery(query, null);

            int numArticles = cur.getCount();
            Article[] articles = new Article[numArticles];

            cur.moveToFirst();
            Article currentArticle = null;
            String dbrefs = null;
            String[] artRefs = null;

            for (int i = 0; i < numArticles; i++) {
                if (isCancelled()) {
                    cur.close();
                    db.close();
                    dbhelper.close();
                    return FINISHED_INTERRUPTED;
                }

                currentArticle = new Article();
                currentArticle.setArticleId(cur.getString(0));
                currentArticle.setArticleNumber(cur.getInt(1));
                currentArticle.setDate(cur.getString(2));
                currentArticle.setFrom(MessageTextProcessor.decodeHeaderInArticleInfo(cur.getString(3), charset));
                currentArticle
                        .setSubject(MessageTextProcessor.decodeHeaderInArticleInfo(cur.getString(4), charset));
                currentArticle.setSimplifiedSubject(cur.getString(5));

                dbrefs = cur.getString(5);
                if (dbrefs != null) {
                    artRefs = dbrefs.split(" ");

                    for (String ref : artRefs) {
                        currentArticle.addReference(ref);
                    }
                }

                articles[i] = currentArticle;
                cur.moveToNext();
            }

            cur.close();
            db.close();
            dbhelper.close();

            mHeaderItemsList = new ArrayList<HeaderItemClass>();

            // XXX YYY ZZZ: ORDENAR AQUI
            if (articles.length > 0) {
                // This class call the recursive functions in the message threader inside a thread with a big stack
                // (Android main thread stack is only 8Kb)
                try {
                    new BigStackThreader(articles);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    articles = null;
                    return FINISHED_INTERRUPTED;
                }
            }

            articles = null;
            mNumUnread = numArticles;
            DBUtils.updateUnreadInGroupsTable(mNumUnread, mGroupID, getApplicationContext());
            return DBGETTER_FINISHED_OK;
        }

        @Override
        protected void onPostExecute(Integer resultObj) {

            if (mWakeLock.isHeld())
                mWakeLock.release();
            if (mProgress != null)
                mProgress.dismiss();

            int result = resultObj.intValue();

            switch (result) {

            case DBGETTER_FINISHED_OK:
                if (mHeaderItemsList != null) {
                    mMsgList.setAdapter(new ArticleAdapter(MessageListActivity.this, R.layout.messagelist_item,
                            mHeaderItemsList));
                    mTitleBar.setText(mGroup + ":" + mNumUnread);
                }
                checkNoUnread();
                break;

            case FINISHED_INTERRUPTED:
                // Nothing currently done, but left as stub
                break;
            }

            mLoadDBTask = null;
        }
    }

    // ========================================================
    // Get the messages from the database, thread them and
    // connect the adapter, using an async task
    // ========================================================
    public void threadMessagesFromDB() {

        mDownloader = null;

        mLoadDBTask = new LoadFromDBAndThreadTask();
        mLoadDBTask.execute();
    }

    // =======================================================================================
    // Note: Its non recursive because the obvious recursive version (which I
    // wrote at first)
    // can tore to pieces the stack pretty easily
    // =======================================================================================   
    private void fillListNonRecursive(Article root, int depth, String replyto) {

        Stack<MiniHeader> stack = new Stack<MiniHeader>();

        boolean markReplies = mPrefs.getBoolean("markReplies", true);
        boolean finished = false;

        String clean_subject;
        MiniHeader tmpMiniItem;
        HeaderItemClass ih = null;
        String[] refsArray;
        String msgId;

        ArrayList<HeaderItemClass> nonStarredItems = new ArrayList<HeaderItemClass>();
        HashSet<String> bannedTrollsSet = DBUtils.getBannedTrolls(getApplicationContext());
        HashSet<String> starredSet = DBUtils.getStarredSubjectsSet(getApplicationContext());

        // Proxy for speed
        HashSet<String> myPostsSetProxy = mMyPostsSet;
        ArrayList<HeaderItemClass> headerItemsListProxy = new ArrayList<HeaderItemClass>();
        int refsArrayLen;

        while (!finished) {

            if (root == null)
                finished = true;

            root.setReplyTo(replyto);

            if (!root.isDummy()) {
                ih = new HeaderItemClass(root, depth);

                // Don't feed the troll
                if (!bannedTrollsSet.contains(root.getFrom())) {

                    // Put the replies in red (if configured)
                    if (markReplies) {
                        refsArray = root.getReferences();
                        refsArrayLen = refsArray.length;
                        msgId = null;

                        if (refsArray != null && refsArrayLen > 0) {
                            msgId = refsArray[refsArrayLen - 1];
                        }

                        if (msgId != null && myPostsSetProxy != null && myPostsSetProxy.contains(msgId))
                            ih.myreply = true;
                        else
                            ih.myreply = false;
                    }

                    clean_subject = root.simplifiedSubject();
                    if (starredSet.contains(clean_subject)) {
                        ih.starred = true;
                        headerItemsListProxy.add(ih); // Starred items first
                    } else {
                        // Nonstarred items will be added to mHeaderItemsList at the end
                        nonStarredItems.add(ih);
                    }
                }
            }

            if (root.next != null) {
                tmpMiniItem = new MiniHeader(root.next, depth, replyto);
                stack.push(tmpMiniItem);
            }

            if (root.kid != null) {

                replyto = root.getFrom();
                if (!root.isDummy())
                    ++depth;
                root = root.kid;

            } else if (!stack.empty()) {

                tmpMiniItem = stack.pop();
                root = tmpMiniItem.article;
                depth = tmpMiniItem.depth;
                replyto = tmpMiniItem.replyto;

            } else
                finished = true;

        }

        // Now add the non starred items after the starred ones
        int nonStarredItemsLen = nonStarredItems.size();
        for (int i = 0; i < nonStarredItemsLen; i++) {
            headerItemsListProxy.add(nonStarredItems.get(i));
        }

        mHeaderItemsList = headerItemsListProxy;
        nonStarredItems = null;
    }

    // ==================================================================================
    // Numbers array is an array containing every article server number. It's
    // used for
    // passing to the MessageActivity (along with an index) so it can implement
    // the Next and Prev buttons. If I knew how to pass the HeaderItemsList to
    // it this
    // array would not be neccesary...
    // ==================================================================================
    private void fillNumbersArray() {

        ArrayList<HeaderItemClass> headerItemsProxy = mHeaderItemsList;
        int headerItemsSize = headerItemsProxy.size();
        long[] numbersArrayProxy = new long[headerItemsSize];

        for (int i = 0; i < headerItemsSize; i++) {
            numbersArrayProxy[i] = headerItemsProxy.get(i).getArticle().getArticleNumber();
        }

        mNumbersArray = numbersArrayProxy;
    }

    // ===================================================================
    // Extension of ArrayAdapter which holds and maps the article fields
    // ===================================================================
    private class ArticleAdapter extends ArrayAdapter<HeaderItemClass> {

        private ArrayList<HeaderItemClass> items;

        public ArticleAdapter(Context context, int textViewResourceId, ArrayList<HeaderItemClass> items) {
            super(context, textViewResourceId, items);
            this.items = items;
        }

        @Override
        public View getView(final int position, View convertView, ViewGroup parent) {

            View v = convertView;

            if (v == null) {
                LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                v = vi.inflate(R.layout.messagelist_item, null);
            }

            HeaderItemClass it = items.get(position);
            HeaderItemClass prev = null;

            if (position > 0)
                prev = items.get(position - 1);
            else
                prev = null;

            if (it != null) {
                Article article = it.getArticle();

                LinearLayout fullItemLayout = (LinearLayout) v.findViewById(R.id.full_item_layout);

                View indentView = (View) v.findViewById(R.id.indentation_view);
                LayoutParams indentLayoutParams = indentView.getLayoutParams();
                indentLayoutParams.width = 4 * it.getLevel();
                indentView.setLayoutParams(indentLayoutParams);

                View subjectChangeLine = (View) v.findViewById(R.id.subject_change_line);
                LayoutParams subjectLineLayoutParams = subjectChangeLine.getLayoutParams();

                /*
                 * Check the references of the article. If the first reference of the article it's equal than the last article
                 * first reference (AKA "thread reference") then the subject didn't change
                 */

                String[] thisArticleReferences = article.getReferences();
                String[] prevArticleReferences = null;
                Article prevArt = null;

                if (prev != null) {
                    prevArt = prev.getArticle();
                    prevArticleReferences = prevArt.getReferences();
                }

                final ImageView star = (ImageView) v.findViewById(R.id.img_thread_star);
                LayoutParams starLayoutParams = star.getLayoutParams();

                star.setOnClickListener(new OnClickListener() {

                    public void onClick(View v) {
                        itemStarClicked(position);
                    }
                });

                TextView bigText = (TextView) v.findViewById(R.id.text_big);
                TextView smallText = (TextView) v.findViewById(R.id.text_small);

                boolean subjectChange = false;

                // If there are no references in this article or the previous it's a new subject. It is, too, 
                // if both have references but the previous article first reference it's different from this article first reference.
                if (thisArticleReferences.length == 0 || prevArticleReferences == null
                        || (prevArticleReferences != null && prevArticleReferences.length > 0
                                && !prevArticleReferences[0].trim()
                                        .equalsIgnoreCase(thisArticleReferences[0].trim()))) {

                    subjectChange = true;
                    Log.d("XXX", "subjectChange = true\n");
                } else
                    Log.d("XXX", "subjectChange = false\n");

                // New subject
                if (subjectChange) {

                    fullItemLayout.setBackgroundColor(0x9956a5ec);

                    // Show the subject change line
                    subjectLineLayoutParams.height = 8;
                    subjectChangeLine.setLayoutParams(subjectLineLayoutParams);
                    subjectChangeLine.setVisibility(View.VISIBLE);

                    // Show the star
                    starLayoutParams.width = LayoutParams.FILL_PARENT;
                    star.setLayoutParams(starLayoutParams);
                    star.setVisibility(View.VISIBLE);

                    if (it.starred)
                        star.setImageDrawable(getResources().getDrawable(R.drawable.star_big_on));
                    else
                        star.setImageDrawable(getResources().getDrawable(R.drawable.star_big_off));

                    // Subject in big text, author in small text
                    bigText.setText(article.getSubject(), TextView.BufferType.SPANNABLE);
                    smallText.setText(it.getFromNoEmail());

                }
                // Non-first message in a thread
                else {
                    // Hide the subject change line
                    subjectLineLayoutParams.height = 0;
                    subjectChangeLine.setLayoutParams(subjectLineLayoutParams);
                    subjectChangeLine.setVisibility(View.INVISIBLE);

                    // Hide the star widget
                    starLayoutParams.width = 0;
                    star.setLayoutParams(starLayoutParams);
                    star.setVisibility(View.INVISIBLE);

                    // Author in big text, subject in small text
                    bigText.setText(it.getFromNoEmail(), TextView.BufferType.SPANNABLE);
                    smallText.setText(article.getSubject());

                }

                ImageView fav = (ImageView) v.findViewById(R.id.messagelistitem_img_love);
                LayoutParams favLayoutParams = fav.getLayoutParams();

                // Show or hide the heart marking favorite authors
                if (MessageListActivity.this.mFavoritesSet.contains(article.getFrom())) {
                    fav.setImageDrawable(getResources().getDrawable(R.drawable.love));
                    favLayoutParams.width = LayoutParams.FILL_PARENT;
                    fav.setLayoutParams(favLayoutParams);
                } else {
                    fav.setImageDrawable(getResources().getDrawable(R.drawable.nullimage));
                    favLayoutParams.width = 0;
                    fav.setLayoutParams(favLayoutParams);
                }

                if (MessageListActivity.this.mReadSet.contains(article.getArticleId())) {
                    //it.read = true; read messages in grey
                    fullItemLayout.setBackgroundColor(0x99aaaaaa);
                } else if (it.myreply) {
                    // replies to the user in yellow
                    fullItemLayout.setBackgroundColor(0x99ffff00);
                } else if (!subjectChange) {
                    //it.read = false;
                    fullItemLayout.setBackgroundColor(Color.TRANSPARENT);
                }
            }
            return v;
        }
    }

    private void itemStarClicked(int position) {

        HeaderItemClass header = mHeaderItemsList.get(position);
        boolean newValue = !header.starred;
        ArrayList<HeaderItemClass> itemsProxy = mHeaderItemsList;
        int itemsSize = itemsProxy.size();

        String starred_thread_subject = header.getArticle().simplifiedSubject();

        HeaderItemClass current;
        for (int i = 0; i < itemsSize; i++) {
            current = itemsProxy.get(i);
            if (starred_thread_subject.equalsIgnoreCase(current.getArticle().simplifiedSubject())) {
                current.starred = !current.starred;
            }
        }

        DBUtils.updateStarredThread(newValue, starred_thread_subject, mGroupID, getApplicationContext());
        mMsgList.invalidateViews();
    }
}