org.runnerup.view.DetailActivity.java Source code

Java tutorial

Introduction

Here is the source code for org.runnerup.view.DetailActivity.java

Source

/*
 * Copyright (C) 2012 - 2013 jonas.oreland@gmail.com
 *
 *  This program 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.
 *
 *  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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.runnerup.view;

import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Color;
import android.location.Location;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.FragmentActivity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.widget.ListView;
import android.widget.TabHost;
import android.widget.TabHost.TabSpec;
import android.widget.TextView;

import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMap.OnCameraChangeListener;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.maps.model.PolylineOptions;
import com.jjoe64.graphview.GraphView;
import com.jjoe64.graphview.GraphView.GraphViewData;
import com.jjoe64.graphview.GraphViewSeries;
import com.jjoe64.graphview.LineGraphView;

import org.runnerup.R;
import org.runnerup.content.ActivityProvider;
import org.runnerup.content.WorkoutFileProvider;
import org.runnerup.db.ActivityCleaner;
import org.runnerup.db.DBHelper;
import org.runnerup.export.UploadManager;
import org.runnerup.export.Uploader;
import org.runnerup.export.Uploader.Feature;
import org.runnerup.util.Bitfield;
import org.runnerup.common.util.Constants;
import org.runnerup.util.Formatter;
import org.runnerup.util.HRZones;
import org.runnerup.widget.TitleSpinner;
import org.runnerup.widget.WidgetUtil;
import org.runnerup.workout.Intensity;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;

@TargetApi(Build.VERSION_CODES.FROYO)
public class DetailActivity extends FragmentActivity implements Constants {

    long mID = 0;
    DBHelper mDBHelper = null;
    SQLiteDatabase mDB = null;
    final HashSet<String> pendingUploaders = new HashSet<String>();
    final HashSet<String> alreadyUploadedUploaders = new HashSet<String>();

    boolean lapHrPresent = false;
    ContentValues laps[] = null;
    final ArrayList<ContentValues> reports = new ArrayList<ContentValues>();
    final ArrayList<BaseAdapter> adapters = new ArrayList<BaseAdapter>(2);

    int mode; // 0 == save 1 == details
    final static int MODE_SAVE = 0;
    final static int MODE_DETAILS = 1;
    boolean edit = false;
    boolean uploading = false;

    Button saveButton = null;
    Button discardButton = null;
    Button uploadButton = null;
    Button resumeButton = null;
    TextView activityTime = null;
    TextView activityPace = null;
    TextView activityDistance = null;

    TitleSpinner sport = null;
    EditText notes = null;
    MenuItem recomputeMenuItem = null;

    View mapViewLayout = null;
    GoogleMap map = null;
    View mapView = null;
    LatLngBounds mapBounds = null;
    AsyncTask<String, String, Route> loadRouteTask = null;
    LinearLayout graphTab = null;
    GraphView graphView;
    GraphView graphView2;

    LinearLayout hrzonesBarLayout;
    HRZonesBar hrzonesBar;

    UploadManager uploadManager = null;
    Formatter formatter = null;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.detail);
        WidgetUtil.addLegacyOverflowButton(getWindow());

        Intent intent = getIntent();
        mID = intent.getLongExtra("ID", -1);
        String mode = intent.getStringExtra("mode");

        mDBHelper = new DBHelper(this);
        mDB = mDBHelper.getReadableDatabase();
        uploadManager = new UploadManager(this);
        formatter = new Formatter(this);

        if (mode.contentEquals("save")) {
            this.mode = MODE_SAVE;
        } else if (mode.contentEquals("details")) {
            this.mode = MODE_DETAILS;
        } else {
            assert (false);
        }

        saveButton = (Button) findViewById(R.id.save_button);
        discardButton = (Button) findViewById(R.id.discard_button);
        resumeButton = (Button) findViewById(R.id.resume_button);
        uploadButton = (Button) findViewById(R.id.upload_button);
        activityTime = (TextView) findViewById(R.id.activity_time);
        activityDistance = (TextView) findViewById(R.id.activity_distance);
        activityPace = (TextView) findViewById(R.id.activity_pace);
        sport = (TitleSpinner) findViewById(R.id.summary_sport);
        notes = (EditText) findViewById(R.id.notes_text);
        notes.setHint("Notes about your workout");
        map = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map)).getMap();

        if (map != null) {
            map.setOnCameraChangeListener(new OnCameraChangeListener() {

                @Override
                public void onCameraChange(CameraPosition arg0) {
                    if (mapBounds != null) {
                        // Move camera.
                        map.moveCamera(CameraUpdateFactory.newLatLngBounds(mapBounds, 5));
                        // Remove listener to prevent position reset on camera
                        // move.
                        map.setOnCameraChangeListener(null);
                    }
                }
            });
        }
        saveButton.setOnClickListener(saveButtonClick);
        uploadButton.setOnClickListener(uploadButtonClick);
        if (this.mode == MODE_SAVE) {
            resumeButton.setOnClickListener(resumeButtonClick);
            discardButton.setOnClickListener(discardButtonClick);
            setEdit(true);
        } else if (this.mode == MODE_DETAILS) {
            resumeButton.setVisibility(View.GONE);
            discardButton.setVisibility(View.GONE);
            setEdit(false);
        }
        uploadButton.setVisibility(View.GONE);

        fillHeaderData();
        requery();

        loadRoute();

        TabHost th = (TabHost) findViewById(R.id.tabhost);
        th.setup();
        TabSpec tabSpec = th.newTabSpec("notes");
        tabSpec.setIndicator(WidgetUtil.createHoloTabIndicator(this, "Notes"));
        tabSpec.setContent(R.id.tab_main);
        th.addTab(tabSpec);

        tabSpec = th.newTabSpec("laps");
        tabSpec.setIndicator(WidgetUtil.createHoloTabIndicator(this, "Laps"));
        tabSpec.setContent(R.id.tab_lap);
        th.addTab(tabSpec);

        tabSpec = th.newTabSpec("map");
        tabSpec.setIndicator(WidgetUtil.createHoloTabIndicator(this, "Map"));
        tabSpec.setContent(R.id.tab_map);
        th.addTab(tabSpec);

        tabSpec = th.newTabSpec("graph");
        tabSpec.setIndicator(WidgetUtil.createHoloTabIndicator(this, "Graph"));
        tabSpec.setContent(R.id.tab_graph);
        th.addTab(tabSpec);

        tabSpec = th.newTabSpec("share");
        tabSpec.setIndicator(WidgetUtil.createHoloTabIndicator(this, "Upload"));
        tabSpec.setContent(R.id.tab_upload);
        th.addTab(tabSpec);

        th.getTabWidget().setBackgroundColor(Color.DKGRAY);

        {
            ListView lv = (ListView) findViewById(R.id.laplist);
            LapListAdapter adapter = new LapListAdapter();
            adapters.add(adapter);
            lv.setAdapter(adapter);
        }
        {
            ListView lv = (ListView) findViewById(R.id.report_list);
            ReportListAdapter adapter = new ReportListAdapter();
            adapters.add(adapter);
            lv.setAdapter(adapter);
        }
        graphTab = (LinearLayout) findViewById(R.id.tab_graph);
        {
            graphView = new LineGraphView(this, "Pace") {
                @Override
                protected String formatLabel(double value, boolean isValueX) {
                    if (!isValueX) {
                        return formatter.formatPace(Formatter.TXT_SHORT, value);
                    } else
                        return formatter.formatDistance(Formatter.TXT_SHORT, (long) value);
                }
            };

            graphView2 = new LineGraphView(this, "HRM") {
                @Override
                protected String formatLabel(double value, boolean isValueX) {
                    if (!isValueX) {
                        return Integer.toString((int) Math.round(value));
                    } else {
                        return formatter.formatDistance(Formatter.TXT_SHORT, (long) value);
                    }
                }
            };
        }
        hrzonesBarLayout = (LinearLayout) findViewById(R.id.hrzonesBarLayout);
        hrzonesBar = new HRZonesBar(this);
    }

    private void setEdit(boolean value) {
        edit = value;
        saveButton.setEnabled(value);
        uploadButton.setEnabled(value);
        WidgetUtil.setEditable(notes, value);
        sport.setEnabled(value);
        if (recomputeMenuItem != null)
            recomputeMenuItem.setEnabled(value);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        if (mode == MODE_DETAILS) {
            getMenuInflater().inflate(R.menu.detail_menu, menu);
            recomputeMenuItem = menu.findItem(R.id.menu_recompute_activity);
        }
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.menu_delete_activity:
            deleteButtonClick.onClick(null);
            break;
        case R.id.menu_edit_activity:
            if (edit == false) {
                setEdit(true);
                notes.requestFocus();
                requery();
            }
            break;
        case R.id.menu_recompute_activity:
            new ActivityCleaner().recompute(mDB, mID);
            requery();
            break;
        case R.id.menu_share_activity:
            shareActivity();
            break;
        }
        return true;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mDB.close();
        mDBHelper.close();
        uploadManager.close();
    }

    void requery() {
        {
            /**
             * Laps
             */
            String[] from = new String[] { "_id", DB.LAP.LAP, DB.LAP.INTENSITY, DB.LAP.TIME, DB.LAP.DISTANCE,
                    DB.LAP.PLANNED_TIME, DB.LAP.PLANNED_DISTANCE, DB.LAP.PLANNED_PACE, DB.LAP.AVG_HR };

            Cursor c = mDB.query(DB.LAP.TABLE, from, DB.LAP.ACTIVITY + " == " + mID, null, null, null, "_id", null);

            laps = DBHelper.toArray(c);
            c.close();
            lapHrPresent = false;
            for (ContentValues v : laps) {
                if (v.containsKey(DB.LAP.AVG_HR) && v.getAsInteger(DB.LAP.AVG_HR) > 0) {
                    lapHrPresent = true;
                    break;
                }
            }
        }

        {
            /**
             * Accounts/reports
             */
            String sql = new String("SELECT DISTINCT " + "  acc._id, " // 0
                    + ("  acc." + DB.ACCOUNT.NAME + ", ") + ("  acc." + DB.ACCOUNT.DESCRIPTION + ", ")
                    + ("  acc." + DB.ACCOUNT.FLAGS + ", ") + ("  acc." + DB.ACCOUNT.AUTH_METHOD + ", ")
                    + ("  acc." + DB.ACCOUNT.AUTH_CONFIG + ", ") + ("  acc." + DB.ACCOUNT.ENABLED + ", ")
                    + ("  rep._id as repid, ") + ("  rep." + DB.EXPORT.ACCOUNT + ", ")
                    + ("  rep." + DB.EXPORT.ACTIVITY + ", ") + ("  rep." + DB.EXPORT.STATUS)
                    + (" FROM " + DB.ACCOUNT.TABLE + " acc ") + (" LEFT OUTER JOIN " + DB.EXPORT.TABLE + " rep ")
                    + (" ON ( acc._id = rep." + DB.EXPORT.ACCOUNT)
                    + ("     AND rep." + DB.EXPORT.ACTIVITY + " = " + mID + " )")
                    + (" WHERE acc." + DB.ACCOUNT.ENABLED + " != 0 ")
                    + ("   AND acc." + DB.ACCOUNT.AUTH_CONFIG + " is not null"));

            Cursor c = mDB.rawQuery(sql, null);
            alreadyUploadedUploaders.clear();
            pendingUploaders.clear();
            reports.clear();
            if (c.moveToFirst()) {
                do {
                    ContentValues tmp = DBHelper.get(c);
                    Uploader uploader = uploadManager.add(tmp);
                    if (!uploader.checkSupport(Feature.UPLOAD)) {
                        continue;
                    }

                    reports.add(tmp);
                    if (tmp.containsKey("repid")) {
                        alreadyUploadedUploaders.add(tmp.getAsString(DB.ACCOUNT.NAME));
                    } else if (tmp.containsKey(DB.ACCOUNT.FLAGS)
                            && Bitfield.test(tmp.getAsLong(DB.ACCOUNT.FLAGS), DB.ACCOUNT.FLAG_UPLOAD)) {
                        pendingUploaders.add(tmp.getAsString(DB.ACCOUNT.NAME));
                    }
                } while (c.moveToNext());
            }
            c.close();
        }

        if (mode == MODE_DETAILS) {
            if (pendingUploaders.isEmpty()) {
                uploadButton.setVisibility(View.GONE);
            } else {
                uploadButton.setVisibility(View.VISIBLE);
            }
        }

        for (BaseAdapter a : adapters) {
            a.notifyDataSetChanged();
        }
    }

    void fillHeaderData() {
        // Fields from the database (projection)
        // Must include the _id column for the adapter to work
        String[] from = new String[] { DB.ACTIVITY.START_TIME, DB.ACTIVITY.DISTANCE, DB.ACTIVITY.TIME,
                DB.ACTIVITY.COMMENT, DB.ACTIVITY.SPORT };

        Cursor c = mDB.query(DB.ACTIVITY.TABLE, from, "_id == " + mID, null, null, null, null, null);
        c.moveToFirst();
        ContentValues tmp = DBHelper.get(c);
        c.close();

        long st = 0;
        if (tmp.containsKey(DB.ACTIVITY.START_TIME)) {
            st = tmp.getAsLong(DB.ACTIVITY.START_TIME);
            setTitle("RunnerUp - " + formatter.formatDateTime(Formatter.TXT_LONG, st));
        }
        float d = 0;
        if (tmp.containsKey(DB.ACTIVITY.DISTANCE)) {
            d = tmp.getAsFloat(DB.ACTIVITY.DISTANCE);
            activityDistance.setText(formatter.formatDistance(Formatter.TXT_LONG, (long) d));
        } else {
            activityDistance.setText("");
        }

        float t = 0;
        if (tmp.containsKey(DB.ACTIVITY.TIME)) {
            t = tmp.getAsFloat(DB.ACTIVITY.TIME);
            activityTime.setText(formatter.formatElapsedTime(Formatter.TXT_SHORT, (long) t));
        } else {
            activityTime.setText("");
        }

        if (d != 0 && t != 0) {
            activityPace.setText(formatter.formatPace(Formatter.TXT_LONG, t / d));
        } else {
            activityPace.setText("");
        }

        if (tmp.containsKey(DB.ACTIVITY.COMMENT)) {
            notes.setText(tmp.getAsString(DB.ACTIVITY.COMMENT));
        }

        if (tmp.containsKey(DB.ACTIVITY.SPORT)) {
            sport.setValue(tmp.getAsInteger(DB.ACTIVITY.SPORT));
        }
    }

    class LapListAdapter extends BaseAdapter {

        @Override
        public int getCount() {
            return laps.length;
        }

        @Override
        public Object getItem(int position) {
            return laps[position];
        }

        @Override
        public long getItemId(int position) {
            return laps[position].getAsLong("_id");
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            LayoutInflater inflater = LayoutInflater.from(DetailActivity.this);
            View view = inflater.inflate(R.layout.laplist_row, parent, false);
            TextView tv0 = (TextView) view.findViewById(R.id.lap_list_type);
            int i = laps[position].getAsInteger(DB.LAP.INTENSITY);
            Intensity intensity = Intensity.values()[i];
            switch (intensity) {
            case ACTIVE:
                tv0.setText("");
                break;
            case COOLDOWN:
            case RESTING:
            case RECOVERY:
            case WARMUP:
            case REPEAT:
                tv0.setText("(" + getResources().getString(intensity.getTextId()) + ")");
            default:
                break;

            }
            TextView tv1 = (TextView) view.findViewById(R.id.lap_list_id);
            tv1.setText(laps[position].getAsString("_id"));
            TextView tv2 = (TextView) view.findViewById(R.id.lap_list_distance);
            float d = laps[position].containsKey(DB.LAP.DISTANCE) ? laps[position].getAsFloat(DB.LAP.DISTANCE) : 0;
            tv2.setText(formatter.formatDistance(Formatter.TXT_LONG, (long) d));
            TextView tv3 = (TextView) view.findViewById(R.id.lap_list_time);
            long t = laps[position].containsKey(DB.LAP.TIME) ? laps[position].getAsLong(DB.LAP.TIME) : 0;
            tv3.setText(formatter.formatElapsedTime(Formatter.TXT_SHORT, t));
            TextView tv4 = (TextView) view.findViewById(R.id.lap_list_pace);
            if (t != 0 && d != 0) {
                tv4.setText(formatter.formatPace(Formatter.TXT_LONG, t / d));
            } else {
                tv4.setText("");
            }
            int hr = laps[position].containsKey(DB.LAP.AVG_HR) ? laps[position].getAsInteger(DB.LAP.AVG_HR) : 0;
            TextView tvHr = (TextView) view.findViewById(R.id.lap_list_hr);
            if (hr > 0) {
                tvHr.setVisibility(View.VISIBLE);
                tvHr.setText(formatter.formatHeartRate(Formatter.TXT_LONG, hr) + " bpm");
            } else if (lapHrPresent) {
                tvHr.setVisibility(View.INVISIBLE);
            } else {
                tvHr.setVisibility(View.GONE);
            }

            return view;
        }
    }

    class ReportListAdapter extends BaseAdapter {

        @Override
        public int getCount() {
            return reports.size() + 1;
        }

        @Override
        public Object getItem(int position) {
            if (position < reports.size())
                return reports.get(position);
            return this;
        }

        @Override
        public long getItemId(int position) {
            if (position < reports.size())
                return reports.get(position).getAsLong("_id");

            return 0;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            if (position == reports.size()) {
                Button b = new Button(DetailActivity.this);
                b.setText(getString(R.string.configure_accounts));
                b.setBackgroundResource(R.drawable.btn_blue);
                b.setTextColor(getResources().getColorStateList(R.color.btn_text_color));
                b.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Intent i = new Intent(DetailActivity.this, AccountListActivity.class);
                        DetailActivity.this.startActivityForResult(i, UploadManager.CONFIGURE_REQUEST + 1);
                    }
                });
                return b;
            }

            LayoutInflater inflater = LayoutInflater.from(DetailActivity.this);
            View view = inflater.inflate(R.layout.reportlist_row, parent, false);

            TextView tv0 = (TextView) view.findViewById(R.id.account_id);
            CheckBox cb = (CheckBox) view.findViewById(R.id.report_sent);
            TextView tv1 = (TextView) view.findViewById(R.id.account_name);

            ContentValues tmp = reports.get(position);

            String name = tmp.getAsString("name");
            cb.setTag(name);
            if (alreadyUploadedUploaders.contains(name)) {
                cb.setChecked(true);
                cb.setText(getString(R.string.uploaded));
                if (edit) {
                    cb.setOnLongClickListener(clearUploadClick);
                } else {
                    cb.setEnabled(false);
                }
            } else if ((tmp.containsKey(DB.ACCOUNT.FLAGS)
                    && Bitfield.test(tmp.getAsLong(DB.ACCOUNT.FLAGS), DB.ACCOUNT.FLAG_UPLOAD))
                    || pendingUploaders.contains(name)) {
                cb.setChecked(true);
            } else {
                cb.setChecked(false);
            }
            if (mode == MODE_DETAILS && !alreadyUploadedUploaders.contains(name)) {
                cb.setEnabled(edit);
            }
            cb.setOnCheckedChangeListener(onSendChecked);

            tv0.setText(tmp.getAsString("_id"));
            tv1.setText(name);
            return view;
        }
    }

    void saveActivity() {
        ContentValues tmp = new ContentValues();
        tmp.put(DB.ACTIVITY.COMMENT, notes.getText().toString());
        tmp.put(DB.ACTIVITY.SPORT, sport.getValueInt());
        String whereArgs[] = { Long.toString(mID) };
        mDB.update(DB.ACTIVITY.TABLE, tmp, "_id = ?", whereArgs);
    }

    final OnLongClickListener clearUploadClick = new OnLongClickListener() {

        @Override
        public boolean onLongClick(View arg0) {
            final String name = (String) arg0.getTag();
            AlertDialog.Builder builder = new AlertDialog.Builder(DetailActivity.this);
            builder.setTitle("Clear upload for " + name);
            builder.setMessage(getString(R.string.are_you_sure));
            builder.setPositiveButton(getString(R.string.yes), new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    dialog.dismiss();
                    uploadManager.clearUpload(name, mID);
                    requery();
                }
            });
            builder.setNegativeButton(getString(R.string.no), new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    // Do nothing but close the dialog
                    dialog.dismiss();
                }

            });
            builder.show();
            return false;
        }

    };

    final OnClickListener saveButtonClick = new OnClickListener() {
        public void onClick(View v) {
            saveActivity();
            if (mode == MODE_DETAILS) {
                setEdit(false);
                requery();
                return;
            }
            uploading = true;
            uploadManager.startUploading(new UploadManager.Callback() {
                @Override
                public void run(String uploader, Uploader.Status status) {
                    uploading = false;
                    DetailActivity.this.setResult(RESULT_OK);
                    DetailActivity.this.finish();
                }
            }, pendingUploaders, mID);
        }
    };

    final OnClickListener discardButtonClick = new OnClickListener() {
        public void onClick(View v) {
            AlertDialog.Builder builder = new AlertDialog.Builder(DetailActivity.this);
            builder.setTitle(getString(R.string.discard_activity));
            builder.setMessage(getString(R.string.are_you_sure));
            builder.setPositiveButton(getString(R.string.yes), new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    dialog.dismiss();
                    DetailActivity.this.setResult(RESULT_CANCELED);
                    DetailActivity.this.finish();
                }
            });
            builder.setNegativeButton(getString(R.string.no), new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    // Do nothing but close the dialog
                    dialog.dismiss();
                }

            });
            builder.show();
        }
    };

    @Override
    public void onBackPressed() {
        if (uploading == true) {
            /**
             * Ignore while uploading
             */
            return;
        }
        if (mode == MODE_SAVE) {
            resumeButtonClick.onClick(resumeButton);
        } else {
            super.onBackPressed();
        }
    }

    final OnClickListener resumeButtonClick = new OnClickListener() {
        public void onClick(View v) {
            DetailActivity.this.setResult(RESULT_FIRST_USER);
            DetailActivity.this.finish();
        }
    };

    final OnClickListener uploadButtonClick = new OnClickListener() {
        public void onClick(View v) {
            uploading = true;
            uploadManager.startUploading(new UploadManager.Callback() {
                @Override
                public void run(String uploader, Uploader.Status status) {
                    uploading = false;
                    requery();
                }
            }, pendingUploaders, mID);
        }
    };

    public final OnCheckedChangeListener onSendChecked = new OnCheckedChangeListener() {

        @Override
        public void onCheckedChanged(CompoundButton arg0, boolean arg1) {
            final String name = (String) arg0.getTag();
            if (alreadyUploadedUploaders.contains(name)) {
                // Only accept long clicks
                arg0.setChecked(true);
            } else {
                if (arg1 == true) {
                    pendingUploaders.add((String) arg0.getTag());
                } else {
                    pendingUploaders.remove((String) arg0.getTag());
                }

                if (mode == MODE_DETAILS) {
                    if (pendingUploaders.isEmpty())
                        uploadButton.setVisibility(View.GONE);
                    else
                        uploadButton.setVisibility(View.VISIBLE);
                }
            }
        }
    };

    final OnClickListener deleteButtonClick = new OnClickListener() {
        public void onClick(View v) {
            AlertDialog.Builder builder = new AlertDialog.Builder(DetailActivity.this);
            builder.setTitle(getString(R.string.delete_activity));
            builder.setMessage(getString(R.string.are_you_sure));
            builder.setPositiveButton(getString(R.string.yes), new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    DBHelper.deleteActivity(mDB, mID);
                    dialog.dismiss();
                    DetailActivity.this.setResult(RESULT_OK);
                    DetailActivity.this.finish();
                }
            });
            builder.setNegativeButton(getString(R.string.no), new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    // Do nothing but close the dialog
                    dialog.dismiss();
                }

            });
            builder.show();
        }
    };

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == UploadManager.CONFIGURE_REQUEST) {
            uploadManager.onActivityResult(requestCode, resultCode, data);
        }
        requery();
    }

    public static int position(String arr[], String key) {
        for (int i = 0; i < arr.length; i++) {
            if (arr[i].contentEquals(key))
                return i;
        }
        return -1;
    }

    class Route {
        final PolylineOptions path = new PolylineOptions();
        final LatLngBounds.Builder bounds = new LatLngBounds.Builder();
        final ArrayList<MarkerOptions> markers = new ArrayList<MarkerOptions>(10);

        Route() {
            path.color(Color.RED);
            path.width(3);
        }
    }

    class GraphProducer {
        static final int GRAPH_INTERVAL_SECONDS = 5; // 1 point every 5 sec
        static final int GRAPH_AVERAGE_SECONDS = 30; // moving average 30 sec

        final int interval;
        int pos = 0;
        double time[] = null;
        double distance[] = null;
        double sum_time = 0;
        double sum_distance = 0;
        double acc_time = 0;

        int[] hr = null;
        double[] hrzHist = null;

        double tot_avg_hr = 0;
        int min_hr = Integer.MAX_VALUE;
        int max_hr = 0;

        double avg_pace = 0;
        double min_pace = Double.MAX_VALUE;
        double max_pace = Double.MIN_VALUE;
        List<GraphViewData> paceList = null;
        List<GraphViewData> hrList = null;

        boolean showHR = false;
        boolean showHRZhist = false;
        HRZones hrCalc = null;

        GraphProducer() {
            this(GRAPH_INTERVAL_SECONDS, GRAPH_AVERAGE_SECONDS);
        }

        public GraphProducer(int graphIntervalSeconds, int graphAverageSeconds) {
            this.paceList = new ArrayList<GraphViewData>();
            this.interval = graphIntervalSeconds;
            this.time = new double[graphAverageSeconds];
            this.distance = new double[graphAverageSeconds];

            this.hrList = new ArrayList<GraphViewData>();
            this.hr = new int[graphAverageSeconds];

            Resources res = getResources();
            Context ctx = getApplicationContext();
            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
            int zone = prefs.getInt(res.getString(R.string.pref_basic_target_hrz), 0);
            if (zone > 0) {
                this.hrCalc = new HRZones(res, prefs);
                this.hrzHist = new double[hrCalc.getCount() + 1];
                for (int i = 0; i < this.hrzHist.length; i++) {
                    this.hrzHist[i] = 0;
                }
                showHRZhist = true;
            }

            clear(0);
        }

        void clear(double tot_distance) {
            if (pos >= (this.time.length / 2) && (acc_time >= 1000 * (interval / 2)) && sum_distance > 0) {
                emit(tot_distance);
            }

            for (int i = 0; i < this.distance.length; i++) {
                time[i] = 0;
                distance[i] = 0;
                hr[i] = 0;
            }
            pos = 0;
            sum_time = 0;
            sum_distance = 0;
            acc_time = 0;
        }

        void addObservation(double delta_time, double delta_distance, double tot_distance, int hr) {
            if (delta_time < 500)
                return;
            if (hr > 0) {
                showHR = true;
            }

            int p = pos % this.time.length;
            sum_time -= this.time[p];
            sum_distance -= this.distance[p];
            sum_time += delta_time;
            sum_distance += delta_distance;

            this.time[p] = delta_time;
            this.distance[p] = delta_distance;
            this.hr[p] = hr;
            pos = (pos + 1);

            if (showHRZhist) {
                this.hrzHist[hrCalc.getZoneInt(hr)] += delta_time;
            }

            acc_time += delta_time;

            if (pos >= this.time.length && (acc_time >= 1000 * interval) && sum_distance > 0) {
                emit(tot_distance);
            }
        }

        void emit(double tot_distance) {
            double avg_time = sum_time;
            double avg_dist = sum_distance;
            double avg_hr = calculateAverage(hr);
            if (true) {
                // remove max/min pace to (maybe) get smoother graph
                double max_pace[] = { 0, 0, 0 };
                double min_pace[] = { Double.MAX_VALUE, 0, 0 };
                for (int i = 0; i < this.time.length; i++) {
                    double pace = this.time[i] / this.distance[i];
                    if (pace > max_pace[0]) {
                        max_pace[0] = pace;
                        max_pace[1] = this.time[i];
                        max_pace[2] = this.distance[i];
                    }
                    if (pace < min_pace[0]) {
                        min_pace[0] = pace;
                        min_pace[1] = this.time[i];
                        min_pace[2] = this.distance[i];
                    }
                }
                avg_time -= (max_pace[1] + min_pace[1]);
                avg_dist -= (max_pace[2] + min_pace[2]);
            }
            if (avg_dist > 0) {
                double pace = avg_time / avg_dist / 1000.0;
                paceList.add(new GraphViewData(tot_distance, pace));
                hrList.add(new GraphViewData(tot_distance, Math.round(avg_hr)));
                acc_time = 0;

                tot_avg_hr += avg_hr;
                avg_pace += pace;
                min_pace = Math.min(min_pace, pace);
                max_pace = Math.max(max_pace, pace);
            }
        }

        class GraphFilter {

            double data[] = null;
            final List<GraphViewData> source;

            GraphFilter(List<GraphViewData> paceList) {
                source = paceList;
                data = new double[paceList.size()];
                for (int i = 0; i < paceList.size(); i++)
                    data[i] = paceList.get(i).valueY;
            }

            void complete() {
                for (int i = 0; i < source.size(); i++)
                    source.set(i, new GraphViewData(source.get(i).valueX, data[i]));
            }

            void init(double window[], double val) {
                for (int j = 0; j < window.length - 1; j++)
                    window[j] = val;
            }

            void shiftLeft(double window[], double newVal) {
                for (int j = 0; j < window.length - 1; j++)
                    window[j] = window[j + 1];
                window[window.length - 1] = newVal;
            }

            /**
             * Perform in place moving average
             */
            void movingAvergage(int windowLen) {
                double window[] = new double[windowLen];
                init(window, data[0]);

                final int mid = (window.length - 1) / 2;
                final int last = window.length - 1;
                for (int i = 0; i < data.length && i <= mid; i++) {
                    window[i + mid] = data[i];
                }

                double sum = 0;
                for (double aWindow : window)
                    sum += aWindow;

                for (int i = 0; i < data.length; i++) {
                    double newY = sum / windowLen;
                    data[i] = newY;
                    sum -= window[0];
                    shiftLeft(window, (i + mid) < data.length ? data[i + mid] : avg_pace);
                    sum += window[last];
                }
            }

            /**
             * Perform in place moving average
             */
            void movingMedian(int windowLen) {
                double window[] = new double[windowLen];
                init(window, data[0]);

                final int mid = (window.length - 1) / 2;
                for (int i = 0; i < data.length && i <= mid; i++) {
                    window[i + mid] = data[i];
                }

                double sort[] = new double[windowLen];
                for (int i = 0; i < data.length; i++) {
                    System.arraycopy(window, 0, sort, 0, windowLen);
                    Arrays.sort(sort);
                    data[i] = sort[mid];
                    shiftLeft(window, (i + mid) < data.length ? data[i + mid] : avg_pace);
                }
            }

            /**
             * Perform in place SavitzkyGolay windowLen = 5
             */
            void SavitzkyGolay5() {
                final int len = 5;
                double window[] = new double[len];
                init(window, data[0]);

                final int mid = (window.length - 1) / 2;
                for (int i = 0; i < data.length && i <= mid; i++) {
                    window[i + mid] = data[i];
                }
                for (int i = 0; i < data.length; i++) {
                    double newY = (-3 * window[0] + 12 * window[1] + 17 * window[2] + 12 * window[3]
                            - 3 * window[4]) / 35;
                    data[i] = newY;
                    shiftLeft(window, (i + mid) < data.length ? data[i + mid] : avg_pace);
                }
            }

            /**
             * Perform in place SavitzkyGolay windowLen = 7
             */
            void SavitzkyGolay7() {
                final int len = 7;
                double window[] = new double[len];
                init(window, data[0]);

                final int mid = (window.length - 1) / 2;
                for (int i = 0; i < data.length && i <= mid; i++) {
                    window[i + mid] = data[i];
                }
                for (int i = 0; i < data.length; i++) {
                    double newY = (-2 * window[0] + 3 * window[1] + 6 * window[2] + 7 * window[3] + 6 * window[4]
                            + 3 * window[5] - 2 * window[6]) / 21;
                    data[i] = newY;
                    shiftLeft(window, (i + mid) < data.length ? data[i + mid] : avg_pace);
                }
            }

            void KolmogorovZurbenko(int n, int len) {
                for (int i = 0; i < n; i++)
                    movingAvergage(len);
            }
        }

        public void complete(GraphView graphView) {
            avg_pace /= paceList.size();
            System.err.println("graph: " + paceList.size() + " points");

            boolean smoothData = PreferenceManager.getDefaultSharedPreferences(DetailActivity.this)
                    .getBoolean(getResources().getString(R.string.pref_pace_graph_smoothing), true);
            if (paceList.size() > 0 && smoothData) {
                GraphFilter f = new GraphFilter(paceList);
                final String defaultFilterList = "mm(31);kz(5,13);sg(5)";
                final String filterList = PreferenceManager.getDefaultSharedPreferences(DetailActivity.this)
                        .getString(getResources().getString(R.string.pref_pace_graph_smoothing_filters),
                                defaultFilterList);
                final String filters[] = filterList.split(";");
                System.err.print("Applying filters(" + filters.length + ", >" + filterList + "<):");
                for (String filter : filters) {
                    int args[] = getArgs(filter);
                    if (filter.startsWith("mm")) {
                        if (args.length == 1) {
                            f.movingMedian(args[0]);
                            System.err.print(" mm(" + args[0] + ")");
                        }
                    } else if (filter.startsWith("ma")) {
                        if (args.length == 1) {
                            f.movingAvergage(args[0]);
                            System.err.print(" ma(" + args[0] + ")");
                        }
                    } else if (filter.startsWith("kz")) {
                        if (args.length == 2) {
                            f.KolmogorovZurbenko(args[0], args[1]);
                            System.err.print(" kz(" + args[0] + "," + args[1] + ")");
                        }
                    } else if (filter.startsWith("sg")) {
                        if (args.length == 1 && args[0] == 5) {
                            f.SavitzkyGolay5();
                            System.err.print(" sg(5)");
                        } else if (args.length == 1 && args[0] == 7) {
                            f.SavitzkyGolay7();
                            System.err.print(" sg(7)");
                        }
                    }
                }
                System.err.println("");
                f.complete();
            }
            GraphViewSeries graphViewData = new GraphViewSeries(
                    paceList.toArray(new GraphViewData[paceList.size()]));
            graphView.addSeries(graphViewData); // data
            graphView.redrawAll();
            if (showHR) {
                GraphViewSeries graphViewData2 = new GraphViewSeries(
                        hrList.toArray(new GraphViewData[hrList.size()]));
                graphView2.addSeries(graphViewData2); // data

                if (showHRZhist) {
                    System.err.print("HR Zones:");
                    double sum = 0;
                    for (double aHrzHist : hrzHist) {
                        sum += aHrzHist;
                    }
                    for (int i = 0; i < hrzHist.length; i++) {
                        hrzHist[i] = hrzHist[i] / sum;
                        System.err.print(" " + hrzHist[i]);
                    }
                    System.err.println("\n");
                    hrzonesBar.pushHrzData(hrzHist);
                }
            }
        }

        private int[] getArgs(String s) {
            try {
                s = s.substring(s.indexOf('(') + 1);
                s = s.substring(0, s.indexOf(')'));
                String sargs[] = s.split(",");
                int args[] = new int[sargs.length];
                for (int i = 0; i < args.length; i++) {
                    args[i] = Integer.parseInt(sargs[i]);
                }
                return args;
            } catch (Exception e) {
                e.printStackTrace();
                return new int[0];
            }
        }

        public boolean HasHRInfo() {
            return showHR;
        }

        public boolean HasHRZHist() {
            return showHR && showHRZhist;
        }
    }

    public double calculateAverage(int[] data) {
        int sum = 0;

        for (int aData : data) {
            sum = sum + aData;
        }
        double average = (double) sum / data.length;
        return average;
    }

    private void loadRoute() {
        final GraphProducer graphData = new GraphProducer();

        final String[] from = new String[] { DB.LOCATION.LATITUDE, DB.LOCATION.LONGITUDE, DB.LOCATION.TYPE,
                DB.LOCATION.TIME, DB.LOCATION.LAP, DB.LOCATION.HR };

        loadRouteTask = new AsyncTask<String, String, Route>() {

            @Override
            protected Route doInBackground(String... params) {

                int cnt = 0;
                Route route = null;
                Cursor c = mDB.query(DB.LOCATION.TABLE, from, "activity_id == " + mID, null, null, null, "_id",
                        null);
                if (c.moveToFirst()) {
                    route = new Route();
                    double acc_distance = 0;
                    double tot_distance = 0;
                    int cnt_distance = 0;
                    LatLng lastLocation = null;
                    long lastTime = 0;
                    int lastLap = -1;
                    int hr = 0;
                    do {
                        cnt++;
                        LatLng point = new LatLng(c.getDouble(0), c.getDouble(1));
                        route.path.add(point);
                        route.bounds.include(point);
                        int type = c.getInt(2);
                        long time = c.getLong(3);
                        int lap = c.getInt(4);
                        if (!c.isNull(5))
                            hr = c.getInt(5);
                        MarkerOptions m;
                        switch (type) {
                        case DB.LOCATION.TYPE_START:
                        case DB.LOCATION.TYPE_END:
                        case DB.LOCATION.TYPE_PAUSE:
                        case DB.LOCATION.TYPE_RESUME:
                            if (type == DB.LOCATION.TYPE_PAUSE) {
                                if (lap != lastLap) {
                                    graphData.clear(tot_distance);
                                } else if (lastTime != 0 && lastLocation != null) {
                                    float res[] = { 0 };
                                    Location.distanceBetween(lastLocation.latitude, lastLocation.longitude,
                                            point.latitude, point.longitude, res);
                                    graphData.addObservation(time - lastTime, res[0], tot_distance, hr);
                                    // hrList.clear();
                                    graphData.clear(tot_distance);
                                }
                                lastLap = lap;
                                lastTime = 0;
                            } else if (type == DB.LOCATION.TYPE_RESUME) {
                                lastLap = lap;
                                lastTime = time;
                            }
                            m = new MarkerOptions();
                            m.position((lastLocation = point));
                            m.title(type == DB.LOCATION.TYPE_START ? "Start"
                                    : type == DB.LOCATION.TYPE_END ? "Stop"
                                            : type == DB.LOCATION.TYPE_PAUSE ? "Pause"
                                                    : type == DB.LOCATION.TYPE_RESUME ? "Resume" : "<Unknown>");
                            m.snippet(null);
                            m.draggable(false);
                            route.markers.add(m);
                            break;
                        case DB.LOCATION.TYPE_GPS:
                            float res[] = { 0 };
                            Location.distanceBetween(lastLocation.latitude, lastLocation.longitude, point.latitude,
                                    point.longitude, res);
                            acc_distance += res[0];
                            tot_distance += res[0];

                            if (lap != lastLap) {
                                graphData.clear(tot_distance);
                            } else if (lastTime != 0) {
                                graphData.addObservation(time - lastTime, res[0], tot_distance, hr);
                            }
                            lastLap = lap;
                            lastTime = time;

                            if (acc_distance >= formatter.getUnitMeters()) {
                                cnt_distance++;
                                acc_distance = 0;
                                m = new MarkerOptions();
                                m.position(point);
                                m.title("" + cnt_distance + " " + formatter.getUnitString());
                                m.snippet(null);
                                m.draggable(false);
                                route.markers.add(m);
                            }
                            lastLocation = point;
                            break;
                        }
                    } while (c.moveToNext());
                    System.err.println("Finished loading " + cnt + " points");
                }
                c.close();
                return route;
            }

            @Override
            protected void onPostExecute(Route route) {

                if (route != null) {
                    graphData.complete(graphView);
                    if (!graphData.HasHRInfo()) {
                        graphTab.addView(graphView);
                    } else {
                        graphTab.addView(graphView, new LayoutParams(LayoutParams.MATCH_PARENT, 0, 0.5f));

                        graphTab.addView(graphView2, new LayoutParams(LayoutParams.MATCH_PARENT, 0, 0.5f));
                    }

                    if (graphData.HasHRZHist()) {
                        hrzonesBarLayout.setVisibility(View.VISIBLE);
                        hrzonesBarLayout.addView(hrzonesBar);
                    } else {
                        hrzonesBarLayout.setVisibility(View.GONE);
                    }

                    if (map != null) {
                        map.addPolyline(route.path);
                        mapBounds = route.bounds.build();
                        System.err.println("Added polyline");
                        int cnt = 0;
                        for (MarkerOptions m : route.markers) {
                            cnt++;
                            map.addMarker(m);
                        }
                        System.err.println("Added " + cnt + " markers");

                        route = new Route(); // release mem for old...
                    }
                }
            }
        }.execute("kalle");
    }

    private void shareActivity() {
        final int which[] = { -1 };
        final CharSequence items[] = { "gpx", "tcx" /* "nike+xml" */
        };
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(getString(R.string.share_activity));
        builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int w) {
                if (which[0] == -1) {
                    dialog.dismiss();
                    return;
                }

                final Activity context = DetailActivity.this;
                final CharSequence fmt = items[which[0]];
                final Intent intent = new Intent(Intent.ACTION_SEND);

                intent.setType(WorkoutFileProvider.MIME);
                Uri uri = Uri.parse("content://" + ActivityProvider.AUTHORITY + "/" + fmt + "/" + mID + "/"
                        + "activity." + fmt);
                intent.putExtra(Intent.EXTRA_STREAM, uri);
                context.startActivity(Intent.createChooser(intent, "Share workout..."));

            }
        });
        builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                // Do nothing but close the dialog
                dialog.dismiss();
            }

        });
        builder.setSingleChoiceItems(items, which[0], new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int w) {
                which[0] = w;
            }
        });
        builder.show();
    }
}