Merge heads

This commit is contained in:
youtaol
2012-05-30 09:49:31 +08:00
parent d98617b23c
commit ad01b379c7
30 changed files with 1444 additions and 1554 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:installLocation="auto"
android:versionCode="40"
android:versionCode="41"
android:versionName="2.0"
package="com.google.android.maps.mytracks" >
+6 -6
View File
@@ -34,12 +34,12 @@ limitations under the License.
<TextView
android:id="@+id/sensor_state_last_sensor_time"
style="@style/SensorStateValue" />
<!-- Power -->
<!-- Heart rate -->
<TextView
android:text="@string/sensor_state_power"
android:text="@string/sensor_state_heart_rate"
style="@style/TextMedium" />
<TextView
android:id="@+id/sensor_state_power"
android:id="@+id/sensor_state_heart_rate"
style="@style/SensorStateValue" />
<!-- Cadence -->
<TextView
@@ -48,12 +48,12 @@ limitations under the License.
<TextView
android:id="@+id/sensor_state_cadence"
style="@style/SensorStateValue" />
<!-- Heart rate -->
<!-- Power -->
<TextView
android:text="@string/sensor_state_heart_rate"
android:text="@string/sensor_state_power"
style="@style/TextMedium" />
<TextView
android:id="@+id/sensor_state_heart_rate"
android:id="@+id/sensor_state_power"
style="@style/SensorStateValue" />
<!-- Battery level -->
<TextView
+4
View File
@@ -33,6 +33,10 @@ limitations under the License.
<color name="speed_border">#FF0066FF</color>
<color name="speed_fill">#40C8E0FF</color>
<!-- Blue and a transparent blue -->
<color name="pace_border">#FF0066FF</color>
<color name="pace_fill">#40C8E0FF</color>
<!-- Red and a transparent red -->
<color name="heartrate_border">#FFCC3300</color>
<color name="heartrate_fill">#40E88A70</color>
+34 -24
View File
@@ -451,6 +451,10 @@ limitations under the License.
%2$.1f speed in miles per hour">
Average speed: %1$.2f km/h (%2$.1f mi/h)
</string>
<string name="description_elevation_imperial"
translation_description="Elevation in imperial units, feet.">
Elevation (ft)
</string>
<string name="description_elevation_gain"
translation_description="Elevation gain description.
@@ -458,6 +462,10 @@ limitations under the License.
%2$d elevation in feet">
Elevation gain: %1$d m (%2$d ft)
</string>
<string name="description_elevation_metric"
translation_description="Elevation in metric units, meters.">
Elevation (m)
</string>
<string name="description_fastest_pace"
translation_description="Fastest pace description.
@@ -489,10 +497,6 @@ limitations under the License.
translation_description="Label for location speed in meters per second.">
Speed (m/s)
</string>
<string name="description_location_time"
translation_description="Label for location time.">
Time
</string>
<string name="description_max_elevation"
translation_description="Max elevation description.
@@ -532,6 +536,14 @@ limitations under the License.
%1$s time. E.g., '1:08:59'">
Moving time: %1$s
</string>
<string name="description_pace_imperial"
translation_description="Pace in imperial units, hours per mile.">
Pace (hr/mi)
</string>
<string name="description_pace_metric"
translation_description="Pace in metric units, hours per kilometer.">
Pace (hr/km)
</string>
<string name="description_recorded_time"
translation_description="Recorded time description.
@@ -554,6 +566,18 @@ limitations under the License.
translation_description="Label for power with its unit watt (W).">
Power (W)
</string>
<string name="description_speed_imperial"
translation_description="Speed in imperial units, miles per hour.">
Speed (mi/hr)
</string>
<string name="description_speed_metric"
translation_description="Speed in metric units, kilometers per hour.">
Speed (km/hr)
</string>
<string name="description_time"
translation_description="Label for time.">
Time
</string>
<string name="description_track_point"
translation_description="Label for a track point">
Point
@@ -1721,6 +1745,12 @@ limitations under the License.
%1$d: sensor id number. E.g., '1'.">
Paired with: %1$d.
</string>
<string name="settings_sensor_bluetooth_connected"
translation_description="The message when connected to a bluetooth sensor.
%1$s: sensor name. E.g., 'polar bluetooth'.">
Connected to %1$s
</string>
<string name="settings_sensor_bluetooth_options"
translation_description="In the 'Sensor' settings, the section header to configure the Bluetooth
options.
@@ -1898,26 +1928,6 @@ limitations under the License.
of inclination to the horizontal, also known as slope or incline.">
Grade
</string>
<string name="settings_stats_rate_pace_imperial"
translation_description="In the 'Stats' settings, the option to display pace, with example in
imperial units.">
Pace (hr/mi)
</string>
<string name="settings_stats_rate_pace_metric"
translation_description="In the 'Stats' settings, the option to display pace, with example in
metric units.">
Pace (hr/km)
</string>
<string name="settings_stats_rate_speed_imperial"
translation_description="In the 'Stats' settings, the option to display speed, with example in
imperial units.">
Speed (mi/hr)
</string>
<string name="settings_stats_rate_speed_metric"
translation_description="In the 'Stats' settings, the option to display speed, with example in
metric units.">
Speed (km/hr)
</string>
<string name="settings_stats_rate_title"
translation_description="In the 'Stats' settings, the option to change the preferred rate,
either speed (mi/hr) or pace (hr/mi).">
@@ -13,293 +13,252 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.stats.ExtremityMonitor;
import com.google.common.annotations.VisibleForTesting;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Paint.Style;
import android.graphics.Path;
import java.text.NumberFormat;
/**
* This class encapsulates meta data about one series of values for a chart.
*
* This class encapsulates the meta data for one series of the chart values.
*
* @author Sandor Dornbush
*/
public class ChartValueSeries {
private final ExtremityMonitor monitor = new ExtremityMonitor();
private final NumberFormat format;
private final Path path = new Path();
private static final float STROKE_WIDTH = 2f;
private final int absoluteMin;
private final int absoluteMax;
private final int[] intervalValues;
private final int metricTitleId;
private final int imperialTitleId;
private final Paint fillPaint;
private final Paint strokePaint;
private final Paint labelPaint;
private final ZoomSettings zoomSettings;
private String title;
private double min;
private double max = 1.0;
private int effectiveMax;
private int effectiveMin;
private double spread;
private int interval;
private final Paint titlePaint;
private final Paint markerPaint;
private final ExtremityMonitor extremityMonitor;
private final NumberFormat numberFormat;
private final Path path;
private int interval = 1;
private int minMarkerValue = 0;
private int maxMarkerValue = interval * ChartView.Y_AXIS_INTERVALS;
private boolean enabled = true;
/**
* This class controls how effective min/max values of a {@link ChartValueSeries} are calculated.
* Constructor.
*
* @param context the context
* @param absoluteMin the absolute min value
* @param absoluteMax the absolute max value
* @param intervalValues the list of interval values
* @param metricTitleId the metric title id
* @param imperialTitleId the imperial title id
* @param fillColor the fill color
* @param strokeColor the stroke color
*/
public static class ZoomSettings {
private int intervals;
private final int absoluteMin;
private final int absoluteMax;
private final int[] zoomLevels;
public ChartValueSeries(Context context, int absoluteMin, int absoluteMax, int[] intervalValues,
int metricTitleId, int imperialTitleId, int fillColor, int strokeColor) {
this.absoluteMin = absoluteMin;
this.absoluteMax = absoluteMax;
this.intervalValues = intervalValues;
this.metricTitleId = metricTitleId;
this.imperialTitleId = imperialTitleId;
public ZoomSettings(int intervals, int[] zoomLevels) {
this.intervals = intervals;
this.absoluteMin = Integer.MAX_VALUE;
this.absoluteMax = Integer.MIN_VALUE;
this.zoomLevels = zoomLevels;
checkArgs();
}
public ZoomSettings(int intervals, int absoluteMin, int absoluteMax, int[] zoomLevels) {
this.intervals = intervals;
this.absoluteMin = absoluteMin;
this.absoluteMax = absoluteMax;
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");
}
for (int i = 1; i < zoomLevels.length; ++i) {
if (zoomLevels[i] <= zoomLevels[i - 1]) {
throw new IllegalArgumentException("Expecting zoom levels in ascending order");
}
}
}
public int getIntervals() {
return intervals;
}
public int getAbsoluteMin() {
return absoluteMin;
}
public int getAbsoluteMax() {
return absoluteMax;
}
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
*/
public int calculateInterval(double min, double max) {
min = Math.min(min, absoluteMin);
max = Math.max(max, absoluteMax);
for (int i = 0; i < zoomLevels.length; ++i) {
int zoomLevel = zoomLevels[i];
int roundedMin = (int)(min / zoomLevel) * zoomLevel;
if (roundedMin > min) {
roundedMin -= zoomLevel;
}
double interval = (max - roundedMin) / intervals;
if (zoomLevel >= interval) {
return zoomLevel;
}
}
return zoomLevels[zoomLevels.length - 1];
}
}
/**
* Constructs a new chart value series.
*
* @param context The context for the chart
* @param fillColor The paint for filling the chart
* @param strokeColor The paint for stroking the outside the chart, optional
* @param zoomSettings The settings related to zooming
* @param titleId The title ID
*
* TODO: Get rid of Context and inject appropriate values instead.
*/
public ChartValueSeries(
Context context, int fillColor, int strokeColor, ZoomSettings zoomSettings, int titleId) {
this.format = NumberFormat.getIntegerInstance();
fillPaint = new Paint();
fillPaint.setStyle(Style.FILL);
fillPaint.setColor(context.getResources().getColor(fillColor));
fillPaint.setAntiAlias(true);
if (strokeColor != -1) {
strokePaint = new Paint();
strokePaint.setStyle(Style.STROKE);
strokePaint.setColor(context.getResources().getColor(strokeColor));
strokePaint.setAntiAlias(true);
// Make a copy of the stroke paint with the default thickness.
labelPaint = new Paint(strokePaint);
strokePaint.setStrokeWidth(2f);
} else {
strokePaint = null;
labelPaint = fillPaint;
}
this.zoomSettings = zoomSettings;
this.title = context.getString(titleId);
strokePaint = new Paint();
strokePaint.setStyle(Style.STROKE);
strokePaint.setColor(context.getResources().getColor(strokeColor));
strokePaint.setAntiAlias(true);
float scale = context.getResources().getDisplayMetrics().density;
// Make copies of the stroke paint with the default thickness
titlePaint = new Paint(strokePaint);
titlePaint.setTextSize(ChartView.MEDIUM_TEXT_SIZE * scale);
titlePaint.setTextAlign(Align.CENTER);
markerPaint = new Paint(strokePaint);
markerPaint.setTextSize(ChartView.SMALL_TEXT_SIZE * scale);
markerPaint.setTextAlign(Align.RIGHT);
// Set stroke paint thickness
strokePaint.setStrokeWidth(STROKE_WIDTH);
extremityMonitor = new ExtremityMonitor();
numberFormat = NumberFormat.getIntegerInstance();
path = new Path();
}
/**
* Draws the path of the chart
* Sets the series enabled value.
*
* @param enabled true to enable
*/
public void drawPath(Canvas c) {
c.drawPath(path, fillPaint);
if (strokePaint != null) {
c.drawPath(path, strokePaint);
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
/**
* Resets this series
*/
public void reset() {
monitor.reset();
}
/**
* Updates this series with a new value
*/
public void update(double d) {
monitor.update(d);
}
/**
* @return The interval between markers
*/
public int getInterval() {
return interval;
}
/**
* Determines what the min and max of the chart will be.
* This will round down and up the min and max respectively.
*/
public void updateDimension() {
if (monitor.getMax() == Double.NEGATIVE_INFINITY) {
min = 0;
max = 1;
} else {
min = monitor.getMin();
max = monitor.getMax();
}
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;
// Round it down.
effectiveMin = ((int) (min / interval)) * interval;
if (min < 0) {
effectiveMin -= interval;
}
spread = effectiveMax - effectiveMin;
}
/**
* @return The length of the longest string from the series
*/
public int getMaxLabelLength() {
String minS = format.format(effectiveMin);
String maxS = format.format(getMax());
return Math.max(minS.length(), maxS.length());
}
/**
* @return The rounded down minimum value
*/
public int getMin() {
return effectiveMin;
}
/**
* @return The rounded up maximum value
*/
public int getMax() {
return effectiveMax;
}
/**
* @return The difference between the min and max values in the series
*/
public double getSpread() {
return spread;
}
/**
* @return The number format for this series
*/
NumberFormat getFormat() {
return format;
}
/**
* @return The path for this series
*/
Path getPath() {
return path;
}
/**
* @return The paint for this series
*/
Paint getPaint() {
return strokePaint == null ? fillPaint : strokePaint;
}
public Paint getLabelPaint() {
return labelPaint;
}
/**
* @return The title of the series
*/
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
/**
* @return is this series enabled
* Returns true if the series is enabled.
*/
public boolean isEnabled() {
return enabled;
}
/**
* Sets the series enabled flag.
* Returns true if the series has data.
*/
public void setEnabled(boolean enabled) {
this.enabled = enabled;
public boolean hasData() {
return extremityMonitor.hasData();
}
public boolean hasData() {
return monitor.hasData();
/**
* Updates the series with a new value.
*
* @param value the new value
*/
public void update(double value) {
extremityMonitor.update(value);
}
/**
* Gets the path.
*/
public Path getPath() {
return path;
}
/**
* Draws the path on canvas.
*
* @param canvas the canvas
*/
public void drawPath(Canvas canvas) {
canvas.drawPath(path, fillPaint);
canvas.drawPath(path, strokePaint);
}
/**
* Updates the y axis dimension.
*/
public void updateDimension() {
double min = hasData() ? extremityMonitor.getMin() : 0.0;
double max = hasData() ? extremityMonitor.getMax() : 1.0;
min = Math.max(min, absoluteMin);
max = Math.min(max, absoluteMax);
interval = getInterval(min, max);
minMarkerValue = getMinMarkerValue(min, interval);
maxMarkerValue = minMarkerValue + interval * ChartView.Y_AXIS_INTERVALS;
}
/**
* Gets the interval value.
*
* @param min the min value
* @param max the max value
*/
private int getInterval(double min, double max) {
for (int i = 0; i < intervalValues.length; i++) {
int intervalValue = intervalValues[i];
int minValue = getMinMarkerValue(min, intervalValue);
double targetInterval = (max - minValue) / ChartView.Y_AXIS_INTERVALS;
if (intervalValue >= targetInterval) {
return intervalValue;
}
}
// Return the largest interval
return intervalValues[intervalValues.length - 1];
}
/**
* Gets the min marker value.
*
* @param min the min series value
* @param intervalValue the interval value
*/
private int getMinMarkerValue(double min, int intervalValue) {
// Round down to the nearest intervalValue
int value = ((int) (min / intervalValue)) * intervalValue;
// value > min if min is negative
if (value > min) {
return value - intervalValue;
}
return value;
}
/**
* Gets the interval value.
*/
public int getInterval() {
return interval;
}
/**
* Gets the min marker value.
*/
public int getMinMarkerValue() {
return minMarkerValue;
}
/**
* Gets the max marker value.
*/
@VisibleForTesting
int getMaxMarkerValue() {
return maxMarkerValue;
}
/**
* Gets the title id.
*/
public int getTitleId(boolean metricUnits) {
return metricUnits ? metricTitleId : imperialTitleId;
}
/**
* Gets the title paint.
*/
public Paint getTitlePaint() {
return titlePaint;
}
/**
* Gets the marker paint.
*/
public Paint getMarkerPaint() {
return markerPaint;
}
/**
* Gets the largest marker.
*/
public String getLargestMarker() {
String minMarker = numberFormat.format(getMinMarkerValue());
String maxMarker = numberFormat.format(getMaxMarkerValue());
return minMarker.length() >= maxMarker.length() ? minMarker : maxMarker;
}
/**
* Formats a marker value.
*
* @param value the value
*/
public String formatMarker(int value) {
return numberFormat.format(value);
}
}
File diff suppressed because it is too large Load Diff
@@ -172,8 +172,7 @@ public class ImportActivity extends Activity {
if (success) {
showDialog(DIALOG_RESULT_ID);
} else {
Toast.makeText(this, getString(R.string.sd_card_import_error), Toast.LENGTH_LONG)
.show();
Toast.makeText(this, R.string.sd_card_import_error, Toast.LENGTH_LONG).show();
finish();
}
}
@@ -101,6 +101,7 @@ public class MarkerDetailActivity extends AbstractMyTracksActivity {
Intent intent = IntentUtils.newIntent(this, MarkerListActivity.class)
.putExtra(MarkerListActivity.EXTRA_TRACK_ID, waypoint.getTrackId());
startActivity(intent);
finish();
}
@Override
@@ -126,7 +126,7 @@ public class SensorStateActivity extends AbstractMyTracksActivity {
*/
private void stopTempSensorManager() {
if (tempSensorManager != null) {
SensorManagerFactory.getInstance().releaseSensorManager(tempSensorManager);
SensorManagerFactory.releaseTempSensorManager();
tempSensorManager = null;
}
}
@@ -139,7 +139,7 @@ public class SensorStateActivity extends AbstractMyTracksActivity {
Sensor.SensorDataSet sensorDataSet = null;
if (tempSensorManager == null) {
tempSensorManager = SensorManagerFactory.getInstance().getSensorManager(this);
tempSensorManager = SensorManagerFactory.getTempSensorManager(this);
}
if (tempSensorManager != null) {
@@ -199,19 +199,19 @@ public class SensorStateActivity extends AbstractMyTracksActivity {
String lastSensorTime = sensorDataSet == null ? getString(R.string.value_unknown)
: getLastSensorTime(sensorDataSet);
String power = sensorDataSet == null ? getString(R.string.value_unknown)
: getPower(sensorDataSet);
String cadence = sensorDataSet == null ? getString(R.string.value_unknown)
: getCadence(sensorDataSet);
String heartRate = sensorDataSet == null ? getString(R.string.value_unknown)
: getHeartRate(sensorDataSet);
String cadence = sensorDataSet == null ? getString(R.string.value_unknown)
: getCadence(sensorDataSet);
String power = sensorDataSet == null ? getString(R.string.value_unknown)
: getPower(sensorDataSet);
String battery = sensorDataSet == null ? getString(R.string.value_unknown)
: getBattery(sensorDataSet);
((TextView) findViewById(R.id.sensor_state_last_sensor_time)).setText(lastSensorTime);
((TextView) findViewById(R.id.sensor_state_power)).setText(power);
((TextView) findViewById(R.id.sensor_state_cadence)).setText(cadence);
((TextView) findViewById(R.id.sensor_state_heart_rate)).setText(heartRate);
((TextView) findViewById(R.id.sensor_state_cadence)).setText(cadence);
((TextView) findViewById(R.id.sensor_state_power)).setText(power);
((TextView) findViewById(R.id.sensor_state_battery)).setText(battery);
}
@@ -225,19 +225,20 @@ public class SensorStateActivity extends AbstractMyTracksActivity {
}
/**
* Gets the power.
* Gets the heart rate.
*
* @param sensorDataSet sensor data set
*/
private String getPower(Sensor.SensorDataSet sensorDataSet) {
private String getHeartRate(Sensor.SensorDataSet sensorDataSet) {
String value;
if (sensorDataSet.hasPower() && sensorDataSet.getPower().hasValue()
&& sensorDataSet.getPower().getState() == Sensor.SensorState.SENDING) {
value = getString(R.string.sensor_state_power_value, sensorDataSet.getPower().getValue());
if (sensorDataSet.hasHeartRate() && sensorDataSet.getHeartRate().hasValue()
&& sensorDataSet.getHeartRate().getState() == Sensor.SensorState.SENDING) {
value = getString(
R.string.sensor_state_heart_rate_value, sensorDataSet.getHeartRate().getValue());
} else {
value = SensorUtils.getStateAsString(
sensorDataSet.hasPower() ? sensorDataSet.getPower().getState() : Sensor.SensorState.NONE,
this);
sensorDataSet.hasHeartRate() ? sensorDataSet.getHeartRate().getState()
: Sensor.SensorState.NONE, this);
}
return value;
}
@@ -261,20 +262,19 @@ public class SensorStateActivity extends AbstractMyTracksActivity {
}
/**
* Gets the heart rate.
* Gets the power.
*
* @param sensorDataSet sensor data set
*/
private String getHeartRate(Sensor.SensorDataSet sensorDataSet) {
private String getPower(Sensor.SensorDataSet sensorDataSet) {
String value;
if (sensorDataSet.hasHeartRate() && sensorDataSet.getHeartRate().hasValue()
&& sensorDataSet.getHeartRate().getState() == Sensor.SensorState.SENDING) {
value = getString(
R.string.sensor_state_heart_rate_value, sensorDataSet.getHeartRate().getValue());
if (sensorDataSet.hasPower() && sensorDataSet.getPower().hasValue()
&& sensorDataSet.getPower().getState() == Sensor.SensorState.SENDING) {
value = getString(R.string.sensor_state_power_value, sensorDataSet.getPower().getValue());
} else {
value = SensorUtils.getStateAsString(
sensorDataSet.hasHeartRate() ? sensorDataSet.getHeartRate().getState()
: Sensor.SensorState.NONE, this);
sensorDataSet.hasPower() ? sensorDataSet.getPower().getState() : Sensor.SensorState.NONE,
this);
}
return value;
}
@@ -230,6 +230,7 @@ public class TrackDetailActivity extends AbstractMyTracksActivity implements Del
protected void onHomeSelected() {
Intent intent = IntentUtils.newIntent(this, TrackListActivity.class);
startActivity(intent);
finish();
}
@Override
@@ -154,7 +154,7 @@ class DataSourcesWrapperImpl implements DataSourcesWrapper {
}
// Let the user know we have only an approximate location:
Toast.makeText(context, context.getString(toastResId), Toast.LENGTH_LONG).show();
Toast.makeText(context, toastResId, Toast.LENGTH_LONG).show();
}
return loc;
}
@@ -77,15 +77,15 @@ public class ChartFragment extends Fragment implements TrackDataListener {
private boolean reportSpeed = true;
private boolean chartByDistance = true;
private boolean[] chartShow = new boolean[] {true, true, true, true, true};
private boolean[] chartShow = new boolean[] {true, true, true, true, true, true};
// UI elements
private ChartView chartView;
private ZoomControls zoomControls;
/**
* A runnable that will remove the spinner (if any), enable/disable zoom
* controls and orange pointer as appropriate and redraw.
* A runnable that will enable/disable zoom controls and orange pointer as
* appropriate and redraw.
*/
private final Runnable updateChart = new Runnable() {
@Override
@@ -215,7 +215,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
@Override
public void onNewTrackPoint(Location location) {
if (LocationUtils.isValidLocation(location)) {
double[] data = new double[6];
double[] data = new double[ChartView.NUM_SERIES + 1];
fillDataPoint(location, data);
pendingPoints.add(data);
}
@@ -280,7 +280,11 @@ public class ChartFragment extends Fragment implements TrackDataListener {
return false;
}
reportSpeed = speed;
chartView.setReportSpeed(speed, getActivity());
chartView.setReportSpeed(reportSpeed);
boolean chartShowSpeed = PreferencesUtils.getBoolean(
getActivity(), R.string.chart_show_speed_key, PreferencesUtils.CHART_SHOW_SPEED_DEFAULT);
setSeriesEnabled(ChartView.SPEED_SERIES, chartShowSpeed && reportSpeed);
setSeriesEnabled(ChartView.PACE_SERIES, chartShowSpeed && !reportSpeed);
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
@@ -306,8 +310,13 @@ public class ChartFragment extends Fragment implements TrackDataListener {
R.string.chart_show_elevation_key, PreferencesUtils.CHART_SHOW_ELEVATION_DEFAULT))) {
needUpdate = true;
}
if (setSeriesEnabled(ChartView.SPEED_SERIES, PreferencesUtils.getBoolean(
getActivity(), R.string.chart_show_speed_key, PreferencesUtils.CHART_SHOW_SPEED_DEFAULT))) {
boolean chartShowSpeed = PreferencesUtils.getBoolean(
getActivity(), R.string.chart_show_speed_key, PreferencesUtils.CHART_SHOW_SPEED_DEFAULT);
if (setSeriesEnabled(ChartView.SPEED_SERIES, chartShowSpeed && reportSpeed)) {
needUpdate = true;
}
if (setSeriesEnabled(ChartView.PACE_SERIES, chartShowSpeed && !reportSpeed)) {
needUpdate = true;
}
if (setSeriesEnabled(ChartView.POWER_SERIES, PreferencesUtils.getBoolean(
@@ -405,13 +414,14 @@ public class ChartFragment extends Fragment implements TrackDataListener {
}
/**
* Given a location, fill in a data point, an array of double[6]. <br>
* Given a location, fill in a data point, an array of double[]. <br>
* data[0] = time/distance <br>
* data[1] = elevation <br>
* data[2] = speed <br>
* data[3] = power <br>
* data[4] = cadence <br>
* data[5] = heart rate <br>
* data[3] = pace <br>
* data[4] = heart rate <br>
* data[5] = cadence <br>
* data[6] = power <br>
*
* @param location the location
* @param data the data point to fill in, can be null
@@ -421,9 +431,10 @@ public class ChartFragment extends Fragment implements TrackDataListener {
double timeOrDistance = Double.NaN;
double elevation = Double.NaN;
double speed = Double.NaN;
double power = Double.NaN;
double pace = Double.NaN;
double heartRate = Double.NaN;
double cadence = Double.NaN;
double heartRate = Double.NaN;
double power = Double.NaN;
// TODO: Use TripStatisticsBuilder
if (chartByDistance) {
@@ -460,36 +471,36 @@ public class ChartFragment extends Fragment implements TrackDataListener {
if (!metricUnits) {
speed *= UnitConversions.KM_TO_MI;
}
if (!reportSpeed) {
speed = speed == 0 ? 0.0 : 60.0 / speed;
}
pace = speed == 0 ? 0.0 : 60.0 / speed;
if (location instanceof MyTracksLocation
&& ((MyTracksLocation) location).getSensorDataSet() != null) {
SensorDataSet sensorDataSet = ((MyTracksLocation) location).getSensorDataSet();
if (sensorDataSet.hasPower() && sensorDataSet.getPower().getState() == Sensor.SensorState.SENDING
&& sensorDataSet.getPower().hasValue()) {
power = sensorDataSet.getPower().getValue();
if (sensorDataSet.hasHeartRate()
&& sensorDataSet.getHeartRate().getState() == Sensor.SensorState.SENDING
&& sensorDataSet.getHeartRate().hasValue()) {
heartRate = sensorDataSet.getHeartRate().getValue();
}
if (sensorDataSet.hasCadence()
&& sensorDataSet.getCadence().getState() == Sensor.SensorState.SENDING
&& sensorDataSet.getCadence().hasValue()) {
cadence = sensorDataSet.getCadence().getValue();
}
if (sensorDataSet.hasHeartRate()
&& sensorDataSet.getHeartRate().getState() == Sensor.SensorState.SENDING
&& sensorDataSet.getHeartRate().hasValue()) {
heartRate = sensorDataSet.getHeartRate().getValue();
if (sensorDataSet.hasPower()
&& sensorDataSet.getPower().getState() == Sensor.SensorState.SENDING
&& sensorDataSet.getPower().hasValue()) {
power = sensorDataSet.getPower().getValue();
}
}
if (data != null) {
data[0] = timeOrDistance;
data[1] = elevation;
data[2] = speed;
data[3] = power;
data[4] = cadence;
data[5] = heartRate;
data[3] = pace;
data[4] = heartRate;
data[5] = cadence;
data[6] = power;
}
lastLocation = location;
}
@@ -110,7 +110,7 @@ public class CsvTrackWriter implements TrackFormatWriter {
context.getString(R.string.description_location_bearing),
context.getString(R.string.description_location_accuracy),
context.getString(R.string.description_location_speed),
context.getString(R.string.description_location_time));
context.getString(R.string.description_time));
}
@Override
@@ -143,7 +143,7 @@ public class CsvTrackWriter implements TrackFormatWriter {
context.getString(R.string.description_location_bearing),
context.getString(R.string.description_location_accuracy),
context.getString(R.string.description_location_speed),
context.getString(R.string.description_location_time),
context.getString(R.string.description_time),
context.getString(R.string.description_sensor_power),
context.getString(R.string.description_sensor_cadence),
context.getString(R.string.description_sensor_heart_rate),
@@ -70,8 +70,9 @@ public class SendFusionTablesAsyncTask extends AbstractSendAsyncTask {
// See http://support.google.com/fusiontables/bin/answer.py?hl=en&answer=185991
private static final String MARKER_TYPE_START = "large_green";
private static final String MARKER_TYPE_END = "large_red";
private static final String MARKER_TYPE_WAYPOINT = "large_yellow";
private static final String MARKER_TYPE_WAYPOINT = "large_blue";
private static final String MARKER_TYPE_STATISTICS = "large_yellow";
private static final String TAG = SendFusionTablesAsyncTask.class.getSimpleName();
private final Context context;
@@ -336,8 +337,9 @@ public class SendFusionTablesAsyncTask extends AbstractSendAsyncTask {
// track).
while (cursor.moveToNext()) {
Waypoint wpt = myTracksProviderUtils.createWaypoint(cursor);
if (!createNewPoint(
wpt.getName(), wpt.getDescription(), wpt.getLocation(), MARKER_TYPE_WAYPOINT)) {
String type = wpt.getType() == Waypoint.TYPE_STATISTICS ? MARKER_TYPE_STATISTICS
: MARKER_TYPE_WAYPOINT;
if (!createNewPoint(wpt.getName(), wpt.getDescription(), wpt.getLocation(), type)) {
Log.d(TAG, "Upload waypoints failed");
return false;
}
@@ -44,7 +44,8 @@ import android.widget.TextView;
public class UploadResultActivity extends FragmentActivity {
private static final String TAG = UploadResultActivity.class.getSimpleName();
private static final int DIALOG_RESULT_ID = 0;
@VisibleForTesting
static final int DIALOG_RESULT_ID = 0;
private SendRequest sendRequest;
private String shareUrl;
@@ -359,7 +359,7 @@ public class TrackRecordingService extends Service {
unregisterLocationListener();
shutdownTaskExecutors();
if (sensorManager != null) {
SensorManagerFactory.getInstance().releaseSensorManager(sensorManager);
SensorManagerFactory.releaseSystemSensorManager();
sensorManager = null;
}
@@ -595,7 +595,7 @@ public class TrackRecordingService extends Service {
length = 0;
showNotification();
registerLocationListener();
sensorManager = SensorManagerFactory.getInstance().getSensorManager(this);
sensorManager = SensorManagerFactory.getSystemSensorManager(this);
// Reset the number of auto-resume retries.
setAutoResumeTrackRetries(0);
@@ -724,7 +724,7 @@ public class TrackRecordingService extends Service {
boolean hasSensorData = sensorManager != null
&& sensorManager.isEnabled()
&& sensorManager.getSensorDataSet() != null
&& sensorManager.isDataValid();
&& sensorManager.isSensorDataSetValid();
// If the user has been stationary for two recording just record the first
// two and ignore the rest. This code will only have an effect if the
@@ -823,7 +823,7 @@ public class TrackRecordingService extends Service {
Location locationToInsert = location;
if (sensorManager != null && sensorManager.isEnabled()) {
SensorDataSet sd = sensorManager.getSensorDataSet();
if (sd != null && sensorManager.isDataValid()) {
if (sd != null && sensorManager.isSensorDataSetValid()) {
locationToInsert = new MyTracksLocation(location, sd);
}
}
@@ -1042,7 +1042,7 @@ public class TrackRecordingService extends Service {
PreferencesUtils.setLong(this, R.string.recording_track_id_key, recordingTrackId);
if (sensorManager != null) {
SensorManagerFactory.getInstance().releaseSensorManager(sensorManager);
SensorManagerFactory.releaseSystemSensorManager();
sensorManager = null;
}
@@ -16,8 +16,8 @@
package com.google.android.apps.mytracks.services.sensors;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.content.Sensor.SensorState;
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
import android.bluetooth.BluetoothAdapter;
@@ -30,382 +30,264 @@ import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;
/**
* This class does all the work for setting up and managing Bluetooth
* connections with other devices. It has a thread that listens for incoming
* connections, a thread for connecting with a device, and a thread for
* performing data transmissions when connected.
*
* Manages bluetooth connection. It has a thread for connecting with a bluetooth
* device and a thread for performing data transmission when connected.
*
* @author Sandor Dornbush
*/
public class BluetoothConnectionManager {
// Unique Bluetooth UUID for My Tracks
public static final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
// My Tracks UUID
public static final UUID MY_TRACKS_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
private MessageParser parser;
// Message types sent to hander
public static final int MESSAGE_DEVICE_NAME = 1;
public static final int MESSAGE_READ = 2;
// Member fields
private final BluetoothAdapter adapter;
// Key for storing the device name
public static final String KEY_DEVICE_NAME = "device_name";
private static final String TAG = BluetoothConnectionManager.class.getSimpleName();
private final BluetoothAdapter bluetoothAdapter;
private final Handler handler;
private final MessageParser messageParser;
private SensorState sensorState;
private ConnectThread connectThread;
private ConnectedThread connectedThread;
private Sensor.SensorState state;
// Message types sent from the BluetoothSenorService Handler
public static final int MESSAGE_STATE_CHANGE = 1;
public static final int MESSAGE_READ = 2;
public static final int MESSAGE_WRITE = 3;
public static final int MESSAGE_DEVICE_NAME = 4;
// Key names received from the BluetoothSenorService Handler
public static final String DEVICE_NAME = "device_name";
/**
* Constructor. Prepares a new BluetoothSensor session.
*
* @param handler A Handler to send messages back to the UI Activity
* @param parser A message parser
* Constructor.
*
* @param bluetoothAdapter the bluetooth adapter
* @param handler a hander for sending messages back to the UI activity
* @param messageParser a message parser
*/
public BluetoothConnectionManager(Handler handler, MessageParser parser) {
this.adapter = BluetoothAdapter.getDefaultAdapter();
this.state = Sensor.SensorState.NONE;
public BluetoothConnectionManager(
BluetoothAdapter bluetoothAdapter, Handler handler, MessageParser messageParser) {
this.bluetoothAdapter = bluetoothAdapter;
this.handler = handler;
this.parser = parser;
this.messageParser = messageParser;
this.sensorState = SensorState.NONE;
}
/**
* Set the current state of the sensor connection
*
* @param state An integer defining the current connection state
* Gets the sensor state.
*/
private synchronized void setState(Sensor.SensorState state) {
// TODO pretty print this.
Log.d(Constants.TAG, "setState(" + state + ")");
this.state = state;
// Give the new state to the Handler so the UI Activity can update
handler.obtainMessage(MESSAGE_STATE_CHANGE, state.getNumber(), -1).sendToTarget();
public synchronized SensorState getSensorState() {
return sensorState;
}
/**
* Return the current connection state.
* Sets the sensor state.
*
* @param sensorState the sensor state
*/
public synchronized Sensor.SensorState getState() {
return state;
private synchronized void setState(Sensor.SensorState sensorState) {
this.sensorState = sensorState;
}
/**
* Start the sensor service. Specifically start AcceptThread to begin a session
* in listening (server) mode. Called by the Activity onResume()
* Resets the bluetooth connection manager.
*/
public synchronized void start() {
Log.d(Constants.TAG, "BluetoothConnectionManager.start()");
// Cancel any thread attempting to make a connection
if (connectThread != null) {
connectThread.cancel();
connectThread = null;
}
// Cancel any thread currently running a connection
if (connectedThread != null) {
connectedThread.cancel();
connectedThread = null;
}
public synchronized void reset() {
cancelThreads();
setState(Sensor.SensorState.NONE);
}
/**
* Start the ConnectThread to initiate a connection to a remote device.
*
* @param device The BluetoothDevice to connect
* Cancels all the threads.
*/
public synchronized void connect(BluetoothDevice device) {
Log.d(Constants.TAG, "connect to: " + device);
// Cancel any thread attempting to make a connection
if (state == Sensor.SensorState.CONNECTING) {
if (connectThread != null) {
connectThread.cancel();
connectThread = null;
}
private void cancelThreads() {
if (connectThread != null) {
connectThread.cancel();
connectThread = null;
}
// Cancel any thread currently running a connection
if (connectedThread != null) {
connectedThread.cancel();
connectedThread = null;
}
}
// Start the thread to connect with the given device
connectThread = new ConnectThread(device);
/**
* Connects to a bluetooth device.
*
* @param bluetoothDevice the bluetooth device
*/
public synchronized void connect(BluetoothDevice bluetoothDevice) {
Log.d(TAG, "connect to: " + bluetoothDevice);
cancelThreads();
connectThread = new ConnectThread(bluetoothDevice);
connectThread.start();
setState(Sensor.SensorState.CONNECTING);
}
/**
* Start the ConnectedThread to begin managing a Bluetooth connection
*
* @param socket The BluetoothSocket on which the connection was made
* @param device The BluetoothDevice that has been connected
* Starts the ConnectedThread to read data.
*
* @param bluetoothSocket the bluetooth socket
* @param bluetoothDevice the bluetooth device
*/
public synchronized void connected(BluetoothSocket socket,
BluetoothDevice device) {
Log.d(Constants.TAG, "connected");
private synchronized void connected(
BluetoothSocket bluetoothSocket, BluetoothDevice bluetoothDevice) {
cancelThreads();
// Cancel the thread that completed the connection
if (connectThread != null) {
connectThread.cancel();
connectThread = null;
}
// Cancel any thread currently running a connection
if (connectedThread != null) {
connectedThread.cancel();
connectedThread = null;
}
// Start the thread to manage the connection and perform transmissions
connectedThread = new ConnectedThread(socket);
connectedThread = new ConnectedThread(bluetoothSocket);
connectedThread.start();
// Send the name of the connected device back to the UI Activity
Message msg = handler.obtainMessage(MESSAGE_DEVICE_NAME);
// Send the device name to the handler
Message message = handler.obtainMessage(MESSAGE_DEVICE_NAME);
Bundle bundle = new Bundle();
bundle.putString(DEVICE_NAME, device.getName());
msg.setData(bundle);
handler.sendMessage(msg);
bundle.putString(KEY_DEVICE_NAME, bluetoothDevice.getName());
message.setData(bundle);
handler.sendMessage(message);
setState(Sensor.SensorState.CONNECTED);
}
/**
* Stop all threads
*/
public synchronized void stop() {
Log.d(Constants.TAG, "stop()");
if (connectThread != null) {
connectThread.cancel();
connectThread = null;
}
if (connectedThread != null) {
connectedThread.cancel();
connectedThread = null;
}
setState(Sensor.SensorState.NONE);
}
/**
* Write to the ConnectedThread in an unsynchronized manner
*
* @param out The bytes to write
* @see ConnectedThread#write(byte[])
*/
public void write(byte[] out) {
// Create temporary object
ConnectedThread r;
// Synchronize a copy of the ConnectedThread
synchronized (this) {
if (state != Sensor.SensorState.CONNECTED) {
return;
}
r = connectedThread;
}
// Perform the write unsynchronized
r.write(out);
}
/**
* Indicate that the connection attempt failed and notify the UI Activity.
*/
private void connectionFailed() {
setState(Sensor.SensorState.DISCONNECTED);
Log.i(Constants.TAG, "Bluetooth connection failed.");
}
/**
* Indicate that the connection was lost and notify the UI Activity.
*/
private void connectionLost() {
setState(Sensor.SensorState.DISCONNECTED);
Log.i(Constants.TAG, "Bluetooth connection lost.");
}
/**
* This thread runs while attempting to make an outgoing connection with a
* device. It runs straight through; the connection either succeeds or fails.
* A thread to connect to a bluetooth device.
*/
private class ConnectThread extends Thread {
private final BluetoothSocket socket;
private final BluetoothDevice device;
private final BluetoothSocket bluetoothSocket;
private final BluetoothDevice bluetoothDevice;
public ConnectThread(BluetoothDevice device) {
setName("ConnectThread-" + device.getName());
this.device = device;
this.bluetoothDevice = device;
BluetoothSocket tmp = null;
// Get a BluetoothSocket for a connection with the
// given BluetoothDevice
try {
tmp = ApiAdapterFactory.getApiAdapter().getBluetoothSocket(device);
} catch (IOException e) {
Log.e(Constants.TAG, "create() failed", e);
Log.e(TAG, "Unable to get blueooth socket.", e);
}
socket = tmp;
bluetoothSocket = tmp;
}
@Override
public void run() {
Log.d(Constants.TAG, "BEGIN mConnectThread");
if (bluetoothAdapter == null) {
BluetoothConnectionManager.this.reset();
return;
}
// Cancel discovery to prevent slow down
bluetoothAdapter.cancelDiscovery();
// Always cancel discovery because it will slow down a connection
adapter.cancelDiscovery();
// Make a connection to the BluetoothSocket
try {
// This is a blocking call and will only return on a
// successful connection or an exception
socket.connect();
} catch (IOException e) {
connectionFailed();
// Close the socket
bluetoothSocket.connect();
} catch (IOException connectException) {
Log.i(TAG, "Unable to connect.", connectException);
setState(Sensor.SensorState.DISCONNECTED);
try {
socket.close();
} catch (IOException e2) {
Log.e(Constants.TAG,
"unable to close() socket during connection failure", e2);
bluetoothSocket.close();
} catch (IOException e) {
Log.e(TAG, "Unable to close blueooth socket.", e);
}
// Start the service over to restart listening mode
BluetoothConnectionManager.this.start();
// Reset the bluetooth connection manager
BluetoothConnectionManager.this.reset();
return;
}
// Reset the ConnectThread because we're done
// Reset the ConnectThread since we are done
synchronized (BluetoothConnectionManager.this) {
connectThread = null;
}
// Start the connected thread
connected(socket, device);
connected(bluetoothSocket, bluetoothDevice);
}
/**
* Cancels this thread.
*/
public void cancel() {
try {
socket.close();
bluetoothSocket.close();
} catch (IOException e) {
Log.e(Constants.TAG, "close() of connect socket failed", e);
Log.e(TAG, "Unable to close bluetooth socket.", e);
}
}
}
/**
* This thread runs during a connection with a remote device. It handles all
* incoming and outgoing transmissions.
* This thread handles data transmission when connected.
*/
private class ConnectedThread extends Thread {
private final BluetoothSocket btSocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;
private final BluetoothSocket bluetoothSSocket;
private final InputStream inputStream;
public ConnectedThread(BluetoothSocket socket) {
Log.d(Constants.TAG, "create ConnectedThread");
btSocket = socket;
InputStream tmpIn = null;
OutputStream tmpOut = null;
public ConnectedThread(BluetoothSocket bluetoothSocket) {
this.bluetoothSSocket = bluetoothSocket;
InputStream tmp = null;
// Get the BluetoothSocket input and output streams
try {
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
tmp = bluetoothSocket.getInputStream();
} catch (IOException e) {
Log.e(Constants.TAG, "temp sockets not created", e);
Log.e(TAG, "Unable to get input stream.", e);
}
mmInStream = tmpIn;
mmOutStream = tmpOut;
inputStream = tmp;
}
@Override
public void run() {
Log.i(Constants.TAG, "BEGIN mConnectedThread");
byte[] buffer = new byte[parser.getFrameSize()];
int bytes;
byte[] buffer = new byte[messageParser.getFrameSize()];
int bytes; // bytes read
int offset = 0;
// Keep listening to the InputStream while connected
// Keep listening to the inputStream while connected
while (true) {
try {
// Read from the InputStream
bytes = mmInStream.read(buffer, offset, parser.getFrameSize() - offset);
// Read from the inputStream
bytes = inputStream.read(buffer, offset, messageParser.getFrameSize() - offset);
if (bytes < 0) {
throw new IOException("EOF reached");
}
if (bytes == -1) { throw new IOException("EOF reached."); }
offset += bytes;
if (offset != parser.getFrameSize()) {
// partial frame received, call read() again to receive the rest
if (offset != messageParser.getFrameSize()) {
// Partial frame received. Call read again to receive the rest.
continue;
}
// check if its a valid frame
if (!parser.isValid(buffer)) {
int index = parser.findNextAlignment(buffer);
if (index > 0) {
// re-align
offset = parser.getFrameSize() - index;
System.arraycopy(buffer, index, buffer, 0, offset);
Log.w(Constants.TAG, "Misaligned data, found new message at " +
index + " recovering...");
if (!messageParser.isValid(buffer)) {
int index = messageParser.findNextAlignment(buffer);
if (index == -1) {
Log.w(TAG, "Could not find any valid data. Drop data.");
offset = 0;
continue;
}
Log.w(Constants.TAG, "Could not find valid data, dropping data");
offset = 0;
Log.w(TAG, "Misaligned data. Found new message at " + index + ". Recovering...");
offset = messageParser.getFrameSize() - index;
System.arraycopy(buffer, index, buffer, 0, offset);
continue;
}
offset = 0;
// Send copy of the obtained bytes to the UI Activity.
// Avoids memory inconsistency issues.
handler.obtainMessage(MESSAGE_READ, bytes, -1, buffer.clone())
.sendToTarget();
// Send a copy of the obtained bytes to the handler to avoid memory
// inconsistency issues
handler.obtainMessage(MESSAGE_READ, bytes, -1, buffer.clone()).sendToTarget();
} catch (IOException e) {
Log.e(Constants.TAG, "disconnected", e);
connectionLost();
Log.i(TAG, "Bluetooth connection lost.", e);
setState(Sensor.SensorState.DISCONNECTED);
break;
}
}
}
/**
* Write to the connected OutStream.
*
* @param buffer The bytes to write
* Cancels this thread.
*/
public void write(byte[] buffer) {
try {
mmOutStream.write(buffer);
// Share the sent message back to the UI Activity
handler.obtainMessage(MESSAGE_WRITE, -1, -1, buffer).sendToTarget();
} catch (IOException e) {
Log.e(Constants.TAG, "Exception during write", e);
}
}
public void cancel() {
try {
btSocket.close();
bluetoothSSocket.close();
} catch (IOException e) {
Log.e(Constants.TAG, "close() of connect socket failed", e);
Log.e(TAG, "Unable to close bluetooth socket.", e);
}
}
}
@@ -13,12 +13,11 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.services.sensors;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.content.Sensor.SensorState;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.maps.mytracks.R;
@@ -34,66 +33,31 @@ import android.widget.Toast;
import java.util.ArrayList;
/**
* Manage the connection to a bluetooth sensor.
*
* Bluetooth sensor manager.
*
* @author Sandor Dornbush
*/
public class BluetoothSensorManager extends SensorManager {
// Local Bluetooth adapter
private static final BluetoothAdapter bluetoothAdapter = getDefaultBluetoothAdapter();
// Member object for the sensor threads and connections.
private BluetoothConnectionManager connectionManager = null;
// Name of the connected device
private String connectedDeviceName = null;
private Context context = null;
private Sensor.SensorDataSet sensorDataSet = null;
private MessageParser parser;
public BluetoothSensorManager(
Context context, MessageParser parser) {
this.context = context;
this.parser = parser;
// If BT is not available or not enabled quit.
if (!isEnabled()) {
return;
}
setupSensor();
}
private void setupSensor() {
Log.d(Constants.TAG, "setupSensor()");
// Initialize the BluetoothSensorAdapter to perform bluetooth connections.
connectionManager = new BluetoothConnectionManager(messageHandler, parser);
}
private static final String TAG = BluetoothConnectionManager.class.getSimpleName();
/**
* Code for assigning the local bluetooth adapter
*
* @return The default bluetooth adapter, if one is available, NULL if it isn't.
* Gets the default bluetooth adapter.
*/
private static BluetoothAdapter getDefaultBluetoothAdapter() {
// Check if the calling thread is the main application thread,
// if it is, do it directly.
// If from the main application thread, return directly
if (Thread.currentThread().equals(Looper.getMainLooper().getThread())) {
return BluetoothAdapter.getDefaultAdapter();
}
// If the calling thread, isn't the main application thread,
// then get the main application thread to return the default adapter.
// Get the default adapter from the main application thread
final ArrayList<BluetoothAdapter> adapters = new ArrayList<BluetoothAdapter>(1);
final Object mutex = new Object();
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
@Override
public void run() {
adapters.add(BluetoothAdapter.getDefaultAdapter());
synchronized (mutex) {
@@ -101,7 +65,7 @@ public class BluetoothSensorManager extends SensorManager {
}
}
});
while (adapters.isEmpty()) {
synchronized (mutex) {
try {
@@ -111,106 +75,104 @@ public class BluetoothSensorManager extends SensorManager {
}
}
}
if (adapters.get(0) == null) {
Log.w(TAG, "No bluetooth adapter found!");
Log.w(TAG, "No bluetooth adapter found.");
return null;
}
return adapters.get(0);
}
private final Context context;
private final MessageParser messageParser;
private final BluetoothConnectionManager bluetoothConnectionManager;
private SensorDataSet sensorDataSet = null;
// Handler that gets information back from the bluetoothConnectionManager
private final Handler messageHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message message) {
switch (message.what) {
case BluetoothConnectionManager.MESSAGE_DEVICE_NAME:
String deviceName = message.getData()
.getString(BluetoothConnectionManager.KEY_DEVICE_NAME);
Toast.makeText(context,
context.getString(R.string.settings_sensor_bluetooth_connected, deviceName),
Toast.LENGTH_SHORT).show();
break;
case BluetoothConnectionManager.MESSAGE_READ:
try {
byte[] readBuf = (byte[]) message.obj;
sensorDataSet = messageParser.parseBuffer(readBuf);
Log.d(TAG, "MESSAGE_READ: " + sensorDataSet);
} catch (IllegalArgumentException e) {
sensorDataSet = null;
Log.i(TAG, "Unexpected exception on read", e);
} catch (RuntimeException e) {
sensorDataSet = null;
Log.i(TAG, "Unexpected exception on read.", e);
}
break;
default:
break;
}
}
};
/**
* Constructor.
*
* @param context the context
* @param messageParser the message parser
*/
public BluetoothSensorManager(Context context, MessageParser messageParser) {
this.context = context;
this.messageParser = messageParser;
bluetoothConnectionManager = new BluetoothConnectionManager(
bluetoothAdapter, messageHandler, messageParser);
}
@Override
public boolean isEnabled() {
return bluetoothAdapter != null && bluetoothAdapter.isEnabled();
}
public void setupChannel() {
if (!isEnabled() || connectionManager == null) {
Log.w(Constants.TAG, "Disabled manager onStartTrack");
@Override
protected void setUpChannel() {
if (!isEnabled()) {
Log.w(TAG, "Bluetooth not enabled.");
return;
}
String address = PreferencesUtils.getString(
context, R.string.bluetooth_sensor_key, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT);
if (PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT.equals(address)) {
Log.w(TAG, "No blueooth address.");
return;
}
Log.w(Constants.TAG, "Connecting to bluetooth sensor: " + address);
// Get the BluetoothDevice object
Log.w(TAG, "Connecting to bluetooth address: " + address);
BluetoothDevice device;
try {
device = bluetoothAdapter.getRemoteDevice(address);
} catch (IllegalArgumentException e) {
Log.d(Constants.TAG, "Invalid address " + address, e);
Log.d(TAG, "Unable to get remote device for: " + address, e);
return;
}
// Attempt to connect to the device
connectionManager.connect(device);
// Performing this check in onResume() covers the case in which BT was
// not enabled during onStart(), so we were paused to enable it...
// onResume() will be called when ACTION_REQUEST_ENABLE activity returns.
if (connectionManager != null) {
// Only if the state is STATE_NONE, do we know that we haven't started
// already
if (connectionManager.getState() == Sensor.SensorState.NONE) {
// Start the Bluetooth sensor services
Log.w(Constants.TAG, "Disabled manager onStartTrack");
connectionManager.start();
}
}
bluetoothConnectionManager.connect(device);
}
public void onDestroy() {
// Stop the Bluetooth sensor services
if (connectionManager != null) {
connectionManager.stop();
}
@Override
protected void tearDownChannel() {
bluetoothConnectionManager.reset();
}
public Sensor.SensorDataSet getSensorDataSet() {
@Override
public SensorState getSensorState() {
return bluetoothConnectionManager.getSensorState();
}
@Override
public SensorDataSet getSensorDataSet() {
return sensorDataSet;
}
public Sensor.SensorState getSensorState() {
return (connectionManager == null)
? Sensor.SensorState.NONE
: connectionManager.getState();
}
// The Handler that gets information back from the BluetoothSensorService
private final Handler messageHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case BluetoothConnectionManager.MESSAGE_STATE_CHANGE:
// TODO should we update the SensorManager state var?
Log.i(Constants.TAG, "MESSAGE_STATE_CHANGE: " + msg.arg1);
break;
case BluetoothConnectionManager.MESSAGE_WRITE:
break;
case BluetoothConnectionManager.MESSAGE_READ:
byte[] readBuf = null;
try {
readBuf = (byte[]) msg.obj;
sensorDataSet = parser.parseBuffer(readBuf);
Log.d(Constants.TAG, "MESSAGE_READ: " + sensorDataSet.toString());
} catch (IllegalArgumentException iae) {
sensorDataSet = null;
Log.i(Constants.TAG,
"Got bad sensor data: " + new String(readBuf, 0, readBuf.length),
iae);
} catch (RuntimeException re) {
sensorDataSet = null;
Log.i(Constants.TAG, "Unexpected exception on read.", re);
}
break;
case BluetoothConnectionManager.MESSAGE_DEVICE_NAME:
// save the connected device's name
connectedDeviceName =
msg.getData().getString(BluetoothConnectionManager.DEVICE_NAME);
Toast.makeText(context.getApplicationContext(),
"Connected to " + connectedDeviceName, Toast.LENGTH_SHORT)
.show();
break;
}
}
};
}
@@ -13,14 +13,16 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.services.sensors;
import android.content.Context;
/**
* PolarSensorManager - A sensor manager for Polar heart rate monitors.
* A Polar heart rate sensor manager.
*
* @author Jimmy Shih
*/
public class PolarSensorManager extends BluetoothSensorManager {
public PolarSensorManager(Context context) {
super(context, new PolarMessageParser());
@@ -13,126 +13,118 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.services.sensors;
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.content.Sensor.SensorState;
import android.util.Log;
import java.util.Timer;
import java.util.TimerTask;
import android.util.Log;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.content.Sensor;
/**
* Manage the connection to a sensor.
*
*
* @author Sandor Dornbush
*/
public abstract class SensorManager {
/**
* The maximum age where the data is considered valid.
*/
public static final long MAX_AGE = 5000;
private static final String TAG = SensorManager.class.getSimpleName();
private static final long MAX_SENSOR_DATE_SET_AGE = 5000;
private static final long MAX_SENSOR_STATE_AGE = 30000;
private static final int RETRY_PERIOD = 30000;
private SensorState sensorState = SensorState.NONE;
private long sensorStateTimestamp = System.currentTimeMillis();
/**
* Time to wait after a time out to retry.
* A time task to check sensor connection.
*/
public static final int RETRY_PERIOD = 30000;
private Sensor.SensorState sensorState = Sensor.SensorState.NONE;
private long sensorStateTimestamp = 0;
/**
* A task to run periodically to check to see if connection was lost.
*/
private TimerTask checkSensorManager = new TimerTask() {
@Override
private TimerTask checkSensorConnectionTimeTask = new TimerTask() {
@Override
public void run() {
Log.i(Constants.TAG,
"SensorManager state: " + getSensorState());
switch (getSensorState()) {
case CONNECTING:
long age = System.currentTimeMillis() - getSensorStateTimestamp();
if (age > 2 * RETRY_PERIOD) {
Log.i(Constants.TAG, "Retrying connecting SensorManager.");
setupChannel();
if (System.currentTimeMillis() - sensorStateTimestamp > MAX_SENSOR_STATE_AGE) {
Log.i(TAG, "Retry setUpChannel");
setUpChannel();
}
break;
case NONE:
case DISCONNECTED:
Log.i(Constants.TAG,
"Re-registering disconnected SensoManager.");
setupChannel();
setUpChannel();
break;
default:
break;
}
}
};
/**
* This timer invokes periodically the checkLocationListener timer task.
*/
private final Timer timer = new Timer();
/**
* Is the sensor that this manages enabled.
* @return true if the sensor is enabled
* Returns true if the sensor is enabled.
*/
public abstract boolean isEnabled();
/**
* This is called when my tracks starts recording a new track.
* This is the place to open connections to the sensor.
* Sets up the sensor channel.
*/
public void onStartTrack() {
setupChannel();
timer.schedule(checkSensorManager, RETRY_PERIOD, RETRY_PERIOD);
protected abstract void setUpChannel();
/**
* Tears down the sensor channel.
*/
protected abstract void tearDownChannel();
/**
* Gets the sensor data set.
*/
public abstract SensorDataSet getSensorDataSet();
/**
* Starts the sensor.
*/
public void startSensor() {
setUpChannel();
timer.schedule(checkSensorConnectionTimeTask, RETRY_PERIOD, RETRY_PERIOD);
}
/**
* This method is used to set up any necessary connections to underlying
* sensor hardware.
* Stops the sensor.
*/
protected abstract void setupChannel();
public void shutdown() {
public void stopSensor() {
timer.cancel();
onDestroy();
tearDownChannel();
}
/**
* This is called when my tracks stops recording.
* This is the place to shutdown any open connections.
* Sets the sensor state.
*
* @param sensorState the sensor state
*/
public abstract void onDestroy();
/**
* Return the last sensor reading.
* @return The last reading from the sensor.
*/
public abstract Sensor.SensorDataSet getSensorDataSet();
public void setSensorState(Sensor.SensorState sensorState) {
public void setSensorState(SensorState sensorState) {
sensorStateTimestamp = System.currentTimeMillis();
this.sensorState = sensorState;
}
/**
* Return the current sensor state.
* @return The current sensor state.
* Gets the sensor state.
*/
public Sensor.SensorState getSensorState() {
public SensorState getSensorState() {
return sensorState;
}
public long getSensorStateTimestamp() {
return sensorStateTimestamp;
}
/**
* @return True if the data is recent enough to be considered valid.
* Returns true if the sensor data set is valid.
*/
public boolean isDataValid() {
return (System.currentTimeMillis() - getSensorDataSet().getCreationTime()) < MAX_AGE;
public boolean isSensorDataSetValid() {
SensorDataSet sensorDataSet = getSensorDataSet();
if (sensorDataSet == null) {
return false;
}
return (System.currentTimeMillis() - sensorDataSet.getCreationTime()) < MAX_SENSOR_DATE_SET_AGE;
}
}
@@ -13,101 +13,99 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.services.sensors;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.services.sensors.ant.AntDirectSensorManager;
import com.google.android.apps.mytracks.services.sensors.ant.AntSrmBridgeSensorManager;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.maps.mytracks.R;
import android.content.Context;
import android.util.Log;
/**
* A factory of SensorManagers.
* A factory of {@link SensorManager}.
*
* @author Sandor Dornbush
*/
public class SensorManagerFactory {
private String activeSensorType;
private SensorManager activeSensorManager;
private int refCount;
private static SensorManager systemSensorManager = null;
private static SensorManager tempSensorManager = null;
private static SensorManagerFactory instance = new SensorManagerFactory();
private SensorManagerFactory() {}
private SensorManagerFactory() {
/**
* Gets the system sensor manager.
*
* @param context the context
*/
public static SensorManager getSystemSensorManager(Context context) {
releaseTempSensorManager();
releaseSystemSensorManager();
systemSensorManager = getSensorManager(context);
if (systemSensorManager != null) {
systemSensorManager.startSensor();
}
return systemSensorManager;
}
/**
* Get the factory instance.
* Releases the system sensor manager.
*/
public static SensorManagerFactory getInstance() {
return instance;
public static void releaseSystemSensorManager() {
if (systemSensorManager != null) {
systemSensorManager.stopSensor();
}
systemSensorManager = null;
}
/**
* Get and start a new sensor manager.
* @param context Context to fetch system preferences.
* @return The sensor manager that corresponds to the sensor type setting.
* Gets the temp sensor manager.
*
* @param context
*/
public SensorManager getSensorManager(Context context) {
context = context.getApplicationContext();
public static SensorManager getTempSensorManager(Context context) {
releaseTempSensorManager();
if (systemSensorManager != null) {
return null;
}
tempSensorManager = getSensorManager(context);
if (tempSensorManager != null) {
tempSensorManager.startSensor();
}
return tempSensorManager;
}
/**
* Releases the temp sensor manager.
*/
public static void releaseTempSensorManager() {
if (tempSensorManager != null) {
tempSensorManager.stopSensor();
}
tempSensorManager = null;
}
/**
* Gets the sensor manager.
*
* @param context the context
*/
private static SensorManager getSensorManager(Context context) {
String sensorTypeValueNone = context.getString(R.string.sensor_type_value_none);
String sensorType = PreferencesUtils.getString(
context, R.string.sensor_type_key, sensorTypeValueNone);
Log.i(Constants.TAG, "Creating sensor of type: " + sensorType);
if (sensorTypeValueNone.equals(sensorType)) {
reset();
return null;
}
if (sensorType.equals(activeSensorType)) {
Log.i(Constants.TAG, "Returning existing sensor manager.");
refCount++;
return activeSensorManager;
}
reset();
if (sensorType.equals(context.getString(R.string.sensor_type_value_ant))) {
activeSensorManager = new AntDirectSensorManager(context);
return new AntDirectSensorManager(context);
} else if (sensorType.equals(context.getString(R.string.sensor_type_value_srm_ant_bridge))) {
activeSensorManager = new AntSrmBridgeSensorManager(context);
return new AntSrmBridgeSensorManager(context);
} else if (sensorType.equals(context.getString(R.string.sensor_type_value_zephyr))) {
activeSensorManager = new ZephyrSensorManager(context);
return new ZephyrSensorManager(context);
} else if (sensorType.equals(context.getString(R.string.sensor_type_value_polar))) {
activeSensorManager = new PolarSensorManager(context);
} else {
Log.w(Constants.TAG, "Unable to find sensor type: " + sensorType);
return null;
return new PolarSensorManager(context);
}
activeSensorType = sensorType;
refCount = 1;
activeSensorManager.onStartTrack();
return activeSensorManager;
}
/**
* Finish using a sensor manager.
*/
public void releaseSensorManager(SensorManager sensorManager) {
Log.i(Constants.TAG, "releaseSensorManager: " + activeSensorType + " " + refCount);
if (sensorManager != activeSensorManager) {
Log.e(Constants.TAG, "invalid parameter to releaseSensorManager");
}
if (--refCount > 0) {
return;
}
reset();
}
private void reset() {
activeSensorType = null;
if (activeSensorManager != null) {
activeSensorManager.shutdown();
}
activeSensorManager = null;
refCount = 0;
return null;
}
}
@@ -13,14 +13,19 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.services.sensors;
import android.content.Context;
/**
* A Zephyr heart rate sensor manager.
*
* @author Jimmy Shih
*/
public class ZephyrSensorManager extends BluetoothSensorManager {
public ZephyrSensorManager(Context context) {
super(context, new ZephyrMessageParser());
}
}
@@ -133,7 +133,7 @@ public abstract class AntSensorManager extends SensorManager {
}
@Override
public void onDestroy() {
protected void tearDownChannel() {
Log.i(TAG, "destroying AntSensorManager");
cleanAntInterface();
@@ -161,7 +161,7 @@ public abstract class AntSensorManager extends SensorManager {
* {@link #setupAntSensorChannels}.
*/
@Override
protected final void setupChannel() {
protected void setUpChannel() {
setup();
}
@@ -71,11 +71,9 @@ public class StatsSettingsActivity extends AbstractSettingsActivity {
getString(R.string.report_speed_key));
boolean metric = PreferencesUtils.getBoolean(
this, R.string.metric_units_key, PreferencesUtils.METRIC_UNITS_DEFAULT);
reportSpeedCheckBoxPreference.setSummaryOn(
metric ? getString(R.string.settings_stats_rate_speed_metric)
: getString(R.string.settings_stats_rate_speed_imperial));
reportSpeedCheckBoxPreference.setSummaryOff(
metric ? getString(R.string.settings_stats_rate_pace_metric)
: getString(R.string.settings_stats_rate_pace_imperial));
reportSpeedCheckBoxPreference.setSummaryOn(metric ? getString(R.string.description_speed_metric)
: getString(R.string.description_speed_imperial));
reportSpeedCheckBoxPreference.setSummaryOff(metric ? getString(R.string.description_pace_metric)
: getString(R.string.description_pace_imperial));
}
}
@@ -37,10 +37,10 @@ public class Api10Adapter extends Api9Adapter {
public BluetoothSocket getBluetoothSocket(BluetoothDevice bluetoothDevice) throws IOException {
try {
return bluetoothDevice.createInsecureRfcommSocketToServiceRecord(
BluetoothConnectionManager.SPP_UUID);
BluetoothConnectionManager.MY_TRACKS_UUID);
} catch (IOException e) {
Log.d(Constants.TAG, "Unable to create insecure connection", e);
}
return bluetoothDevice.createRfcommSocketToServiceRecord(BluetoothConnectionManager.SPP_UUID);
return bluetoothDevice.createRfcommSocketToServiceRecord(BluetoothConnectionManager.MY_TRACKS_UUID);
};
}
@@ -104,7 +104,7 @@ public class Api7Adapter implements ApiAdapter {
} catch (InvocationTargetException e) {
Log.d(Constants.TAG, "Unable to create insecure connection", e);
}
return bluetoothDevice.createRfcommSocketToServiceRecord(BluetoothConnectionManager.SPP_UUID);
return bluetoothDevice.createRfcommSocketToServiceRecord(BluetoothConnectionManager.MY_TRACKS_UUID);
}
@Override
@@ -15,36 +15,34 @@
*/
package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.ChartValueSeries.ZoomSettings;
import com.google.android.maps.mytracks.R;
import android.graphics.Paint.Style;
import android.test.AndroidTestCase;
/**
* Tests {@link ChartValueSeries}.
*
* @author Sandor Dornbush
*/
public class ChartValueSeriesTest extends AndroidTestCase {
private ChartValueSeries series;
@Override
protected void setUp() throws Exception {
series = new ChartValueSeries(getContext(),
Integer.MIN_VALUE,
Integer.MAX_VALUE,
new int[] {100, 1000 },
R.string.description_elevation_metric,
R.string.description_elevation_imperial,
R.color.elevation_fill,
R.color.elevation_border,
new ZoomSettings(5, new int[] {100}),
R.string.stats_elevation);
R.color.elevation_border);
}
public void testInitialConditions() {
assertEquals(0, series.getInterval());
assertEquals(1, series.getMaxLabelLength());
assertEquals(0, series.getMin());
assertEquals(0, series.getMax());
assertEquals(0.0, series.getSpread());
assertEquals(Style.STROKE, series.getPaint().getStyle());
assertEquals(getContext().getString(R.string.stats_elevation),
series.getTitle());
assertEquals(1, series.getInterval());
assertEquals(0, series.getMinMarkerValue());
assertEquals(5, series.getMaxMarkerValue());
assertTrue(series.isEnabled());
}
@@ -52,87 +50,31 @@ public class ChartValueSeriesTest extends AndroidTestCase {
series.setEnabled(false);
assertFalse(series.isEnabled());
}
public void testSmallUpdates() {
series.update(0);
series.update(10);
series.updateDimension();
assertEquals(100, series.getInterval());
assertEquals(3, series.getMaxLabelLength());
assertEquals(0, series.getMin());
assertEquals(100, series.getMax());
assertEquals(100.0, series.getSpread());
assertEquals(0, series.getMinMarkerValue());
assertEquals(500, series.getMaxMarkerValue());
}
public void testBigUpdates() {
series.update(0);
series.update(901);
series.updateDimension();
assertEquals(100, series.getInterval());
assertEquals(5, series.getMaxLabelLength());
assertEquals(0, series.getMin());
assertEquals(1000, series.getMax());
assertEquals(1000.0, series.getSpread());
assertEquals(1000, series.getInterval());
assertEquals(0, series.getMinMarkerValue());
assertEquals(5000, series.getMaxMarkerValue());
}
public void testNotZeroBasedUpdates() {
series.update(500);
series.update(1401);
series.update(220);
series.update(250);
series.updateDimension();
assertEquals(100, series.getInterval());
assertEquals(5, series.getMaxLabelLength());
assertEquals(500, series.getMin());
assertEquals(1500, series.getMax());
assertEquals(1000.0, series.getSpread());
}
public void testZoomSettings_invalidArgs() {
try {
new ZoomSettings(0, new int[] {10, 50, 100});
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
// OK.
}
try {
new ZoomSettings(1, null);
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
// OK.
}
try {
new ZoomSettings(1, new int[] {});
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
// OK.
}
try {
new ZoomSettings(1, new int[] {1, 3, 2});
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
// OK.
}
}
public void testZoomSettings_minAligned() {
ZoomSettings settings = new ZoomSettings(5, new int[] {10, 50, 100});
assertEquals(10, settings.calculateInterval(0, 15));
assertEquals(10, settings.calculateInterval(0, 50));
assertEquals(50, settings.calculateInterval(0, 111));
assertEquals(50, settings.calculateInterval(0, 250));
assertEquals(100, settings.calculateInterval(0, 251));
assertEquals(100, settings.calculateInterval(0, 10000));
}
public void testZoomSettings_minNotAligned() {
ZoomSettings settings = new ZoomSettings(5, new int[] {10, 50, 100});
assertEquals(50, settings.calculateInterval(5, 55));
assertEquals(10, settings.calculateInterval(10, 60));
assertEquals(50, settings.calculateInterval(7, 250));
assertEquals(100, settings.calculateInterval(7, 257));
assertEquals(100, settings.calculateInterval(11, 10000));
// A regression test.
settings = new ZoomSettings(5, new int[] {5, 10, 20});
assertEquals(10, settings.calculateInterval(-37.14, -11.89));
assertEquals(200, series.getMinMarkerValue());
assertEquals(700, series.getMaxMarkerValue());
}
}
@@ -52,31 +52,32 @@ public class ChartFragmentTest extends AndroidTestCase {
// No input.
double[] point = fillDataPointTestHelper(myTracksLocation);
assertEquals(Double.NaN, point[3]);
assertEquals(Double.NaN, point[4]);
assertEquals(Double.NaN, point[5]);
assertEquals(Double.NaN, point[ChartView.HEART_RATE_SERIES + 1]);
assertEquals(Double.NaN, point[ChartView.CADENCE_SERIES + 1]);
assertEquals(Double.NaN, point[ChartView.POWER_SERIES + 1]);
// Input incorrect state.
// Creates SensorData.
Sensor.SensorData.Builder powerData = Sensor.SensorData.newBuilder()
.setValue(20).setState(Sensor.SensorState.NONE);
Sensor.SensorData.Builder cadenceData = Sensor.SensorData.newBuilder()
.setValue(20).setState(Sensor.SensorState.NONE);
Sensor.SensorData.Builder heartRateData = Sensor.SensorData.newBuilder()
.setValue(20).setState(Sensor.SensorState.NONE);
.setValue(100).setState(Sensor.SensorState.NONE);
Sensor.SensorData.Builder cadenceData = Sensor.SensorData.newBuilder()
.setValue(101).setState(Sensor.SensorState.NONE);
Sensor.SensorData.Builder powerData = Sensor.SensorData.newBuilder()
.setValue(102).setState(Sensor.SensorState.NONE);
// Creates SensorDataSet.
SensorDataSet sensorDataSet = myTracksLocation.getSensorDataSet();
sensorDataSet = sensorDataSet.toBuilder()
.setPower(powerData)
.setCadence(cadenceData)
.setHeartRate(heartRateData)
.setCadence(cadenceData)
.setPower(powerData)
.build();
myTracksLocation.setSensorData(sensorDataSet);
// Test.
point = fillDataPointTestHelper(myTracksLocation);
assertEquals(Double.NaN, point[3]);
assertEquals(Double.NaN, point[4]);
assertEquals(Double.NaN, point[5]);
assertEquals(Double.NaN, point[ChartView.HEART_RATE_SERIES + 1]);
assertEquals(Double.NaN, point[ChartView.CADENCE_SERIES + 1]);
assertEquals(Double.NaN, point[ChartView.POWER_SERIES + 1]);
}
/**
@@ -87,31 +88,31 @@ public class ChartFragmentTest extends AndroidTestCase {
MyTracksLocation myTracksLocation = TrackStubUtils.createMyTracksLocation();
// No input.
double[] point = fillDataPointTestHelper(myTracksLocation);
assertEquals(Double.NaN, point[3]);
assertEquals(Double.NaN, point[4]);
assertEquals(Double.NaN, point[5]);
assertEquals(Double.NaN, point[ChartView.HEART_RATE_SERIES + 1]);
assertEquals(Double.NaN, point[ChartView.CADENCE_SERIES + 1]);
assertEquals(Double.NaN, point[ChartView.POWER_SERIES + 1]);
// Creates SensorData.
Sensor.SensorData.Builder powerData = Sensor.SensorData.newBuilder()
.setValue(20).setState(Sensor.SensorState.SENDING);
Sensor.SensorData.Builder cadenceData = Sensor.SensorData.newBuilder()
.setValue(20).setState(Sensor.SensorState.SENDING);
Sensor.SensorData.Builder heartRateData = Sensor.SensorData.newBuilder()
.setValue(20).setState(Sensor.SensorState.SENDING);
.setValue(100).setState(Sensor.SensorState.SENDING);
Sensor.SensorData.Builder cadenceData = Sensor.SensorData.newBuilder()
.setValue(101).setState(Sensor.SensorState.SENDING);
Sensor.SensorData.Builder powerData = Sensor.SensorData.newBuilder()
.setValue(102).setState(Sensor.SensorState.SENDING);
// Creates SensorDataSet.
SensorDataSet sensorDataSet = myTracksLocation.getSensorDataSet();
sensorDataSet = sensorDataSet.toBuilder()
.setPower(powerData)
.setCadence(cadenceData)
.setHeartRate(heartRateData)
.setCadence(cadenceData)
.setPower(powerData)
.build();
myTracksLocation.setSensorData(sensorDataSet);
// Test.
point = fillDataPointTestHelper(myTracksLocation);
assertEquals(20.0, point[3]);
assertEquals(20.0, point[4]);
assertEquals(20.0, point[5]);
assertEquals(100.0, point[ChartView.HEART_RATE_SERIES + 1]);
assertEquals(101.0, point[ChartView.CADENCE_SERIES + 1]);
assertEquals(102.0, point[ChartView.POWER_SERIES + 1]);
}
/**
@@ -230,7 +231,7 @@ public class ChartFragmentTest extends AndroidTestCase {
* parameter. Then only one value INITIALLONGTITUDE in buffer.
*/
double[] point = fillDataPointTestHelper(myTracksLocation1);
assertEquals(TrackStubUtils.INITIAL_ALTITUDE, point[1]);
assertEquals(TrackStubUtils.INITIAL_ALTITUDE, point[ChartView.ELEVATION_SERIES + 1]);
/*
* Send another value to buffer, now there are two values, INITIALALTITUDE
@@ -239,8 +240,8 @@ public class ChartFragmentTest extends AndroidTestCase {
MyTracksLocation myTracksLocation2 = TrackStubUtils.createMyTracksLocation();
myTracksLocation2.setAltitude(TrackStubUtils.INITIAL_ALTITUDE * 2);
point = fillDataPointTestHelper(myTracksLocation2);
assertEquals(
(TrackStubUtils.INITIAL_ALTITUDE + TrackStubUtils.INITIAL_ALTITUDE * 2) / 2.0, point[1]);
assertEquals((TrackStubUtils.INITIAL_ALTITUDE + TrackStubUtils.INITIAL_ALTITUDE * 2) / 2.0,
point[ChartView.ELEVATION_SERIES + 1]);
}
/**
@@ -260,7 +261,7 @@ public class ChartFragmentTest extends AndroidTestCase {
MyTracksLocation myTracksLocation1 = TrackStubUtils.createMyTracksLocation();
myTracksLocation1.setSpeed(129);
double[] point = fillDataPointTestHelper(myTracksLocation1);
assertEquals(0.0, point[2]);
assertEquals(0.0, point[ChartView.SPEED_SERIES + 1]);
/*
* Tests the logic when both metricUnits and reportSpeed are true.This
@@ -275,7 +276,7 @@ public class ChartFragmentTest extends AndroidTestCase {
myTracksLocation2.setTime(myTracksLocation1.getTime() + 222);
myTracksLocation2.setSpeed(130);
point = fillDataPointTestHelper(myTracksLocation2);
assertEquals(130.0 * UnitConversions.MS_TO_KMH, point[2]);
assertEquals(130.0 * UnitConversions.MS_TO_KMH, point[ChartView.SPEED_SERIES + 1]);
}
/**
@@ -287,7 +288,8 @@ public class ChartFragmentTest extends AndroidTestCase {
MyTracksLocation myTracksLocation = TrackStubUtils.createMyTracksLocation();
myTracksLocation.setSpeed(132);
double[] point = fillDataPointTestHelper(myTracksLocation);
assertEquals(132.0 * UnitConversions.MS_TO_KMH * UnitConversions.KM_TO_MI, point[2]);
assertEquals(132.0 * UnitConversions.MS_TO_KMH * UnitConversions.KM_TO_MI,
point[ChartView.SPEED_SERIES + 1]);
}
/**
@@ -299,8 +301,8 @@ public class ChartFragmentTest extends AndroidTestCase {
MyTracksLocation myTracksLocation = TrackStubUtils.createMyTracksLocation();
myTracksLocation.setSpeed(134);
double[] point = fillDataPointTestHelper(myTracksLocation);
assertEquals(HOURS_PER_UNIT / (134.0 * UnitConversions.MS_TO_KMH), point[2]);
assertEquals(
HOURS_PER_UNIT / (134.0 * UnitConversions.MS_TO_KMH), point[ChartView.PACE_SERIES + 1]);
}
/**
@@ -313,7 +315,7 @@ public class ChartFragmentTest extends AndroidTestCase {
MyTracksLocation myTracksLocation = TrackStubUtils.createMyTracksLocation();
myTracksLocation.setSpeed(0);
double[] point = fillDataPointTestHelper(myTracksLocation);
assertEquals(0.0, point[2]);
assertEquals(0.0, point[ChartView.PACE_SERIES + 1]);
}
/**
@@ -323,7 +325,7 @@ public class ChartFragmentTest extends AndroidTestCase {
* @return data of this location
*/
private double[] fillDataPointTestHelper(Location location) {
double[] point = new double[6];
double[] point = new double[ChartView.NUM_SERIES + 1];
chartFragment.fillDataPoint(location, point);
return point;
}
@@ -46,6 +46,7 @@ public class UploadResultActivityTest
*/
public void testAllSuccess() {
initialActivity(true, true, true, true, true, true);
uploadResultActivity.showDialog(UploadResultActivity.DIALOG_RESULT_ID);
Dialog dialog = uploadResultActivity.getDialog();
TextView textView = (TextView) dialog.findViewById(R.id.upload_result_success_footer);
assertTrue(textView.isShown());
@@ -57,6 +58,7 @@ public class UploadResultActivityTest
public void testAllFailed() {
// Send all kinds but all failed.
initialActivity(true, true, true, false, false, false);
uploadResultActivity.showDialog(UploadResultActivity.DIALOG_RESULT_ID);
Dialog dialog = uploadResultActivity.getDialog();
TextView textView = (TextView) dialog.findViewById(R.id.upload_result_error_footer);
assertTrue(textView.isShown());
@@ -72,6 +74,7 @@ public class UploadResultActivityTest
*/
public void testPartialSuccess() {
initialActivity(true, false, true, true, false, false);
uploadResultActivity.showDialog(UploadResultActivity.DIALOG_RESULT_ID);
Dialog dialog = uploadResultActivity.getDialog();
TextView textView = (TextView) dialog.findViewById(R.id.upload_result_error_footer);
assertTrue(textView.isShown());
@@ -23,7 +23,7 @@ public class SensorManagerFactoryTest extends AndroidTestCase {
@SmallTest
public void testDefaultSettings() throws Exception {
assertNull(SensorManagerFactory.getInstance().getSensorManager(getContext()));
assertNull(SensorManagerFactory.getSystemSensorManager(getContext()));
}
@SmallTest
@@ -38,9 +38,9 @@ public class SensorManagerFactoryTest extends AndroidTestCase {
private void assertClassForName(Class<?> c, int i) {
PreferencesUtils.setString(getContext(), R.string.sensor_type_key, getContext().getString(i));
SensorManager sm = SensorManagerFactory.getInstance().getSensorManager(getContext());
SensorManager sm = SensorManagerFactory.getSystemSensorManager(getContext());
assertNotNull(sm);
assertTrue(c.isInstance(sm));
SensorManagerFactory.getInstance().releaseSensorManager(sm);
SensorManagerFactory.releaseSystemSensorManager();
}
}