package me.guillaumin.android.osmtracker.activity;
import java.io.File;
import java.util.Date;
import me.guillaumin.android.osmtracker.OSMTracker;
import me.guillaumin.android.osmtracker.R;
import me.guillaumin.android.osmtracker.db.DataHelper;
import me.guillaumin.android.osmtracker.db.TrackContentProvider;
import me.guillaumin.android.osmtracker.db.TrackContentProvider.Schema;
import me.guillaumin.android.osmtracker.db.TracklistAdapter;
import me.guillaumin.android.osmtracker.exception.CreateTrackException;
import me.guillaumin.android.osmtracker.gpx.ExportToStorageTask;
import me.guillaumin.android.osmtracker.util.FileSystemUtils;
import android.app.AlertDialog;
import android.app.ListActivity;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.CursorAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
/**
* Lists existing tracks.
* Each track is displayed using {@link TracklistAdapter}.
*
* @author Nicolas Guillaumin
*
*/
public class TrackManager extends ListActivity {
@SuppressWarnings("unused")
private static final String TAG = TrackManager.class.getSimpleName();
/** Bundle key for {@link #prevItemVisible} */
private static final String PREV_VISIBLE = "prev_visible";
/** Constant used if no track is active (-1)*/
private static final long TRACK_ID_NO_TRACK = -1;
/** The active track being recorded, if any, or {@link TRACK_ID_NO_TRACK}; value is updated in {@link #onResume()} */
private long currentTrackId = TRACK_ID_NO_TRACK;
/** The previous item visible, or -1; for scrolling back to its position in {@link #onResume()} */
private int prevItemVisible = -1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.trackmanager);
getListView().setEmptyView(findViewById(R.id.trackmgr_empty));
registerForContextMenu(getListView());
if (savedInstanceState != null) {
prevItemVisible = savedInstanceState.getInt(PREV_VISIBLE, -1);
}
}
@Override
protected void onResume() {
Cursor cursor = getContentResolver().query(
TrackContentProvider.CONTENT_URI_TRACK, null, null, null,
Schema.COL_START_DATE + " desc");
startManagingCursor(cursor);
setListAdapter(new TracklistAdapter(TrackManager.this, cursor));
getListView().setEmptyView(findViewById(R.id.trackmgr_empty)); // undo change from onPause
// Is any track active?
currentTrackId = DataHelper.getActiveTrackId(getContentResolver());
if (currentTrackId != TRACK_ID_NO_TRACK) {
((TextView) findViewById(R.id.trackmgr_hint)).setText(
getResources().getString(R.string.trackmgr_continuetrack_hint)
.replace("{0}", Long.toString(currentTrackId)));
// Scroll to the active track of the list
cursor.moveToFirst();
// we will use the flag selectionSet to handle the while loop
boolean selectionSet = false;
while(!selectionSet && cursor.moveToNext()){
if(cursor.getInt(cursor.getColumnIndex(Schema.COL_ACTIVE)) == 1){
// This is the active track
// set selection to the current cursor position
getListView().setSelection(cursor.getPosition());
selectionSet = true;
}
}
} else {
((TextView) findViewById(R.id.trackmgr_hint)).setText(R.string.trackmgr_newtrack_hint);
// Scroll to the previous listview position,
// now that we're bound to data again
if (prevItemVisible != -1) {
final int cmax = getListView().getCount() - 1;
if (prevItemVisible > cmax) {
prevItemVisible = cmax;
}
getListView().setSelection(prevItemVisible);
}
}
super.onResume();
}
@Override
protected void onPause() {
// Remember position in listview (before any adapter change)
prevItemVisible = getListView().getFirstVisiblePosition();
CursorAdapter adapter = (CursorAdapter) getListAdapter();
if (adapter != null) {
// Prevents on-screen 'no tracks' message
getListView().setEmptyView(findViewById(android.R.id.empty));
// Properly close the adapter cursor
Cursor cursor = adapter.getCursor();
stopManagingCursor(cursor);
cursor.close();
setListAdapter(null);
}
super.onPause();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(PREV_VISIBLE, prevItemVisible);
}
@Override
protected void onRestoreInstanceState(Bundle state) {
super.onRestoreInstanceState(state);
prevItemVisible = state.getInt(PREV_VISIBLE, -1);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.trackmgr_menu, menu);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
if (currentTrackId != -1) {
// Currently tracking. Display "Continue" option
menu.findItem(R.id.trackmgr_menu_continuetrack).setVisible(true);
// Display a 'stop tracking' option
menu.findItem(R.id.trackmgr_menu_stopcurrenttrack).setVisible(true);
} else {
// Not currently tracking. Remove "Continue" option
menu.findItem(R.id.trackmgr_menu_continuetrack).setVisible(false);
// Remove the 'stop tracking' option
menu.findItem(R.id.trackmgr_menu_stopcurrenttrack).setVisible(false);
}
// Remove "delete all" button if no tracks
menu.findItem(R.id.trackmgr_menu_deletetracks).setVisible(getListView().getCount() > 0);
return super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.trackmgr_menu_newtrack:
// Start track logger activity
try {
Intent i = new Intent(this, TrackLogger.class);
// New track
currentTrackId = createNewTrack();
i.putExtra(Schema.COL_TRACK_ID, currentTrackId);
startActivity(i);
} catch (CreateTrackException cte) {
Toast.makeText(this,
getResources().getString(R.string.trackmgr_newtrack_error).replace("{0}", cte.getMessage()),
Toast.LENGTH_LONG)
.show();
}
break;
case R.id.trackmgr_menu_continuetrack:
Intent i = new Intent(this, TrackLogger.class);
i.putExtra(TrackLogger.STATE_IS_TRACKING, true);
i.putExtra(Schema.COL_TRACK_ID, currentTrackId);
startActivity(i);
break;
case R.id.trackmgr_menu_stopcurrenttrack:
stopActiveTrack();
break;
case R.id.trackmgr_menu_deletetracks:
// Confirm and delete all track
new AlertDialog.Builder(this)
.setTitle(R.string.trackmgr_contextmenu_delete)
.setMessage(getResources().getString(R.string.trackmgr_deleteall_confirm))
.setCancelable(true)
.setIcon(android.R.drawable.ic_dialog_alert)
.setPositiveButton(R.string.menu_deletetracks, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
deleteAllTracks();
dialog.dismiss();
}
})
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
}).create().show();
break;
case R.id.trackmgr_menu_exportall:
// Confirm
new AlertDialog.Builder(this)
.setTitle(R.string.menu_exportall)
.setMessage(getResources().getString(R.string.trackmgr_exportall_confirm))
.setCancelable(true)
.setIcon(android.R.drawable.ic_dialog_alert)
.setPositiveButton(R.string.menu_exportall, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Cursor cursor = getContentResolver().query(TrackContentProvider.CONTENT_URI_TRACK,
null, null, null, Schema.COL_START_DATE + " desc");
if (cursor.moveToFirst()) {
long[] ids = new long[cursor.getCount()];
int idCol = cursor.getColumnIndex(Schema.COL_ID);
int i=0;
do {
ids[i++] = cursor.getLong(idCol);
} while (cursor.moveToNext());
new ExportToStorageTask(TrackManager.this, ids).execute();
}
cursor.close();
}
})
.setNegativeButton(android.R.string.cancel, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
}).create().show();
break;
case R.id.trackmgr_menu_settings:
// Start settings activity
startActivity(new Intent(this, Preferences.class));
break;
case R.id.trackmgr_menu_about:
// Start About activity
startActivity(new Intent(this, About.class));
break;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
getMenuInflater().inflate(R.menu.trackmgr_contextmenu, menu);
long selectedId = ((AdapterContextMenuInfo) menuInfo).id;
menu.setHeaderTitle(getResources().getString(R.string.trackmgr_contextmenu_title).replace("{0}", Long.toString(selectedId)));
if(currentTrackId == selectedId){
// the selected one is the active track, so we will show the stop item
menu.findItem(R.id.trackmgr_contextmenu_stop).setVisible(true);
}else{
// the selected item is not active, so we need to hide the stop item
menu.findItem(R.id.trackmgr_contextmenu_stop).setVisible(false);
}
menu.setHeaderTitle(getResources().getString(R.string.trackmgr_contextmenu_title).replace("{0}", Long.toString(selectedId)));
if ( currentTrackId == selectedId) {
// User has pressed the active track, hide the delete option
menu.removeItem(R.id.trackmgr_contextmenu_delete);
}
}
@Override
public boolean onContextItemSelected(MenuItem item) {
final AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
Intent i;
switch(item.getItemId()) {
case R.id.trackmgr_contextmenu_stop:
// stop the active track
stopActiveTrack();
break;
case R.id.trackmgr_contextmenu_resume:
// let's activate the track and start the TrackLogger activity
setActiveTrack(info.id);
i = new Intent(this, TrackLogger.class);
i.putExtra(Schema.COL_TRACK_ID, info.id);
startActivity(i);
break;
case R.id.trackmgr_contextmenu_delete:
// Confirm and delete selected track
new AlertDialog.Builder(this)
.setTitle(R.string.trackmgr_contextmenu_delete)
.setMessage(getResources().getString(R.string.trackmgr_delete_confirm).replace("{0}", Long.toString(info.id)))
.setCancelable(true)
.setIcon(android.R.drawable.ic_dialog_alert)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
deleteTrack(info.id);
dialog.dismiss();
}
})
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
}).create().show();
break;
case R.id.trackmgr_contextmenu_export:
new ExportToStorageTask(this, info.id).execute();
break;
case R.id.trackmgr_contextmenu_osm_upload:
i = new Intent(this, OpenStreetMapUpload.class);
i.putExtra(Schema.COL_TRACK_ID, info.id);
startActivity(i);
break;
case R.id.trackmgr_contextmenu_display:
// Start display track activity, with or without OSM background
boolean useOpenStreetMapBackground = PreferenceManager.getDefaultSharedPreferences(this).getBoolean(
OSMTracker.Preferences.KEY_UI_DISPLAYTRACK_OSM, OSMTracker.Preferences.VAL_UI_DISPLAYTRACK_OSM);
if (useOpenStreetMapBackground) {
i = new Intent(this, DisplayTrackMap.class);
} else {
i = new Intent(this, DisplayTrack.class);
}
i.putExtra(Schema.COL_TRACK_ID, info.id);
startActivity(i);
break;
case R.id.trackmgr_contextmenu_details:
i = new Intent(this, TrackDetail.class);
i.putExtra(Schema.COL_TRACK_ID, info.id);
startActivity(i);
break;
}
return super.onContextItemSelected(item);
}
/**
* User has clicked the active track or a previous track.
* @param lv listview; this
* @param iv item clicked
* @param position position within list
* @param id track ID
*/
@Override
protected void onListItemClick(ListView lv, View iv, final int position, final long id) {
Intent i;
if (id == currentTrackId) {
// continue recording the current track
i = new Intent(this, TrackLogger.class);
i.putExtra(Schema.COL_TRACK_ID, currentTrackId);
i.putExtra(TrackLogger.STATE_IS_TRACKING, true);
} else {
// show track info
i = new Intent(this, TrackDetail.class);
i.putExtra(Schema.COL_TRACK_ID, id);
}
startActivity(i);
}
/**
* Creates a new track, in DB and on SD card
* @returns The ID of the new track
* @throws CreateTrackException
*/
private long createNewTrack() throws CreateTrackException {
Date startDate = new Date();
// Create entry in TRACK table
ContentValues values = new ContentValues();
values.put(Schema.COL_NAME, "");
values.put(Schema.COL_START_DATE, startDate.getTime());
values.put(Schema.COL_ACTIVE, Schema.VAL_TRACK_ACTIVE);
Uri trackUri = getContentResolver().insert(TrackContentProvider.CONTENT_URI_TRACK, values);
long trackId = ContentUris.parseId(trackUri);
// set the active track
setActiveTrack(trackId);
return trackId;
}
/**
* Deletes the track with the specified id from DB and SD card
* @param The ID of the track to be deleted
*/
private void deleteTrack(long id) {
getContentResolver().delete(
ContentUris.withAppendedId(TrackContentProvider.CONTENT_URI_TRACK, id),
null, null);
((CursorAdapter) TrackManager.this.getListAdapter()).getCursor().requery();
// Delete any data stored for the track we're deleting
File trackStorageDirectory = DataHelper.getTrackDirectory(id);
if (trackStorageDirectory.exists()) {
FileSystemUtils.delete(trackStorageDirectory, true);
}
}
/**
* Deletes all tracks and their data
*/
private void deleteAllTracks() {
Cursor cursor = getContentResolver().query(TrackContentProvider.CONTENT_URI_TRACK, null, null, null, Schema.COL_START_DATE + " asc");
// Stop any currently active tracks
if (currentTrackId != -1) {
stopActiveTrack();
}
if (cursor.moveToFirst()) {
int id_col = cursor.getColumnIndex(Schema.COL_ID);
do {
deleteTrack(cursor.getLong(id_col));
} while (cursor.moveToNext());
}
cursor.close();
}
/**
* Sets the active track
* calls {@link stopActiveTrack()} to stop all currently
* @param trackId ID of the track to activate
*/
private void setActiveTrack(long trackId){
// to be sure that no tracking will be in progress when we set a new track
stopActiveTrack();
// set the track active
ContentValues values = new ContentValues();
values.put(Schema.COL_ACTIVE, Schema.VAL_TRACK_ACTIVE);
getContentResolver().update(TrackContentProvider.CONTENT_URI_TRACK, values, Schema.COL_ID + " = ?", new String[] {Long.toString(trackId)});
}
/**
* Stops the active track
* Sends a broadcast to be received by GPSLogger to stop logging
* and forces the DataHelper to stop tracking.
*/
private void stopActiveTrack(){
if(currentTrackId != TRACK_ID_NO_TRACK){
// we send a broadcast to inform all registered services to stop tracking
Intent intent = new Intent(OSMTracker.INTENT_STOP_TRACKING);
sendBroadcast(intent);
// need to get sure, that the database is up to date
DataHelper dataHelper = new DataHelper(this);
dataHelper.stopTracking(currentTrackId);
// set the currentTrackId to "no track"
currentTrackId = TRACK_ID_NO_TRACK;
}
}
}