org.openbmap.activities.SessionListFragment.java Source code

Java tutorial

Introduction

Here is the source code for org.openbmap.activities.SessionListFragment.java

Source

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

package org.openbmap.activities;

import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.database.ContentObserver;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.widget.SimpleCursorAdapter;
import android.support.v4.widget.SimpleCursorAdapter.ViewBinder;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import org.openbmap.R;
import org.openbmap.RadioBeacon;
import org.openbmap.db.DataHelper;
import org.openbmap.db.RadioBeaconContentProvider;
import org.openbmap.db.Schema;
import org.openbmap.db.models.Session;
import org.openbmap.utils.ActionModeUtils;
import org.openbmap.utils.ActionModeUtils.LongClickCallback;
import org.openbmap.utils.OnAlertClickInterface;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

/**
 * Fragment for displaying all sessions
 */
public class SessionListFragment extends ListFragment
        implements LoaderCallbacks<Cursor>, LongClickCallback, OnAlertClickInterface {

    private static final String TAG = SessionListFragment.class.getSimpleName();

    /**
     * Dialog id
     */
    private static final int ID_MULTIPLE_UPLOADS = 1;

    private SimpleCursorAdapter mAdapter;

    private boolean mAdapterUpdatePending = true;

    private ContentObserver mObserver;

    /**
     * List action bar commands
     */
    public interface SessionFragementListener {

        void deleteCommand(int id);

        /**
         * Creates a new session.
         */
        void startCommand();

        /**
         * Stops session
         */
        void stopCommand(int id);

        /**
         * Resumes session.
         * @param id
         *      Session to resume
         */
        void resumeCommand(int id);

        /**
         * Deletes all sessions.
         */
        void deleteAllCommand();

        /**
         * Uploads selected session.
         * @param id
         *      Session to upload
         */
        void uploadCommand(int id);

        /**
         * Uploads all sessions not yet uploaded
         */
        void uploadAllCommand();

        /**
         * Exports GPX track of selected session
         * @param id
         */
        void exportGpxCommand(int id);

        void reloadListFragment();
    }

    /**
     * @link http://stackoverflow.com/questions/6317767/cant-add-a-headerview-to-a-listfragment
     *      Fragment lifecycle
     *      onAttach(Activity) called once the fragment is associated with its activity.
     *      onCreate(Bundle) called to do initial creation of the fragment.
     *      onCreateView(LayoutInflater, ViewGroup, Bundle) creates and returns the view hierarchy associated with the fragment.
     *      onActivityCreated(Bundle) tells the fragment that its activity has completed its own Activity.onCreate.
     *      onStart() makes the fragment visible to the user (based on its containing activity being started).
     *      onResume() makes the fragment interacting with the user (based on its containing activity being resumed).
     */

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

        final String[] from = new String[] { Schema.COL_ID, Schema.COL_CREATED_AT, Schema.COL_IS_ACTIVE,
                Schema.COL_HAS_BEEN_EXPORTED, Schema.COL_NUMBER_OF_CELLS, Schema.COL_NUMBER_OF_WIFIS };

        final int[] to = new int[] { R.id.sessionlistfragment_id, R.id.sessionlistfragment_created_at,
                R.id.sessionlistfragment_statusicon, R.id.sessionlistfragment_uploadicon,
                R.id.sessionlistfragment_no_cells, R.id.sessionlistfragment_no_wifis };

        mAdapter = new SimpleCursorAdapter(getActivity().getBaseContext(), R.layout.sessionlistfragment, null, from,
                to, 0);
        mAdapter.setViewBinder(new SessionViewBinder());

        // Trying to add a Header View.
        final View header = (View) getLayoutInflater(savedInstanceState).inflate(R.layout.sessionlistheader, null);
        this.getListView().addHeaderView(header);

        // setup data adapters
        setListAdapter(mAdapter);
        getActivity().getSupportLoaderManager().initLoader(0, null, this);

        // register for change notifications
        mObserver = new ContentObserver(new Handler()) {
            @Override
            public void onChange(final boolean selfChange) {
                refreshAdapter();
            };
        };

        getListView().setLongClickable(true);
        getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
        getListView().setOnItemLongClickListener(new ActionModeUtils((ActionBarActivity) this.getActivity(),
                R.menu.session_context, this, getListView()));
    }

    @Override
    public final void onResume() {
        super.onResume();
        getActivity().getContentResolver().registerContentObserver(RadioBeaconContentProvider.CONTENT_URI_SESSION,
                true, mObserver);
    }

    @Override
    public final void onPause() {
        getActivity().getContentResolver().unregisterContentObserver(mObserver);

        super.onPause();
    }

    @Override
    public final void onDestroy() {
        setListAdapter(null);

        super.onDestroy();
    }

    /**
     * Stops session (and services)
     */
    private void stop(final int id) {
        ((SessionFragementListener) getActivity()).stopCommand(id);
    }

    /**
     * Resumes session, if session hasn't been uploaded yet.
     */
    private void resume(final int id) {
        final DataHelper datahelper = new DataHelper(this.getActivity());
        final Session check = datahelper.loadSession(id);
        if (check != null && !check.hasBeenExported()) {
            ((SessionFragementListener) getActivity()).resumeCommand(id);
        } else {
            Toast.makeText(this.getActivity(), R.string.warning_session_closed, Toast.LENGTH_SHORT).show();
        }
    }

    /*
     * OnListClick resumes corresponding session.
     */
    @Override
    public final void onListItemClick(final ListView lv, final View iv, final int position, final long id) {
        // ignore clicks on list header (position 0)
        if (position != 0) {
            resume((int) id);
        }
    }

    @Override
    public final Loader<Cursor> onCreateLoader(final int arg0, final Bundle arg1) {
        final String[] projection = { Schema.COL_ID, Schema.COL_CREATED_AT, Schema.COL_IS_ACTIVE,
                Schema.COL_HAS_BEEN_EXPORTED, Schema.COL_NUMBER_OF_CELLS, Schema.COL_NUMBER_OF_WIFIS };
        final CursorLoader cursorLoader = new CursorLoader(getActivity().getBaseContext(),
                RadioBeaconContentProvider.CONTENT_URI_SESSION, projection, null, null,
                Schema.COL_CREATED_AT + " DESC");
        return cursorLoader;
    }

    @Override
    public final void onLoadFinished(final Loader<Cursor> arg0, final Cursor cursor) {
        mAdapter.swapCursor(cursor);
        mAdapterUpdatePending = false;
    }

    @Override
    public final void onLoaderReset(final Loader<Cursor> arg0) {
        Log.d(TAG, "onLoadReset called");
        mAdapter.swapCursor(null);
    }

    /**
     * Forces an adapter refresh.
     */
    public final void refreshAdapter() {
        if (!mAdapterUpdatePending) {
            mAdapterUpdatePending = true;
            getActivity().getSupportLoaderManager().restartLoader(0, null, this);
        } else {
            Log.d(TAG, "refreshAdapter skipped. Another update is in progress");
        }
    }

    /**
     * Replaces column values with icons and formats date to human-readable format.
     */
    private static class SessionViewBinder implements ViewBinder {
        public boolean setViewValue(final View view, final Cursor cursor, final int columnIndex) {
            final ImageView imgStatus = (ImageView) view.findViewById(R.id.sessionlistfragment_statusicon);
            final ImageView imgUpload = (ImageView) view.findViewById(R.id.sessionlistfragment_uploadicon);
            final TextView tvCreatedAt = (TextView) view.findViewById(R.id.sessionlistfragment_created_at);
            if (columnIndex == cursor.getColumnIndex(Schema.COL_IS_ACTIVE)) {
                //Log.d(TAG, "Modifying col " + cursor.getColumnIndex(Schema.COL_IS_ACTIVE));
                // symbol for active track
                final int result = cursor.getInt(columnIndex);
                if (result > 0) {
                    // Yellow clock icon for Active
                    imgStatus.setImageResource(android.R.drawable.presence_away);
                    imgStatus.setVisibility(View.VISIBLE);
                } else {
                    imgStatus.setVisibility(View.INVISIBLE);
                }
                return true;
            } else if (columnIndex == cursor.getColumnIndex(Schema.COL_HAS_BEEN_EXPORTED)) {
                // symbol for uploaded tracks
                final int result = cursor.getInt(columnIndex);
                if (result > 0) {
                    // Lock icon for uploaded sessions
                    imgUpload.setImageResource(android.R.drawable.ic_lock_lock);
                    imgUpload.setVisibility(View.VISIBLE);
                } else {
                    imgUpload.setVisibility(View.INVISIBLE);
                }
                return true;
            } else if (columnIndex == cursor.getColumnIndex(Schema.COL_CREATED_AT)) {
                final Date result = new Date(cursor.getLong(columnIndex));
                final SimpleDateFormat date = new SimpleDateFormat("yyyyMMdd", Locale.US);
                tvCreatedAt.setText(date.format(result));
                return true;
            }
            return false;
        }
    }

    /* (non-Javadoc)
     * @see org.openbmap.utils.ActionModeHelper.ItemLongClickCallback#performAction(int, int)
     */
    @Override
    public boolean onItemLongClick(final int item, final int position, final int id) {
        // cancel if underlying item has gone..
        if (id == -1) {
            Log.i(TAG, "Skipping item click - nothing selected");
            return true;
        }

        switch (item) {
        case R.id.menu_upload_session:
            final DataHelper datahelper = new DataHelper(this.getActivity());
            final int pending = datahelper.countPendingExports();
            if (pending > 1) {
                AlertDialogHelper
                        .newInstance(this, ID_MULTIPLE_UPLOADS, R.string.dialog_found_pending_uploads_title,
                                R.string.dialog_found_pending_uploads_message, String.valueOf(id), false)
                        .show(getActivity().getSupportFragmentManager(), "multiple");
            } else {
                stop(id);
                ((SessionFragementListener) getActivity()).uploadCommand(id);
            }
            return true;
        case R.id.menu_export_gpx:
            ((SessionFragementListener) getActivity()).exportGpxCommand(id);
            return true;
        case R.id.menu_delete_session:
            ((SessionFragementListener) getActivity()).deleteCommand(id);
            return true;
        case R.id.menu_delete_all_sessions:
            ((SessionFragementListener) getActivity()).deleteAllCommand();
            return true;
        case R.id.menu_resume_session:
            resume(id);
            return true;
        case R.id.menu_stop_session:
            stop(id);
            return true;
        default:
            break;
        }
        return true;
    }

    public static class AlertDialogHelper extends DialogFragment {

        /**
         * Creates a new alert dialog
         * @param id Alert dialog id
         * @param title Alert title resource
         * @param message Alert message resource
         * @param args opional argument (e.g. session id)
         * @param neutralOnly show only neutral button
         * @return
         */
        public static AlertDialogHelper newInstance(final Fragment container, final int id, final int title,
                final int message, final String args, final boolean neutralOnly) {
            final AlertDialogHelper frag = new AlertDialogHelper();
            frag.setTargetFragment(container, id);
            // Caution: Don't set setRetainInstance(true) explicitly. This will cause the dialog to disappear
            // see http://stackoverflow.com/questions/11307390/dialogfragment-disappears-on-rotation-despite-setretaininstancetrue
            //frag.setRetainInstance(true);
            final Bundle bundle = new Bundle();
            bundle.putInt("dialog_id", id);
            bundle.putInt("title", title);
            bundle.putInt("message", message);
            bundle.putString("args", args);
            bundle.putBoolean("only_neutral", neutralOnly);
            frag.setArguments(bundle);
            return frag;
        }

        @Override
        public Dialog onCreateDialog(final Bundle savedInstanceState) {
            final int dialogId = getArguments().getInt("dialog_id");
            final int title = getArguments().getInt("title");
            final int message = getArguments().getInt("message");
            final String args = getArguments().getString("args");
            final boolean neutralOnly = getArguments().getBoolean("only_neutral");

            final Builder dialog = new AlertDialog.Builder(getActivity())
                    .setIcon(android.R.drawable.ic_dialog_alert).setTitle(title).setMessage(message)
                    .setCancelable(false);

            if (neutralOnly) {
                dialog.setNeutralButton(android.R.string.ok, new OnClickListener() {
                    @Override
                    public void onClick(final DialogInterface alert, final int which) {
                        ((OnAlertClickInterface) getTargetFragment()).onAlertNeutralClick(dialogId, args);
                    }
                });
            } else {
                dialog.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
                    public void onClick(final DialogInterface dialog, final int whichButton) {
                        ((OnAlertClickInterface) getTargetFragment()).onAlertPositiveClick(dialogId, args);
                    }
                }).setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
                    public void onClick(final DialogInterface dialog, final int whichButton) {
                        ((OnAlertClickInterface) getTargetFragment()).onAlertNegativeClick(dialogId, args);
                    }
                });
            }
            return dialog.create();
        }
    }

    /* (non-Javadoc)
     * @see org.openbmap.utils.OnAlertClickInterface#onAlertPositiveClick(int, java.lang.String)
     */
    @Override
    public void onAlertPositiveClick(final int alertId, final String args) {
        if (alertId == ID_MULTIPLE_UPLOADS) {
            // just all pending
            final int id = (args != null ? Integer.valueOf(args) : RadioBeacon.SESSION_NOT_TRACKING);
            stop(id);
            ((SessionFragementListener) getActivity()).uploadAllCommand();
        }

    }

    /* (non-Javadoc)
     * @see org.openbmap.utils.OnAlertClickInterface#onAlertNegativeClick(int, java.lang.String)
     */
    @Override
    public void onAlertNegativeClick(final int alertId, final String args) {
        if (alertId == ID_MULTIPLE_UPLOADS) {
            // just upload selected
            final int id = (args != null ? Integer.valueOf(args) : RadioBeacon.SESSION_NOT_TRACKING);
            stop(id);
            ((SessionFragementListener) getActivity()).uploadCommand(id);
        }

    }

    /* (non-Javadoc)
     * @see org.openbmap.utils.OnAlertClickInterface#onAlertNeutralClick(int, java.lang.String)
     */
    @Override
    public void onAlertNeutralClick(final int alertId, final String args) {
        // TODO Auto-generated method stub

    }
}