Merge with 25704964a526fcfe09919af8f29c1e526b5409ec

This commit is contained in:
Bartlomiej Niechwiej
2010-10-04 16:34:38 -07:00
parent 256dac3a82
commit 34bb467b97
55 changed files with 942 additions and 486 deletions
@@ -25,5 +25,8 @@ public class MyMapsConstants {
"http://maps.google.com/maps/ms";
public static final String MAPSHOP_SERVICE = MapsClient.SERVICE;
/**
* Private constructor to prevent instantiation.
*/
private MyMapsConstants() { }
}
@@ -0,0 +1,114 @@
package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Window;
import android.widget.ScrollView;
import android.widget.TextView;
import java.util.List;
/**
* Activity for viewing the combined statistics for all the recorded tracks.
*
* Other features to add - menu items to change setings.
*
* @author Fergus Nelson
*/
public class AggregatedStatsActivity extends Activity implements
OnSharedPreferenceChangeListener {
private final StatsUtilities utils;
private MyTracksProviderUtils tracksProvider;
private boolean metricUnits = true;
public AggregatedStatsActivity() {
this.utils = new StatsUtilities(this);
this.tracksProvider = MyTracksProviderUtils.Factory.get(this);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
String key) {
Log.d(MyTracksConstants.TAG, "StatsActivity: onSharedPreferences changed "
+ key);
if (key != null) {
if (key.equals(R.string.metric_units_key)) {
metricUnits = sharedPreferences.getBoolean(
getString(R.string.metric_units_key), true);
utils.setMetricUnits(metricUnits);
utils.updateUnits();
loadAggregatedStats();
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// We don't need a window title bar:
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.stats);
ScrollView sv = ((ScrollView) findViewById(R.id.scrolly));
sv.setScrollBarStyle(ScrollView.SCROLLBARS_OUTSIDE_INSET);
SharedPreferences preferences = getSharedPreferences(
MyTracksSettings.SETTINGS_NAME, 0);
if (preferences != null) {
metricUnits = preferences.getBoolean(getString(R.string.metric_units_key), true);
preferences.registerOnSharedPreferenceChangeListener(this);
}
utils.setMetricUnits(metricUnits);
utils.updateUnits();
utils.setSpeedLabel(R.id.speed_label, R.string.speed, R.string.pace_label);
utils.setSpeedLabels();
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
if (metrics.heightPixels > 600) {
((TextView) findViewById(R.id.speed_register)).setTextSize(80.0f);
}
loadAggregatedStats();
}
/**
* 1. Reads tracks from the db
* 2. Merges the trip stats from the tracks
* 3. Updates the view
*/
private void loadAggregatedStats() {
List<Track> tracks = retrieveTracks();
TripStatistics rollingStats = null;
if (!tracks.isEmpty()) {
rollingStats = new TripStatistics(tracks.iterator().next()
.getStatistics());
for (int i = 1; i < tracks.size(); i++) {
rollingStats.merge(tracks.get(i).getStatistics());
}
}
updateView(rollingStats);
}
private List<Track> retrieveTracks() {
return tracksProvider.getAllTracks();
}
private void updateView(TripStatistics aggStats) {
if (aggStats != null) {
utils.setAllStats(aggStats);
}
}
}
@@ -176,11 +176,15 @@ public class ChartActivity extends Activity implements
final int idColumnIdx =
cursor.getColumnIndexOrThrow(TrackPointsColumns._ID);
ArrayList<double[]> data = new ArrayList<double[]>();
// Need two locations so we can keep track of the last location.
Location location = new Location("");
do {
lastSeenLocationId = cursor.getLong(idColumnIdx);
Location location = providerUtils.createLocation(cursor);
if (location != null && MyTracksUtils.isValidLocation(location)) {
data.add(getDataPoint(location, track));
providerUtils.fillLocation(cursor, location);
if (MyTracksUtils.isValidLocation(location)) {
double[] point = new double[3];
location = getDataPoint(location, track, point);
data.add(point);
}
} while (cursor.moveToPrevious());
cv.addDataPoints(data);
@@ -461,11 +465,14 @@ public class ChartActivity extends Activity implements
* data[1] = the elevation
* data[2] = the speed
*
* @param location a location
* @return the data point
* This must be called in order for each point.
*
* @param location the location to get data for (this method takes ownership of that location)
* @param track the track to get data from
* @param result the resulting point to fill out
* @return the previous location, now available for reuse
*/
public double[] getDataPoint(Location location, Track track) {
double[] result = new double[3];
private Location getDataPoint(Location location, Track track, double[] result) {
switch (mode) {
case BY_DISTANCE:
result[0] = profileLength;
@@ -512,8 +519,16 @@ public class ChartActivity extends Activity implements
// Format as hours per unit
result[2] = (60.0 / result[2]);
}
Location oldLastLocation = lastLocation;
lastLocation = location;
return result;
if (oldLastLocation == null) {
// No previous location, but return a blank one for reuse
return new Location("");
}
return oldLastLocation;
}
/**
@@ -556,6 +571,8 @@ public class ChartActivity extends Activity implements
try {
final ArrayList<double[]> theData = new ArrayList<double[]>();
int points = 0;
// Need two locations so we can keep track of the last location.
Location location = new Location("");
while (lastLocationRead < track.getStopId()) {
cursor = providerUtils.getLocationsCursor(
selectedTrackId, lastLocationRead, bufferSize, false);
@@ -567,11 +584,12 @@ public class ChartActivity extends Activity implements
cursor.getColumnIndexOrThrow(TrackPointsColumns._ID);
while (cursor.moveToNext()) {
points++;
Location location = providerUtils.createLocation(cursor);
providerUtils.fillLocation(cursor, location);
if (MyTracksUtils.isValidLocation(location)) {
lastLocationRead = lastSeenLocationId =
cursor.getLong(idColumnIdx);
double[] point = getDataPoint(location, track);
double[] point = new double[3];
location = getDataPoint(location, track, point);
if (points % chartSamplingFrequency == 0) {
theData.add(point);
}
@@ -78,6 +78,11 @@ class MenuManager {
activity.startActivity(startIntent);
return true;
}
case R.id.menu_aggregated_stats: {
Intent startIntent = new Intent(activity, AggregatedStatsActivity.class);
activity.startActivity(startIntent);
return true;
}
case R.id.menu_help: {
Intent startIntent = new Intent(activity, WelcomeActivity.class);
activity.startActivity(startIntent);
@@ -75,7 +75,6 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Random;
import javax.xml.parsers.ParserConfigurationException;
@@ -217,40 +216,25 @@ public class MyTracks extends TabActivity implements OnTouchListener,
* Tabs/View navigation:
*/
private static final int NUM_TABS = 3;
private int currentTab = 0;
private NavControls navControls;
private final int icons[] =
{ R.drawable.arrow_grey, R.drawable.menu_by_time,
R.drawable.menu_elevation };
/** Icons shown on the left for each tab. */
private final int leftIcons[] =
{ R.drawable.left_arrow_chart_stateful,
R.drawable.left_arrow_track_stateful,
R.drawable.left_arrow_stats_stateful };
/** Icons shown on the right for each tab. */
private final int rightIcons[] =
{ R.drawable.right_arrow_stats_stateful,
R.drawable.right_arrow_chart_stateful,
R.drawable.right_arrow_track_stateful };
private final Runnable nextActivity = new Runnable() {
private final Runnable changeTab = new Runnable() {
public void run() {
currentTab = (currentTab + 1) % NUM_TABS;
navControls.setLeftIcon(icons[(currentTab + NUM_TABS - 1) % NUM_TABS]);
navControls.setRightIcon(icons[(currentTab + NUM_TABS + 1) % NUM_TABS]);
getTabHost().setCurrentTab(currentTab);
navControls.show();
getTabHost().setCurrentTab(navControls.getCurrentIcons());
}
};
private final Runnable prevActivity = new Runnable() {
public void run() {
currentTab--;
if (currentTab < 0) {
currentTab = NUM_TABS - 1;
}
navControls.setLeftIcon(icons[(currentTab + NUM_TABS - 1) % NUM_TABS]);
navControls.setRightIcon(icons[(currentTab + NUM_TABS + 1) % NUM_TABS]);
getTabHost().setCurrentTab(currentTab);
navControls.show();
}
};
private final Random random = new Random();
public static MyTracks getInstance() {
return instance;
}
@@ -311,9 +295,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
LayoutParams params =
new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
layout.setLayoutParams(params);
navControls = new NavControls(this, layout, prevActivity, nextActivity);
navControls.setLeftIcon(icons[NUM_TABS - 1]);
navControls.setRightIcon(icons[1]);
navControls = new NavControls(this, layout, leftIcons, rightIcons, changeTab);
navControls.show();
tabHost.addView(layout);
layout.setOnTouchListener(this);
@@ -376,8 +358,6 @@ public class MyTracks extends TabActivity implements OnTouchListener,
Log.d(MyTracksConstants.TAG, "MyTracks.onResume");
super.onResume();
tryBindTrackRecordingService();
navControls.setLeftIcon(icons[(currentTab + NUM_TABS - 1) % NUM_TABS]);
navControls.setRightIcon(icons[(currentTab + NUM_TABS + 1) % NUM_TABS]);
}
@Override
@@ -828,33 +808,33 @@ public class MyTracks extends TabActivity implements OnTouchListener,
* This is for debugging and testing only. Useful if there is no GPS signal
* available.
*/
public void recordRandomLocation() {
if (trackRecordingService != null) {
Location loc = new Location("gps");
double latitude = 37.5 + random.nextDouble() / 1000;
double longitude = -120.0 + random.nextDouble() / 1000;
loc.setLatitude(latitude);
loc.setLongitude(longitude);
loc.setAltitude(random.nextDouble() * 100);
loc.setTime(System.currentTimeMillis());
loc.setSpeed(random.nextFloat());
MyTracksMap map =
(MyTracksMap) getLocalActivityManager().getActivity("tab1");
if (map != null) {
map.onLocationChanged(loc);
}
StatsActivity stats =
(StatsActivity) getLocalActivityManager().getActivity("tab2");
if (stats != null) {
stats.onLocationChanged(loc);
}
try {
trackRecordingService.recordLocation(loc);
} catch (RemoteException e) {
Log.e(MyTracksConstants.TAG, "MyTracks", e);
}
}
}
// public void recordRandomLocation() {
// if (trackRecordingService != null) {
// Location loc = new Location("gps");
// double latitude = 37.5 + random.nextDouble() / 1000;
// double longitude = -120.0 + random.nextDouble() / 1000;
// loc.setLatitude(latitude);
// loc.setLongitude(longitude);
// loc.setAltitude(random.nextDouble() * 100);
// loc.setTime(System.currentTimeMillis());
// loc.setSpeed(random.nextFloat());
// MyTracksMap map =
// (MyTracksMap) getLocalActivityManager().getActivity("tab1");
// if (map != null) {
// map.onLocationChanged(loc);
// }
// StatsActivity stats =
// (StatsActivity) getLocalActivityManager().getActivity("tab2");
// if (stats != null) {
// stats.onLocationChanged(loc);
// }
// try {
// trackRecordingService.recordLocation(loc);
// } catch (RemoteException e) {
// Log.e(MyTracksConstants.TAG, "MyTracks", e);
// }
// }
// }
/**
* Resets status information for sending to MyMaps/Docs.
@@ -70,8 +70,9 @@ import android.widget.Toast;
* @author Leif Hendrik Wilden
*/
public class MyTracksMap extends MapActivity
implements LocationListener, SensorEventListener, View.OnTouchListener,
View.OnClickListener, SharedPreferences.OnSharedPreferenceChangeListener {
implements View.OnTouchListener, View.OnClickListener,
SharedPreferences.OnSharedPreferenceChangeListener {
private static final int TRACKPOINT_BUFFER_SIZE = 1024;
// Saved instance state keys:
// ---------------------------
@@ -82,9 +83,9 @@ public class MyTracksMap extends MapActivity
public static final String KEY_HAVE_GOOD_FIX = "haveGoodFix";
/**
* The currently selected track (or null if nothing selected).
* The ID of the currently selected track (or -1 if nothing selected).
*/
private Track selectedTrack;
private long selectedTrackId = -1;
/**
* The id of the currently recording track.
@@ -97,13 +98,19 @@ public class MyTracksMap extends MapActivity
*/
private boolean keepMyLocationVisible;
/**
* Id of the first location that was seen when reading tracks from the
* provider.
*/
private long firstSeenLocationId = -1;
/**
* Id of the last location that was seen when reading tracks from the
* provider. This is used to determine which locations are new compared to the
* last time the mapOverlay was updated.
*/
private long lastSeenLocationId = -1;
/**
* Magnetic variation.
*/
@@ -130,19 +137,13 @@ public class MyTracksMap extends MapActivity
* A thread with a looper. Post to updateTrackHandler to execute Runnables on
* this thread.
*/
private final HandlerThread updateTrackThread =
new HandlerThread("updateTrackThread");
private HandlerThread updateTrackThread;
/** Handler for updateTrackThread */
private Handler updateTrackHandler;
private MyTracksProviderUtils providerUtils;
/**
* This value is used to decide how many points to display.
*/
private int samplingFrequency = 1;
/**
* A runnable that updates the track from the provider (looking for points
* added after "lastSeenLocationId".
@@ -150,7 +151,7 @@ public class MyTracksMap extends MapActivity
private final Runnable updateTrackRunnable = new Runnable() {
@Override
public void run() {
if (selectedTrack == null) {
if (!isATrackSelected()) {
return;
}
readAllNewTrackPoints();
@@ -163,11 +164,12 @@ public class MyTracksMap extends MapActivity
private Runnable restoreTrackRunnable = new Runnable() {
@Override
public void run() {
if (selectedTrack == null) {
if (!isATrackSelected()) {
return;
}
mapOverlay.clearPoints();
lastSeenLocationId = selectedTrack.getStartId();
firstSeenLocationId = -1;
lastSeenLocationId = -1;
readAllNewTrackPoints();
}
};
@@ -178,7 +180,7 @@ public class MyTracksMap extends MapActivity
private final Runnable restoreWaypointsRunnable = new Runnable() {
@Override
public void run() {
if (selectedTrack == null) {
if (!isATrackSelected()) {
return;
}
@@ -188,7 +190,7 @@ public class MyTracksMap extends MapActivity
// We will silently drop extra waypoints to make the app responsive.
// TODO: Try to only load the waypoints in the view port.
cursor = providerUtils.getWaypointsCursor(
selectedTrack.getId(), 0,
selectedTrackId, 0,
MyTracksConstants.MAX_DISPLAYED_WAYPOINTS_POINTS);
if (cursor != null) {
if (cursor.moveToFirst()) {
@@ -219,10 +221,9 @@ public class MyTracksMap extends MapActivity
public void run() {
uiHandler.post(new Runnable() {
public void run() {
showTrack(selectedTrack);
mapOverlay.setSelectedTrack(selectedTrack);
mapOverlay.setShowEndMarker(selectedTrack == null ||
selectedTrack.getId() != recordingTrackId);
showTrack(selectedTrackId);
mapOverlay.setTrackDrawingEnabled(isATrackSelected());
mapOverlay.setShowEndMarker(!isRecordingSelected());
mapView.invalidate();
busyPane.setVisibility(View.GONE);
updateOptionsButton();
@@ -316,6 +317,7 @@ public class MyTracksMap extends MapActivity
locationManager =
(LocationManager) getSystemService(Context.LOCATION_SERVICE);
updateTrackThread = new HandlerThread("updateTrackThread");
updateTrackThread.start();
updateTrackHandler = new Handler(updateTrackThread.getLooper());
@@ -331,8 +333,7 @@ public class MyTracksMap extends MapActivity
// No track is being recorded. We should not be here.
return;
}
if (selectedTrack == null
|| selectedTrack.getId() != recordingTrackId) {
if (!isRecordingSelected()) {
// No track, or one other than the recording track is selected,
// don't bother.
return;
@@ -348,7 +349,7 @@ public class MyTracksMap extends MapActivity
public void onChange(boolean selfChange) {
Log.d(MyTracksConstants.TAG,
"MyTracksMap: ContentObserver.onChange waypoints");
if (selectedTrack == null) {
if (!isATrackSelected()) {
return;
}
updateTrackHandler.post(restoreWaypointsRunnable);
@@ -371,20 +372,29 @@ public class MyTracksMap extends MapActivity
setSelectedTrack(selectedTrackId);
}
updateOptionsButton();
mapOverlay.setDrawBounds(preferences.getBoolean(
getString(R.string.debug_draw_bounds_key), false));
preferences.registerOnSharedPreferenceChangeListener(this);
}
}
protected void setupZoomControls() {
mapView.setBuiltInZoomControls(true);
/**
* Returns whether there's a track currently selected for display.
*/
private boolean isATrackSelected() {
return selectedTrackId >= 0;
}
@Override
protected void onDestroy() {
// Final cleanup before activity is destroyed.
// May not be called at all in some situations.
Log.d(MyTracksConstants.TAG, "MyTracksMap.onDestroy");
super.onDestroy();
/**
* Returns whether we're currently recording the same track that's selected
* for display.
*/
private boolean isRecordingSelected() {
return isATrackSelected() && selectedTrackId == recordingTrackId;
}
protected void setupZoomControls() {
mapView.setBuiltInZoomControls(true);
}
@Override
@@ -449,10 +459,9 @@ public class MyTracksMap extends MapActivity
// While this activity was paused the user may have deleted the selected
// track. In that case the map overlay needs to be cleared:
Track track = mapOverlay.getSelectedTrack();
if (track != null && !providerUtils.trackExists(track.getId())) {
if (isATrackSelected() && !providerUtils.trackExists(selectedTrackId)) {
// The recording track must have been deleted meanwhile.
mapOverlay.setSelectedTrack(null);
mapOverlay.setTrackDrawingEnabled(false);
mapView.invalidate();
}
}
@@ -514,10 +523,10 @@ public class MyTracksMap extends MapActivity
+ gpsProvider.getName());
}
locationManager.requestLocationUpdates(gpsProvider.getName(),
0 /*minTime*/, 0 /*minDist*/, this);
0 /*minTime*/, 0 /*minDist*/, locationListener);
try {
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
1000 * 60 * 5 /*minTime*/, 0 /*minDist*/, this);
1000 * 60 * 5 /*minTime*/, 0 /*minDist*/, locationListener);
} catch (RuntimeException e) {
// If anything at all goes wrong with getting a cell location do not
// abort. Cell location is not essential to this app.
@@ -535,7 +544,7 @@ public class MyTracksMap extends MapActivity
Log.d(MyTracksConstants.TAG,
"MyTracksMap: Now registering sensor listeners.");
sensorManager.registerListener(
this, compass, SensorManager.SENSOR_DELAY_UI);
sensorListener, compass, SensorManager.SENSOR_DELAY_UI);
}
/**
@@ -545,12 +554,12 @@ public class MyTracksMap extends MapActivity
if (locationManager != null) {
Log.d(MyTracksConstants.TAG,
"MyTracksMap: Now unregistering location listeners.");
locationManager.removeUpdates(this);
locationManager.removeUpdates(locationListener);
}
if (sensorManager != null) {
Log.d(MyTracksConstants.TAG,
"MyTracksMap: Now unregistering sensor listeners.");
sensorManager.unregisterListener(this);
sensorManager.unregisterListener(sensorListener);
}
}
@@ -579,7 +588,7 @@ public class MyTracksMap extends MapActivity
*/
private void updateOptionsButton() {
optionsBtn.setVisibility(
selectedTrack != null ? View.VISIBLE : View.INVISIBLE);
isATrackSelected() ? View.VISIBLE : View.INVISIBLE);
}
/**
@@ -632,12 +641,18 @@ public class MyTracksMap extends MapActivity
/**
* Zooms and pans the map so that the given track is visible.
*
* @param track a given track
* @param trackId a given track ID
*/
public void showTrack(Track track) {
if (track == null || mapView == null || track.getNumberOfPoints() < 2) {
public void showTrack(long trackId) {
if (mapView == null) {
return;
}
Track track = providerUtils.getTrack(trackId);
if (track == null || track.getNumberOfPoints() < 2) {
return;
}
TripStatistics stats = track.getStatistics();
int bottom = stats.getBottom();
int left = stats.getLeft();
@@ -680,24 +695,30 @@ public class MyTracksMap extends MapActivity
* @param trackId a given track id
*/
public void setSelectedTrack(final long trackId) {
if (selectedTrack != null && selectedTrack.getId() == trackId) {
if (selectedTrackId == trackId) {
// Selected track did not change, nothing to do.
mapOverlay.setSelectedTrack(selectedTrack);
mapOverlay.setTrackDrawingEnabled(isATrackSelected());
mapView.invalidate();
updateOptionsButton();
return;
}
if (trackId < 0) {
// Remove selection.
selectedTrack = null;
mapOverlay.setSelectedTrack(null);
selectedTrackId = -1;
mapOverlay.setTrackDrawingEnabled(false);
mapOverlay.clearWaypoints();
updateOptionsButton();
mapView.invalidate();
return;
}
busyPane.setVisibility(View.VISIBLE);
selectedTrack = providerUtils.getTrack(trackId);
selectedTrackId = trackId;
loadSelectedTrack();
}
private void loadSelectedTrack() {
updateTrackHandler.post(restoreTrackRunnable);
updateTrackHandler.post(restoreWaypointsRunnable);
updateTrackHandler.post(setSelectedTrackRunnable);
@@ -753,8 +774,7 @@ public class MyTracksMap extends MapActivity
menu.setHeaderTitle(R.string.tracklist_this_track);
menu.add(0, MyTracksConstants.MENU_EDIT, 0,
R.string.tracklist_edit_track);
if (!MyTracks.getInstance().isRecording() || (selectedTrack != null
&& selectedTrack.getId() != recordingTrackId)) {
if (!MyTracks.getInstance().isRecording() || !isRecordingSelected()) {
menu.add(0, MyTracksConstants.MENU_SEND_TO_GOOGLE, 0,
R.string.tracklist_send_to_google);
SubMenu share = menu.addSubMenu(0, MyTracksConstants.MENU_SHARE, 0,
@@ -791,7 +811,7 @@ public class MyTracksMap extends MapActivity
@Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
if (!super.onMenuItemSelected(featureId, item)) {
if (selectedTrack != null) {
if (isATrackSelected()) {
MyTracks.getInstance().onActivityResult(
MyTracksConstants.getActionFromMenuId(item.getItemId()), RESULT_OK,
new Intent());
@@ -852,8 +872,7 @@ public class MyTracksMap extends MapActivity
public void onClick(View v) {
if (v == messagePane) {
launchMyLocationSettings();
}
if (v == optionsBtn) {
} else if (v == optionsBtn) {
optionsBtn.performLongClick();
}
}
@@ -895,139 +914,155 @@ public class MyTracksMap extends MapActivity
sharedPreferences.getLong(
getString(R.string.recording_track_key),
-1);
if (selectedTrack != null) {
mapOverlay.setShowEndMarker(
selectedTrack.getId() != recordingTrackId);
if (isATrackSelected()) {
mapOverlay.setShowEndMarker(!isRecordingSelected());
mapView.postInvalidate();
}
} else if (key.equals(getString(R.string.debug_draw_bounds_key))) {
mapOverlay.setDrawBounds(
sharedPreferences.getBoolean(
getString(R.string.debug_draw_bounds_key), false));
}
}
});
}
}
@Override
public void onProviderEnabled(String provider) {
if (provider.equals(MyTracksConstants.GPS_PROVIDER)) {
messageText.setText(R.string.wait_for_fix);
}
}
@Override
public void onProviderDisabled(String provider) {
if (provider.equals(MyTracksConstants.GPS_PROVIDER)) {
messageText.setText(R.string.status_enable_gps);
messagePane.setVisibility(View.VISIBLE);
messagePane.setOnClickListener(this);
screen.requestLayout();
}
}
@Override
public void onLocationChanged(Location location) {
if (location.getProvider().equals(MyTracksConstants.GPS_PROVIDER)) {
// Recalculate the variation if there was a jump in location > 1km:
if (currentLocation == null
|| location.distanceTo(currentLocation) > 1000) {
setVariation(location);
private final LocationListener locationListener = new LocationListener() {
@Override
public void onProviderEnabled(String provider) {
if (provider.equals(MyTracksConstants.GPS_PROVIDER)) {
messageText.setText(R.string.wait_for_fix);
}
currentLocation = location;
boolean haveGoodFixNow =
currentLocation.getAccuracy() < minRequiredAccuracy;
if (haveGoodFixNow != haveGoodFix) {
haveGoodFix = haveGoodFixNow;
messagePane.setVisibility(haveGoodFix ? View.GONE : View.VISIBLE);
}
@Override
public void onProviderDisabled(String provider) {
if (provider.equals(MyTracksConstants.GPS_PROVIDER)) {
messageText.setText(R.string.status_enable_gps);
messagePane.setVisibility(View.VISIBLE);
messagePane.setOnClickListener(MyTracksMap.this);
screen.requestLayout();
}
showCurrentLocation();
} else {
Log.d(MyTracksConstants.TAG,
"MyTracksMap: Network location update received.");
}
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
if (provider.equals(MyTracksConstants.GPS_PROVIDER)) {
switch (status) {
case LocationProvider.OUT_OF_SERVICE:
case LocationProvider.TEMPORARILY_UNAVAILABLE:
haveGoodFix = false;
messagePane.setVisibility(View.VISIBLE);
@Override
public void onLocationChanged(Location location) {
if (location.getProvider().equals(MyTracksConstants.GPS_PROVIDER)) {
// Recalculate the variation if there was a jump in location > 1km:
if (currentLocation == null
|| location.distanceTo(currentLocation) > 1000) {
setVariation(location);
}
currentLocation = location;
boolean haveGoodFixNow =
currentLocation.getAccuracy() < minRequiredAccuracy;
if (haveGoodFixNow != haveGoodFix) {
haveGoodFix = haveGoodFixNow;
messagePane.setVisibility(haveGoodFix ? View.GONE : View.VISIBLE);
screen.requestLayout();
break;
}
showCurrentLocation();
} else {
Log.d(MyTracksConstants.TAG,
"MyTracksMap: Network location update received.");
}
}
}
@Override
public void onSensorChanged(SensorEvent se) {
synchronized (this) {
float magneticHeading = se.values[0];
double heading = magneticHeading + variation;
if (mapOverlay.setHeading((float) heading)) {
mapView.invalidate();
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
if (provider.equals(MyTracksConstants.GPS_PROVIDER)) {
switch (status) {
case LocationProvider.OUT_OF_SERVICE:
case LocationProvider.TEMPORARILY_UNAVAILABLE:
haveGoodFix = false;
messagePane.setVisibility(View.VISIBLE);
screen.requestLayout();
break;
}
}
}
}
};
@Override
public void onAccuracyChanged(Sensor s, int accuracy) {
// do nothing
}
/**
* Set the sampling frequency from the total number of points in the track.
*
* @param track The track to read the total number of points from.
*/
private void setSamplingFrequency(Track track) {
long totalLocations = track.getStopId() - track.getStartId();
private final SensorEventListener sensorListener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent se) {
synchronized (this) {
float magneticHeading = se.values[0];
double heading = magneticHeading + variation;
if (mapOverlay.setHeading((float) heading)) {
mapView.invalidate();
}
}
}
@Override
public void onAccuracyChanged(Sensor s, int accuracy) {
// do nothing
}
};
// Limit the number of map points readings.
samplingFrequency =
(int) (1 +
totalLocations / MyTracksConstants.TARGET_DISPLAYED_TRACK_POINTS);
Log.i(MyTracksConstants.TAG, "Sampling locations: " + samplingFrequency);
}
private void readAllNewTrackPoints() {
Cursor cursor = null;
// Refetch the track to get the latest StopId
selectedTrack = providerUtils.getTrack(selectedTrack.getId());
long totalLocations = selectedTrack.getStopId() -
selectedTrack.getStartId();
int numPoints = mapOverlay.getNumLocations();
if (numPoints >= MyTracksConstants.MAX_DISPLAYED_TRACK_POINTS) {
// We're about to exceed the maximum allowed number of points, so reload
// the whole track with fewer points (the sampling frequency will be
// lower).
loadSelectedTrack();
return;
}
long lastStoredLocationId =
providerUtils.getLastLocationId(selectedTrackId);
int samplingFrequency = -1;
Location location = new Location("");
for (;;) {
Cursor cursor = null;
try {
cursor = providerUtils.getLocationsCursor(selectedTrackId,
lastSeenLocationId + 1, TRACKPOINT_BUFFER_SIZE, false);
if (cursor == null || !cursor.moveToFirst()) {
// No (more) data
break;
}
setSamplingFrequency(selectedTrack);
int bufferSize = 1024;
int points = 0;
while (lastSeenLocationId < selectedTrack.getStopId()) {
cursor = providerUtils.getLocationsCursor(
selectedTrack.getId(), lastSeenLocationId, bufferSize, false);
if (cursor != null && cursor.moveToFirst()) {
final int idColumnIdx = cursor.getColumnIndexOrThrow(
TrackPointsColumns._ID);
while (cursor.moveToNext()) {
points++;
Location location = providerUtils.createLocation(cursor);
lastSeenLocationId = cursor.getLong(idColumnIdx);
// Include a point if it fits one of the following criteria:
// - Has the mod for the sampling frequency.
// - Is the first point.
// - Is the last point and we are not recording this track.
if (!MyTracksUtils.isValidLocation(location) ||
points % samplingFrequency == 0 ||
points == 0 ||
(recordingTrackId != selectedTrack.getId() &&
points == (totalLocations - 1))) {
mapOverlay.addLocation(location);
do {
long locationId = cursor.getLong(idColumnIdx);
lastSeenLocationId = locationId;
if (firstSeenLocationId == -1) {
// This was our first point, keep its ID
firstSeenLocationId = locationId;
}
if (samplingFrequency == -1) {
// Now we already have at least one point, calculate the sampling
// frequency
long numTotalPoints = lastStoredLocationId - firstSeenLocationId;
samplingFrequency = (int) (1 +
numTotalPoints / MyTracksConstants.TARGET_DISPLAYED_TRACK_POINTS);
}
providerUtils.fillLocation(cursor, location);
// Include a point if it fits one of the following criteria:
// - Has the mod for the sampling frequency (includes first point).
// - Is the last point and we are not recording this track.
// - The point is a segment split
if (numPoints % samplingFrequency == 0 ||
(!isRecordingSelected() && locationId == lastStoredLocationId) ||
!MyTracksUtils.isValidLocation(location)) {
// Only allocate a new location if it is going to be kept around.
mapOverlay.addLocation(new Location(location));
}
numPoints++;
} while (cursor.moveToNext());
} finally {
if (cursor != null) {
cursor.close();
}
} else {
lastSeenLocationId += bufferSize;
}
cursor.close();
cursor = null;
}
mapView.postInvalidate();
}
@@ -15,10 +15,8 @@
*/
package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.util.MyTracksUtils;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapView;
import com.google.android.maps.Overlay;
@@ -31,6 +29,7 @@ import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.location.Location;
@@ -57,10 +56,11 @@ public class MyTracksOverlay extends Overlay {
private final ArrayList<Waypoint> waypoints = new ArrayList<Waypoint>();
private final ArrayList<Location> points = new ArrayList<Location>(1024);
private Track selectedTrack;
private boolean trackDrawingEnabled;
private int lastHeading = 0;
private Location myLocation;
private boolean showEndMarker = true;
private boolean drawBounds;
public MyTracksOverlay(Context context) {
this.context = context;
@@ -121,14 +121,6 @@ public class MyTracksOverlay extends Overlay {
errorCirclePaint.setAntiAlias(true);
}
public void setSelectedTrack(Track track) {
selectedTrack = track;
}
public Track getSelectedTrack() {
return selectedTrack;
}
/**
* Add a location to the map overlay.
*
@@ -137,19 +129,35 @@ public class MyTracksOverlay extends Overlay {
* @param l the location to add
*/
public void addLocation(Location l) {
points.add(l);
if (l != null) {
synchronized (points) {
points.add(l);
}
}
}
public void addWaypoint(Waypoint wpt) {
waypoints.add(wpt);
}
public int getNumLocations() {
synchronized (points) {
return points.size();
}
}
public void clearWaypoints() {
waypoints.clear();
}
public void clearPoints() {
points.clear();
synchronized (points) {
points.clear();
}
}
public void setTrackDrawingEnabled(boolean trackDrawingEnabled) {
this.trackDrawingEnabled = trackDrawingEnabled;
}
public void setShowEndMarker(boolean showEndMarker) {
@@ -158,15 +166,23 @@ public class MyTracksOverlay extends Overlay {
@Override
public void draw(Canvas canvas, MapView mapView, boolean shadow) {
if (shadow) {
return;
}
// Draw the selected track:
drawTrack(canvas, mapView, selectedTrack, true);
if (trackDrawingEnabled) {
// Draw the selected track:
drawTrack(canvas, mapView);
// Draw the waypoints:
// Draw the waypoints:
drawWaypoints(canvas, mapView);
}
// Draw the current location
drawMyLocation(canvas, mapView);
}
private void drawWaypoints(Canvas canvas, MapView mapView) {
ArrayList<Waypoint> currentWaypoints = waypoints;
for (int i = 1; i < currentWaypoints.size(); i++) {
Waypoint wpt = currentWaypoints.get(i);
@@ -189,11 +205,14 @@ public class MyTracksOverlay extends Overlay {
}
canvas.restore();
}
}
private void drawMyLocation(Canvas canvas, MapView mapView) {
// Draw the arrow icon:
if (myLocation == null) {
return;
}
GeoPoint geoPoint = new GeoPoint(
(int) (myLocation.getLatitude() * 1E6),
(int) (myLocation.getLongitude() * 1E6));
@@ -210,79 +229,119 @@ public class MyTracksOverlay extends Overlay {
canvas.drawCircle(pt.x, pt.y, radius, errorCirclePaint);
}
public void drawTrack(Canvas canvas, MapView mapView, Track track,
boolean selected) {
if (track == null) {
return;
}
if (points.size() < 2) {
return;
}
private void drawTrack(Canvas canvas, MapView mapView) {
Path path;
Point pt = new Point();
Location lastValidLocation;
int locLon = 0, locLat = 0;
GeoPoint firstGeoPoint = null;
boolean lastLocValid;
// Get the current viewing window:
int w = mapView.getLongitudeSpan();
int h = mapView.getLatitudeSpan();
int cx = mapView.getMapCenter().getLongitudeE6();
int cy = mapView.getMapCenter().getLatitudeE6();
Rect rect = new Rect(cx - w, cy - h, cx + w, cy + h);
Rect viewRect = new Rect(cx - w, cy - h, cx + w, cy + h);
Point pt = new Point();
GeoPoint geoPoint;
Location loc;
Location lastValidLocation = null;
Path path;
boolean wasInside;
// Do as much allocation and preparation outside the loop over track
// points:
wasInside = false;
int locLon = 0, locLat = 0, minLon, maxLon, minLat, maxLat;
int lastLocLon = (int) (points.get(0).getLongitude() * 1E6);
int lastLocLat = (int) (points.get(0).getLatitude() * 1E6);
// Loop over track points:
path = new Path();
for (int i = 1; i < points.size(); i++) {
loc = points.get(i);
if (loc == null) {
continue;
// Global bounding box, including points not visible
int allMinLat, allMinLon, allMaxLat, allMaxLon;
synchronized (points) {
int numPoints = points.size();
if (numPoints < 2) {
return;
}
locLon = (int) (loc.getLongitude() * 1E6);
locLat = (int) (loc.getLatitude() * 1E6);
// Draw a line segment if it's inside the viewing window:
if (locLat < 90E6 && lastLocLat < 90E6) {
lastValidLocation = loc;
minLon = Math.min(locLon, lastLocLon);
maxLon = Math.max(locLon, lastLocLon);
minLat = Math.min(locLat, lastLocLat);
maxLat = Math.max(locLat, lastLocLat);
if (rect.intersects(minLon, minLat, maxLon, maxLat)) {
if (!wasInside) {
geoPoint = new GeoPoint(lastLocLat, lastLocLon);
mapView.getProjection().toPixels(geoPoint, pt);
path.moveTo(pt.x, pt.y);
GeoPoint geoPoint;
Location loc;
int minLon, maxLon, minLat, maxLat;
lastValidLocation = points.get(0);
int lastLocLon = allMinLon = allMaxLon = (int) (lastValidLocation.getLongitude() * 1E6);
int lastLocLat = allMinLat = allMaxLat = (int) (lastValidLocation.getLatitude() * 1E6);
lastLocValid = MyTracksUtils.isValidLocation(lastValidLocation);
boolean lastLocVisible = false;
path = new Path();
// Loop over track points:
path.incReserve(numPoints);
for (int i = 1; i < numPoints; i++) {
loc = points.get(i);
boolean locValid = MyTracksUtils.isValidLocation(loc);
boolean locVisible = false;
if (locValid) {
locLon = (int) (loc.getLongitude() * 1E6);
locLat = (int) (loc.getLatitude() * 1E6);
if (firstGeoPoint == null) {
// Found the starting point
firstGeoPoint = new GeoPoint(locLat, locLon);
}
// If both the current and previous locations were valid
if (lastLocValid) {
lastValidLocation = loc;
// Get the bounding box of the segment about to be drawn
if (locLon > lastLocLon) {
minLon = lastLocLon;
maxLon = locLon;
} else {
minLon = locLon;
maxLon = lastLocLon;
}
if (locLat > lastLocLat) {
minLat = lastLocLat;
maxLat = locLat;
} else {
minLat = locLat;
maxLat = lastLocLat;
}
if (drawBounds) {
allMaxLat = Math.max(allMaxLat, maxLat);
allMinLat = Math.min(allMinLat, minLat);
allMaxLon = Math.max(allMaxLon, maxLon);
allMinLon = Math.min(allMinLon, minLon);
}
// See if that bounding box intersects the viewable bounding box
// Assume that if it does, the location is visible
locVisible = viewRect.intersects(minLon, minLat, maxLon, maxLat);
if (locVisible) {
// If the previous point wasn't drawn, start at its position
if (!lastLocVisible) {
geoPoint = new GeoPoint(lastLocLat, lastLocLon);
mapView.getProjection().toPixels(geoPoint, pt);
path.moveTo(pt.x, pt.y);
}
// Draw a line to the new point
geoPoint = new GeoPoint(locLat, locLon);
mapView.getProjection().toPixels(geoPoint, pt);
path.lineTo(pt.x, pt.y);
}
}
geoPoint = new GeoPoint(locLat, locLon);
mapView.getProjection().toPixels(geoPoint, pt);
path.lineTo(pt.x, pt.y);
wasInside = rect.contains(locLon, locLat);
}
} else {
wasInside = false;
lastLocLon = locLon;
lastLocLat = locLat;
lastLocValid = locValid;
lastLocVisible = locVisible;
}
lastLocLon = locLon;
lastLocLat = locLat;
}
canvas.drawPath(path, selectedTrackPaint);
if (drawBounds) {
drawBoundingBox(canvas, mapView,
allMinLat, allMinLon, allMaxLat, allMaxLon);
}
// Draw the "End" marker:
Location currentLastValidLocation = lastValidLocation;
if (showEndMarker && currentLastValidLocation != null) {
if (showEndMarker && lastValidLocation != null) {
canvas.save();
geoPoint = MyTracksUtils.getGeoPoint(currentLastValidLocation);
GeoPoint geoPoint = MyTracksUtils.getGeoPoint(lastValidLocation);
mapView.getProjection().toPixels(geoPoint, pt);
canvas.translate(pt.x - (markerWidth / 2), pt.y - markerHeight);
endMarker.draw(canvas);
@@ -290,22 +349,46 @@ public class MyTracksOverlay extends Overlay {
}
// Draw the "Start" marker:
for (int i = 0; i < points.size(); i++) {
loc = points.get(i);
if (loc.getLatitude() < 90) {
locLon = (int) (loc.getLongitude() * 1E6);
locLat = (int) (loc.getLatitude() * 1E6);
geoPoint = new GeoPoint(locLat, locLon);
mapView.getProjection().toPixels(geoPoint, pt);
canvas.save();
canvas.translate(pt.x - (markerWidth / 2), pt.y - markerHeight);
startMarker.draw(canvas);
canvas.restore();
break;
}
if (firstGeoPoint != null) {
mapView.getProjection().toPixels(firstGeoPoint, pt);
canvas.save();
canvas.translate(pt.x - (markerWidth / 2), pt.y - markerHeight);
startMarker.draw(canvas);
canvas.restore();
}
}
private void drawBoundingBox(Canvas canvas, MapView mapView, int allMinLat,
int allMinLon, int allMaxLat, int allMaxLon) {
// Transform coordinates
GeoPoint maxPoint = new GeoPoint(allMaxLat, allMaxLon);
GeoPoint minPoint = new GeoPoint(allMinLat, allMinLon);
Point minPt = new Point();
Point maxPt = new Point();
mapView.getProjection().toPixels(minPoint, minPt);
mapView.getProjection().toPixels(maxPoint, maxPt);
Rect allBounds = new Rect(minPt.x, minPt.y, maxPt.x, maxPt.y);
// Prepare a green paint
Paint boundingBoxPaint = new Paint();
boundingBoxPaint.setColor(context.getResources().getColor(R.color.green));
boundingBoxPaint.setStrokeWidth(2);
boundingBoxPaint.setStyle(Paint.Style.STROKE);
boundingBoxPaint.setAntiAlias(true);
// Disable clipping
canvas.save();
canvas.clipRect(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY,
Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY,
Region.Op.REPLACE);
// Draw the bounding box
canvas.drawRect(allBounds, boundingBoxPaint);
// Re-enable clipping
canvas.restore();
}
@Override
public boolean draw(Canvas canvas, MapView mapView, boolean shadow,
long when) {
@@ -374,4 +457,8 @@ public class MyTracksOverlay extends Overlay {
}
return super.onTap(p, mapView);
}
public void setDrawBounds(boolean drawBounds) {
this.drawBounds = drawBounds;
}
}
@@ -115,7 +115,7 @@ public class MyTracksSettings extends PreferenceActivity {
announcementFrequency.setEnabled(false);
announcementFrequency.setValue("-1");
announcementFrequency.setSummary(
R.string.settings_announcement_not_available_summary);
R.string.settings_not_available_summary);
}
}
@@ -135,7 +135,6 @@ public class MyTracksWaypointDetails extends Activity
iconId = R.drawable.ylw_pushpin;
TripStatistics waypointStats = waypoint.getStatistics();
utils.setAllStats(waypointStats);
utils.setTime(R.id.total_time_register, waypointStats.getTotalTime());
utils.setAltitude(
R.id.elevation_register, waypoint.getLocation().getAltitude());
break;
@@ -15,8 +15,6 @@
*/
package com.google.android.apps.mytracks;
import com.google.android.maps.mytracks.R;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Handler;
@@ -50,24 +48,22 @@ public class NavControls {
/**
* A touchable image view.
* When touched it changes the navigation control icons accordingly.
*/
public class TouchLayout extends RelativeLayout {
private final ImageView arrow;
private class TouchLayout extends RelativeLayout implements Runnable {
private final boolean isLeft;
private final ImageView icon;
public TouchLayout(Context context, boolean isLeft) {
super(context);
arrow = new ImageView(context);
arrow.setImageDrawable(context.getResources().getDrawable(
isLeft ? R.drawable.btn_arrow_left : R.drawable.btn_arrow_right));
icon = new ImageView(context);
this.isLeft = isLeft;
this.icon = new ImageView(context);
icon.setVisibility(View.GONE);
addView(arrow);
addView(icon);
icon.setPadding((isLeft ? 15 : 10), 27, 15, 0);
}
public void setIcon(Drawable drawable) {
public void setIcon(int iconId) {
Drawable drawable = getContext().getResources().getDrawable(iconId);
icon.setImageDrawable(drawable);
icon.setVisibility(View.VISIBLE);
}
@@ -77,18 +73,11 @@ public class NavControls {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
setPressed(true);
hide();
if (this == prevImage) {
if (prevRunnable != null) {
handler.post(prevRunnable);
return true;
}
} else if (this == nextImage) {
if (nextRunnable != null) {
handler.post(nextRunnable);
return true;
}
}
shiftIcons(isLeft);
// Call the user back
handler.post(this);
break;
case MotionEvent.ACTION_UP:
setPressed(false);
@@ -96,55 +85,69 @@ public class NavControls {
}
return super.onTouchEvent(event);
}
@Override
public void run() {
touchRunnable.run();
setPressed(false);
}
}
private final Context context;
private final Runnable prevRunnable;
private final Runnable nextRunnable;
private final Handler handler = new Handler();
private final Runnable dismissControls = new Runnable() {
public void run() {
hide();
}
};
private final TouchLayout prevImage;
private final TouchLayout nextImage;
private final int[] leftIcons;
private final int[] rightIcons;
private final Runnable touchRunnable;
private boolean isVisible = false;
private boolean hasNext = true;
private boolean hasPrev = true;
private int currentIcons;
public NavControls(Context context, ViewGroup container,
Runnable prevRunnable, Runnable nextRunnable) {
this.context = context;
this.prevRunnable = prevRunnable;
this.nextRunnable = nextRunnable;
int[] leftIcons, int[] rightIcons,
Runnable touchRunnable) {
this.leftIcons = leftIcons;
this.rightIcons = rightIcons;
this.touchRunnable = touchRunnable;
if (leftIcons.length != rightIcons.length || leftIcons.length < 1) {
throw new IllegalArgumentException("Invalid icons specified");
}
if (touchRunnable == null) {
throw new NullPointerException("Runnable cannot be null");
}
LayoutParams prevParams = new LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
prevParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
prevParams.addRule(RelativeLayout.CENTER_VERTICAL);
prevImage = new TouchLayout(context, true);
prevImage.setLayoutParams(prevParams);
prevImage.setVisibility(View.INVISIBLE);
container.addView(prevImage);
LayoutParams nextParams = new LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
prevParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
nextParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
prevParams.addRule(RelativeLayout.CENTER_VERTICAL);
nextParams.addRule(RelativeLayout.CENTER_VERTICAL);
nextImage = new TouchLayout(context, false);
prevImage = new TouchLayout(context, true);
nextImage.setLayoutParams(nextParams);
prevImage.setLayoutParams(prevParams);
nextImage.setVisibility(View.INVISIBLE);
prevImage.setVisibility(View.INVISIBLE);
container.addView(prevImage);
container.addView(nextImage);
}
public void setHasNext(boolean hasNext) {
this.hasNext = hasNext;
}
public void setHasPrev(boolean hasPrev) {
this.hasPrev = hasPrev;
prevImage.setIcon(leftIcons[0]);
nextImage.setIcon(rightIcons[0]);
this.currentIcons = 0;
}
private void keepVisible() {
@@ -156,20 +159,18 @@ public class NavControls {
public void show() {
if (!isVisible) {
if (prevRunnable != null && hasPrev) {
SHOW_PREV_ANIMATION.setDuration(500);
SHOW_PREV_ANIMATION.startNow();
prevImage.setPressed(false);
prevImage.setAnimation(SHOW_PREV_ANIMATION);
prevImage.setVisibility(View.VISIBLE);
}
if (nextRunnable != null && hasNext) {
SHOW_NEXT_ANIMATION.setDuration(500);
SHOW_NEXT_ANIMATION.startNow();
nextImage.setPressed(false);
nextImage.setAnimation(SHOW_NEXT_ANIMATION);
nextImage.setVisibility(View.VISIBLE);
}
SHOW_PREV_ANIMATION.setDuration(500);
SHOW_PREV_ANIMATION.startNow();
prevImage.setPressed(false);
prevImage.setAnimation(SHOW_PREV_ANIMATION);
prevImage.setVisibility(View.VISIBLE);
SHOW_NEXT_ANIMATION.setDuration(500);
SHOW_NEXT_ANIMATION.startNow();
nextImage.setPressed(false);
nextImage.setAnimation(SHOW_NEXT_ANIMATION);
nextImage.setVisibility(View.VISIBLE);
isVisible = true;
keepVisible();
} else {
@@ -188,33 +189,25 @@ public class NavControls {
public void hide() {
isVisible = false;
if (prevRunnable != null) {
if (hasPrev) {
prevImage.setAnimation(HIDE_PREV_ANIMATION);
HIDE_PREV_ANIMATION.setDuration(500);
HIDE_PREV_ANIMATION.startNow();
} else {
prevImage.clearAnimation();
}
prevImage.setVisibility(View.INVISIBLE);
}
if (nextRunnable != null) {
if (hasNext) {
nextImage.setAnimation(HIDE_NEXT_ANIMATION);
HIDE_NEXT_ANIMATION.setDuration(500);
HIDE_NEXT_ANIMATION.startNow();
} else {
nextImage.clearAnimation();
}
nextImage.setVisibility(View.INVISIBLE);
}
prevImage.setAnimation(HIDE_PREV_ANIMATION);
HIDE_PREV_ANIMATION.setDuration(500);
HIDE_PREV_ANIMATION.startNow();
prevImage.setVisibility(View.INVISIBLE);
nextImage.setAnimation(HIDE_NEXT_ANIMATION);
HIDE_NEXT_ANIMATION.setDuration(500);
HIDE_NEXT_ANIMATION.startNow();
nextImage.setVisibility(View.INVISIBLE);
}
public void setLeftIcon(int resourceId) {
prevImage.setIcon(context.getResources().getDrawable(resourceId));
public int getCurrentIcons() {
return currentIcons;
}
public void setRightIcon(int resourceId) {
nextImage.setIcon(context.getResources().getDrawable(resourceId));
private void shiftIcons(boolean isLeft) {
// Increment or decrement by one, with wrap around
currentIcons = (currentIcons + leftIcons.length + (isLeft ? -1 : 1)) % leftIcons.length;
prevImage.setIcon(leftIcons[currentIcons]);
nextImage.setIcon(rightIcons[currentIcons]);
}
}
@@ -49,14 +49,12 @@ import android.widget.Toast;
* @author Sandor Dornbush
*/
public class StatsActivity extends Activity
implements OnSharedPreferenceChangeListener, LocationListener {
implements OnSharedPreferenceChangeListener {
private final StatsUtilities utils;
private UIUpdateThread thread;
private ContentObserver observer;
private LocationManager locationManager;
/**
* The id of the currently selected track.
@@ -96,7 +94,6 @@ public class StatsActivity extends Activity
private boolean showCurrentSegment = false;
private final MyTracksProviderUtils providerUtils;
private Track track = null;
/**
* A runnable for posting to the UI thread. Will update the total time field.
@@ -179,11 +176,9 @@ public class StatsActivity extends Activity
preferences.getBoolean(getString(R.string.report_speed_key), true);
checkLiveTrack();
restoreStats();
updateLocation(null);
showUnknownLocation();
preferences.registerOnSharedPreferenceChangeListener(this);
}
locationManager =
(LocationManager) getSystemService(Context.LOCATION_SERVICE);
utils.setMetricUnits(metricUnits);
utils.setReportSpeed(displaySpeed);
utils.updateUnits();
@@ -214,7 +209,7 @@ public class StatsActivity extends Activity
activityOnTop = true;
checkLiveTrack();
restoreStats();
updateLocation(null);
showUnknownLocation();
super.onResume();
}
@@ -234,7 +229,7 @@ public class StatsActivity extends Activity
-1);
checkLiveTrack();
restoreStats();
updateLocation(null);
showUnknownLocation();
} else if (key.equals(getString(R.string.recording_track_key))) {
recordingTrackId =
sharedPreferences.getLong(
@@ -242,7 +237,7 @@ public class StatsActivity extends Activity
-1);
checkLiveTrack();
restoreStats();
updateLocation(null);
showUnknownLocation();
} else if (key.equals(getString(R.string.metric_units_key))) {
metricUnits =
sharedPreferences.getBoolean(
@@ -299,29 +294,36 @@ public class StatsActivity extends Activity
return super.onOptionsItemSelected(item);
}
@Override
public void onLocationChanged(Location l) {
if (selectedTrackIsRecording()) {
updateLocation(l);
private final LocationListener locationListener = new LocationListener() {
@Override
public void onLocationChanged(Location l) {
if (selectedTrackIsRecording()) {
showLocation(l);
}
}
}
@Override
public void onProviderDisabled(String provider) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onProviderDisabled(String provider) {
// Do nothing
}
@Override
public void onProviderEnabled(String provider) {
// Do nothing
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
// Do nothing
}
};
/**
* Registers to receive location updates from the GPS location provider.
*/
private void registerLocationListener() {
LocationManager locationManager =
(LocationManager) getSystemService(Context.LOCATION_SERVICE);
if (locationManager != null) {
LocationProvider gpsProvider =
locationManager.getProvider(MyTracksConstants.GPS_PROVIDER);
@@ -334,7 +336,7 @@ public class StatsActivity extends Activity
+ gpsProvider.getName());
}
locationManager.requestLocationUpdates(gpsProvider.getName(),
0/*minTime*/, 0/*minDist*/, this);
0/*minTime*/, 0/*minDist*/, locationListener);
}
}
@@ -342,8 +344,10 @@ public class StatsActivity extends Activity
* Unregisters all location listener.
*/
private void unregisterLocationListener() {
LocationManager locationManager =
(LocationManager) getSystemService(Context.LOCATION_SERVICE);
if (locationManager != null) {
locationManager.removeUpdates(this);
locationManager.removeUpdates(locationListener);
}
}
@@ -364,7 +368,7 @@ public class StatsActivity extends Activity
return;
}
track = providerUtils.getTrack(selectedTrackId);
Track track = providerUtils.getTrack(selectedTrackId);
if (track == null || track.getStatistics() == null) {
utils.setAllToUnknown();
return;
@@ -421,17 +425,17 @@ public class StatsActivity extends Activity
*
* @param l may be null (will set location fields to unknown)
*/
private void updateLocation(Location l) {
if (l == null) {
utils.setUnknown(R.id.elevation_register);
utils.setUnknown(R.id.latitude_register);
utils.setUnknown(R.id.longitude_register);
utils.setUnknown(R.id.speed_register);
} else {
utils.setAltitude(R.id.elevation_register, l.getAltitude());
utils.setLatLong(R.id.latitude_register, l.getLatitude());
utils.setLatLong(R.id.longitude_register, l.getLongitude());
utils.setSpeed(R.id.speed_register, l.getSpeed() * 3.6);
}
private void showLocation(Location l) {
utils.setAltitude(R.id.elevation_register, l.getAltitude());
utils.setLatLong(R.id.latitude_register, l.getLatitude());
utils.setLatLong(R.id.longitude_register, l.getLongitude());
utils.setSpeed(R.id.speed_register, l.getSpeed() * 3.6);
}
private void showUnknownLocation() {
utils.setUnknown(R.id.elevation_register);
utils.setUnknown(R.id.latitude_register);
utils.setUnknown(R.id.longitude_register);
utils.setUnknown(R.id.speed_register);
}
}
@@ -20,13 +20,13 @@ import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import android.app.Activity;
import android.util.Log;
import android.widget.TextView;
import java.text.DecimalFormat;
import java.text.NumberFormat;
/**
* Various utility functions for views that display statistics information.
*
@@ -218,7 +218,7 @@ public class StatsUtilities {
setGrade(R.id.max_grade_register, maxGrade);
}
public void setAllStats(TripStatistics stats) {
public void setAllStats(TripStatistics stats) {
setTime(R.id.moving_time_register, stats.getMovingTime());
setDistance(R.id.total_distance_register, stats.getTotalDistance() / 1000);
setSpeed(R.id.average_speed_register, stats.getAverageSpeed() * 3.6);
@@ -230,6 +230,8 @@ public class StatsUtilities {
setAltitude(R.id.elevation_gain_register, stats.getTotalElevationGain());
setGrade(R.id.min_grade_register, stats.getMinGrade());
setGrade(R.id.max_grade_register, stats.getMaxGrade());
setTime(R.id.total_time_register, stats.getTotalTime());
}
public void setSpeedLabel(int id, int speedString, int paceString) {
@@ -21,6 +21,8 @@ import android.database.Cursor;
import android.location.Location;
import android.net.Uri;
import java.util.List;
/**
* Utility to access data from the mytracks content provider.
*
@@ -184,6 +186,15 @@ public interface MyTracksProviderUtils {
* @return a Track object, or null if not found
*/
Track getTrack(long id);
/**
* Retrieves all tracks without track points. If no tracks exist an empty
* list will be returned. Use {@link #getTrackPoints(Track, int)} to load
* the track points.
*
* @return a list of all the recorded tracks
*/
List<Track> getAllTracks();
/**
* Loads the track points for a given track.
@@ -203,9 +214,24 @@ public interface MyTracksProviderUtils {
*
* @param track to load locations for
* @param buffer an array of locations to fill
* @deprecated use {@link #fillTrackPoints} instead
*/
@Deprecated
void getTrackPoints(Track track, TrackBuffer buffer);
/**
* Fetches some number of locations for the given track.
*
* This is designed to be used to stream through large tracks without loading
* all points into memory.
* This method will reuse the Location objects in the buffer. If you need a
* Location object copy the object.
*
* @param track to load locations for
* @param buffer an array of locations to fill
*/
void fillTrackPoints(Track track, TrackBuffer buffer);
/**
* Creates a cursor over the tracks provider with a given selection.
* Caller gets to own the returned cursor. Don't forget to close it.
@@ -293,7 +319,15 @@ public interface MyTracksProviderUtils {
* @return a new location object
*/
Location createLocation(Cursor cursor);
/**
* Fill a location object with values from a given cursor.
*
* @param cursor a cursor pointing at a db or provider with locations
* @param location a location object to be overwritten
*/
void fillLocation(Cursor cursor, Location location);
/**
* Creates a waypoint object from a given cursor.
*
@@ -24,6 +24,9 @@ import android.location.Location;
import android.net.Uri;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
/**
* Helper class providing easy access to locations and tracks in the
* MyTracksProvider. All static members.
@@ -168,6 +171,15 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
@Override
public Location createLocation(Cursor cursor) {
Location location = new Location("");
fillLocation(cursor, location);
return location;
}
@Override
public void fillLocation(Cursor cursor, Location location) {
location.reset();
int idxLatitude = cursor.getColumnIndexOrThrow(TrackPointsColumns.LATITUDE);
int idxLongitude =
cursor.getColumnIndexOrThrow(TrackPointsColumns.LONGITUDE);
@@ -177,7 +189,6 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
int idxAccuracy = cursor.getColumnIndexOrThrow(TrackPointsColumns.ACCURACY);
int idxSpeed = cursor.getColumnIndexOrThrow(TrackPointsColumns.SPEED);
Location location = new Location("");
if (!cursor.isNull(idxLatitude)) {
location.setLatitude(1. * cursor.getInt(idxLatitude) / 1E6);
}
@@ -199,7 +210,6 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
if (!cursor.isNull(idxAccuracy)) {
location.setAccuracy(cursor.getFloat(idxAccuracy));
}
return location;
}
@Override
@@ -790,6 +800,21 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
String select = TracksColumns._ID + "=" + id;
return findTrackBy(select);
}
@Override
public List<Track> getAllTracks() {
Cursor cursor = getTracksCursor(null);
if (cursor == null || !cursor.moveToFirst()) {
return new ArrayList<Track>();
}
List<Track> tracks = new ArrayList<Track>(cursor.getCount());
do {
tracks.add(createTrack(cursor));
} while(cursor.moveToNext());
return tracks;
}
@Override
public long getTrackPoints(Track track, int maxPoints) {
@@ -822,6 +847,16 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
@Override
public void getTrackPoints(Track track, TrackBuffer buffer) {
getTrackPoints(track, buffer, false);
}
@Override
public void fillTrackPoints(Track track, TrackBuffer buffer) {
getTrackPoints(track, buffer, true);
}
public void getTrackPoints(Track track, TrackBuffer buffer,
boolean reuseLocations) {
long startingPoint = buffer.getLastLocationRead() == 0 ? track.getStartId()
: buffer.getLastLocationRead();
buffer.reset();
@@ -849,13 +884,17 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
final int idColumnIdx =
cursor.getColumnIndexOrThrow(TrackPointsColumns._ID);
do {
Location location = createLocation(cursor);
if (location == null) {
continue;
if (reuseLocations) {
fillLocation(cursor, buffer.add(cursor.getLong(idColumnIdx)));
} else {
Location location = createLocation(cursor);
if (location == null) {
continue;
}
buffer.add(location, cursor.getLong(idColumnIdx));
}
buffer.add(location, cursor.getLong(idColumnIdx));
} while (cursor.moveToNext());
if (buffer.getLocationsLoaded() == 0) {
Log.w(MyTracksProvider.TAG, "No locations read.");
buffer.resetAt(startingPoint + buffer.getSize());
@@ -94,6 +94,22 @@ public class TrackBuffer {
buffer[locationsLoaded++] = location;
lastLocationRead = Math.max(lastLocationRead, id);
}
/**
* Get a location from the end of the buffer.
* This may reuse Location objects already allocated to the buffer.
* @param locationId The of the location to be added.
* @return The location object associated with the give id.
*/
public Location add(long locationId) {
lastLocationRead = Math.max(lastLocationRead, locationId);
if (buffer[locationsLoaded] == null) {
buffer[locationsLoaded] = new Location("");
} else {
buffer[locationsLoaded].reset();
}
return buffer[locationsLoaded++];
}
/**
* @return The id of the last location loaded into the buffer
@@ -264,7 +264,7 @@ public class TrackWriter {
while (buffer.getLastLocationRead() < track.getStopId()) {
Log.d(MyTracksConstants.TAG,
"Reading track points starting at: " + buffer.getLastLocationRead());
providerUtils.getTrackPoints(track, buffer);
providerUtils.fillTrackPoints(track, buffer);
if (!wroteFirst) {
Location first = buffer.findStartLocation();
writer.writeBeginTrack(first);
@@ -103,4 +103,9 @@ public class ExtremityMonitor {
public void setMax(double max) {
this.max = max;
}
@Override
public String toString() {
return "Min: " + min + " Max: " + max;
}
}