cgeo.geocaching.cgeocaches.java Source code

Java tutorial

Introduction

Here is the source code for cgeo.geocaching.cgeocaches.java

Source

package cgeo.geocaching;

import cgeo.geocaching.activity.AbstractActivity;
import cgeo.geocaching.activity.AbstractListActivity;
import cgeo.geocaching.activity.FilteredActivity;
import cgeo.geocaching.activity.Progress;
import cgeo.geocaching.apps.cache.navi.NavigationAppFactory;
import cgeo.geocaching.apps.cachelist.CacheListAppFactory;
import cgeo.geocaching.connector.gc.SearchHandler;
import cgeo.geocaching.enumerations.CacheListType;
import cgeo.geocaching.enumerations.CacheType;
import cgeo.geocaching.enumerations.LoadFlags;
import cgeo.geocaching.enumerations.StatusCode;
import cgeo.geocaching.export.ExportFactory;
import cgeo.geocaching.files.GPXImporter;
import cgeo.geocaching.filter.FilterUserInterface;
import cgeo.geocaching.filter.IFilter;
import cgeo.geocaching.geopoint.Geopoint;
import cgeo.geocaching.loaders.AbstractSearchLoader;
import cgeo.geocaching.loaders.AbstractSearchLoader.CacheListLoaderType;
import cgeo.geocaching.loaders.AddressGeocacheListLoader;
import cgeo.geocaching.loaders.CoordsGeocacheListLoader;
import cgeo.geocaching.loaders.HistoryGeocacheListLoader;
import cgeo.geocaching.loaders.KeywordGeocacheListLoader;
import cgeo.geocaching.loaders.NextPageGeocacheListLoader;
import cgeo.geocaching.loaders.OfflineGeocacheListLoader;
import cgeo.geocaching.loaders.OwnerGeocacheListLoader;
import cgeo.geocaching.loaders.RemoveFromHistoryLoader;
import cgeo.geocaching.loaders.UsernameGeocacheListLoader;
import cgeo.geocaching.maps.CGeoMap;
import cgeo.geocaching.network.Cookies;
import cgeo.geocaching.network.Network;
import cgeo.geocaching.network.Parameters;
import cgeo.geocaching.sorting.CacheComparator;
import cgeo.geocaching.sorting.ComparatorUserInterface;
import cgeo.geocaching.sorting.EventDateComparator;
import cgeo.geocaching.sorting.VisitComparator;
import cgeo.geocaching.ui.CacheListAdapter;
import cgeo.geocaching.ui.LoggingUI;
import cgeo.geocaching.ui.WeakReferenceHandler;
import cgeo.geocaching.utils.AsyncTaskWithProgress;
import cgeo.geocaching.utils.DateUtils;
import cgeo.geocaching.utils.GeoDirHandler;
import cgeo.geocaching.utils.Log;
import cgeo.geocaching.utils.RunnableWithArgument;

import ch.boye.httpclientandroidlib.HttpResponse;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.ListView;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class cgeocaches extends AbstractListActivity
        implements FilteredActivity, LoaderManager.LoaderCallbacks<SearchResult> {

    private static final int MAX_LIST_ITEMS = 1000;
    private static final int MENU_REFRESH_STORED = 2;
    private static final int MENU_CACHE_DETAILS = 4;
    private static final int MENU_DROP_CACHES = 5;
    private static final int MENU_IMPORT_GPX = 6;
    private static final int MENU_CREATE_LIST = 7;
    private static final int MENU_DROP_LIST = 8;
    private static final int MENU_INVERT_SELECTION = 9;
    private static final int MENU_SWITCH_LIST = 17;
    private static final int MENU_IMPORT_WEB = 21;
    private static final int MENU_EXPORT = 22;
    private static final int MENU_REMOVE_FROM_HISTORY = 23;
    private static final int MENU_DROP_CACHE = 24;
    private static final int MENU_MOVE_TO_LIST = 25;
    private static final int MENU_REFRESH = 26;
    private static final int MENU_SWITCH_SELECT_MODE = 52;
    private static final int SUBMENU_SHOW_MAP = 54;
    private static final int SUBMENU_MANAGE_LISTS = 55;
    private static final int SUBMENU_MANAGE_OFFLINE = 56;
    private static final int MENU_SORT = 57;
    private static final int SUBMENU_MANAGE_HISTORY = 60;
    private static final int MENU_RENAME_LIST = 64;
    private static final int MENU_DROP_CACHES_AND_LIST = 65;
    private static final int MENU_DEFAULT_NAVIGATION = 66;
    private static final int MENU_NAVIGATION = 69;
    private static final int MENU_STORE_CACHE = 73;
    private static final int MENU_FILTER = 74;
    private static final int MENU_DELETE_EVENTS = 75;
    private static final int MENU_CLEAR_OFFLINE_LOGS = 76;

    private static final int MSG_DONE = -1;
    private static final int MSG_RESTART_GEO_AND_DIR = -2;
    private static final int MSG_CANCEL = -99;

    private CacheListType type = null;
    private Geopoint coords = null;
    private SearchResult search = null;
    /** The list of shown caches shared with Adapter. Don't manipulate outside of main thread only with Handler */
    private final List<Geocache> cacheList = new ArrayList<Geocache>();
    private CacheListAdapter adapter = null;
    private LayoutInflater inflater = null;
    private View listFooter = null;
    private TextView listFooterText = null;
    private final Progress progress = new Progress();
    private String title = "";
    private int detailTotal = 0;
    private int detailProgress = 0;
    private long detailProgressTime = 0L;
    private LoadDetailsThread threadDetails = null;
    private LoadFromWebThread threadWeb = null;
    private int listId = StoredList.TEMPORARY_LIST_ID; // Only meaningful for the OFFLINE type
    private final GeoDirHandler geoDirHandler=new GeoDirHandler(){

    @Override public void updateGeoData(final IGeoData geo){if(geo.getCoords()!=null){adapter.setActualCoordinates(geo.getCoords());}if(!Settings.isUseCompass()||geo.getSpeed()>5){ // use GPS when speed is higher than 18 km/h
    adapter.setActualHeading(geo.getBearing());}}

    @Override public void updateDirection(final float direction){if(!Settings.isLiveList()){return;}

    if(app.currentGeo().getSpeed()<=5){ // use compass when speed is lower than 18 km/h) {
    final float northHeading=DirectionProvider.getDirectionNow(cgeocaches.this,direction);adapter.setActualHeading(northHeading);}}

    };
    private ContextMenuInfo lastMenuInfo;
    private String contextMenuGeocode = "";
    /**
     * the navigation menu item for the cache list (not the context menu!), or <code>null</code>
     */
    private MenuItem navigationMenu;

    public void handleCachesLoaded() {
        try {
            setAdapter();

            updateTitle();

            setDateComparatorForEventList();

            showFooterMoreCaches();

            if (search != null && search.getError() == StatusCode.UNAPPROVED_LICENSE) {
                AlertDialog.Builder dialog = new AlertDialog.Builder(this);
                dialog.setTitle(res.getString(R.string.license));
                dialog.setMessage(res.getString(R.string.err_license));
                dialog.setCancelable(true);
                dialog.setNegativeButton(res.getString(R.string.license_dismiss),
                        new DialogInterface.OnClickListener() {

                            @Override
                            public void onClick(DialogInterface dialog, int id) {
                                Cookies.clearCookies();
                                dialog.cancel();
                            }
                        });
                dialog.setPositiveButton(res.getString(R.string.license_show),
                        new DialogInterface.OnClickListener() {

                            @Override
                            public void onClick(DialogInterface dialog, int id) {
                                Cookies.clearCookies();
                                startActivity(new Intent(Intent.ACTION_VIEW,
                                        Uri.parse("http://www.geocaching.com/software/agreement.aspx?ID=0")));
                            }
                        });

                AlertDialog alert = dialog.create();
                alert.show();
            } else if (search != null && search.getError() != null) {
                showToast(res.getString(R.string.err_download_fail) + ' ' + search.getError().getErrorString(res)
                        + '.');

                hideLoading();
                showProgress(false);

                finish();
                return;
            }

            setAdapterCurrentCoordinates(false);
        } catch (Exception e) {
            showToast(res.getString(R.string.err_detail_cache_find_any));
            Log.e("cgeocaches.loadCachesHandler", e);

            hideLoading();
            showProgress(false);

            finish();
            return;
        }

        try {
            hideLoading();
            showProgress(false);
        } catch (Exception e2) {
            Log.e("cgeocaches.loadCachesHandler.2", e2);
        }

        adapter.setSelectMode(false);
    }

    private Handler loadCachesHandler = new LoadCachesHandler(this);

    private static class LoadCachesHandler extends WeakReferenceHandler<cgeocaches> {

        protected LoadCachesHandler(cgeocaches activity) {
            super(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            final cgeocaches activity = getActivity();
            if (activity == null) {
                return;
            }
            activity.handleCachesLoaded();
        }
    }

    /**
     * Loads the caches and fills the {@link #cacheList} according to {@link #search} content.
     *
     * If {@link #search} is <code>null</code>, this does nothing.
     */

    private void replaceCacheListFromSearch() {
        if (search != null) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    cacheList.clear();

                    // The database search was moved into the UI call intentionally. If this is done before the runOnUIThread,
                    // then we have 2 sets of caches in memory. This can lead to OOM for huge cache lists.
                    final Set<Geocache> cachesFromSearchResult = search
                            .getCachesFromSearchResult(LoadFlags.LOAD_CACHE_OR_DB);

                    cacheList.addAll(cachesFromSearchResult);
                    adapter.reFilter();
                    updateTitle();
                    showFooterMoreCaches();
                }
            });
        }
    }

    protected void updateTitle() {
        ArrayList<Integer> numbers = new ArrayList<Integer>();
        if (adapter.isFiltered()) {
            numbers.add(adapter.getCount());
        }
        if (search != null) {
            numbers.add(search.getCount());
        }
        if (numbers.isEmpty()) {
            setTitle(title);
        } else {
            setTitle(title + " [" + StringUtils.join(numbers, '/') + ']');
        }
    }

    private Handler loadDetailsHandler=new Handler(){

    @Override public void handleMessage(Message msg){setAdapter();

    if(msg.what>-1){cacheList.get(msg.what).setStatusChecked(false);

    adapter.notifyDataSetChanged();

    int secondsElapsed=(int)((System.currentTimeMillis()-detailProgressTime)/1000);int minutesRemaining=((detailTotal-detailProgress)*secondsElapsed/((detailProgress>0)?detailProgress:1)/60);

    progress.setProgress(detailProgress);if(minutesRemaining<1){progress.setMessage(res.getString(R.string.caches_downloading)+" "+res.getString(R.string.caches_eta_ltm));}else{progress.setMessage(res.getString(R.string.caches_downloading)+" "+minutesRemaining+" "+res.getQuantityString(R.plurals.caches_eta_mins,minutesRemaining));}}else if(msg.what==MSG_CANCEL){if(threadDetails!=null){threadDetails.kill();}}else if(msg.what==MSG_RESTART_GEO_AND_DIR){startGeoAndDir();}else{if(search!=null){final Set<Geocache>cacheListTmp=search.getCachesFromSearchResult(LoadFlags.LOAD_CACHE_OR_DB);if(CollectionUtils.isNotEmpty(cacheListTmp)){cacheList.clear();cacheList.addAll(cacheListTmp);}}

    setAdapterCurrentCoordinates(false);

    showProgress(false);progress.dismiss();

    startGeoAndDir();}}};

/**
 * TODO Possibly parts should be a Thread not a Handler
 */
private Handler downloadFromWebHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        setAdapter();

        adapter.notifyDataSetChanged();

        if (msg.what == 0) { //no caches
            progress.setMessage(res.getString(R.string.web_import_waiting));
        } else if (msg.what == 1) { //cache downloading
            progress.setMessage(res.getString(R.string.web_downloading) + " " + msg.obj + '');
        } else if (msg.what == 2) { //Cache downloaded
            progress.setMessage(res.getString(R.string.web_downloaded) + " " + msg.obj + '');
            refreshCurrentList();
        } else if (msg.what == -2) {
            progress.dismiss();
            showToast(res.getString(R.string.sendToCgeo_download_fail));
            finish();
        } else if (msg.what == -3) {
            progress.dismiss();
            showToast(res.getString(R.string.sendToCgeo_no_registration));
            finish();
        } else if (msg.what == MSG_CANCEL) {
            if (threadWeb != null) {
                threadWeb.kill();
            }
        } else {
            adapter.setSelectMode(false);

            replaceCacheListFromSearch();

            progress.dismiss();
        }
    }
};
    private Handler clearOfflineLogsHandler=new Handler(){

    @Override public void handleMessage(Message msg){if(msg.what!=MSG_CANCEL){adapter.setSelectMode(false);

    refreshCurrentList();

    replaceCacheListFromSearch();

    progress.dismiss();}}};

    private Handler importGpxAttachementFinishedHandler=new Handler(){@Override public void handleMessage(Message msg){refreshCurrentList();}};
    private AbstractSearchLoader currentLoader;

    public cgeocaches() {
        super(true);
    }

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

        setTheme();
        setContentView(R.layout.cacheslist_activity);

        // get parameters
        Bundle extras = getIntent().getExtras();
        if (extras != null) {
            Object typeObject = extras.get(Intents.EXTRA_LIST_TYPE);
            type = (typeObject instanceof CacheListType) ? (CacheListType) typeObject : CacheListType.OFFLINE;
            coords = extras.getParcelable(Intents.EXTRA_COORDS);
        } else {
            extras = new Bundle();
        }
        if (isInvokedFromAttachment()) {
            type = CacheListType.OFFLINE;
            if (coords == null) {
                coords = new Geopoint(0.0, 0.0);
            }
        }

        // Add the list selection in code. This way we can leave the XML layout of the action bar the same as for other activities.
        final View titleBar = findViewById(R.id.actionbar_title);
        titleBar.setClickable(true);
        titleBar.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                selectList(v);
            }
        });

        setTitle(title);
        setAdapter();

        prepareFilterBar();

        currentLoader = (AbstractSearchLoader) getSupportLoaderManager().initLoader(type.ordinal(), extras, this);

        // init
        if (CollectionUtils.isNotEmpty(cacheList)) {
            // currentLoader can be null if this activity is created from a map, as onCreateLoader() will return null.
            if (currentLoader != null && currentLoader.isStarted()) {
                showFooterLoadingCaches();
            } else {
                showFooterMoreCaches();
            }
        }

        if (isInvokedFromAttachment()) {
            importGpxAttachement();
        }
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        if (currentLoader != null && currentLoader.isLoading()) {
            showFooterLoadingCaches();
        }
    }

    private boolean isConcreteList() {
        return type == CacheListType.OFFLINE
                && (listId == StoredList.STANDARD_LIST_ID || listId >= cgData.customListIdOffset);
    }

    private boolean isInvokedFromAttachment() {
        return Intent.ACTION_VIEW.equals(getIntent().getAction());
    }

    private void importGpxAttachement() {
        new AlertDialog.Builder(this).setTitle(res.getString(R.string.gpx_import_title))
                .setMessage(res.getString(R.string.gpx_import_confirm)).setCancelable(false)
                .setPositiveButton(getString(android.R.string.yes), new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int id) {
                        new GPXImporter(cgeocaches.this, listId, importGpxAttachementFinishedHandler).importGPX();
                    }
                }).setNegativeButton(getString(android.R.string.no), new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int id) {
                        dialog.cancel();
                    }
                }).create().show();
    }

    @Override
    public void onResume() {
        super.onResume();

        startGeoAndDir();

        adapter.setSelectMode(false);
        setAdapterCurrentCoordinates(true);

        if (loadCachesHandler != null && search != null) {
            replaceCacheListFromSearch();
            loadCachesHandler.sendEmptyMessage(0);
        }

        // refresh standard list if it has changed (new caches downloaded)
        if (type == CacheListType.OFFLINE && listId >= StoredList.STANDARD_LIST_ID && search != null) {
            SearchResult newSearch = cgData.getBatchOfStoredCaches(coords, Settings.getCacheType(), listId);
            if (newSearch != null && newSearch.getTotal() != search.getTotal()) {
                refreshCurrentList();
            }
        }
    }

    private void setAdapterCurrentCoordinates(final boolean forceSort) {
        final Geopoint coordsNow = app.currentGeo().getCoords();
        if (coordsNow != null) {
            adapter.setActualCoordinates(coordsNow);
            if (forceSort) {
                adapter.forceSort();
            }
        }
    }

    @Override
    public void onPause() {
        removeGeoAndDir();

        super.onPause();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        menu.add(0, MENU_FILTER, 0, res.getString(R.string.caches_filter)).setIcon(R.drawable.ic_menu_filter);

        if (type != CacheListType.HISTORY) {
            menu.add(0, MENU_SORT, 0, res.getString(R.string.caches_sort))
                    .setIcon(R.drawable.ic_menu_sort_alphabetically);
        }

        menu.add(0, MENU_SWITCH_SELECT_MODE, 0, res.getString(R.string.caches_select_mode))
                .setIcon(R.drawable.ic_menu_agenda);
        menu.add(0, MENU_INVERT_SELECTION, 0, res.getString(R.string.caches_select_invert))
                .setIcon(R.drawable.ic_menu_mark);
        if (type == CacheListType.OFFLINE) {
            SubMenu subMenu = menu.addSubMenu(0, SUBMENU_MANAGE_OFFLINE, 0, res.getString(R.string.caches_manage))
                    .setIcon(R.drawable.ic_menu_save);
            subMenu.add(0, MENU_DROP_CACHES, 0, res.getString(R.string.caches_drop_all)); // delete saved caches
            subMenu.add(0, MENU_DROP_CACHES_AND_LIST, 0, res.getString(R.string.caches_drop_all_and_list));
            subMenu.add(0, MENU_REFRESH_STORED, 0, res.getString(R.string.cache_offline_refresh)); // download details for all caches
            subMenu.add(0, MENU_MOVE_TO_LIST, 0, res.getString(R.string.cache_menu_move_list));
            subMenu.add(0, MENU_DELETE_EVENTS, 0, res.getString(R.string.caches_delete_events));
            subMenu.add(0, MENU_CLEAR_OFFLINE_LOGS, 0, res.getString(R.string.caches_clear_offlinelogs));

            //TODO: add submenu/AlertDialog and use R.string.gpx_import_title
            subMenu.add(0, MENU_IMPORT_GPX, 0, res.getString(R.string.gpx_import_title));
            if (Settings.getWebDeviceCode() != null) {
                subMenu.add(0, MENU_IMPORT_WEB, 0, res.getString(R.string.web_import_title));
            }

            subMenu.add(0, MENU_EXPORT, 0, res.getString(R.string.export)); // export caches
        } else {
            if (type == CacheListType.HISTORY) {
                SubMenu subMenu = menu
                        .addSubMenu(0, SUBMENU_MANAGE_HISTORY, 0, res.getString(R.string.caches_manage))
                        .setIcon(R.drawable.ic_menu_save);
                subMenu.add(0, MENU_REMOVE_FROM_HISTORY, 0, res.getString(R.string.cache_clear_history)); // remove from history
                subMenu.add(0, MENU_EXPORT, 0, res.getString(R.string.export)); // export caches
                subMenu.add(0, MENU_CLEAR_OFFLINE_LOGS, 0, res.getString(R.string.caches_clear_offlinelogs));
                menu.add(0, MENU_REFRESH_STORED, 0, res.getString(R.string.cache_offline_refresh))
                        .setIcon(R.drawable.ic_menu_set_as);
            } else {
                menu.add(0, MENU_REFRESH_STORED, 0, res.getString(R.string.caches_store_offline))
                        .setIcon(R.drawable.ic_menu_set_as); // download details for all caches
            }
        }

        navigationMenu = CacheListAppFactory.addMenuItems(menu, this, res);

        if (type == CacheListType.OFFLINE) {
            SubMenu subMenu = menu.addSubMenu(0, SUBMENU_MANAGE_LISTS, 0, res.getString(R.string.list_menu))
                    .setIcon(R.drawable.ic_menu_more);
            subMenu.add(0, MENU_CREATE_LIST, 0, res.getString(R.string.list_menu_create));
            subMenu.add(0, MENU_DROP_LIST, 0, res.getString(R.string.list_menu_drop));
            subMenu.add(0, MENU_RENAME_LIST, 0, res.getString(R.string.list_menu_rename));
            subMenu.add(0, MENU_SWITCH_LIST, 0, res.getString(R.string.list_menu_change));
        }

        return true;
    }

    private static void setVisible(final Menu menu, final int itemId, final boolean visible) {
        final MenuItem item = menu.findItem(itemId);
        if (item != null) {
            item.setVisible(visible);
        }
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        super.onPrepareOptionsMenu(menu);

        try {
            if (adapter.isSelectMode()) {
                menu.findItem(MENU_SWITCH_SELECT_MODE).setTitle(res.getString(R.string.caches_select_mode_exit))
                        .setIcon(R.drawable.ic_menu_clear_playlist);
                menu.findItem(MENU_INVERT_SELECTION).setVisible(true);
            } else {
                menu.findItem(MENU_SWITCH_SELECT_MODE).setTitle(res.getString(R.string.caches_select_mode))
                        .setIcon(R.drawable.ic_menu_agenda);
                menu.findItem(MENU_INVERT_SELECTION).setVisible(false);
            }

            final boolean isEmpty = cacheList.isEmpty();
            final boolean isConcrete = isConcreteList();

            setVisible(menu, MENU_SWITCH_SELECT_MODE, !isEmpty);
            setVisible(menu, SUBMENU_MANAGE_HISTORY, !isEmpty);
            setVisible(menu, SUBMENU_SHOW_MAP, !isEmpty);
            setVisible(menu, MENU_SORT, !isEmpty);
            setVisible(menu, MENU_REFRESH_STORED, !isEmpty && (isConcrete || type != CacheListType.OFFLINE));
            setVisible(menu, MENU_DROP_CACHES, !isEmpty);
            setVisible(menu, MENU_DROP_CACHES_AND_LIST, isConcrete && !isEmpty);
            setVisible(menu, MENU_DELETE_EVENTS, isConcrete && !isEmpty && containsEvents());
            setVisible(menu, MENU_MOVE_TO_LIST, !isEmpty);
            setVisible(menu, MENU_EXPORT, !isEmpty);
            setVisible(menu, MENU_REMOVE_FROM_HISTORY, !isEmpty);
            setVisible(menu, MENU_CLEAR_OFFLINE_LOGS, !isEmpty && containsOfflineLogs());

            if (navigationMenu != null) {
                navigationMenu.setVisible(!isEmpty);
            }

            final boolean hasSelection = adapter != null && adapter.getCheckedCount() > 0;
            final boolean isNonDefaultList = isConcrete && listId != StoredList.STANDARD_LIST_ID;

            if (type == CacheListType.OFFLINE || type == CacheListType.HISTORY) { // only offline list
                setMenuItemLabel(menu, MENU_DROP_CACHES, R.string.caches_drop_selected, R.string.caches_drop_all);
                menu.findItem(MENU_DROP_CACHES_AND_LIST)
                        .setVisible(!hasSelection && isNonDefaultList && !adapter.isFiltered());
                setMenuItemLabel(menu, MENU_REFRESH_STORED, R.string.caches_refresh_selected,
                        R.string.caches_refresh_all);
                setMenuItemLabel(menu, MENU_MOVE_TO_LIST, R.string.caches_move_selected, R.string.caches_move_all);
            } else { // search and global list (all other than offline and history)
                setMenuItemLabel(menu, MENU_REFRESH_STORED, R.string.caches_store_selected,
                        R.string.caches_store_offline);
            }

            MenuItem item = menu.findItem(MENU_DROP_LIST);
            if (item != null) {
                item.setVisible(isNonDefaultList);
            }
            item = menu.findItem(MENU_RENAME_LIST);
            if (item != null) {
                item.setVisible(isNonDefaultList);
            }

            final boolean multipleLists = cgData.getLists().size() >= 2;
            item = menu.findItem(MENU_SWITCH_LIST);
            if (item != null) {
                item.setVisible(multipleLists);
            }
            item = menu.findItem(MENU_MOVE_TO_LIST);
            if (item != null) {
                item.setVisible(!isEmpty);
            }

            setMenuItemLabel(menu, MENU_REMOVE_FROM_HISTORY, R.string.cache_remove_from_history,
                    R.string.cache_clear_history);
            setMenuItemLabel(menu, MENU_EXPORT, R.string.export, R.string.export);
        } catch (Exception e) {
            Log.e("cgeocaches.onPrepareOptionsMenu", e);
        }

        return true;
    }

    private boolean containsEvents() {
        for (Geocache cache : adapter.getCheckedOrAllCaches()) {
            if (cache.isEventCache()) {
                return true;
            }
        }
        return false;
    }

    private boolean containsOfflineLogs() {
        for (Geocache cache : adapter.getCheckedOrAllCaches()) {
            if (cache.isLogOffline()) {
                return true;
            }
        }
        return false;
    }

    private void setMenuItemLabel(final Menu menu, final int menuId, final int resIdSelection, final int resId) {
        final MenuItem menuItem = menu.findItem(menuId);
        if (menuItem == null) {
            return;
        }
        boolean hasSelection = adapter != null && adapter.getCheckedCount() > 0;
        if (hasSelection) {
            menuItem.setTitle(res.getString(resIdSelection) + " (" + adapter.getCheckedCount() + ")");
        } else {
            menuItem.setTitle(res.getString(resId));
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int itemId = item.getItemId();
        switch (itemId) {
        case MENU_SWITCH_SELECT_MODE:
            adapter.switchSelectMode();
            invalidateOptionsMenuCompatible();
            return true;
        case MENU_REFRESH_STORED:
            refreshStored(adapter.getCheckedOrAllCaches());
            invalidateOptionsMenuCompatible();
            return true;
        case MENU_DROP_CACHES:
            dropStored(false);
            invalidateOptionsMenuCompatible();
            return false;
        case MENU_DROP_CACHES_AND_LIST:
            dropStored(true);
            invalidateOptionsMenuCompatible();
            return true;
        case MENU_IMPORT_GPX:
            importGpx();
            invalidateOptionsMenuCompatible();
            return false;
        case MENU_CREATE_LIST:
            new StoredList.UserInterface(this).promptForListCreation(null);
            invalidateOptionsMenuCompatible();
            return false;
        case MENU_DROP_LIST:
            removeList(true);
            invalidateOptionsMenuCompatible();
            return false;
        case MENU_RENAME_LIST:
            renameList();
            return false;
        case MENU_INVERT_SELECTION:
            adapter.invertSelection();
            invalidateOptionsMenuCompatible();
            return false;
        case MENU_SWITCH_LIST:
            selectList(null);
            invalidateOptionsMenuCompatible();
            return false;
        case MENU_FILTER:
            showFilterMenu(null);
            return true;
        case MENU_SORT:
            final CacheComparator oldComparator = adapter.getCacheComparator();
            new ComparatorUserInterface(this).selectComparator(oldComparator,
                    new RunnableWithArgument<CacheComparator>() {
                        @Override
                        public void run(CacheComparator selectedComparator) {
                            // selecting the same sorting twice will toggle the order
                            if (selectedComparator != null && oldComparator != null
                                    && selectedComparator.getClass().equals(oldComparator.getClass())) {
                                adapter.toggleInverseSort();
                            } else {
                                // always reset the inversion for a new sorting criteria
                                adapter.resetInverseSort();
                            }
                            setComparator(selectedComparator);
                        }
                    });
            return true;
        case MENU_IMPORT_WEB:
            importWeb();
            return false;
        case MENU_EXPORT:
            ExportFactory.showExportMenu(adapter.getCheckedOrAllCaches(), this);
            return false;
        case MENU_REMOVE_FROM_HISTORY:
            removeFromHistoryCheck();
            invalidateOptionsMenuCompatible();
            return false;
        case MENU_MOVE_TO_LIST:
            moveCachesToOtherList();
            invalidateOptionsMenuCompatible();
            return true;
        case MENU_DELETE_EVENTS:
            deletePastEvents();
            invalidateOptionsMenuCompatible();
            return true;
        case MENU_CLEAR_OFFLINE_LOGS:
            clearOfflineLogs();
            invalidateOptionsMenuCompatible();
            return true;
        default:
            return CacheListAppFactory.onMenuItemSelected(item, cacheList, this, search);
        }
    }

    public void deletePastEvents() {
        final List<Geocache> deletion = new ArrayList<Geocache>();
        for (Geocache cache : adapter.getCheckedOrAllCaches()) {
            if (cache.isEventCache()) {
                final Date eventDate = cache.getHiddenDate();
                if (DateUtils.daysSince(eventDate.getTime()) > 0) {
                    deletion.add(cache);
                }
            }
        }
        new DropDetailsTask(false).execute(deletion.toArray(new Geocache[deletion.size()]));
    }

    public void clearOfflineLogs() {
        progress.show(this, null, res.getString(R.string.caches_clear_offlinelogs_progress), true,
                clearOfflineLogsHandler.obtainMessage(MSG_CANCEL));
        new ClearOfflineLogsThread(clearOfflineLogsHandler).start();
    }

    /**
     * called from the filter bar view
     */
    @Override
    public void showFilterMenu(final View view) {
        new FilterUserInterface(this).selectFilter(new RunnableWithArgument<IFilter>() {
            @Override
            public void run(IFilter selectedFilter) {
                if (selectedFilter != null) {
                    setFilter(selectedFilter);
                } else {
                    // clear filter
                    setFilter(null);
                }
            }
        });
    }

    private void setComparator(final CacheComparator comparator) {
        adapter.setComparator(comparator);
    }

    @Override
    public void onCreateContextMenu(final ContextMenu menu, final View view,
            final ContextMenu.ContextMenuInfo info) {
        super.onCreateContextMenu(menu, view, info);

        AdapterContextMenuInfo adapterInfo = null;
        try {
            adapterInfo = (AdapterContextMenuInfo) info;
        } catch (Exception e) {
            Log.w("cgeocaches.onCreateContextMenu", e);
        }

        if (adapterInfo == null || adapterInfo.position >= adapter.getCount()) {
            return;
        }
        final Geocache cache = adapter.getItem(adapterInfo.position);

        menu.setHeaderTitle(StringUtils.defaultIfBlank(cache.getName(), cache.getGeocode()));

        contextMenuGeocode = cache.getGeocode();

        if (cache.getCoords() != null) {
            menu.add(0, MENU_DEFAULT_NAVIGATION, 0,
                    NavigationAppFactory.getDefaultNavigationApplication().getName());
            menu.add(1, MENU_NAVIGATION, 0, res.getString(R.string.cache_menu_navigate))
                    .setIcon(R.drawable.ic_menu_mapmode);
            LoggingUI.addMenuItems(this, menu, cache);
            menu.add(0, MENU_CACHE_DETAILS, 0, res.getString(R.string.cache_menu_details));
        }
        if (cache.isOffline()) {
            menu.add(0, MENU_DROP_CACHE, 0, res.getString(R.string.cache_offline_drop));
            menu.add(0, MENU_MOVE_TO_LIST, 0, res.getString(R.string.cache_menu_move_list));
            menu.add(0, MENU_EXPORT, 0, res.getString(R.string.export));
            menu.add(0, MENU_REFRESH, 0, res.getString(R.string.cache_menu_refresh));
        } else {
            menu.add(0, MENU_STORE_CACHE, 0, res.getString(R.string.cache_offline_store));
        }
    }

    private void moveCachesToOtherList() {
        new StoredList.UserInterface(this).promptForListSelection(R.string.cache_menu_move_list,
                new RunnableWithArgument<Integer>() {

                    @Override
                    public void run(Integer newListId) {
                        cgData.moveToList(adapter.getCheckedOrAllCaches(), newListId);
                        adapter.setSelectMode(false);

                        refreshCurrentList();
                    }
                }, true, listId);
    }

    @Override
    public boolean onContextItemSelected(MenuItem item) {
        ContextMenu.ContextMenuInfo info = item.getMenuInfo();

        // restore menu info for sub menu items, see
        // https://code.google.com/p/android/issues/detail?id=7139
        if (info == null) {
            info = lastMenuInfo;
            lastMenuInfo = null;
        }

        AdapterContextMenuInfo adapterInfo = null;
        try {
            adapterInfo = (AdapterContextMenuInfo) info;
        } catch (Exception e) {
            Log.w("cgeocaches.onContextItemSelected", e);
        }

        final Geocache cache = adapterInfo != null ? getCacheFromAdapter(adapterInfo) : null;

        // just in case the list got resorted while we are executing this code
        if (cache == null) {
            return true;
        }

        final int id = item.getItemId();
        switch (id) {
        case MENU_DEFAULT_NAVIGATION:
            NavigationAppFactory.startDefaultNavigationApplication(1, this, cache);
            break;
        case MENU_NAVIGATION:
            NavigationAppFactory.showNavigationMenu(this, cache, null, null);
            break;
        case MENU_CACHE_DETAILS:
            final Intent cachesIntent = new Intent(this, CacheDetailActivity.class);
            cachesIntent.putExtra(Intents.EXTRA_GEOCODE, cache.getGeocode());
            cachesIntent.putExtra(Intents.EXTRA_NAME, cache.getName());
            startActivity(cachesIntent);
            break;
        case MENU_DROP_CACHE:
            cache.drop(new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    adapter.notifyDataSetChanged();
                    refreshCurrentList();
                }
            });
            break;
        case MENU_MOVE_TO_LIST:
            new StoredList.UserInterface(this).promptForListSelection(R.string.cache_menu_move_list,
                    new RunnableWithArgument<Integer>() {

                        @Override
                        public void run(Integer newListId) {
                            cgData.moveToList(Collections.singletonList(cache), newListId);
                            adapter.setSelectMode(false);
                            refreshCurrentList();
                        }
                    }, true, listId);
            break;
        case MENU_STORE_CACHE:
        case MENU_REFRESH:
            refreshStored(Collections.singletonList(cache));
            break;
        case MENU_EXPORT:
            ExportFactory.showExportMenu(Collections.singletonList(cache), this);
            return false;
        default:
            // we must remember the menu info for the sub menu, there is a bug
            // in Android:
            // https://code.google.com/p/android/issues/detail?id=7139
            lastMenuInfo = info;
            LoggingUI.onMenuItemSelected(item, this, cache);
        }

        return true;
    }

    /**
     * Extract a cache from adapter data.
     *
     * @param adapterInfo
     *            an adapterInfo
     * @return the pointed cache
     */
    private Geocache getCacheFromAdapter(final AdapterContextMenuInfo adapterInfo) {
        final Geocache cache = adapter.getItem(adapterInfo.position);
        if (cache.getGeocode().equalsIgnoreCase(contextMenuGeocode)) {
            return cache;
        }

        return adapter.findCacheByGeocode(contextMenuGeocode);
    }

    private boolean setFilter(IFilter filter) {
        adapter.setFilter(filter);
        prepareFilterBar();
        updateTitle();
        invalidateOptionsMenuCompatible();
        return true;
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            if (adapter.isSelectMode()) {
                adapter.setSelectMode(false);
                return true;
            }
        }
        return super.onKeyDown(keyCode, event);
    }

    private void setAdapter() {
        if (listFooter == null) {
            if (inflater == null) {
                inflater = getLayoutInflater();
            }
            listFooter = inflater.inflate(R.layout.cacheslist_footer, null);
            listFooter.setClickable(true);
            listFooter.setOnClickListener(new MoreCachesListener());

            listFooterText = (TextView) listFooter.findViewById(R.id.more_caches);

            getListView().addFooterView(listFooter);
        }

        if (adapter == null) {
            final ListView list = getListView();

            registerForContextMenu(list);
            list.setLongClickable(true);

            adapter = new CacheListAdapter(this, cacheList, type);
            setListAdapter(adapter);
        } else {
            adapter.notifyDataSetChanged();
        }
        adapter.forceSort();
        adapter.reFilter();
    }

    private void showFooterLoadingCaches() {
        if (listFooter == null) {
            return;
        }

        listFooterText.setText(res.getString(R.string.caches_more_caches_loading));
        listFooter.setClickable(false);
        listFooter.setOnClickListener(null);
    }

    private void showFooterMoreCaches() {
        if (listFooter == null) {
            return;
        }

        boolean enableMore = (type != CacheListType.OFFLINE && cacheList.size() < MAX_LIST_ITEMS);
        if (enableMore && search != null) {
            final int count = search.getTotal();
            enableMore = enableMore && count > 0 && cacheList.size() < count;
        }

        if (enableMore) {
            listFooterText.setText(res.getString(R.string.caches_more_caches) + " ("
                    + res.getString(R.string.caches_more_caches_currently) + ": " + cacheList.size() + ")");
            listFooter.setOnClickListener(new MoreCachesListener());
        } else {
            listFooterText.setText(res.getString(CollectionUtils.isEmpty(cacheList) ? R.string.caches_no_cache
                    : R.string.caches_more_caches_no));
            listFooter.setOnClickListener(null);
        }
        listFooter.setClickable(enableMore);
    }

    private void startGeoAndDir() {
        geoDirHandler.startGeo();
        if (Settings.isLiveMap()) {
            geoDirHandler.startDir();
        }
    }

    private void removeGeoAndDir() {
        geoDirHandler.stopGeoAndDir();
    }

    private void importGpx() {
        GpxFileListActivity.startSubActivity(this, listId);
    }

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

    public void refreshStored(final List<Geocache> caches) {
        detailTotal = caches.size();
        if (detailTotal == 0) {
            return;
        }

        if (!Network.isNetworkConnected(getApplicationContext())) {
            showToast(getString(R.string.err_server));
            return;
        }

        if (Settings.getChooseList() && type != CacheListType.OFFLINE) {
            // let user select list to store cache in
            new StoredList.UserInterface(this).promptForListSelection(R.string.list_title,
                    new RunnableWithArgument<Integer>() {
                        @Override
                        public void run(final Integer selectedListId) {
                            refreshStored(caches, selectedListId);
                        }
                    }, true, StoredList.TEMPORARY_LIST_ID);
        } else {
            refreshStored(caches, this.listId);
        }
    }

    private void refreshStored(final List<Geocache> caches, final int storeListId) {
        detailProgress = 0;

        showProgress(false);

        int etaTime = ((detailTotal * 25) / 60);
        String message;
        if (etaTime < 1) {
            message = res.getString(R.string.caches_downloading) + " " + res.getString(R.string.caches_eta_ltm);
        } else {
            message = res.getString(R.string.caches_downloading) + " " + etaTime + " "
                    + res.getQuantityString(R.plurals.caches_eta_mins, etaTime);
        }

        progress.show(this, null, message, ProgressDialog.STYLE_HORIZONTAL,
                loadDetailsHandler.obtainMessage(MSG_CANCEL));
        progress.setMaxProgressAndReset(detailTotal);

        detailProgressTime = System.currentTimeMillis();

        threadDetails = new LoadDetailsThread(loadDetailsHandler, caches, storeListId);
        threadDetails.start();
    }

    public void removeFromHistoryCheck() {
        AlertDialog.Builder dialog = new AlertDialog.Builder(this);
        dialog.setCancelable(true);
        dialog.setTitle(res.getString(R.string.caches_removing_from_history));
        dialog.setMessage((adapter != null && adapter.getCheckedCount() > 0)
                ? res.getString(R.string.cache_remove_from_history)
                : res.getString(R.string.cache_clear_history));
        dialog.setPositiveButton(getString(android.R.string.yes), new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int id) {
                removeFromHistory();
                dialog.cancel();
            }
        });
        dialog.setNegativeButton(getString(android.R.string.no), new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int id) {
                dialog.cancel();
            }
        });

        AlertDialog alert = dialog.create();
        alert.show();
    }

    public void removeFromHistory() {
        final List<Geocache> caches = adapter.getCheckedOrAllCaches();
        final String[] geocodes = new String[caches.size()];
        for (int i = 0; i < geocodes.length; i++) {
            geocodes[i] = caches.get(i).getGeocode();
        }
        Bundle b = new Bundle();
        b.putStringArray(Intents.EXTRA_CACHELIST, geocodes);
        getSupportLoaderManager().initLoader(CacheListLoaderType.REMOVE_FROM_HISTORY.ordinal(), b, this);
    }

    public void importWeb() {
        detailProgress = 0;

        showProgress(false);
        progress.show(this, null, res.getString(R.string.web_import_waiting), true,
                downloadFromWebHandler.obtainMessage(MSG_CANCEL));

        threadWeb = new LoadFromWebThread(downloadFromWebHandler, listId);
        threadWeb.start();
    }

    public void dropStored(final boolean removeListAfterwards) {
        AlertDialog.Builder dialog = new AlertDialog.Builder(this);
        dialog.setCancelable(true);
        dialog.setTitle(res.getString(R.string.caches_drop_stored));

        if (adapter.getCheckedCount() > 0) {
            dialog.setMessage(res.getString(R.string.caches_drop_selected_ask));
        } else {
            dialog.setMessage(res.getString(R.string.caches_drop_all_ask));
        }
        dialog.setPositiveButton(getString(android.R.string.yes), new DialogInterface.OnClickListener() {

            @Override
            public void onClick(DialogInterface dialog, int id) {
                dropSelected(removeListAfterwards);
                dialog.cancel();
            }
        });
        dialog.setNegativeButton(getString(android.R.string.no), new DialogInterface.OnClickListener() {

            @Override
            public void onClick(DialogInterface dialog, int id) {
                dialog.cancel();
            }
        });

        AlertDialog alert = dialog.create();
        alert.show();
    }

    public void dropSelected(boolean removeListAfterwards) {
        final List<Geocache> selected = adapter.getCheckedOrAllCaches();
        new DropDetailsTask(removeListAfterwards).execute(selected.toArray(new Geocache[selected.size()]));
    }

    /**
     * Thread to refresh the cache details.
     */

    private class LoadDetailsThread extends Thread {

        final private Handler handler;
        final private int listIdLD;
        private volatile boolean needToStop = false;
        private long last = 0L;
        final private List<Geocache> caches;

        public LoadDetailsThread(Handler handlerIn, List<Geocache> caches, int listId) {
            handler = handlerIn;
            this.caches = caches;

            // in case of online lists, set the list id to the standard list
            this.listIdLD = Math.max(listId, StoredList.STANDARD_LIST_ID);
        }

        public void kill() {
            needToStop = true;
        }

        @Override
        public void run() {
            removeGeoAndDir();

            final List<Geocache> cachesWithStaticMaps = new ArrayList<Geocache>(this.caches.size());
            for (Geocache cache : this.caches) {
                if (Settings.isStoreOfflineMaps() && cache.hasStaticMap()) {
                    cachesWithStaticMaps.add(cache);
                    continue;
                }
                if (!refreshCache(cache)) {
                    // in case of interruption avoid the second loop
                    cachesWithStaticMaps.clear();
                    break;
                }
            }

            for (Geocache cache : cachesWithStaticMaps) {
                if (!refreshCache(cache)) {
                    break;
                }
            }

            handler.sendEmptyMessage(MSG_RESTART_GEO_AND_DIR);
            handler.sendEmptyMessage(MSG_DONE);
        }

        /**
         * Refreshes the cache information.
         *
         * @param cache
         *            The cache to refresh
         * @return
         *         <code>false</code> if the storing was interrupted, <code>true</code> otherwise
         */
        private boolean refreshCache(Geocache cache) {
            try {
                if (needToStop) {
                    throw new InterruptedException("Stopped storing process.");
                }

                if ((System.currentTimeMillis() - last) < 1500) {
                    try {
                        int delay = 1000 + ((Double) (Math.random() * 1000)).intValue()
                                - (int) (System.currentTimeMillis() - last);
                        if (delay < 0) {
                            delay = 500;
                        }

                        Log.i("Waiting for next cache " + delay + " ms");
                    } catch (Exception e) {
                        Log.e("cgeocaches.LoadDetailsThread.sleep", e);
                    }
                }

                if (needToStop) {
                    throw new InterruptedException("Stopped storing process.");
                }

                detailProgress++;
                cache.refresh(listIdLD, null);

                handler.sendEmptyMessage(cacheList.indexOf(cache));

                yield();
            } catch (InterruptedException e) {
                Log.i(e.getMessage());
                return false;
            } catch (Exception e) {
                Log.e("cgeocaches.LoadDetailsThread", e);
            }

            last = System.currentTimeMillis();
            return true;
        }
    }

    private class LoadFromWebThread extends Thread {

        final private Handler handler;
        final private int listIdLFW;
        private volatile boolean needToStop = false;

        public LoadFromWebThread(Handler handlerIn, int listId) {
            handler = handlerIn;
            listIdLFW = StoredList.getConcreteList(listId);
        }

        public void kill() {
            needToStop = true;
        }

        @Override
        public void run() {

            removeGeoAndDir();

            int delay = -1;
            int times = 0;

            int ret = MSG_DONE;
            while (!needToStop && times < 3 * 60 / 5) { // maximum: 3 minutes, every 5 seconds
                //download new code
                String deviceCode = Settings.getWebDeviceCode();
                if (deviceCode == null) {
                    deviceCode = "";
                }
                final Parameters params = new Parameters("code", deviceCode);
                HttpResponse responseFromWeb = Network.getRequest("http://send2.cgeo.org/read.html", params);

                if (responseFromWeb != null && responseFromWeb.getStatusLine().getStatusCode() == 200) {
                    final String response = Network.getResponseData(responseFromWeb);
                    if (response.length() > 2) {
                        delay = 1;
                        handler.sendMessage(handler.obtainMessage(1, response));
                        yield();

                        Geocache.storeCache(null, response, listIdLFW, false, null);

                        handler.sendMessage(handler.obtainMessage(2, response));
                        yield();
                    } else if ("RG".equals(response)) {
                        //Server returned RG (registration) and this device no longer registered.
                        Settings.setWebNameCode(null, null);
                        ret = -3;
                        needToStop = true;
                        break;
                    } else {
                        delay = 0;
                        handler.sendEmptyMessage(0);
                        yield();
                    }
                }
                if (responseFromWeb == null || responseFromWeb.getStatusLine().getStatusCode() != 200) {
                    ret = -2;
                    needToStop = true;
                    break;
                }

                try {
                    yield();
                    if (delay == 0) {
                        sleep(5000); //No caches 5s
                        times++;
                    } else {
                        sleep(500); //Cache was loaded 0.5s
                        times = 0;
                    }
                } catch (InterruptedException e) {
                    Log.e("cgeocaches.LoadFromWebThread.sleep", e);
                }
            }

            handler.sendEmptyMessage(ret);

            startGeoAndDir();
        }
    }

    private class DropDetailsTask extends AsyncTaskWithProgress<Geocache, Void> {

        private final boolean removeListAfterwards;

        public DropDetailsTask(boolean removeListAfterwards) {
            super(cgeocaches.this, null, res.getString(R.string.caches_drop_progress), true);
            this.removeListAfterwards = removeListAfterwards;
        }

        @Override
        protected Void doInBackgroundInternal(Geocache[] caches) {
            removeGeoAndDir();
            cgData.markDropped(Arrays.asList(caches));
            startGeoAndDir();
            return null;
        }

        @Override
        protected void onPostExecuteInternal(Void result) {
            // remove list in UI because of toast
            if (removeListAfterwards) {
                removeList(false);
            }

            adapter.setSelectMode(false);
            refreshCurrentList();
            replaceCacheListFromSearch();
        }

    }

    private class ClearOfflineLogsThread extends Thread {

        final private Handler handler;
        final private List<Geocache> selected;

        public ClearOfflineLogsThread(Handler handlerIn) {
            handler = handlerIn;
            selected = adapter.getCheckedOrAllCaches();
        }

        @Override
        public void run() {
            cgData.clearLogsOffline(selected);
            handler.sendEmptyMessage(MSG_DONE);
        }
    }

    private class MoreCachesListener implements View.OnClickListener {

        @Override
        public void onClick(View arg0) {
            showProgress(true);
            showFooterLoadingCaches();
            listFooter.setOnClickListener(null);

            getSupportLoaderManager().restartLoader(CacheListLoaderType.NEXT_PAGE.ordinal(), null, cgeocaches.this);
        }
    }

    private void hideLoading() {
        final ListView list = getListView();
        if (list.getVisibility() == View.GONE) {
            list.setVisibility(View.VISIBLE);
            final View loading = findViewById(R.id.loading);
            loading.setVisibility(View.GONE);
        }
    }

    /**
     * @param view
     *            unused here but needed since this method is referenced from XML layout
     */
    public void selectList(View view) {
        if (type != CacheListType.OFFLINE) {
            return;
        }
        new StoredList.UserInterface(this).promptForListSelection(R.string.list_title,
                new RunnableWithArgument<Integer>() {

                    @Override
                    public void run(final Integer selectedListId) {
                        switchListById(selectedListId);
                    }
                });
    }

    public void switchListById(int id) {
        if (id < 0) {
            return;
        }

        StoredList list = cgData.getList(id);
        if (list == null) {
            return;
        }

        listId = list.id;
        title = list.title;

        Settings.saveLastList(listId);

        showProgress(true);
        showFooterLoadingCaches();
        cgData.moveToList(adapter.getCheckedCaches(), listId);

        currentLoader = (OfflineGeocacheListLoader) getSupportLoaderManager()
                .initLoader(CacheListType.OFFLINE.ordinal(), new Bundle(), this);
        currentLoader.reset();
        ((OfflineGeocacheListLoader) currentLoader).setListId(listId);
        ((OfflineGeocacheListLoader) currentLoader).setSearchCenter(coords);
        currentLoader.startLoading();

        invalidateOptionsMenuCompatible();
    }

    private void renameList() {
        new StoredList.UserInterface(this).promptForListRename(listId, new Runnable() {

            @Override
            public void run() {
                refreshCurrentList();
            }
        });
    }

    private void removeListInternal() {
        if (cgData.removeList(listId)) {
            showToast(res.getString(R.string.list_dialog_remove_ok));
            switchListById(StoredList.STANDARD_LIST_ID);
        } else {
            showToast(res.getString(R.string.list_dialog_remove_err));
        }
    }

    private void removeList(final boolean askForConfirmation) {
        // if there are no caches on this list, don't bother the user with questions.
        // there is no harm in deleting the list, he could recreate it easily
        if (CollectionUtils.isEmpty(cacheList)) {
            removeListInternal();
            return;
        }

        if (!askForConfirmation) {
            removeListInternal();
            return;
        }

        // ask him, if there are caches on the list
        final AlertDialog.Builder alert = new AlertDialog.Builder(this);

        alert.setTitle(R.string.list_dialog_remove_title);
        alert.setMessage(R.string.list_dialog_remove_description);
        alert.setPositiveButton(R.string.list_dialog_remove, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int whichButton) {
                removeListInternal();
            }
        });
        alert.setNegativeButton(res.getString(R.string.list_dialog_cancel), new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int whichButton) {
                dialog.dismiss();
            }
        });

        alert.show();
    }

    /**
     * @param view
     *            unused here but needed since this method is referenced from XML layout
     */
    public void goMap(View view) {
        if (search == null || CollectionUtils.isEmpty(cacheList)) {
            showToast(res.getString(R.string.warn_no_cache_coord));

            return;
        }

        // apply filter settings (if there's a filter)
        Set<String> geocodes = new HashSet<String>();
        for (Geocache cache : adapter.getFilteredList()) {
            geocodes.add(cache.getGeocode());
        }

        final SearchResult searchToUse = new SearchResult(geocodes);
        final int count = searchToUse.getCount();
        String mapTitle = title;
        if (count > 0) {
            mapTitle = title + " [" + count + "]";
        }
        CGeoMap.startActivitySearch(this, searchToUse, mapTitle);
    }

    private void refreshCurrentList() {
        switchListById(listId);
    }

    public static void startActivityOffline(final Context context) {
        final Intent cachesIntent = new Intent(context, cgeocaches.class);
        cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.OFFLINE);
        context.startActivity(cachesIntent);
    }

    public static void startActivityOwner(final AbstractActivity context, final String userName) {
        if (!isValidUsername(context, userName)) {
            return;
        }
        final Intent cachesIntent = new Intent(context, cgeocaches.class);
        cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.OWNER);
        cachesIntent.putExtra(Intents.EXTRA_USERNAME, userName);
        context.startActivity(cachesIntent);
    }

    private static boolean isValidUsername(AbstractActivity context, String username) {
        if (StringUtils.isBlank(username)) {
            context.showToast(cgeoapplication.getInstance().getString(R.string.warn_no_username));
            return false;
        }
        return true;
    }

    public static void startActivityUserName(final AbstractActivity context, final String userName) {
        if (!isValidUsername(context, userName)) {
            return;
        }
        final Intent cachesIntent = new Intent(context, cgeocaches.class);
        cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.USERNAME);
        cachesIntent.putExtra(Intents.EXTRA_USERNAME, userName);
        context.startActivity(cachesIntent);
    }

    private void prepareFilterBar() {
        if (Settings.getCacheType() != CacheType.ALL || adapter.isFiltered()) {
            final StringBuilder output = new StringBuilder(Settings.getCacheType().getL10n());

            if (adapter.isFiltered()) {
                output.append(", ").append(adapter.getFilterName());
            }

            ((TextView) findViewById(R.id.filter_text)).setText(output.toString());
            findViewById(R.id.filter_bar).setVisibility(View.VISIBLE);
        } else {
            findViewById(R.id.filter_bar).setVisibility(View.GONE);
        }
    }

    /**
     * set date comparator for pure event lists
     *
     * TODO: move this method into the adapter
     */
    private void setDateComparatorForEventList() {
        if (CollectionUtils.isNotEmpty(cacheList)) {
            boolean eventsOnly = true;
            for (Geocache cache : cacheList) {
                if (!cache.isEventCache()) {
                    eventsOnly = false;
                    break;
                }
            }
            if (eventsOnly) {
                adapter.setComparator(new EventDateComparator());
            } else if (type == CacheListType.HISTORY) {
                adapter.setComparator(new VisitComparator());
            } else if (adapter.getCacheComparator() != null
                    && adapter.getCacheComparator() instanceof EventDateComparator) {
                adapter.setComparator(null);
            }
        }
    }

    public static void startActivityNearest(final AbstractActivity context, final Geopoint coordsNow) {
        if (!isValidCoords(context, coordsNow)) {
            return;
        }
        final Intent cachesIntent = new Intent(context, cgeocaches.class);
        cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.NEAREST);
        cachesIntent.putExtra(Intents.EXTRA_COORDS, coordsNow);
        context.startActivity(cachesIntent);
    }

    public static void startActivityHistory(Context context) {
        final Intent cachesIntent = new Intent(context, cgeocaches.class);
        cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.HISTORY);
        context.startActivity(cachesIntent);
    }

    public static void startActivityAddress(final Context context, final Geopoint coords, final String address) {
        final Intent addressIntent = new Intent(context, cgeocaches.class);
        addressIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.ADDRESS);
        addressIntent.putExtra(Intents.EXTRA_COORDS, coords);
        addressIntent.putExtra(Intents.EXTRA_ADDRESS, address);
        context.startActivity(addressIntent);
    }

    public static void startActivityCoordinates(final AbstractActivity context, final Geopoint coords) {
        if (!isValidCoords(context, coords)) {
            return;
        }
        final Intent cachesIntent = new Intent(context, cgeocaches.class);
        cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.COORDINATE);
        cachesIntent.putExtra(Intents.EXTRA_COORDS, coords);
        context.startActivity(cachesIntent);
    }

    private static boolean isValidCoords(AbstractActivity context, Geopoint coords) {
        if (coords == null) {
            context.showToast(cgeoapplication.getInstance().getString(R.string.warn_no_coordinates));
            return false;
        }
        return true;
    }

    public static void startActivityKeyword(final AbstractActivity context, final String keyword) {
        if (keyword == null) {
            context.showToast(cgeoapplication.getInstance().getString(R.string.warn_no_keyword));
            return;
        }
        final Intent cachesIntent = new Intent(context, cgeocaches.class);
        cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.KEYWORD);
        cachesIntent.putExtra(Intents.EXTRA_KEYWORD, keyword);
        context.startActivity(cachesIntent);
    }

    public static void startActivityMap(final Context context, final SearchResult search) {
        final Intent cachesIntent = new Intent(context, cgeocaches.class);
        cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.MAP);
        cachesIntent.putExtra(Intents.EXTRA_SEARCH, search);
        context.startActivity(cachesIntent);
    }

    // Loaders

    @Override
    public Loader<SearchResult> onCreateLoader(int type, Bundle extras) {
        if (type >= CacheListLoaderType.values().length) {
            throw new IllegalArgumentException("invalid loader type " + type);
        }
        CacheListLoaderType enumType = CacheListLoaderType.values()[type];
        AbstractSearchLoader loader = null;
        switch (enumType) {
        case OFFLINE:
            listId = Settings.getLastList();
            if (listId <= StoredList.TEMPORARY_LIST_ID) {
                listId = StoredList.STANDARD_LIST_ID;
                title = res.getString(R.string.stored_caches_button);
            } else {
                final StoredList list = cgData.getList(listId);
                // list.id may be different if listId was not valid
                listId = list.id;
                title = list.title;
            }

            loader = new OfflineGeocacheListLoader(this.getBaseContext(), coords, listId);

            break;
        case HISTORY:
            title = res.getString(R.string.caches_history);
            loader = new HistoryGeocacheListLoader(app, coords);
            break;
        case NEAREST:
            title = res.getString(R.string.caches_nearby);
            loader = new CoordsGeocacheListLoader(app, coords);
            break;
        case COORDINATE:
            title = coords.toString();
            loader = new CoordsGeocacheListLoader(app, coords);
            break;
        case KEYWORD:
            final String keyword = extras.getString(Intents.EXTRA_KEYWORD);
            title = keyword;
            loader = new KeywordGeocacheListLoader(app, keyword);
            break;
        case ADDRESS:
            final String address = extras.getString(Intents.EXTRA_ADDRESS);
            if (StringUtils.isNotBlank(address)) {
                title = address;
            } else {
                title = coords.toString();
            }
            if (coords != null) {
                loader = new CoordsGeocacheListLoader(app, coords);
            } else {
                loader = new AddressGeocacheListLoader(app, address);
            }
            break;
        case USERNAME:
            final String username = extras.getString(Intents.EXTRA_USERNAME);
            title = username;
            loader = new UsernameGeocacheListLoader(app, username);
            break;
        case OWNER:
            final String ownerName = extras.getString(Intents.EXTRA_USERNAME);
            title = ownerName;
            loader = new OwnerGeocacheListLoader(app, ownerName);
            break;
        case MAP:
            //TODO Build Nullloader
            title = res.getString(R.string.map_map);
            search = (SearchResult) extras.get(Intents.EXTRA_SEARCH);
            replaceCacheListFromSearch();
            loadCachesHandler.sendMessage(Message.obtain());
            break;
        case REMOVE_FROM_HISTORY:
            title = res.getString(R.string.caches_history);
            loader = new RemoveFromHistoryLoader(app, extras.getStringArray(Intents.EXTRA_CACHELIST), coords);
            break;
        case NEXT_PAGE:
            loader = new NextPageGeocacheListLoader(app, search);
            break;
        }
        setTitle(title);
        showProgress(true);
        showFooterLoadingCaches();

        if (loader != null) {
            loader.setRecaptchaHandler(new SearchHandler(this, res, loader));
        }
        return loader;
    }

    @Override
    public void onLoadFinished(Loader<SearchResult> arg0, SearchResult searchIn) {
        // The database search was moved into the UI call intentionally. If this is done before the runOnUIThread,
        // then we have 2 sets of caches in memory. This can lead to OOM for huge cache lists.
        if (searchIn != null) {
            cacheList.clear();
            final Set<Geocache> cachesFromSearchResult = searchIn
                    .getCachesFromSearchResult(LoadFlags.LOAD_CACHE_OR_DB);
            cacheList.addAll(cachesFromSearchResult);
            search = searchIn;
            adapter.reFilter();
            adapter.forceSort();
            adapter.notifyDataSetChanged();
            updateTitle();
            showFooterMoreCaches();
        }
        showProgress(false);
        hideLoading();
    }

    @Override
    public void onLoaderReset(Loader<SearchResult> arg0) {
        //Not interessting
    }
}