org.totschnig.myexpenses.fragment.TemplatesList.java Source code

Java tutorial

Introduction

Here is the source code for org.totschnig.myexpenses.fragment.TemplatesList.java

Source

/*   This file is part of My Expenses.
 *   My Expenses is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   My Expenses is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with My Expenses.  If not, see <http://www.gnu.org/licenses/>.
*/

package org.totschnig.myexpenses.fragment;

import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.StyleSpan;
import android.text.style.UnderlineSpan;
import android.util.SparseBooleanArray;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
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.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import org.totschnig.myexpenses.R;
import org.totschnig.myexpenses.activity.ExpenseEdit;
import org.totschnig.myexpenses.activity.ManageTemplates;
import org.totschnig.myexpenses.activity.ProtectedFragmentActivity;
import org.totschnig.myexpenses.dialog.ConfirmationDialogFragment;
import org.totschnig.myexpenses.dialog.MessageDialogFragment;
import org.totschnig.myexpenses.model.Account;
import org.totschnig.myexpenses.model.Category;
import org.totschnig.myexpenses.preference.PrefKey;
import org.totschnig.myexpenses.provider.DatabaseConstants;
import org.totschnig.myexpenses.provider.DbUtils;
import org.totschnig.myexpenses.provider.TransactionProvider;
import org.totschnig.myexpenses.task.TaskExecutionFragment;
import org.totschnig.myexpenses.ui.SimpleCursorAdapter;
import org.totschnig.myexpenses.util.Utils;

import java.lang.ref.WeakReference;
import java.util.ArrayList;

import icepick.Icepick;
import icepick.State;

import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_AMOUNT;
import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_CATID;
import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_COLOR;
import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_COMMENT;
import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_CURRENCY;
import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_INSTANCEID;
import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_LABEL_MAIN;
import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_LABEL_SUB;
import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_PAYEE_NAME;
import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_PLANID;
import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_PLAN_INFO;
import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_ROWID;
import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_TEMPLATEID;
import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_TITLE;
import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_TRANSFER_ACCOUNT;
import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_TRANSFER_PEER;
import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_UUID;

public class TemplatesList extends SortableListFragment {

    public static final String CALDROID_DIALOG_FRAGMENT_TAG = "CALDROID_DIALOG_FRAGMENT";
    private ListView mListView;
    private PlanMonthFragment planMonthFragment;

    protected int getMenuResource() {
        return R.menu.templateslist_context;
    }

    private Cursor mTemplatesCursor;
    private SimpleCursorAdapter mAdapter;
    private LoaderManager mManager;

    private int columnIndexAmount, columnIndexLabelSub, columnIndexComment, columnIndexPayee, columnIndexColor,
            columnIndexTransferPeer, columnIndexCurrency, columnIndexTransferAccount, columnIndexPlanId,
            columnIndexTitle, columnIndexRowId, columnIndexPlanInfo;
    private boolean indexesCalculated = false;
    /**
     * if we are called from the calendar app, we only need to handle display of plan once
     */
    @State
    boolean expandedHandled = false;

    @State
    boolean repairTriggered = false;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setHasOptionsMenu(true);
        Icepick.restoreInstanceState(this, savedInstanceState);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        Icepick.saveInstanceState(this, outState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.templates_list, container, false);
        mListView = (ListView) v.findViewById(R.id.list);

        mManager = getLoaderManager();
        mManager.initLoader(SORTABLE_CURSOR, null, this);
        // Create an array to specify the fields we want to display in the list
        String[] from = new String[] { KEY_TITLE, KEY_LABEL_MAIN, KEY_AMOUNT };
        // and an array of the fields we want to bind those fields to
        int[] to = new int[] { R.id.title, R.id.category, R.id.amount };
        mAdapter = new MyAdapter(getActivity(), R.layout.template_row, null, from, to, 0);
        mListView.setAdapter(mAdapter);
        mListView.setEmptyView(v.findViewById(R.id.empty));
        mListView.setOnItemClickListener(new OnItemClickListener() {

            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                if (mTemplatesCursor == null || !mTemplatesCursor.moveToPosition(position))
                    return;
                if (!mTemplatesCursor.isNull(columnIndexPlanId)) {
                    if (isCalendarPermissionGranted()) {
                        planMonthFragment = PlanMonthFragment.newInstance(
                                mTemplatesCursor.getString(columnIndexTitle), id,
                                mTemplatesCursor.getLong(columnIndexPlanId),
                                mTemplatesCursor.getInt(columnIndexColor), false);
                        planMonthFragment.show(getChildFragmentManager(), CALDROID_DIALOG_FRAGMENT_TAG);
                    } else {
                        ((ProtectedFragmentActivity) getActivity()).requestCalendarPermission();
                    }
                } else if (isForeignExchangeTransfer(position)) {
                    ((ManageTemplates) getActivity()).dispatchCommand(R.id.CREATE_INSTANCE_EDIT_COMMAND, id);
                } else if (PrefKey.TEMPLATE_CLICK_HINT_SHOWN.getBoolean(false)) {
                    if (PrefKey.TEMPLATE_CLICK_DEFAULT.getString("SAVE").equals("SAVE")) {
                        dispatchCreateInstanceSave(new Long[] { id });
                    } else {
                        dispatchCreateInstanceEdit(id);
                    }
                } else {
                    Bundle b = new Bundle();
                    b.putLong(KEY_ROWID, id);
                    b.putInt(ConfirmationDialogFragment.KEY_TITLE, R.string.dialog_title_information);
                    b.putString(ConfirmationDialogFragment.KEY_MESSAGE, getString(R.string.hint_template_click));
                    b.putInt(ConfirmationDialogFragment.KEY_COMMAND_POSITIVE, R.id.CREATE_INSTANCE_SAVE_COMMAND);
                    b.putInt(ConfirmationDialogFragment.KEY_COMMAND_NEGATIVE, R.id.CREATE_INSTANCE_EDIT_COMMAND);
                    b.putString(ConfirmationDialogFragment.KEY_PREFKEY, PrefKey.TEMPLATE_CLICK_HINT_SHOWN.getKey());
                    b.putInt(ConfirmationDialogFragment.KEY_POSITIVE_BUTTON_LABEL,
                            R.string.menu_create_instance_save);
                    b.putInt(ConfirmationDialogFragment.KEY_NEGATIVE_BUTTON_LABEL,
                            R.string.menu_create_instance_edit);
                    ConfirmationDialogFragment.newInstance(b).show(getFragmentManager(), "TEMPLATE_CLICK_HINT");
                }
            }
        });
        registerForContextualActionBar(mListView);
        return v;
    }

    private boolean isCalendarPermissionGranted() {
        return ContextCompat.checkSelfPermission(getContext(),
                Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_GRANTED;
    }

    @Override
    public boolean dispatchCommandMultiple(int command, SparseBooleanArray positions, Long[] itemIds) {
        switch (command) {
        case R.id.DELETE_COMMAND:
            MessageDialogFragment.newInstance(R.string.dialog_title_warning_delete_template, //TODO check if template
                    getResources().getQuantityString(R.plurals.warning_delete_template, itemIds.length,
                            itemIds.length),
                    new MessageDialogFragment.Button(R.string.menu_delete, R.id.DELETE_COMMAND_DO, itemIds), null,
                    new MessageDialogFragment.Button(android.R.string.no, R.id.CANCEL_CALLBACK_COMMAND, null))
                    .show(getActivity().getSupportFragmentManager(), "DELETE_TEMPLATE");
            return true;
        case R.id.CREATE_INSTANCE_SAVE_COMMAND:
            finishActionMode();
            dispatchCreateInstanceSave(itemIds);
            return true;
        case R.id.CREATE_PLAN_INSTANCE_SAVE_COMMAND:
        case R.id.CANCEL_PLAN_INSTANCE_COMMAND:
        case R.id.RESET_PLAN_INSTANCE_COMMAND:
            requirePlanMonthFragment().dispatchCommandMultiple(command, positions);
            finishActionMode();
            return true;
        }
        return super.dispatchCommandMultiple(command, positions, itemIds);
    }

    @Override
    public boolean dispatchCommandSingle(int command, ContextMenu.ContextMenuInfo info) {
        AdapterContextMenuInfo menuInfo = (AdapterContextMenuInfo) info;
        Intent i;
        switch (command) {
        case R.id.CREATE_INSTANCE_EDIT_COMMAND:
            finishActionMode();
            dispatchCreateInstanceEdit(menuInfo.id);
            return true;
        case R.id.EDIT_COMMAND:
            finishActionMode();
            i = new Intent(getActivity(), ExpenseEdit.class);
            i.putExtra(DatabaseConstants.KEY_TEMPLATEID, menuInfo.id);
            //TODO check what to do on Result
            startActivityForResult(i, ProtectedFragmentActivity.EDIT_TRANSACTION_REQUEST);
            return true;
        case R.id.EDIT_PLAN_INSTANCE_COMMAND:
        case R.id.CREATE_PLAN_INSTANCE_EDIT_COMMAND:
            requirePlanMonthFragment().dispatchCommandSingle(command, menuInfo.position);
            finishActionMode();
            return true;
        }
        return super.dispatchCommandSingle(command, info);
    }

    public void dispatchCreateInstanceSave(Long[] itemIds) {
        ((ProtectedFragmentActivity) getActivity()).startTaskExecution(TaskExecutionFragment.TASK_NEW_FROM_TEMPLATE,
                itemIds, null, 0);
    }

    public void dispatchCreateInstanceEdit(long itemId) {
        Intent intent = new Intent(getActivity(), ExpenseEdit.class);
        intent.putExtra(KEY_TEMPLATEID, itemId);
        intent.putExtra(KEY_INSTANCEID, -1L);
        startActivity(intent);
    }

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle bundle) {
        switch (id) {
        case SORTABLE_CURSOR:
            return new CursorLoader(getActivity(),
                    TransactionProvider.TEMPLATES_URI.buildUpon()
                            .appendQueryParameter(TransactionProvider.QUERY_PARAMETER_WITH_PLAN_INFO, "1").build(),
                    null, null, null, null);
        }
        return null;
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
        switch (loader.getId()) {
        case SORTABLE_CURSOR:
            mTemplatesCursor = c;
            if (c != null && !indexesCalculated) {
                columnIndexRowId = c.getColumnIndex(KEY_ROWID);
                columnIndexAmount = c.getColumnIndex(KEY_AMOUNT);
                columnIndexLabelSub = c.getColumnIndex(KEY_LABEL_SUB);
                columnIndexComment = c.getColumnIndex(KEY_COMMENT);
                columnIndexPayee = c.getColumnIndex(KEY_PAYEE_NAME);
                columnIndexColor = c.getColumnIndex(KEY_COLOR);
                columnIndexTransferPeer = c.getColumnIndex(KEY_TRANSFER_PEER);
                columnIndexCurrency = c.getColumnIndex(KEY_CURRENCY);
                columnIndexTransferAccount = c.getColumnIndex(KEY_TRANSFER_ACCOUNT);
                columnIndexPlanId = c.getColumnIndex(KEY_PLANID);
                columnIndexTitle = c.getColumnIndex(KEY_TITLE);
                columnIndexPlanInfo = c.getColumnIndex(KEY_PLAN_INFO);
                indexesCalculated = true;
            }
            mAdapter.swapCursor(mTemplatesCursor);
            invalidateCAB();
            if (isCalendarPermissionGranted() && mTemplatesCursor != null && mTemplatesCursor.moveToFirst()) {
                long needToExpand = expandedHandled ? ManageTemplates.NOT_CALLED
                        : ((ManageTemplates) getActivity()).getCalledFromCalendarWithId();
                boolean foundToExpand = false;
                while (!mTemplatesCursor.isAfterLast()) {
                    long templateId = mTemplatesCursor.getLong(columnIndexRowId);
                    if (needToExpand == templateId) {
                        planMonthFragment = PlanMonthFragment.newInstance(
                                mTemplatesCursor.getString(columnIndexTitle), templateId,
                                mTemplatesCursor.getLong(columnIndexPlanId),
                                mTemplatesCursor.getInt(columnIndexColor), false);
                        foundToExpand = true;
                    }
                    mTemplatesCursor.moveToNext();
                }
                if (needToExpand != ManageTemplates.NOT_CALLED) {
                    expandedHandled = true;
                    if (foundToExpand) {
                        planMonthFragment.show(getChildFragmentManager(), CALDROID_DIALOG_FRAGMENT_TAG);
                    } else {
                        Toast.makeText(getActivity(), R.string.save_transaction_template_deleted, Toast.LENGTH_LONG)
                                .show();
                    }
                }
                //look for plans that we could possible relink
                if (!repairTriggered && mTemplatesCursor.moveToFirst()) {
                    final ArrayList<String> missingUuids = new ArrayList<>();
                    while (!mTemplatesCursor.isAfterLast()) {
                        if (!mTemplatesCursor.isNull(columnIndexPlanId)
                                && mTemplatesCursor.isNull(columnIndexPlanInfo)) {
                            missingUuids.add(mTemplatesCursor.getString(mTemplatesCursor.getColumnIndex(KEY_UUID)));
                        }
                        mTemplatesCursor.moveToNext();
                    }
                    if (missingUuids.size() > 0) {
                        new RepairHandler(this)
                                .obtainMessage(0, missingUuids.toArray(new String[missingUuids.size()]))
                                .sendToTarget();
                    }
                }
            }
            break;
        }
    }

    private static class RepairHandler extends Handler {
        private final WeakReference<TemplatesList> mFragment;

        public RepairHandler(TemplatesList fragment) {
            mFragment = new WeakReference<TemplatesList>(fragment);
        }

        @Override
        public void handleMessage(Message msg) {
            String[] missingUuids = (String[]) msg.obj;
            TemplatesList fragment = mFragment.get();
            if (fragment != null && fragment.getActivity() != null) {
                fragment.repairTriggered = true;
                ((ProtectedFragmentActivity) fragment.getActivity())
                        .startTaskExecution(TaskExecutionFragment.TASK_REPAIR_PLAN, missingUuids, null, 0);
            }
        }
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        switch (loader.getId()) {
        case SORTABLE_CURSOR:
            mTemplatesCursor = null;
            mAdapter.swapCursor(null);
            break;
        }
    }

    @Override
    protected PrefKey getSortOrderPrefKey() {
        return PrefKey.SORT_ORDER_TEMPLATES;
    }

    //after orientation change, we need to restore the reference
    public PlanMonthFragment requirePlanMonthFragment() {
        return planMonthFragment != null ? planMonthFragment
                : ((PlanMonthFragment) getChildFragmentManager().findFragmentByTag(CALDROID_DIALOG_FRAGMENT_TAG));
    }

    private class MyAdapter extends SimpleCursorAdapter {
        private int colorExpense;
        private int colorIncome;
        private String categorySeparator = " : ", commentSeparator = " / ";

        public MyAdapter(Context context, int layout, Cursor c, String[] from, int[] to, int flags) {
            super(context, layout, c, from, to, flags);
            colorIncome = ((ProtectedFragmentActivity) context).getColorIncome();
            colorExpense = ((ProtectedFragmentActivity) context).getColorExpense();
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            convertView = super.getView(position, convertView, parent);
            Cursor c = getCursor();
            c.moveToPosition(position);
            boolean doesHavePlan = !c.isNull(columnIndexPlanId);
            TextView tv1 = (TextView) convertView.findViewById(R.id.amount);
            long amount = c.getLong(columnIndexAmount);
            tv1.setTextColor(amount < 0 ? colorExpense : colorIncome);
            tv1.setText(Utils.convAmount(amount, Utils.getSaveInstance(c.getString(columnIndexCurrency))));
            int color = c.getInt(columnIndexColor);
            convertView.findViewById(R.id.colorAccount).setBackgroundColor(color);
            TextView tv2 = (TextView) convertView.findViewById(R.id.category);
            CharSequence catText = tv2.getText();
            if (c.getInt(columnIndexTransferPeer) > 0) {
                catText = ((amount < 0) ? "=> " : "<= ") + catText;
            } else {
                Long catId = DbUtils.getLongOrNull(c, KEY_CATID);
                if (catId == null) {
                    catText = Category.NO_CATEGORY_ASSIGNED_LABEL;
                } else {
                    String label_sub = c.getString(columnIndexLabelSub);
                    if (label_sub != null && label_sub.length() > 0) {
                        catText = catText + categorySeparator + label_sub;
                    }
                }
            }
            //TODO: simplify confer TemplateWidget
            SpannableStringBuilder ssb;
            String comment = c.getString(columnIndexComment);
            if (comment != null && comment.length() > 0) {
                ssb = new SpannableStringBuilder(comment);
                ssb.setSpan(new StyleSpan(android.graphics.Typeface.ITALIC), 0, comment.length(), 0);
                catText = TextUtils.concat(catText, commentSeparator, ssb);
            }
            String payee = c.getString(columnIndexPayee);
            if (payee != null && payee.length() > 0) {
                ssb = new SpannableStringBuilder(payee);
                ssb.setSpan(new UnderlineSpan(), 0, payee.length(), 0);
                catText = TextUtils.concat(catText, commentSeparator, ssb);
            }
            tv2.setText(catText);

            if (doesHavePlan) {
                String planInfo = c.getString(columnIndexPlanInfo);
                if (planInfo == null) {
                    planInfo = getString(ContextCompat.checkSelfPermission(getActivity(),
                            Manifest.permission.WRITE_CALENDAR) == PackageManager.PERMISSION_DENIED
                                    ? R.string.calendar_permission_required
                                    : R.string.plan_event_deleted);
                }
                ((TextView) convertView.findViewById(R.id.title)).setText(
                        //noinspection SetTextI18n
                        c.getString(columnIndexTitle) + " (" + planInfo + ")");
            }
            ((ImageView) convertView.findViewById(R.id.Plan))
                    .setImageResource(doesHavePlan ? R.drawable.ic_event : R.drawable.ic_menu_template);
            return convertView;
        }
    }

    @Override
    protected void configureMenuLegacy(Menu menu, ContextMenu.ContextMenuInfo menuInfo, int listId) {
        super.configureMenuLegacy(menu, menuInfo, listId);
        switch (listId) {
        case R.id.list:
            AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
            configureMenuInternal(menu, 1, isForeignExchangeTransfer(info.position), isPlan(info.position));
            break;
        case R.id.calendar_gridview:
            requirePlanMonthFragment().configureMenuLegacy(menu, menuInfo);
        }
    }

    @Override
    protected void configureMenu11(Menu menu, int count, AbsListView lv) {
        super.configureMenu11(menu, count, lv);
        switch (lv.getId()) {
        case R.id.list:
            SparseBooleanArray checkedItemPositions = mListView.getCheckedItemPositions();
            boolean hasForeignExchangeTransfer = false, hasPlan = false;
            for (int i = 0; i < checkedItemPositions.size(); i++) {
                if (checkedItemPositions.valueAt(i) && isForeignExchangeTransfer(checkedItemPositions.keyAt(i))) {
                    hasForeignExchangeTransfer = true;
                    break;
                }
            }
            for (int i = 0; i < checkedItemPositions.size(); i++) {
                if (checkedItemPositions.valueAt(i) && isPlan(checkedItemPositions.keyAt(i))) {
                    hasPlan = true;
                    break;
                }
            }
            configureMenuInternal(menu, count, hasForeignExchangeTransfer, hasPlan);
            break;
        case R.id.calendar_gridview:
            requirePlanMonthFragment().configureMenu11(menu, count, lv);
        }
    }

    private void configureMenuInternal(Menu menu, int count, boolean foreignExchangeTransfer, boolean hasPlan) {
        menu.findItem(R.id.CREATE_INSTANCE_SAVE_COMMAND).setVisible(!foreignExchangeTransfer && !hasPlan);
        menu.findItem(R.id.CREATE_INSTANCE_EDIT_COMMAND).setVisible(count == 1 && !hasPlan);
    }

    private boolean isForeignExchangeTransfer(int position) {
        if (mTemplatesCursor != null && mTemplatesCursor.moveToPosition(position)) {
            if (mTemplatesCursor.getInt(columnIndexTransferPeer) != 0) {
                Account transferAccount = Account
                        .getInstanceFromDb(mTemplatesCursor.getLong(columnIndexTransferAccount));
                return !mTemplatesCursor.getString(columnIndexCurrency)
                        .equals(transferAccount.currency.getCurrencyCode());
            }
        }
        return false;
    }

    private boolean isPlan(int position) {
        if (mTemplatesCursor != null && mTemplatesCursor.moveToPosition(position)) {
            return !mTemplatesCursor.isNull(columnIndexPlanId);
        }
        return false;
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        inflater.inflate(R.menu.sort, menu);
        SubMenu subMenu = menu.findItem(R.id.SORT_COMMAND).getSubMenu();
        subMenu.findItem(R.id.SORT_AMOUNT_COMMAND).setVisible(true);
        subMenu.findItem(R.id.SORT_NEXT_INSTANCE_COMMAND).setVisible(true);
    }

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

    @Override
    protected void inflateHelper(Menu menu, int listId) {
        switch (listId) {
        case R.id.list:
            super.inflateHelper(menu, listId);
            break;
        case R.id.calendar_gridview:
            getActivity().getMenuInflater().inflate(R.menu.planlist_context, menu);
        }
    }

    public void refresh() {
        Utils.requireLoader(mManager, SORTABLE_CURSOR, null, this);
    }
}