org.akop.crosswords.fragment.SelectorFragment.java Source code

Java tutorial

Introduction

Here is the source code for org.akop.crosswords.fragment.SelectorFragment.java

Source

// Copyright (c) 2014-2015 Akop Karapetyan
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

package org.akop.crosswords.fragment;

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.res.Resources;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.widget.SwipeRefreshLayout;
import android.text.format.DateUtils;
import android.util.SparseBooleanArray;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.CursorAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;

import com.bumptech.glide.GenericRequestBuilder;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.resource.bitmap.BitmapEncoder;
import com.bumptech.glide.load.resource.bitmap.StreamBitmapDecoder;
import com.bumptech.glide.load.resource.file.FileToStreamDecoder;
import com.bumptech.glide.request.target.BitmapImageViewTarget;

import org.akop.crosswords.Crosswords;
import org.akop.crosswords.Preferences;
import org.akop.crosswords.R;
import org.akop.crosswords.Storage;
import org.akop.crosswords.activity.CrosswordActivity;
import org.akop.crosswords.activity.SubscriptionActivity;
import org.akop.crosswords.graphics.CrosswordModelLoader;
import org.akop.crosswords.graphics.CrosswordResourceDecoder;
import org.akop.crosswords.model.Config;
import org.akop.crosswords.model.PuzzleSource;
import org.akop.crosswords.service.CrosswordFetchRunnable;
import org.akop.crosswords.service.CrosswordFetchService;
import org.akop.crosswords.utility.SimpleCursorLoader;
import org.joda.time.DateTime;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

public class SelectorFragment extends BaseFragment implements FoldersFragment.OnFolderSelectedListener {
    public static Bundle createArgs(long folderId) {
        Bundle bundle = new Bundle();
        bundle.putLong(ARG_FOLDER_ID, folderId);

        return bundle;
    }

    private ListView mListView;
    private View mUnsubscribed;
    private View mEmptyContainer;
    private TextView mEmptyMessage;
    private View mRefresh;
    private SwipeRefreshLayout mSwiper;

    private CrosswordAdapter mAdapter;
    private GenericRequestBuilder<String, String, Bitmap, Bitmap> mReqBuilder;

    private long mFolderId;

    private static final int INDEX_PUZZLE_ID = 0;
    private static final int INDEX_PUZZLE_TITLE = 1;
    private static final int INDEX_PUZZLE_AUTHOR = 2;
    private static final int INDEX_PUZZLE_HASH = 3;
    private static final int INDEX_PUZZLE_SOURCE_ID = 4;
    private static final int INDEX_PUZZLE_STATE_PERCENT_SOLVED = 5;
    private static final int INDEX_PUZZLE_STATE_PERCENT_CHEATED = 6;
    private static final int INDEX_PUZZLE_STATE_PERCENT_WRONG = 7;
    private static final int INDEX_PUZZLE_STATE_PLAY_TIME_MILLIS = 8;
    private static final int INDEX_PUZZLE_STATE_LAST_PLAYED = 9;

    private static final String sQueryTemplate = "SELECT " + "p." + Storage.Puzzle._ID + ", " + "p."
            + Storage.Puzzle.TITLE + "," + "p." + Storage.Puzzle.AUTHOR + "," + "p." + Storage.Puzzle.HASH + ","
            + "p." + Storage.Puzzle.SOURCE_ID + "," + "s." + Storage.PuzzleState.PERCENT_SOLVED + "," + "s."
            + Storage.PuzzleState.PERCENT_CHEATED + "," + "s." + Storage.PuzzleState.PERCENT_WRONG + "," + "s."
            + Storage.PuzzleState.PLAY_TIME_MILLIS + "," + "s." + Storage.PuzzleState.LAST_PLAYED + " " + "FROM "
            + Storage.Puzzle.TABLE + " p " + "LEFT JOIN " + Storage.PuzzleState.TABLE + " s " + "ON s."
            + Storage.PuzzleState.PUZZLE_ID + " = p." + Storage.Puzzle._ID + " " + "WHERE p."
            + Storage.Puzzle.FOLDER_ID + "= %d " + "ORDER BY " + Storage.Puzzle.DATE + " DESC";

    private static String ARG_FOLDER_ID = "folderId";

    private LoaderManager.LoaderCallbacks<Cursor> mLoaderCallbacks = new LoaderManager.LoaderCallbacks<Cursor>() {
        @Override
        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
            return new CrosswordLoader(getActivity(), args.getLong(ARG_FOLDER_ID));
        }

        @Override
        public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
            mAdapter.swapCursor(data);
            getActivity().invalidateOptionsMenu();
        }

        @Override
        public void onLoaderReset(Loader<Cursor> loader) {
            mAdapter.swapCursor(null);
        }
    };

    private BroadcastReceiver mPuzzleChangeReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            Bundle args = new Bundle();
            args.putLong(ARG_FOLDER_ID, mFolderId);
            getLoaderManager().restartLoader(0, args, mLoaderCallbacks);
        }
    };

    private SwipeRefreshLayout.OnRefreshListener mRefreshListener = new SwipeRefreshLayout.OnRefreshListener() {
        @Override
        public void onRefresh() {
            updateSubscriptions(DateTime.now());
            mSwiper.setRefreshing(false);
        }
    };

    private AbsListView.MultiChoiceModeListener mMultiChoiceListener = new AbsListView.MultiChoiceModeListener() {
        @Override
        public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
            // I'm going to make a crazy assumption that the user doesn't have
            // more than ~4.2bn puzzles here

            mAdapter.mCheckedItems.put((int) id, checked);
            mAdapter.notifyDataSetChanged();
        }

        @Override
        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
            MenuInflater inflater = mode.getMenuInflater();
            inflater.inflate(R.menu.fragment_selector_context, menu);

            return true;
        }

        @Override
        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
            menu.setGroupVisible(R.id.menu_group_inboxable, mFolderId != Storage.FOLDER_INBOX);
            menu.setGroupVisible(R.id.menu_group_archivable, mFolderId != Storage.FOLDER_ARCHIVES);
            menu.setGroupVisible(R.id.menu_group_deletable, mFolderId != Storage.FOLDER_TRASH);

            return true;
        }

        @Override
        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
            boolean handled = false;

            switch (item.getItemId()) {
            case R.id.menu_move_to_inbox:
                handled = true;
                moveToFolder(Storage.FOLDER_INBOX, mListView.getCheckedItemIds());
                break;
            case R.id.menu_archive:
                handled = true;
                moveToFolder(Storage.FOLDER_ARCHIVES, mListView.getCheckedItemIds());
                break;
            case R.id.menu_delete:
                handled = true;
                moveToFolder(Storage.FOLDER_TRASH, mListView.getCheckedItemIds());
                break;
            }

            if (handled) {
                mode.finish();
            }

            return handled;
        }

        @Override
        public void onDestroyActionMode(ActionMode mode) {
            mAdapter.mCheckedItems.clear();
        }
    };

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

        Bundle args = getArguments();
        if (args != null) {
            mFolderId = args.getLong(ARG_FOLDER_ID);
        }

        if (savedInstanceState != null) {
            mFolderId = savedInstanceState.getLong(ARG_FOLDER_ID);
        }

        Activity activity = getActivity();
        mAdapter = new CrosswordAdapter(activity);

        Bundle loaderArgs = new Bundle();
        loaderArgs.putLong(ARG_FOLDER_ID, mFolderId);
        getLoaderManager().initLoader(0, loaderArgs, mLoaderCallbacks);

        // Add a listener for puzzle changes
        IntentFilter filter = new IntentFilter();
        filter.addAction(Storage.ACTION_PUZZLE_CHANGE);
        filter.addAction(Storage.ACTION_PUZZLE_STATE_CHANGE);

        LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(activity);
        lbm.registerReceiver(mPuzzleChangeReceiver, filter);

        setHasOptionsMenu(true);

        mReqBuilder = Glide.with(this).using(new CrosswordModelLoader(), String.class).from(String.class)
                .as(Bitmap.class).decoder(new CrosswordResourceDecoder(activity))
                .cacheDecoder(new FileToStreamDecoder(new StreamBitmapDecoder(activity)))
                .encoder(new BitmapEncoder());
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View layout = inflater.inflate(R.layout.fragment_selector, container, false);

        mListView = (ListView) layout.findViewById(R.id.list_view);
        mSwiper = (SwipeRefreshLayout) layout.findViewById(R.id.swiper);
        mUnsubscribed = layout.findViewById(R.id.unsubscribed);
        mEmptyContainer = layout.findViewById(R.id.empty_container);
        mEmptyMessage = (TextView) layout.findViewById(R.id.empty_message);

        View selectSubs = mUnsubscribed.findViewById(R.id.select_subscriptions);
        selectSubs.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                SubscriptionActivity.launch(getActivity());
            }
        });

        mRefresh = mEmptyContainer.findViewById(R.id.refresh);
        mRefresh.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                updateSubscriptions(DateTime.now());
            }
        });

        mSwiper.setOnRefreshListener(mRefreshListener);

        mListView.setEmptyView(mEmptyContainer);
        mListView.setMultiChoiceModeListener(mMultiChoiceListener);
        mListView.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE_MODAL);
        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                CrosswordActivity.launch(getActivity(), id);
            }
        });

        updateEmptyView();

        return layout;
    }

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

        updateEmptyView();
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        super.onCreateOptionsMenu(menu, inflater);

        inflater.inflate(R.menu.fragment_selector, menu);
    }

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

        menu.setGroupVisible(R.id.menu_group_emptyable,
                mFolderId == Storage.FOLDER_TRASH && mAdapter.getCount() > 0);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.menu_empty_trash:
            confirmEmptyTrash();
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

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

        if (savedInstanceState != null) {
            readSparseBooleanArray(savedInstanceState, "cabItems", mAdapter.mCheckedItems);
        }

        mListView.setAdapter(mAdapter);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

        putSparseBooleanArray(outState, "cabItems", mAdapter.mCheckedItems);
        outState.putLong(ARG_FOLDER_ID, mFolderId);
    }

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

        LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(getActivity());
        lbm.unregisterReceiver(mPuzzleChangeReceiver);
    }

    @Override
    public void onFolderSelected(long id, String title) {
        if (mFolderId != id) {
            mFolderId = id;
            Bundle args = new Bundle();
            args.putLong(ARG_FOLDER_ID, mFolderId);
            getLoaderManager().restartLoader(0, args, mLoaderCallbacks);

            if (mListView != null) {
                updateEmptyView();
            }

            if (getActivity() != null) {
                getActivity().invalidateOptionsMenu();
            }
        }
    }

    private void confirmEmptyTrash() {
        int count = mAdapter.getCount();
        if (count < 1) {
            return;
        }

        Resources res = getResources();
        String message = res.getQuantityString(R.plurals.permanently_delete_puzzles_f, count, count);

        AlertDialog dialog = new AlertDialog.Builder(getActivity()).setMessage(message)
                .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        emptyTrash();
                    }
                }).setNegativeButton(R.string.no, null).create();

        dialog.show();
    }

    private void emptyTrash() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Storage.getInstance().emptyFolder(Storage.FOLDER_TRASH);
            }
        }).start();
    }

    private void moveToFolder(final long folderId, final long[] puzzleIds) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Storage.getInstance().moveTo(folderId, puzzleIds);
            }
        }).start();
    }

    protected void updateSubscriptions(DateTime date) {
        Config config = Crosswords.getInstance().getConfig();
        Preferences prefs = Preferences.getInstance();
        Set<String> subscriptions = prefs.getSubscriptions();

        // Start the IntentService that will do the actual fetching
        List<CrosswordFetchRunnable.Request> requests = new ArrayList<>();

        for (String subscription : subscriptions) {
            PuzzleSource source = config.getSource(subscription);
            if (source != null) {
                CrosswordFetchRunnable.Request req = new CrosswordFetchRunnable.Request(source, date);
                String url = req.getUrl();

                long crosswordId = Storage.getInstance().findBySourceUrl(url);
                if (crosswordId == Storage.ID_NOT_FOUND) {
                    requests.add(req);
                }
            }
        }

        if (requests.size() != 0) {
            CrosswordFetchRunnable.Request[] requestArray = new CrosswordFetchRunnable.Request[requests.size()];
            requests.toArray(requestArray);

            CrosswordFetchService.startService(getActivity(), requestArray, false, true);
        } else {
            if (subscriptions.size() == 0) {
                showMessage(getString(R.string.not_subscribed), R.string.subscribe, new Runnable() {
                    @Override
                    public void run() {
                        SubscriptionActivity.launch(getActivity());
                    }
                });
            } else {
                showMessage(getString(R.string.all_caught_up));
            }
        }
    }

    protected void updateEmptyView() {
        Preferences prefs = Preferences.getInstance();
        if (mFolderId == Storage.FOLDER_INBOX) {
            if (!prefs.hasSubscriptions()) {
                mListView.setEmptyView(mUnsubscribed);
                mEmptyContainer.setVisibility(View.GONE);
            } else {
                mListView.setEmptyView(mEmptyContainer);
                mEmptyMessage.setText(R.string.nothing_downloaded_yet);
                mRefresh.setVisibility(View.VISIBLE);
                mUnsubscribed.setVisibility(View.GONE);
            }
        } else {
            mListView.setEmptyView(mEmptyContainer);
            mEmptyMessage.setText(R.string.theres_nothing_here);
            mRefresh.setVisibility(View.GONE);
            mUnsubscribed.setVisibility(View.GONE);
        }
    }

    private static class SourceInfo {
        int mColor;
        String mAbbrev;
        String mTitle;

        SourceInfo(int color, PuzzleSource source) {
            mColor = color;
            if (source == null) {
                mAbbrev = "??";
                mTitle = "??";
            } else {
                mAbbrev = source.getAbbreviation();
                mTitle = source.getName();
            }
        }
    }

    private static class ViewHolder {
        ImageView mThumbnail;
        TextView mTitle;
        TextView mAuthor;
        TextView mSource;
        View mGauge;
        View mPercentSolved;
        View mPercentCheated;
        View mPercentWrong;
        TextView mPlayTime;
        TextView mLastPlayed;
        BitmapImageViewTarget mThumbnailTarget;
    }

    private class CrosswordAdapter extends CursorAdapter {
        private Context mContext;
        private SparseBooleanArray mCheckedItems;
        private Map<String, SourceInfo> mSourceInfos;
        private SourceInfo mUnknownSource;
        private int mGaugeSolvedColor;
        private int mGaugePerfectColor;

        public CrosswordAdapter(Context context) {
            super(context, null, false);

            mContext = context;
            mCheckedItems = new SparseBooleanArray();
            mGaugeSolvedColor = getThemedColor(context, R.attr.puzzleSolvedGauge);
            mGaugePerfectColor = getThemedColor(context, R.attr.puzzlePerfectGauge);

            mSourceInfos = new TreeMap<>();

            int[] colors = context.getResources().getIntArray(R.array.source_colors);
            Config config = Crosswords.getInstance().getConfig();
            for (PuzzleSource source : config.getSources()) {
                int colorIndex = source.getColorIndex() % colors.length;
                mSourceInfos.put(source.getId(), new SourceInfo(colors[colorIndex], source));
            }

            int unknownSourceColor = getThemedColor(context, R.attr.roundedViewUnavailable);
            mUnknownSource = new SourceInfo(unknownSourceColor, null);
        }

        @Override
        public View newView(Context context, Cursor cursor, ViewGroup parent) {
            LayoutInflater inflater = LayoutInflater.from(context);
            View layout = inflater.inflate(R.layout.template_crossword_item, parent, false);

            ViewHolder vh = new ViewHolder();
            vh.mTitle = (TextView) layout.findViewById(R.id.title);
            vh.mThumbnail = (ImageView) layout.findViewById(R.id.thumbnail);
            vh.mThumbnailTarget = new BitmapImageViewTarget(vh.mThumbnail);
            vh.mAuthor = (TextView) layout.findViewById(R.id.author);
            vh.mSource = (TextView) layout.findViewById(R.id.source);
            vh.mGauge = layout.findViewById(R.id.gauge);
            vh.mPercentSolved = layout.findViewById(R.id.gauge_percent_solved);
            vh.mPercentCheated = layout.findViewById(R.id.gauge_percent_cheated);
            vh.mPercentWrong = layout.findViewById(R.id.gauge_percent_wrong);
            vh.mLastPlayed = (TextView) layout.findViewById(R.id.last_played);
            vh.mPlayTime = (TextView) layout.findViewById(R.id.play_time);

            layout.setTag(vh);

            return layout;
        }

        @Override
        public void bindView(View view, Context context, Cursor cursor) {
            ViewHolder vh = (ViewHolder) view.getTag();

            long id = cursor.getLong(INDEX_PUZZLE_ID);
            String title = cursor.getString(INDEX_PUZZLE_TITLE);
            String author = cursor.getString(INDEX_PUZZLE_AUTHOR);
            String hash = cursor.getString(INDEX_PUZZLE_HASH);
            String sourceId = cursor.getString(INDEX_PUZZLE_SOURCE_ID);
            int percentSolved = cursor.getInt(INDEX_PUZZLE_STATE_PERCENT_SOLVED);
            int percentCheated = cursor.getInt(INDEX_PUZZLE_STATE_PERCENT_CHEATED);
            int percentWrong = cursor.getInt(INDEX_PUZZLE_STATE_PERCENT_WRONG);
            long playTimeMillis = cursor.getLong(INDEX_PUZZLE_STATE_PLAY_TIME_MILLIS);
            long lastPlayedMillis = cursor.getLong(INDEX_PUZZLE_STATE_LAST_PLAYED);

            SourceInfo info = mSourceInfos.get(sourceId);
            if (info == null) {
                info = mUnknownSource;
            }

            vh.mTitle.setText(title);
            vh.mAuthor.setText(author);
            vh.mSource.setText(info.mTitle);

            if (mCheckedItems.get((int) id)) {
                vh.mThumbnail.setAlpha(0.33f);
            } else {
                vh.mThumbnail.setAlpha(1f);
            }

            LinearLayout.LayoutParams lp;
            lp = (LinearLayout.LayoutParams) vh.mPercentSolved.getLayoutParams();
            lp.weight = (float) percentSolved;
            vh.mPercentSolved.requestLayout();

            lp = (LinearLayout.LayoutParams) vh.mPercentCheated.getLayoutParams();
            lp.weight = (float) percentCheated;
            vh.mPercentCheated.requestLayout();

            lp = (LinearLayout.LayoutParams) vh.mPercentWrong.getLayoutParams();
            lp.weight = (float) percentWrong;
            vh.mPercentWrong.requestLayout();

            if (percentSolved == 100) {
                vh.mPercentSolved.setBackgroundColor(mGaugePerfectColor);
            } else {
                vh.mPercentSolved.setBackgroundColor(mGaugeSolvedColor);
            }

            if (playTimeMillis == 0) {
                vh.mTitle.setTextAppearance(mContext, R.style.TextAppearance_CrosswordList_Title_Unplayed);
                vh.mAuthor.setTextAppearance(mContext, R.style.TextAppearance_CrosswordList_Detail_Unplayed);

                vh.mGauge.setVisibility(View.INVISIBLE);
                vh.mLastPlayed.setVisibility(View.INVISIBLE);
                vh.mPlayTime.setVisibility(View.INVISIBLE);
            } else {
                vh.mTitle.setTextAppearance(mContext, R.style.TextAppearance_CrosswordList_Title);
                vh.mAuthor.setTextAppearance(mContext, R.style.TextAppearance_CrosswordList_Detail);

                vh.mLastPlayed.setVisibility(View.VISIBLE);
                vh.mPlayTime.setVisibility(View.VISIBLE);

                String lastPlayed = mContext.getString(R.string.last_played_f,
                        DateUtils.getRelativeTimeSpanString(mContext, lastPlayedMillis));
                String playTime = DateUtils.formatElapsedTime(playTimeMillis / 1000);

                vh.mGauge.setVisibility(View.VISIBLE);
                vh.mLastPlayed.setText(lastPlayed);
                vh.mPlayTime.setText(playTime);
            }

            mReqBuilder.load(hash).into(vh.mThumbnailTarget);
        }
    }

    private static class CrosswordLoader extends SimpleCursorLoader {
        private long mFolderId;

        private CrosswordLoader(Context context, long folderId) {
            super(context);

            mFolderId = folderId;
        }

        @Override
        public Cursor loadInBackground() {
            SQLiteDatabase db = Storage.getInstance().getDatabase(false);
            Cursor cursor = db.rawQuery(String.format(sQueryTemplate, mFolderId), null);

            if (cursor != null) {
                cursor.getCount();
            }

            return cursor;
        }
    }
}