/*
* Copyright (c) 2013, Will Szumski
* Copyright (c) 2013, Doug Szumski
*
* This file is part of Cyclismo.
*
* Cyclismo 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.
*
* Cyclismo 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 Cyclismo. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.cowboycoders.cyclismo;
import android.app.Activity;
import android.app.Dialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.database.Cursor;
import android.location.Location;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.widget.ResourceCursorAdapter;
import android.util.Log;
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;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast;
import com.ipaulpro.afilechooser.utils.FileUtils;
import org.cowboycoders.cyclismo.content.CourseTracksColumns;
import org.cowboycoders.cyclismo.content.CyclismoProviderUtils;
import org.cowboycoders.cyclismo.content.MyTracksCourseProviderUtils;
import org.cowboycoders.cyclismo.content.MyTracksProviderUtils;
import org.cowboycoders.cyclismo.content.Track;
import org.cowboycoders.cyclismo.content.TrackDataListener;
import org.cowboycoders.cyclismo.content.User;
import org.cowboycoders.cyclismo.content.Waypoint;
import org.cowboycoders.cyclismo.fragments.DeleteAllTrackDialogFragment;
import org.cowboycoders.cyclismo.fragments.DeleteOneTrackDialogFragment;
import org.cowboycoders.cyclismo.fragments.DeleteOneTrackDialogFragment.DeleteOneTrackCaller;
import org.cowboycoders.cyclismo.io.file.GpxImporter;
import org.cowboycoders.cyclismo.services.TrackRecordingServiceConnection;
import org.cowboycoders.cyclismo.util.ApiAdapterFactory;
import org.cowboycoders.cyclismo.util.IntentUtils;
import org.cowboycoders.cyclismo.util.ListItemUtils;
import org.cowboycoders.cyclismo.util.PreferencesUtils;
import org.cowboycoders.cyclismo.util.StringUtils;
import org.cowboycoders.cyclismo.util.TrackIconUtils;
import org.xml.sax.SAXException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.xml.parsers.ParserConfigurationException;
/**
* An activity displaying a list of tracks.
*
* @author Leif Hendrik Wilden
*/
public class CourseListActivity extends FragmentActivity implements DeleteOneTrackCaller {
private static final String TAG = CourseListActivity.class.getSimpleName();
private static final int GOOGLE_PLAY_SERVICES_REQUEST_CODE = 0;
private static final String[] PROJECTION = new String[] { CourseTracksColumns._ID, CourseTracksColumns.NAME,
CourseTracksColumns.DESCRIPTION, CourseTracksColumns.CATEGORY, CourseTracksColumns.STARTTIME,
CourseTracksColumns.TOTALDISTANCE, CourseTracksColumns.TOTALTIME, CourseTracksColumns.ICON };
/*
* Note that sharedPreferenceChangeListenr cannot be an anonymous inner class.
* Anonymous inner class will get garbage collected.
*/
private final OnSharedPreferenceChangeListener
sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
if (key == null || key.equals(
PreferencesUtils.getKey(CourseListActivity.this, R.string.metric_units_key))) {
metricUnits = PreferencesUtils.getBoolean(CourseListActivity.this,
R.string.metric_units_key, PreferencesUtils.METRIC_UNITS_DEFAULT);
}
if (key != null) {
runOnUiThread(new Runnable() {
@Override
public void run() {
updateMenuItems();
resourceCursorAdapter.notifyDataSetChanged();
}
});
}
}
};
// Callback when an item is selected in the contextual action mode
private final ContextualActionModeCallback
contextualActionModeCallback = new ContextualActionModeCallback() {
@Override
public boolean onClick(int itemId, int position, long id) {
return handleContextItem(itemId, id);
}
};
private final TrackDataListener trackDataListener = new TrackDataListener() {
@Override
public void onTrackUpdated(Track track) {
// Ignore
}
@Override
public void onSelectedTrackChanged(Track track) {
// Ignore
}
@Override
public void onSegmentSplit(Location location) {
// Ignore
}
@Override
public void onSampledOutTrackPoint(Location location) {
// Ignore
}
@Override
public void onSampledInTrackPoint(Location location) {
// Ignore
}
@Override
public boolean onReportSpeedChanged(boolean reportSpeed) {
return false;
}
@Override
public void onNewWaypointsDone() {
// Ignore
}
@Override
public void onNewWaypoint(Waypoint waypoint) {
// Ignore
}
@Override
public void onNewTrackPointsDone() {
// Ignore
}
@Override
public boolean onMinRecordingDistanceChanged(int minRecordingDistance) {
return false;
}
@Override
public boolean onMetricUnitsChanged(boolean isMetricUnits) {
return false;
}
@Override
public void onLocationStateChanged(LocationState locationState) {
// Ignore
}
@Override
public void onLocationChanged(Location location) {
// Ignore
}
@Override
public void onHeadingChanged(double heading) {
// Ignore
}
@Override
public void clearWaypoints() {
// Ignore
}
@Override
public void clearTrackPoints() {
// Ignore
}
};
// The following are set in onCreate
private SharedPreferences sharedPreferences;
private ListView listView;
private ResourceCursorAdapter resourceCursorAdapter;
// Preferences
private boolean metricUnits = PreferencesUtils.METRIC_UNITS_DEFAULT;
private Button importButton;
private Button cancelButton;
private static final int REQUEST_CODE = 0x1234;
private void updateTrackIdSharedPreference(long id) {
Track track = new MyTracksCourseProviderUtils(CourseListActivity.this.getContentResolver()).getTrack(id);
if (track == null) {
id = -1;
}
PreferencesUtils.setLong(CourseListActivity.this, R.string.course_track_id, id);
}
/**
* This also syncs up the shared preference
* @return
*/
private Long getTrackIdSharedPreference() {
Long id = PreferencesUtils.getLong(this, R.string.course_track_id);
Track track = new MyTracksCourseProviderUtils(CourseListActivity.this.getContentResolver()).getTrack(id);
if (track == null) {
id = -1L;
}
return id;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.course_list);
sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
listView = (ListView) findViewById(R.id.course_list);
listView.setEmptyView(findViewById(R.id.course_list_empty_view));
listView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
updateTrackIdSharedPreference(id);
//Editor editor = sharedPreferences.edit();
//editor.putLong(PreferencesUtils.getKey(CourseListActivity.this,R.string.course_track_id), id);
//editor.apply();
doFinish();
}
});
resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.list_item, null, 0) {
@Override
public void bindView(View view, Context context, Cursor cursor) {
int idIndex = cursor.getColumnIndex(CourseTracksColumns._ID);
int iconIndex = cursor.getColumnIndex(CourseTracksColumns.ICON);
int nameIndex = cursor.getColumnIndex(CourseTracksColumns.NAME);
int categoryIndex = cursor.getColumnIndex(CourseTracksColumns.CATEGORY);
int totalTimeIndex = cursor.getColumnIndexOrThrow(CourseTracksColumns.TOTALTIME);
int totalDistanceIndex = cursor.getColumnIndexOrThrow(CourseTracksColumns.TOTALDISTANCE);
int startTimeIndex = cursor.getColumnIndexOrThrow(CourseTracksColumns.STARTTIME);
int descriptionIndex = cursor.getColumnIndex(CourseTracksColumns.DESCRIPTION);
int iconId = TrackIconUtils.getIconDrawable(cursor.getString(iconIndex));
String name = cursor.getString(nameIndex);
String totalTime = StringUtils.formatElapsedTime(cursor.getLong(totalTimeIndex));
String totalDistance = StringUtils.formatDistance(
CourseListActivity.this, cursor.getDouble(totalDistanceIndex), metricUnits);
long startTime = cursor.getLong(startTimeIndex);
String startTimeDisplay = StringUtils.formatDateTime(context, startTime).equals(name) ? null
: StringUtils.formatRelativeDateTime(context, startTime);
Log.d(TAG,"resourceCursorAdapter.bindview : ListItemUtils.setListItem");
ListItemUtils.setListItem(CourseListActivity.this, view, false, false,
iconId, R.string.icon_track, name, cursor.getString(categoryIndex), totalTime,
totalDistance, startTimeDisplay, cursor.getString(descriptionIndex));
Log.d(TAG,"resourceCursorAdapter.bindview : ListItemUtils.setListItem finished");
}
};
listView.setAdapter(resourceCursorAdapter);
ApiAdapterFactory.getApiAdapter()
.configureListViewContextualMenu(this, listView, contextualActionModeCallback);
getSupportLoaderManager().initLoader(0, null, new LoaderCallbacks<Cursor>() {
@Override
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
Log.d(TAG,"onCreateLoader");
CursorLoader rtn = new CursorLoader(CourseListActivity.this, CourseTracksColumns.CONTENT_URI, PROJECTION, null,
null, CourseTracksColumns._ID + " DESC");
Log.d(TAG,"onCreateLoader returning");
return rtn;
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
Log.d(TAG,"onLoadFinished");
resourceCursorAdapter.swapCursor(cursor);
Log.d(TAG,"onLoadFinished returning");
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
Log.d(TAG,"onLoaderReset");
resourceCursorAdapter.swapCursor(null);
Log.d(TAG,"onLoaderReset returning");
}
});
importButton = (Button) this.findViewById(R.id.course_import_button);
importButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showChooser();
}
});
cancelButton = (Button) this.findViewById(R.id.course_cancel_button);
cancelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
cancel();
}
});
showStartupDialogs();
}
private void cancel() {
Intent resultData = new Intent();
resultData.putExtra(getString(R.string.course_track_id), getTrackIdSharedPreference());
setResult(Activity.RESULT_CANCELED, resultData);
finish();
}
private void doFinish() {
Intent resultData = new Intent();
resultData.putExtra(getString(R.string.course_track_id), getTrackIdSharedPreference());
setResult(Activity.RESULT_OK, resultData);
finish();
}
private void showChooser() {
// Use the GET_CONTENT intent from the utility class
Intent target = FileUtils.createGetContentIntent();
// Create the chooser Intent
Intent intent = Intent.createChooser(
target, getString(R.string.course_file_chooser_title));
try {
startActivityForResult(intent, REQUEST_CODE);
} catch (ActivityNotFoundException e) {
Toast.makeText(CourseListActivity.this,
"SecurityException: unable to load file manager", Toast.LENGTH_LONG).show();
Log.e(TAG,e.toString());
}
}
@Override
protected void onStart() {
super.onStart();
// Register shared preferences listener
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
// Update shared preferences
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
}
@Override
protected void onResume() {
super.onResume();
// Update UI
updateMenuItems();
resourceCursorAdapter.notifyDataSetChanged();
}
@Override
protected void onPause() {
super.onPause();
// Update track data hub
//trackDataHub.unregisterTrackDataListener(trackDataListener);
// Update UI
}
@Override
protected void onStop() {
super.onStop();
// Unregister shared preferences listener
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
//trackDataHub.stopTimer();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == GOOGLE_PLAY_SERVICES_REQUEST_CODE) {
} else {
switch (requestCode) {
case REQUEST_CODE:
// If the file selection was successful
if (resultCode == RESULT_OK) {
if (data != null) {
// Get the URI of the selected file
final Uri uri = data.getData();
try {
// Create a file instance from the URI
final File file = FileUtils.getFile(uri);
InputStream fileStream = new FileInputStream(file);
Toast.makeText(CourseListActivity.this,
"importing: "+file.getAbsolutePath(), Toast.LENGTH_LONG).show();
importGpx(fileStream);
} catch (IOException e) {
Log.e(TAG, "File select error", e);
}
}
}
break;
}
}
super.onActivityResult(requestCode, resultCode, data);
}
private void importGpx(InputStream fileStream) {
try {
CyclismoProviderUtils providerUtils = MyTracksProviderUtils.Factory.getCyclimso(this);
User currentUser = providerUtils.getUser(PreferencesUtils.getLong(this, R.string.settings_select_user_current_selection_key));
GpxImporter.importGPXFile(fileStream,new MyTracksCourseProviderUtils(this.getContentResolver()),
PreferencesUtils.MIN_RECORDING_DISTANCE_DEFAULT, currentUser);
} catch (ParserConfigurationException e) {
final String msg = "error parsing gpx file";
Log.e(TAG,msg);
Toast.makeText(CourseListActivity.this,
msg, Toast.LENGTH_LONG).show();
} catch (SAXException e) {
final String msg = "error parsing gpx file";
Log.e(TAG,msg);
Toast.makeText(CourseListActivity.this,
msg, Toast.LENGTH_LONG).show();
} catch (IOException e) {
final String msg = "IOException whilst parsing gpx file";
Log.e(TAG,msg);
Toast.makeText(CourseListActivity.this,
msg, Toast.LENGTH_LONG).show();
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.course_list, menu);
//deleteAllMenuItem = menu.findItem(R.id.course_list_delete_all);
updateMenuItems();
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.course_list_delete_all:
new DeleteAllTrackDialogFragment(new MyTracksCourseProviderUtils(this.getContentResolver())).show(
getSupportFragmentManager(), DeleteAllTrackDialogFragment.DELETE_ALL_TRACK_DIALOG_TAG);
return true;
}
return true;
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
getMenuInflater().inflate(R.menu.list_context_menu, menu);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
if (handleContextItem(item.getItemId(), ((AdapterContextMenuInfo) item.getMenuInfo()).id)) {
return true;
}
return super.onContextItemSelected(item);
}
/**
* Shows start up dialogs.
*/
public void showStartupDialogs() {
findViewById(R.id.course_list_empty_view).setVisibility(View.VISIBLE);
}
/**
* Updates the menu items.
*
*/
private void updateMenuItems() {
//if (deleteAllMenuItem != null) {
// deleteAllMenuItem.setVisible(false);
//}
}
/**
* Handles a context item selection.
*
* @param itemId the menu item id
* @param trackId the track id
* @return true if handled.
*/
private boolean handleContextItem(int itemId, long trackId) {
Intent intent;
switch (itemId) {
case R.id.list_context_menu_show_on_map:
intent = IntentUtils.newIntent(this, TrackDetailActivity.class)
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, trackId)
.putExtra(TrackDetailActivity.EXTRA_USE_COURSE_PROVIDER, true);
startActivity(intent);
return true;
case R.id.list_context_menu_edit:
intent = IntentUtils.newIntent(this, TrackEditActivity.class)
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId)
.putExtra(TrackEditActivity.EXTRA_USE_COURSE_PROVIDER, true);
startActivity(intent);
return true;
case R.id.list_context_menu_delete:
DeleteOneTrackDialogFragment.newInstance(trackId, true).show(
getSupportFragmentManager(), DeleteOneTrackDialogFragment.DELETE_ONE_TRACK_DIALOG_TAG);
return true;
default:
return false;
}
}
@Override
public TrackRecordingServiceConnection getTrackRecordingServiceConnection() {
return null;
}
}