com.todoroo.astrid.activity.TaskListFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.todoroo.astrid.activity.TaskListFragment.java

Source

/**
 * Copyright (c) 2012 Todoroo Inc
 *
 * See the file "LICENSE" for the full license governing this code.
 */
package com.todoroo.astrid.activity;

import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicReference;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.text.TextUtils;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnKeyListener;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;

import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.app.SherlockListFragment;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuItem;
import com.timsu.astrid.R;
import com.todoroo.andlib.data.Property;
import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.ContextManager;
import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.service.ExceptionService;
import com.todoroo.andlib.sql.Criterion;
import com.todoroo.andlib.sql.Field;
import com.todoroo.andlib.sql.Join;
import com.todoroo.andlib.utility.AndroidUtilities;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.andlib.utility.Preferences;
import com.todoroo.astrid.actfm.ActFmLoginActivity;
import com.todoroo.astrid.actfm.CommentsActivity;
import com.todoroo.astrid.actfm.TagViewFragment;
import com.todoroo.astrid.actfm.sync.ActFmPreferenceService;
import com.todoroo.astrid.activity.SortSelectionActivity.OnSortSelectedListener;
import com.todoroo.astrid.adapter.TaskAdapter;
import com.todoroo.astrid.adapter.TaskAdapter.OnCompletedTaskListener;
import com.todoroo.astrid.adapter.TaskAdapter.ViewHolder;
import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.api.FilterWithCustomIntent;
import com.todoroo.astrid.api.TaskContextActionExposer;
import com.todoroo.astrid.api.TaskDecoration;
import com.todoroo.astrid.core.CoreFilterExposer;
import com.todoroo.astrid.core.CustomFilterActivity;
import com.todoroo.astrid.core.SortHelper;
import com.todoroo.astrid.dao.Database;
import com.todoroo.astrid.dao.TaskListMetadataDao;
import com.todoroo.astrid.data.Metadata;
import com.todoroo.astrid.data.RemoteModel;
import com.todoroo.astrid.data.TagData;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.data.TaskAttachment;
import com.todoroo.astrid.data.TaskListMetadata;
import com.todoroo.astrid.data.User;
import com.todoroo.astrid.helper.SyncActionHelper;
import com.todoroo.astrid.helper.TaskListContextMenuExtensionLoader;
import com.todoroo.astrid.helper.TaskListContextMenuExtensionLoader.ContextMenuItem;
import com.todoroo.astrid.reminders.ReminderDebugContextActions;
import com.todoroo.astrid.service.AddOnService;
import com.todoroo.astrid.service.AstridDependencyInjector;
import com.todoroo.astrid.service.MetadataService;
import com.todoroo.astrid.service.StatisticsConstants;
import com.todoroo.astrid.service.StatisticsService;
import com.todoroo.astrid.service.TagDataService;
import com.todoroo.astrid.service.TaskService;
import com.todoroo.astrid.service.ThemeService;
import com.todoroo.astrid.service.UpgradeService;
import com.todoroo.astrid.subtasks.SubtasksHelper;
import com.todoroo.astrid.subtasks.SubtasksListFragment;
import com.todoroo.astrid.subtasks.SubtasksUpdater;
import com.todoroo.astrid.sync.SyncProviderPreferences;
import com.todoroo.astrid.tags.TaskToTagMetadata;
import com.todoroo.astrid.timers.TimerPlugin;
import com.todoroo.astrid.ui.FeedbackPromptDialogs;
import com.todoroo.astrid.ui.QuickAddBar;
import com.todoroo.astrid.utility.AstridPreferences;
import com.todoroo.astrid.utility.Flags;
import com.todoroo.astrid.welcome.HelpInfoPopover;
import com.todoroo.astrid.welcome.tutorial.WelcomeWalkthrough;
import com.todoroo.astrid.widget.TasksWidget;

/**
 * Primary activity for the Bente application. Shows a list of upcoming tasks
 * and a user's coaches.
 *
 * @author Tim Su <tim@todoroo.com>
 *
 */
public class TaskListFragment extends SherlockListFragment implements OnSortSelectedListener {

    public static final String TAG_TASKLIST_FRAGMENT = "tasklist_fragment"; //$NON-NLS-1$

    public static final String PREF_LAST_FEEDBACK_TIME = "pref_last_feedback_time"; //$NON-NLS-1$
    private static final long FEEDBACK_TIME_INTERVAL = DateUtilities.ONE_WEEK * 12;

    // --- activities

    public static final long AUTOSYNC_INTERVAL = 90000L;
    private static final long BACKGROUND_REFRESH_INTERVAL = 120000L;
    private static final long WAIT_BEFORE_AUTOSYNC = 2000L;
    public static final int ACTIVITY_EDIT_TASK = 0;
    public static final int ACTIVITY_SETTINGS = 1;
    public static final int ACTIVITY_SORT = 2;
    public static final int ACTIVITY_ADDONS = 3;
    public static final int ACTIVITY_MENU_EXTERNAL = 4;
    public static final int ACTIVITY_REQUEST_NEW_FILTER = 5;

    // --- menu codes

    protected static final int MENU_SETTINGS_ID = R.string.TLA_menu_settings;
    public static final int MENU_SORT_ID = R.string.TLA_menu_sort;
    protected static final int MENU_SYNC_ID = R.string.TLA_menu_sync;
    protected static final int MENU_SUPPORT_ID = R.string.TLA_menu_support;
    public static final int MENU_NEW_FILTER_ID = R.string.FLA_new_filter;
    protected static final int MENU_ADDON_INTENT_ID = Menu.FIRST + 199;

    protected static final int CONTEXT_MENU_EDIT_TASK_ID = R.string.TAd_contextEditTask;
    protected static final int CONTEXT_MENU_COPY_TASK_ID = R.string.TAd_contextCopyTask;
    protected static final int CONTEXT_MENU_DELETE_TASK_ID = R.string.TAd_contextDeleteTask;
    protected static final int CONTEXT_MENU_UNDELETE_TASK_ID = R.string.TAd_contextUndeleteTask;
    protected static final int CONTEXT_MENU_PURGE_TASK_ID = R.string.TAd_contextPurgeTask;
    protected static final int CONTEXT_MENU_BROADCAST_INTENT_ID = Menu.FIRST + 25;
    protected static final int CONTEXT_MENU_PLUGIN_ID_FIRST = Menu.FIRST + 26;

    // --- constants

    /** token for passing a {@link Filter} object through extras */
    public static final String TOKEN_FILTER = "filter"; //$NON-NLS-1$

    private static final String TOKEN_EXTRAS = "extras"; //$NON-NLS-1$

    // --- instance variables

    @Autowired
    protected ExceptionService exceptionService;

    @Autowired
    protected TaskService taskService;

    @Autowired
    MetadataService metadataService;

    @Autowired
    Database database;

    @Autowired
    AddOnService addOnService;

    @Autowired
    UpgradeService upgradeService;

    @Autowired
    TagDataService tagDataService;

    @Autowired
    TaskListMetadataDao taskListMetadataDao;

    @Autowired
    ActFmPreferenceService actFmPreferenceService;

    private final TaskContextActionExposer[] contextItemExposers = new TaskContextActionExposer[] {
            new ReminderDebugContextActions.MakeNotification(), new ReminderDebugContextActions.WhenReminder(), };

    protected Resources resources;
    protected TaskAdapter taskAdapter = null;
    protected DetailReceiver detailReceiver = new DetailReceiver();
    protected RefreshReceiver refreshReceiver = new RefreshReceiver();
    protected final AtomicReference<String> sqlQueryTemplate = new AtomicReference<String>();
    protected SyncActionHelper syncActionHelper;
    protected Filter filter;
    protected int sortFlags;
    protected int sortSort;
    protected QuickAddBar quickAddBar;

    private Timer backgroundTimer;
    protected Bundle extras;
    protected boolean isInbox;
    protected boolean isTodayFilter;
    protected TaskListMetadata taskListMetadata;

    private final TaskListContextMenuExtensionLoader contextMenuExtensionLoader = new TaskListContextMenuExtensionLoader();

    // --- fragment handling variables
    protected OnTaskListItemClickedListener mListener;
    private boolean mDualFragments = false;

    /*
     * ======================================================================
     * ======================================================= initialization
     * ======================================================================
     */

    static {
        AstridDependencyInjector.initialize();
    }

    /**
     * Instantiates and returns an instance of TaskListFragment (or some subclass). Custom types of
     * TaskListFragment can be created, with the following precedence:
     *
     * --If the filter is of type {@link FilterWithCustomIntent}, the task list type it specifies will be used
     * --Otherwise, the specified customComponent will be used
     *
     * See also: instantiateWithFilterAndExtras(Filter, Bundle) which uses TaskListFragment as the default
     * custom component.
     * @param filter
     * @param extras
     * @param customComponent
     * @return
     */
    @SuppressWarnings("nls")
    public static TaskListFragment instantiateWithFilterAndExtras(Filter filter, Bundle extras,
            Class<?> customComponent) {
        Class<?> component = customComponent;
        if (filter instanceof FilterWithCustomIntent && component == null) {
            try {
                component = Class.forName(((FilterWithCustomIntent) filter).customTaskList.getClassName());
            } catch (Exception e) {
                // Invalid
            }
        }
        if (component == null)
            component = TaskListFragment.class;

        TaskListFragment newFragment;
        try {
            newFragment = (TaskListFragment) component.newInstance();
        } catch (java.lang.InstantiationException e) {
            Log.e("tla-instantiate", "tla-instantiate", e);
            newFragment = new TaskListFragment();
        } catch (IllegalAccessException e) {
            Log.e("tla-instantiate", "tla-instantiate", e);
            newFragment = new TaskListFragment();
        }
        Bundle args = new Bundle();
        args.putBundle(TOKEN_EXTRAS, extras);
        newFragment.setArguments(args);
        return newFragment;
    }

    /**
     * Convenience method for calling instantiateWithFilterAndExtras(Filter, Bundle, Class<?>) with
     * TaskListFragment as the default component
     * @param filter
     * @param extras
     * @return
     */
    public static TaskListFragment instantiateWithFilterAndExtras(Filter filter, Bundle extras) {
        return instantiateWithFilterAndExtras(filter, extras, null);
    }

    /**
     * Container Activity must implement this interface and we ensure that it
     * does during the onAttach() callback
     */
    public interface OnTaskListItemClickedListener {
        public void onTaskListItemClicked(long taskId);

        public void onTaskListItemClicked(long taskId, boolean editable);
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        // Check that the container activity has implemented the callback
        // interface
        try {
            mListener = (OnTaskListItemClickedListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement OnTaskListItemClickedListener"); //$NON-NLS-1$
        }
    }

    /**
     * @return view to attach to the body of the task list. must contain two
     *         elements, a view with id android:id/empty and a list view with id
     *         android:id/list. It should NOT be attached to root
     */
    protected View getListBody(ViewGroup root) {
        return getActivity().getLayoutInflater().inflate(R.layout.task_list_body_standard, root, false);
    }

    /** Called when loading up the activity */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        DependencyInjectionService.getInstance().inject(this);
        super.onCreate(savedInstanceState);
        extras = getArguments() != null ? getArguments().getBundle(TOKEN_EXTRAS) : null;
        if (extras == null)
            extras = new Bundle(); // Just need an empty one to prevent potential null pointers
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * android.support.v4.app.Fragment#onCreateView(android.view.LayoutInflater,
     * android.view.ViewGroup, android.os.Bundle)
     */
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        ViewGroup parent = (ViewGroup) getActivity().getLayoutInflater().inflate(R.layout.task_list_activity,
                container, false);
        parent.addView(getListBody(parent), 0);

        return parent;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        // We have a menu item to show in action bar.
        resources = getResources();
        setHasOptionsMenu(true);
        syncActionHelper = new SyncActionHelper(getActivity(), this);
        setUpUiComponents();
        initializeData();
        setupQuickAddBar();

        Fragment filterlistFrame = getFragmentManager()
                .findFragmentByTag(FilterListFragment.TAG_FILTERLIST_FRAGMENT);
        mDualFragments = (filterlistFrame != null) && filterlistFrame.isInLayout();

        if (mDualFragments) {
            // In dual-pane mode, the list view highlights the selected item.
            getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
            getListView().setItemsCanFocus(false);
        }

        if (Preferences.getInt(AstridPreferences.P_UPGRADE_FROM, -1) > -1)
            upgradeService.showChangeLog(getActivity(), Preferences.getInt(AstridPreferences.P_UPGRADE_FROM, -1));

        getListView().setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                if (taskAdapter != null) {
                    TodorooCursor<Task> cursor = (TodorooCursor<Task>) taskAdapter.getItem(position);
                    Task task = new Task(cursor);
                    if (task.isDeleted())
                        return;

                    onTaskListItemClicked(id, task.isEditable());
                }
            }
        });
    }

    /**
     * @return the current tag you are viewing, or null if you're not viewing a tag
     */
    public TagData getActiveTagData() {
        return null;
    }

    public TaskListMetadata getTaskListMetadata() {
        return taskListMetadata;
    }

    protected void initializeData() {
        if (extras != null && extras.containsKey(TOKEN_FILTER)) {
            filter = extras.getParcelable(TOKEN_FILTER);
            extras.remove(TOKEN_FILTER); // Otherwise writing this filter to parcel gives infinite recursion
        } else {
            filter = CoreFilterExposer.buildInboxFilter(resources);
        }
        filter.setFilterQueryOverride(null);
        isInbox = CoreFilterExposer.isInbox(filter);
        isTodayFilter = false;
        if (!isInbox)
            isTodayFilter = CoreFilterExposer.isTodayFilter(filter);

        initializeTaskListMetadata();

        setUpTaskList();
        ((AstridActivity) getActivity()).setupActivityFragment(getActiveTagData());

        contextMenuExtensionLoader.loadInNewThread(getActivity());
    }

    protected void initializeTaskListMetadata() {
        TagData td = getActiveTagData();
        String tdId;
        if (td == null) {
            String filterId = null;
            String prefId = null;
            if (isInbox) {
                filterId = TaskListMetadata.FILTER_ID_ALL;
                prefId = SubtasksUpdater.ACTIVE_TASKS_ORDER;
            } else if (isTodayFilter) {
                filterId = TaskListMetadata.FILTER_ID_TODAY;
                prefId = SubtasksUpdater.TODAY_TASKS_ORDER;
            }
            if (!TextUtils.isEmpty(filterId)) {
                taskListMetadata = taskListMetadataDao.fetchByTagId(filterId, TaskListMetadata.PROPERTIES);
                if (taskListMetadata == null) {
                    String defaultOrder = Preferences.getStringValue(prefId);
                    if (TextUtils.isEmpty(defaultOrder))
                        defaultOrder = "[]"; //$NON-NLS-1$
                    defaultOrder = SubtasksHelper.convertTreeToRemoteIds(defaultOrder);
                    taskListMetadata = new TaskListMetadata();
                    taskListMetadata.setValue(TaskListMetadata.FILTER, filterId);
                    taskListMetadata.setValue(TaskListMetadata.TASK_IDS, defaultOrder);
                    taskListMetadataDao.createNew(taskListMetadata);
                }
            }
        } else {
            tdId = td.getUuid();
            taskListMetadata = taskListMetadataDao.fetchByTagId(td.getUuid(), TaskListMetadata.PROPERTIES);
            if (taskListMetadata == null && !RemoteModel.isUuidEmpty(tdId)) {
                taskListMetadata = new TaskListMetadata();
                taskListMetadata.setValue(TaskListMetadata.TAG_UUID, tdId);
                taskListMetadataDao.createNew(taskListMetadata);
            }
        }
        postLoadTaskListMetadata();
    }

    protected void postLoadTaskListMetadata() {
        // Hook
    }

    protected void addSyncRefreshMenuItem(Menu menu, int themeFlags) {
        addMenuItem(menu, R.string.TLA_menu_sync, ThemeService.getDrawable(R.drawable.icn_menu_refresh, themeFlags),
                MENU_SYNC_ID, true);
    }

    protected void addMenuItem(Menu menu, int title, int imageRes, int id, boolean showAsAction) {
        AstridActivity activity = (AstridActivity) getActivity();
        if ((activity.getFragmentLayout() != AstridActivity.LAYOUT_SINGLE && showAsAction)
                || !(activity instanceof TaskListActivity)) {
            MenuItem item = menu.add(Menu.NONE, id, Menu.NONE, title);
            item.setIcon(imageRes);
            if (activity instanceof TaskListActivity)
                item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
        } else {
            ((TaskListActivity) activity).getMainMenuPopover().addMenuItem(title, imageRes, id);
        }
    }

    protected void addMenuItem(Menu menu, CharSequence title, Drawable image, Intent customIntent, int id) {
        Activity activity = getActivity();
        if (activity instanceof TaskListActivity) {
            ((TaskListActivity) activity).getMainMenuPopover().addMenuItem(title, image, customIntent, id);
        } else {
            MenuItem item = menu.add(Menu.NONE, id, Menu.NONE, title);
            item.setIcon(image);
            item.setIntent(customIntent);
        }
    }

    /**
     * Create options menu (displayed when user presses menu key)
     *
     * @return true if menu should be displayed
     */
    @Override
    public void onCreateOptionsMenu(Menu menu, com.actionbarsherlock.view.MenuInflater inflater) {
        Activity activity = getActivity();
        if (activity == null)
            return;
        if (!isCurrentTaskListFragment())
            return;

        addMenuItems(menu, activity);
    }

    protected void addMenuItems(Menu menu, Activity activity) {
        boolean isTablet = AstridPreferences.useTabletLayout(activity);
        TaskListActivity tla = null;
        if (activity instanceof TaskListActivity) {
            tla = (TaskListActivity) activity;
            tla.getMainMenuPopover().clear();
        }
        // --- sync
        if (tla == null
                || tla.getTaskEditFragment() == null && Preferences.getBoolean(R.string.p_show_menu_sync, true))
            addSyncRefreshMenuItem(menu, isTablet ? ThemeService.FLAG_INVERT : 0);

        // --- sort
        if (allowResorting() && Preferences.getBoolean(R.string.p_show_menu_sort, true)) {
            addMenuItem(menu, R.string.TLA_menu_sort, ThemeService.getDrawable(R.drawable.icn_menu_sort_by_size,
                    isTablet ? ThemeService.FLAG_FORCE_DARK : 0), MENU_SORT_ID, false);
        }

        // --- new filter
        if (Preferences.getBoolean(R.string.p_use_filters, true))
            addMenuItem(menu, R.string.FLA_new_filter, ThemeService.getDrawable(R.drawable.icn_menu_filters,
                    isTablet ? ThemeService.FLAG_FORCE_DARK : 0), MENU_NEW_FILTER_ID, false);

        // ask about plug-ins
        Intent queryIntent = new Intent(AstridApiConstants.ACTION_TASK_LIST_MENU);

        PackageManager pm = getActivity().getPackageManager();
        List<ResolveInfo> resolveInfoList = pm.queryIntentActivities(queryIntent, 0);
        int length = resolveInfoList.size();
        for (int i = 0; i < length; i++) {
            ResolveInfo resolveInfo = resolveInfoList.get(i);
            Intent intent = new Intent(AstridApiConstants.ACTION_TASK_LIST_MENU);
            intent.setClassName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name);
            addMenuItem(menu, resolveInfo.loadLabel(pm), resolveInfo.loadIcon(pm), intent, MENU_ADDON_INTENT_ID);
        }
    }

    protected boolean allowResorting() {
        return true;
    }

    protected void setUpUiComponents() {
        // set listener for quick-changing task priority
        getListView().setOnKeyListener(new OnKeyListener() {
            @Override
            public boolean onKey(View view, int keyCode, KeyEvent event) {
                if (event.getAction() != KeyEvent.ACTION_UP || view == null)
                    return false;

                boolean filterOn = getListView().isTextFilterEnabled();
                View selected = getListView().getSelectedView();

                // hot-key to set task priority - 1-4 or ALT + Q-R
                if (!filterOn && event.getUnicodeChar() >= '1' && event.getUnicodeChar() <= '4'
                        && selected != null) {
                    int importance = event.getNumber() - '1';
                    Task task = ((ViewHolder) selected.getTag()).task;
                    task.setValue(Task.IMPORTANCE, importance);
                    taskService.save(task);
                    taskAdapter.setFieldContentsAndVisibility(selected);
                }
                // filter
                else if (!filterOn && event.getUnicodeChar() != 0) {
                    getListView().setTextFilterEnabled(true);
                    getListView().setFilterText(Character.toString((char) event.getUnicodeChar()));
                }
                // turn off filter if nothing is selected
                else if (filterOn && TextUtils.isEmpty(getListView().getTextFilter())) {
                    getListView().setTextFilterEnabled(false);
                }

                return false;
            }
        });

        SharedPreferences publicPrefs = AstridPreferences.getPublicPrefs(getActivity());
        sortFlags = publicPrefs.getInt(SortHelper.PREF_SORT_FLAGS, 0);
        sortSort = publicPrefs.getInt(SortHelper.PREF_SORT_SORT, 0);
        sortFlags = SortHelper.setManualSort(sortFlags, isDraggable());

        getView().findViewById(R.id.progressBar).setVisibility(View.GONE);
    }

    protected void setupQuickAddBar() {
        quickAddBar = (QuickAddBar) getView().findViewById(R.id.taskListFooter);
        quickAddBar.initialize((AstridActivity) getActivity(), this, mListener);

        getListView().setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                quickAddBar.clearFocus();
                return false;
            }
        });

        // set listener for astrid icon
        ((TextView) getView().findViewById(android.R.id.empty)).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                quickAddBar.performButtonClick();
            }
        });
    }

    public void transitionForTaskEdit() {
        AndroidUtilities.callOverridePendingTransition(getActivity(), R.anim.slide_left_in, R.anim.slide_left_out);
    }

    private void setUpBackgroundJobs() {
        backgroundTimer = new Timer();

        // start a thread to refresh periodically
        backgroundTimer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                // refresh if conditions match
                Flags.checkAndClear(Flags.REFRESH);
                getActivity().runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            refresh();
                        } catch (IllegalStateException e) {
                            // view may have been destroyed
                        }
                    }
                });
            }
        }, BACKGROUND_REFRESH_INTERVAL, BACKGROUND_REFRESH_INTERVAL);
    }

    /*
     * ======================================================================
     * ============================================================ lifecycle
     * ======================================================================
     */

    @Override
    public void onStart() {
        super.onStart();
        quickAddBar.setupRecognizerApi();
    }

    @Override
    public void onStop() {
        super.onStop();
        quickAddBar.destroyRecognizerApi();
    }

    /**
     * Crazy hack so that tag view fragment won't automatically initiate an autosync after a tag
     * is deleted. TagViewFragment has to call onResume, but we don't want it to call
     * the normal tasklist onResume.
     */
    protected void parentOnResume() {
        super.onResume();
    }

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

        getActivity().registerReceiver(detailReceiver, new IntentFilter(AstridApiConstants.BROADCAST_SEND_DETAILS));
        getActivity().registerReceiver(detailReceiver,
                new IntentFilter(AstridApiConstants.BROADCAST_SEND_DECORATIONS));
        getActivity().registerReceiver(refreshReceiver,
                new IntentFilter(AstridApiConstants.BROADCAST_EVENT_REFRESH));
        syncActionHelper.register();

        if (Flags.checkAndClear(Flags.REFRESH)) {
            refresh();
        }

        setUpBackgroundJobs();

        if (!Preferences.getBoolean(WelcomeWalkthrough.KEY_SHOWED_WELCOME_LOGIN, false)) {
            Preferences.setBoolean(WelcomeWalkthrough.KEY_SHOWED_WELCOME_LOGIN, true);
            Intent showWelcomeLogin = new Intent(getActivity(), WelcomeWalkthrough.class);
            showWelcomeLogin.putExtra(ActFmLoginActivity.SHOW_TOAST, false);
            startActivity(showWelcomeLogin);
            return;
        }

        if (!Preferences.getBoolean(R.string.p_showed_add_task_help, false)) {
            showTaskCreateHelpPopover();
        } else if (!Preferences.getBoolean(R.string.p_showed_tap_task_help, false)) {
            showTaskEditHelpPopover();
        } else if (!Preferences.getBoolean(R.string.p_showed_lists_help, false)) {
            showListsHelp();
        }
        refreshFilterCount();

        initiateAutomaticSync();

        showFeedbackPrompt();
    }

    private void showFeedbackPrompt() {
        if (!(this instanceof TagViewFragment)
                && (DateUtilities.now() - Preferences.getLong(PREF_LAST_FEEDBACK_TIME, 0)) > FEEDBACK_TIME_INTERVAL
                && taskService.getUserActivationStatus()) {
            final LinearLayout root = (LinearLayout) getView().findViewById(R.id.taskListParent);
            if (root.findViewById(R.id.feedback_banner) == null) {
                final View feedbackPrompt = getActivity().getLayoutInflater().inflate(R.layout.feedback_prompt,
                        root, false);

                feedbackPrompt.findViewById(R.id.positiveFeedback).setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        try {
                            root.removeView(feedbackPrompt);
                            FeedbackPromptDialogs.showFeedbackDialog((AstridActivity) getActivity(), true);
                            Preferences.setLong(PREF_LAST_FEEDBACK_TIME, DateUtilities.now());
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                });

                feedbackPrompt.findViewById(R.id.negativeFeedback).setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        try {
                            root.removeView(feedbackPrompt);
                            FeedbackPromptDialogs.showFeedbackDialog((AstridActivity) getActivity(), false);
                            Preferences.setLong(PREF_LAST_FEEDBACK_TIME, DateUtilities.now());
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                });

                root.addView(feedbackPrompt, 0);
            }
        }
    }

    protected boolean isCurrentTaskListFragment() {
        AstridActivity activity = (AstridActivity) getActivity();
        if (activity != null) {
            return activity.getTaskListFragment() == this;
        }
        return false;
    }

    public final void initiateAutomaticSync() {
        final AstridActivity activity = (AstridActivity) getActivity();
        if (activity == null)
            return;
        if (activity.fragmentLayout != AstridActivity.LAYOUT_SINGLE) {
            initiateAutomaticSyncImpl();
        } else {
            // In single fragment case, we're using swipe between lists,
            // so wait a couple seconds before initiating the autosync.
            new Thread() {
                @Override
                public void run() {
                    AndroidUtilities.sleepDeep(WAIT_BEFORE_AUTOSYNC);
                    activity.runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            initiateAutomaticSyncImpl();
                        }
                    });
                }
            }.start();
        }
    }

    /**
     * Implementation of initiation automatic sync. Subclasses should override this method;
     * the above method takes care of calling it in the correct way
     */
    protected void initiateAutomaticSyncImpl() {
        if (isCurrentTaskListFragment() && isInbox)
            syncActionHelper.initiateAutomaticSync();
    }

    // Subclasses should override this
    public void requestCommentCountUpdate() {
        TaskListActivity activity = (TaskListActivity) getActivity();
        if (activity != null)
            activity.setCommentsCount(0);
    }

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

        AndroidUtilities.tryUnregisterReceiver(getActivity(), detailReceiver);
        AndroidUtilities.tryUnregisterReceiver(getActivity(), refreshReceiver);
        syncActionHelper.unregister();

        backgroundTimer.cancel();
    }

    /**
     * Receiver which receives refresh intents
     *
     * @author Tim Su <tim@todoroo.com>
     *
     */
    protected class RefreshReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent == null || !AstridApiConstants.BROADCAST_EVENT_REFRESH.equals(intent.getAction()))
                return;

            final Activity activity = getActivity();
            if (activity != null) {
                activity.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        refresh();
                        if (activity instanceof TaskListActivity)
                            ((TaskListActivity) activity).refreshMainMenu();
                    }
                });
            }
        }
    }

    /**
     * Called by the RefreshReceiver when the task list receives a refresh
     * broadcast. Subclasses should override this.
     */
    protected void refresh() {
        if (taskAdapter != null)
            taskAdapter.flushCaches();
        taskService.cleanup();
        loadTaskListContent(true);
    }

    /**
     * Receiver which receives detail or decoration intents
     *
     * @author Tim Su <tim@todoroo.com>
     *
     */
    protected class DetailReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            try {
                Bundle receivedExtras = intent.getExtras();
                long taskId = receivedExtras.getLong(AstridApiConstants.EXTRAS_TASK_ID);
                String addOn = receivedExtras.getString(AstridApiConstants.EXTRAS_ADDON);

                if (AstridApiConstants.BROADCAST_SEND_DECORATIONS.equals(intent.getAction())) {
                    TaskDecoration deco = receivedExtras.getParcelable(AstridApiConstants.EXTRAS_RESPONSE);
                    taskAdapter.decorationManager.addNew(taskId, addOn, deco, null);
                } else if (AstridApiConstants.BROADCAST_SEND_DETAILS.equals(intent.getAction())) {
                    String detail = receivedExtras.getString(AstridApiConstants.EXTRAS_RESPONSE);
                    taskAdapter.addDetails(taskId, detail);
                }
            } catch (Exception e) {
                exceptionService.reportError("receive-detail-" + //$NON-NLS-1$
                        intent.getStringExtra(AstridApiConstants.EXTRAS_ADDON), e);
            }
        }
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (quickAddBar.onActivityResult(requestCode, resultCode, data))
            return;

        if (requestCode == ACTIVITY_SETTINGS) {
            if (resultCode == EditPreferences.RESULT_CODE_THEME_CHANGED
                    || resultCode == EditPreferences.RESULT_CODE_PERFORMANCE_PREF_CHANGED) {
                getActivity().finish();
                getActivity().startActivity(getActivity().getIntent());
                TasksWidget.updateWidgets(getActivity());
                return;
            } else if (resultCode == SyncProviderPreferences.RESULT_CODE_SYNCHRONIZE) {
                Preferences.setLong(SyncActionHelper.PREF_LAST_AUTO_SYNC, 0); // Forces autosync to occur after login
            }
        }

        super.onActivityResult(requestCode, resultCode, data);

        if (!Preferences.getBoolean(R.string.p_showed_add_task_help, false)) {
            if (!AstridPreferences.canShowPopover())
                return;
            quickAddBar.getQuickAddBox().postDelayed(new Runnable() {
                @Override
                public void run() {
                    Activity activity = getActivity();
                    if (activity != null) {
                        HelpInfoPopover.showPopover(getActivity(), quickAddBar.getQuickAddBox(),
                                R.string.help_popover_add_task, null);
                        Preferences.setBoolean(R.string.p_showed_add_task_help, true);
                    }
                }
            }, 1000);
        }
    }

    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        // do nothing
    }

    /*
     * ======================================================================
     * =================================================== managing list view
     * ======================================================================
     */

    /**
     * Load or re-load action items and update views
     *
     * @param requery
     */
    public void loadTaskListContent(boolean requery) {
        if (taskAdapter == null) {
            setUpTaskList();
            return;
        }

        Cursor taskCursor = taskAdapter.getCursor();

        if (requery) {
            taskCursor.requery();
            taskAdapter.flushCaches();
            taskAdapter.notifyDataSetChanged();
        }

        if (getView() != null) { // This was happening sometimes
            int oldListItemSelected = getListView().getSelectedItemPosition();
            if (oldListItemSelected != ListView.INVALID_POSITION && oldListItemSelected < taskCursor.getCount())
                getListView().setSelection(oldListItemSelected);
        }

        // also load sync actions
        syncActionHelper.request();
    }

    public static int getTaskRowResource() {
        int rowStyle = Preferences.getIntegerFromString(R.string.p_taskRowStyle_v2, 0);
        switch (rowStyle) {
        case 1:
            return R.layout.task_adapter_row_simple;
        case 2:
            return R.layout.task_adapter_row_title_only;
        case 0:
        default:
            return R.layout.task_adapter_row;
        }
    }

    protected TaskAdapter createTaskAdapter(TodorooCursor<Task> cursor) {

        return new TaskAdapter(this, getTaskRowResource(), cursor, sqlQueryTemplate, false,
                new OnCompletedTaskListener() {
                    @Override
                    public void onCompletedTask(Task item, boolean newState) {
                        if (newState == true)
                            onTaskCompleted(item);
                    }
                });
    }

    public static final String TAGS_METADATA_JOIN = "for_tags"; //$NON-NLS-1$

    public static final String USER_IMAGE_JOIN = "for_images"; // //$NON-NLS-1$

    public static final String FILE_METADATA_JOIN = "for_actions"; //$NON-NLS-1$

    /**
     * Fill in the Task List with current items
     *
     * @param withCustomId
     *            force task with given custom id to be part of list
     */
    public void setUpTaskList() {
        if (filter == null)
            return;

        TodorooCursor<Task> currentCursor = constructCursor();
        if (currentCursor == null)
            return;

        // set up list adapters
        taskAdapter = createTaskAdapter(currentCursor);

        setListAdapter(taskAdapter);
        registerForContextMenu(getListView());

        loadTaskListContent(true);
    }

    public Property<?>[] taskProperties() {
        if (Preferences.getIntegerFromString(R.string.p_taskRowStyle_v2, 0) == 2)
            return TaskAdapter.BASIC_PROPERTIES;
        return TaskAdapter.PROPERTIES;
    }

    public Filter getFilter() {
        return filter;
    }

    @SuppressWarnings("nls")
    private TodorooCursor<Task> constructCursor() {
        String tagName = null;
        if (getActiveTagData() != null)
            tagName = getActiveTagData().getValue(TagData.NAME);

        Criterion tagsJoinCriterion = Criterion.and(
                Field.field(TAGS_METADATA_JOIN + "." + Metadata.KEY.name).eq(TaskToTagMetadata.KEY), //$NON-NLS-1$
                Field.field(TAGS_METADATA_JOIN + "." + Metadata.DELETION_DATE.name).eq(0),
                Task.ID.eq(Field.field(TAGS_METADATA_JOIN + "." + Metadata.TASK.name)));
        if (tagName != null)
            tagsJoinCriterion = Criterion.and(tagsJoinCriterion,
                    Field.field(TAGS_METADATA_JOIN + "." + TaskToTagMetadata.TAG_NAME.name).neq(tagName));

        // TODO: For now, we'll modify the query to join and include the things like tag data here.
        // Eventually, we might consider restructuring things so that this query is constructed elsewhere.
        String joinedQuery = Join.left(Metadata.TABLE.as(TAGS_METADATA_JOIN), tagsJoinCriterion).toString() //$NON-NLS-1$
                + Join.left(User.TABLE.as(USER_IMAGE_JOIN),
                        Task.USER_ID.eq(Field.field(USER_IMAGE_JOIN + "." + User.UUID.name))).toString()
                + Join.left(TaskAttachment.TABLE.as(FILE_METADATA_JOIN),
                        Task.UUID.eq(Field.field(FILE_METADATA_JOIN + "." + TaskAttachment.TASK_UUID.name)))
                + filter.getSqlQuery();

        sqlQueryTemplate.set(SortHelper.adjustQueryForFlagsAndSort(joinedQuery, sortFlags, sortSort));

        String groupedQuery;
        if (sqlQueryTemplate.get().contains("GROUP BY"))
            groupedQuery = sqlQueryTemplate.get();
        else if (sqlQueryTemplate.get().contains("ORDER BY")) //$NON-NLS-1$
            groupedQuery = sqlQueryTemplate.get().replace("ORDER BY", "GROUP BY " + Task.ID + " ORDER BY"); //$NON-NLS-1$
        else
            groupedQuery = sqlQueryTemplate.get() + " GROUP BY " + Task.ID;
        sqlQueryTemplate.set(groupedQuery);

        // Peform query
        try {
            return taskService.fetchFiltered(sqlQueryTemplate.get(), null, taskProperties());
        } catch (SQLiteException e) {
            // We don't show this error anymore--seems like this can get triggered
            // by a strange bug, but there seems to not be any negative side effect.
            // For now, we'll suppress the error
            // See http://astrid.com/home#tags-7tsoi/task-1119pk
            return null;
        }
    }

    public void reconstructCursor() {
        TodorooCursor<Task> cursor = constructCursor();
        if (cursor == null || taskAdapter == null)
            return;
        taskAdapter.changeCursor(cursor);
    }

    /**
     * Select a custom task id in the list. If it doesn't exist, create a new
     * custom filter
     *
     * @param withCustomId
     */
    public void selectCustomId(long withCustomId) {
        // if already in the list, select it
        TodorooCursor<Task> currentCursor = (TodorooCursor<Task>) taskAdapter.getCursor();
        for (int i = 0; i < currentCursor.getCount(); i++) {
            currentCursor.moveToPosition(i);
            if (currentCursor.get(Task.ID) == withCustomId) {
                getListView().setSelection(i);
                return;
            }
        }
    }

    private void showTaskCreateHelpPopover() {
        if (!AstridPreferences.canShowPopover())
            return;
        if (!Preferences.getBoolean(R.string.p_showed_add_task_help, false)) {
            Preferences.setBoolean(R.string.p_showed_add_task_help, true);
            HelpInfoPopover.showPopover(getActivity(), quickAddBar.getQuickAddBox(), R.string.help_popover_add_task,
                    null);
        }
    }

    public void showTaskEditHelpPopover() {
        if (!AstridPreferences.canShowPopover())
            return;
        if (!Preferences.getBoolean(R.string.p_showed_tap_task_help, false)) {
            quickAddBar.hideKeyboard();
            getListView().postDelayed(new Runnable() {
                public void run() {
                    try {
                        if (taskAdapter != null && taskAdapter.getCount() > 0) {
                            final View view = getListView().getChildAt(getListView().getChildCount() - 1);
                            if (view != null) {
                                Activity activity = getActivity();
                                if (activity != null) {
                                    HelpInfoPopover.showPopover(getActivity(), view, R.string.help_popover_tap_task,
                                            null);
                                    Preferences.setBoolean(R.string.p_showed_tap_task_help, true);
                                }
                            }
                        }
                    } catch (IllegalStateException e) {
                        // Whoops, view is gone. Try again later
                    }
                }
            }, 1000L);

        }
    }

    private void showListsHelp() {
        if (!AstridPreferences.canShowPopover())
            return;
        if (!Preferences.getBoolean(R.string.p_showed_lists_help, false)) {
            AstridActivity activity = (AstridActivity) getActivity();
            if (activity != null) {
                if (AstridPreferences.useTabletLayout(activity)) {
                    FilterListFragment flf = activity.getFilterListFragment();
                    if (flf != null)
                        flf.showAddListPopover();
                } else {
                    ActionBar ab = activity.getSupportActionBar();
                    View anchor = ab.getCustomView().findViewById(R.id.lists_nav);
                    HelpInfoPopover.showPopover(activity, anchor, R.string.help_popover_switch_lists, null);
                }
                Preferences.setBoolean(R.string.p_showed_lists_help, true);
            }
        }
    }

    /*
     * ======================================================================
     * ============================================================== actions
     * ======================================================================
     */

    /**
     * A task was completed from the task adapter
     *
     * @param item
     *            task that was completed
     */
    protected void onTaskCompleted(Task item) {
        if (isInbox)
            StatisticsService.reportEvent(StatisticsConstants.TASK_COMPLETED_INBOX);
        else
            StatisticsService.reportEvent(StatisticsConstants.TASK_COMPLETED_FILTER);
    }

    /**
     * Comments button in action bar was clicked
     */
    protected void handleCommentsButtonClicked() {
        Activity activity = getActivity();
        if (activity != null) {
            Intent intent = new Intent(activity, CommentsActivity.class);
            long id = 0;
            TagData td = getActiveTagData();
            if (td != null)
                id = td.getId();
            intent.putExtra(TagViewFragment.EXTRA_TAG_DATA, id);
            startActivity(intent);
            AndroidUtilities.callOverridePendingTransition(activity, R.anim.slide_left_in, R.anim.slide_left_out);
        }
    }

    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
        AdapterContextMenuInfo adapterInfo = (AdapterContextMenuInfo) menuInfo;
        Task task = ((ViewHolder) adapterInfo.targetView.getTag()).task;
        if (task.getValue(Task.IS_READONLY) > 0)
            return;

        int id = (int) task.getId();
        menu.setHeaderTitle(task.getValue(Task.TITLE));

        if (task.isDeleted()) {
            menu.add(id, CONTEXT_MENU_UNDELETE_TASK_ID, Menu.NONE, R.string.TAd_contextUndeleteTask);

            menu.add(id, CONTEXT_MENU_PURGE_TASK_ID, Menu.NONE, R.string.TAd_contextPurgeTask);
        } else {
            menu.add(id, CONTEXT_MENU_EDIT_TASK_ID, Menu.NONE, R.string.TAd_contextEditTask);
            menu.add(id, CONTEXT_MENU_COPY_TASK_ID, Menu.NONE, R.string.TAd_contextCopyTask);

            for (int i = 0; i < contextItemExposers.length; i++) {
                Object label = contextItemExposers[i].getLabel(task);
                if (label != null) {
                    if (label instanceof Integer)
                        menu.add(id, CONTEXT_MENU_PLUGIN_ID_FIRST + i, Menu.NONE, (Integer) label);
                    else
                        menu.add(id, CONTEXT_MENU_PLUGIN_ID_FIRST + i, Menu.NONE, (String) label);
                }
            }

            long taskId = task.getId();
            for (ContextMenuItem item : contextMenuExtensionLoader.getList()) {
                android.view.MenuItem menuItem = menu.add(id, CONTEXT_MENU_BROADCAST_INTENT_ID, Menu.NONE,
                        item.title);
                item.intent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId);
                menuItem.setIntent(item.intent);
            }

            menu.add(id, CONTEXT_MENU_DELETE_TASK_ID, Menu.NONE, R.string.TAd_contextDeleteTask);

        }
    }

    public boolean isInbox() {
        return isInbox;
    }

    /** Show a dialog box and delete the task specified */
    private void deleteTask(final Task task) {
        new AlertDialog.Builder(getActivity()).setTitle(R.string.DLG_confirm_title)
                .setMessage(R.string.DLG_delete_this_task_question).setIcon(android.R.drawable.ic_dialog_alert)
                .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        onTaskDelete(task);
                        taskService.delete(task);
                        loadTaskListContent(true);
                    }
                }).setNegativeButton(android.R.string.cancel, null).show();
    }

    public void onTaskCreated(Task task) {
        incrementFilterCount();
    }

    protected void onTaskDelete(Task task) {
        decrementFilterCount();

        Activity a = getActivity();
        if (a instanceof AstridActivity) {
            AstridActivity activity = (AstridActivity) a;
            TaskEditFragment tef = activity.getTaskEditFragment();
            if (tef != null) {
                if (task.getId() == tef.model.getId())
                    tef.discardButtonClick();
            }
        }
        TimerPlugin.updateTimer(ContextManager.getContext(), task, false);
    }

    public void incrementFilterCount() {
        if (getActivity() instanceof TaskListActivity) {
            ((TaskListActivity) getActivity()).incrementFilterCount(filter);
        }
    }

    public void decrementFilterCount() {
        if (getActivity() instanceof TaskListActivity) {
            ((TaskListActivity) getActivity()).decrementFilterCount(filter);
        }
    }

    public void refreshFilterCount() {
        if (getActivity() instanceof TaskListActivity) {
            ((TaskListActivity) getActivity()).refreshFilterCount(filter);
        }
    }

    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        super.onListItemClick(l, v, position, id);
        if (mDualFragments)
            setSelection(position);
    }

    @Override
    public boolean onContextItemSelected(MenuItem item) {
        return onOptionsItemSelected(item);
    }

    public boolean handleOptionsMenuItemSelected(int id, Intent intent) {
        Activity activity = getActivity();
        switch (id) {
        case MENU_SORT_ID:
            StatisticsService.reportEvent(StatisticsConstants.TLA_MENU_SORT);
            if (activity != null) {
                AlertDialog dialog = SortSelectionActivity.createDialog(getActivity(), hasDraggableOption(), this,
                        sortFlags, sortSort);
                dialog.show();
            }
            return true;
        case MENU_SYNC_ID:
            StatisticsService.reportEvent(StatisticsConstants.TLA_MENU_SYNC);
            syncActionHelper.performSyncAction();
            return true;
        case MENU_ADDON_INTENT_ID:
            if (activity != null)
                AndroidUtilities.startExternalIntent(activity, intent, ACTIVITY_MENU_EXTERNAL);
            return true;
        case MENU_NEW_FILTER_ID:
            if (activity != null) {
                intent = new Intent(activity, CustomFilterActivity.class);
                activity.startActivityForResult(intent, ACTIVITY_REQUEST_NEW_FILTER);
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean onOptionsItemSelected(final MenuItem item) {
        Intent intent;
        long itemId;

        if (!isCurrentTaskListFragment())
            return false;

        // handle my own menus
        if (handleOptionsMenuItemSelected(item.getItemId(), item.getIntent()))
            return true;

        switch (item.getItemId()) {
        // --- context menu items

        case CONTEXT_MENU_BROADCAST_INTENT_ID: {
            intent = item.getIntent();
            getActivity().sendBroadcast(intent);
            return true;
        }
        case CONTEXT_MENU_EDIT_TASK_ID: {
            itemId = item.getGroupId();
            mListener.onTaskListItemClicked(itemId);
            return true;
        }
        case CONTEXT_MENU_COPY_TASK_ID: {
            itemId = item.getGroupId();
            duplicateTask(itemId);
            return true;
        }
        case CONTEXT_MENU_DELETE_TASK_ID: {
            itemId = item.getGroupId();
            Task task = taskService.fetchById(itemId, Task.ID, Task.UUID);
            if (task != null)
                deleteTask(task);
            return true;
        }
        case CONTEXT_MENU_UNDELETE_TASK_ID: {
            itemId = item.getGroupId();
            Task task = new Task();
            task.setId(itemId);
            task.setValue(Task.DELETION_DATE, 0L);
            taskService.save(task);
            loadTaskListContent(true);
            return true;
        }
        case CONTEXT_MENU_PURGE_TASK_ID: {
            itemId = item.getGroupId();
            Task task = new Task();
            task.setId(itemId);
            TimerPlugin.updateTimer(getActivity(), task, false);
            taskService.purge(itemId);
            loadTaskListContent(true);
            return true;
        }
        default: {
            if (item.getItemId() < CONTEXT_MENU_PLUGIN_ID_FIRST)
                return false;
            if (item.getItemId() - CONTEXT_MENU_PLUGIN_ID_FIRST >= contextItemExposers.length)
                return false;

            AdapterContextMenuInfo adapterInfo = (AdapterContextMenuInfo) item.getMenuInfo();
            Task task = ((ViewHolder) adapterInfo.targetView.getTag()).task;
            contextItemExposers[item.getItemId() - CONTEXT_MENU_PLUGIN_ID_FIRST].invoke(task);

            return true;
        }
        }
    }

    protected void duplicateTask(long itemId) {
        long cloneId = taskService.duplicateTask(itemId);

        Intent intent = new Intent(getActivity(), TaskEditActivity.class);
        intent.putExtra(TaskEditFragment.TOKEN_ID, cloneId);
        intent.putExtra(TOKEN_FILTER, filter);
        getActivity().startActivityForResult(intent, ACTIVITY_EDIT_TASK);
        transitionForTaskEdit();
    }

    public void showSettings() {
        Activity activity = getActivity();
        if (activity == null)
            return;

        StatisticsService.reportEvent(StatisticsConstants.TLA_MENU_SETTINGS);
        Intent intent = new Intent(activity, EditPreferences.class);
        startActivityForResult(intent, ACTIVITY_SETTINGS);
    }

    public void onTaskListItemClicked(long taskId, boolean editable) {
        mListener.onTaskListItemClicked(taskId, editable);
    }

    protected void toggleDragDrop(boolean newState) {
        extras.putParcelable(TOKEN_FILTER, filter);
        if (newState)
            ((AstridActivity) getActivity()).setupTasklistFragmentWithFilterAndCustomTaskList(filter, extras,
                    SubtasksListFragment.class);
        else {
            filter.setFilterQueryOverride(null);
            ((AstridActivity) getActivity()).setupTasklistFragmentWithFilterAndCustomTaskList(filter, extras,
                    TaskListFragment.class);
        }
    }

    protected boolean isDraggable() {
        return false;
    }

    protected boolean hasDraggableOption() {
        return isInbox || isTodayFilter;
    }

    @Override
    public void onSortSelected(boolean always, int flags, int sort) {
        boolean manualSettingChanged = SortHelper.isManualSort(sortFlags) != SortHelper.isManualSort(flags);

        sortFlags = flags;
        sortSort = sort;

        if (always) {
            SharedPreferences publicPrefs = AstridPreferences.getPublicPrefs(ContextManager.getContext());
            if (publicPrefs != null) {
                Editor editor = publicPrefs.edit();
                if (editor != null) {
                    editor.putInt(SortHelper.PREF_SORT_FLAGS, flags);
                    editor.putInt(SortHelper.PREF_SORT_SORT, sort);
                    editor.commit();
                    TasksWidget.updateWidgets(ContextManager.getContext());
                }
            }
        }

        try {
            if (manualSettingChanged)
                toggleDragDrop(SortHelper.isManualSort(sortFlags));
            else
                setUpTaskList();
        } catch (IllegalStateException e) {
            // TODO: Fragment got detached somehow (rare)
        }
    }
}