forked from upstream-mirrors/OpenTracks
Merged widget.
This commit is contained in:
@@ -18,10 +18,12 @@ package com.google.android.apps.mytracks;
|
||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
|
||||
import com.google.android.apps.mytracks.ChartView.Mode;
|
||||
import com.google.android.apps.mytracks.TrackDataHub.ListenerDataType;
|
||||
import com.google.android.apps.mytracks.content.MyTracksLocation;
|
||||
import com.google.android.apps.mytracks.content.Sensor;
|
||||
import com.google.android.apps.mytracks.content.TrackDataHub;
|
||||
import com.google.android.apps.mytracks.content.TrackDataListener;
|
||||
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
|
||||
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory;
|
||||
@@ -137,11 +139,12 @@ public class ChartActivity extends Activity implements TrackDataListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
dataHub.registerTrackDataListener(this, EnumSet.of(
|
||||
ListenerDataType.SELECTED_TRACK_CHANGED,
|
||||
ListenerDataType.TRACK_UPDATES,
|
||||
ListenerDataType.POINT_UPDATES,
|
||||
ListenerDataType.SAMPLED_OUT_POINT_UPDATES,
|
||||
ListenerDataType.WAYPOINT_UPDATES,
|
||||
@@ -149,10 +152,10 @@ public class ChartActivity extends Activity implements TrackDataListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
protected void onPause() {
|
||||
dataHub.unregisterTrackDataListener(this);
|
||||
|
||||
super.onStop();
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
private void zoomIn() {
|
||||
@@ -257,13 +260,13 @@ public class ChartActivity extends Activity implements TrackDataListener {
|
||||
// TODO: Account for segment splits?
|
||||
switch (mode) {
|
||||
case BY_DISTANCE:
|
||||
timeOrDistance = profileLength;
|
||||
timeOrDistance = profileLength / 1000.0;
|
||||
if (lastLocation != null) {
|
||||
double d = lastLocation.distanceTo(location);
|
||||
if (metricUnits) {
|
||||
profileLength += d / 1000.0;
|
||||
profileLength += d;
|
||||
} else {
|
||||
profileLength += d * UnitConversions.KM_TO_MI / 1000.0;
|
||||
profileLength += d * UnitConversions.KM_TO_MI;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -336,7 +339,6 @@ public class ChartActivity extends Activity implements TrackDataListener {
|
||||
|
||||
@Override
|
||||
public void onSelectedTrackChanged(Track track, boolean isRecording) {
|
||||
Log.e(TAG, "Visible", new Throwable());
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -347,6 +349,11 @@ public class ChartActivity extends Activity implements TrackDataListener {
|
||||
|
||||
@Override
|
||||
public void onTrackUpdated(Track track) {
|
||||
if (track == null || track.getStatistics() == null) {
|
||||
trackMaxSpeed = 0.0;
|
||||
return;
|
||||
}
|
||||
|
||||
trackMaxSpeed = track.getStatistics().getMaxSpeed();
|
||||
}
|
||||
|
||||
@@ -356,9 +363,16 @@ public class ChartActivity extends Activity implements TrackDataListener {
|
||||
lastLocation = null;
|
||||
startTime = -1;
|
||||
elevationBuffer.reset();
|
||||
speedBuffer.reset();
|
||||
chartView.reset();
|
||||
speedBuffer.reset();
|
||||
pendingPoints.clear();
|
||||
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
chartView.resetScroll();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -405,15 +419,21 @@ public class ChartActivity extends Activity implements TrackDataListener {
|
||||
|
||||
@Override
|
||||
public boolean onUnitsChanged(boolean metric) {
|
||||
boolean changed = metric != this.metricUnits;
|
||||
if (!changed) return false;
|
||||
|
||||
this.metricUnits = metric;
|
||||
|
||||
chartView.setMetricUnits(metric);
|
||||
|
||||
|
||||
return true; // Reload data
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onReportSpeedChanged(boolean reportSpeed) {
|
||||
boolean changed = reportSpeed != this.reportSpeed;
|
||||
if (!changed) return false;
|
||||
|
||||
this.reportSpeed = reportSpeed;
|
||||
|
||||
chartView.setReportSpeed(reportSpeed, this);
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/*
|
||||
* Copyright 2009 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
|
||||
@@ -48,7 +48,7 @@ public class ChartValueSeries {
|
||||
private double spread;
|
||||
private int interval;
|
||||
private boolean enabled = true;
|
||||
|
||||
|
||||
/**
|
||||
* This class controls how effective min/max values of a {@link ChartValueSeries} are calculated.
|
||||
*/
|
||||
@@ -70,10 +70,10 @@ public class ChartValueSeries {
|
||||
this.intervals = intervals;
|
||||
this.absoluteMin = absoluteMin;
|
||||
this.absoluteMax = absoluteMax;
|
||||
this.zoomLevels = zoomLevels;
|
||||
this.zoomLevels = zoomLevels;
|
||||
checkArgs();
|
||||
}
|
||||
|
||||
|
||||
private void checkArgs() {
|
||||
if (intervals <= 0 || zoomLevels == null || zoomLevels.length == 0) {
|
||||
throw new IllegalArgumentException("Expecing positive intervals and non-empty zoom levels");
|
||||
@@ -100,12 +100,12 @@ public class ChartValueSeries {
|
||||
public int[] getZoomLevels() {
|
||||
return zoomLevels;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculates the interval between markings given the min and max values.
|
||||
* This function attempts to find the smallest zoom level that fits [min,max] after rounding
|
||||
* it to the current zoom level.
|
||||
*
|
||||
*
|
||||
* @param min the minimum value in the series
|
||||
* @param max the maximum value in the series
|
||||
* @return the calculated interval for the given range
|
||||
@@ -136,7 +136,7 @@ public class ChartValueSeries {
|
||||
* @param fill The paint for filling the chart
|
||||
* @param stroke The paint for stroking the outside the chart, optional
|
||||
* @param zoomSettings The settings related to zooming
|
||||
*
|
||||
*
|
||||
* TODO: Get rid of Context and inject appropriate values instead.
|
||||
*/
|
||||
public ChartValueSeries(Context context, String formatString, int fillColor, int strokeColor,
|
||||
@@ -207,7 +207,7 @@ public class ChartValueSeries {
|
||||
}
|
||||
min = Math.min(min, zoomSettings.getAbsoluteMin());
|
||||
max = Math.max(max, zoomSettings.getAbsoluteMax());
|
||||
|
||||
|
||||
this.interval = zoomSettings.calculateInterval(min, max);
|
||||
// Round it up.
|
||||
effectiveMax = ((int) (max / interval)) * interval + interval;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/*
|
||||
* 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
|
||||
@@ -28,9 +28,9 @@ import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.DashPathEffect;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Paint.Align;
|
||||
import android.graphics.Paint.Style;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.VelocityTracker;
|
||||
@@ -66,36 +66,38 @@ public class ChartView extends View {
|
||||
private int maxZoomLevel = 10;
|
||||
|
||||
private static final int MAX_INTERVALS = 5;
|
||||
|
||||
|
||||
/*
|
||||
* Borders, margins, dimensions (in pixels):
|
||||
*/
|
||||
private int leftBorder = -1;
|
||||
|
||||
/**
|
||||
* Unscaled top border of the chart.
|
||||
* Unscaled top border of the chart.
|
||||
*/
|
||||
private static final int TOP_BORDER = 15;
|
||||
|
||||
|
||||
/**
|
||||
* Device scaled top border of the chart.
|
||||
*/
|
||||
private int topBorder;
|
||||
|
||||
/**
|
||||
* Unscaled bottom border of the chart.
|
||||
* Unscaled bottom border of the chart.
|
||||
*/
|
||||
private static final float BOTTOM_BORDER = 40;
|
||||
|
||||
|
||||
/**
|
||||
* Device scaled bottom border of the chart.
|
||||
*/
|
||||
private int bottomBorder;
|
||||
|
||||
|
||||
private static final int RIGHT_BORDER = 17;
|
||||
|
||||
/** Space to leave for drawing the unit labels */
|
||||
private static final int UNIT_BORDER = 15;
|
||||
private static final int FONT_HEIGHT = 10;
|
||||
|
||||
private int w = 0;
|
||||
private int h = 0;
|
||||
private int effectiveWidth = 0;
|
||||
@@ -118,11 +120,11 @@ public class ChartView extends View {
|
||||
private ChartValueSeries[] series;
|
||||
|
||||
private final ExtremityMonitor xMonitor = new ExtremityMonitor();
|
||||
private final NumberFormat xFormat = new DecimalFormat("###,###");
|
||||
private final NumberFormat xShortFormat = new DecimalFormat("#.0");
|
||||
private static final NumberFormat X_FORMAT = new DecimalFormat("###,###");
|
||||
private static final NumberFormat X_SHORT_FORMAT = new DecimalFormat("#.0");
|
||||
|
||||
/*
|
||||
* Paints etc. used when drawing the histogram:
|
||||
* Paints etc. used when drawing the chart:
|
||||
*/
|
||||
private final Paint borderPaint = new Paint();
|
||||
private final Paint labelPaint = new Paint();
|
||||
@@ -174,7 +176,7 @@ public class ChartView extends View {
|
||||
|
||||
gridBarPaint.set(gridPaint);
|
||||
gridBarPaint.setPathEffect(new DashPathEffect(new float[] {3, 2}, 0));
|
||||
|
||||
|
||||
clearPaint.setStyle(Style.FILL);
|
||||
clearPaint.setColor(context.getResources().getColor(R.color.white));
|
||||
clearPaint.setAntiAlias(false);
|
||||
@@ -271,49 +273,12 @@ public class ChartView extends View {
|
||||
: R.string.pace_label));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the data that is displayed by the chart.
|
||||
*
|
||||
* @return an array list with data points
|
||||
*/
|
||||
public ArrayList<double[]> getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the data that is to be displayed by the chart.
|
||||
*
|
||||
* @param theData an array list of data points
|
||||
*/
|
||||
public synchronized void setDataPoints(ArrayList<double[]> theData) {
|
||||
scrollTo(0, 0);
|
||||
zoomLevel = 1;
|
||||
data.clear();
|
||||
xMonitor.reset();
|
||||
for (ChartValueSeries cvs : series) {
|
||||
cvs.reset();
|
||||
}
|
||||
addDataPoints(theData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new data point to the chart.
|
||||
*
|
||||
* @param theData a data point
|
||||
*/
|
||||
public synchronized void addDataPoint(double[] theData) {
|
||||
data.add(theData);
|
||||
addDataPointInternal(theData);
|
||||
updateDimensions();
|
||||
setUpPath();
|
||||
}
|
||||
|
||||
private void addDataPointInternal(double[] theData) {
|
||||
xMonitor.update(theData[0]);
|
||||
int min = Math.min(series.length, theData.length - 1);
|
||||
for (int i = 0; i < min; i++) {
|
||||
if (!Double.isNaN(theData[i + 1])) {
|
||||
series[i].update(theData[i + 1]);
|
||||
for (int i = 1; i <= min; i++) {
|
||||
if (!Double.isNaN(theData[i])) {
|
||||
series[i - 1].update(theData[i]);
|
||||
}
|
||||
}
|
||||
// Fill in the extra's if needed.
|
||||
@@ -329,25 +294,32 @@ public class ChartView extends View {
|
||||
*
|
||||
* @param theData an array list of data points to be added
|
||||
*/
|
||||
public synchronized void addDataPoints(ArrayList<double[]> theData) {
|
||||
data.addAll(theData);
|
||||
for (int i = 0; i < theData.size(); i++) {
|
||||
double d[] = theData.get(i);
|
||||
addDataPointInternal(d);
|
||||
public void addDataPoints(ArrayList<double[]> theData) {
|
||||
synchronized (data) {
|
||||
data.addAll(theData);
|
||||
for (int i = 0; i < theData.size(); i++) {
|
||||
double d[] = theData.get(i);
|
||||
addDataPointInternal(d);
|
||||
}
|
||||
updateDimensions();
|
||||
setUpPath();
|
||||
}
|
||||
updateDimensions();
|
||||
setUpPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all data.
|
||||
* Call this only from the UI thread!
|
||||
*/
|
||||
public synchronized void reset() {
|
||||
data.clear();
|
||||
zoomLevel = 1;
|
||||
public void reset() {
|
||||
synchronized (data) {
|
||||
data.clear();
|
||||
xMonitor.reset();
|
||||
zoomLevel = 1;
|
||||
updateDimensions();
|
||||
}
|
||||
}
|
||||
|
||||
public void resetScroll() {
|
||||
scrollTo(0, 0);
|
||||
updateDimensions();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -392,13 +364,6 @@ public class ChartView extends View {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the current zoom level (1 equals to showing all data points)
|
||||
*/
|
||||
public int getZoomLevel() {
|
||||
return zoomLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates flinging.
|
||||
*
|
||||
@@ -427,14 +392,6 @@ public class ChartView extends View {
|
||||
scrollTo(scrollX, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the scroll position of the chart. This will trigger a redraw.
|
||||
*/
|
||||
@Override
|
||||
public void scrollTo(int x, int y) {
|
||||
super.scrollTo(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the current display mode (by distance, by time)
|
||||
*/
|
||||
@@ -551,38 +508,82 @@ public class ChartView extends View {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void onDraw(Canvas c) {
|
||||
if (w != c.getWidth() || h != c.getHeight()) {
|
||||
// Dimensions have changed (for example due to orientation change).
|
||||
w = c.getWidth();
|
||||
h = c.getHeight();
|
||||
effectiveWidth = Math.max(0, w - leftBorder - RIGHT_BORDER);
|
||||
effectiveHeight = Math.max(0, h - topBorder - bottomBorder);
|
||||
setUpPath();
|
||||
}
|
||||
c.save();
|
||||
c.drawColor(Color.WHITE);
|
||||
if (data.size() < 1) {
|
||||
drawXAxis(c);
|
||||
drawYAxis(c);
|
||||
c.restore();
|
||||
return;
|
||||
}
|
||||
|
||||
c.save();
|
||||
c.clipRect(leftBorder + 1 + getScrollX(), topBorder + 1,
|
||||
w - RIGHT_BORDER + getScrollX() - 1, h - bottomBorder - 1);
|
||||
protected void onDraw(Canvas c) {
|
||||
synchronized (data) {
|
||||
updateEffectiveDimensionsIfChanged(c);
|
||||
|
||||
drawGrid(c);
|
||||
|
||||
// Draw the data series.
|
||||
for (ChartValueSeries cvs : series) {
|
||||
if (cvs.isEnabled() && cvs.hasData()) {
|
||||
cvs.drawPath(c);
|
||||
// Keep original state.
|
||||
c.save();
|
||||
|
||||
c.drawColor(Color.WHITE);
|
||||
|
||||
if (data.isEmpty()) {
|
||||
// No data, draw only axes
|
||||
drawXAxis(c);
|
||||
drawYAxis(c);
|
||||
c.restore();
|
||||
return;
|
||||
}
|
||||
|
||||
// Clip to graph drawing space
|
||||
c.save();
|
||||
clipToGraphSpace(c);
|
||||
|
||||
// Draw the grid and the data on it.
|
||||
drawGrid(c);
|
||||
drawDataSeries(c);
|
||||
drawWaypoints(c);
|
||||
|
||||
// Go back to full canvas drawing.
|
||||
c.restore();
|
||||
|
||||
// Draw the axes and their labels.
|
||||
drawAxesAndLabels(c);
|
||||
|
||||
// Go back to original state.
|
||||
c.restore();
|
||||
|
||||
// Draw the pointer
|
||||
if (showPointer) {
|
||||
drawPointer(c);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the waypoints.
|
||||
}
|
||||
|
||||
/** Clips the given canvas to the area where the graph lines should be drawn. */
|
||||
private void clipToGraphSpace(Canvas c) {
|
||||
c.clipRect(leftBorder + 1 + getScrollX(), topBorder + 1,
|
||||
w - RIGHT_BORDER + getScrollX() - 1, h - bottomBorder - 1);
|
||||
}
|
||||
|
||||
/** Draws the axes and their labels into th e given canvas. */
|
||||
private void drawAxesAndLabels(Canvas c) {
|
||||
drawXLabels(c);
|
||||
drawXAxis(c);
|
||||
drawSeriesTitles(c);
|
||||
|
||||
c.translate(getScrollX(), 0);
|
||||
drawYAxis(c);
|
||||
float density = getContext().getResources().getDisplayMetrics().density;
|
||||
final int spacer = (int) (5 * density);
|
||||
int x = leftBorder - spacer;
|
||||
for (ChartValueSeries cvs : series) {
|
||||
if (cvs.isEnabled() && cvs.hasData()) {
|
||||
x -= drawYLabels(cvs, c, x) + spacer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Draws the current pointer into the given canvas. */
|
||||
private void drawPointer(Canvas c) {
|
||||
c.translate(getX(maxX) - pointer.getIntrinsicWidth() / 2,
|
||||
getY(series[0], data.get(data.size() - 1)[1])
|
||||
- pointer.getIntrinsicHeight() / 2 - 12);
|
||||
pointer.draw(c);
|
||||
}
|
||||
|
||||
/** Draws the waypoints into the given canvas. */
|
||||
private void drawWaypoints(Canvas c) {
|
||||
for (int i = 1; i < waypoints.size(); i++) {
|
||||
final Waypoint waypoint = waypoints.get(i);
|
||||
if (waypoint.getLocation() == null) {
|
||||
@@ -600,33 +601,18 @@ public class ChartView extends View {
|
||||
}
|
||||
c.restore();
|
||||
}
|
||||
}
|
||||
|
||||
c.restore();
|
||||
|
||||
// Draw the axis and labels.
|
||||
drawXLabels(c);
|
||||
drawXAxis(c);
|
||||
drawSeriesTitles(c);
|
||||
|
||||
c.translate(getScrollX(), 0);
|
||||
drawYAxis(c);
|
||||
float density = getContext().getResources().getDisplayMetrics().density;
|
||||
final int spacer = (int) (5 * density);
|
||||
int x = leftBorder - spacer;
|
||||
/** Draws the data series into the given canvas. */
|
||||
private void drawDataSeries(Canvas c) {
|
||||
for (ChartValueSeries cvs : series) {
|
||||
if (cvs.isEnabled() && cvs.hasData()) {
|
||||
x -= drawYLabels(cvs, c, x) + spacer;
|
||||
cvs.drawPath(c);
|
||||
}
|
||||
}
|
||||
c.restore();
|
||||
if (showPointer && !data.isEmpty()) {
|
||||
c.translate(getX(maxX) - pointer.getIntrinsicWidth() / 2,
|
||||
getY(series[0], data.get(data.size() - 1)[1])
|
||||
- pointer.getIntrinsicHeight() / 2 - 12);
|
||||
pointer.draw(c);
|
||||
}
|
||||
}
|
||||
|
||||
/** Draws the colored titles for the data series. */
|
||||
private void drawSeriesTitles(Canvas c) {
|
||||
int sections = 1;
|
||||
for (ChartValueSeries cvs : series) {
|
||||
@@ -644,17 +630,24 @@ public class ChartView extends View {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the path that is used to draw the histogram in onDraw(). The path
|
||||
* Sets up the path that is used to draw the chart in onDraw(). The path
|
||||
* needs to be updated any time after the data or histogram dimensions change.
|
||||
*/
|
||||
private synchronized void setUpPath() {
|
||||
for (ChartValueSeries cvs : series) {
|
||||
cvs.getPath().reset();
|
||||
}
|
||||
if (data.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
private void setUpPath() {
|
||||
synchronized (data) {
|
||||
for (ChartValueSeries cvs : series) {
|
||||
cvs.getPath().reset();
|
||||
}
|
||||
|
||||
if (!data.isEmpty()) {
|
||||
drawPaths();
|
||||
closePaths();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Actually draws the data points as a path. */
|
||||
private void drawPaths() {
|
||||
// All of the data points to the respective series.
|
||||
// TODO: Come up with a better sampling than Math.max(1, (maxZoomLevel - zoomLevel + 1) / 2);
|
||||
int sampling = 1;
|
||||
@@ -673,7 +666,10 @@ public class ChartView extends View {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Closes the drawn path so it looks like a solid graph. */
|
||||
private void closePaths() {
|
||||
// Close the path.
|
||||
int yCorner = topBorder + effectiveHeight;
|
||||
int xCorner = getX(data.get(0)[0]);
|
||||
@@ -692,12 +688,13 @@ public class ChartView extends View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find the index of the first point which has a series populated.
|
||||
* @param seriesIndex The index of the value series to search for.
|
||||
* Finds the index of the first point which has a series populated.
|
||||
*
|
||||
* @param seriesIndex The index of the value series to search for
|
||||
* @return The index in the first data for the point in the series that has series
|
||||
* index value populated or -1 if none is found.
|
||||
* index value populated or -1 if none is found
|
||||
*/
|
||||
private int getFirstPointPopulatedIndex(int seriesIndex) {
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
@@ -709,7 +706,7 @@ public class ChartView extends View {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the histogram dimensions.
|
||||
* Updates the chart dimensions.
|
||||
*/
|
||||
private void updateDimensions() {
|
||||
maxX = xMonitor.getMax();
|
||||
@@ -730,10 +727,29 @@ public class ChartView extends View {
|
||||
float density = getContext().getResources().getDisplayMetrics().density;
|
||||
maxLength = Math.max(maxLength, 1);
|
||||
leftBorder = (int) (density * (4 + 8 * maxLength));
|
||||
effectiveWidth = w - leftBorder - RIGHT_BORDER;
|
||||
bottomBorder = (int) (density * BOTTOM_BORDER);
|
||||
topBorder = (int) (density * TOP_BORDER);
|
||||
effectiveHeight = h - topBorder - bottomBorder;
|
||||
updateEffectiveDimensions();
|
||||
}
|
||||
|
||||
/** Updates the effective dimensions where the graph will be drawn. */
|
||||
private void updateEffectiveDimensions() {
|
||||
effectiveWidth = Math.max(0, w - leftBorder - RIGHT_BORDER);
|
||||
effectiveHeight = Math.max(0, h - topBorder - bottomBorder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the effective dimensions where the graph will be drawn, only if the
|
||||
* dimensions of the given canvas have changed since the last call.
|
||||
*/
|
||||
private void updateEffectiveDimensionsIfChanged(Canvas c) {
|
||||
if (w != c.getWidth() || h != c.getHeight()) {
|
||||
// Dimensions have changed (for example due to orientation change).
|
||||
w = c.getWidth();
|
||||
h = c.getHeight();
|
||||
updateEffectiveDimensions();
|
||||
setUpPath();
|
||||
}
|
||||
}
|
||||
|
||||
private int getX(double distance) {
|
||||
@@ -746,6 +762,7 @@ public class ChartView extends View {
|
||||
- (int) ((y - cvs.getMin()) * effectiveHeight / effectiveSpread);
|
||||
}
|
||||
|
||||
/** Draws the labels on the X axis into the given canvas. */
|
||||
private void drawXLabels(Canvas c) {
|
||||
double interval = (int) (maxX / zoomLevel / 4);
|
||||
boolean shortFormat = false;
|
||||
@@ -770,6 +787,7 @@ public class ChartView extends View {
|
||||
}
|
||||
}
|
||||
|
||||
/** Draws the labels on the Y axis into the given canvas. */
|
||||
private float drawYLabels(ChartValueSeries cvs, Canvas c, int x) {
|
||||
int interval = cvs.getInterval();
|
||||
float maxTextWidth = 0;
|
||||
@@ -779,13 +797,14 @@ public class ChartView extends View {
|
||||
return maxTextWidth;
|
||||
}
|
||||
|
||||
/** Draws a single label on the X axis. */
|
||||
private void drawXLabel(Canvas c, double x, boolean shortFormat) {
|
||||
if (x < 0) {
|
||||
return;
|
||||
}
|
||||
String s =
|
||||
(mode == Mode.BY_DISTANCE)
|
||||
? (shortFormat ? xShortFormat.format(x) : xFormat.format(x))
|
||||
? (shortFormat ? X_SHORT_FORMAT.format(x) : X_FORMAT.format(x))
|
||||
: StringUtils.formatTime((long) x);
|
||||
c.drawText(s,
|
||||
getX(x),
|
||||
@@ -793,6 +812,7 @@ public class ChartView extends View {
|
||||
labelPaint);
|
||||
}
|
||||
|
||||
/** Draws a single label on the Y axis. */
|
||||
private float drawYLabel(ChartValueSeries cvs, Canvas c, int x, int y) {
|
||||
int desiredY = (int) ((y - cvs.getMin()) * effectiveHeight /
|
||||
(cvs.getInterval() * MAX_INTERVALS));
|
||||
@@ -804,6 +824,7 @@ public class ChartView extends View {
|
||||
return p.measureText(text);
|
||||
}
|
||||
|
||||
/** Draws the actual X axis line and its label. */
|
||||
private void drawXAxis(Canvas canvas) {
|
||||
float rightEdge = getX(maxX);
|
||||
final int y = effectiveHeight + topBorder;
|
||||
@@ -815,6 +836,7 @@ public class ChartView extends View {
|
||||
canvas.drawText(s, rightEdge, effectiveHeight + .2f * UNIT_BORDER + topBorder, labelPaint);
|
||||
}
|
||||
|
||||
/** Draws the actual Y axis line and its label. */
|
||||
private void drawYAxis(Canvas canvas) {
|
||||
canvas.drawRect(0, 0,
|
||||
leftBorder - 1, effectiveHeight + topBorder + UNIT_BORDER + 1,
|
||||
@@ -826,18 +848,15 @@ public class ChartView extends View {
|
||||
int y = i * effectiveHeight / MAX_INTERVALS + topBorder;
|
||||
canvas.drawLine(leftBorder - 5, y, leftBorder, y, gridPaint);
|
||||
}
|
||||
|
||||
|
||||
Context c = getContext();
|
||||
// TODO: This should really show units for all series.
|
||||
String s = metricUnits ? c.getString(R.string.meter) : c.getString(R.string.feet);
|
||||
canvas.drawText(s, leftBorder - UNIT_BORDER * .2f, UNIT_BORDER * .8f + topBorder, labelPaint);
|
||||
}
|
||||
|
||||
private synchronized void drawGrid(Canvas c) {
|
||||
if (data.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/** Draws the grid for the graph. */
|
||||
private void drawGrid(Canvas c) {
|
||||
float rightEdge = getX(maxX);
|
||||
for (int i = 1; i < MAX_INTERVALS; ++i) {
|
||||
int y = i * effectiveHeight / MAX_INTERVALS + topBorder;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/*
|
||||
* Copyright 2009 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
|
||||
@@ -133,7 +133,7 @@ public class ExportAllTracks {
|
||||
Log.i(Constants.TAG, "ExportAllTracks: Releasing wake lock.");
|
||||
}
|
||||
Log.i(Constants.TAG, "ExportAllTracks: Done");
|
||||
Toast.makeText(activity, R.string.export_done, Toast.LENGTH_SHORT).show();
|
||||
showToast(R.string.export_done, Toast.LENGTH_SHORT);
|
||||
}
|
||||
|
||||
private void makeProgressDialog(final int trackCount) {
|
||||
@@ -189,12 +189,16 @@ public class ExportAllTracks {
|
||||
Log.i(Constants.TAG, "ExportAllTracks: exporting: " + id);
|
||||
TrackWriter writer =
|
||||
TrackWriterFactory.newWriter(activity, providerUtils, id, format);
|
||||
if (writer == null) {
|
||||
showToast(R.string.error_track_does_not_exist, Toast.LENGTH_LONG);
|
||||
return;
|
||||
}
|
||||
|
||||
writer.writeTrack();
|
||||
|
||||
if (!writer.wasSuccess()) {
|
||||
// Abort the whole export on the first error.
|
||||
int error = writer.getErrorMessage();
|
||||
Toast.makeText(activity, error, Toast.LENGTH_LONG).show();
|
||||
showToast(writer.getErrorMessage(), Toast.LENGTH_LONG);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -210,4 +214,13 @@ public class ExportAllTracks {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void showToast(final int messageId, final int length) {
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(activity, messageId, length).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/*
|
||||
* 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
|
||||
@@ -17,9 +17,11 @@ package com.google.android.apps.mytracks;
|
||||
|
||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
|
||||
import com.google.android.apps.mytracks.TrackDataHub.ListenerDataType;
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.content.TrackDataHub;
|
||||
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
|
||||
import com.google.android.apps.mytracks.content.TrackDataListener;
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory;
|
||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||
@@ -33,7 +35,6 @@ import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.location.Location;
|
||||
import android.location.LocationManager;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
@@ -50,6 +51,7 @@ import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
@@ -176,9 +178,9 @@ public class MapActivity extends com.google.android.maps.MapActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
protected void onResume() {
|
||||
Log.d(TAG, "MapActivity.onStart");
|
||||
super.onStart();
|
||||
super.onResume();
|
||||
|
||||
dataHub.registerTrackDataListener(this, EnumSet.of(
|
||||
ListenerDataType.SELECTED_TRACK_CHANGED,
|
||||
@@ -199,12 +201,12 @@ public class MapActivity extends com.google.android.maps.MapActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
protected void onPause() {
|
||||
Log.d(TAG, "MapActivity.onStop");
|
||||
|
||||
dataHub.unregisterTrackDataListener(this);
|
||||
|
||||
super.onStop();
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
// Utility functions:
|
||||
@@ -253,17 +255,18 @@ public class MapActivity extends com.google.android.maps.MapActivity
|
||||
* the current location is outside the visible area.
|
||||
*/
|
||||
private void showCurrentLocation() {
|
||||
if (currentLocation == null || mapOverlay == null || mapView == null) {
|
||||
if (mapOverlay == null || mapView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mapOverlay.setMyLocation(currentLocation);
|
||||
mapView.postInvalidate();
|
||||
if (keepMyLocationVisible && !locationIsVisible(currentLocation)) {
|
||||
|
||||
if (currentLocation != null && keepMyLocationVisible && !locationIsVisible(currentLocation)) {
|
||||
GeoPoint geoPoint = LocationUtils.getGeoPoint(currentLocation);
|
||||
MapController controller = mapView.getController();
|
||||
controller.animateTo(geoPoint);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -334,9 +337,9 @@ public class MapActivity extends com.google.android.maps.MapActivity
|
||||
|
||||
if (trackSelected) {
|
||||
busyPane.setVisibility(View.VISIBLE);
|
||||
|
||||
|
||||
zoomMapToBoundaries(track);
|
||||
|
||||
|
||||
mapOverlay.setShowEndMarker(!isRecording);
|
||||
busyPane.setVisibility(View.GONE);
|
||||
}
|
||||
@@ -462,21 +465,21 @@ public class MapActivity extends com.google.android.maps.MapActivity
|
||||
@Override
|
||||
public void onProviderStateChange(ProviderState state) {
|
||||
final int messageId;
|
||||
final boolean bindClick;
|
||||
final boolean isGpsDisabled;
|
||||
switch (state) {
|
||||
case DISABLED:
|
||||
messageId = R.string.status_enable_gps;
|
||||
bindClick = true;
|
||||
isGpsDisabled = true;
|
||||
break;
|
||||
case NO_FIX:
|
||||
case BAD_FIX:
|
||||
messageId = R.string.wait_for_fix;
|
||||
bindClick = false;
|
||||
isGpsDisabled = false;
|
||||
break;
|
||||
case GOOD_FIX:
|
||||
// Nothing to show.
|
||||
messageId = -1;
|
||||
bindClick = false;
|
||||
isGpsDisabled = false;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unexpected state: " + state);
|
||||
@@ -489,7 +492,13 @@ public class MapActivity extends com.google.android.maps.MapActivity
|
||||
messageText.setText(messageId);
|
||||
messagePane.setVisibility(View.VISIBLE);
|
||||
|
||||
if (bindClick) {
|
||||
if (isGpsDisabled) {
|
||||
// Give a warning about this state.
|
||||
Toast.makeText(MapActivity.this,
|
||||
R.string.error_no_gps_location_provider,
|
||||
Toast.LENGTH_LONG).show();
|
||||
|
||||
// Make clicking take the user to the location settings.
|
||||
messagePane.setOnClickListener(MapActivity.this);
|
||||
} else {
|
||||
messagePane.setOnClickListener(null);
|
||||
@@ -505,11 +514,6 @@ public class MapActivity extends com.google.android.maps.MapActivity
|
||||
|
||||
@Override
|
||||
public void onCurrentLocationChanged(Location location) {
|
||||
if (!location.getProvider().equals(LocationManager.GPS_PROVIDER)) {
|
||||
Log.d(TAG,
|
||||
"MapActivity: Network location update received (provider '" + location.getProvider() + "'.");
|
||||
}
|
||||
|
||||
currentLocation = location;
|
||||
showCurrentLocation();
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import static com.google.android.apps.mytracks.DialogManager.DIALOG_SEND_TO_GOOG
|
||||
import com.google.android.accounts.Account;
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.content.TrackDataHub;
|
||||
import com.google.android.apps.mytracks.content.WaypointCreationRequest;
|
||||
import com.google.android.apps.mytracks.io.AuthManager;
|
||||
import com.google.android.apps.mytracks.io.AuthManagerFactory;
|
||||
@@ -904,7 +905,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
* Initiates the process to send tracks to google.
|
||||
* This is called once the user has selected sending options via the
|
||||
* SendToGoogleDialog.
|
||||
*
|
||||
*
|
||||
* TODO: Change this whole flow to an actual state machine.
|
||||
*/
|
||||
public void sendToGoogle() {
|
||||
@@ -962,8 +963,12 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
// Update the map id for this track:
|
||||
try {
|
||||
Track track = providerUtils.getTrack(trackId);
|
||||
track.setMapId(mapId);
|
||||
providerUtils.updateTrack(track);
|
||||
if (track != null) {
|
||||
track.setMapId(mapId);
|
||||
providerUtils.updateTrack(track);
|
||||
} else {
|
||||
Log.w(TAG, "Updating map id failed.");
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
// If that fails whatever reasons we'll just log an error, but
|
||||
// continue.
|
||||
@@ -1016,15 +1021,19 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
// Update the table id for this track:
|
||||
try {
|
||||
Track track = providerUtils.getTrack(trackId);
|
||||
track.setTableId(tableId);
|
||||
providerUtils.updateTrack(track);
|
||||
if (track != null) {
|
||||
track.setTableId(tableId);
|
||||
providerUtils.updateTrack(track);
|
||||
} else {
|
||||
Log.w(TAG, "Updating table id failed.");
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
// If that fails whatever reasons we'll just log an error, but
|
||||
// continue.
|
||||
Log.w(TAG, "Updating table id failed.", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onSendToFusionTablesDone();
|
||||
}
|
||||
};
|
||||
@@ -1071,7 +1080,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
Log.d(TAG, "Sending to Docs....");
|
||||
setProgressValue(50);
|
||||
setProgressMessage(R.string.progress_message_sending_docs);
|
||||
final SendToDocs sender = new SendToDocs(this,
|
||||
final SendToDocs sender = new SendToDocs(this,
|
||||
authMap.get(SendToDocs.GDATA_SERVICE_NAME_TRIX),
|
||||
authMap.get(SendToDocs.GDATA_SERVICE_NAME_DOCLIST), trackId);
|
||||
sendToTrackId = trackId;
|
||||
@@ -1227,6 +1236,11 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
*/
|
||||
public void saveTrack(long trackId, TrackFileFormat format) {
|
||||
TrackWriter writer = TrackWriterFactory.newWriter(this, providerUtils, trackId, format);
|
||||
if (writer == null) {
|
||||
dialogManager.showMessageDialog(R.string.error_track_does_not_exist, false);
|
||||
return;
|
||||
}
|
||||
|
||||
WriteProgressController controller = new WriteProgressController(this, writer);
|
||||
controller.setOnCompletionListener(new WriteProgressController.OnCompletionListener() {
|
||||
@Override
|
||||
@@ -1246,6 +1260,10 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
*/
|
||||
public void sendTrack(long trackId, final TrackFileFormat format) {
|
||||
TrackWriter writer = TrackWriterFactory.newWriter(this, providerUtils, trackId, format);
|
||||
if (writer == null) {
|
||||
dialogManager.showMessageDialog(R.string.error_track_does_not_exist, false);
|
||||
return;
|
||||
}
|
||||
|
||||
FileUtils fileUtils = new FileUtils();
|
||||
String extension = format.getExtension();
|
||||
@@ -1312,17 +1330,17 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
long getRecordingTrackId() {
|
||||
return sharedPreferences.getLong(getString(R.string.recording_track_key), -1);
|
||||
}
|
||||
|
||||
|
||||
// @VisibleForTesting
|
||||
SharedPreferences getSharedPreferences() {
|
||||
return sharedPreferences;
|
||||
}
|
||||
|
||||
|
||||
// @VisibleForTesting
|
||||
static void clearInstance() {
|
||||
instance = null;
|
||||
}
|
||||
|
||||
|
||||
// @VisibleForTesting
|
||||
ITrackRecordingService getTrackRecordingService() {
|
||||
return trackRecordingService;
|
||||
|
||||
@@ -17,8 +17,10 @@ package com.google.android.apps.mytracks;
|
||||
|
||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
|
||||
import com.google.android.apps.mytracks.TrackDataHub.ListenerDataType;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.content.TrackDataHub;
|
||||
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
|
||||
import com.google.android.apps.mytracks.content.TrackDataListener;
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory;
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
@@ -129,18 +131,18 @@ public class StatsActivity extends Activity implements TrackDataListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
protected void onResume() {
|
||||
dataHub.registerTrackDataListener(this, EnumSet.of(
|
||||
ListenerDataType.SELECTED_TRACK_CHANGED,
|
||||
ListenerDataType.TRACK_UPDATES,
|
||||
ListenerDataType.LOCATION_UPDATES,
|
||||
ListenerDataType.DISPLAY_PREFERENCES));
|
||||
|
||||
super.onStart();
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
protected void onPause() {
|
||||
dataHub.unregisterTrackDataListener(this);
|
||||
|
||||
if (thread != null) {
|
||||
@@ -153,6 +155,9 @@ public class StatsActivity extends Activity implements TrackDataListener {
|
||||
|
||||
@Override
|
||||
public boolean onUnitsChanged(boolean metric) {
|
||||
// Ignore if unchanged.
|
||||
if (metric == utils.isMetricUnits()) return false;
|
||||
|
||||
utils.setMetricUnits(metric);
|
||||
updateLabels();
|
||||
|
||||
@@ -161,6 +166,9 @@ public class StatsActivity extends Activity implements TrackDataListener {
|
||||
|
||||
@Override
|
||||
public boolean onReportSpeedChanged(boolean displaySpeed) {
|
||||
// Ignore if unchanged.
|
||||
if (displaySpeed == utils.isReportSpeed()) return false;
|
||||
|
||||
utils.setReportSpeed(displaySpeed);
|
||||
updateLabels();
|
||||
|
||||
@@ -248,21 +256,21 @@ public class StatsActivity extends Activity implements TrackDataListener {
|
||||
thread.interrupt();
|
||||
thread = null;
|
||||
}
|
||||
|
||||
if (track == null || track.getStatistics() == null) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
utils.setAllToUnknown();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCurrentLocationChanged(Location loc) {
|
||||
public void onCurrentLocationChanged(final Location loc) {
|
||||
if (dataHub.isRecordingSelected()) {
|
||||
showLocation(loc);
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (loc != null) {
|
||||
showLocation(loc);
|
||||
} else {
|
||||
showUnknownLocation();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,7 +284,12 @@ public class StatsActivity extends Activity implements TrackDataListener {
|
||||
switch (state) {
|
||||
case DISABLED:
|
||||
case NO_FIX:
|
||||
showUnknownLocation();
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
showUnknownLocation();
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -286,6 +299,11 @@ public class StatsActivity extends Activity implements TrackDataListener {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (track == null || track.getStatistics() == null) {
|
||||
utils.setAllToUnknown();
|
||||
return;
|
||||
}
|
||||
|
||||
startTime = track.getStatistics().getStartTime();
|
||||
if (!dataHub.isRecordingSelected()) {
|
||||
utils.setTime(R.id.total_time_register,
|
||||
|
||||
@@ -83,6 +83,8 @@ public class StatsUtilities {
|
||||
public void setText(int id, double d, NumberFormat format) {
|
||||
if (!Double.isNaN(d) && !Double.isInfinite(d)) {
|
||||
setText(id, format.format(d));
|
||||
} else {
|
||||
setUnknown(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,7 +220,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);
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/*
|
||||
* Copyright 2009 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
|
||||
@@ -73,15 +73,18 @@ public class WaypointsList extends ListActivity
|
||||
contextPosition = info.position;
|
||||
waypointId = WaypointsList.this.listView.getAdapter()
|
||||
.getItemId(contextPosition);
|
||||
int type = providerUtils.getWaypoint(info.id).getType();
|
||||
menu.add(0, Constants.MENU_SHOW, 0,
|
||||
R.string.waypointslist_show_waypoint);
|
||||
menu.add(0, Constants.MENU_EDIT, 0,
|
||||
R.string.waypointslist_edit_waypoint);
|
||||
menu.add(0, Constants.MENU_DELETE, 0,
|
||||
R.string.waypointslist_delete_waypoint).setEnabled(
|
||||
recordingTrackId < 0 || type == Waypoint.TYPE_WAYPOINT ||
|
||||
info.id != providerUtils.getLastWaypointId(recordingTrackId));
|
||||
Waypoint waypoint = providerUtils.getWaypoint(info.id);
|
||||
if (waypoint != null) {
|
||||
int type = waypoint.getType();
|
||||
menu.add(0, Constants.MENU_SHOW, 0,
|
||||
R.string.waypointslist_show_waypoint);
|
||||
menu.add(0, Constants.MENU_EDIT, 0,
|
||||
R.string.waypointslist_edit_waypoint);
|
||||
menu.add(0, Constants.MENU_DELETE, 0,
|
||||
R.string.waypointslist_delete_waypoint).setEnabled(
|
||||
recordingTrackId < 0 || type == Waypoint.TYPE_WAYPOINT ||
|
||||
info.id != providerUtils.getLastWaypointId(recordingTrackId));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -159,7 +162,7 @@ public class WaypointsList extends ListActivity
|
||||
trackId = -1;
|
||||
}
|
||||
|
||||
final long firstWaypointId = providerUtils.getFirstWaypointId(trackId);
|
||||
final long firstWaypointId = providerUtils.getFirstWaypointId(trackId);
|
||||
waypointsCursor = getContentResolver().query(
|
||||
WaypointsColumns.CONTENT_URI, null,
|
||||
WaypointsColumns.TRACKID + "=" + trackId + " AND "
|
||||
@@ -209,7 +212,7 @@ public class WaypointsList extends ListActivity
|
||||
new String[] { WaypointsColumns.NAME, WaypointsColumns.TIME,
|
||||
WaypointsColumns.CATEGORY, WaypointsColumns.TYPE },
|
||||
new int[] { R.id.waypointslist_item_name,
|
||||
R.id.waypointslist_item_time,
|
||||
R.id.waypointslist_item_time,
|
||||
R.id.waypointslist_item_category,
|
||||
R.id.waypointslist_item_icon });
|
||||
|
||||
@@ -219,7 +222,7 @@ public class WaypointsList extends ListActivity
|
||||
waypointsCursor.getColumnIndexOrThrow(WaypointsColumns.TYPE);
|
||||
adapter.setViewBinder(new SimpleCursorAdapter.ViewBinder() {
|
||||
@Override
|
||||
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
|
||||
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
|
||||
if (columnIndex == timeIdx) {
|
||||
long time = cursor.getLong(timeIdx);
|
||||
TextView textView = (TextView) view;
|
||||
|
||||
@@ -0,0 +1,264 @@
|
||||
package com.google.android.apps.mytracks.content;
|
||||
|
||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
|
||||
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||
import android.database.ContentObserver;
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorEventListener;
|
||||
import android.hardware.SensorManager;
|
||||
import android.location.Location;
|
||||
import android.location.LocationListener;
|
||||
import android.location.LocationManager;
|
||||
import android.location.LocationProvider;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* External data source manager, which converts system-level events into My Tracks data events.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
class DataSourceManager {
|
||||
|
||||
/** Single interface for receiving system events that were registered for. */
|
||||
interface DataSourceListener {
|
||||
void notifyTrackUpdated();
|
||||
void notifyWaypointUpdated();
|
||||
void notifyPointsUpdated();
|
||||
void notifyPreferenceChanged(String key);
|
||||
void notifyLocationProviderEnabled(boolean enabled);
|
||||
void notifyLocationChanged(Location loc);
|
||||
void notifyHeadingChanged(float heading);
|
||||
}
|
||||
|
||||
private final DataSourceListener listener;
|
||||
|
||||
/** Observer for when the tracks table is updated. */
|
||||
private class TrackObserver extends ContentObserver {
|
||||
public TrackObserver() {
|
||||
super(contentHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
listener.notifyTrackUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
/** Observer for when the waypoints table is updated. */
|
||||
private class WaypointObserver extends ContentObserver {
|
||||
public WaypointObserver() {
|
||||
super(contentHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
listener.notifyWaypointUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
/** Observer for when the points table is updated. */
|
||||
private class PointObserver extends ContentObserver {
|
||||
public PointObserver() {
|
||||
super(contentHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
listener.notifyPointsUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
/** Listener for when preferences change. */
|
||||
private class HubSharedPreferenceListener implements OnSharedPreferenceChangeListener {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
listener.notifyPreferenceChanged(key);
|
||||
}
|
||||
}
|
||||
|
||||
/** Listener for the current location (independent from track data). */
|
||||
private class CurrentLocationListener implements
|
||||
LocationListener {
|
||||
@Override
|
||||
public void onStatusChanged(String provider, int status, Bundle extras) {
|
||||
if (!LocationManager.GPS_PROVIDER.equals(provider)) return;
|
||||
|
||||
listener.notifyLocationProviderEnabled(status == LocationProvider.AVAILABLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderEnabled(String provider) {
|
||||
if (!LocationManager.GPS_PROVIDER.equals(provider)) return;
|
||||
|
||||
listener.notifyLocationProviderEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderDisabled(String provider) {
|
||||
if (!LocationManager.GPS_PROVIDER.equals(provider)) return;
|
||||
|
||||
listener.notifyLocationProviderEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocationChanged(Location location) {
|
||||
listener.notifyLocationChanged(location);
|
||||
}
|
||||
}
|
||||
|
||||
/** Listener for compass readings. */
|
||||
private class CompassListener implements
|
||||
SensorEventListener {
|
||||
@Override
|
||||
public void onSensorChanged(SensorEvent event) {
|
||||
listener.notifyHeadingChanged(event.values[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAccuracyChanged(Sensor sensor, int accuracy) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
/** Wrapper for registering internal listeners. */
|
||||
private final DataSourcesWrapper dataSources;
|
||||
|
||||
// Internal listeners (to receive data from the system)
|
||||
private final Set<ListenerDataType> registeredListeners =
|
||||
EnumSet.noneOf(ListenerDataType.class);
|
||||
private final Handler contentHandler;
|
||||
private final ContentObserver pointObserver;
|
||||
private final ContentObserver waypointObserver;
|
||||
private final ContentObserver trackObserver;
|
||||
private final LocationListener locationListener;
|
||||
private final OnSharedPreferenceChangeListener preferenceListener;
|
||||
private final SensorEventListener compassListener;
|
||||
|
||||
DataSourceManager(DataSourceListener listener, DataSourcesWrapper dataSources) {
|
||||
this.listener = listener;
|
||||
this.dataSources = dataSources;
|
||||
|
||||
contentHandler = new Handler();
|
||||
pointObserver = new PointObserver();
|
||||
waypointObserver = new WaypointObserver();
|
||||
trackObserver = new TrackObserver();
|
||||
|
||||
compassListener = new CompassListener();
|
||||
locationListener = new CurrentLocationListener();
|
||||
preferenceListener = new HubSharedPreferenceListener();
|
||||
}
|
||||
|
||||
/** Updates the internal (sensor, position, etc) listeners. */
|
||||
void updateAllListeners(EnumSet<ListenerDataType> externallyNeededListeners) {
|
||||
EnumSet<ListenerDataType> neededListeners = EnumSet.copyOf(externallyNeededListeners);
|
||||
|
||||
// Special case - map sampled-out points type to points type since they
|
||||
// correspond to the same internal listener.
|
||||
if (neededListeners.contains(ListenerDataType.SAMPLED_OUT_POINT_UPDATES)) {
|
||||
neededListeners.remove(ListenerDataType.SAMPLED_OUT_POINT_UPDATES);
|
||||
neededListeners.add(ListenerDataType.POINT_UPDATES);
|
||||
}
|
||||
|
||||
Log.d(TAG, "Updating internal listeners to types " + neededListeners);
|
||||
|
||||
// Unnecessary = registered - needed
|
||||
Set<ListenerDataType> unnecessaryListeners = EnumSet.copyOf(registeredListeners);
|
||||
unnecessaryListeners.removeAll(neededListeners);
|
||||
|
||||
// Missing = needed - registered
|
||||
Set<ListenerDataType> missingListeners = EnumSet.copyOf(neededListeners);
|
||||
missingListeners.removeAll(registeredListeners);
|
||||
|
||||
// Remove all unnecessary listeners.
|
||||
for (ListenerDataType type : unnecessaryListeners) {
|
||||
unregisterListener(type);
|
||||
}
|
||||
|
||||
// Add all missing listeners.
|
||||
for (ListenerDataType type : missingListeners) {
|
||||
registerListener(type);
|
||||
}
|
||||
|
||||
// Now all needed types are registered.
|
||||
registeredListeners.clear();
|
||||
registeredListeners.addAll(neededListeners);
|
||||
}
|
||||
|
||||
private void registerListener(ListenerDataType type) {
|
||||
switch (type) {
|
||||
case COMPASS_UPDATES: {
|
||||
// Listen to compass
|
||||
Sensor compass = dataSources.getSensor(Sensor.TYPE_ORIENTATION);
|
||||
if (compass != null) {
|
||||
Log.d(TAG, "TrackDataHub: Now registering sensor listener.");
|
||||
dataSources.registerSensorListener(compassListener, compass, SensorManager.SENSOR_DELAY_UI);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LOCATION_UPDATES:
|
||||
dataSources.requestLocationUpdates(locationListener);
|
||||
break;
|
||||
case POINT_UPDATES:
|
||||
dataSources.registerContentObserver(
|
||||
TrackPointsColumns.CONTENT_URI, false, pointObserver);
|
||||
break;
|
||||
case TRACK_UPDATES:
|
||||
dataSources.registerContentObserver(TracksColumns.CONTENT_URI, false, trackObserver);
|
||||
break;
|
||||
case WAYPOINT_UPDATES:
|
||||
dataSources.registerContentObserver(
|
||||
WaypointsColumns.CONTENT_URI, false, waypointObserver);
|
||||
break;
|
||||
case DISPLAY_PREFERENCES:
|
||||
dataSources.registerOnSharedPreferenceChangeListener(preferenceListener);
|
||||
break;
|
||||
case SAMPLED_OUT_POINT_UPDATES:
|
||||
throw new IllegalArgumentException("Should have been mapped to point updates");
|
||||
}
|
||||
}
|
||||
|
||||
private void unregisterListener(ListenerDataType type) {
|
||||
switch (type) {
|
||||
case COMPASS_UPDATES:
|
||||
dataSources.unregisterSensorListener(compassListener);
|
||||
break;
|
||||
case LOCATION_UPDATES:
|
||||
dataSources.removeLocationUpdates(locationListener);
|
||||
break;
|
||||
case POINT_UPDATES:
|
||||
dataSources.unregisterContentObserver(pointObserver);
|
||||
break;
|
||||
case TRACK_UPDATES:
|
||||
dataSources.unregisterContentObserver(trackObserver);
|
||||
break;
|
||||
case WAYPOINT_UPDATES:
|
||||
dataSources.unregisterContentObserver(waypointObserver);
|
||||
break;
|
||||
case DISPLAY_PREFERENCES:
|
||||
dataSources.unregisterOnSharedPreferenceChangeListener(preferenceListener);
|
||||
break;
|
||||
case SAMPLED_OUT_POINT_UPDATES:
|
||||
throw new IllegalArgumentException("Should have been mapped to point updates");
|
||||
}
|
||||
}
|
||||
|
||||
/** Unregisters all internal (sensor, position, etc.) listeners. */
|
||||
void unregisterAllListeners() {
|
||||
dataSources.removeLocationUpdates(locationListener);
|
||||
dataSources.unregisterSensorListener(compassListener);
|
||||
dataSources.unregisterContentObserver(trackObserver);
|
||||
dataSources.unregisterContentObserver(waypointObserver);
|
||||
dataSources.unregisterContentObserver(pointObserver);
|
||||
dataSources.unregisterOnSharedPreferenceChangeListener(preferenceListener);
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -13,7 +13,7 @@
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks;
|
||||
package com.google.android.apps.mytracks.content;
|
||||
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||
import android.database.ContentObserver;
|
||||
@@ -24,11 +24,11 @@ import android.location.LocationListener;
|
||||
import android.net.Uri;
|
||||
|
||||
/**
|
||||
* Interface for abstracting external data sources.
|
||||
* Interface for abstracting registration of external data source listeners.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
interface TrackDataSources {
|
||||
interface DataSourcesWrapper {
|
||||
// Preferences
|
||||
void registerOnSharedPreferenceChangeListener(
|
||||
OnSharedPreferenceChangeListener listener);
|
||||
+4
-3
@@ -13,11 +13,12 @@
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks;
|
||||
package com.google.android.apps.mytracks.content;
|
||||
|
||||
import static com.google.android.apps.mytracks.Constants.MAX_LOCATION_AGE_MS;
|
||||
import static com.google.android.apps.mytracks.Constants.MAX_NETWORK_AGE_MS;
|
||||
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
@@ -41,7 +42,7 @@ import android.widget.Toast;
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
class TrackDataSourcesImpl implements TrackDataSources {
|
||||
class DataSourcesWrapperImpl implements DataSourcesWrapper {
|
||||
// System services
|
||||
private final SensorManager sensorManager;
|
||||
private final LocationManager locationManager;
|
||||
@@ -49,7 +50,7 @@ class TrackDataSourcesImpl implements TrackDataSources {
|
||||
private final SharedPreferences sharedPreferences;
|
||||
private final Context context;
|
||||
|
||||
TrackDataSourcesImpl(Context context, SharedPreferences sharedPreferences) {
|
||||
DataSourcesWrapperImpl(Context context, SharedPreferences sharedPreferences) {
|
||||
this.context = context;
|
||||
this.sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
|
||||
this.locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
||||
+289
-427
File diff suppressed because it is too large
Load Diff
+1
-3
@@ -13,7 +13,7 @@
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks;
|
||||
package com.google.android.apps.mytracks.content;
|
||||
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
@@ -23,8 +23,6 @@ import android.location.Location;
|
||||
/**
|
||||
* Listener for track data, for both initial and incremental loading.
|
||||
*
|
||||
* TODO: Allow listening to just part of the data received here.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public interface TrackDataListener {
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright 2011 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 com.google.android.apps.mytracks.content;
|
||||
|
||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
|
||||
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
/**
|
||||
* Manager for the external data listeners and their listening types.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
class TrackDataListeners {
|
||||
|
||||
/** Internal representation of a listener's registration. */
|
||||
static class ListenerRegistration {
|
||||
final TrackDataListener listener;
|
||||
final EnumSet<ListenerDataType> types;
|
||||
|
||||
// State that was last notified to the listener, for resuming after a pause.
|
||||
long lastTrackId;
|
||||
long lastPointId;
|
||||
int lastSamplingFrequency;
|
||||
int numLoadedPoints;
|
||||
|
||||
public ListenerRegistration(TrackDataListener listener,
|
||||
EnumSet<ListenerDataType> types) {
|
||||
this.listener = listener;
|
||||
this.types = types;
|
||||
}
|
||||
|
||||
public boolean isInterestedIn(ListenerDataType type) {
|
||||
return types.contains(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ListenerRegistration [listener=" + listener + ", types=" + types
|
||||
+ ", lastTrackId=" + lastTrackId + ", lastPointId=" + lastPointId
|
||||
+ ", lastSamplingFrequency=" + lastSamplingFrequency
|
||||
+ ", numLoadedPoints=" + numLoadedPoints + "]";
|
||||
}
|
||||
}
|
||||
|
||||
/** Map of external listener to its registration details. */
|
||||
private final Map<TrackDataListener, ListenerRegistration> registeredListeners =
|
||||
new HashMap<TrackDataListener, ListenerRegistration>();
|
||||
|
||||
/**
|
||||
* Map of external paused listener to its registration details.
|
||||
* This will automatically discard listeners which are GCed.
|
||||
*/
|
||||
private final WeakHashMap<TrackDataListener, ListenerRegistration> oldListeners =
|
||||
new WeakHashMap<TrackDataListener, ListenerRegistration>();
|
||||
|
||||
/** Map of data type to external listeners interested in it. */
|
||||
private final Map<ListenerDataType, Set<TrackDataListener>> listenerSetsPerType =
|
||||
new EnumMap<ListenerDataType, Set<TrackDataListener>>(ListenerDataType.class);
|
||||
|
||||
public TrackDataListeners() {
|
||||
// Create sets for all data types at startup.
|
||||
for (ListenerDataType type : ListenerDataType.values()) {
|
||||
listenerSetsPerType.put(type, new LinkedHashSet<TrackDataListener>());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a listener to send data to.
|
||||
* It is ok to call this method before {@link start}, and in that case
|
||||
* the data will only be passed to listeners when {@link start} is called.
|
||||
*
|
||||
* @param listener the listener to register
|
||||
* @param dataTypes the type of data that the listener is interested in
|
||||
*/
|
||||
public ListenerRegistration registerTrackDataListener(final TrackDataListener listener, EnumSet<ListenerDataType> dataTypes) {
|
||||
Log.d(TAG, "Registered track data listener: " + listener);
|
||||
if (registeredListeners.containsKey(listener)) {
|
||||
throw new IllegalStateException("Listener already registered");
|
||||
}
|
||||
|
||||
ListenerRegistration registration = oldListeners.remove(listener);
|
||||
if (registration == null) {
|
||||
registration = new ListenerRegistration(listener, dataTypes);
|
||||
}
|
||||
registeredListeners.put(listener, registration);
|
||||
|
||||
for (ListenerDataType type : dataTypes) {
|
||||
// This is guaranteed not to be null.
|
||||
Set<TrackDataListener> typeSet = listenerSetsPerType.get(type);
|
||||
typeSet.add(listener);
|
||||
}
|
||||
|
||||
return registration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a listener to send data to.
|
||||
*
|
||||
* @param listener the listener to unregister
|
||||
*/
|
||||
public void unregisterTrackDataListener(TrackDataListener listener) {
|
||||
Log.d(TAG, "Unregistered track data listener: " + listener);
|
||||
// Remove and keep the corresponding registration.
|
||||
ListenerRegistration match = registeredListeners.remove(listener);
|
||||
if (match == null) {
|
||||
Log.w(TAG, "Tried to unregister listener which is not registered.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove it from the per-type sets
|
||||
for (ListenerDataType type : match.types) {
|
||||
listenerSetsPerType.get(type).remove(listener);
|
||||
}
|
||||
|
||||
// Keep it around in case it's re-registered soon
|
||||
oldListeners.put(listener, match);
|
||||
}
|
||||
|
||||
public ListenerRegistration getRegistration(TrackDataListener listener) {
|
||||
ListenerRegistration registration = registeredListeners.get(listener);
|
||||
if (registration == null) {
|
||||
registration = oldListeners.get(listener);
|
||||
}
|
||||
return registration;
|
||||
}
|
||||
|
||||
public Set<TrackDataListener> getListenersFor(ListenerDataType type) {
|
||||
return listenerSetsPerType.get(type);
|
||||
}
|
||||
|
||||
public EnumSet<ListenerDataType> getAllRegisteredTypes() {
|
||||
EnumSet<ListenerDataType> listeners = EnumSet.noneOf(ListenerDataType.class);
|
||||
for (ListenerRegistration registration : this.registeredListeners.values()) {
|
||||
listeners.addAll(registration.types);
|
||||
}
|
||||
return listeners;
|
||||
}
|
||||
|
||||
public boolean hasListeners() {
|
||||
return !registeredListeners.isEmpty();
|
||||
}
|
||||
|
||||
public int getNumListeners() {
|
||||
return registeredListeners.size();
|
||||
}
|
||||
}
|
||||
@@ -179,6 +179,11 @@ public class SendToFusionTables implements Runnable {
|
||||
|
||||
// Get the track meta-data
|
||||
Track track = providerUtils.getTrack(trackId);
|
||||
if (track == null) {
|
||||
Log.w(Constants.TAG, "Cannot get track.");
|
||||
return;
|
||||
}
|
||||
|
||||
String originalDescription = track.getDescription();
|
||||
|
||||
// Create a new table:
|
||||
@@ -207,7 +212,6 @@ public class SendToFusionTables implements Runnable {
|
||||
Log.d(Constants.TAG, "SendToFusionTables: Done: " + success);
|
||||
progressIndicator.setProgressValue(PROGRESS_COMPLETE);
|
||||
} finally {
|
||||
|
||||
final boolean finalSuccess = success;
|
||||
final int finalStatusMessageId = statusMessageId;
|
||||
context.runOnUiThread(new Runnable() {
|
||||
@@ -304,7 +308,7 @@ public class SendToFusionTables implements Runnable {
|
||||
|
||||
Cursor locationsCursor = providerUtils.getLocationsCursor(track.getId(), 0, -1, false);
|
||||
try {
|
||||
if (!locationsCursor.moveToFirst()) {
|
||||
if (locationsCursor == null || !locationsCursor.moveToFirst()) {
|
||||
Log.w(Constants.TAG, "Unable to get any points to upload");
|
||||
return false;
|
||||
}
|
||||
@@ -391,7 +395,9 @@ public class SendToFusionTables implements Runnable {
|
||||
|
||||
return true;
|
||||
} finally {
|
||||
locationsCursor.close();
|
||||
if (locationsCursor != null) {
|
||||
locationsCursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/*
|
||||
* 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
|
||||
@@ -76,7 +76,7 @@ public class SendToMyMaps implements Runnable {
|
||||
public interface OnSendCompletedListener {
|
||||
void onSendCompleted(String mapId, boolean success, int statusMessage);
|
||||
}
|
||||
|
||||
|
||||
public SendToMyMaps(Activity context, String mapId, AuthManager auth,
|
||||
long trackId, ProgressIndicator progressIndicator,
|
||||
OnSendCompletedListener onCompletion) {
|
||||
@@ -103,15 +103,20 @@ public class SendToMyMaps implements Runnable {
|
||||
progressIndicator.setProgressValue(1);
|
||||
progressIndicator.setProgressMessage(
|
||||
R.string.progress_message_reading_track);
|
||||
|
||||
|
||||
// Get the track meta-data
|
||||
Track track = providerUtils.getTrack(trackId);
|
||||
if (track == null) {
|
||||
Log.w(Constants.TAG, "Cannot get track.");
|
||||
return;
|
||||
}
|
||||
|
||||
String originalDescription = track.getDescription();
|
||||
track.setDescription("<p>" + track.getDescription() + "</p><p>"
|
||||
+ stringUtils.generateTrackDescription(track, null, null) + "</p>");
|
||||
|
||||
mapsClient = new MapsFacade(context, auth);
|
||||
|
||||
|
||||
// Create a new map if necessary:
|
||||
boolean isNewMap = mapId.equals(NEW_MAP_ID);
|
||||
if (isNewMap) {
|
||||
@@ -166,7 +171,7 @@ public class SendToMyMaps implements Runnable {
|
||||
Log.w(TAG, "SendToMyMaps: upload waypoints failed.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (success) {
|
||||
statusMessageId = isNewMap
|
||||
? R.string.status_new_mymap_has_been_created
|
||||
@@ -178,7 +183,7 @@ public class SendToMyMaps implements Runnable {
|
||||
if (mapsClient != null) {
|
||||
mapsClient.cleanUp();
|
||||
}
|
||||
|
||||
|
||||
final boolean finalSuccess = success;
|
||||
final int finalStatusMessageId = statusMessageId;
|
||||
context.runOnUiThread(new Runnable() {
|
||||
@@ -206,17 +211,17 @@ public class SendToMyMaps implements Runnable {
|
||||
Cursor locationsCursor =
|
||||
providerUtils.getLocationsCursor(track.getId(), 0, -1, false);
|
||||
try {
|
||||
if (!locationsCursor.moveToFirst()) {
|
||||
if (locationsCursor == null || !locationsCursor.moveToFirst()) {
|
||||
Log.w(TAG, "Unable to get any points to upload");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
totalLocationsRead = 0;
|
||||
totalLocationsPrepared = 0;
|
||||
totalLocationsUploaded = 0;
|
||||
totalLocations = locationsCursor.getCount();
|
||||
totalSegmentsUploaded = 0;
|
||||
|
||||
|
||||
// Limit the number of elevation readings. Ideally we would want around 250.
|
||||
int elevationSamplingFrequency =
|
||||
Math.max(1, (int) (totalLocations / 250.0));
|
||||
@@ -224,12 +229,12 @@ public class SendToMyMaps implements Runnable {
|
||||
"Using elevation sampling factor: " + elevationSamplingFrequency
|
||||
+ " on " + totalLocations);
|
||||
double totalDistance = 0;
|
||||
|
||||
|
||||
Vector<Double> distances = new Vector<Double>();
|
||||
Vector<Double> elevations = new Vector<Double>();
|
||||
DoubleBuffer elevationBuffer =
|
||||
new DoubleBuffer(Constants.ELEVATION_SMOOTHING_FACTOR);
|
||||
|
||||
|
||||
List<Location> locations = new ArrayList<Location>(MAX_POINTS_PER_UPLOAD);
|
||||
progressIndicator.setProgressMessage(
|
||||
R.string.progress_message_reading_track);
|
||||
@@ -238,15 +243,15 @@ public class SendToMyMaps implements Runnable {
|
||||
if (totalLocationsRead % 100 == 0) {
|
||||
updateProgress();
|
||||
}
|
||||
|
||||
|
||||
Location loc = providerUtils.createLocation(locationsCursor);
|
||||
locations.add(loc);
|
||||
|
||||
|
||||
if (totalLocationsRead == 0) {
|
||||
// Put a marker at the first point of the first valid segment:
|
||||
mapsClient.uploadMarker(mapId, track.getName(), track.getDescription(), loc, true);
|
||||
}
|
||||
|
||||
|
||||
// Add to the elevation profile.
|
||||
if (loc != null && LocationUtils.isValidLocation(loc)) {
|
||||
// All points go into the smoothing buffer...
|
||||
@@ -256,19 +261,19 @@ public class SendToMyMaps implements Runnable {
|
||||
double dist = lastLocation.distanceTo(loc);
|
||||
totalDistance += dist;
|
||||
}
|
||||
|
||||
|
||||
// ...but only a few points are really used to keep the url short.
|
||||
if (totalLocationsRead % elevationSamplingFrequency == 0) {
|
||||
distances.add(totalDistance);
|
||||
elevations.add(elevationBuffer.getAverage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If the location was not valid, it's a segment split, so make sure the
|
||||
// distance between the previous segment and the new one is not accounted
|
||||
// for in the next iteration.
|
||||
lastLocation = loc;
|
||||
|
||||
|
||||
// Every now and then, upload the accumulated points
|
||||
if (totalLocationsRead % MAX_POINTS_PER_UPLOAD
|
||||
== MAX_POINTS_PER_UPLOAD - 1) {
|
||||
@@ -276,28 +281,30 @@ public class SendToMyMaps implements Runnable {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
totalLocationsRead++;
|
||||
} while (locationsCursor.moveToNext());
|
||||
|
||||
|
||||
// Do a final upload with what's left
|
||||
if (!prepareAndUploadPoints(track, locations)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Put an end marker at the last point of the last valid segment:
|
||||
if (lastLocation != null) {
|
||||
track.setDescription("<p>" + originalDescription + "</p><p>"
|
||||
+ stringUtils.generateTrackDescription(
|
||||
track, distances, elevations)
|
||||
+ "</p>");
|
||||
return mapsClient.uploadMarker(mapId, track.getName(), track.getDescription(),
|
||||
return mapsClient.uploadMarker(mapId, track.getName(), track.getDescription(),
|
||||
lastLocation, false);
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
} finally {
|
||||
locationsCursor.close();
|
||||
if (locationsCursor != null) {
|
||||
locationsCursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,17 +313,17 @@ public class SendToMyMaps implements Runnable {
|
||||
progressIndicator.setProgressMessage(
|
||||
R.string.progress_message_preparing_track);
|
||||
updateProgress();
|
||||
|
||||
|
||||
int numLocations = locations.size();
|
||||
if (numLocations < 2) {
|
||||
Log.d(TAG, "Not preparing/uploading too few points");
|
||||
totalLocationsUploaded += numLocations;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Prepare/pre-process the points
|
||||
ArrayList<Track> splitTracks = prepareLocations(track, locations);
|
||||
|
||||
|
||||
// Start uploading them
|
||||
progressIndicator.setProgressMessage(
|
||||
R.string.progress_message_sending_mymaps);
|
||||
@@ -330,7 +337,7 @@ public class SendToMyMaps implements Runnable {
|
||||
Log.d(TAG,
|
||||
"SendToMyMaps: Prepared feature for upload w/ "
|
||||
+ splitTrack.getLocations().size() + " points.");
|
||||
|
||||
|
||||
// Transmit tracks via GData feed:
|
||||
// -------------------------------
|
||||
Log.d(TAG,
|
||||
@@ -340,7 +347,7 @@ public class SendToMyMaps implements Runnable {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
locations.clear();
|
||||
totalLocationsUploaded += numLocations;
|
||||
updateProgress();
|
||||
@@ -358,7 +365,7 @@ public class SendToMyMaps implements Runnable {
|
||||
private ArrayList<Track> prepareLocations(
|
||||
Track track, Iterable<Location> locations) {
|
||||
ArrayList<Track> splitTracks = new ArrayList<Track>();
|
||||
|
||||
|
||||
// Create segments from each full track:
|
||||
Track segment = new Track();
|
||||
TripStatistics segmentStats = segment.getStatistics();
|
||||
@@ -377,11 +384,11 @@ public class SendToMyMaps implements Runnable {
|
||||
if (loc.getLatitude() > 90) {
|
||||
startNewTrackSegment = true;
|
||||
}
|
||||
|
||||
|
||||
if (startNewTrackSegment) {
|
||||
// Close up the last segment.
|
||||
prepareTrackSegment(segment, splitTracks);
|
||||
|
||||
|
||||
Log.d(TAG,
|
||||
"MyTracksSendToMyMaps: Starting new track segment...");
|
||||
startNewTrackSegment = false;
|
||||
@@ -391,7 +398,7 @@ public class SendToMyMaps implements Runnable {
|
||||
segment.setDescription(/* track.getDescription() */ "");
|
||||
segment.setCategory(track.getCategory());
|
||||
}
|
||||
|
||||
|
||||
if (loc.getLatitude() <= 90) {
|
||||
segment.addLocation(loc);
|
||||
if (segmentStats.getStartTime() < 0) {
|
||||
@@ -400,9 +407,9 @@ public class SendToMyMaps implements Runnable {
|
||||
}
|
||||
totalLocationsPrepared++;
|
||||
}
|
||||
|
||||
|
||||
prepareTrackSegment(segment, splitTracks);
|
||||
|
||||
|
||||
return splitTracks;
|
||||
}
|
||||
|
||||
@@ -425,13 +432,13 @@ public class SendToMyMaps implements Runnable {
|
||||
&& segment.getLocations().size() > 0) {
|
||||
segmentStats.setStopTime(segment.getLocations().size() - 1);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Decimate to 2 meter precision. Mapshop doesn't like too many
|
||||
* points:
|
||||
*/
|
||||
LocationUtils.decimate(segment, 2.0);
|
||||
|
||||
|
||||
/* It the track still has > 500 points, split it in pieces: */
|
||||
if (segment.getLocations().size() > 500) {
|
||||
splitTracks.addAll(LocationUtils.split(segment, 500));
|
||||
|
||||
@@ -363,8 +363,8 @@ public class DocsHelper {
|
||||
wr.close();
|
||||
rd.close();
|
||||
}
|
||||
|
||||
private static IOException newIOException(GDataWrapper wrapper,
|
||||
|
||||
private static IOException newIOException(GDataWrapper<GDataServiceClient> wrapper,
|
||||
String message) {
|
||||
return new IOException(String.format("%s: %d: %s", message,
|
||||
wrapper.getErrorType(), wrapper.getErrorMessage()));
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileForm
|
||||
import com.google.android.apps.mytracks.util.StringUtils;
|
||||
|
||||
import android.location.Location;
|
||||
import android.os.Build;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
@@ -85,7 +86,7 @@ public class GpxTrackWriter implements TrackFormatWriter {
|
||||
pw.println("<?xml-stylesheet type=\"text/xsl\" href=\"details.xsl\"?>");
|
||||
pw.println("<gpx");
|
||||
pw.println(" version=\"1.1\"");
|
||||
pw.println(" creator=\"My Tracks for the G1 running Android\"");
|
||||
pw.format(" creator=\"My Tracks running on %s\"\n", Build.MODEL);
|
||||
pw.println(" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"");
|
||||
pw.println(" xmlns=\"http://www.topografix.com/GPX/1/1\"");
|
||||
pw.print(" xmlns:topografix=\"http://www.topografix.com/GPX/Private/"
|
||||
|
||||
@@ -22,6 +22,7 @@ import com.google.android.apps.mytracks.util.StringUtils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.location.Location;
|
||||
import android.os.Build;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
@@ -70,8 +71,9 @@ public class KmlTrackWriter implements TrackFormatWriter {
|
||||
pw.print(" xmlns=\"http://earth.google.com/kml/2.0\"");
|
||||
pw.println(" xmlns:atom=\"http://www.w3.org/2005/Atom\">");
|
||||
pw.println("<Document>");
|
||||
pw.println("<atom:author><atom:name>My Tracks running on Android"
|
||||
+ "</atom:name></atom:author>");
|
||||
pw.format("<atom:author><atom:name>My Tracks running on %s"
|
||||
+ "</atom:name></atom:author>\n", Build.MODEL);
|
||||
|
||||
pw.println("<name>" + StringUtils.stringAsCData(track.getName())
|
||||
+ "</name>");
|
||||
pw.println("<description>"
|
||||
|
||||
@@ -187,7 +187,7 @@ public class TcxTrackWriter implements TrackFormatWriter {
|
||||
&& sensorData.getCadence().getState() == Sensor.SensorState.SENDING
|
||||
&& sensorData.getCadence().hasValue()) {
|
||||
pw.print(" <Cadence>");
|
||||
pw.print(sensorData.getCadence().getValue());
|
||||
pw.print(Math.min(255, sensorData.getCadence().getValue()));
|
||||
pw.println("</Cadence>");
|
||||
}
|
||||
}
|
||||
@@ -209,7 +209,7 @@ public class TcxTrackWriter implements TrackFormatWriter {
|
||||
}
|
||||
pw.println(" </Lap>");
|
||||
pw.print(" <Creator xsi:type=\"Device_t\">");
|
||||
pw.format("<Name>%s</Name>\n", Build.MODEL);
|
||||
pw.format("<Name>My Tracks running on %s</Name>\n", Build.MODEL);
|
||||
|
||||
// The following code is correct. ID is inconsistently capitalized in the
|
||||
// TCX schema.
|
||||
|
||||
@@ -16,10 +16,13 @@
|
||||
|
||||
package com.google.android.apps.mytracks.io.file;
|
||||
|
||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* A factory to produce track writers for any format.
|
||||
@@ -90,6 +93,11 @@ public class TrackWriterFactory {
|
||||
MyTracksProviderUtils providerUtils,
|
||||
long trackId, TrackFileFormat format) {
|
||||
Track track = providerUtils.getTrack(trackId);
|
||||
if (track == null) {
|
||||
Log.w(TAG, "Trying to create a writer for an invalid track, id=" + trackId);
|
||||
return null;
|
||||
}
|
||||
|
||||
return newWriter(context, providerUtils, track, format);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,14 +24,16 @@ import com.google.android.maps.mytracks.R;
|
||||
* A collection of methods for message parsers.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
* @author Nico Laum
|
||||
*/
|
||||
public class SensorUtils {
|
||||
|
||||
private SensorUtils() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract one unsigned short from a big endian byte array.
|
||||
*
|
||||
* @param buffer the buffer to extract the short from
|
||||
* @param index the first byte to be interpreted as part of the short
|
||||
* @return The unsigned short at the given index in the buffer
|
||||
@@ -42,6 +44,58 @@ public class SensorUtils {
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract one unsigned short from a little endian byte array.
|
||||
*
|
||||
* @param buffer the buffer to extract the short from
|
||||
* @param index the first byte to be interpreted as part of the short
|
||||
* @return The unsigned short at the given index in the buffer
|
||||
*/
|
||||
public static int unsignedShortToIntLittleEndian(byte[] buffer, int index) {
|
||||
int r = buffer[index] & 0xFF;
|
||||
r |= (buffer[index + 1] & 0xFF) << 8;
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns CRC8 (polynomial 0x8C) from byte array buffer[start] until
|
||||
* (including) buffer[end]
|
||||
*
|
||||
* @param buffer the byte array of data (payload)
|
||||
* @param start the position in the byte array where the payload begins
|
||||
* @param end the position in the byte array where the payload ends
|
||||
* @return CRC8 value
|
||||
*/
|
||||
public static byte getCrc8(byte[] buffer, int start, int length) {
|
||||
byte crc = 0x0;
|
||||
|
||||
for (int i = start; i < (start + length); i++) {
|
||||
crc = crc8PushByte(crc, buffer[i]);
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a CRC8 value by using the next byte passed to this method
|
||||
*
|
||||
* @param crc int of crc value
|
||||
* @param add the next byte to add to the CRC8 calculation
|
||||
*/
|
||||
private static byte crc8PushByte(byte crc, byte add) {
|
||||
crc = (byte) (crc ^ add);
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if ((crc & 0x1) != 0x0) {
|
||||
// Using a 0xFF bit assures that 0-bits are introduced during the shift operation.
|
||||
// Otherwise, implicit casts to signed int could shift in 1-bits if the signed bit is 1.
|
||||
crc = (byte) (((crc & 0xFF) >> 1) ^ 0x8C);
|
||||
} else {
|
||||
crc = (byte) ((crc & 0xFF) >> 1);
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
public static String getStateAsString(Sensor.SensorState state, Context c) {
|
||||
switch (state) {
|
||||
case NONE:
|
||||
|
||||
+24
-13
@@ -21,12 +21,16 @@ import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.android.apps.mytracks.content.Sensor;
|
||||
|
||||
/**
|
||||
* An implementation of a SensorData parser for Zephyr HRM.
|
||||
* An implementation of a Sensor MessageParser for Zephyr.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class ZephyrMessageParser implements MessageParser {
|
||||
|
||||
public static final int ZEPHYR_HXM_BYTE_STX = 0;
|
||||
public static final int ZEPHYR_HXM_BYTE_CRC = 58;
|
||||
public static final int ZEPHYR_HXM_BYTE_ETX = 59;
|
||||
|
||||
@Override
|
||||
public Sensor.SensorDataSet parseBuffer(byte[] buffer) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
@@ -34,20 +38,24 @@ public class ZephyrMessageParser implements MessageParser {
|
||||
sb.append(String.format("%02X", buffer[i]));
|
||||
}
|
||||
Log.w(Constants.TAG, "Got zephyr data: " + sb);
|
||||
// The provided units are 1/16 strides per minute.
|
||||
// TODO: Fix the cadence calculation.
|
||||
// int cadence = SensorUtils.unsignedShortToInt(buffer, 56);
|
||||
// Heart Rate
|
||||
Sensor.SensorData.Builder b = Sensor.SensorData.newBuilder()
|
||||
.setValue(buffer[12] & 0xFF)
|
||||
.setState(Sensor.SensorState.SENDING);
|
||||
// Cadence
|
||||
//.setCadence(cadence / 16)
|
||||
//.build();
|
||||
Sensor.SensorData.Builder heartrate = Sensor.SensorData.newBuilder()
|
||||
.setValue(buffer[12] & 0xFF)
|
||||
.setState(Sensor.SensorState.SENDING);
|
||||
// Changes Nico Laum (Power and Cadence)
|
||||
Sensor.SensorData.Builder power = Sensor.SensorData.newBuilder()
|
||||
.setValue(buffer[11])
|
||||
.setState(Sensor.SensorState.SENDING);
|
||||
Sensor.SensorData.Builder cadence = Sensor.SensorData.newBuilder()
|
||||
.setValue(SensorUtils.unsignedShortToIntLittleEndian(buffer, 56) / 16)
|
||||
.setState(Sensor.SensorState.SENDING);
|
||||
|
||||
Sensor.SensorDataSet sds =
|
||||
Sensor.SensorDataSet.newBuilder()
|
||||
.setCreationTime(System.currentTimeMillis())
|
||||
.setHeartRate(b)
|
||||
.setPower(power)
|
||||
.setHeartRate(heartrate)
|
||||
.setCadence(cadence)
|
||||
.build();
|
||||
|
||||
return sds;
|
||||
@@ -55,8 +63,11 @@ public class ZephyrMessageParser implements MessageParser {
|
||||
|
||||
@Override
|
||||
public boolean isValid(byte[] buffer) {
|
||||
// TODO crc etc.
|
||||
return buffer[0] == 0x02 && buffer[59] == 0x03;
|
||||
// Check STX (Start of Text), ETX (End of Text) and CRC Checksum
|
||||
return buffer.length > ZEPHYR_HXM_BYTE_ETX
|
||||
&& buffer[ZEPHYR_HXM_BYTE_STX] == 0x02
|
||||
&& buffer[ZEPHYR_HXM_BYTE_ETX] == 0x03
|
||||
&& SensorUtils.getCrc8(buffer, 3, 55) == buffer[ZEPHYR_HXM_BYTE_CRC];
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -19,7 +19,6 @@ package com.google.android.apps.mytracks.stats;
|
||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.android.apps.mytracks.SettingsActivity;
|
||||
|
||||
import android.location.Location;
|
||||
import android.util.Log;
|
||||
|
||||
Reference in New Issue
Block a user